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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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