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 | |
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 | |
87 | QAbstractPhysicsNode::QAbstractPhysicsNode() |
88 | { |
89 | QPhysicsWorld::registerNode(physicsNode: this); |
90 | } |
91 | |
92 | QAbstractPhysicsNode::~QAbstractPhysicsNode() |
93 | { |
94 | for (auto shape : std::as_const(t&: m_collisionShapes)) |
95 | shape->disconnect(receiver: this); |
96 | QPhysicsWorld::deregisterNode(physicsNode: this); |
97 | } |
98 | |
99 | QQmlListProperty<QAbstractCollisionShape> QAbstractPhysicsNode::collisionShapes() |
100 | { |
101 | return QQmlListProperty<QAbstractCollisionShape>( |
102 | this, nullptr, QAbstractPhysicsNode::qmlAppendShape, |
103 | QAbstractPhysicsNode::qmlShapeCount, QAbstractPhysicsNode::qmlShapeAt, |
104 | QAbstractPhysicsNode::qmlClearShapes); |
105 | } |
106 | |
107 | const QVector<QAbstractCollisionShape *> &QAbstractPhysicsNode::getCollisionShapesList() const |
108 | { |
109 | return m_collisionShapes; |
110 | } |
111 | |
112 | void 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 | |
133 | bool QAbstractPhysicsNode::sendContactReports() const |
134 | { |
135 | return m_sendContactReports; |
136 | } |
137 | |
138 | void 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 | |
147 | bool QAbstractPhysicsNode::receiveContactReports() const |
148 | { |
149 | return m_receiveContactReports; |
150 | } |
151 | |
152 | void 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 | |
161 | bool QAbstractPhysicsNode::sendTriggerReports() const |
162 | { |
163 | return m_sendTriggerReports; |
164 | } |
165 | |
166 | void 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 | |
175 | bool QAbstractPhysicsNode::receiveTriggerReports() const |
176 | { |
177 | return m_receiveTriggerReports; |
178 | } |
179 | |
180 | void 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 | |
189 | void 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 | |
197 | void QAbstractPhysicsNode::onShapeDestroyed(QObject *object) |
198 | { |
199 | m_collisionShapes.removeAll(t: static_cast<QAbstractCollisionShape *>(object)); |
200 | } |
201 | |
202 | void QAbstractPhysicsNode::onShapeNeedsRebuild(QObject * /*object*/) |
203 | { |
204 | m_shapesDirty = true; |
205 | } |
206 | |
207 | void 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 | |
239 | QAbstractCollisionShape * |
240 | QAbstractPhysicsNode::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 | |
246 | qsizetype QAbstractPhysicsNode::qmlShapeCount(QQmlListProperty<QAbstractCollisionShape> *list) |
247 | { |
248 | QAbstractPhysicsNode *self = static_cast<QAbstractPhysicsNode *>(list->object); |
249 | return self->m_collisionShapes.count(); |
250 | } |
251 | |
252 | void 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 | |
265 | QT_END_NAMESPACE |
266 | |