Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qtriggerbody.cpp
Go to the documentation of this file.
1
// Copyright (C) 2021 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
// Qt-Security score:significant reason:default
4
5
#
include
"qtriggerbody_p.h"
6
#
include
"physxnode/qphysxtriggerbody_p.h"
7
8
#
include
<
QtCore
/
QPointer
>
9
#
include
<
QtCore
/
QVarLengthArray
>
10
11
QT_BEGIN_NAMESPACE
12
13
/*!
14
\qmltype TriggerBody
15
\inherits PhysicsNode
16
\inqmlmodule QtQuick3D.Physics
17
\since 6.4
18
\brief Reports when objects enter a given volume.
19
20
This type defines a trigger body. A trigger body is a body that does not interact
21
physically but is used to detect when objects intersect with its volume.
22
*/
23
24
/*!
25
\qmlproperty int TriggerBody::collisionCount
26
This property returns the number of bodies currently colliding with the trigger body.
27
28
A body counts once, however many of its collision shapes are inside the trigger body.
29
30
Only a body that had \l {PhysicsNode::sendTriggerReports}{sendTriggerReports} set when it
31
entered is counted, and it goes on counting for as long as it is inside, whatever it sets
32
that property to in the meantime.
33
*/
34
35
/*!
36
\qmlsignal TriggerBody::bodyEntered(PhysicsNode *body)
37
This signal is emitted when the trigger body is penetrated by the specified \a body.
38
39
Emitted once per body, however many of its collision shapes penetrate the trigger body.
40
*/
41
42
/*!
43
\qmlsignal TriggerBody::bodyExited(PhysicsNode *body)
44
This signal is emitted when the trigger body is no longer penetrated by the specified \a body.
45
46
\note This signal is not emitted for a body that is deleted while it is still inside the
47
trigger body, since there is no longer a body to report. \l collisionCount does count such a
48
body out, so a list kept by hand from these signals has to be cleared of deleted bodies
49
another way.
50
*/
51
52
QTriggerBody::QTriggerBody() =
default
;
53
54
void
QTriggerBody::registerCollision(QAbstractPhysicsNode *collision, ShapePair shapes)
55
{
56
// PhysX reports one pair per pair of shapes, so a body with several shapes
57
// inside the trigger, or a trigger with several shapes around one body, is
58
// reported once per pair. Keeping the pairs apart means that one of them no
59
// longer overlapping does not report the whole body as having left, and that
60
// the same pair reported again is the same overlap.
61
const
auto
it = m_collisions.find(collision);
62
if
(it != m_collisions.end()) {
63
// Another pair of a body already inside, or one of its pairs reported
64
// again after a rebuild, which is what says it still overlaps. A body
65
// stays followed for as long as it is inside, whatever it asks for in
66
// the meantime, or the report it is owed on the way out never comes.
67
it->pairs.insert(shapes);
68
it->unreported.remove(shapes);
69
return
;
70
}
71
72
// What a body asks for is read once, as it enters, so that the reports on
73
// the way out are the ones that were made on the way in. One that asks for
74
// neither is followed all the same, or it would be asked again every time
75
// its shapes are rebuilt and could be reported as entering without having
76
// moved.
77
const
bool
sends = collision->sendTriggerReports();
78
const
bool
receives = collision->receiveTriggerReports();
79
80
Overlaps &overlaps = m_collisions[collision];
81
overlaps.pairs.insert(shapes);
82
overlaps.sends = sends;
83
overlaps.receives = receives;
84
if
(sends)
85
++m_reportedCount;
86
87
// Nothing takes a body off this list when it is deleted while still inside,
88
// so the count would keep reporting a body that no longer exists.
89
connect(collision, &QObject::destroyed,
this
, &QTriggerBody::dropDestroyedBody,
90
Qt::UniqueConnection);
91
reportEntered(collision, sends, receives);
92
}
93
94
void
QTriggerBody::deregisterCollision(QAbstractPhysicsNode *collision, ShapePair shapes)
95
{
96
const
auto
it = m_collisions.find(collision);
97
if
(it == m_collisions.end())
98
return
;
99
100
it->pairs.remove(shapes);
101
it->unreported.remove(shapes);
102
if
(!it->pairs.isEmpty())
103
return
;
104
105
const
bool
sends = it->sends;
106
const
bool
receives = it->receives;
107
if
(sends)
108
--m_reportedCount;
109
m_collisions.erase(it);
110
disconnect(collision, &QObject::destroyed,
this
, &QTriggerBody::dropDestroyedBody);
111
reportExited(collision, sends, receives);
112
}
113
114
// The trigger holds every body that asked to hear about one of the two, since
115
// both are about a body being inside it, not about a pair of shapes overlapping
116
117
// Both take what to report by value and hold the body and this trigger weakly,
118
// since the first report can leave the rest with a body that is gone, a trigger
119
// that is gone, and an entry taken out from under a reference into the list.
120
// That is reachable from dropUnreportedOverlaps(), which runs from the frame and
121
// not from a callback holding the world's lock
122
123
void
QTriggerBody::reportEntered(QAbstractPhysicsNode *body,
bool
sends,
bool
receives)
124
{
125
const
QPointer<QTriggerBody> self(
this
);
126
const
QPointer<QAbstractPhysicsNode> weakBody(body);
127
128
if
(sends) {
129
emit bodyEntered(body);
130
// Off the list again is a handler having deleted the body it was just
131
// handed, which counted it out and reported that itself.
132
if
(!self.isNull() && m_collisions.contains(body))
133
emit collisionCountChanged();
134
}
135
if
(receives && !self.isNull() && !weakBody.isNull())
136
emit body->enteredTriggerBody(
this
);
137
}
138
139
void
QTriggerBody::reportExited(QAbstractPhysicsNode *body,
bool
sends,
bool
receives)
140
{
141
const
QPointer<QTriggerBody> self(
this
);
142
const
QPointer<QAbstractPhysicsNode> weakBody(body);
143
144
if
(sends) {
145
emit bodyExited(body);
146
if
(!self.isNull())
147
emit collisionCountChanged();
148
}
149
if
(receives && !self.isNull() && !weakBody.isNull())
150
emit body->exitedTriggerBody(
this
);
151
}
152
153
void
QTriggerBody::invalidateOverlaps(QAbstractPhysicsNode *node)
154
{
155
// Every shape of node has just been replaced by a new one. PhysX does report
156
// the pairs of the shapes it took away, but as pairs of a shape that is
157
// already gone, which cannot be told apart and is ignored. So the pairs of
158
// those shapes are held on to until the next round of reports, which says
159
// which of them the new shapes are in: overlapping shapes are reported as
160
// found again, and what is not reported again is no longer overlapping.
161
//
162
// Holding on to them, rather than dropping them and letting the reports
163
// build the list up again, is what keeps a body inside a trigger from being
164
// reported as leaving and entering again every time its shapes are rebuilt,
165
// which is once per frame if any of them is animated.
166
if
(node ==
this
) {
167
for
(Overlaps &overlaps : m_collisions)
168
overlaps.unreported = overlaps.pairs;
169
return
;
170
}
171
172
const
auto
it = m_collisions.find(node);
173
if
(it != m_collisions.end())
174
it->unreported = it->pairs;
175
}
176
177
void
QTriggerBody::dropUnreportedOverlaps()
178
{
179
struct
PendingExit
180
{
181
QPointer<QAbstractPhysicsNode> body;
182
bool
sends =
false
;
183
bool
receives =
false
;
184
};
185
QVarLengthArray<PendingExit, 4> pending;
186
187
for
(
auto
it = m_collisions.begin(); it != m_collisions.end();) {
188
if
(it->unreported.isEmpty()) {
189
++it;
190
continue
;
191
}
192
193
it->pairs.subtract(it->unreported);
194
it->unreported.clear();
195
196
if
(it->pairs.isEmpty()) {
197
QAbstractPhysicsNode *body = it.key();
198
disconnect(body, &QObject::destroyed,
this
, &QTriggerBody::dropDestroyedBody);
199
if
(it->sends)
200
--m_reportedCount;
201
pending.append({ body, it->sends, it->receives });
202
it = m_collisions.erase(it);
203
}
else
{
204
++it;
205
}
206
}
207
208
// Reported once the list is settled, since a handler is free to register or
209
// deregister a collision on this trigger. A handler is also free to delete
210
// one of the bodies still waiting to be reported, or this trigger: one that
211
// no longer exists is not handed out, the same way a body destroyed while
212
// inside the trigger is not, and a trigger that no longer exists reports
213
// nothing further.
214
//
215
// Several bodies can go at once here, and all of them are out of the list
216
// before any of them is reported, so collisionCount is what it will be for
217
// the whole round rather than counting down through it.
218
const
QPointer<QTriggerBody> self(
this
);
219
220
for
(
const
PendingExit &exit : pending) {
221
if
(self.isNull())
222
return
;
223
224
if
(!exit.body.isNull())
225
reportExited(exit.body.data(), exit.sends, exit.receives);
226
else
if
(exit.sends)
227
emit collisionCountChanged();
228
}
229
}
230
231
void
QTriggerBody::dropDestroyedBody(QObject *body)
232
{
233
// Runs from ~QObject, so only the QObject part of the body is left: it is
234
// compared as a QObject and never passed on. No bodyExited() or
235
// exitedTriggerBody() either, since those would pass a half destroyed body
236
// to a handler, or read from one.
237
for
(
auto
it = m_collisions.cbegin(); it != m_collisions.cend(); ++it) {
238
if
(
static_cast
<QObject *>(it.key()) == body) {
239
const
bool
wasCounted = it->sends;
240
if
(wasCounted)
241
--m_reportedCount;
242
m_collisions.erase(it);
243
if
(wasCounted)
244
emit collisionCountChanged();
245
return
;
246
}
247
}
248
}
249
250
int
QTriggerBody::collisionCount()
const
251
{
252
return
m_reportedCount;
253
}
254
255
QAbstractPhysXNode *QTriggerBody::createPhysXBackend()
256
{
257
return
new
QPhysXTriggerBody(
this
);
258
}
259
260
QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
#define QT_BEGIN_NAMESPACE
Definition
qtconfigmacros.h:161
QT_END_NAMESPACE
#define QT_END_NAMESPACE
Definition
qtconfigmacros.h:162
qtquick3dphysics
src
quick3dphysics
qtriggerbody.cpp
Generated on
for Qt by
1.18.0