Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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
6#include "physxnode/qphysxtriggerbody_p.h"
7
8#include <QtCore/QPointer>
9#include <QtCore/QVarLengthArray>
10
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
52QTriggerBody::QTriggerBody() = default;
53
54void 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
94void 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
123void 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
139void 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
153void 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
177void 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
231void 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
250int QTriggerBody::collisionCount() const
251{
252 return m_reportedCount;
253}
254
255QAbstractPhysXNode *QTriggerBody::createPhysXBackend()
256{
257 return new QPhysXTriggerBody(this);
258}
259
#define QT_BEGIN_NAMESPACE
#define QT_END_NAMESPACE