@ggez/workers 0.1.0 → 0.1.2

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/dist/index.js CHANGED
@@ -54,1532 +54,56 @@ function createWorkerTaskManager() {
54
54
  };
55
55
  }
56
56
 
57
- // ../shared/src/utils.ts
58
- import { Euler, Matrix4, Quaternion, Vector3 } from "three";
59
- function createBlockoutTextureDataUri(color, edgeColor = "#f5f2ea", edgeThickness = 0.018) {
60
- const size = 256;
61
- const frame = Math.max(2, Math.min(6, Math.round(size * edgeThickness)));
62
- const innerInset = frame + 3;
63
- const seamInset = innerInset + 5;
64
- const corner = 18;
65
- const highlight = mixHexColors(edgeColor, "#ffffff", 0.42);
66
- const frameColor = mixHexColors(edgeColor, color, 0.12);
67
- const innerShadow = mixHexColors(edgeColor, color, 0.28);
68
- const svg = `
69
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
70
- <rect width="${size}" height="${size}" rx="${corner}" fill="${color}" />
71
- <rect x="${frame / 2}" y="${frame / 2}" width="${size - frame}" height="${size - frame}" rx="${corner - 2}" fill="none" stroke="${frameColor}" stroke-width="${frame}" />
72
- <rect x="${innerInset}" y="${innerInset}" width="${size - innerInset * 2}" height="${size - innerInset * 2}" rx="${corner - 5}" fill="none" stroke="${highlight}" stroke-opacity="0.42" stroke-width="1" />
73
- <rect x="${seamInset}" y="${seamInset}" width="${size - seamInset * 2}" height="${size - seamInset * 2}" rx="${corner - 9}" fill="none" stroke="${innerShadow}" stroke-opacity="0.12" stroke-width="1" />
74
- <path d="M ${innerInset} ${size * 0.28} H ${size - innerInset}" stroke="${highlight}" stroke-opacity="0.08" stroke-width="1" />
75
- <path d="M ${size * 0.28} ${innerInset} V ${size - innerInset}" stroke="${highlight}" stroke-opacity="0.06" stroke-width="1" />
76
- </svg>
77
- `.trim();
78
- return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`;
79
- }
80
- function vec3(x, y, z) {
81
- return { x, y, z };
82
- }
83
- function mixHexColors(left, right, t) {
84
- const normalizedLeft = normalizeHex(left);
85
- const normalizedRight = normalizeHex(right);
86
- const leftValue = Number.parseInt(normalizedLeft.slice(1), 16);
87
- const rightValue = Number.parseInt(normalizedRight.slice(1), 16);
88
- const channels = [16, 8, 0].map((shift) => {
89
- const leftChannel = leftValue >> shift & 255;
90
- const rightChannel = rightValue >> shift & 255;
91
- return Math.round(leftChannel + (rightChannel - leftChannel) * t).toString(16).padStart(2, "0");
92
- });
93
- return `#${channels.join("")}`;
94
- }
95
- function normalizeHex(color) {
96
- if (/^#[0-9a-f]{6}$/i.test(color)) {
97
- return color;
98
- }
99
- if (/^#[0-9a-f]{3}$/i.test(color)) {
100
- return `#${color.slice(1).split("").map((channel) => `${channel}${channel}`).join("")}`;
101
- }
102
- return "#808080";
103
- }
104
- function addVec3(left, right) {
105
- return vec3(left.x + right.x, left.y + right.y, left.z + right.z);
106
- }
107
- function subVec3(left, right) {
108
- return vec3(left.x - right.x, left.y - right.y, left.z - right.z);
109
- }
110
- function scaleVec3(vector, scalar) {
111
- return vec3(vector.x * scalar, vector.y * scalar, vector.z * scalar);
112
- }
113
- function dotVec3(left, right) {
114
- return left.x * right.x + left.y * right.y + left.z * right.z;
115
- }
116
- function crossVec3(left, right) {
117
- return vec3(
118
- left.y * right.z - left.z * right.y,
119
- left.z * right.x - left.x * right.z,
120
- left.x * right.y - left.y * right.x
121
- );
122
- }
123
- function lengthVec3(vector) {
124
- return Math.sqrt(dotVec3(vector, vector));
125
- }
126
- function normalizeVec3(vector, epsilon = 1e-6) {
127
- const length = lengthVec3(vector);
128
- if (length <= epsilon) {
129
- return vec3(0, 0, 0);
130
- }
131
- return scaleVec3(vector, 1 / length);
132
- }
133
- function averageVec3(vectors) {
134
- if (vectors.length === 0) {
135
- return vec3(0, 0, 0);
136
- }
137
- const total = vectors.reduce((sum, vector) => addVec3(sum, vector), vec3(0, 0, 0));
138
- return scaleVec3(total, 1 / vectors.length);
139
- }
140
- var tempPosition = new Vector3();
141
- var tempQuaternion = new Quaternion();
142
- var tempScale = new Vector3();
143
- function isBrushNode(node) {
144
- return node.kind === "brush";
145
- }
146
- function isMeshNode(node) {
147
- return node.kind === "mesh";
148
- }
149
- function isGroupNode(node) {
150
- return node.kind === "group";
151
- }
152
- function isModelNode(node) {
153
- return node.kind === "model";
154
- }
155
- function isPrimitiveNode(node) {
156
- return node.kind === "primitive";
157
- }
158
- function isInstancingNode(node) {
159
- return node.kind === "instancing";
160
- }
161
- function isInstancingSourceNode(node) {
162
- return isBrushNode(node) || isMeshNode(node) || isPrimitiveNode(node) || isModelNode(node);
163
- }
164
- function resolveInstancingSourceNode(nodes, nodeOrId, maxDepth = 32) {
165
- const nodesById = new Map(Array.from(nodes, (node) => [node.id, node]));
166
- let current = typeof nodeOrId === "string" ? nodesById.get(nodeOrId) : nodeOrId;
167
- let depth = 0;
168
- while (current && depth <= maxDepth) {
169
- if (isInstancingSourceNode(current)) {
170
- return current;
171
- }
172
- if (!isInstancingNode(current)) {
173
- return void 0;
174
- }
175
- current = nodesById.get(current.data.sourceNodeId);
176
- depth += 1;
177
- }
178
- return void 0;
179
- }
180
-
181
- // ../geometry-kernel/src/polygon/polygon-utils.ts
182
- import earcut from "earcut";
183
- function computePolygonNormal(vertices) {
184
- if (vertices.length < 3) {
185
- return vec3(0, 1, 0);
186
- }
187
- let normal = vec3(0, 0, 0);
188
- for (let index = 0; index < vertices.length; index += 1) {
189
- const current = vertices[index];
190
- const next = vertices[(index + 1) % vertices.length];
191
- normal = addVec3(
192
- normal,
193
- vec3(
194
- (current.y - next.y) * (current.z + next.z),
195
- (current.z - next.z) * (current.x + next.x),
196
- (current.x - next.x) * (current.y + next.y)
197
- )
198
- );
199
- }
200
- const normalized = normalizeVec3(normal);
201
- return lengthVec3(normalized) === 0 ? vec3(0, 1, 0) : normalized;
202
- }
203
- function projectPolygonToPlane(vertices, normal = computePolygonNormal(vertices)) {
204
- const origin = averageVec3(vertices);
205
- const tangentReference = Math.abs(normal.y) < 0.99 ? vec3(0, 1, 0) : vec3(1, 0, 0);
206
- let tangent = normalizeVec3(crossVec3(tangentReference, normal));
207
- if (lengthVec3(tangent) === 0) {
208
- tangent = normalizeVec3(crossVec3(vec3(0, 0, 1), normal));
209
- }
210
- const bitangent = normalizeVec3(crossVec3(normal, tangent));
211
- return vertices.map((vertex) => {
212
- const offset = subVec3(vertex, origin);
213
- return [dotVec3(offset, tangent), dotVec3(offset, bitangent)];
214
- });
215
- }
216
- function polygonSignedArea(points) {
217
- let area = 0;
218
- for (let index = 0; index < points.length; index += 1) {
219
- const [x1, y1] = points[index];
220
- const [x2, y2] = points[(index + 1) % points.length];
221
- area += x1 * y2 - x2 * y1;
222
- }
223
- return area * 0.5;
224
- }
225
- function sortVerticesOnPlane(vertices, normal) {
226
- const center = averageVec3(vertices);
227
- const tangentReference = Math.abs(normal.y) < 0.99 ? vec3(0, 1, 0) : vec3(1, 0, 0);
228
- let tangent = normalizeVec3(crossVec3(tangentReference, normal));
229
- if (lengthVec3(tangent) === 0) {
230
- tangent = normalizeVec3(crossVec3(vec3(0, 0, 1), normal));
231
- }
232
- const bitangent = normalizeVec3(crossVec3(normal, tangent));
233
- const sorted = [...vertices].sort((left, right) => {
234
- const leftOffset = subVec3(left, center);
235
- const rightOffset = subVec3(right, center);
236
- const leftAngle = Math.atan2(dotVec3(leftOffset, bitangent), dotVec3(leftOffset, tangent));
237
- const rightAngle = Math.atan2(dotVec3(rightOffset, bitangent), dotVec3(rightOffset, tangent));
238
- return leftAngle - rightAngle;
239
- });
240
- if (sorted.length < 3) {
241
- return sorted;
242
- }
243
- const windingNormal = normalizeVec3(
244
- crossVec3(subVec3(sorted[1], sorted[0]), subVec3(sorted[2], sorted[0]))
245
- );
246
- return dotVec3(windingNormal, normal) < 0 ? sorted.reverse() : sorted;
247
- }
248
- function triangulatePolygon(points) {
249
- const flattened = points.flatMap(([x, y]) => [x, y]);
250
- return earcut(flattened);
251
- }
252
- function triangulatePolygon3D(vertices, normal = computePolygonNormal(vertices)) {
253
- if (vertices.length < 3) {
254
- return [];
255
- }
256
- const projected = projectPolygonToPlane(vertices, normal);
257
- if (Math.abs(polygonSignedArea(projected)) <= 1e-6) {
258
- return [];
259
- }
260
- return triangulatePolygon(projected);
261
- }
262
- function computeFaceCenter(vertices) {
263
- return averageVec3(vertices);
264
- }
265
-
266
- // ../geometry-kernel/src/brush/brush-kernel.ts
267
- function reconstructBrushFaces(brush, epsilon = 1e-4) {
268
- if (brush.planes.length < 4) {
269
- return {
270
- faces: [],
271
- vertices: [],
272
- valid: false,
273
- errors: ["Brush reconstruction requires at least four planes."]
274
- };
275
- }
276
- const vertexRegistry = /* @__PURE__ */ new Map();
277
- const faces = [];
278
- for (let planeIndex = 0; planeIndex < brush.planes.length; planeIndex += 1) {
279
- const plane = brush.planes[planeIndex];
280
- const faceVertices = collectFaceVertices(brush.planes, planeIndex, epsilon);
281
- if (faceVertices.length < 3) {
282
- continue;
283
- }
284
- const orderedVertices = sortVerticesOnPlane(faceVertices, normalizeVec3(plane.normal));
285
- const triangleIndices = triangulatePolygon3D(orderedVertices, plane.normal);
286
- if (triangleIndices.length < 3) {
287
- continue;
288
- }
289
- const vertices = orderedVertices.map((position) => registerBrushVertex(vertexRegistry, position, epsilon));
290
- const seedFace = brush.faces[planeIndex];
291
- faces.push({
292
- id: seedFace?.id ?? `face:brush:${planeIndex}`,
293
- plane,
294
- materialId: seedFace?.materialId,
295
- uvOffset: seedFace?.uvOffset,
296
- uvScale: seedFace?.uvScale,
297
- vertexIds: vertices.map((vertex) => vertex.id),
298
- vertices,
299
- center: computeFaceCenter(orderedVertices),
300
- normal: normalizeVec3(plane.normal),
301
- triangleIndices
302
- });
303
- }
304
- return {
305
- faces,
306
- vertices: Array.from(vertexRegistry.values()),
307
- valid: faces.length >= 4,
308
- errors: faces.length >= 4 ? [] : ["Brush reconstruction did not produce a closed convex solid."]
309
- };
310
- }
311
- function classifyPointAgainstPlane(point, plane, epsilon = 1e-4) {
312
- const signedDistance = signedDistanceToPlane(point, plane);
313
- return signedDistance > epsilon ? "outside" : "inside";
314
- }
315
- function signedDistanceToPlane(point, plane) {
316
- return dotVec3(plane.normal, point) - plane.distance;
317
- }
318
- function intersectPlanes(first, second, third, epsilon = 1e-6) {
319
- const denominator = dotVec3(first.normal, crossVec3(second.normal, third.normal));
320
- if (Math.abs(denominator) <= epsilon) {
321
- return void 0;
322
- }
323
- const firstTerm = scaleVec3(crossVec3(second.normal, third.normal), first.distance);
324
- const secondTerm = scaleVec3(crossVec3(third.normal, first.normal), second.distance);
325
- const thirdTerm = scaleVec3(crossVec3(first.normal, second.normal), third.distance);
326
- return scaleVec3(addVec3(addVec3(firstTerm, secondTerm), thirdTerm), 1 / denominator);
327
- }
328
- function collectFaceVertices(planes, planeIndex, epsilon) {
329
- const plane = planes[planeIndex];
330
- const vertices = /* @__PURE__ */ new Map();
331
- for (let firstIndex = 0; firstIndex < planes.length; firstIndex += 1) {
332
- if (firstIndex === planeIndex) {
333
- continue;
334
- }
335
- for (let secondIndex = firstIndex + 1; secondIndex < planes.length; secondIndex += 1) {
336
- if (secondIndex === planeIndex) {
337
- continue;
338
- }
339
- const intersection = intersectPlanes(plane, planes[firstIndex], planes[secondIndex], epsilon);
340
- if (!intersection) {
341
- continue;
342
- }
343
- const liesOnPlane = Math.abs(signedDistanceToPlane(intersection, plane)) <= epsilon * 4;
344
- const insideAllPlanes = planes.every(
345
- (candidatePlane) => classifyPointAgainstPlane(intersection, candidatePlane, epsilon * 4) === "inside"
346
- );
347
- if (!liesOnPlane || !insideAllPlanes) {
348
- continue;
349
- }
350
- vertices.set(makeVertexKey(intersection, epsilon), intersection);
351
- }
352
- }
353
- return Array.from(vertices.values());
354
- }
355
- function registerBrushVertex(registry, position, epsilon) {
356
- const key = makeVertexKey(position, epsilon);
357
- const existing = registry.get(key);
358
- if (existing) {
359
- return existing;
360
- }
361
- const vertex = {
362
- id: `vertex:brush:${registry.size}`,
363
- position: vec3(position.x, position.y, position.z)
364
- };
365
- registry.set(key, vertex);
366
- return vertex;
367
- }
368
- function makeVertexKey(position, epsilon) {
369
- return [
370
- Math.round(position.x / epsilon),
371
- Math.round(position.y / epsilon),
372
- Math.round(position.z / epsilon)
373
- ].join(":");
374
- }
375
-
376
- // ../geometry-kernel/src/mesh/editable-mesh.ts
377
- var editableMeshIndexCache = /* @__PURE__ */ new WeakMap();
378
- function getFaceVertexIds(mesh, faceId) {
379
- const index = getEditableMeshIndex(mesh);
380
- const cachedIds = index.faceVertexIds.get(faceId);
381
- if (cachedIds) {
382
- return cachedIds;
383
- }
384
- const face = index.faceById.get(faceId);
385
- if (!face) {
386
- return [];
387
- }
388
- const ids = [];
389
- let currentEdgeId = face.halfEdge;
390
- let guard = 0;
391
- while (currentEdgeId && guard < mesh.halfEdges.length + 1) {
392
- const halfEdge = index.halfEdgeById.get(currentEdgeId);
393
- if (!halfEdge) {
394
- return [];
395
- }
396
- ids.push(halfEdge.vertex);
397
- currentEdgeId = halfEdge.next;
398
- guard += 1;
399
- if (currentEdgeId === face.halfEdge) {
400
- break;
401
- }
402
- }
403
- index.faceVertexIds.set(faceId, ids);
404
- return ids;
405
- }
406
- function getFaceVertices(mesh, faceId) {
407
- const index = getEditableMeshIndex(mesh);
408
- return getFaceVertexIds(mesh, faceId).map((vertexId) => index.vertexById.get(vertexId)).filter((vertex) => Boolean(vertex));
409
- }
410
- function triangulateMeshFace(mesh, faceId) {
411
- const faceVertices = getFaceVertices(mesh, faceId);
412
- if (faceVertices.length < 3) {
413
- return void 0;
414
- }
415
- const normal = computePolygonNormal(faceVertices.map((vertex) => vertex.position));
416
- const indices = triangulatePolygon3D(
417
- faceVertices.map((vertex) => vertex.position),
418
- normal
419
- );
420
- if (indices.length < 3) {
421
- return void 0;
422
- }
423
- return {
424
- faceId,
425
- vertexIds: faceVertices.map((vertex) => vertex.id),
426
- normal,
427
- indices
428
- };
429
- }
430
- function getEditableMeshIndex(mesh) {
431
- const cached = editableMeshIndexCache.get(mesh);
432
- if (cached && cached.faces === mesh.faces && cached.halfEdges === mesh.halfEdges && cached.vertices === mesh.vertices) {
433
- return cached;
434
- }
435
- const nextIndex = {
436
- faceById: new Map(mesh.faces.map((face) => [face.id, face])),
437
- faceVertexIds: /* @__PURE__ */ new Map(),
438
- faces: mesh.faces,
439
- halfEdgeById: new Map(mesh.halfEdges.map((halfEdge) => [halfEdge.id, halfEdge])),
440
- halfEdges: mesh.halfEdges,
441
- vertexById: new Map(mesh.vertices.map((vertex) => [vertex.id, vertex])),
442
- vertices: mesh.vertices
443
- };
444
- editableMeshIndexCache.set(mesh, nextIndex);
445
- return nextIndex;
446
- }
447
-
448
- // ../runtime-build/src/bundle.ts
449
- import { unzipSync, zipSync } from "fflate";
450
-
451
- // ../runtime-format/src/types.ts
452
- var CURRENT_RUNTIME_SCENE_VERSION = 6;
453
-
454
- // ../runtime-build/src/bundle.ts
455
- var TEXTURE_FIELDS = ["baseColorTexture", "metallicRoughnessTexture", "normalTexture"];
456
- async function externalizeRuntimeAssets(scene, options = {}) {
457
- const manifest = structuredClone(scene);
458
- const files = [];
459
- const assetDir = trimSlashes(options.assetDir ?? "assets");
460
- const copyExternalAssets = options.copyExternalAssets ?? true;
461
- const pathBySource = /* @__PURE__ */ new Map();
462
- const usedPaths = /* @__PURE__ */ new Set();
463
- for (const material of manifest.materials) {
464
- for (const field of TEXTURE_FIELDS) {
465
- const source = material[field];
466
- if (!source) {
467
- continue;
468
- }
469
- const bundledPath = await materializeSource(source, {
470
- copyExternalAssets,
471
- files,
472
- pathBySource,
473
- preferredStem: `${assetDir}/textures/${slugify(material.id)}-${textureFieldSuffix(field)}`,
474
- usedPaths
475
- });
476
- if (bundledPath) {
477
- material[field] = bundledPath;
478
- }
479
- }
480
- }
481
- for (const asset of manifest.assets) {
482
- if (asset.type !== "model") {
483
- continue;
484
- }
485
- const bundledPath = await materializeSource(asset.path, {
486
- copyExternalAssets,
487
- files,
488
- pathBySource,
489
- preferredExtension: inferModelExtension(asset.path, asset.metadata.modelFormat),
490
- preferredStem: `${assetDir}/models/${slugify(asset.id)}`,
491
- usedPaths
492
- });
493
- if (bundledPath) {
494
- asset.path = bundledPath;
495
- }
496
- const texturePath = asset.metadata.texturePath;
497
- if (typeof texturePath === "string" && texturePath.length > 0) {
498
- const bundledTexturePath = await materializeSource(texturePath, {
499
- copyExternalAssets,
500
- files,
501
- pathBySource,
502
- preferredStem: `${assetDir}/model-textures/${slugify(asset.id)}`,
503
- usedPaths
504
- });
505
- if (bundledTexturePath) {
506
- asset.metadata.texturePath = bundledTexturePath;
507
- }
508
- }
509
- }
510
- const skyboxSource = manifest.settings.world.skybox.source;
511
- if (skyboxSource) {
512
- const bundledSkyboxPath = await materializeSource(skyboxSource, {
513
- copyExternalAssets,
514
- files,
515
- pathBySource,
516
- preferredExtension: manifest.settings.world.skybox.format === "hdr" ? "hdr" : inferExtensionFromPath(skyboxSource),
517
- preferredStem: `${assetDir}/skyboxes/${slugify(manifest.settings.world.skybox.name || "skybox")}`,
518
- usedPaths
519
- });
520
- if (bundledSkyboxPath) {
521
- manifest.settings.world.skybox.source = bundledSkyboxPath;
522
- }
523
- }
524
- return {
525
- files,
526
- manifest
527
- };
528
- }
529
- async function materializeSource(source, context) {
530
- const existing = context.pathBySource.get(source);
531
- if (existing) {
532
- return existing;
533
- }
534
- if (isDataUrl(source)) {
535
- const parsed = parseDataUrl(source);
536
- const path2 = ensureUniquePath(
537
- `${context.preferredStem}.${inferExtension(parsed.mimeType, context.preferredExtension)}`,
538
- context.usedPaths
539
- );
540
- context.files.push({
541
- bytes: parsed.bytes,
542
- mimeType: parsed.mimeType,
543
- path: path2
544
- });
545
- context.pathBySource.set(source, path2);
546
- return path2;
547
- }
548
- if (!context.copyExternalAssets) {
549
- return void 0;
550
- }
551
- const response = await fetch(source);
552
- if (!response.ok) {
553
- throw new Error(`Failed to bundle asset: ${source}`);
554
- }
555
- const blob = await response.blob();
556
- const bytes = new Uint8Array(await blob.arrayBuffer());
557
- const path = ensureUniquePath(
558
- `${context.preferredStem}.${inferExtension(blob.type, context.preferredExtension ?? inferExtensionFromPath(source))}`,
559
- context.usedPaths
560
- );
561
- context.files.push({
562
- bytes,
563
- mimeType: blob.type || "application/octet-stream",
564
- path
565
- });
566
- context.pathBySource.set(source, path);
567
- return path;
568
- }
569
- function parseDataUrl(source) {
570
- const match = /^data:([^;,]+)?(?:;charset=[^;,]+)?(;base64)?,(.*)$/i.exec(source);
571
- if (!match) {
572
- throw new Error("Invalid data URL.");
573
- }
574
- const mimeType = match[1] || "application/octet-stream";
575
- const payload = match[3] || "";
576
- if (match[2]) {
577
- const binary = atob(payload);
578
- const bytes = new Uint8Array(binary.length);
579
- for (let index = 0; index < binary.length; index += 1) {
580
- bytes[index] = binary.charCodeAt(index);
581
- }
582
- return { bytes, mimeType };
583
- }
584
- return {
585
- bytes: new TextEncoder().encode(decodeURIComponent(payload)),
586
- mimeType
587
- };
588
- }
589
- function textureFieldSuffix(field) {
590
- switch (field) {
591
- case "baseColorTexture":
592
- return "color";
593
- case "metallicRoughnessTexture":
594
- return "orm";
595
- default:
596
- return "normal";
597
- }
598
- }
599
- function inferModelExtension(path, modelFormat) {
600
- if (typeof modelFormat === "string" && modelFormat.length > 0) {
601
- return modelFormat.toLowerCase();
602
- }
603
- return inferExtensionFromPath(path) ?? "bin";
604
- }
605
- function inferExtension(mimeType, fallback) {
606
- const normalized = mimeType?.toLowerCase();
607
- if (normalized === "image/png") {
608
- return "png";
609
- }
610
- if (normalized === "image/jpeg") {
611
- return "jpg";
612
- }
613
- if (normalized === "image/svg+xml") {
614
- return "svg";
615
- }
616
- if (normalized === "image/vnd.radiance") {
617
- return "hdr";
618
- }
619
- if (normalized === "model/gltf+json") {
620
- return "gltf";
621
- }
622
- if (normalized === "model/gltf-binary" || normalized === "application/octet-stream") {
623
- return fallback ?? "bin";
624
- }
625
- return fallback ?? "bin";
626
- }
627
- function inferExtensionFromPath(path) {
628
- const cleanPath = path.split("?")[0]?.split("#")[0] ?? path;
629
- const parts = cleanPath.split(".");
630
- return parts.length > 1 ? parts.at(-1)?.toLowerCase() : void 0;
631
- }
632
- function ensureUniquePath(path, usedPaths) {
633
- if (!usedPaths.has(path)) {
634
- usedPaths.add(path);
635
- return path;
636
- }
637
- const lastDot = path.lastIndexOf(".");
638
- const stem = lastDot >= 0 ? path.slice(0, lastDot) : path;
639
- const extension = lastDot >= 0 ? path.slice(lastDot) : "";
640
- let counter = 2;
641
- while (usedPaths.has(`${stem}-${counter}${extension}`)) {
642
- counter += 1;
643
- }
644
- const resolved = `${stem}-${counter}${extension}`;
645
- usedPaths.add(resolved);
646
- return resolved;
647
- }
648
- function isDataUrl(value) {
649
- return value.startsWith("data:");
650
- }
651
- function slugify(value) {
652
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
653
- return normalized || "asset";
654
- }
655
- function trimSlashes(value) {
656
- return value.replace(/^\/+|\/+$/g, "");
657
- }
658
-
659
- // ../runtime-build/src/snapshot-build.ts
660
- import { MeshBVH } from "three-mesh-bvh";
661
- import {
662
- Box3,
663
- BoxGeometry,
664
- BufferGeometry,
665
- ConeGeometry,
666
- Float32BufferAttribute,
667
- Group,
668
- Mesh,
669
- MeshStandardMaterial,
670
- RepeatWrapping,
671
- Scene,
672
- SphereGeometry,
673
- SRGBColorSpace,
674
- TextureLoader,
675
- Vector3 as Vector32,
676
- CylinderGeometry
677
- } from "three";
678
- import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
679
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
680
- import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";
681
- import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
682
- var gltfLoader = new GLTFLoader();
683
- var gltfExporter = new GLTFExporter();
684
- var mtlLoader = new MTLLoader();
685
- var modelTextureLoader = new TextureLoader();
686
- async function buildRuntimeBundleFromSnapshot(snapshot, options = {}) {
687
- return externalizeRuntimeAssets(await buildRuntimeSceneFromSnapshot(snapshot), options);
688
- }
689
- async function serializeRuntimeScene(snapshot) {
690
- return JSON.stringify(await buildRuntimeSceneFromSnapshot(snapshot));
691
- }
692
- async function buildRuntimeSceneFromSnapshot(snapshot) {
693
- const assetsById = new Map(snapshot.assets.map((asset) => [asset.id, asset]));
694
- const materialsById = new Map(snapshot.materials.map((material) => [material.id, material]));
695
- const exportedMaterials = await Promise.all(snapshot.materials.map((material) => resolveRuntimeMaterial(material)));
696
- const shouldBakeLods = snapshot.settings.world.lod.enabled;
697
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
698
- const exportedSettings = shouldBakeLods ? {
699
- ...snapshot.settings,
700
- world: {
701
- ...snapshot.settings.world,
702
- lod: {
703
- ...snapshot.settings.world.lod,
704
- bakedAt: exportedAt
705
- }
706
- }
707
- } : snapshot.settings;
708
- const generatedAssets = [];
709
- const exportedNodes = [];
710
- for (const node of snapshot.nodes) {
711
- if (isGroupNode(node)) {
712
- exportedNodes.push({
713
- data: node.data,
714
- hooks: node.hooks,
715
- id: node.id,
716
- kind: "group",
717
- metadata: node.metadata,
718
- name: node.name,
719
- parentId: node.parentId,
720
- tags: node.tags,
721
- transform: node.transform
722
- });
723
- continue;
724
- }
725
- if (isBrushNode(node)) {
726
- const geometry = await buildExportGeometry(node, materialsById);
727
- exportedNodes.push({
728
- data: node.data,
729
- geometry,
730
- hooks: node.hooks,
731
- id: node.id,
732
- kind: "brush",
733
- lods: shouldBakeLods ? await buildGeometryLods(geometry, snapshot.settings.world.lod) : void 0,
734
- metadata: node.metadata,
735
- name: node.name,
736
- parentId: node.parentId,
737
- tags: node.tags,
738
- transform: node.transform
739
- });
740
- continue;
741
- }
742
- if (isMeshNode(node)) {
743
- const geometry = await buildExportGeometry(node, materialsById);
744
- exportedNodes.push({
745
- data: node.data,
746
- geometry,
747
- hooks: node.hooks,
748
- id: node.id,
749
- kind: "mesh",
750
- lods: shouldBakeLods ? await buildGeometryLods(geometry, snapshot.settings.world.lod) : void 0,
751
- metadata: node.metadata,
752
- name: node.name,
753
- parentId: node.parentId,
754
- tags: node.tags,
755
- transform: node.transform
756
- });
757
- continue;
758
- }
759
- if (isPrimitiveNode(node)) {
760
- const geometry = await buildExportGeometry(node, materialsById);
761
- exportedNodes.push({
762
- data: node.data,
763
- geometry,
764
- hooks: node.hooks,
765
- id: node.id,
766
- kind: "primitive",
767
- lods: shouldBakeLods ? await buildGeometryLods(geometry, snapshot.settings.world.lod) : void 0,
768
- metadata: node.metadata,
769
- name: node.name,
770
- parentId: node.parentId,
771
- tags: node.tags,
772
- transform: node.transform
773
- });
774
- continue;
775
- }
776
- if (isModelNode(node)) {
777
- const modelLodBake = shouldBakeLods ? await buildModelLods(node.name, assetsById.get(node.data.assetId), node.id, snapshot.settings.world.lod) : void 0;
778
- generatedAssets.push(...modelLodBake?.assets ?? []);
779
- exportedNodes.push({
780
- data: node.data,
781
- hooks: node.hooks,
782
- id: node.id,
783
- kind: "model",
784
- lods: modelLodBake?.lods,
785
- metadata: node.metadata,
786
- name: node.name,
787
- parentId: node.parentId,
788
- tags: node.tags,
789
- transform: node.transform
790
- });
791
- continue;
792
- }
793
- if (isInstancingNode(node)) {
794
- const sourceNode = resolveInstancingSourceNode(snapshot.nodes, node);
795
- if (!sourceNode || !(isBrushNode(sourceNode) || isMeshNode(sourceNode) || isPrimitiveNode(sourceNode) || isModelNode(sourceNode))) {
796
- continue;
797
- }
798
- exportedNodes.push({
799
- data: {
800
- sourceNodeId: sourceNode.id
801
- },
802
- hooks: node.hooks,
803
- id: node.id,
804
- kind: "instancing",
805
- metadata: node.metadata,
806
- name: node.name,
807
- parentId: node.parentId,
808
- tags: node.tags,
809
- transform: sanitizeInstanceTransform(node.transform)
810
- });
811
- continue;
812
- }
813
- exportedNodes.push({
814
- data: node.data,
815
- id: node.id,
816
- kind: "light",
817
- metadata: node.metadata,
818
- name: node.name,
819
- parentId: node.parentId,
820
- tags: node.tags,
821
- transform: node.transform
822
- });
823
- }
824
- return {
825
- assets: [...snapshot.assets, ...generatedAssets],
826
- entities: snapshot.entities,
827
- layers: snapshot.layers,
828
- materials: exportedMaterials,
829
- metadata: {
830
- exportedAt,
831
- format: "web-hammer-engine",
832
- version: CURRENT_RUNTIME_SCENE_VERSION
833
- },
834
- nodes: exportedNodes,
835
- settings: exportedSettings
836
- };
837
- }
838
- async function buildExportGeometry(node, materialsById) {
839
- const fallbackMaterial = await resolveRuntimeMaterial({
840
- color: node.kind === "brush" ? "#f69036" : node.kind === "primitive" && node.data.role === "prop" ? "#7f8ea3" : "#6ed5c0",
841
- id: `material:fallback:${node.id}`,
842
- metalness: node.kind === "brush" ? 0 : node.kind === "primitive" && node.data.role === "prop" ? 0.12 : 0.05,
843
- name: `${node.name} Default`,
844
- roughness: node.kind === "brush" ? 0.95 : node.kind === "primitive" && node.data.role === "prop" ? 0.64 : 0.82
845
- });
846
- const primitiveByMaterial = /* @__PURE__ */ new Map();
847
- const appendFace = async (params) => {
848
- const material = params.faceMaterialId ? await resolveRuntimeMaterial(materialsById.get(params.faceMaterialId)) : fallbackMaterial;
849
- const primitive = primitiveByMaterial.get(material.id) ?? {
850
- indices: [],
851
- material,
852
- normals: [],
853
- positions: [],
854
- uvs: []
855
- };
856
- const vertexOffset = primitive.positions.length / 3;
857
- const uvs = params.uvs && params.uvs.length === params.vertices.length ? params.uvs.flatMap((uv) => [uv.x, uv.y]) : projectPlanarUvs(params.vertices, params.normal, params.uvScale, params.uvOffset);
858
- params.vertices.forEach((vertex) => {
859
- primitive.positions.push(vertex.x, vertex.y, vertex.z);
860
- primitive.normals.push(params.normal.x, params.normal.y, params.normal.z);
861
- });
862
- primitive.uvs.push(...uvs);
863
- params.triangleIndices.forEach((index) => {
864
- primitive.indices.push(vertexOffset + index);
865
- });
866
- primitiveByMaterial.set(material.id, primitive);
867
- };
868
- if (isBrushNode(node)) {
869
- const rebuilt = reconstructBrushFaces(node.data);
870
- if (!rebuilt.valid) {
871
- return { primitives: [] };
872
- }
873
- for (const face of rebuilt.faces) {
874
- await appendFace({
875
- faceMaterialId: face.materialId,
876
- normal: face.normal,
877
- triangleIndices: face.triangleIndices,
878
- uvOffset: face.uvOffset,
879
- uvScale: face.uvScale,
880
- vertices: face.vertices.map((vertex) => vertex.position)
881
- });
882
- }
883
- }
884
- if (isMeshNode(node)) {
885
- for (const face of node.data.faces) {
886
- const triangulated = triangulateMeshFace(node.data, face.id);
887
- if (!triangulated) {
888
- continue;
889
- }
890
- await appendFace({
891
- faceMaterialId: face.materialId,
892
- normal: triangulated.normal,
893
- triangleIndices: triangulated.indices,
894
- uvOffset: face.uvOffset,
895
- uvScale: face.uvScale,
896
- uvs: face.uvs,
897
- vertices: getFaceVertices(node.data, face.id).map((vertex) => vertex.position)
898
- });
899
- }
900
- }
901
- if (isPrimitiveNode(node)) {
902
- const material = node.data.materialId ? await resolveRuntimeMaterial(materialsById.get(node.data.materialId)) : fallbackMaterial;
903
- const primitive = buildPrimitiveGeometry(node.data.shape, node.data.size, node.data.radialSegments ?? 24);
904
- if (primitive) {
905
- primitiveByMaterial.set(material.id, {
906
- indices: primitive.indices,
907
- material,
908
- normals: primitive.normals,
909
- positions: primitive.positions,
910
- uvs: primitive.uvs
911
- });
912
- }
913
- }
914
- return {
915
- primitives: Array.from(primitiveByMaterial.values())
916
- };
917
- }
918
- async function buildGeometryLods(geometry, settings) {
919
- if (!geometry.primitives.length) {
920
- return void 0;
921
- }
922
- const midGeometry = simplifyExportGeometry(geometry, settings.midDetailRatio);
923
- const lowGeometry = simplifyExportGeometry(geometry, settings.lowDetailRatio);
924
- const lods = [];
925
- if (midGeometry) {
926
- lods.push({
927
- geometry: midGeometry,
928
- level: "mid"
929
- });
930
- }
931
- if (lowGeometry) {
932
- lods.push({
933
- geometry: lowGeometry,
934
- level: "low"
935
- });
936
- }
937
- return lods.length ? lods : void 0;
938
- }
939
- async function buildModelLods(name, asset, nodeId, settings) {
940
- if (!asset?.path) {
941
- return { assets: [], lods: void 0 };
942
- }
943
- const source = await loadModelSceneForLodBake(asset);
944
- const bakedLevels = [];
945
- for (const [level, ratio] of [
946
- ["mid", settings.midDetailRatio],
947
- ["low", settings.lowDetailRatio]
948
- ]) {
949
- const simplified = simplifyModelSceneForRatio(source, ratio);
950
- if (!simplified) {
951
- continue;
952
- }
953
- const bytes = await exportModelSceneAsGlb(simplified);
954
- bakedLevels.push({
955
- asset: createGeneratedModelLodAsset(asset, name, nodeId, level, bytes),
956
- level
957
- });
958
- }
959
- return {
960
- assets: bakedLevels.map((entry) => entry.asset),
961
- lods: bakedLevels.length ? bakedLevels.map((entry) => ({
962
- assetId: entry.asset.id,
963
- level: entry.level
964
- })) : void 0
965
- };
966
- }
967
- async function loadModelSceneForLodBake(asset) {
968
- const format = resolveModelAssetFormat(asset);
969
- if (format === "obj") {
970
- const objLoader = new OBJLoader();
971
- const texturePath = readModelAssetString(asset, "texturePath");
972
- const resolvedTexturePath = typeof texturePath === "string" && texturePath.length > 0 ? texturePath : void 0;
973
- const mtlText = readModelAssetString(asset, "materialMtlText");
974
- if (mtlText) {
975
- const materialCreator = mtlLoader.parse(patchMtlTextureReferences(mtlText, resolvedTexturePath), "");
976
- materialCreator.preload();
977
- objLoader.setMaterials(materialCreator);
978
- } else {
979
- objLoader.setMaterials(void 0);
980
- }
981
- const object = await objLoader.loadAsync(asset.path);
982
- if (!mtlText && resolvedTexturePath) {
983
- const texture = await modelTextureLoader.loadAsync(resolvedTexturePath);
984
- texture.wrapS = RepeatWrapping;
985
- texture.wrapT = RepeatWrapping;
986
- texture.colorSpace = SRGBColorSpace;
987
- object.traverse((child) => {
988
- if (child instanceof Mesh) {
989
- child.material = new MeshStandardMaterial({
990
- map: texture,
991
- metalness: 0.12,
992
- roughness: 0.76
993
- });
994
- }
995
- });
996
- }
997
- return object;
998
- }
999
- return (await gltfLoader.loadAsync(asset.path)).scene;
1000
- }
1001
- function simplifyModelSceneForRatio(source, ratio) {
1002
- if (ratio >= 0.98) {
1003
- return void 0;
1004
- }
1005
- const simplifiedRoot = source.clone(true);
1006
- expandGroupedModelMeshesForLodBake(simplifiedRoot);
1007
- let simplifiedMeshCount = 0;
1008
- simplifiedRoot.traverse((child) => {
1009
- if (!(child instanceof Mesh)) {
1010
- return;
1011
- }
1012
- if ("isSkinnedMesh" in child && child.isSkinnedMesh) {
1013
- return;
1014
- }
1015
- const simplifiedGeometry = simplifyModelGeometry(child.geometry, ratio);
1016
- if (!simplifiedGeometry) {
1017
- return;
1018
- }
1019
- child.geometry = simplifiedGeometry;
1020
- simplifiedMeshCount += 1;
1021
- });
1022
- return simplifiedMeshCount > 0 ? simplifiedRoot : void 0;
1023
- }
1024
- function expandGroupedModelMeshesForLodBake(root) {
1025
- const replacements = [];
1026
- root.traverse((child) => {
1027
- if (!(child instanceof Mesh) || !Array.isArray(child.material) || child.geometry.groups.length <= 1 || !child.parent) {
1028
- return;
1029
- }
1030
- const container = new Group();
1031
- container.name = child.name ? `${child.name}:lod-groups` : "lod-groups";
1032
- container.position.copy(child.position);
1033
- container.quaternion.copy(child.quaternion);
1034
- container.scale.copy(child.scale);
1035
- container.visible = child.visible;
1036
- container.renderOrder = child.renderOrder;
1037
- container.userData = structuredClone(child.userData ?? {});
1038
- child.geometry.groups.forEach((group, groupIndex) => {
1039
- const material = child.material[group.materialIndex] ?? child.material[0];
1040
- if (!material) {
1041
- return;
1042
- }
1043
- const partGeometry = extractGeometryGroup(child.geometry, group.start, group.count);
1044
- const partMesh = new Mesh(partGeometry, material);
1045
- partMesh.name = child.name ? `${child.name}:group:${groupIndex}` : `group:${groupIndex}`;
1046
- partMesh.castShadow = child.castShadow;
1047
- partMesh.receiveShadow = child.receiveShadow;
1048
- partMesh.userData = structuredClone(child.userData ?? {});
1049
- container.add(partMesh);
1050
- });
1051
- replacements.push({
1052
- container,
1053
- mesh: child,
1054
- parent: child.parent
1055
- });
1056
- });
1057
- replacements.forEach(({ container, mesh, parent }) => {
1058
- parent.add(container);
1059
- parent.remove(mesh);
1060
- });
1061
- }
1062
- function extractGeometryGroup(geometry, start, count) {
1063
- const groupGeometry = new BufferGeometry();
1064
- const index = geometry.getIndex();
1065
- const attributes = geometry.attributes;
1066
- Object.entries(attributes).forEach(([name, attribute]) => {
1067
- groupGeometry.setAttribute(name, attribute);
1068
- });
1069
- if (index) {
1070
- groupGeometry.setIndex(Array.from(index.array).slice(start, start + count));
1071
- } else {
1072
- groupGeometry.setIndex(Array.from({ length: count }, (_, offset) => start + offset));
1073
- }
1074
- groupGeometry.computeBoundingBox();
1075
- groupGeometry.computeBoundingSphere();
1076
- return groupGeometry;
1077
- }
1078
- function simplifyModelGeometry(geometry, ratio) {
1079
- const positionAttribute = geometry.getAttribute("position");
1080
- const vertexCount = positionAttribute?.count ?? 0;
1081
- if (!positionAttribute || vertexCount < 12 || ratio >= 0.98) {
1082
- return void 0;
1083
- }
1084
- const workingGeometry = geometry.getAttribute("normal") ? geometry : geometry.clone();
1085
- if (!workingGeometry.getAttribute("normal")) {
1086
- workingGeometry.computeVertexNormals();
1087
- }
1088
- workingGeometry.computeBoundingBox();
1089
- const bounds = workingGeometry.boundingBox?.clone();
1090
- if (!bounds) {
1091
- if (workingGeometry !== geometry) {
1092
- workingGeometry.dispose();
1093
- }
1094
- return void 0;
1095
- }
1096
- const normalAttribute = workingGeometry.getAttribute("normal");
1097
- const uvAttribute = workingGeometry.getAttribute("uv");
1098
- const index = workingGeometry.getIndex();
1099
- const simplified = simplifyPrimitiveWithVertexClustering(
1100
- {
1101
- indices: index ? Array.from(index.array) : Array.from({ length: vertexCount }, (_, value) => value),
1102
- material: {
1103
- color: "#ffffff",
1104
- id: "material:model-simplify",
1105
- metallicFactor: 0,
1106
- name: "Model Simplify",
1107
- roughnessFactor: 1
1108
- },
1109
- normals: Array.from(normalAttribute.array),
1110
- positions: Array.from(positionAttribute.array),
1111
- uvs: uvAttribute ? Array.from(uvAttribute.array) : []
1112
- },
1113
- ratio,
1114
- bounds
1115
- );
1116
- if (workingGeometry !== geometry) {
1117
- workingGeometry.dispose();
1118
- }
1119
- if (!simplified) {
1120
- return void 0;
1121
- }
1122
- const simplifiedGeometry = createBufferGeometryFromPrimitive(simplified);
1123
- simplifiedGeometry.computeBoundingBox();
1124
- simplifiedGeometry.computeBoundingSphere();
1125
- return simplifiedGeometry;
1126
- }
1127
- async function exportModelSceneAsGlb(object) {
1128
- try {
1129
- return await exportGlbBytesFromObject(object);
1130
- } catch {
1131
- return await exportGlbBytesFromObject(stripTextureReferencesFromObject(object.clone(true)));
1132
- }
1133
- }
1134
- async function exportGlbBytesFromObject(object) {
1135
- const scene = new Scene();
1136
- scene.add(object);
1137
- const exported = await gltfExporter.parseAsync(scene, {
1138
- binary: true,
1139
- includeCustomExtensions: false
1140
- });
1141
- if (!(exported instanceof ArrayBuffer)) {
1142
- throw new Error("Expected GLB binary output for baked model LOD.");
1143
- }
1144
- return new Uint8Array(exported);
1145
- }
1146
- function stripTextureReferencesFromObject(object) {
1147
- object.traverse((child) => {
1148
- if (!(child instanceof Mesh)) {
1149
- return;
1150
- }
1151
- const strip = (material) => {
1152
- const clone = material.clone();
1153
- clone.alphaMap = null;
1154
- clone.aoMap = null;
1155
- clone.bumpMap = null;
1156
- clone.displacementMap = null;
1157
- clone.emissiveMap = null;
1158
- clone.lightMap = null;
1159
- clone.map = null;
1160
- clone.metalnessMap = null;
1161
- clone.normalMap = null;
1162
- clone.roughnessMap = null;
1163
- return clone;
1164
- };
1165
- if (Array.isArray(child.material)) {
1166
- child.material = child.material.map(
1167
- (material) => material instanceof MeshStandardMaterial ? strip(material) : new MeshStandardMaterial({
1168
- color: "color" in material ? material.color : "#7f8ea3",
1169
- metalness: "metalness" in material && typeof material.metalness === "number" ? material.metalness : 0.1,
1170
- roughness: "roughness" in material && typeof material.roughness === "number" ? material.roughness : 0.8
1171
- })
1172
- );
1173
- return;
1174
- }
1175
- const fallbackMaterial = child.material;
1176
- child.material = child.material instanceof MeshStandardMaterial ? strip(child.material) : new MeshStandardMaterial({
1177
- color: "color" in fallbackMaterial ? fallbackMaterial.color : "#7f8ea3",
1178
- metalness: "metalness" in fallbackMaterial && typeof fallbackMaterial.metalness === "number" ? fallbackMaterial.metalness : 0.1,
1179
- roughness: "roughness" in fallbackMaterial && typeof fallbackMaterial.roughness === "number" ? fallbackMaterial.roughness : 0.8
1180
- });
1181
- });
1182
- return object;
1183
- }
1184
- function createGeneratedModelLodAsset(asset, name, nodeId, level, bytes) {
1185
- return {
1186
- id: `asset:model-lod:${slugify2(`${name}-${nodeId}`)}:${level}`,
1187
- metadata: {
1188
- ...asset.metadata,
1189
- lodGenerated: true,
1190
- lodLevel: level,
1191
- lodSourceAssetId: asset.id,
1192
- materialMtlText: "",
1193
- modelFormat: "glb",
1194
- texturePath: ""
1195
- },
1196
- path: createBinaryDataUrl(bytes, "model/gltf-binary"),
1197
- type: "model"
1198
- };
1199
- }
1200
- function createBinaryDataUrl(bytes, mimeType) {
1201
- let binary = "";
1202
- const chunkSize = 32768;
1203
- for (let index = 0; index < bytes.length; index += chunkSize) {
1204
- binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
1205
- }
1206
- return `data:${mimeType};base64,${btoa(binary)}`;
1207
- }
1208
- function sanitizeInstanceTransform(transform) {
1209
- return {
1210
- position: structuredClone(transform.position),
1211
- rotation: structuredClone(transform.rotation),
1212
- scale: structuredClone(transform.scale)
1213
- };
1214
- }
1215
- function resolveModelAssetFormat(asset) {
1216
- const format = readModelAssetString(asset, "modelFormat")?.toLowerCase();
1217
- return format === "obj" || asset.path.toLowerCase().endsWith(".obj") ? "obj" : "gltf";
1218
- }
1219
- function readModelAssetString(asset, key) {
1220
- const value = asset?.metadata[key];
1221
- return typeof value === "string" && value.length > 0 ? value : void 0;
1222
- }
1223
- function patchMtlTextureReferences(mtlText, texturePath) {
1224
- if (!texturePath) {
1225
- return mtlText;
1226
- }
1227
- const mapPattern = /^(map_Ka|map_Kd|map_d|map_Bump|bump)\s+.+$/gm;
1228
- const hasDiffuseMap = /^map_Kd\s+.+$/m.test(mtlText);
1229
- const normalized = mtlText.replace(mapPattern, (line) => {
1230
- if (line.startsWith("map_Kd ")) {
1231
- return `map_Kd ${texturePath}`;
1232
- }
1233
- return line;
1234
- });
1235
- return hasDiffuseMap ? normalized : `${normalized.trim()}
1236
- map_Kd ${texturePath}
1237
- `;
1238
- }
1239
- function slugify2(value) {
1240
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1241
- return normalized || "model";
1242
- }
1243
- function simplifyExportGeometry(geometry, ratio) {
1244
- const primitives = geometry.primitives.map((primitive) => simplifyExportPrimitive(primitive, ratio)).filter((primitive) => primitive !== void 0);
1245
- return primitives.length ? { primitives } : void 0;
1246
- }
1247
- function simplifyExportPrimitive(primitive, ratio) {
1248
- const vertexCount = Math.floor(primitive.positions.length / 3);
1249
- const triangleCount = Math.floor(primitive.indices.length / 3);
1250
- if (vertexCount < 12 || triangleCount < 8 || ratio >= 0.98) {
1251
- return void 0;
1252
- }
1253
- const geometry = createBufferGeometryFromPrimitive(primitive);
1254
- const boundsTree = new MeshBVH(geometry, { maxLeafSize: 12, setBoundingBox: true });
1255
- const bounds = boundsTree.getBoundingBox(new Box3());
1256
- const simplified = simplifyPrimitiveWithVertexClustering(primitive, ratio, bounds);
1257
- geometry.dispose();
1258
- if (!simplified) {
1259
- return void 0;
1260
- }
1261
- return simplified;
1262
- }
1263
- function createBufferGeometryFromPrimitive(primitive) {
1264
- const geometry = new BufferGeometry();
1265
- geometry.setAttribute("position", new Float32BufferAttribute(primitive.positions, 3));
1266
- geometry.setAttribute("normal", new Float32BufferAttribute(primitive.normals, 3));
1267
- if (primitive.uvs.length) {
1268
- geometry.setAttribute("uv", new Float32BufferAttribute(primitive.uvs, 2));
1269
- }
1270
- geometry.setIndex(primitive.indices);
1271
- return geometry;
1272
- }
1273
- function simplifyPrimitiveWithVertexClustering(primitive, ratio, bounds) {
1274
- const targetVertexCount = Math.max(8, Math.floor(primitive.positions.length / 3 * Math.max(0.04, ratio)));
1275
- const size = bounds.getSize(new Vector32());
1276
- let resolution = Math.max(1, Math.round(Math.cbrt(targetVertexCount)));
1277
- let best;
1278
- for (let attempt = 0; attempt < 5; attempt += 1) {
1279
- const simplified = clusterPrimitiveVertices(primitive, bounds, size, Math.max(1, resolution - attempt));
1280
- if (!simplified) {
1281
- continue;
1282
- }
1283
- best = simplified;
1284
- if (simplified.positions.length / 3 <= targetVertexCount) {
1285
- break;
1286
- }
1287
- }
1288
- if (!best) {
1289
- return void 0;
1290
- }
1291
- if (best.positions.length >= primitive.positions.length || best.indices.length >= primitive.indices.length) {
1292
- return void 0;
1293
- }
1294
- return best;
1295
- }
1296
- function clusterPrimitiveVertices(primitive, bounds, size, resolution) {
1297
- const min = bounds.min;
1298
- const cellSizeX = Math.max(size.x / resolution, 1e-4);
1299
- const cellSizeY = Math.max(size.y / resolution, 1e-4);
1300
- const cellSizeZ = Math.max(size.z / resolution, 1e-4);
1301
- const vertexCount = primitive.positions.length / 3;
1302
- const clusters = /* @__PURE__ */ new Map();
1303
- const clusterKeyByVertex = new Array(vertexCount);
1304
- for (let vertexIndex = 0; vertexIndex < vertexCount; vertexIndex += 1) {
1305
- const positionOffset = vertexIndex * 3;
1306
- const uvOffset = vertexIndex * 2;
1307
- const x = primitive.positions[positionOffset];
1308
- const y = primitive.positions[positionOffset + 1];
1309
- const z = primitive.positions[positionOffset + 2];
1310
- const normalX = primitive.normals[positionOffset];
1311
- const normalY = primitive.normals[positionOffset + 1];
1312
- const normalZ = primitive.normals[positionOffset + 2];
1313
- const cellX = Math.floor((x - min.x) / cellSizeX);
1314
- const cellY = Math.floor((y - min.y) / cellSizeY);
1315
- const cellZ = Math.floor((z - min.z) / cellSizeZ);
1316
- const clusterKey = `${cellX}:${cellY}:${cellZ}:${resolveNormalBucket(normalX, normalY, normalZ)}`;
1317
- const cluster = clusters.get(clusterKey) ?? {
1318
- count: 0,
1319
- normalX: 0,
1320
- normalY: 0,
1321
- normalZ: 0,
1322
- positionX: 0,
1323
- positionY: 0,
1324
- positionZ: 0,
1325
- uvX: 0,
1326
- uvY: 0
1327
- };
1328
- cluster.count += 1;
1329
- cluster.positionX += x;
1330
- cluster.positionY += y;
1331
- cluster.positionZ += z;
1332
- cluster.normalX += normalX;
1333
- cluster.normalY += normalY;
1334
- cluster.normalZ += normalZ;
1335
- cluster.uvX += primitive.uvs[uvOffset] ?? 0;
1336
- cluster.uvY += primitive.uvs[uvOffset + 1] ?? 0;
1337
- clusters.set(clusterKey, cluster);
1338
- clusterKeyByVertex[vertexIndex] = clusterKey;
1339
- }
1340
- const remappedIndices = [];
1341
- const positions = [];
1342
- const normals = [];
1343
- const uvs = [];
1344
- const clusterIndexByKey = /* @__PURE__ */ new Map();
1345
- const ensureClusterIndex = (clusterKey) => {
1346
- const existing = clusterIndexByKey.get(clusterKey);
1347
- if (existing !== void 0) {
1348
- return existing;
1349
- }
1350
- const cluster = clusters.get(clusterKey);
1351
- if (!cluster || cluster.count === 0) {
1352
- return void 0;
1353
- }
1354
- const averagedNormal = normalizeVec3(vec3(cluster.normalX, cluster.normalY, cluster.normalZ));
1355
- const index = positions.length / 3;
1356
- positions.push(cluster.positionX / cluster.count, cluster.positionY / cluster.count, cluster.positionZ / cluster.count);
1357
- normals.push(averagedNormal.x, averagedNormal.y, averagedNormal.z);
1358
- uvs.push(cluster.uvX / cluster.count, cluster.uvY / cluster.count);
1359
- clusterIndexByKey.set(clusterKey, index);
1360
- return index;
1361
- };
1362
- for (let index = 0; index < primitive.indices.length; index += 3) {
1363
- const a = ensureClusterIndex(clusterKeyByVertex[primitive.indices[index]]);
1364
- const b = ensureClusterIndex(clusterKeyByVertex[primitive.indices[index + 1]]);
1365
- const c = ensureClusterIndex(clusterKeyByVertex[primitive.indices[index + 2]]);
1366
- if (a === void 0 || b === void 0 || c === void 0) {
1367
- continue;
1368
- }
1369
- if (a === b || b === c || a === c) {
1370
- continue;
1371
- }
1372
- if (triangleArea(positions, a, b, c) <= 1e-6) {
1373
- continue;
1374
- }
1375
- remappedIndices.push(a, b, c);
1376
- }
1377
- if (remappedIndices.length < 12 || positions.length >= primitive.positions.length) {
1378
- return void 0;
1379
- }
1380
- return {
1381
- indices: remappedIndices,
1382
- material: primitive.material,
1383
- normals,
1384
- positions,
1385
- uvs
1386
- };
1387
- }
1388
- function resolveNormalBucket(x, y, z) {
1389
- const ax = Math.abs(x);
1390
- const ay = Math.abs(y);
1391
- const az = Math.abs(z);
1392
- if (ax >= ay && ax >= az) {
1393
- return x >= 0 ? "xp" : "xn";
1394
- }
1395
- if (ay >= ax && ay >= az) {
1396
- return y >= 0 ? "yp" : "yn";
1397
- }
1398
- return z >= 0 ? "zp" : "zn";
1399
- }
1400
- function triangleArea(positions, a, b, c) {
1401
- const ax = positions[a * 3];
1402
- const ay = positions[a * 3 + 1];
1403
- const az = positions[a * 3 + 2];
1404
- const bx = positions[b * 3];
1405
- const by = positions[b * 3 + 1];
1406
- const bz = positions[b * 3 + 2];
1407
- const cx = positions[c * 3];
1408
- const cy = positions[c * 3 + 1];
1409
- const cz = positions[c * 3 + 2];
1410
- const ab = vec3(bx - ax, by - ay, bz - az);
1411
- const ac = vec3(cx - ax, cy - ay, cz - az);
1412
- const cross = crossVec3(ab, ac);
1413
- return Math.sqrt(cross.x * cross.x + cross.y * cross.y + cross.z * cross.z) * 0.5;
1414
- }
1415
- function buildPrimitiveGeometry(shape, size, radialSegments) {
1416
- const geometry = shape === "cube" ? new BoxGeometry(Math.abs(size.x), Math.abs(size.y), Math.abs(size.z)) : shape === "sphere" ? new SphereGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, radialSegments, Math.max(8, Math.floor(radialSegments * 0.75))) : shape === "cylinder" ? new CylinderGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments) : new ConeGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments);
1417
- const positionAttribute = geometry.getAttribute("position");
1418
- const normalAttribute = geometry.getAttribute("normal");
1419
- const uvAttribute = geometry.getAttribute("uv");
1420
- const index = geometry.getIndex();
1421
- const primitive = {
1422
- indices: index ? Array.from(index.array) : Array.from({ length: positionAttribute.count }, (_, value) => value),
1423
- normals: Array.from(normalAttribute.array),
1424
- positions: Array.from(positionAttribute.array),
1425
- uvs: uvAttribute ? Array.from(uvAttribute.array) : []
1426
- };
1427
- geometry.dispose();
1428
- return primitive;
1429
- }
1430
- async function resolveRuntimeMaterial(material) {
1431
- const resolved = material ?? {
1432
- color: "#ffffff",
1433
- id: "material:fallback:default",
1434
- metalness: 0.05,
1435
- name: "Default Material",
1436
- roughness: 0.8
1437
- };
1438
- return {
1439
- baseColorTexture: await resolveEmbeddedTextureUri(resolved.colorTexture ?? resolveGeneratedBlockoutTexture(resolved)),
1440
- color: resolved.color,
1441
- id: resolved.id,
1442
- metallicFactor: resolved.metalness ?? 0,
1443
- metallicRoughnessTexture: await createMetallicRoughnessTextureDataUri(
1444
- resolved.metalnessTexture,
1445
- resolved.roughnessTexture,
1446
- resolved.metalness ?? 0,
1447
- resolved.roughness ?? 0.8
1448
- ),
1449
- name: resolved.name,
1450
- normalTexture: await resolveEmbeddedTextureUri(resolved.normalTexture),
1451
- roughnessFactor: resolved.roughness ?? 0.8,
1452
- side: resolved.side
1453
- };
1454
- }
1455
- function resolveGeneratedBlockoutTexture(material) {
1456
- return material.category === "blockout" ? createBlockoutTextureDataUri(material.color, material.edgeColor ?? "#2f3540", material.edgeThickness ?? 0.035) : void 0;
1457
- }
1458
- function projectPlanarUvs(vertices, normal, uvScale, uvOffset) {
1459
- const basis = createFacePlaneBasis(normal);
1460
- const origin = vertices[0] ?? vec3(0, 0, 0);
1461
- const scaleX = Math.abs(uvScale?.x ?? 1) <= 1e-4 ? 1 : uvScale?.x ?? 1;
1462
- const scaleY = Math.abs(uvScale?.y ?? 1) <= 1e-4 ? 1 : uvScale?.y ?? 1;
1463
- const offsetX = uvOffset?.x ?? 0;
1464
- const offsetY = uvOffset?.y ?? 0;
1465
- return vertices.flatMap((vertex) => {
1466
- const offset = subVec3(vertex, origin);
1467
- return [dotVec3(offset, basis.u) * scaleX + offsetX, dotVec3(offset, basis.v) * scaleY + offsetY];
1468
- });
1469
- }
1470
- function createFacePlaneBasis(normal) {
1471
- const normalizedNormal = normalizeVec3(normal);
1472
- const reference = Math.abs(normalizedNormal.y) < 0.99 ? vec3(0, 1, 0) : vec3(1, 0, 0);
1473
- const u = normalizeVec3(crossVec3(reference, normalizedNormal));
1474
- const v = normalizeVec3(crossVec3(normalizedNormal, u));
1475
- return { u, v };
1476
- }
1477
- async function resolveEmbeddedTextureUri(source) {
1478
- if (!source) {
1479
- return void 0;
1480
- }
1481
- if (source.startsWith("data:")) {
1482
- return source;
1483
- }
1484
- const response = await fetch(source);
1485
- const blob = await response.blob();
1486
- const buffer = await blob.arrayBuffer();
1487
- return `data:${blob.type || "application/octet-stream"};base64,${toBase64(new Uint8Array(buffer))}`;
1488
- }
1489
- async function createMetallicRoughnessTextureDataUri(metalnessSource, roughnessSource, metalnessFactor, roughnessFactor) {
1490
- if (!metalnessSource && !roughnessSource) {
1491
- return void 0;
1492
- }
1493
- if (typeof OffscreenCanvas === "undefined" || typeof createImageBitmap === "undefined") {
1494
- return void 0;
1495
- }
1496
- const [metalness, roughness] = await Promise.all([
1497
- loadImagePixels(metalnessSource),
1498
- loadImagePixels(roughnessSource)
1499
- ]);
1500
- const width = Math.max(metalness?.width ?? 1, roughness?.width ?? 1);
1501
- const height = Math.max(metalness?.height ?? 1, roughness?.height ?? 1);
1502
- const canvas = new OffscreenCanvas(width, height);
1503
- const context = canvas.getContext("2d");
1504
- if (!context) {
1505
- return void 0;
1506
- }
1507
- const imageData = context.createImageData(width, height);
1508
- const metalDefault = Math.round(clamp01(metalnessFactor) * 255);
1509
- const roughDefault = Math.round(clamp01(roughnessFactor) * 255);
1510
- for (let index = 0; index < imageData.data.length; index += 4) {
1511
- imageData.data[index] = 0;
1512
- imageData.data[index + 1] = roughness?.pixels[index] ?? roughDefault;
1513
- imageData.data[index + 2] = metalness?.pixels[index] ?? metalDefault;
1514
- imageData.data[index + 3] = 255;
1515
- }
1516
- context.putImageData(imageData, 0, 0);
1517
- const blob = await canvas.convertToBlob({ type: "image/png" });
1518
- const buffer = await blob.arrayBuffer();
1519
- return `data:image/png;base64,${toBase64(new Uint8Array(buffer))}`;
1520
- }
1521
- async function loadImagePixels(source) {
1522
- if (!source || typeof OffscreenCanvas === "undefined" || typeof createImageBitmap === "undefined") {
1523
- return void 0;
1524
- }
1525
- const response = await fetch(source);
1526
- const blob = await response.blob();
1527
- const bitmap = await createImageBitmap(blob);
1528
- const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
1529
- const context = canvas.getContext("2d", { willReadFrequently: true });
1530
- if (!context) {
1531
- bitmap.close();
1532
- return void 0;
1533
- }
1534
- context.drawImage(bitmap, 0, 0);
1535
- bitmap.close();
1536
- const imageData = context.getImageData(0, 0, bitmap.width, bitmap.height);
1537
- return {
1538
- height: imageData.height,
1539
- pixels: imageData.data,
1540
- width: imageData.width
1541
- };
1542
- }
1543
- function clamp01(value) {
1544
- return Math.max(0, Math.min(1, value));
1545
- }
1546
- function toBase64(bytes) {
1547
- let binary = "";
1548
- bytes.forEach((byte) => {
1549
- binary += String.fromCharCode(byte);
1550
- });
1551
- return btoa(binary);
1552
- }
1553
-
1554
57
  // src/export-tasks.ts
