1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4// Security note: This file reads and cooks meshes but since it is a tool it is safe
5
6#include "cooking/PxCooking.h"
7
8#include <QtQuick3DUtils/private/qssgmesh_p.h>
9#include <QtQuick3DPhysics/private/qcacheutils_p.h>
10
11#include <QtCore/QFile>
12#include <QtCore/QFileInfo>
13#include <QtGui/QImage>
14#include <QCommandLineParser>
15#include <QScopeGuard>
16
17#include "PxPhysicsAPI.h"
18#include "cooking/PxCooking.h"
19
20#include <iostream>
21
22bool tryReadMesh(QFile *file, QSSGMesh::Mesh &mesh)
23{
24 auto device = QSharedPointer<QIODevice>(file);
25 const quint32 id = 1;
26 mesh = QSSGMesh::Mesh::loadMesh(device: device.data(), id);
27 return mesh.isValid();
28}
29
30bool tryReadImage(const QString &inputPath, QImage &image)
31{
32 image = QImage(inputPath);
33 return image.format() != QImage::Format_Invalid;
34}
35
36bool cookMeshes(const QString &inputPath, QSSGMesh::Mesh &mesh, physx::PxCooking *cooking)
37{
38 Q_ASSERT(cooking);
39
40 const int vStride = mesh.vertexBuffer().stride;
41 const int vCount = mesh.vertexBuffer().data.size() / vStride;
42 const auto *vd = mesh.vertexBuffer().data.constData();
43
44 const int iStride = mesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4;
45 const int iCount = mesh.indexBuffer().data.size() / iStride;
46
47 int m_posOffset = 0;
48
49 for (auto &v : mesh.vertexBuffer().entries) {
50 Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32);
51 if (v.name == "attr_pos")
52 m_posOffset = v.offset;
53 }
54
55 { // Triangle mesh
56 physx::PxTriangleMeshCookingResult::Enum result;
57 physx::PxTriangleMeshDesc triangleDesc;
58 triangleDesc.points.count = vCount;
59 triangleDesc.points.stride = vStride;
60 triangleDesc.points.data = vd + m_posOffset;
61
62 triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or
63 // physx::PxMeshFlag::e16_BIT_INDICES
64 triangleDesc.triangles.count = iCount / 3;
65 triangleDesc.triangles.stride = iStride * 3;
66 triangleDesc.triangles.data = mesh.indexBuffer().data.constData();
67
68 physx::PxDefaultMemoryOutputStream buf;
69 if (!cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) {
70 std::cerr << "Error: could not cook triangle mesh '" << inputPath.toStdString() << "'." << std::endl;
71 return false;
72 }
73
74 auto size = buf.getSize();
75 auto *data = buf.getData();
76 physx::PxDefaultMemoryInputData input(data, size);
77
78 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.tri");
79 auto outputFile = QFile(output);
80
81 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
82 std::cerr << "Error: could not open '" << output.toStdString() << "' for writing." << std::endl;
83 return false;
84 }
85
86 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
87 outputFile.close();
88
89 std::cout << "Success: wrote triangle mesh '" << output.toStdString() << "'." << std::endl;
90 }
91
92 { // Convex mesh
93 physx::PxConvexMeshCookingResult::Enum result;
94 QVector<physx::PxVec3> verts;
95
96 for (int i = 0; i < vCount; ++i) {
97 auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset);
98 verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() };
99 }
100
101 const auto *convexVerts = verts.constData();
102
103 physx::PxConvexMeshDesc convexDesc;
104 convexDesc.points.count = vCount;
105 convexDesc.points.stride = sizeof(physx::PxVec3);
106 convexDesc.points.data = convexVerts;
107 convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
108
109 physx::PxDefaultMemoryOutputStream buf;
110 if (!cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) {
111 std::cerr << "Error: could not cook convex mesh '" << inputPath.toStdString() << "'." << std::endl;
112 return false;
113 }
114
115 auto size = buf.getSize();
116 auto *data = buf.getData();
117 physx::PxDefaultMemoryInputData input(data, size);
118
119 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.cvx");
120 auto outputFile = QFile(output);
121
122 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
123 std::cerr << "Error: could not open '" << output.toStdString() << "' for writing." << std::endl;
124 return false;
125 }
126
127 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
128 outputFile.close();
129
130 std::cout << "Success: wrote convex mesh '" << output.toStdString() << "'." << std::endl;
131 }
132
133 return true;
134}
135
136bool cookHeightfield(const QString &inputPath, QImage &heightMap, physx::PxCooking *cooking)
137{
138 Q_ASSERT(cooking);
139
140 int numRows = heightMap.height();
141 int numCols = heightMap.width();
142
143 auto samples = reinterpret_cast<physx::PxHeightFieldSample *>(malloc(size: sizeof(physx::PxHeightFieldSample) * (numRows * numCols)));
144 for (int i = 0; i < numCols; i++) {
145 for (int j = 0; j < numRows; j++) {
146 float f = heightMap.pixelColor(x: i, y: j).valueF() - 0.5;
147 samples[i * numRows + j] = { .height: qint16(0xffff * f), .materialIndex0: 0, .materialIndex1: 0 };
148 }
149 }
150
151 physx::PxHeightFieldDesc hfDesc;
152 hfDesc.format = physx::PxHeightFieldFormat::eS16_TM;
153 hfDesc.nbColumns = numRows;
154 hfDesc.nbRows = numCols;
155 hfDesc.samples.data = samples;
156 hfDesc.samples.stride = sizeof(physx::PxHeightFieldSample);
157
158 physx::PxDefaultMemoryOutputStream buf;
159 if (!(numRows && numCols && cooking->cookHeightField(desc: hfDesc, stream&: buf))) {
160 std::cerr << "Could not create height field from '" << inputPath.toStdString() << "'." << std::endl;
161 return false;
162 }
163
164 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.hf");
165 auto outputFile = QFile(output);
166
167 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
168 std::cerr << "Could not open '" << output.toStdString() << "' for writing." << std::endl;
169 return false;
170 }
171
172 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
173 outputFile.close();
174 std::cout << "Success: wrote height field '" << output.toStdString() << "'" << std::endl;
175
176 return true;
177}
178
179int main(int argc, char *argv[])
180{
181 QCoreApplication app(argc, argv);
182 QCoreApplication::setApplicationName("cooker");
183 QCoreApplication::setApplicationVersion("6.5.7");
184
185 QCommandLineParser parser;
186 parser.setApplicationDescription(
187 "A commandline utility for pre-cooking meshes for use with the QtQuick3DPhysics module.");
188 parser.addHelpOption();
189 parser.addVersionOption();
190 parser.addPositionalArgument(name: "input",
191 description: "The input file(s). Accepts either a .mesh created by QtQuick3D's balsam"
192 " or a Qt compatible image file. The output filename will be of the format"
193 " input.cooked.{cvx/tri/hf}. The filename suffixes .cvx, .tri, and .hf"
194 " mean it is a convex mesh, a triangle mesh or a heightfield.");
195 parser.process(app);
196
197 const QStringList args = parser.positionalArguments();
198 if (args.isEmpty())
199 parser.showHelp(exitCode: 0);
200
201 physx::PxDefaultErrorCallback defaultErrorCallback;
202 physx::PxDefaultAllocator defaultAllocatorCallback;
203 auto foundation = PxCreateFoundation(PX_PHYSICS_VERSION, allocator&: defaultAllocatorCallback, errorCallback&: defaultErrorCallback);
204 auto cooking = PxCreateCooking(PX_PHYSICS_VERSION, foundation&: *foundation, params: physx::PxCookingParams(physx::PxTolerancesScale()));
205 auto cleanup = qScopeGuard(f: [&] {
206 cooking->release();
207 foundation->release();
208 });
209
210 for (const QString &inputPath : args) {
211 QFile *file = new QFile(inputPath);
212 if (!file->open(flags: QIODevice::ReadOnly)) {
213 delete file;
214 std::cerr << "Error: could not open input file '" << inputPath.toStdString() << "'" << std::endl;
215 return -1;
216 }
217
218 QImage image;
219 QSSGMesh::Mesh mesh;
220 if (tryReadImage(inputPath, image)) {
221 if (!cookHeightfield(inputPath, heightMap&: image, cooking))
222 return -1;
223 } else if (tryReadMesh(file, mesh)) {
224 if (!cookMeshes(inputPath, mesh, cooking))
225 return -1;
226 } else {
227 std::cerr << "Error: failed to read mesh or image from file '" << inputPath.toStdString() << "'" << std::endl;
228 return -1;
229 }
230 }
231
232 return 0;
233}
234

source code of qtquick3dphysics/tools/cooker/main.cpp