1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qcacheutils_p.h"
5
6#include <QFile>
7#include <QFileInfo>
8#include <QtQml/QQmlFile>
9#include <extensions/PxExtensionsAPI.h>
10#include "qphysicsworld_p.h"
11
12QT_BEGIN_NAMESPACE
13namespace QCacheUtils {
14
15enum class CacheGeometry { TriangleMesh, ConvexMesh, HeightField };
16
17static QString MESH_CACHE_PATH = qEnvironmentVariable(varName: "QT_PHYSICS_CACHE_PATH");
18
19static QString getCachedFilename(const QString &filePath, CacheGeometry geom)
20{
21 const char *extension = "unknown_physx";
22 switch (geom) {
23 case CacheGeometry::TriangleMesh:
24 extension = "triangle_physx";
25 break;
26 case CacheGeometry::ConvexMesh:
27 extension = "convex_physx";
28 break;
29 case CacheGeometry::HeightField:
30 extension = "heightfield_physx";
31 break;
32 }
33
34 return QString::fromUtf8(utf8: "%1/%2.%3")
35 .arg(args&: MESH_CACHE_PATH, args: QFileInfo(filePath).fileName(), args: QLatin1StringView(extension));
36}
37
38static void readCachedMesh(const QString &meshFilename, physx::PxPhysics &physics,
39 physx::PxTriangleMesh *&triangleMesh, physx::PxConvexMesh *&convexMesh,
40 physx::PxHeightField *&heightField, CacheGeometry geom)
41{
42 if (MESH_CACHE_PATH.isEmpty())
43 return;
44
45 QString cacheFilename = getCachedFilename(filePath: meshFilename, geom);
46 QFile cacheFile(cacheFilename);
47 QFile meshFile(meshFilename);
48 uchar *cacheData = nullptr;
49 uchar *meshData = nullptr;
50
51 auto cleanup = qScopeGuard(f: [&] {
52 if (cacheData)
53 cacheFile.unmap(address: cacheData);
54 if (meshData)
55 meshFile.unmap(address: meshData);
56 if (cacheFile.isOpen())
57 cacheFile.close();
58 if (meshFile.isOpen())
59 meshFile.close();
60 });
61
62 if (!cacheFile.open(flags: QIODevice::ReadOnly)) {
63 return;
64 }
65 if (!meshFile.open(flags: QIODevice::ReadOnly)) {
66 qWarning() << "Could not open" << meshFilename;
67 return;
68 }
69
70 // first uint64 (8 bytes) is hash of input file
71 if (cacheFile.size() <= qint64(sizeof(uint64_t))) {
72 qWarning() << "Invalid convex mesh from file" << cacheFilename;
73 return;
74 }
75
76 cacheData = cacheFile.map(offset: 0, size: cacheFile.size());
77 if (!cacheData) {
78 qWarning() << "Could not map" << cacheFilename;
79 return;
80 }
81 uint64_t cacheHash = *reinterpret_cast<uint64_t *>(cacheData);
82
83 meshData = meshFile.map(offset: 0, size: meshFile.size());
84 if (!meshData) {
85 qWarning() << "Could not map" << meshFilename;
86 return;
87 }
88 uint64_t meshHash = qHash(key: QByteArrayView(meshData, meshFile.size()));
89
90 if (cacheHash != meshHash)
91 return; // hash is different, need to re-cook
92
93 physx::PxDefaultMemoryInputData input(cacheData + sizeof(uint64_t),
94 physx::PxU32(cacheFile.size() - sizeof(uint64_t)));
95
96 switch (geom) {
97 case CacheGeometry::TriangleMesh: {
98 triangleMesh = physics.createTriangleMesh(stream&: input);
99 qCDebug(lcQuick3dPhysics) << "Read triangle mesh" << triangleMesh << "from file"
100 << cacheFilename;
101 break;
102 }
103 case CacheGeometry::ConvexMesh: {
104 convexMesh = physics.createConvexMesh(stream&: input);
105 qCDebug(lcQuick3dPhysics) << "Read convex mesh" << convexMesh << "from file"
106 << cacheFilename;
107 break;
108 }
109 case CacheGeometry::HeightField:
110 heightField = physics.createHeightField(stream&: input);
111 qCDebug(lcQuick3dPhysics) << "Read height field" << heightField << "from file"
112 << cacheFilename;
113 break;
114 }
115}
116
117static void writeCachedMesh(const QString &meshFilename, physx::PxDefaultMemoryOutputStream &buf,
118 CacheGeometry geom)
119{
120 if (MESH_CACHE_PATH.isEmpty())
121 return;
122
123 QString cacheFilename = getCachedFilename(filePath: meshFilename, geom);
124 QFile cacheFile(cacheFilename);
125 QFile meshFile(meshFilename);
126 uchar *cacheData = nullptr;
127 uchar *meshData = nullptr;
128
129 auto cleanup = qScopeGuard(f: [&] {
130 if (cacheData)
131 cacheFile.unmap(address: cacheData);
132 if (meshData)
133 meshFile.unmap(address: meshData);
134 if (cacheFile.isOpen())
135 cacheFile.close();
136 if (meshFile.isOpen())
137 meshFile.close();
138 });
139
140 if (!cacheFile.open(flags: QIODevice::WriteOnly)) {
141 qCWarning(lcQuick3dPhysics) << "Could not open" << cacheFile.fileName() << "for writing.";
142 return;
143 }
144 if (!meshFile.open(flags: QIODevice::ReadOnly)) {
145 qWarning() << "Could not open" << meshFilename;
146 return;
147 }
148
149 meshData = meshFile.map(offset: 0, size: meshFile.size());
150 if (!meshData) {
151 qWarning() << "Could not map" << meshFilename;
152 return;
153 }
154 uint64_t meshHash = qHash(key: QByteArrayView(meshData, meshFile.size()));
155
156 cacheFile.write(data: reinterpret_cast<char *>(&meshHash), len: sizeof(uint64_t));
157 cacheFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
158 cacheFile.close();
159
160 qCDebug(lcQuick3dPhysics) << "Wrote" << cacheFile.size() << "bytes to" << cacheFile.fileName();
161}
162
163void writeCachedTriangleMesh(const QString &filePath, physx::PxDefaultMemoryOutputStream &buf)
164{
165 writeCachedMesh(meshFilename: filePath, buf, geom: CacheGeometry::TriangleMesh);
166}
167
168void writeCachedConvexMesh(const QString &filePath, physx::PxDefaultMemoryOutputStream &buf)
169{
170 writeCachedMesh(meshFilename: filePath, buf, geom: CacheGeometry::ConvexMesh);
171}
172
173void writeCachedHeightField(const QString &filePath, physx::PxDefaultMemoryOutputStream &buf)
174{
175 writeCachedMesh(meshFilename: filePath, buf, geom: CacheGeometry::HeightField);
176}
177
178static void readCookedMesh(const QString &meshFilename, physx::PxPhysics &physics,
179 physx::PxTriangleMesh *&triangleMesh, physx::PxConvexMesh *&convexMesh,
180 physx::PxHeightField *&heightField, CacheGeometry geom)
181{
182 QFile file(meshFilename);
183 uchar *data = nullptr;
184
185 auto cleanup = qScopeGuard(f: [&] {
186 if (data)
187 file.unmap(address: data);
188 if (file.isOpen())
189 file.close();
190 });
191
192 if (!file.open(flags: QIODevice::ReadOnly)) {
193 qWarning() << "Could not open" << meshFilename;
194 return;
195 }
196
197 data = file.map(offset: 0, size: file.size());
198 if (!data) {
199 qWarning() << "Could not map" << meshFilename;
200 return;
201 }
202
203 physx::PxDefaultMemoryInputData input(data, physx::PxU32(file.size()));
204
205 switch (geom) {
206 case CacheGeometry::TriangleMesh: {
207 triangleMesh = physics.createTriangleMesh(stream&: input);
208 break;
209 }
210 case CacheGeometry::ConvexMesh: {
211 convexMesh = physics.createConvexMesh(stream&: input);
212 break;
213 }
214 case CacheGeometry::HeightField:
215 heightField = physics.createHeightField(stream&: input);
216 break;
217 }
218}
219
220physx::PxTriangleMesh *readCachedTriangleMesh(const QString &filePath, physx::PxPhysics &physics)
221{
222 physx::PxTriangleMesh *triangleMesh = nullptr;
223 physx::PxConvexMesh *convexMesh = nullptr;
224 physx::PxHeightField *heightField = nullptr;
225 readCachedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
226 geom: CacheGeometry::TriangleMesh);
227 return triangleMesh;
228}
229
230physx::PxConvexMesh *readCachedConvexMesh(const QString &filePath, physx::PxPhysics &physics)
231{
232 physx::PxTriangleMesh *triangleMesh = nullptr;
233 physx::PxConvexMesh *convexMesh = nullptr;
234 physx::PxHeightField *heightField = nullptr;
235 readCachedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
236 geom: CacheGeometry::ConvexMesh);
237 return convexMesh;
238}
239
240physx::PxHeightField *readCachedHeightField(const QString &filePath, physx::PxPhysics &physics)
241{
242 physx::PxTriangleMesh *triangleMesh = nullptr;
243 physx::PxConvexMesh *convexMesh = nullptr;
244 physx::PxHeightField *heightField = nullptr;
245 readCachedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
246 geom: CacheGeometry::HeightField);
247 return heightField;
248}
249
250physx::PxTriangleMesh *readCookedTriangleMesh(const QString &filePath, physx::PxPhysics &physics)
251{
252 physx::PxTriangleMesh *triangleMesh = nullptr;
253 physx::PxConvexMesh *convexMesh = nullptr;
254 physx::PxHeightField *heightField = nullptr;
255 readCookedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
256 geom: CacheGeometry::TriangleMesh);
257 return triangleMesh;
258}
259
260physx::PxConvexMesh *readCookedConvexMesh(const QString &filePath, physx::PxPhysics &physics)
261{
262 physx::PxTriangleMesh *triangleMesh = nullptr;
263 physx::PxConvexMesh *convexMesh = nullptr;
264 physx::PxHeightField *heightField = nullptr;
265 readCookedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
266 geom: CacheGeometry::ConvexMesh);
267 return convexMesh;
268}
269
270physx::PxHeightField *readCookedHeightField(const QString &filePath, physx::PxPhysics &physics)
271{
272 physx::PxTriangleMesh *triangleMesh = nullptr;
273 physx::PxConvexMesh *convexMesh = nullptr;
274 physx::PxHeightField *heightField = nullptr;
275 readCookedMesh(meshFilename: filePath, physics, triangleMesh, convexMesh, heightField,
276 geom: CacheGeometry::HeightField);
277 return heightField;
278}
279
280}
281QT_END_NAMESPACE
282

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