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 | |
18 | bool 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 | |
26 | bool tryReadImage(const char *inputPath, QImage &image) |
27 | { |
28 | image = QImage(inputPath); |
29 | return image.format() != QImage::Format_Invalid; |
30 | } |
31 | |
32 | bool 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 | |
135 | bool 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 | |
181 | int 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 | |