occt-gltf-addon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +183 -0
- package/LICENSE +22 -0
- package/README.md +74 -0
- package/example.js +81 -0
- package/index.d.ts +71 -0
- package/index.js +52 -0
- package/package.json +71 -0
- package/scripts/install.js +79 -0
- package/src/addon.cpp +504 -0
- package/src/convert_worker.cpp +474 -0
- package/src/convert_worker.h +94 -0
- package/src/gltf_writer.cpp +367 -0
- package/src/gltf_writer.h +24 -0
- package/src/gltf_writer_scene.cpp +545 -0
- package/src/mesh_types.h +78 -0
- package/src/occt_convert.cpp +313 -0
- package/src/occt_convert.h +40 -0
- package/src/occt_convert_xde.cpp +760 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
#include "gltf_writer.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <array>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include <fstream>
|
|
8
|
+
#include <limits>
|
|
9
|
+
#include <sstream>
|
|
10
|
+
#include <stdexcept>
|
|
11
|
+
#include <string>
|
|
12
|
+
#include <unordered_map>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
#ifdef OCCT_GLTF_WITH_DRACO
|
|
16
|
+
#include <draco/compression/encode.h>
|
|
17
|
+
#include <draco/core/encoder_buffer.h>
|
|
18
|
+
#include <draco/mesh/mesh.h>
|
|
19
|
+
#include <draco/attributes/geometry_attribute.h>
|
|
20
|
+
#endif
|
|
21
|
+
|
|
22
|
+
static inline uint32_t Align4(uint32_t v) { return (v + 3u) & ~3u; }
|
|
23
|
+
|
|
24
|
+
static void AppendU32(std::vector<uint8_t>& out, uint32_t v)
|
|
25
|
+
{
|
|
26
|
+
out.push_back(static_cast<uint8_t>(v & 0xFFu));
|
|
27
|
+
out.push_back(static_cast<uint8_t>((v >> 8) & 0xFFu));
|
|
28
|
+
out.push_back(static_cast<uint8_t>((v >> 16) & 0xFFu));
|
|
29
|
+
out.push_back(static_cast<uint8_t>((v >> 24) & 0xFFu));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static void AppendBytes(std::vector<uint8_t>& out, const void* data, size_t size)
|
|
33
|
+
{
|
|
34
|
+
const auto* p = static_cast<const uint8_t*>(data);
|
|
35
|
+
out.insert(out.end(), p, p + size);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static void AppendFloat32(std::vector<uint8_t>& out, float f)
|
|
39
|
+
{
|
|
40
|
+
static_assert(sizeof(float) == 4, "float must be 32-bit");
|
|
41
|
+
uint32_t u = 0;
|
|
42
|
+
std::memcpy(&u, &f, 4);
|
|
43
|
+
AppendU32(out, u);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static std::array<float, 3> Min3Init()
|
|
47
|
+
{
|
|
48
|
+
return {std::numeric_limits<float>::infinity(),
|
|
49
|
+
std::numeric_limits<float>::infinity(),
|
|
50
|
+
std::numeric_limits<float>::infinity()};
|
|
51
|
+
}
|
|
52
|
+
static std::array<float, 3> Max3Init()
|
|
53
|
+
{
|
|
54
|
+
return {-std::numeric_limits<float>::infinity(),
|
|
55
|
+
-std::numeric_limits<float>::infinity(),
|
|
56
|
+
-std::numeric_limits<float>::infinity()};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static bool IsIdentityMatrix(const std::array<float, 16>& m)
|
|
60
|
+
{
|
|
61
|
+
const std::array<float, 16> I = {
|
|
62
|
+
1,0,0,0,
|
|
63
|
+
0,1,0,0,
|
|
64
|
+
0,0,1,0,
|
|
65
|
+
0,0,0,1
|
|
66
|
+
};
|
|
67
|
+
return m == I;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static std::string JsonEscapeUtf8(const std::string& s)
|
|
71
|
+
{
|
|
72
|
+
std::string out;
|
|
73
|
+
out.reserve(s.size() + 16);
|
|
74
|
+
static const char hex[] = "0123456789ABCDEF";
|
|
75
|
+
|
|
76
|
+
for (unsigned char uc : s)
|
|
77
|
+
{
|
|
78
|
+
switch (uc)
|
|
79
|
+
{
|
|
80
|
+
case '\\': out += "\\\\"; break;
|
|
81
|
+
case '"': out += "\\\""; break;
|
|
82
|
+
case '\b': out += "\\b"; break;
|
|
83
|
+
case '\f': out += "\\f"; break;
|
|
84
|
+
case '\n': out += "\\n"; break;
|
|
85
|
+
case '\r': out += "\\r"; break;
|
|
86
|
+
case '\t': out += "\\t"; break;
|
|
87
|
+
default:
|
|
88
|
+
{
|
|
89
|
+
// JSON does not allow unescaped control characters (0x00-0x1F).
|
|
90
|
+
if (uc < 0x20)
|
|
91
|
+
{
|
|
92
|
+
out += "\\u00";
|
|
93
|
+
out.push_back(hex[(uc >> 4) & 0x0F]);
|
|
94
|
+
out.push_back(hex[uc & 0x0F]);
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
{
|
|
98
|
+
out.push_back(static_cast<char>(uc));
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
struct MeshChunk
|
|
108
|
+
{
|
|
109
|
+
// Uncompressed layout
|
|
110
|
+
uint32_t posOff = 0, posLen = 0;
|
|
111
|
+
uint32_t nrmOff = 0, nrmLen = 0;
|
|
112
|
+
|
|
113
|
+
// Draco-compressed layout
|
|
114
|
+
uint32_t dracoOff = 0, dracoLen = 0;
|
|
115
|
+
int dracoPosId = 0;
|
|
116
|
+
int dracoNrmId = 1;
|
|
117
|
+
|
|
118
|
+
uint32_t vCount = 0;
|
|
119
|
+
std::array<float, 3> minP = Min3Init();
|
|
120
|
+
std::array<float, 3> maxP = Max3Init();
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
static void PadBinTo4(std::vector<uint8_t>& bin)
|
|
124
|
+
{
|
|
125
|
+
const uint32_t pad = Align4(static_cast<uint32_t>(bin.size())) - static_cast<uint32_t>(bin.size());
|
|
126
|
+
for (uint32_t i = 0; i < pad; ++i) bin.push_back(0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#ifdef OCCT_GLTF_WITH_DRACO
|
|
130
|
+
static std::vector<uint8_t> EncodeMeshDraco(const MeshData& mesh, const GltfWriteOptions& opt, int& posAttId, int& nrmAttId)
|
|
131
|
+
{
|
|
132
|
+
const uint32_t vCount = static_cast<uint32_t>(mesh.positions.size() / 3);
|
|
133
|
+
if (vCount == 0)
|
|
134
|
+
{
|
|
135
|
+
posAttId = 0;
|
|
136
|
+
nrmAttId = 1;
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
if (vCount % 3 != 0)
|
|
140
|
+
{
|
|
141
|
+
throw std::runtime_error("Draco encode expects triangle-list with vertexCount % 3 == 0.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const uint32_t faceCount = vCount / 3;
|
|
145
|
+
|
|
146
|
+
draco::Mesh dmesh;
|
|
147
|
+
dmesh.SetNumFaces(faceCount);
|
|
148
|
+
dmesh.set_num_points(vCount);
|
|
149
|
+
|
|
150
|
+
draco::GeometryAttribute posAtt;
|
|
151
|
+
posAtt.Init(draco::GeometryAttribute::POSITION,
|
|
152
|
+
/*attribute_name*/ nullptr,
|
|
153
|
+
/*num_components*/ 3,
|
|
154
|
+
draco::DT_FLOAT32,
|
|
155
|
+
/*normalized*/ false,
|
|
156
|
+
/*byte_stride*/ sizeof(float) * 3,
|
|
157
|
+
/*byte_offset*/ 0);
|
|
158
|
+
posAttId = dmesh.AddAttribute(posAtt, /*identity_mapping*/ true, vCount);
|
|
159
|
+
|
|
160
|
+
draco::GeometryAttribute nrmAtt;
|
|
161
|
+
nrmAtt.Init(draco::GeometryAttribute::NORMAL,
|
|
162
|
+
/*attribute_name*/ nullptr,
|
|
163
|
+
/*num_components*/ 3,
|
|
164
|
+
draco::DT_FLOAT32,
|
|
165
|
+
/*normalized*/ false,
|
|
166
|
+
/*byte_stride*/ sizeof(float) * 3,
|
|
167
|
+
/*byte_offset*/ 0);
|
|
168
|
+
nrmAttId = dmesh.AddAttribute(nrmAtt, /*identity_mapping*/ true, vCount);
|
|
169
|
+
|
|
170
|
+
auto* posPtr = dmesh.attribute(posAttId);
|
|
171
|
+
auto* nrmPtr = dmesh.attribute(nrmAttId);
|
|
172
|
+
if (posPtr == nullptr || nrmPtr == nullptr)
|
|
173
|
+
{
|
|
174
|
+
throw std::runtime_error("Draco: failed to create attributes.");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (uint32_t i = 0; i < vCount; ++i)
|
|
178
|
+
{
|
|
179
|
+
const draco::PointIndex pi(i);
|
|
180
|
+
posPtr->SetAttributeValue(posPtr->mapped_index(pi), &mesh.positions[i * 3]);
|
|
181
|
+
nrmPtr->SetAttributeValue(nrmPtr->mapped_index(pi), &mesh.normals[i * 3]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (uint32_t f = 0; f < faceCount; ++f)
|
|
185
|
+
{
|
|
186
|
+
const draco::FaceIndex fi(f);
|
|
187
|
+
draco::Mesh::Face face;
|
|
188
|
+
face[0] = draco::PointIndex(f * 3 + 0);
|
|
189
|
+
face[1] = draco::PointIndex(f * 3 + 1);
|
|
190
|
+
face[2] = draco::PointIndex(f * 3 + 2);
|
|
191
|
+
dmesh.SetFace(fi, face);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
draco::Encoder encoder;
|
|
195
|
+
const int encSpeed = std::max(0, std::min(10, 10 - opt.dracoCompressionLevel));
|
|
196
|
+
encoder.SetSpeedOptions(encSpeed, /*decode_speed*/ 10);
|
|
197
|
+
encoder.SetEncodingMethod(draco::MESH_EDGEBREAKER_ENCODING);
|
|
198
|
+
|
|
199
|
+
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, opt.dracoQuantBitsPosition);
|
|
200
|
+
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, opt.dracoQuantBitsNormal);
|
|
201
|
+
|
|
202
|
+
draco::EncoderBuffer buffer;
|
|
203
|
+
const draco::Status status = encoder.EncodeMeshToBuffer(dmesh, &buffer);
|
|
204
|
+
if (!status.ok())
|
|
205
|
+
{
|
|
206
|
+
throw std::runtime_error(std::string("Draco encode failed: ") + status.error_msg());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const uint8_t* p = reinterpret_cast<const uint8_t*>(buffer.data());
|
|
210
|
+
return std::vector<uint8_t>(p, p + buffer.size());
|
|
211
|
+
}
|
|
212
|
+
#endif
|
|
213
|
+
|
|
214
|
+
std::uint64_t WriteGLB(const std::string& outputPath, const SceneData& scene, const GltfWriteOptions& opt)
|
|
215
|
+
{
|
|
216
|
+
std::vector<uint8_t> bin;
|
|
217
|
+
bin.reserve(1024 * 1024);
|
|
218
|
+
|
|
219
|
+
std::vector<MeshChunk> chunks;
|
|
220
|
+
chunks.reserve(scene.meshes.size());
|
|
221
|
+
|
|
222
|
+
if (opt.dracoEnabled)
|
|
223
|
+
{
|
|
224
|
+
#ifndef OCCT_GLTF_WITH_DRACO
|
|
225
|
+
throw std::runtime_error("Draco compression requested, but addon was built without Draco (OCCT_GLTF_WITH_DRACO).");
|
|
226
|
+
#endif
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Build BIN and per-mesh metadata.
|
|
230
|
+
for (const auto& mesh : scene.meshes)
|
|
231
|
+
{
|
|
232
|
+
if (mesh.positions.size() % 3 != 0 || mesh.normals.size() != mesh.positions.size())
|
|
233
|
+
{
|
|
234
|
+
throw std::runtime_error("Invalid mesh buffers (positions/normals mismatch).");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
MeshChunk ch;
|
|
238
|
+
ch.vCount = static_cast<uint32_t>(mesh.positions.size() / 3);
|
|
239
|
+
|
|
240
|
+
// min/max for POSITION
|
|
241
|
+
for (uint32_t i = 0; i < ch.vCount; ++i)
|
|
242
|
+
{
|
|
243
|
+
const float x = mesh.positions[i * 3 + 0];
|
|
244
|
+
const float y = mesh.positions[i * 3 + 1];
|
|
245
|
+
const float z = mesh.positions[i * 3 + 2];
|
|
246
|
+
ch.minP[0] = std::min(ch.minP[0], x); ch.minP[1] = std::min(ch.minP[1], y); ch.minP[2] = std::min(ch.minP[2], z);
|
|
247
|
+
ch.maxP[0] = std::max(ch.maxP[0], x); ch.maxP[1] = std::max(ch.maxP[1], y); ch.maxP[2] = std::max(ch.maxP[2], z);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!opt.dracoEnabled)
|
|
251
|
+
{
|
|
252
|
+
ch.posOff = static_cast<uint32_t>(bin.size());
|
|
253
|
+
for (float f : mesh.positions) AppendFloat32(bin, f);
|
|
254
|
+
ch.posLen = static_cast<uint32_t>(bin.size()) - ch.posOff;
|
|
255
|
+
|
|
256
|
+
ch.nrmOff = static_cast<uint32_t>(bin.size());
|
|
257
|
+
for (float f : mesh.normals) AppendFloat32(bin, f);
|
|
258
|
+
ch.nrmLen = static_cast<uint32_t>(bin.size()) - ch.nrmOff;
|
|
259
|
+
}
|
|
260
|
+
else
|
|
261
|
+
{
|
|
262
|
+
#ifdef OCCT_GLTF_WITH_DRACO
|
|
263
|
+
PadBinTo4(bin);
|
|
264
|
+
ch.dracoOff = static_cast<uint32_t>(bin.size());
|
|
265
|
+
int posId = 0, nrmId = 1;
|
|
266
|
+
const std::vector<uint8_t> draco = EncodeMeshDraco(mesh, opt, posId, nrmId);
|
|
267
|
+
ch.dracoPosId = posId;
|
|
268
|
+
ch.dracoNrmId = nrmId;
|
|
269
|
+
AppendBytes(bin, draco.data(), draco.size());
|
|
270
|
+
ch.dracoLen = static_cast<uint32_t>(bin.size()) - ch.dracoOff;
|
|
271
|
+
PadBinTo4(bin);
|
|
272
|
+
#endif
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
chunks.push_back(ch);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Build glTF JSON (UTF-8) preserving nodes hierarchy.
|
|
279
|
+
std::ostringstream json;
|
|
280
|
+
json << "{";
|
|
281
|
+
json << "\"asset\":{\"version\":\"2.0\",\"generator\":\"occt-gltf-addon\"},";
|
|
282
|
+
json << "\"scene\":0,";
|
|
283
|
+
if (opt.dracoEnabled)
|
|
284
|
+
{
|
|
285
|
+
json << "\"extensionsUsed\":[\"KHR_draco_mesh_compression\"],";
|
|
286
|
+
json << "\"extensionsRequired\":[\"KHR_draco_mesh_compression\"],";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// scenes
|
|
290
|
+
json << "\"scenes\":[{\"nodes\":[";
|
|
291
|
+
for (size_t i = 0; i < scene.roots.size(); ++i)
|
|
292
|
+
{
|
|
293
|
+
if (i) json << ",";
|
|
294
|
+
json << scene.roots[i];
|
|
295
|
+
}
|
|
296
|
+
json << "]}],";
|
|
297
|
+
|
|
298
|
+
// nodes
|
|
299
|
+
json << "\"nodes\":[";
|
|
300
|
+
for (size_t i = 0; i < scene.nodes.size(); ++i)
|
|
301
|
+
{
|
|
302
|
+
const SceneNode& n = scene.nodes[i];
|
|
303
|
+
if (i) json << ",";
|
|
304
|
+
|
|
305
|
+
std::ostringstream node;
|
|
306
|
+
node << "{";
|
|
307
|
+
bool first = true;
|
|
308
|
+
|
|
309
|
+
if (!n.name.empty())
|
|
310
|
+
{
|
|
311
|
+
node << "\"name\":\"" << JsonEscapeUtf8(n.name) << "\"";
|
|
312
|
+
first = false;
|
|
313
|
+
}
|
|
314
|
+
if (n.meshIndex >= 0)
|
|
315
|
+
{
|
|
316
|
+
if (!first) node << ",";
|
|
317
|
+
node << "\"mesh\":" << n.meshIndex;
|
|
318
|
+
first = false;
|
|
319
|
+
}
|
|
320
|
+
if (!n.children.empty())
|
|
321
|
+
{
|
|
322
|
+
if (!first) node << ",";
|
|
323
|
+
node << "\"children\":[";
|
|
324
|
+
for (size_t ci = 0; ci < n.children.size(); ++ci)
|
|
325
|
+
{
|
|
326
|
+
if (ci) node << ",";
|
|
327
|
+
node << n.children[ci];
|
|
328
|
+
}
|
|
329
|
+
node << "]";
|
|
330
|
+
first = false;
|
|
331
|
+
}
|
|
332
|
+
if (!IsIdentityMatrix(n.matrix))
|
|
333
|
+
{
|
|
334
|
+
if (!first) node << ",";
|
|
335
|
+
node << "\"matrix\":[";
|
|
336
|
+
for (int k = 0; k < 16; ++k)
|
|
337
|
+
{
|
|
338
|
+
if (k) node << ",";
|
|
339
|
+
node << n.matrix[(size_t)k];
|
|
340
|
+
}
|
|
341
|
+
node << "]";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
node << "}";
|
|
345
|
+
json << node.str();
|
|
346
|
+
}
|
|
347
|
+
json << "],";
|
|
348
|
+
|
|
349
|
+
// meshes
|
|
350
|
+
json << "\"meshes\":[";
|
|
351
|
+
for (size_t mi = 0; mi < scene.meshes.size(); ++mi)
|
|
352
|
+
{
|
|
353
|
+
if (mi) json << ",";
|
|
354
|
+
const MeshData& mesh = scene.meshes[mi];
|
|
355
|
+
const uint32_t posAccessor = static_cast<uint32_t>(mi * 2);
|
|
356
|
+
const uint32_t nrmAccessor = static_cast<uint32_t>(mi * 2 + 1);
|
|
357
|
+
json << "{"
|
|
358
|
+
<< "\"primitives\":[{"
|
|
359
|
+
<< "\"attributes\":{"
|
|
360
|
+
<< "\"POSITION\":" << posAccessor << ","
|
|
361
|
+
<< "\"NORMAL\":" << nrmAccessor
|
|
362
|
+
<< "},\"mode\":4";
|
|
363
|
+
if (mesh.materialIndex >= 0)
|
|
364
|
+
{
|
|
365
|
+
json << ",\"material\":" << mesh.materialIndex;
|
|
366
|
+
}
|
|
367
|
+
if (opt.dracoEnabled)
|
|
368
|
+
{
|
|
369
|
+
const MeshChunk& ch = chunks[mi];
|
|
370
|
+
json << ",\"extensions\":{"
|
|
371
|
+
<< "\"KHR_draco_mesh_compression\":{"
|
|
372
|
+
<< "\"bufferView\":" << mi
|
|
373
|
+
<< ",\"attributes\":{"
|
|
374
|
+
<< "\"POSITION\":" << ch.dracoPosId << ","
|
|
375
|
+
<< "\"NORMAL\":" << ch.dracoNrmId
|
|
376
|
+
<< "}}}";
|
|
377
|
+
}
|
|
378
|
+
json
|
|
379
|
+
<< "}]}";
|
|
380
|
+
}
|
|
381
|
+
json << "],";
|
|
382
|
+
|
|
383
|
+
// materials (optional)
|
|
384
|
+
if (!scene.materials.empty())
|
|
385
|
+
{
|
|
386
|
+
json << "\"materials\":[";
|
|
387
|
+
for (size_t i = 0; i < scene.materials.size(); ++i)
|
|
388
|
+
{
|
|
389
|
+
const MaterialData& m = scene.materials[i];
|
|
390
|
+
if (i) json << ",";
|
|
391
|
+
json << "{"
|
|
392
|
+
<< "\"pbrMetallicRoughness\":{"
|
|
393
|
+
<< "\"baseColorFactor\":["
|
|
394
|
+
<< m.baseColorFactor[0] << "," << m.baseColorFactor[1] << "," << m.baseColorFactor[2] << "," << m.baseColorFactor[3]
|
|
395
|
+
<< "],"
|
|
396
|
+
<< "\"metallicFactor\":" << m.metallicFactor << ","
|
|
397
|
+
<< "\"roughnessFactor\":" << m.roughnessFactor
|
|
398
|
+
<< "},"
|
|
399
|
+
<< "\"doubleSided\":" << (m.doubleSided ? "true" : "false");
|
|
400
|
+
if (m.baseColorFactor[3] < 1.0f)
|
|
401
|
+
{
|
|
402
|
+
json << ",\"alphaMode\":\"BLEND\"";
|
|
403
|
+
}
|
|
404
|
+
json << "}";
|
|
405
|
+
}
|
|
406
|
+
json << "],";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// bufferViews
|
|
410
|
+
json << "\"bufferViews\":[";
|
|
411
|
+
if (!opt.dracoEnabled)
|
|
412
|
+
{
|
|
413
|
+
// 2 per mesh (POSITION, NORMAL)
|
|
414
|
+
for (size_t mi = 0; mi < chunks.size(); ++mi)
|
|
415
|
+
{
|
|
416
|
+
const MeshChunk& ch = chunks[mi];
|
|
417
|
+
if (mi) json << ",";
|
|
418
|
+
json << "{"
|
|
419
|
+
<< "\"buffer\":0,\"byteOffset\":" << ch.posOff << ",\"byteLength\":" << ch.posLen << ",\"target\":34962"
|
|
420
|
+
<< "},"
|
|
421
|
+
<< "{"
|
|
422
|
+
<< "\"buffer\":0,\"byteOffset\":" << ch.nrmOff << ",\"byteLength\":" << ch.nrmLen << ",\"target\":34962"
|
|
423
|
+
<< "}";
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else
|
|
427
|
+
{
|
|
428
|
+
// 1 per mesh (Draco compressed data)
|
|
429
|
+
for (size_t mi = 0; mi < chunks.size(); ++mi)
|
|
430
|
+
{
|
|
431
|
+
const MeshChunk& ch = chunks[mi];
|
|
432
|
+
if (mi) json << ",";
|
|
433
|
+
json << "{"
|
|
434
|
+
<< "\"buffer\":0,\"byteOffset\":" << ch.dracoOff << ",\"byteLength\":" << ch.dracoLen
|
|
435
|
+
<< "}";
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
json << "],";
|
|
439
|
+
|
|
440
|
+
// accessors (2 per mesh)
|
|
441
|
+
json << "\"accessors\":[";
|
|
442
|
+
for (size_t mi = 0; mi < chunks.size(); ++mi)
|
|
443
|
+
{
|
|
444
|
+
const MeshChunk& ch = chunks[mi];
|
|
445
|
+
if (mi) json << ",";
|
|
446
|
+
if (!opt.dracoEnabled)
|
|
447
|
+
{
|
|
448
|
+
const uint32_t posView = static_cast<uint32_t>(mi * 2);
|
|
449
|
+
const uint32_t nrmView = static_cast<uint32_t>(mi * 2 + 1);
|
|
450
|
+
json << "{"
|
|
451
|
+
<< "\"bufferView\":" << posView
|
|
452
|
+
<< ",\"byteOffset\":0,\"componentType\":5126,\"count\":" << ch.vCount
|
|
453
|
+
<< ",\"type\":\"VEC3\""
|
|
454
|
+
<< ",\"min\":[" << ch.minP[0] << "," << ch.minP[1] << "," << ch.minP[2] << "]"
|
|
455
|
+
<< ",\"max\":[" << ch.maxP[0] << "," << ch.maxP[1] << "," << ch.maxP[2] << "]"
|
|
456
|
+
<< "},"
|
|
457
|
+
<< "{"
|
|
458
|
+
<< "\"bufferView\":" << nrmView
|
|
459
|
+
<< ",\"byteOffset\":0,\"componentType\":5126,\"count\":" << ch.vCount
|
|
460
|
+
<< ",\"type\":\"VEC3\""
|
|
461
|
+
<< "}";
|
|
462
|
+
}
|
|
463
|
+
else
|
|
464
|
+
{
|
|
465
|
+
// For Draco, accessors describe the attribute type/count; data comes from the extension bufferView.
|
|
466
|
+
json << "{"
|
|
467
|
+
<< "\"componentType\":5126,\"count\":" << ch.vCount
|
|
468
|
+
<< ",\"type\":\"VEC3\""
|
|
469
|
+
<< ",\"min\":[" << ch.minP[0] << "," << ch.minP[1] << "," << ch.minP[2] << "]"
|
|
470
|
+
<< ",\"max\":[" << ch.maxP[0] << "," << ch.maxP[1] << "," << ch.maxP[2] << "]"
|
|
471
|
+
<< "},"
|
|
472
|
+
<< "{"
|
|
473
|
+
<< "\"componentType\":5126,\"count\":" << ch.vCount
|
|
474
|
+
<< ",\"type\":\"VEC3\""
|
|
475
|
+
<< "}";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
json << "],";
|
|
479
|
+
|
|
480
|
+
json << "\"buffers\":[{\"byteLength\":" << bin.size() << "}]}";
|
|
481
|
+
|
|
482
|
+
std::string jsonStr = json.str();
|
|
483
|
+
|
|
484
|
+
// JSON chunk padded to 4 bytes with spaces.
|
|
485
|
+
const uint32_t jsonLen = static_cast<uint32_t>(jsonStr.size());
|
|
486
|
+
const uint32_t jsonPaddedLen = Align4(jsonLen);
|
|
487
|
+
jsonStr.resize(jsonPaddedLen, ' ');
|
|
488
|
+
|
|
489
|
+
// BIN chunk padded to 4 bytes with zeros.
|
|
490
|
+
const uint32_t binLen = static_cast<uint32_t>(bin.size());
|
|
491
|
+
const uint32_t binPaddedLen = Align4(binLen);
|
|
492
|
+
bin.resize(binPaddedLen, 0);
|
|
493
|
+
|
|
494
|
+
const uint32_t totalLen = 12u
|
|
495
|
+
+ 8u + static_cast<uint32_t>(jsonStr.size())
|
|
496
|
+
+ 8u + static_cast<uint32_t>(bin.size());
|
|
497
|
+
|
|
498
|
+
std::vector<uint8_t> glb;
|
|
499
|
+
glb.reserve(totalLen);
|
|
500
|
+
|
|
501
|
+
AppendU32(glb, 0x46546C67u); // 'glTF'
|
|
502
|
+
AppendU32(glb, 2u);
|
|
503
|
+
AppendU32(glb, totalLen);
|
|
504
|
+
|
|
505
|
+
// JSON chunk
|
|
506
|
+
AppendU32(glb, static_cast<uint32_t>(jsonStr.size()));
|
|
507
|
+
AppendU32(glb, 0x4E4F534Au); // 'JSON'
|
|
508
|
+
AppendBytes(glb, jsonStr.data(), jsonStr.size());
|
|
509
|
+
|
|
510
|
+
// BIN chunk
|
|
511
|
+
AppendU32(glb, static_cast<uint32_t>(bin.size()));
|
|
512
|
+
AppendU32(glb, 0x004E4942u); // 'BIN\0'
|
|
513
|
+
AppendBytes(glb, bin.data(), bin.size());
|
|
514
|
+
|
|
515
|
+
if (glb.size() != totalLen)
|
|
516
|
+
{
|
|
517
|
+
throw std::runtime_error("Internal error: GLB size mismatch.");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
std::ofstream ofs(outputPath, std::ios::binary);
|
|
521
|
+
if (!ofs)
|
|
522
|
+
{
|
|
523
|
+
throw std::runtime_error("Failed to open output file: " + outputPath);
|
|
524
|
+
}
|
|
525
|
+
ofs.write(reinterpret_cast<const char*>(glb.data()), static_cast<std::streamsize>(glb.size()));
|
|
526
|
+
if (!ofs)
|
|
527
|
+
{
|
|
528
|
+
throw std::runtime_error("Failed to write output file: " + outputPath);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return static_cast<std::uint64_t>(glb.size());
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
std::uint64_t WriteGLB(const std::string& outputPath, const MeshData& mesh, const GltfWriteOptions& opt)
|
|
535
|
+
{
|
|
536
|
+
SceneData scene;
|
|
537
|
+
scene.meshes.push_back(mesh);
|
|
538
|
+
SceneNode root;
|
|
539
|
+
root.name = "Root";
|
|
540
|
+
root.meshIndex = 0;
|
|
541
|
+
scene.nodes.push_back(root);
|
|
542
|
+
scene.roots.push_back(0);
|
|
543
|
+
return WriteGLB(outputPath, scene, opt);
|
|
544
|
+
}
|
|
545
|
+
|
package/src/mesh_types.h
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
struct MeshData
|
|
9
|
+
{
|
|
10
|
+
// Unindexed triangle list. Each 3 floats = one vertex position.
|
|
11
|
+
std::vector<float> positions; // xyz xyz xyz ...
|
|
12
|
+
std::vector<float> normals; // xyz xyz xyz ...
|
|
13
|
+
|
|
14
|
+
// glTF material index (SceneData::materials), -1 for default.
|
|
15
|
+
int materialIndex = -1;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
struct MaterialData
|
|
19
|
+
{
|
|
20
|
+
// glTF PBR baseColorFactor (linear RGBA in [0..1])
|
|
21
|
+
std::array<float, 4> baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
22
|
+
float metallicFactor = 0.0f;
|
|
23
|
+
float roughnessFactor = 1.0f;
|
|
24
|
+
bool doubleSided = true;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
struct ConvertStats
|
|
28
|
+
{
|
|
29
|
+
// Inputs
|
|
30
|
+
double linearDeflection = 0.1;
|
|
31
|
+
double angularDeflection = 0.5;
|
|
32
|
+
|
|
33
|
+
// STEP / mesh stats
|
|
34
|
+
std::uint64_t facesTotal = 0;
|
|
35
|
+
std::uint64_t facesWithTriangulation = 0;
|
|
36
|
+
std::uint64_t triangles = 0;
|
|
37
|
+
std::uint64_t vertices = 0; // expanded vertices (triangle list)
|
|
38
|
+
|
|
39
|
+
// Output stats
|
|
40
|
+
std::uint64_t glbBytes = 0;
|
|
41
|
+
|
|
42
|
+
// Timings (milliseconds)
|
|
43
|
+
std::uint64_t msRead = 0;
|
|
44
|
+
std::uint64_t msTransfer = 0;
|
|
45
|
+
std::uint64_t msMesh = 0;
|
|
46
|
+
std::uint64_t msExtract = 0;
|
|
47
|
+
std::uint64_t msWrite = 0;
|
|
48
|
+
std::uint64_t msTotal = 0;
|
|
49
|
+
|
|
50
|
+
// Scene graph stats
|
|
51
|
+
std::uint64_t nodes = 0;
|
|
52
|
+
std::uint64_t meshes = 0;
|
|
53
|
+
|
|
54
|
+
// Diagnostics
|
|
55
|
+
std::uint64_t skippedMeshesNoTriangulation = 0;
|
|
56
|
+
std::uint64_t skippedSmallParts = 0;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
struct SceneNode
|
|
60
|
+
{
|
|
61
|
+
std::string name; // UTF-8 (supports Chinese)
|
|
62
|
+
std::vector<int> children; // indices into SceneData::nodes
|
|
63
|
+
int meshIndex = -1; // index into SceneData::meshes, -1 for none
|
|
64
|
+
std::array<float, 16> matrix = { // glTF column-major 4x4
|
|
65
|
+
1,0,0,0,
|
|
66
|
+
0,1,0,0,
|
|
67
|
+
0,0,1,0,
|
|
68
|
+
0,0,0,1
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
struct SceneData
|
|
73
|
+
{
|
|
74
|
+
std::vector<MeshData> meshes;
|
|
75
|
+
std::vector<MaterialData> materials;
|
|
76
|
+
std::vector<SceneNode> nodes;
|
|
77
|
+
std::vector<int> roots; // root node indices
|
|
78
|
+
};
|