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