1555
- import { MeshBVH as MeshBVH2 } from "three-mesh-bvh";
58
+ import { getFaceVertices, reconstructBrushFaces, triangulateMeshFace } from "@ggez/geometry-kernel";
59
+ import {
60
+ createBlockoutTextureDataUri,
61
+ crossVec3,
62
+ dotVec3,
63
+ isBrushNode,
64
+ isGroupNode,
65
+ isInstancingNode,
66
+ isMeshNode,
67
+ isModelNode,
68
+ isPrimitiveNode,
69
+ normalizeVec3,
70
+ resolveInstancingSourceNode,
71
+ subVec3,
72
+ vec3
73
+ } from "@ggez/shared";
1556
74
  import {
1557
- Box3 as Box32,
1558
- BoxGeometry as BoxGeometry2,
1559
- BufferGeometry as BufferGeometry2,
1560
- ConeGeometry as ConeGeometry2,
1561
- CylinderGeometry as CylinderGeometry2,
1562
- Euler as Euler2,
1563
- Float32BufferAttribute as Float32BufferAttribute2,
1564
- Group as Group2,
1565
- Mesh as Mesh2,
1566
- MeshStandardMaterial as MeshStandardMaterial2,
1567
- Quaternion as Quaternion2,
1568
- RepeatWrapping as RepeatWrapping2,
1569
- Scene as Scene2,
1570
- SphereGeometry as SphereGeometry2,
1571
- SRGBColorSpace as SRGBColorSpace2,
1572
- TextureLoader as TextureLoader2,
1573
- Vector3 as Vector33
75
+ buildRuntimeBundleFromSnapshot,
76
+ buildRuntimeSceneFromSnapshot,
77
+ serializeRuntimeScene
78
+ } from "@ggez/runtime-build";
79
+ import { MeshBVH } from "three-mesh-bvh";
80
+ import {
81
+ Box3,
82
+ BoxGeometry,
83
+ BufferGeometry,
84
+ ConeGeometry,
85
+ CylinderGeometry,
86
+ Euler,
87
+ Float32BufferAttribute,
88
+ Group,
89
+ Mesh,
90
+ MeshStandardMaterial,
91
+ Quaternion,
92
+ RepeatWrapping,
93
+ Scene,
94
+ SphereGeometry,
95
+ SRGBColorSpace,
96
+ TextureLoader,
97
+ Vector3
1574
98
  } from "three";
