1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qcacheutils_p.h"
5#include "qmeshshape_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 <QtQuick3D/QQuick3DGeometry>
22
23#include "qmeshshape_p.h"
24#include "qphysicsworld_p.h"
25#include "qphysicsmeshutils_p_p.h"
26
27QT_BEGIN_NAMESPACE
28
29static QQuick3DGeometry::Attribute
30attributeBySemantic(const QQuick3DGeometry *geometry,
31 QQuick3DGeometry::Attribute::Semantic semantic)
32{
33 for (int i = 0; i < geometry->attributeCount(); i++) {
34 const auto attr = geometry->attribute(index: i);
35 if (attr.semantic == semantic)
36 return attr;
37 }
38
39 Q_UNREACHABLE();
40 return QQuick3DGeometry::Attribute();
41};
42
43physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh()
44{
45 if (m_convexMesh != nullptr)
46 return m_convexMesh;
47
48 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
49 if (thePhysics == nullptr)
50 return nullptr;
51
52 if (m_meshGeometry)
53 return convexMeshGeometrySource();
54 if (!m_meshPath.isEmpty())
55 return convexMeshQmlSource();
56 return nullptr;
57}
58
59physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh()
60{
61 if (m_triangleMesh != nullptr)
62 return m_triangleMesh;
63
64 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
65 if (thePhysics == nullptr)
66 return nullptr;
67
68 if (m_meshGeometry)
69 return triangleMeshGeometrySource();
70 if (!m_meshPath.isEmpty())
71 return triangleMeshQmlSource();
72 return nullptr;
73}
74
75physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMeshQmlSource()
76{
77 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
78
79 m_convexMesh = QCacheUtils::readCachedConvexMesh(filePath: m_meshPath, physics&: *thePhysics);
80 if (m_convexMesh != nullptr)
81 return m_convexMesh;
82
83 m_convexMesh = QCacheUtils::readCookedConvexMesh(filePath: m_meshPath, physics&: *thePhysics);
84 if (m_convexMesh != nullptr)
85 return m_convexMesh;
86
87 loadSsgMesh();
88
89 if (!m_ssgMesh.isValid())
90 return nullptr;
91
92 const int vStride = m_ssgMesh.vertexBuffer().stride;
93 const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride;
94
95 qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts";
96
97 physx::PxConvexMeshDesc convexDesc;
98 convexDesc.points.count = vCount;
99 convexDesc.points.stride = vStride;
100 convexDesc.points.data = m_ssgMesh.vertexBuffer().data.constData() + m_posOffset;
101 convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
102
103 // NOTE: Since we are making a mesh for the convex hull and are only
104 // interested in the positions we can Skip the index array.
105
106 physx::PxDefaultMemoryOutputStream buf;
107 physx::PxConvexMeshCookingResult::Enum result;
108 const auto cooking = QPhysicsWorld::getCooking();
109 if (cooking && cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) {
110 auto size = buf.getSize();
111 auto *data = buf.getData();
112 physx::PxDefaultMemoryInputData input(data, size);
113 m_convexMesh = thePhysics->createConvexMesh(stream&: input);
114 qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this;
115 QCacheUtils::writeCachedConvexMesh(filePath: m_meshPath, buf);
116 } else {
117 qCWarning(lcQuick3dPhysics) << "Could not create convex mesh from" << m_meshPath;
118 }
119
120 return m_convexMesh;
121}
122
123physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMeshGeometrySource()
124{
125 auto vertexBuffer = m_meshGeometry->vertexData();
126
127 if (m_meshGeometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
128 qWarning() << "QQuick3DPhysicsMesh: Invalid geometry primitive type, must be Triangles. ";
129 return nullptr;
130 }
131
132 if (!vertexBuffer.size()) {
133 qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, vertexData is empty. ";
134 return nullptr;
135 }
136
137 const auto vertexAttribute =
138 attributeBySemantic(geometry: m_meshGeometry, semantic: QQuick3DGeometry::Attribute::PositionSemantic);
139 Q_ASSERT(vertexAttribute.componentType == QQuick3DGeometry::Attribute::F32Type);
140
141 const auto stride = m_meshGeometry->stride();
142 const auto numVertices = vertexBuffer.size() / stride;
143
144 physx::PxConvexMeshDesc convexDesc;
145 convexDesc.points.count = numVertices;
146 convexDesc.points.stride = stride;
147 convexDesc.points.data = vertexBuffer.constData() + vertexAttribute.offset;
148 convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
149
150 // NOTE: Since we are making a mesh for the convex hull and are only
151 // interested in the positions we can Skip the index array.
152
153 const auto cooking = QPhysicsWorld::getCooking();
154 physx::PxDefaultMemoryOutputStream buf;
155 physx::PxConvexMeshCookingResult::Enum result;
156 if (cooking && cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) {
157 auto size = buf.getSize();
158 auto *data = buf.getData();
159 physx::PxDefaultMemoryInputData input(data, size);
160 m_convexMesh = QPhysicsWorld::getPhysics()->createConvexMesh(stream&: input);
161 qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this;
162 } else {
163 qCWarning(lcQuick3dPhysics) << "Could not create convex mesh for" << this;
164 }
165
166 return m_convexMesh;
167}
168
169physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMeshQmlSource()
170{
171 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
172
173 m_triangleMesh = QCacheUtils::readCachedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics);
174 if (m_triangleMesh != nullptr)
175 return m_triangleMesh;
176
177 m_triangleMesh = QCacheUtils::readCookedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics);
178 if (m_triangleMesh != nullptr)
179 return m_triangleMesh;
180
181 loadSsgMesh();
182 if (!m_ssgMesh.isValid())
183 return nullptr;
184
185 auto vertexBuffer = m_ssgMesh.vertexBuffer().data;
186
187 const int posOffset = m_posOffset;
188 const auto stride = m_ssgMesh.vertexBuffer().stride;
189 const auto numVertices = vertexBuffer.size() / stride;
190
191 physx::PxTriangleMeshDesc triangleDesc;
192 triangleDesc.points.count = numVertices;
193 triangleDesc.points.stride = stride;
194 triangleDesc.points.data = vertexBuffer.constData() + posOffset;
195
196 auto indexBuffer = m_ssgMesh.indexBuffer().data;
197 if (indexBuffer.size()) {
198 const bool u16IndexType =
199 m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16;
200
201 Q_ASSERT(m_ssgMesh.indexBuffer().componentType
202 == QSSGMesh::Mesh::ComponentType::UnsignedInt16
203 || m_ssgMesh.indexBuffer().componentType
204 == QSSGMesh::Mesh::ComponentType::UnsignedInt32);
205
206 triangleDesc.triangles.data = indexBuffer.constData();
207 if (u16IndexType) {
208 triangleDesc.flags.set(physx::PxMeshFlag::e16_BIT_INDICES);
209 triangleDesc.triangles.stride = sizeof(quint16) * 3;
210 } else {
211 triangleDesc.triangles.stride = sizeof(quint32) * 3;
212 }
213 triangleDesc.triangles.count = indexBuffer.size() / triangleDesc.triangles.stride;
214 }
215
216 physx::PxDefaultMemoryOutputStream buf;
217 physx::PxTriangleMeshCookingResult::Enum result;
218 const auto cooking = QPhysicsWorld::getCooking();
219 if (cooking && cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) {
220 auto size = buf.getSize();
221 auto *data = buf.getData();
222 physx::PxDefaultMemoryInputData input(data, size);
223 m_triangleMesh = thePhysics->createTriangleMesh(stream&: input);
224 qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh"
225 << this;
226 QCacheUtils::writeCachedTriangleMesh(filePath: m_meshPath, buf);
227 } else {
228 qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh from" << m_meshPath;
229 }
230
231 return m_triangleMesh;
232}
233
234physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMeshGeometrySource()
235{
236 auto vertexBuffer = m_meshGeometry->vertexData();
237
238 if (m_meshGeometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
239 qWarning() << "QQuick3DPhysicsMesh: Invalid geometry primitive type, must be Triangles. ";
240 return nullptr;
241 }
242
243 if (!vertexBuffer.size()) {
244 qWarning() << "QQuick3DPhysicsMesh: Invalid geometry, vertexData is empty. ";
245 return nullptr;
246 }
247
248 const auto vertexAttribute =
249 attributeBySemantic(geometry: m_meshGeometry, semantic: QQuick3DGeometry::Attribute::PositionSemantic);
250 Q_ASSERT(vertexAttribute.componentType == QQuick3DGeometry::Attribute::F32Type);
251
252 const int posOffset = vertexAttribute.offset;
253 const auto stride = m_meshGeometry->stride();
254 const auto numVertices = vertexBuffer.size() / stride;
255
256 physx::PxTriangleMeshDesc triangleDesc;
257 triangleDesc.points.count = numVertices;
258 triangleDesc.points.stride = stride;
259 triangleDesc.points.data = vertexBuffer.constData() + posOffset;
260
261 auto indexBuffer = m_meshGeometry->indexData();
262 if (indexBuffer.size()) {
263 const auto indexAttribute =
264 attributeBySemantic(geometry: m_meshGeometry, semantic: QQuick3DGeometry::Attribute::IndexSemantic);
265 const bool u16IndexType =
266 indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type;
267
268 Q_ASSERT(indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type
269 || indexAttribute.componentType == QQuick3DGeometry::Attribute::U32Type);
270
271 triangleDesc.triangles.data = indexBuffer.constData();
272 if (u16IndexType) {
273 triangleDesc.flags.set(physx::PxMeshFlag::e16_BIT_INDICES);
274 triangleDesc.triangles.stride = sizeof(quint16) * 3;
275 } else {
276 triangleDesc.triangles.stride = sizeof(quint32) * 3;
277 }
278 triangleDesc.triangles.count = indexBuffer.size() / triangleDesc.triangles.stride;
279 }
280
281 physx::PxDefaultMemoryOutputStream buf;
282 physx::PxTriangleMeshCookingResult::Enum result;
283 const auto cooking = QPhysicsWorld::getCooking();
284 if (cooking && cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) {
285 auto size = buf.getSize();
286 auto *data = buf.getData();
287 physx::PxDefaultMemoryInputData input(data, size);
288 m_triangleMesh = QPhysicsWorld::getPhysics()->createTriangleMesh(stream&: input);
289 qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh"
290 << this;
291 } else {
292 qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh for" << this;
293 }
294
295 return m_triangleMesh;
296}
297
298void QQuick3DPhysicsMesh::loadSsgMesh()
299{
300 if (m_ssgMesh.isValid())
301 return;
302
303 static const char *compTypes[] = { "Null", "UnsignedInt8", "Int8", "UnsignedInt16",
304 "Int16", "UnsignedInt32", "Int32", "UnsignedInt64",
305 "Int64", "Float16", "Float32", "Float64" };
306
307 QFileInfo fileInfo = QFileInfo(m_meshPath);
308 if (fileInfo.exists()) {
309 QFile file(fileInfo.absoluteFilePath());
310 if (file.open(flags: QFile::ReadOnly))
311 m_ssgMesh = QSSGMesh::Mesh::loadMesh(device: &file);
312 }
313 qCDebug(lcQuick3dPhysics) << "Loaded SSG mesh from" << m_meshPath << m_ssgMesh.isValid()
314 << "draw" << int(m_ssgMesh.drawMode()) << "wind"
315 << int(m_ssgMesh.winding()) << "subs" << m_ssgMesh.subsets().count()
316 << "attrs" << m_ssgMesh.vertexBuffer().entries.count()
317 << m_ssgMesh.vertexBuffer().data.size() << "stride"
318 << m_ssgMesh.vertexBuffer().stride << "verts"
319 << m_ssgMesh.vertexBuffer().data.size()
320 / m_ssgMesh.vertexBuffer().stride;
321
322 for (auto &v : m_ssgMesh.vertexBuffer().entries) {
323 qCDebug(lcQuick3dPhysics) << " attr" << v.name << compTypes[int(v.componentType)] << "cc"
324 << v.componentCount << "offs" << v.offset;
325 Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32);
326 if (v.name == "attr_pos")
327 m_posOffset = v.offset;
328 }
329
330 if (m_ssgMesh.isValid()) {
331 auto sub = m_ssgMesh.subsets().constFirst();
332 qCDebug(lcQuick3dPhysics) << "..." << sub.name << "count" << sub.count << "bounds"
333 << sub.bounds.min << sub.bounds.max << "offset" << sub.offset;
334 }
335
336#if 0 // EXTRA_DEBUG
337
338 int iStride = m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4;
339 int vStride = m_ssgMesh.vertexBuffer().stride;
340 qDebug() << "IDX" << compTypes[int(m_ssgMesh.indexBuffer().componentType)] << m_ssgMesh.indexBuffer().data.size() / iStride;
341 const auto ib = m_ssgMesh.indexBuffer().data;
342 const auto vb = m_ssgMesh.vertexBuffer().data;
343
344 auto getPoint = [&vb, vStride, this](int idx) -> QVector3D {
345 auto *vp = vb.constData() + vStride * idx + m_posOffset;
346 return *reinterpret_cast<const QVector3D *>(vp);
347 return {};
348 };
349
350 if (iStride == 2) {
351
352 } else {
353 auto *ip = reinterpret_cast<const uint32_t *>(ib.data());
354 int n = ib.size() / iStride;
355 for (int i = 0; i < qMin(50,n); i += 3) {
356
357 qDebug() << " " << ip [i] << ip[i+1] << ip[i+2] << " --- "
358 << getPoint(ip[i]) << getPoint(ip[i+1]) << getPoint(ip[i+2]);
359 }
360 }
361#endif
362 if (!m_ssgMesh.isValid())
363 qCWarning(lcQuick3dPhysics) << "Could not read mesh from" << m_meshPath;
364}
365
366QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(const QUrl &source,
367 const QObject *contextObject)
368{
369 const QQmlContext *context = qmlContext(contextObject);
370 const auto resolvedUrl = context ? context->resolvedUrl(source) : source;
371 const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
372 auto *mesh = sourceMeshHash.value(key: qmlSource);
373 if (!mesh) {
374 mesh = new QQuick3DPhysicsMesh(qmlSource);
375 sourceMeshHash[qmlSource] = mesh;
376 }
377 mesh->ref();
378 return mesh;
379}
380
381QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(QQuick3DGeometry *source)
382{
383 auto *mesh = geometryMeshHash.value(key: source);
384 if (!mesh) {
385 mesh = new QQuick3DPhysicsMesh(source);
386 geometryMeshHash.insert(key: source, value: mesh);
387 }
388 mesh->ref();
389 return mesh;
390}
391
392void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh)
393{
394 if (mesh == nullptr || mesh->deref() > 0)
395 return;
396
397 qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh;
398 erase_if(hash&: sourceMeshHash, pred: [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) {
399 return h.second == mesh;
400 });
401 erase_if(hash&: geometryMeshHash, pred: [mesh](std::pair<QQuick3DGeometry *, QQuick3DPhysicsMesh *&> h) {
402 return h.second == mesh;
403 });
404 delete mesh;
405}
406
407QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::sourceMeshHash;
408QHash<QQuick3DGeometry *, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::geometryMeshHash;
409
410/////////////////////////////////////////////////////////////////////////////
411
412QMeshShape::~QMeshShape()
413{
414 delete m_convexGeometry;
415 if (m_mesh)
416 QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh);
417}
418
419physx::PxGeometry *QMeshShape::getPhysXGeometry()
420{
421 if (m_dirtyPhysx || m_scaleDirty)
422 updatePhysXGeometry();
423 if (shapeType() == MeshType::CONVEX)
424 return m_convexGeometry;
425 if (shapeType() == MeshType::TRIANGLE)
426 return m_triangleGeometry;
427
428 Q_UNREACHABLE_RETURN(nullptr);
429}
430
431void QMeshShape::updatePhysXGeometry()
432{
433 delete m_convexGeometry;
434 delete m_triangleGeometry;
435 m_convexGeometry = nullptr;
436 m_triangleGeometry = nullptr;
437
438 if (!m_mesh)
439 return;
440
441 auto *convexMesh = shapeType() == MeshType::CONVEX ? m_mesh->convexMesh() : nullptr;
442 auto *triangleMesh = shapeType() == MeshType::TRIANGLE ? m_mesh->triangleMesh() : nullptr;
443 if (!convexMesh && !triangleMesh)
444 return;
445
446 auto meshScale = sceneScale();
447 physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()),
448 physx::PxQuat(physx::PxIdentity));
449
450 if (convexMesh)
451 m_convexGeometry = new physx::PxConvexMeshGeometry(convexMesh, scale);
452 if (triangleMesh)
453 m_triangleGeometry = new physx::PxTriangleMeshGeometry(triangleMesh, scale);
454
455 m_dirtyPhysx = false;
456}
457
458const QUrl &QMeshShape::source() const
459{
460 return m_meshSource;
461}
462
463void QMeshShape::setSource(const QUrl &newSource)
464{
465 if (m_meshSource == newSource)
466 return;
467 m_meshSource = newSource;
468
469 // If we get a new source and our mesh was from the old source
470 // (meaning it was NOT from a geometry) we deref
471 if (m_geometry == nullptr) {
472 QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh);
473 m_mesh = nullptr;
474 }
475
476 // Load new mesh only if we don't have a geometry as source
477 if (m_geometry == nullptr && !newSource.isEmpty())
478 m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_meshSource, contextObject: this);
479
480 updatePhysXGeometry();
481 m_dirtyPhysx = true;
482
483 emit needsRebuild(this);
484 emit sourceChanged();
485}
486
487QQuick3DGeometry *QMeshShape::geometry() const
488{
489 return m_geometry;
490}
491
492void QMeshShape::setGeometry(QQuick3DGeometry *newGeometry)
493{
494 if (m_geometry == newGeometry)
495 return;
496 if (m_geometry)
497 m_geometry->disconnect(receiver: this);
498
499 m_geometry = newGeometry;
500
501 if (m_geometry != nullptr) {
502 connect(sender: m_geometry, signal: &QObject::destroyed, context: this, slot: &QMeshShape::geometryDestroyed);
503 connect(sender: m_geometry, signal: &QQuick3DGeometry::geometryChanged, context: this,
504 slot: &QMeshShape::geometryContentChanged);
505 }
506
507 // New geometry means we get a new mesh so deref the old one
508 QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh);
509 m_mesh = nullptr;
510 if (m_geometry != nullptr)
511 m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_geometry);
512 else if (!m_meshSource.isEmpty())
513 m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_meshSource, contextObject: this);
514
515 updatePhysXGeometry();
516 m_dirtyPhysx = true;
517 emit needsRebuild(this);
518 emit geometryChanged();
519}
520
521void QMeshShape::geometryDestroyed(QObject *geometry)
522{
523 Q_ASSERT(m_geometry == geometry);
524 // Set geometry to null and the old one will be disconnected and dereferenced
525 setGeometry(nullptr);
526}
527
528void QMeshShape::geometryContentChanged()
529{
530 Q_ASSERT(m_geometry != nullptr);
531 QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh);
532 m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_geometry);
533
534 updatePhysXGeometry();
535 m_dirtyPhysx = true;
536 emit needsRebuild(this);
537}
538
539QT_END_NAMESPACE
540

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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