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
13#include "PxPhysicsAPI.h"
14#include "cooking/PxCooking.h"
15
16#include <iostream>
17
18bool tryReadMesh(QFile *file, QSSGMesh::Mesh &mesh)
19{
20 auto device = QSharedPointer<QIODevice>(file);
21 const quint32 id = 1;
22 mesh = QSSGMesh::Mesh::loadMesh(device: device.data(), id);
23 return mesh.isValid();
24}
25
26bool tryReadImage(const char *inputPath, QImage &image)
27{
28 image = QImage(inputPath);
29 return image.format() != QImage::Format_Invalid;
30}
31
32bool cookMeshes(const char *inputPath, QSSGMesh::Mesh &mesh)
33{
34 physx::PxDefaultErrorCallback defaultErrorCallback;
35 physx::PxDefaultAllocator defaultAllocatorCallback;
36 auto foundation = PxCreateFoundation(PX_PHYSICS_VERSION, allocator&: defaultAllocatorCallback, errorCallback&: defaultErrorCallback);
37 auto cooking = PxCreateCooking(PX_PHYSICS_VERSION, foundation&: *foundation, params: physx::PxCookingParams(physx::PxTolerancesScale()));
38
39 const int vStride = mesh.vertexBuffer().stride;
40 const int vCount = mesh.vertexBuffer().data.size() / vStride;
41 const auto *vd = mesh.vertexBuffer().data.constData();
42
43 const int iStride = mesh.indexBuffer().componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16 ? 2 : 4;
44 const int iCount = mesh.indexBuffer().data.size() / iStride;
45
46 int m_posOffset = 0;
47
48 for (auto &v : mesh.vertexBuffer().entries) {
49 Q_ASSERT(v.componentType == QSSGMesh::Mesh::ComponentType::Float32);
50 if (v.name == "attr_pos")
51 m_posOffset = v.offset;
52 }
53
54 { // Triangle mesh
55 physx::PxTriangleMeshCookingResult::Enum result;
56 physx::PxTriangleMeshDesc triangleDesc;
57 triangleDesc.points.count = vCount;
58 triangleDesc.points.stride = vStride;
59 triangleDesc.points.data = vd + m_posOffset;
60
61 triangleDesc.flags = {}; //??? physx::PxMeshFlag::eFLIPNORMALS or
62 // physx::PxMeshFlag::e16_BIT_INDICES
63 triangleDesc.triangles.count = iCount / 3;
64 triangleDesc.triangles.stride = iStride * 3;
65 triangleDesc.triangles.data = mesh.indexBuffer().data.constData();
66
67 physx::PxDefaultMemoryOutputStream buf;
68 if (!cooking->cookTriangleMesh(desc: triangleDesc, stream&: buf, condition: &result)) {
69 std::cerr << "Could not cook triangle mesh.";
70 return false;
71 }
72
73 auto size = buf.getSize();
74 auto *data = buf.getData();
75 physx::PxDefaultMemoryInputData input(data, size);
76
77 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.tri");
78 auto outputFile = QFile(output);
79
80 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
81 std::cerr << "Could not open " << output.toStdString() << "for writing.";
82 return false;
83 }
84
85 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
86 outputFile.close();
87
88 std::cout << "Success: wrote triangle mesh '" << output.toStdString() << "'" << std::endl;
89 }
90
91 { // Convex mesh
92 physx::PxConvexMeshCookingResult::Enum result;
93 QVector<physx::PxVec3> verts;
94
95 for (int i = 0; i < vCount; ++i) {
96 auto *vp = reinterpret_cast<const QVector3D *>(vd + vStride * i + m_posOffset);
97 verts << physx::PxVec3 { vp->x(), vp->y(), vp->z() };
98 }
99
100 const auto *convexVerts = verts.constData();
101
102 physx::PxConvexMeshDesc convexDesc;
103 convexDesc.points.count = vCount;
104 convexDesc.points.stride = sizeof(physx::PxVec3);
105 convexDesc.points.data = convexVerts;
106 convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
107
108 physx::PxDefaultMemoryOutputStream buf;
109 if (!cooking->cookConvexMesh(desc: convexDesc, stream&: buf, condition: &result)) {
110 std::cerr << "Could not cook convex mesh.";
111 return false;
112 }
113
114 auto size = buf.getSize();
115 auto *data = buf.getData();
116 physx::PxDefaultMemoryInputData input(data, size);
117
118 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.cvx");
119 auto outputFile = QFile(output);
120
121 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
122 std::cerr << "Could not open " << output.toStdString() << "for writing.";
123 return false;
124 }
125
126 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
127 outputFile.close();
128
129 std::cout << "Success: wrote convex mesh '" << output.toStdString() << "'" << std::endl;
130 }
131
132 return true;
133}
134
135bool cookHeightfield(const char *inputPath, QImage &heightMap)
136{
137 physx::PxDefaultErrorCallback defaultErrorCallback;
138 physx::PxDefaultAllocator defaultAllocatorCallback;
139 auto foundation = PxCreateFoundation(PX_PHYSICS_VERSION, allocator&: defaultAllocatorCallback, errorCallback&: defaultErrorCallback);
140 auto cooking = PxCreateCooking(PX_PHYSICS_VERSION, foundation&: *foundation, params: physx::PxCookingParams(physx::PxTolerancesScale()));
141
142 int numRows = heightMap.height();
143 int numCols = heightMap.width();
144
145 auto samples = reinterpret_cast<physx::PxHeightFieldSample *>(malloc(size: sizeof(physx::PxHeightFieldSample) * (numRows * numCols)));
146 for (int i = 0; i < numCols; i++) {
147 for (int j = 0; j < numRows; j++) {
148 float f = heightMap.pixelColor(x: i, y: j).valueF() - 0.5;
149 samples[i * numRows + j] = { .height: qint16(0xffff * f), .materialIndex0: 0, .materialIndex1: 0 };
150 }
151 }
152
153 physx::PxHeightFieldDesc hfDesc;
154 hfDesc.format = physx::PxHeightFieldFormat::eS16_TM;
155 hfDesc.nbColumns = numRows;
156 hfDesc.nbRows = numCols;
157 hfDesc.samples.data = samples;
158 hfDesc.samples.stride = sizeof(physx::PxHeightFieldSample);
159
160 physx::PxDefaultMemoryOutputStream buf;
161 if (!(numRows && numCols && cooking->cookHeightField(desc: hfDesc, stream&: buf))) {
162 std::cerr << "Could not create height field from " << inputPath << std::endl;
163 return false;
164 }
165
166 QString output = QFileInfo(inputPath).baseName() + QString(".cooked.hf");
167 auto outputFile = QFile(output);
168
169 if (!outputFile.open(flags: QIODevice::WriteOnly)) {
170 std::cerr << "Could not open " << output.toStdString() << "for writing.";
171 return false;
172 }
173
174 outputFile.write(data: reinterpret_cast<char *>(buf.getData()), len: buf.getSize());
175 outputFile.close();
176 std::cout << "Success: wrote height field '" << output.toStdString() << "'" << std::endl;
177
178 return true;
179}
180
181int main(int argc, char *argv[])
182{
183 if (argc != 2) {
184 qDebug() << "Invalid number of arguments provided. Usage: cooker input.mesh";
185 return -1;
186 }
187
188 const char *inputPath = argv[1];
189
190 QFile *file = new QFile(inputPath);
191 if (!file->open(flags: QIODevice::ReadOnly)) {
192 delete file;
193 std::cerr << "Could not open input file '" << inputPath << "'" << std::endl;
194 return -1;
195 }
196
197 QImage image;
198 QSSGMesh::Mesh mesh;
199 if (tryReadImage(inputPath, image)) {
200 if (!cookHeightfield(inputPath, heightMap&: image))
201 return -1;
202 } else if (tryReadMesh(file, mesh)) {
203 if (!cookMeshes(inputPath, mesh))
204 return -1;
205 } else {
206 std::cerr << "Failed to read mesh or image from file '" << inputPath << "'" << std::endl;
207 return -1;
208 }
209
210 return 0;
211}
212

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