1575
- import { GLTFExporter as GLTFExporter2 } from "three/examples/jsm/exporters/GLTFExporter.js";
1576
- import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
1577
- import { MTLLoader as MTLLoader2 } from "three/examples/jsm/loaders/MTLLoader.js";
1578
- import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
1579
- var gltfLoader2 = new GLTFLoader2();
1580
- var gltfExporter2 = new GLTFExporter2();
1581
- var mtlLoader2 = new MTLLoader2();
1582
- var modelTextureLoader2 = new TextureLoader2();
99
+ import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
100
+ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
101
+ import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader.js";
102
+ import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
103
+ var gltfLoader = new GLTFLoader();
104
+ var gltfExporter = new GLTFExporter();
105
+ var mtlLoader = new MTLLoader();
106
+ var modelTextureLoader = new TextureLoader();
1583
107
  async function executeWorkerRequest(request) {
1584
108
  try {
1585
109
  if (request.kind === "whmap-save") {
@@ -1689,7 +213,7 @@ async function serializeGltfScene(snapshot) {
1689
213
  continue;
1690
214
  }
1691
215
  if (isBrushNode(node) || isMeshNode(node) || isPrimitiveNode(node)) {
1692
- const geometry = await buildExportGeometry2(node, materialsById);
216
+ const geometry = await buildExportGeometry(node, materialsById);
1693
217
  if (geometry.primitives.length === 0) {
1694
218
  continue;
1695
219
  }
@@ -1713,7 +237,7 @@ async function serializeGltfScene(snapshot) {
1713
237
  if (!sourceNode || !(isBrushNode(sourceNode) || isMeshNode(sourceNode) || isPrimitiveNode(sourceNode) || isModelNode(sourceNode))) {
1714
238
  continue;
1715
239
  }
1716
- const instanceTransform = sanitizeInstanceTransform2(node.transform);
240
+ const instanceTransform = sanitizeInstanceTransform(node.transform);
1717
241
  if (isModelNode(sourceNode)) {
1718
242
  const previewColor = assetsById.get(sourceNode.data.assetId)?.metadata.previewColor;
1719
243
  const primitive = createCylinderPrimitive();
@@ -1746,7 +270,7 @@ async function serializeGltfScene(snapshot) {
1746
270
  });
1747
271
  continue;
1748
272
  }
1749
- const geometry = await buildExportGeometry2(sourceNode, materialsById);
273
+ const geometry = await buildExportGeometry(sourceNode, materialsById);
1750
274
  if (geometry.primitives.length === 0) {
1751
275
  continue;
1752
276
  }
@@ -1947,7 +471,7 @@ async function buildGltfDocument(exportedNodes) {
1947
471
  buffers: [
1948
472
  {
1949
473
  byteLength: merged.byteLength,
1950
- uri: `data:application/octet-stream;base64,${toBase642(merged)}`
474
+ uri: `data:application/octet-stream;base64,${toBase64(merged)}`
1951
475
  }
1952
476
  ],
1953
477
  images,
@@ -1965,7 +489,7 @@ async function buildGltfDocument(exportedNodes) {
1965
489
  };
1966
490
  return JSON.stringify(gltf, null, 2);
1967
491
  }
1968
- async function buildExportGeometry2(node, materialsById) {
492
+ async function buildExportGeometry(node, materialsById) {
1969
493
  const fallbackMaterial = await resolveExportMaterial({
1970
494
  color: node.kind === "brush" ? "#f69036" : node.kind === "primitive" && node.data.role === "prop" ? "#7f8ea3" : "#6ed5c0",
1971
495
  id: `material:fallback:${node.id}`,
@@ -1984,7 +508,7 @@ async function buildExportGeometry2(node, materialsById) {
1984
508
  uvs: []
1985
509
  };
1986
510
  const vertexOffset = primitive.positions.length / 3;
1987
- const uvs = params.uvs && params.uvs.length === params.vertices.length ? params.uvs.flatMap((uv) => [uv.x, uv.y]) : projectPlanarUvs2(params.vertices, params.normal, params.uvScale, params.uvOffset);
511
+ const uvs = params.uvs && params.uvs.length === params.vertices.length ? params.uvs.flatMap((uv) => [uv.x, uv.y]) : projectPlanarUvs(params.vertices, params.normal, params.uvScale, params.uvOffset);
1988
512
  params.vertices.forEach((vertex) => {
1989
513
  primitive.positions.push(vertex.x, vertex.y, vertex.z);
1990
514
  primitive.normals.push(params.normal.x, params.normal.y, params.normal.z);
@@ -2030,7 +554,7 @@ async function buildExportGeometry2(node, materialsById) {
2030
554
  }
2031
555
  if (isPrimitiveNode(node)) {
2032
556
  const material = node.data.materialId ? await resolveExportMaterial(materialsById.get(node.data.materialId)) : fallbackMaterial;
2033
- const primitive = buildPrimitiveGeometry2(node.data.shape, node.data.size, node.data.radialSegments ?? 24);
557
+ const primitive = buildPrimitiveGeometry(node.data.shape, node.data.size, node.data.radialSegments ?? 24);
2034
558
  if (primitive) {
2035
559
  primitiveByMaterial.set(material.id, {
2036
560
  indices: primitive.indices,
@@ -2045,15 +569,15 @@ async function buildExportGeometry2(node, materialsById) {
2045
569
  primitives: Array.from(primitiveByMaterial.values())
2046
570
  };
2047
571
  }
2048
- function sanitizeInstanceTransform2(transform) {
572
+ function sanitizeInstanceTransform(transform) {
2049
573
  return {
2050
574
  position: structuredClone(transform.position),
2051
575
  rotation: structuredClone(transform.rotation),
2052
576
  scale: structuredClone(transform.scale)
2053
577
  };
2054
578
  }
2055
- function buildPrimitiveGeometry2(shape, size, radialSegments) {
2056
- const geometry = shape === "cube" ? new BoxGeometry2(Math.abs(size.x), Math.abs(size.y), Math.abs(size.z)) : shape === "sphere" ? new SphereGeometry2(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, radialSegments, Math.max(8, Math.floor(radialSegments * 0.75))) : shape === "cylinder" ? new CylinderGeometry2(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments) : new ConeGeometry2(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments);
579
+ function buildPrimitiveGeometry(shape, size, radialSegments) {
580
+ const geometry = shape === "cube" ? new BoxGeometry(Math.abs(size.x), Math.abs(size.y), Math.abs(size.z)) : shape === "sphere" ? new SphereGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, radialSegments, Math.max(8, Math.floor(radialSegments * 0.75))) : shape === "cylinder" ? new CylinderGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments) : new ConeGeometry(Math.max(Math.abs(size.x), Math.abs(size.z)) * 0.5, Math.abs(size.y), radialSegments);
2057
581
  const positionAttribute = geometry.getAttribute("position");
2058
582
  const normalAttribute = geometry.getAttribute("normal");
2059
583
  const uvAttribute = geometry.getAttribute("uv");
@@ -2076,23 +600,23 @@ async function resolveExportMaterial(material) {
2076
600
  roughness: 0.8
2077
601
  };
2078
602
  return {
2079
- baseColorTexture: await resolveEmbeddedTextureUri2(resolved.colorTexture ?? resolveGeneratedBlockoutTexture2(resolved)),
603
+ baseColorTexture: await resolveEmbeddedTextureUri(resolved.colorTexture ?? resolveGeneratedBlockoutTexture(resolved)),
2080
604
  color: resolved.color,
2081
605
  id: resolved.id,
2082
606
  metallicFactor: resolved.metalness ?? 0,
2083
- metallicRoughnessTexture: await createMetallicRoughnessTextureDataUri2(
607
+ metallicRoughnessTexture: await createMetallicRoughnessTextureDataUri(
2084
608
  resolved.metalnessTexture,
2085
609
  resolved.roughnessTexture,
2086
610
  resolved.metalness ?? 0,
2087
611
  resolved.roughness ?? 0.8
2088
612
  ),
2089
613
  name: resolved.name,
2090
- normalTexture: await resolveEmbeddedTextureUri2(resolved.normalTexture),
614
+ normalTexture: await resolveEmbeddedTextureUri(resolved.normalTexture),
2091
615
  roughnessFactor: resolved.roughness ?? 0.8,
2092
616
  side: resolved.side
2093
617
  };
2094
618
  }
2095
- function resolveGeneratedBlockoutTexture2(material) {
619
+ function resolveGeneratedBlockoutTexture(material) {
2096
620
  return material.category === "blockout" ? createBlockoutTextureDataUri(material.color, material.edgeColor ?? "#2f3540", material.edgeThickness ?? 0.035) : void 0;
2097
621
  }
2098
622
  async function ensureGltfMaterial(material, materials, textures, images, imageIndexByUri, textureIndexByUri, materialIndexById) {
@@ -2133,8 +657,8 @@ function ensureGltfTexture(uri, textures, images, imageIndexByUri, textureIndexB
2133
657
  textureIndexByUri.set(uri, textureIndex);
2134
658
  return textureIndex;
2135
659
  }
2136
- function projectPlanarUvs2(vertices, normal, uvScale, uvOffset) {
2137
- const basis = createFacePlaneBasis2(normal);
660
+ function projectPlanarUvs(vertices, normal, uvScale, uvOffset) {
661
+ const basis = createFacePlaneBasis(normal);
2138
662
  const origin = vertices[0] ?? vec3(0, 0, 0);
2139
663
  const scaleX = Math.abs(uvScale?.x ?? 1) <= 1e-4 ? 1 : uvScale?.x ?? 1;
2140
664
  const scaleY = Math.abs(uvScale?.y ?? 1) <= 1e-4 ? 1 : uvScale?.y ?? 1;
@@ -2145,14 +669,14 @@ function projectPlanarUvs2(vertices, normal, uvScale, uvOffset) {
2145
669
  return [dotVec3(offset, basis.u) * scaleX + offsetX, dotVec3(offset, basis.v) * scaleY + offsetY];
2146
670
  });
2147
671
  }
2148
- function createFacePlaneBasis2(normal) {
672
+ function createFacePlaneBasis(normal) {
2149
673
  const normalizedNormal = normalizeVec3(normal);
2150
674
  const reference = Math.abs(normalizedNormal.y) < 0.99 ? vec3(0, 1, 0) : vec3(1, 0, 0);
2151
675
  const u = normalizeVec3(crossVec3(reference, normalizedNormal));
2152
676
  const v = normalizeVec3(crossVec3(normalizedNormal, u));
2153
677
  return { u, v };
2154
678
  }
2155
- async function resolveEmbeddedTextureUri2(source) {
679
+ async function resolveEmbeddedTextureUri(source) {
2156
680
  if (!source) {
2157
681
  return void 0;
2158
682
  }
@@ -2162,9 +686,9 @@ async function resolveEmbeddedTextureUri2(source) {
2162
686
  const response = await fetch(source);
2163
687
  const blob = await response.blob();
2164
688
  const buffer = await blob.arrayBuffer();
2165
- return `data:${blob.type || "application/octet-stream"};base64,${toBase642(new Uint8Array(buffer))}`;
689
+ return `data:${blob.type || "application/octet-stream"};base64,${toBase64(new Uint8Array(buffer))}`;
2166
690
  }
2167
- async function createMetallicRoughnessTextureDataUri2(metalnessSource, roughnessSource, metalnessFactor, roughnessFactor) {
691
+ async function createMetallicRoughnessTextureDataUri(metalnessSource, roughnessSource, metalnessFactor, roughnessFactor) {
2168
692
  if (!metalnessSource && !roughnessSource) {
2169
693
  return void 0;
2170
694
  }
@@ -2172,8 +696,8 @@ async function createMetallicRoughnessTextureDataUri2(metalnessSource, roughness
2172
696
  return void 0;
2173
697
  }
2174
698
  const [metalness, roughness] = await Promise.all([
2175
- loadImagePixels2(metalnessSource),
2176
- loadImagePixels2(roughnessSource)
699
+ loadImagePixels(metalnessSource),
700
+ loadImagePixels(roughnessSource)
2177
701
  ]);
2178
702
  const width = Math.max(metalness?.width ?? 1, roughness?.width ?? 1);
2179
703
  const height = Math.max(metalness?.height ?? 1, roughness?.height ?? 1);
@@ -2183,8 +707,8 @@ async function createMetallicRoughnessTextureDataUri2(metalnessSource, roughness
2183
707
  return void 0;
2184
708
  }
2185
709
  const imageData = context.createImageData(width, height);
2186
- const metalDefault = Math.round(clamp012(metalnessFactor) * 255);
2187
- const roughDefault = Math.round(clamp012(roughnessFactor) * 255);
710
+ const metalDefault = Math.round(clamp01(metalnessFactor) * 255);
711
+ const roughDefault = Math.round(clamp01(roughnessFactor) * 255);
2188
712
  for (let index = 0; index < imageData.data.length; index += 4) {
2189
713
  imageData.data[index] = 0;
2190
714
  imageData.data[index + 1] = roughness?.pixels[index] ?? roughDefault;
@@ -2194,9 +718,9 @@ async function createMetallicRoughnessTextureDataUri2(metalnessSource, roughness
2194
718
  context.putImageData(imageData, 0, 0);
2195
719
  const blob = await canvas.convertToBlob({ type: "image/png" });
2196
720
  const buffer = await blob.arrayBuffer();
2197
- return `data:image/png;base64,${toBase642(new Uint8Array(buffer))}`;
721
+ return `data:image/png;base64,${toBase64(new Uint8Array(buffer))}`;
2198
722
  }
2199
- async function loadImagePixels2(source) {
723
+ async function loadImagePixels(source) {
2200
724
  if (!source || typeof OffscreenCanvas === "undefined" || typeof createImageBitmap === "undefined") {
2201
725
  return void 0;
2202
726
  }
@@ -2250,11 +774,11 @@ function computeCylinderUvs(positions) {
2250
774
  }
2251
775
  return uvs;
2252
776
  }
2253
- function clamp012(value) {
777
+ function clamp01(value) {
2254
778
  return Math.max(0, Math.min(1, value));
2255
779
  }
2256
780
  function toQuaternion(rotation) {
2257
- const quaternion = new Quaternion2().setFromEuler(new Euler2(rotation.x, rotation.y, rotation.z, "XYZ"));
781
+ const quaternion = new Quaternion().setFromEuler(new Euler(rotation.x, rotation.y, rotation.z, "XYZ"));
2258
782
  return [quaternion.x, quaternion.y, quaternion.z, quaternion.w];
2259
783
  }
2260
784
  function createCylinderPrimitive() {
@@ -2292,7 +816,7 @@ function computePositionBounds(positions) {
2292
816
  }
2293
817
  return { max, min };
2294
818
  }
2295
- function toBase642(bytes) {
819
+ function toBase64(bytes) {
2296
820
  let binary = "";
2297
821
  bytes.forEach((byte) => {
2298
822
  binary += String.fromCharCode(byte);