1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qcacheutils_p.h" |
5 | #include "qconvexmeshshape_p.h" |
6 | |
7 | #include <QFile> |
8 | #include <QFileInfo> |
9 | #include <QtQuick3D/QQuick3DGeometry> |
10 | #include <extensions/PxExtensionsAPI.h> |
11 | |
12 | #include "foundation/PxVec3.h" |
13 | #include "cooking/PxConvexMeshDesc.h" |
14 | #include "extensions/PxDefaultStreams.h" |
15 | |
16 | #include <QtQml/qqml.h> |
17 | #include <QtQml/QQmlFile> |
18 | #include <QtQml/qqmlcontext.h> |
19 | |
20 | #include <QtQuick3DUtils/private/qssgmesh_p.h> |
21 | #include "qphysicsworld_p.h" |
22 | #include "qphysicsmeshutils_p_p.h" |
23 | #include "qphysicsutils_p.h" |
24 | |
25 | QT_BEGIN_NAMESPACE |
26 | |
27 | physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh() |
28 | { |
29 | if (m_convexMesh != nullptr) |
30 | return m_convexMesh; |
31 | |
32 | physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); |
33 | if (thePhysics == nullptr) |
34 | return nullptr; |
35 | |
36 | m_convexMesh = QCacheUtils::readCachedConvexMesh(filePath: m_meshPath, physics&: *thePhysics); |
37 | if (m_convexMesh != nullptr) |
38 | return m_convexMesh; |
39 | |
40 | m_convexMesh = QCacheUtils::readCookedConvexMesh(filePath: m_meshPath, physics&: *thePhysics); |
41 | if (m_convexMesh != nullptr) |
42 | return m_convexMesh; |
43 | |
44 | loadSsgMesh(); |
45 | |
46 | if (!m_ssgMesh.isValid()) |
47 | return nullptr; |
48 | |
49 | physx::PxDefaultMemoryOutputStream buf; |
50 | physx::PxConvexMeshCookingResult::Enum result; |
51 | int vStride = m_ssgMesh.vertexBuffer().stride; |
52 | int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; |
53 | const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); |
54 | |
55 | qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts" ; |
56 | |
57 | QVector<physx::PxVec3> verts; |
58 | |
59 | for (int i = 0; i < vCount; ++i) { |
60 | auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset); |
61 | verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() }; |
62 | } |
63 | |
64 | const auto *convexVerts = verts.constData(); |
65 | |
66 | physx::PxConvexMeshDesc convexDesc; |
67 | convexDesc.points.count = vCount; |
68 | convexDesc.points.stride = sizeof(physx::PxVec3); |
69 | convexDesc.points.data = convexVerts; |
70 | convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX; |
71 | |
72 | const auto cooking = QPhysicsWorld::getCooking(); |
73 | if (cooking && cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) { |
74 | auto size = buf.getSize(); |
75 | auto *data = buf.getData(); |
76 | physx::PxDefaultMemoryInputData input(data, size); |
77 | m_convexMesh = thePhysics->createConvexMesh(stream&: input); |
78 | qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this; |
79 | QCacheUtils::writeCachedConvexMesh(filePath: m_meshPath, buf); |
80 | } else { |
81 | qCWarning(lcQuick3dPhysics) << "Could not create convex mesh from" << m_meshPath; |
82 | } |
83 | |
84 | return m_convexMesh; |
85 | } |
86 | |
87 | physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh() |
88 | { |
89 | |
90 | if (m_triangleMesh != nullptr) |
91 | return m_triangleMesh; |
92 | |
93 | physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics(); |
94 | if (thePhysics == nullptr) |
95 | return nullptr; |
96 | |
97 | m_triangleMesh = QCacheUtils::readCachedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics); |
98 | if (m_triangleMesh != nullptr) |
99 | return m_triangleMesh; |
100 | |
101 | m_triangleMesh = QCacheUtils::readCookedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics); |
102 | if (m_triangleMesh != nullptr) |
103 | return m_triangleMesh; |
104 | |
105 | loadSsgMesh(); |
106 | if (!m_ssgMesh.isValid()) |
107 | return nullptr; |
108 | |
109 | physx::PxDefaultMemoryOutputStream buf; |
110 | physx::PxTriangleMeshCookingResult::Enum result; |
111 | const int vStride = m_ssgMesh.vertexBuffer().stride; |
112 | const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride; |
113 | const auto *vd = m_ssgMesh.vertexBuffer().data.constData(); |
114 | |
115 | const int iStride = |
116 | m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 |
117 | ? 2 |
118 | : 4; |
119 | const int iCount = m_ssgMesh.indexBuffer().data.size() / iStride; |
120 | |
121 | qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts" << iCount << "idxs" ; |
122 | |
123 | physx::PxTriangleMeshDesc triangleDesc; |
124 | triangleDesc.points.count = vCount; |
125 | triangleDesc.points.stride = vStride; |
126 | triangleDesc.points.data = vd + m_posOffset; |
127 | |
128 | triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or |
129 | // physx::PxMeshFlag::e16_BIT_INDICES |
130 | triangleDesc.triangles.count = iCount / 3; |
131 | triangleDesc.triangles.stride = iStride * 3; |
132 | triangleDesc.triangles.data = m_ssgMesh.indexBuffer().data.constData(); |
133 | |
134 | const auto cooking = QPhysicsWorld::getCooking(); |
135 | if (cooking && cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) { |
136 | auto size = buf.getSize(); |
137 | auto *data = buf.getData(); |
138 | physx::PxDefaultMemoryInputData input(data, size); |
139 | m_triangleMesh = thePhysics->createTriangleMesh(stream&: input); |
140 | qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh" |
141 | << this; |
142 | QCacheUtils::writeCachedTriangleMesh(filePath: m_meshPath, buf); |
143 | } else { |
144 | qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh from" << m_meshPath; |
145 | } |
146 | |
147 | return m_triangleMesh; |
148 | } |
149 | |
150 | void QQuick3DPhysicsMesh::loadSsgMesh() |
151 | { |
152 | if (m_ssgMesh.isValid()) |
153 | return; |
154 | |
155 | static const char *compTypes[] = { "Null" , "UnsignedInt8" , "Int8" , "UnsignedInt16" , |
156 | "Int16" , "UnsignedInt32" , "Int32" , "UnsignedInt64" , |
157 | "Int64" , "Float16" , "Float32" , "Float64" }; |
158 | |
159 | QFileInfo fileInfo = QFileInfo(m_meshPath); |
160 | if (fileInfo.exists()) { |
161 | QFile file(fileInfo.absoluteFilePath()); |
162 | if (file.open(flags: QFile::ReadOnly)) |
163 | m_ssgMesh = QSSGMesh::Mesh::loadMesh(device: &file); |
164 | } |
165 | qCDebug(lcQuick3dPhysics) << "Loaded SSG mesh from" << m_meshPath << m_ssgMesh.isValid() |
166 | << "draw" << int(m_ssgMesh.drawMode()) << "wind" |
167 | << int(m_ssgMesh.winding()) << "subs" << m_ssgMesh.subsets().count() |
168 | << "attrs" << m_ssgMesh.vertexBuffer().entries.count() |
169 | << m_ssgMesh.vertexBuffer().data.size() << "stride" |
170 | << m_ssgMesh.vertexBuffer().stride << "verts" |
171 | << m_ssgMesh.vertexBuffer().data.size() |
172 | / m_ssgMesh.vertexBuffer().stride; |
173 | |
174 | for (auto &v : m_ssgMesh.vertexBuffer().entries) { |
175 | qCDebug(lcQuick3dPhysics) << " attr" << v.name << compTypes[int(v.componentType)] << "cc" |
176 | << v.componentCount << "offs" << v.offset; |
177 | Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32); |
178 | if (v.name == "attr_pos" ) |
179 | m_posOffset = v.offset; |
180 | } |
181 | |
182 | if (m_ssgMesh.isValid()) { |
183 | auto sub = m_ssgMesh.subsets().constFirst(); |
184 | qCDebug(lcQuick3dPhysics) << "..." << sub.name << "count" << sub.count << "bounds" |
185 | << sub.bounds.min << sub.bounds.max << "offset" << sub.offset; |
186 | } |
187 | |
188 | #if 0 // EXTRA_DEBUG |
189 | |
190 | int iStride = m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4; |
191 | int vStride = m_ssgMesh.vertexBuffer().stride; |
192 | qDebug() << "IDX" << compTypes[int(m_ssgMesh.indexBuffer().componentType)] << m_ssgMesh.indexBuffer().data.size() / iStride; |
193 | const auto ib = m_ssgMesh.indexBuffer().data; |
194 | const auto vb = m_ssgMesh.vertexBuffer().data; |
195 | |
196 | auto getPoint = [&vb, vStride, this](int idx) -> QVector3D { |
197 | auto *vp = vb.constData() + vStride * idx + m_posOffset; |
198 | return *reinterpret_cast<const QVector3D *>(vp); |
199 | return {}; |
200 | }; |
201 | |
202 | if (iStride == 2) { |
203 | |
204 | } else { |
205 | auto *ip = reinterpret_cast<const uint32_t *>(ib.data()); |
206 | int n = ib.size() / iStride; |
207 | for (int i = 0; i < qMin(50,n); i += 3) { |
208 | |
209 | qDebug() << " " << ip [i] << ip[i+1] << ip[i+2] << " --- " |
210 | << getPoint(ip[i]) << getPoint(ip[i+1]) << getPoint(ip[i+2]); |
211 | } |
212 | } |
213 | #endif |
214 | if (!m_ssgMesh.isValid()) |
215 | qCWarning(lcQuick3dPhysics) << "Could not read mesh from" << m_meshPath; |
216 | } |
217 | |
218 | QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(const QUrl &source, |
219 | const QObject *contextObject) |
220 | { |
221 | const QQmlContext *context = qmlContext(contextObject); |
222 | const auto resolvedUrl = context ? context->resolvedUrl(source) : source; |
223 | const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl); |
224 | auto *mesh = meshHash.value(key: qmlSource); |
225 | if (!mesh) { |
226 | mesh = new QQuick3DPhysicsMesh(qmlSource); |
227 | meshHash[qmlSource] = mesh; |
228 | } |
229 | mesh->ref(); |
230 | return mesh; |
231 | } |
232 | |
233 | void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh) |
234 | { |
235 | if (mesh->deref() == 0) { |
236 | qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh; |
237 | erase_if(hash&: meshHash, pred: [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) { |
238 | return h.second == mesh; |
239 | }); |
240 | delete mesh; |
241 | } |
242 | } |
243 | |
244 | QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::meshHash; |
245 | |
246 | /*! |
247 | \qmltype ConvexMeshShape |
248 | \inherits CollisionShape |
249 | \inqmlmodule QtQuick3D.Physics |
250 | \since 6.4 |
251 | \brief A convex collision shape based on a 3D mesh. |
252 | |
253 | This type defines a convex shape based on the same 3D mesh file format used by |
254 | \l [QtQuick3D]{Model::source}{QtQuick3D.Model}. If the mesh is not convex, the convex hull of the |
255 | mesh will be used. |
256 | |
257 | \sa {Qt Quick 3D Physics Shapes and Bodies}{Shapes and Bodies overview documentation}, TriangleMeshShape |
258 | */ |
259 | |
260 | /*! |
261 | \qmlproperty url ConvexMeshShape::source |
262 | This property defines the location of the mesh file used to define the shape. If the |
263 | mesh is not convex, the convex hull of the mesh will be used. The maximum number of faces |
264 | and vertices is 255: If the mesh is more detailed than that, it will be simplified. |
265 | |
266 | Internally, ConvexMeshShape converts the mesh to an optimized data structure. This conversion |
267 | can be done in advance. See the \l{Qt Quick 3D Physics Cooking}{cooking overview documentation} |
268 | for details. |
269 | */ |
270 | |
271 | QConvexMeshShape::QConvexMeshShape() = default; |
272 | |
273 | QConvexMeshShape::~QConvexMeshShape() |
274 | { |
275 | delete m_meshGeometry; |
276 | if (m_mesh) |
277 | QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh); |
278 | } |
279 | |
280 | physx::PxGeometry *QConvexMeshShape::getPhysXGeometry() |
281 | { |
282 | if (m_dirtyPhysx || m_scaleDirty) { |
283 | updatePhysXGeometry(); |
284 | } |
285 | return m_meshGeometry; |
286 | } |
287 | |
288 | void QConvexMeshShape::updatePhysXGeometry() |
289 | { |
290 | delete m_meshGeometry; |
291 | m_meshGeometry = nullptr; |
292 | |
293 | auto *convexMesh = m_mesh->convexMesh(); |
294 | if (!convexMesh) |
295 | return; |
296 | |
297 | auto meshScale = sceneScale(); |
298 | physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()), |
299 | physx::PxQuat(physx::PxIdentity)); |
300 | |
301 | m_meshGeometry = new physx::PxConvexMeshGeometry(convexMesh, scale); |
302 | m_dirtyPhysx = false; |
303 | } |
304 | |
305 | const QUrl &QConvexMeshShape::source() const |
306 | { |
307 | return m_meshSource; |
308 | } |
309 | |
310 | void QConvexMeshShape::setSource(const QUrl &newSource) |
311 | { |
312 | if (m_meshSource == newSource) |
313 | return; |
314 | m_meshSource = newSource; |
315 | m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_meshSource, contextObject: this); |
316 | updatePhysXGeometry(); |
317 | |
318 | m_dirtyPhysx = true; |
319 | |
320 | emit needsRebuild(this); |
321 | emit sourceChanged(); |
322 | } |
323 | |
324 | QT_END_NAMESPACE |
325 | |