1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qabstractphysicsnode_p.h" |
5 | #include <QtQuick3D/private/qquick3dobject_p.h> |
6 | #include <foundation/PxTransform.h> |
7 | |
8 | #include "qphysicsworld_p.h" |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | /*! |
12 | \qmltype PhysicsNode |
13 | \inherits Node |
14 | \inqmlmodule QtQuick3D.Physics |
15 | \since 6.4 |
16 | \brief Base type for all objects in the physics scene. |
17 | |
18 | PhysicsNode is the base type for all the objects that take part in the physics simulation. These |
19 | objects have a position in three-dimensional space and a geometrical shape. |
20 | */ |
21 | |
22 | /*! |
23 | \qmlproperty list<CollisionShape> PhysicsNode::collisionShapes |
24 | |
25 | This property contains the list of collision shapes. These shapes will be combined and act as a |
26 | single rigid body when interacting with other bodies. |
27 | |
28 | \sa {Qt Quick 3D Physics Shapes and Bodies}{Shapes and Bodies overview documentation} |
29 | */ |
30 | |
31 | /*! |
32 | \qmlproperty bool PhysicsNode::sendContactReports |
33 | This property determines whether this body will send contact reports when colliding with other |
34 | bodies. |
35 | |
36 | Default value: \c{false} |
37 | */ |
38 | |
39 | /*! |
40 | \qmlproperty bool PhysicsNode::receiveContactReports |
41 | This property determines whether this body will receive contact reports when colliding with |
42 | other bodies. If activated, this means that the bodyContact signal will be emitted on a |
43 | collision with a body that has sendContactReports set to true. |
44 | |
45 | Default value: \c{false} |
46 | */ |
47 | |
48 | /*! |
49 | \qmlproperty bool PhysicsNode::sendTriggerReports |
50 | This property determines whether this body will send reports when entering or leaving a trigger |
51 | body. |
52 | |
53 | Default value: \c{false} |
54 | */ |
55 | |
56 | /*! |
57 | \qmlproperty bool PhysicsNode::receiveTriggerReports |
58 | This property determines whether this body will receive reports when entering or leaving a |
59 | trigger body. |
60 | |
61 | Default value: \c{false} |
62 | */ |
63 | |
64 | /*! |
65 | \qmlproperty int PhysicsNode::filterGroup |
66 | This property determines what filter group this body is part of. |
67 | |
68 | Default value is \c 0. |
69 | |
70 | Range: \c{[0, 32]} |
71 | |
72 | \sa PhysicsNode::filterIgnoreGroups |
73 | */ |
74 | |
75 | /*! |
76 | \qmlproperty int PhysicsNode::filterIgnoreGroups |
77 | This property determines what groups this body should filter out collisions with. |
78 | |
79 | \note This number is interpreted as a bitmask, meaning that if bit \c i is set then collisions |
80 | with \l filterGroup number \c i will be filtered. For instance, to filter groups \c{1}, \c{3} |
81 | and \c{4} then set the value to \c{0b11010}. |
82 | |
83 | \sa PhysicsNode::filterGroup |
84 | */ |
85 | |
86 | /*! |
87 | \qmlsignal PhysicsNode::bodyContact(PhysicsNode *body, list<vector3D> positions, |
88 | list<vector3D> impulses, list<vector3D> normals) |
89 | |
90 | This signal is emitted when there is a collision between a dynamic body and any |
91 | other body. The \l {PhysicsNode::} {receiveContactReports} in this body and \l {PhysicsNode::} |
92 | {sendContactReports} in the colliding body need to be set to true. The parameters \a body, \a |
93 | positions, \a impulses and \a normals contain the other body, position, impulse force and normal |
94 | for each contact point at the same index. |
95 | |
96 | \sa CharacterController::shapeHit |
97 | \sa PhysicsWorld::reportKinematicKinematicCollisions |
98 | \sa PhysicsWorld::reportStaticKinematicCollisions |
99 | */ |
100 | |
101 | /*! |
102 | \qmlsignal PhysicsNode::enteredTriggerBody(TriggerBody *body) |
103 | |
104 | This signal is emitted when this body enters the specified trigger \a body. |
105 | |
106 | \note Only emitted when receiveTriggerReports is \c true |
107 | \sa receiveTriggerReports exitedTriggerBody |
108 | */ |
109 | |
110 | /*! |
111 | \qmlsignal PhysicsNode::exitedTriggerBody(TriggerBody *body) |
112 | |
113 | This signal is emitted when this body exits the specified trigger \a body. |
114 | |
115 | \note Only emitted when receiveTriggerReports is \c true |
116 | \sa receiveTriggerReports enteredTriggerBody |
117 | */ |
118 | |
119 | QAbstractPhysicsNode::QAbstractPhysicsNode() |
120 | { |
121 | QPhysicsWorld::registerNode(physicsNode: this); |
122 | } |
123 | |
124 | QAbstractPhysicsNode::~QAbstractPhysicsNode() |
125 | { |
126 | for (auto shape : std::as_const(t&: m_collisionShapes)) |
127 | shape->disconnect(receiver: this); |
128 | QPhysicsWorld::deregisterNode(physicsNode: this); |
129 | } |
130 | |
131 | QQmlListProperty<QAbstractCollisionShape> QAbstractPhysicsNode::collisionShapes() |
132 | { |
133 | return QQmlListProperty<QAbstractCollisionShape>( |
134 | this, nullptr, QAbstractPhysicsNode::qmlAppendShape, |
135 | QAbstractPhysicsNode::qmlShapeCount, QAbstractPhysicsNode::qmlShapeAt, |
136 | QAbstractPhysicsNode::qmlClearShapes); |
137 | } |
138 | |
139 | const QVector<QAbstractCollisionShape *> &QAbstractPhysicsNode::getCollisionShapesList() const |
140 | { |
141 | return m_collisionShapes; |
142 | } |
143 | |
144 | void QAbstractPhysicsNode::updateFromPhysicsTransform(const physx::PxTransform &transform) |
145 | { |
146 | const auto pos = transform.p; |
147 | const auto rotation = transform.q; |
148 | const auto qtPosition = QVector3D(pos.x, pos.y, pos.z); |
149 | const auto qtRotation = QQuaternion(rotation.w, rotation.x, rotation.y, rotation.z); |
150 | |
151 | // Get this nodes parent transform |
152 | const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(parentItem()); |
153 | |
154 | if (!parentNode) { |
155 | // then it is the same space |
156 | setRotation(qtRotation); |
157 | setPosition(qtPosition); |
158 | } else { |
159 | setPosition(parentNode->mapPositionFromScene(scenePosition: qtPosition)); |
160 | const auto relativeRotation = parentNode->sceneRotation().inverted() * qtRotation; |
161 | setRotation(relativeRotation); |
162 | } |
163 | } |
164 | |
165 | bool QAbstractPhysicsNode::sendContactReports() const |
166 | { |
167 | return m_sendContactReports; |
168 | } |
169 | |
170 | void QAbstractPhysicsNode::setSendContactReports(bool sendContactReports) |
171 | { |
172 | if (m_sendContactReports == sendContactReports) |
173 | return; |
174 | |
175 | m_sendContactReports = sendContactReports; |
176 | emit sendContactReportsChanged(sendContactReports: m_sendContactReports); |
177 | } |
178 | |
179 | bool QAbstractPhysicsNode::receiveContactReports() const |
180 | { |
181 | return m_receiveContactReports; |
182 | } |
183 | |
184 | void QAbstractPhysicsNode::setReceiveContactReports(bool receiveContactReports) |
185 | { |
186 | if (m_receiveContactReports == receiveContactReports) |
187 | return; |
188 | |
189 | m_receiveContactReports = receiveContactReports; |
190 | emit receiveContactReportsChanged(receiveContactReports: m_receiveContactReports); |
191 | } |
192 | |
193 | bool QAbstractPhysicsNode::sendTriggerReports() const |
194 | { |
195 | return m_sendTriggerReports; |
196 | } |
197 | |
198 | void QAbstractPhysicsNode::setSendTriggerReports(bool sendTriggerReports) |
199 | { |
200 | if (m_sendTriggerReports == sendTriggerReports) |
201 | return; |
202 | |
203 | m_sendTriggerReports = sendTriggerReports; |
204 | emit sendTriggerReportsChanged(sendTriggerReports: m_sendTriggerReports); |
205 | } |
206 | |
207 | bool QAbstractPhysicsNode::receiveTriggerReports() const |
208 | { |
209 | return m_receiveTriggerReports; |
210 | } |
211 | |
212 | void QAbstractPhysicsNode::setReceiveTriggerReports(bool receiveTriggerReports) |
213 | { |
214 | if (m_receiveTriggerReports == receiveTriggerReports) |
215 | return; |
216 | |
217 | m_receiveTriggerReports = receiveTriggerReports; |
218 | emit receiveTriggerReportsChanged(receiveTriggerReports: m_receiveTriggerReports); |
219 | } |
220 | |
221 | void QAbstractPhysicsNode::registerContact(QAbstractPhysicsNode *body, |
222 | const QVector<QVector3D> &positions, |
223 | const QVector<QVector3D> &impulses, |
224 | const QVector<QVector3D> &normals) |
225 | { |
226 | emit bodyContact(body, positions, impulses, normals); |
227 | } |
228 | |
229 | void QAbstractPhysicsNode::onShapeDestroyed(QObject *object) |
230 | { |
231 | m_collisionShapes.removeAll(t: static_cast<QAbstractCollisionShape *>(object)); |
232 | } |
233 | |
234 | void QAbstractPhysicsNode::onShapeNeedsRebuild(QObject * /*object*/) |
235 | { |
236 | m_shapesDirty = true; |
237 | } |
238 | |
239 | void QAbstractPhysicsNode::qmlAppendShape(QQmlListProperty<QAbstractCollisionShape> *list, |
240 | QAbstractCollisionShape *shape) |
241 | { |
242 | if (shape == nullptr) |
243 | return; |
244 | QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object); |
245 | self->m_collisionShapes.push_back(t: shape); |
246 | self->m_hasStaticShapes = self->m_hasStaticShapes || shape->isStaticShape(); |
247 | |
248 | if (shape->parentItem() == nullptr) { |
249 | // If the material has no parent, check if it has a hierarchical parent that's a |
250 | // QQuick3DObject and re-parent it to that, e.g., inline materials |
251 | QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(object: shape->parent()); |
252 | if (parentItem) { |
253 | shape->setParentItem(parentItem); |
254 | } else { // If no valid parent was found, make sure the material refs our scene manager |
255 | const auto &scenManager = QQuick3DObjectPrivate::get(item: self)->sceneManager; |
256 | if (scenManager) |
257 | QQuick3DObjectPrivate::refSceneManager(obj: shape, mgr&: *scenManager); |
258 | // else: If there's no scene manager, defer until one is set, see itemChange() |
259 | } |
260 | } |
261 | |
262 | // Make sure materials are removed when destroyed |
263 | connect(sender: shape, signal: &QAbstractCollisionShape::destroyed, context: self, |
264 | slot: &QAbstractPhysicsNode::onShapeDestroyed); |
265 | |
266 | // Connect to rebuild signal |
267 | connect(sender: shape, signal: &QAbstractCollisionShape::needsRebuild, context: self, |
268 | slot: &QAbstractPhysicsNode::onShapeNeedsRebuild); |
269 | } |
270 | |
271 | QAbstractCollisionShape * |
272 | QAbstractPhysicsNode::qmlShapeAt(QQmlListProperty<QAbstractCollisionShape> *list, qsizetype index) |
273 | { |
274 | QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object); |
275 | return self->m_collisionShapes.at(i: index); |
276 | } |
277 | |
278 | qsizetype QAbstractPhysicsNode::qmlShapeCount(QQmlListProperty<QAbstractCollisionShape> *list) |
279 | { |
280 | QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object); |
281 | return self->m_collisionShapes.count(); |
282 | } |
283 | |
284 | void QAbstractPhysicsNode::qmlClearShapes(QQmlListProperty<QAbstractCollisionShape> *list) |
285 | { |
286 | QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object); |
287 | for (const auto &shape : std::as_const(t&: self->m_collisionShapes)) { |
288 | if (shape->parentItem() == nullptr) |
289 | QQuick3DObjectPrivate::get(item: shape)->derefSceneManager(); |
290 | } |
291 | self->m_hasStaticShapes = false; |
292 | for (auto shape : std::as_const(t&: self->m_collisionShapes)) |
293 | shape->disconnect(receiver: self); |
294 | self->m_collisionShapes.clear(); |
295 | } |
296 | |
297 | int QAbstractPhysicsNode::filterGroup() const |
298 | { |
299 | return m_filterGroup; |
300 | } |
301 | |
302 | void QAbstractPhysicsNode::setfilterGroup(int newfilterGroup) |
303 | { |
304 | if (m_filterGroup == newfilterGroup) |
305 | return; |
306 | m_filterGroup = newfilterGroup; |
307 | m_filtersDirty = true; |
308 | emit filterGroupChanged(); |
309 | } |
310 | |
311 | int QAbstractPhysicsNode::filterIgnoreGroups() const |
312 | { |
313 | return m_filterIgnoreGroups; |
314 | } |
315 | |
316 | void QAbstractPhysicsNode::setFilterIgnoreGroups(int newFilterIgnoreGroups) |
317 | { |
318 | if (m_filterIgnoreGroups == newFilterIgnoreGroups) |
319 | return; |
320 | m_filterIgnoreGroups = newFilterIgnoreGroups; |
321 | m_filtersDirty = true; |
322 | emit filterIgnoreGroupsChanged(); |
323 | } |
324 | |
325 | QT_END_NAMESPACE |
326 | |