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