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"
9QT_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
119QAbstractPhysicsNode::QAbstractPhysicsNode()
120{
121 QPhysicsWorld::registerNode(physicsNode: this);
122}
123
124QAbstractPhysicsNode::~QAbstractPhysicsNode()
125{
126 for (auto shape : std::as_const(t&: m_collisionShapes))
127 shape->disconnect(receiver: this);
128 QPhysicsWorld::deregisterNode(physicsNode: this);
129}
130
131QQmlListProperty<QAbstractCollisionShape> QAbstractPhysicsNode::collisionShapes()
132{
133 return QQmlListProperty<QAbstractCollisionShape>(
134 this, nullptr, QAbstractPhysicsNode::qmlAppendShape,
135 QAbstractPhysicsNode::qmlShapeCount, QAbstractPhysicsNode::qmlShapeAt,
136 QAbstractPhysicsNode::qmlClearShapes);
137}
138
139const QVector<QAbstractCollisionShape *> &QAbstractPhysicsNode::getCollisionShapesList() const
140{
141 return m_collisionShapes;
142}
143
144void 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
165bool QAbstractPhysicsNode::sendContactReports() const
166{
167 return m_sendContactReports;
168}
169
170void 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
179bool QAbstractPhysicsNode::receiveContactReports() const
180{
181 return m_receiveContactReports;
182}
183
184void 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
193bool QAbstractPhysicsNode::sendTriggerReports() const
194{
195 return m_sendTriggerReports;
196}
197
198void 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
207bool QAbstractPhysicsNode::receiveTriggerReports() const
208{
209 return m_receiveTriggerReports;
210}
211
212void 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
221void 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
229void QAbstractPhysicsNode::onShapeDestroyed(QObject *object)
230{
231 m_collisionShapes.removeAll(t: static_cast<QAbstractCollisionShape *>(object));
232}
233
234void QAbstractPhysicsNode::onShapeNeedsRebuild(QObject * /*object*/)
235{
236 m_shapesDirty = true;
237}
238
239void 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
271QAbstractCollisionShape *
272QAbstractPhysicsNode::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
278qsizetype QAbstractPhysicsNode::qmlShapeCount(QQmlListProperty<QAbstractCollisionShape> *list)
279{
280 QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object);
281 return self->m_collisionShapes.count();
282}
283
284void 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
297int QAbstractPhysicsNode::filterGroup() const
298{
299 return m_filterGroup;
300}
301
302void 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
311int QAbstractPhysicsNode::filterIgnoreGroups() const
312{
313 return m_filterIgnoreGroups;
314}
315
316void 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
325QT_END_NAMESPACE
326

source code of qtquick3dphysics/src/quick3dphysics/qabstractphysicsnode.cpp