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
37/*!
38 \qmlproperty bool PhysicsNode::receiveContactReports
39 This property determines whether this body will receive contact reports when colliding with
40 other bodies. If activated, this means that the bodyContact signal will be emitted on a
41 collision with a body that has sendContactReports set to true.
42*/
43
44/*!
45 \qmlproperty bool PhysicsNode::sendTriggerReports
46 This property determines whether this body will send reports when entering or leaving a trigger
47 body.
48*/
49
50/*!
51 \qmlproperty bool PhysicsNode::receiveTriggerReports
52 This property determines whether this body will receive reports when entering or leaving a
53 trigger body.
54*/
55
56/*!
57 \qmlsignal PhysicsNode::bodyContact(PhysicsNode *body, list<vector3D> positions,
58 list<vector3D> impulses, list<vector3D> normals)
59
60 This signal is emitted when there is a collision between a non-kinematic dynamic body and any
61 other body. The \l {PhysicsNode::} {receiveContactReports} in this body and \l {PhysicsNode::}
62 {sendContactReports} in the colliding body need to be set to true. The parameters \a body, \a
63 positions, \a impulses and \a normals contain the other body, position, impulse force and normal
64 for each contact point at the same index.
65
66 \sa CharacterController::shapeHit
67*/
68
69/*!
70 \qmlsignal PhysicsNode::enteredTriggerBody(TriggerBody *body)
71
72 This signal is emitted when this body enters the specified trigger \a body.
73
74 \note Only emitted when receiveTriggerReports is \c true
75 \sa receiveTriggerReports exitedTriggerBody
76*/
77
78/*!
79 \qmlsignal PhysicsNode::exitedTriggerBody(TriggerBody *body)
80
81 This signal is emitted when this body exits the specified trigger \a body.
82
83 \note Only emitted when receiveTriggerReports is \c true
84 \sa receiveTriggerReports enteredTriggerBody
85*/
86
87QAbstractPhysicsNode::QAbstractPhysicsNode()
88{
89 QPhysicsWorld::registerNode(physicsNode: this);
90}
91
92QAbstractPhysicsNode::~QAbstractPhysicsNode()
93{
94 for (auto shape : std::as_const(t&: m_collisionShapes))
95 shape->disconnect(receiver: this);
96 QPhysicsWorld::deregisterNode(physicsNode: this);
97}
98
99QQmlListProperty<QAbstractCollisionShape> QAbstractPhysicsNode::collisionShapes()
100{
101 return QQmlListProperty<QAbstractCollisionShape>(
102 this, nullptr, QAbstractPhysicsNode::qmlAppendShape,
103 QAbstractPhysicsNode::qmlShapeCount, QAbstractPhysicsNode::qmlShapeAt,
104 QAbstractPhysicsNode::qmlClearShapes);
105}
106
107const QVector<QAbstractCollisionShape *> &QAbstractPhysicsNode::getCollisionShapesList() const
108{
109 return m_collisionShapes;
110}
111
112void QAbstractPhysicsNode::updateFromPhysicsTransform(const physx::PxTransform &transform)
113{
114 const auto pos = transform.p;
115 const auto rotation = transform.q;
116 const auto qtPosition = QVector3D(pos.x, pos.y, pos.z);
117 const auto qtRotation = QQuaternion(rotation.w, rotation.x, rotation.y, rotation.z);
118
119 // Get this nodes parent transform
120 const QQuick3DNode *parentNode = static_cast<QQuick3DNode *>(parentItem());
121
122 if (!parentNode) {
123 // then it is the same space
124 setRotation(qtRotation);
125 setPosition(qtPosition);
126 } else {
127 setPosition(parentNode->mapPositionFromScene(scenePosition: qtPosition));
128 const auto relativeRotation = parentNode->sceneRotation().inverted() * qtRotation;
129 setRotation(relativeRotation);
130 }
131}
132
133bool QAbstractPhysicsNode::sendContactReports() const
134{
135 return m_sendContactReports;
136}
137
138void QAbstractPhysicsNode::setSendContactReports(bool sendContactReports)
139{
140 if (m_sendContactReports == sendContactReports)
141 return;
142
143 m_sendContactReports = sendContactReports;
144 emit sendContactReportsChanged(sendContactReports: m_sendContactReports);
145}
146
147bool QAbstractPhysicsNode::receiveContactReports() const
148{
149 return m_receiveContactReports;
150}
151
152void QAbstractPhysicsNode::setReceiveContactReports(bool receiveContactReports)
153{
154 if (m_receiveContactReports == receiveContactReports)
155 return;
156
157 m_receiveContactReports = receiveContactReports;
158 emit receiveContactReportsChanged(receiveContactReports: m_receiveContactReports);
159}
160
161bool QAbstractPhysicsNode::sendTriggerReports() const
162{
163 return m_sendTriggerReports;
164}
165
166void QAbstractPhysicsNode::setSendTriggerReports(bool sendTriggerReports)
167{
168 if (m_sendTriggerReports == sendTriggerReports)
169 return;
170
171 m_sendTriggerReports = sendTriggerReports;
172 emit sendTriggerReportsChanged(sendTriggerReports: m_sendTriggerReports);
173}
174
175bool QAbstractPhysicsNode::receiveTriggerReports() const
176{
177 return m_receiveTriggerReports;
178}
179
180void QAbstractPhysicsNode::setReceiveTriggerReports(bool receiveTriggerReports)
181{
182 if (m_receiveTriggerReports == receiveTriggerReports)
183 return;
184
185 m_receiveTriggerReports = receiveTriggerReports;
186 emit receiveTriggerReportsChanged(receiveTriggerReports: m_receiveTriggerReports);
187}
188
189void QAbstractPhysicsNode::registerContact(QAbstractPhysicsNode *body,
190 const QVector<QVector3D> &positions,
191 const QVector<QVector3D> &impulses,
192 const QVector<QVector3D> &normals)
193{
194 emit bodyContact(body, positions, impulses, normals);
195}
196
197void QAbstractPhysicsNode::onShapeDestroyed(QObject *object)
198{
199 m_collisionShapes.removeAll(t: static_cast<QAbstractCollisionShape *>(object));
200}
201
202void QAbstractPhysicsNode::onShapeNeedsRebuild(QObject * /*object*/)
203{
204 m_shapesDirty = true;
205}
206
207void QAbstractPhysicsNode::qmlAppendShape(QQmlListProperty<QAbstractCollisionShape> *list,
208 QAbstractCollisionShape *shape)
209{
210 if (shape == nullptr)
211 return;
212 QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object);
213 self->m_collisionShapes.push_back(t: shape);
214 self->m_hasStaticShapes = self->m_hasStaticShapes || shape->isStaticShape();
215
216 if (shape->parentItem() == nullptr) {
217 // If the material has no parent, check if it has a hierarchical parent that's a
218 // QQuick3DObject and re-parent it to that, e.g., inline materials
219 QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(object: shape->parent());
220 if (parentItem) {
221 shape->setParentItem(parentItem);
222 } else { // If no valid parent was found, make sure the material refs our scene manager
223 const auto &scenManager = QQuick3DObjectPrivate::get(item: self)->sceneManager;
224 if (scenManager)
225 QQuick3DObjectPrivate::refSceneManager(obj: shape, mgr&: *scenManager);
226 // else: If there's no scene manager, defer until one is set, see itemChange()
227 }
228 }
229
230 // Make sure materials are removed when destroyed
231 connect(sender: shape, signal: &QAbstractCollisionShape::destroyed, context: self,
232 slot: &QAbstractPhysicsNode::onShapeDestroyed);
233
234 // Connect to rebuild signal
235 connect(sender: shape, signal: &QAbstractCollisionShape::needsRebuild, context: self,
236 slot: &QAbstractPhysicsNode::onShapeNeedsRebuild);
237}
238
239QAbstractCollisionShape *
240QAbstractPhysicsNode::qmlShapeAt(QQmlListProperty<QAbstractCollisionShape> *list, qsizetype index)
241{
242 QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object);
243 return self->m_collisionShapes.at(i: index);
244}
245
246qsizetype QAbstractPhysicsNode::qmlShapeCount(QQmlListProperty<QAbstractCollisionShape> *list)
247{
248 QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object);
249 return self->m_collisionShapes.count();
250}
251
252void QAbstractPhysicsNode::qmlClearShapes(QQmlListProperty<QAbstractCollisionShape> *list)
253{
254 QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object);
255 for (const auto &shape : std::as_const(t&: self->m_collisionShapes)) {
256 if (shape->parentItem() == nullptr)
257 QQuick3DObjectPrivate::get(item: shape)->derefSceneManager();
258 }
259 self->m_hasStaticShapes = false;
260 for (auto shape : std::as_const(t&: self->m_collisionShapes))
261 shape->disconnect(receiver: self);
262 self->m_collisionShapes.clear();
263}
264
265QT_END_NAMESPACE
266

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