1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qcacheutils_p.h"
5#include "qconvexmeshshape_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 "qphysicsworld_p.h"
22#include "qphysicsmeshutils_p_p.h"
23#include "qphysicsutils_p.h"
24
25QT_BEGIN_NAMESPACE
26
27physx::PxConvexMesh *QQuick3DPhysicsMesh::convexMesh()
28{
29 if (m_convexMesh != nullptr)
30 return m_convexMesh;
31
32 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
33 if (thePhysics == nullptr)
34 return nullptr;
35
36 m_convexMesh = QCacheUtils::readCachedConvexMesh(filePath: m_meshPath, physics&: *thePhysics);
37 if (m_convexMesh != nullptr)
38 return m_convexMesh;
39
40 m_convexMesh = QCacheUtils::readCookedConvexMesh(filePath: m_meshPath, physics&: *thePhysics);
41 if (m_convexMesh != nullptr)
42 return m_convexMesh;
43
44 loadSsgMesh();
45
46 if (!m_ssgMesh.isValid())
47 return nullptr;
48
49 physx::PxDefaultMemoryOutputStream buf;
50 physx::PxConvexMeshCookingResult::Enum result;
51 int vStride = m_ssgMesh.vertexBuffer().stride;
52 int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride;
53 const auto *vd = m_ssgMesh.vertexBuffer().data.constData();
54
55 qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts";
56
57 QVector<physx::PxVec3> verts;
58
59 for (int i = 0; i < vCount; ++i) {
60 auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset);
61 verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() };
62 }
63
64 const auto *convexVerts = verts.constData();
65
66 physx::PxConvexMeshDesc convexDesc;
67 convexDesc.points.count = vCount;
68 convexDesc.points.stride = sizeof(physx::PxVec3);
69 convexDesc.points.data = convexVerts;
70 convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
71
72 const auto cooking = QPhysicsWorld::getCooking();
73 if (cooking && cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) {
74 auto size = buf.getSize();
75 auto *data = buf.getData();
76 physx::PxDefaultMemoryInputData input(data, size);
77 m_convexMesh = thePhysics->createConvexMesh(stream&: input);
78 qCDebug(lcQuick3dPhysics) << "Created convex mesh" << m_convexMesh << "for mesh" << this;
79 QCacheUtils::writeCachedConvexMesh(filePath: m_meshPath, buf);
80 } else {
81 qCWarning(lcQuick3dPhysics) << "Could not create convex mesh from" << m_meshPath;
82 }
83
84 return m_convexMesh;
85}
86
87physx::PxTriangleMesh *QQuick3DPhysicsMesh::triangleMesh()
88{
89
90 if (m_triangleMesh != nullptr)
91 return m_triangleMesh;
92
93 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
94 if (thePhysics == nullptr)
95 return nullptr;
96
97 m_triangleMesh = QCacheUtils::readCachedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics);
98 if (m_triangleMesh != nullptr)
99 return m_triangleMesh;
100
101 m_triangleMesh = QCacheUtils::readCookedTriangleMesh(filePath: m_meshPath, physics&: *thePhysics);
102 if (m_triangleMesh != nullptr)
103 return m_triangleMesh;
104
105 loadSsgMesh();
106 if (!m_ssgMesh.isValid())
107 return nullptr;
108
109 physx::PxDefaultMemoryOutputStream buf;
110 physx::PxTriangleMeshCookingResult::Enum result;
111 const int vStride = m_ssgMesh.vertexBuffer().stride;
112 const int vCount = m_ssgMesh.vertexBuffer().data.size() / vStride;
113 const auto *vd = m_ssgMesh.vertexBuffer().data.constData();
114
115 const int iStride =
116 m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16
117 ? 2
118 : 4;
119 const int iCount = m_ssgMesh.indexBuffer().data.size() / iStride;
120
121 qCDebug(lcQuick3dPhysics) << "prepare cooking" << vCount << "verts" << iCount << "idxs";
122
123 physx::PxTriangleMeshDesc triangleDesc;
124 triangleDesc.points.count = vCount;
125 triangleDesc.points.stride = vStride;
126 triangleDesc.points.data = vd + m_posOffset;
127
128 triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or
129 // physx::PxMeshFlag::e16_BIT_INDICES
130 triangleDesc.triangles.count = iCount / 3;
131 triangleDesc.triangles.stride = iStride * 3;
132 triangleDesc.triangles.data = m_ssgMesh.indexBuffer().data.constData();
133
134 const auto cooking = QPhysicsWorld::getCooking();
135 if (cooking && cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) {
136 auto size = buf.getSize();
137 auto *data = buf.getData();
138 physx::PxDefaultMemoryInputData input(data, size);
139 m_triangleMesh = thePhysics->createTriangleMesh(stream&: input);
140 qCDebug(lcQuick3dPhysics) << "Created triangle mesh" << m_triangleMesh << "for mesh"
141 << this;
142 QCacheUtils::writeCachedTriangleMesh(filePath: m_meshPath, buf);
143 } else {
144 qCWarning(lcQuick3dPhysics) << "Could not create triangle mesh from" << m_meshPath;
145 }
146
147 return m_triangleMesh;
148}
149
150void QQuick3DPhysicsMesh::loadSsgMesh()
151{
152 if (m_ssgMesh.isValid())
153 return;
154
155 static const char *compTypes[] = { "Null", "UnsignedInt8", "Int8", "UnsignedInt16",
156 "Int16", "UnsignedInt32", "Int32", "UnsignedInt64",
157 "Int64", "Float16", "Float32", "Float64" };
158
159 QFileInfo fileInfo = QFileInfo(m_meshPath);
160 if (fileInfo.exists()) {
161 QFile file(fileInfo.absoluteFilePath());
162 if (file.open(flags: QFile::ReadOnly))
163 m_ssgMesh = QSSGMesh::Mesh::loadMesh(device: &file);
164 }
165 qCDebug(lcQuick3dPhysics) << "Loaded SSG mesh from" << m_meshPath << m_ssgMesh.isValid()
166 << "draw" << int(m_ssgMesh.drawMode()) << "wind"
167 << int(m_ssgMesh.winding()) << "subs" << m_ssgMesh.subsets().count()
168 << "attrs" << m_ssgMesh.vertexBuffer().entries.count()
169 << m_ssgMesh.vertexBuffer().data.size() << "stride"
170 << m_ssgMesh.vertexBuffer().stride << "verts"
171 << m_ssgMesh.vertexBuffer().data.size()
172 / m_ssgMesh.vertexBuffer().stride;
173
174 for (auto &v : m_ssgMesh.vertexBuffer().entries) {
175 qCDebug(lcQuick3dPhysics) << " attr" << v.name << compTypes[int(v.componentType)] << "cc"
176 << v.componentCount << "offs" << v.offset;
177 Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32);
178 if (v.name == "attr_pos")
179 m_posOffset = v.offset;
180 }
181
182 if (m_ssgMesh.isValid()) {
183 auto sub = m_ssgMesh.subsets().constFirst();
184 qCDebug(lcQuick3dPhysics) << "..." << sub.name << "count" << sub.count << "bounds"
185 << sub.bounds.min << sub.bounds.max << "offset" << sub.offset;
186 }
187
188#if 0 // EXTRA_DEBUG
189
190 int iStride = m_ssgMesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4;
191 int vStride = m_ssgMesh.vertexBuffer().stride;
192 qDebug() << "IDX" << compTypes[int(m_ssgMesh.indexBuffer().componentType)] << m_ssgMesh.indexBuffer().data.size() / iStride;
193 const auto ib = m_ssgMesh.indexBuffer().data;
194 const auto vb = m_ssgMesh.vertexBuffer().data;
195
196 auto getPoint = [&vb, vStride, this](int idx) -> QVector3D {
197 auto *vp = vb.constData() + vStride * idx + m_posOffset;
198 return *reinterpret_cast<const QVector3D *>(vp);
199 return {};
200 };
201
202 if (iStride == 2) {
203
204 } else {
205 auto *ip = reinterpret_cast<const uint32_t *>(ib.data());
206 int n = ib.size() / iStride;
207 for (int i = 0; i < qMin(50,n); i += 3) {
208
209 qDebug() << " " << ip [i] << ip[i+1] << ip[i+2] << " --- "
210 << getPoint(ip[i]) << getPoint(ip[i+1]) << getPoint(ip[i+2]);
211 }
212 }
213#endif
214 if (!m_ssgMesh.isValid())
215 qCWarning(lcQuick3dPhysics) << "Could not read mesh from" << m_meshPath;
216}
217
218QQuick3DPhysicsMesh *QQuick3DPhysicsMeshManager::getMesh(const QUrl &source,
219 const QObject *contextObject)
220{
221 const QQmlContext *context = qmlContext(contextObject);
222 const auto resolvedUrl = context ? context->resolvedUrl(source) : source;
223 const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
224 auto *mesh = meshHash.value(key: qmlSource);
225 if (!mesh) {
226 mesh = new QQuick3DPhysicsMesh(qmlSource);
227 meshHash[qmlSource] = mesh;
228 }
229 mesh->ref();
230 return mesh;
231}
232
233void QQuick3DPhysicsMeshManager::releaseMesh(QQuick3DPhysicsMesh *mesh)
234{
235 if (mesh->deref() == 0) {
236 qCDebug(lcQuick3dPhysics()) << "deleting mesh" << mesh;
237 erase_if(hash&: meshHash, pred: [mesh](std::pair<const QString &, QQuick3DPhysicsMesh *&> h) {
238 return h.second == mesh;
239 });
240 delete mesh;
241 }
242}
243
244QHash<QString, QQuick3DPhysicsMesh *> QQuick3DPhysicsMeshManager::meshHash;
245
246/*!
247 \qmltype ConvexMeshShape
248 \inherits CollisionShape
249 \inqmlmodule QtQuick3D.Physics
250 \since 6.4
251 \brief A convex collision shape based on a 3D mesh.
252
253 This type defines a convex shape based on the same 3D mesh file format used by
254 \l [QtQuick3D]{Model::source}{QtQuick3D.Model}. If the mesh is not convex, the convex hull of the
255 mesh will be used.
256
257 \sa {Qt Quick 3D Physics Shapes and Bodies}{Shapes and Bodies overview documentation}, TriangleMeshShape
258*/
259
260/*!
261 \qmlproperty url ConvexMeshShape::source
262 This property defines the location of the mesh file used to define the shape. If the
263 mesh is not convex, the convex hull of the mesh will be used. The maximum number of faces
264 and vertices is 255: If the mesh is more detailed than that, it will be simplified.
265
266 Internally, ConvexMeshShape converts the mesh to an optimized data structure. This conversion
267 can be done in advance. See the \l{Qt Quick 3D Physics Cooking}{cooking overview documentation}
268 for details.
269*/
270
271QConvexMeshShape::QConvexMeshShape() = default;
272
273QConvexMeshShape::~QConvexMeshShape()
274{
275 delete m_meshGeometry;
276 if (m_mesh)
277 QQuick3DPhysicsMeshManager::releaseMesh(mesh: m_mesh);
278}
279
280physx::PxGeometry *QConvexMeshShape::getPhysXGeometry()
281{
282 if (m_dirtyPhysx || m_scaleDirty) {
283 updatePhysXGeometry();
284 }
285 return m_meshGeometry;
286}
287
288void QConvexMeshShape::updatePhysXGeometry()
289{
290 delete m_meshGeometry;
291 m_meshGeometry = nullptr;
292
293 auto *convexMesh = m_mesh->convexMesh();
294 if (!convexMesh)
295 return;
296
297 auto meshScale = sceneScale();
298 physx::PxMeshScale scale(physx::PxVec3(meshScale.x(), meshScale.y(), meshScale.z()),
299 physx::PxQuat(physx::PxIdentity));
300
301 m_meshGeometry = new physx::PxConvexMeshGeometry(convexMesh, scale);
302 m_dirtyPhysx = false;
303}
304
305const QUrl &QConvexMeshShape::source() const
306{
307 return m_meshSource;
308}
309
310void QConvexMeshShape::setSource(const QUrl &newSource)
311{
312 if (m_meshSource == newSource)
313 return;
314 m_meshSource = newSource;
315 m_mesh = QQuick3DPhysicsMeshManager::getMesh(source: m_meshSource, contextObject: this);
316 updatePhysXGeometry();
317
318 m_dirtyPhysx = true;
319
320 emit needsRebuild(this);
321 emit sourceChanged();
322}
323
324QT_END_NAMESPACE
325

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