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