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