@adatechnology/game-animals-three 0.2.1 → 0.3.1

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.
@@ -92,6 +92,1532 @@ var GameAnimalsThree = (() => {
92
92
  return outcome === "dies" ? "dead" : "alive";
93
93
  };
94
94
 
95
+ // ../game-contracts/dist/index.js
96
+ var KIT_ORIGIN_KEY = "kitOrigin";
97
+ function markKitOrigin(object2, origin) {
98
+ const node = object2;
99
+ node.userData = node.userData ?? {};
100
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
101
+ return object2;
102
+ }
103
+
104
+ // ../game-shapes/dist/chunk-R4RY23RH.js
105
+ function canonicalJson(value) {
106
+ if (value === null) return "null";
107
+ if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
108
+ if (typeof value === "string") return JSON.stringify(value);
109
+ if (typeof value === "boolean") return value ? "true" : "false";
110
+ if (typeof value === "function") throw new Error("game-shapes: a function cannot be part of a shape (register it as a generator)");
111
+ if (ArrayBuffer.isView(value)) return canonicalJson(Array.from(value));
112
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : canonicalJson(item)).join(",")}]`;
113
+ if (typeof value === "object") {
114
+ const record = value;
115
+ const keys = Object.keys(record).filter((key) => record[key] !== void 0).sort();
116
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
117
+ }
118
+ return "null";
119
+ }
120
+ function needsUint32(array) {
121
+ for (let i = array.length - 1; i >= 0; --i) if (array[i] >= 65535) return true;
122
+ return false;
123
+ }
124
+ var indexArray = (indices) => needsUint32(indices) ? new Uint32Array(indices) : new Uint16Array(indices);
125
+ function pack(type, parameters, a) {
126
+ return {
127
+ type,
128
+ parameters,
129
+ positions: new Float32Array(a.vertices),
130
+ normals: new Float32Array(a.normals),
131
+ uvs: new Float32Array(a.uvs),
132
+ ...a.indices ? { indices: indexArray(a.indices) } : {},
133
+ groups: a.groups
134
+ };
135
+ }
136
+ var normalize3 = (x, y, z) => {
137
+ const length = Math.sqrt(x * x + y * y + z * z);
138
+ const s = 1 / (length || 1);
139
+ return [x * s, y * s, z * s];
140
+ };
141
+ function box(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1) {
142
+ const parameters = { width, height, depth, widthSegments, heightSegments, depthSegments };
143
+ widthSegments = Math.floor(widthSegments);
144
+ heightSegments = Math.floor(heightSegments);
145
+ depthSegments = Math.floor(depthSegments);
146
+ const indices = [];
147
+ const vertices = [];
148
+ const normals = [];
149
+ const uvs = [];
150
+ const groups = [];
151
+ let numberOfVertices = 0;
152
+ let groupStart = 0;
153
+ const buildPlane = (u, v, w, udir, vdir, width2, height2, depth2, gridX, gridY, materialIndex) => {
154
+ const segmentWidth = width2 / gridX;
155
+ const segmentHeight = height2 / gridY;
156
+ const widthHalf = width2 / 2;
157
+ const heightHalf = height2 / 2;
158
+ const depthHalf = depth2 / 2;
159
+ const gridX1 = gridX + 1;
160
+ const gridY1 = gridY + 1;
161
+ let vertexCounter = 0;
162
+ let groupCount = 0;
163
+ const vector = { x: 0, y: 0, z: 0 };
164
+ for (let iy = 0; iy < gridY1; iy++) {
165
+ const y = iy * segmentHeight - heightHalf;
166
+ for (let ix = 0; ix < gridX1; ix++) {
167
+ const x = ix * segmentWidth - widthHalf;
168
+ vector[u] = x * udir;
169
+ vector[v] = y * vdir;
170
+ vector[w] = depthHalf;
171
+ vertices.push(vector.x, vector.y, vector.z);
172
+ vector[u] = 0;
173
+ vector[v] = 0;
174
+ vector[w] = depth2 > 0 ? 1 : -1;
175
+ normals.push(vector.x, vector.y, vector.z);
176
+ uvs.push(ix / gridX);
177
+ uvs.push(1 - iy / gridY);
178
+ vertexCounter += 1;
179
+ }
180
+ }
181
+ for (let iy = 0; iy < gridY; iy++) {
182
+ for (let ix = 0; ix < gridX; ix++) {
183
+ const a = numberOfVertices + ix + gridX1 * iy;
184
+ const b = numberOfVertices + ix + gridX1 * (iy + 1);
185
+ const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
186
+ const d = numberOfVertices + (ix + 1) + gridX1 * iy;
187
+ indices.push(a, b, d);
188
+ indices.push(b, c, d);
189
+ groupCount += 6;
190
+ }
191
+ }
192
+ groups.push({ start: groupStart, count: groupCount, materialIndex });
193
+ groupStart += groupCount;
194
+ numberOfVertices += vertexCounter;
195
+ };
196
+ buildPlane("z", "y", "x", -1, -1, depth, height, width, depthSegments, heightSegments, 0);
197
+ buildPlane("z", "y", "x", 1, -1, depth, height, -width, depthSegments, heightSegments, 1);
198
+ buildPlane("x", "z", "y", 1, 1, width, depth, height, widthSegments, depthSegments, 2);
199
+ buildPlane("x", "z", "y", 1, -1, width, depth, -height, widthSegments, depthSegments, 3);
200
+ buildPlane("x", "y", "z", 1, -1, width, height, depth, widthSegments, heightSegments, 4);
201
+ buildPlane("x", "y", "z", -1, -1, width, height, -depth, widthSegments, heightSegments, 5);
202
+ return pack("BoxGeometry", parameters, { vertices, normals, uvs, indices, groups });
203
+ }
204
+ function cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {
205
+ radialSegments = Math.floor(radialSegments);
206
+ heightSegments = Math.floor(heightSegments);
207
+ const indices = [];
208
+ const vertices = [];
209
+ const normals = [];
210
+ const uvs = [];
211
+ const groups = [];
212
+ let index = 0;
213
+ const indexArray2 = [];
214
+ const halfHeight = height / 2;
215
+ let groupStart = 0;
216
+ const generateTorso = () => {
217
+ let groupCount = 0;
218
+ const slope = (radiusBottom - radiusTop) / height;
219
+ for (let y = 0; y <= heightSegments; y++) {
220
+ const indexRow = [];
221
+ const v = y / heightSegments;
222
+ const radius = v * (radiusBottom - radiusTop) + radiusTop;
223
+ for (let x = 0; x <= radialSegments; x++) {
224
+ const u = x / radialSegments;
225
+ const theta = u * thetaLength + thetaStart;
226
+ const sinTheta = Math.sin(theta);
227
+ const cosTheta = Math.cos(theta);
228
+ vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta);
229
+ const n = normalize3(sinTheta, slope, cosTheta);
230
+ normals.push(n[0], n[1], n[2]);
231
+ uvs.push(u, 1 - v);
232
+ indexRow.push(index++);
233
+ }
234
+ indexArray2.push(indexRow);
235
+ }
236
+ for (let x = 0; x < radialSegments; x++) {
237
+ for (let y = 0; y < heightSegments; y++) {
238
+ const a = indexArray2[y][x];
239
+ const b = indexArray2[y + 1][x];
240
+ const c = indexArray2[y + 1][x + 1];
241
+ const d = indexArray2[y][x + 1];
242
+ indices.push(a, b, d);
243
+ indices.push(b, c, d);
244
+ groupCount += 6;
245
+ }
246
+ }
247
+ groups.push({ start: groupStart, count: groupCount, materialIndex: 0 });
248
+ groupStart += groupCount;
249
+ };
250
+ const generateCap = (top) => {
251
+ const centerIndexStart = index;
252
+ let groupCount = 0;
253
+ const radius = top === true ? radiusTop : radiusBottom;
254
+ const sign2 = top === true ? 1 : -1;
255
+ for (let x = 1; x <= radialSegments; x++) {
256
+ vertices.push(0, halfHeight * sign2, 0);
257
+ normals.push(0, sign2, 0);
258
+ uvs.push(0.5, 0.5);
259
+ index++;
260
+ }
261
+ const centerIndexEnd = index;
262
+ for (let x = 0; x <= radialSegments; x++) {
263
+ const u = x / radialSegments;
264
+ const theta = u * thetaLength + thetaStart;
265
+ const cosTheta = Math.cos(theta);
266
+ const sinTheta = Math.sin(theta);
267
+ vertices.push(radius * sinTheta, halfHeight * sign2, radius * cosTheta);
268
+ normals.push(0, sign2, 0);
269
+ uvs.push(cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign2 + 0.5);
270
+ index++;
271
+ }
272
+ for (let x = 0; x < radialSegments; x++) {
273
+ const c = centerIndexStart + x;
274
+ const i = centerIndexEnd + x;
275
+ if (top === true) indices.push(i, i + 1, c);
276
+ else indices.push(i + 1, i, c);
277
+ groupCount += 3;
278
+ }
279
+ groups.push({ start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2 });
280
+ groupStart += groupCount;
281
+ };
282
+ generateTorso();
283
+ if (openEnded === false) {
284
+ if (radiusTop > 0) generateCap(true);
285
+ if (radiusBottom > 0) generateCap(false);
286
+ }
287
+ return { vertices, normals, uvs, indices, groups };
288
+ }
289
+ function cylinder(radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
290
+ const parameters = { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
291
+ return pack("CylinderGeometry", parameters, cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
292
+ }
293
+ function cone(radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
294
+ const parameters = { radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
295
+ return pack("ConeGeometry", parameters, cylinderArrays(0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
296
+ }
297
+ function sphere(radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI) {
298
+ const parameters = { radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength };
299
+ widthSegments = Math.max(3, Math.floor(widthSegments));
300
+ heightSegments = Math.max(2, Math.floor(heightSegments));
301
+ const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI);
302
+ let index = 0;
303
+ const grid = [];
304
+ const indices = [];
305
+ const vertices = [];
306
+ const normals = [];
307
+ const uvs = [];
308
+ for (let iy = 0; iy <= heightSegments; iy++) {
309
+ const verticesRow = [];
310
+ const v = iy / heightSegments;
311
+ let uOffset = 0;
312
+ if (iy === 0 && thetaStart === 0) uOffset = 0.5 / widthSegments;
313
+ else if (iy === heightSegments && thetaEnd === Math.PI) uOffset = -0.5 / widthSegments;
314
+ for (let ix = 0; ix <= widthSegments; ix++) {
315
+ const u = ix / widthSegments;
316
+ const x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
317
+ const y = radius * Math.cos(thetaStart + v * thetaLength);
318
+ const z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
319
+ vertices.push(x, y, z);
320
+ const n = normalize3(x, y, z);
321
+ normals.push(n[0], n[1], n[2]);
322
+ uvs.push(u + uOffset, 1 - v);
323
+ verticesRow.push(index++);
324
+ }
325
+ grid.push(verticesRow);
326
+ }
327
+ for (let iy = 0; iy < heightSegments; iy++) {
328
+ for (let ix = 0; ix < widthSegments; ix++) {
329
+ const a = grid[iy][ix + 1];
330
+ const b = grid[iy][ix];
331
+ const c = grid[iy + 1][ix];
332
+ const d = grid[iy + 1][ix + 1];
333
+ if (iy !== 0 || thetaStart > 0) indices.push(a, b, d);
334
+ if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d);
335
+ }
336
+ }
337
+ return pack("SphereGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
338
+ }
339
+ function plane(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
340
+ const parameters = { width, height, widthSegments, heightSegments };
341
+ const widthHalf = width / 2;
342
+ const heightHalf = height / 2;
343
+ const gridX = Math.floor(widthSegments);
344
+ const gridY = Math.floor(heightSegments);
345
+ const gridX1 = gridX + 1;
346
+ const gridY1 = gridY + 1;
347
+ const segmentWidth = width / gridX;
348
+ const segmentHeight = height / gridY;
349
+ const indices = [];
350
+ const vertices = [];
351
+ const normals = [];
352
+ const uvs = [];
353
+ for (let iy = 0; iy < gridY1; iy++) {
354
+ const y = iy * segmentHeight - heightHalf;
355
+ for (let ix = 0; ix < gridX1; ix++) {
356
+ const x = ix * segmentWidth - widthHalf;
357
+ vertices.push(x, -y, 0);
358
+ normals.push(0, 0, 1);
359
+ uvs.push(ix / gridX);
360
+ uvs.push(1 - iy / gridY);
361
+ }
362
+ }
363
+ for (let iy = 0; iy < gridY; iy++) {
364
+ for (let ix = 0; ix < gridX; ix++) {
365
+ const a = ix + gridX1 * iy;
366
+ const b = ix + gridX1 * (iy + 1);
367
+ const c = ix + 1 + gridX1 * (iy + 1);
368
+ const d = ix + 1 + gridX1 * iy;
369
+ indices.push(a, b, d);
370
+ indices.push(b, c, d);
371
+ }
372
+ }
373
+ return pack("PlaneGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
374
+ }
375
+ function circle(radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2) {
376
+ const parameters = { radius, segments, thetaStart, thetaLength };
377
+ segments = Math.max(3, segments);
378
+ const indices = [];
379
+ const vertices = [];
380
+ const normals = [];
381
+ const uvs = [];
382
+ vertices.push(0, 0, 0);
383
+ normals.push(0, 0, 1);
384
+ uvs.push(0.5, 0.5);
385
+ for (let s = 0, i = 3; s <= segments; s++, i += 3) {
386
+ const segment = thetaStart + s / segments * thetaLength;
387
+ vertices.push(radius * Math.cos(segment), radius * Math.sin(segment), 0);
388
+ normals.push(0, 0, 1);
389
+ uvs.push((vertices[i] / radius + 1) / 2, (vertices[i + 1] / radius + 1) / 2);
390
+ }
391
+ for (let i = 1; i <= segments; i++) indices.push(i, i + 1, 0);
392
+ return pack("CircleGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
393
+ }
394
+ function ring(innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2) {
395
+ const parameters = { innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength };
396
+ thetaSegments = Math.max(3, thetaSegments);
397
+ phiSegments = Math.max(1, phiSegments);
398
+ const indices = [];
399
+ const vertices = [];
400
+ const normals = [];
401
+ const uvs = [];
402
+ let radius = innerRadius;
403
+ const radiusStep = (outerRadius - innerRadius) / phiSegments;
404
+ for (let j = 0; j <= phiSegments; j++) {
405
+ for (let i = 0; i <= thetaSegments; i++) {
406
+ const segment = thetaStart + i / thetaSegments * thetaLength;
407
+ const x = radius * Math.cos(segment);
408
+ const y = radius * Math.sin(segment);
409
+ vertices.push(x, y, 0);
410
+ normals.push(0, 0, 1);
411
+ uvs.push((x / outerRadius + 1) / 2, (y / outerRadius + 1) / 2);
412
+ }
413
+ radius += radiusStep;
414
+ }
415
+ for (let j = 0; j < phiSegments; j++) {
416
+ const thetaSegmentLevel = j * (thetaSegments + 1);
417
+ for (let i = 0; i < thetaSegments; i++) {
418
+ const segment = i + thetaSegmentLevel;
419
+ const a = segment;
420
+ const b = segment + thetaSegments + 1;
421
+ const c = segment + thetaSegments + 2;
422
+ const d = segment + 1;
423
+ indices.push(a, b, d);
424
+ indices.push(b, c, d);
425
+ }
426
+ }
427
+ return pack("RingGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
428
+ }
429
+ function lathe(points, segments = 12, phiStart = 0, phiLength = Math.PI * 2) {
430
+ const parameters = { points: points.map(([x, y]) => ({ x, y })), segments, phiStart, phiLength };
431
+ segments = Math.floor(segments);
432
+ phiLength = Math.max(0, Math.min(Math.PI * 2, phiLength));
433
+ const indices = [];
434
+ const vertices = [];
435
+ const uvs = [];
436
+ const initNormals = [];
437
+ const normals = [];
438
+ const inverseSegments = 1 / segments;
439
+ const normal = { x: 0, y: 0, z: 0 };
440
+ const curNormal = { x: 0, y: 0, z: 0 };
441
+ const prevNormal = { x: 0, y: 0, z: 0 };
442
+ let dx = 0;
443
+ let dy = 0;
444
+ const last = points.length - 1;
445
+ const px = (j) => points[j][0];
446
+ const py = (j) => points[j][1];
447
+ const setNormalized = () => {
448
+ const n = normalize3(normal.x, normal.y, normal.z);
449
+ normal.x = n[0];
450
+ normal.y = n[1];
451
+ normal.z = n[2];
452
+ };
453
+ for (let j = 0; j <= last; j++) {
454
+ if (j === 0) {
455
+ dx = px(j + 1) - px(j);
456
+ dy = py(j + 1) - py(j);
457
+ normal.x = dy * 1;
458
+ normal.y = -dx;
459
+ normal.z = dy * 0;
460
+ prevNormal.x = normal.x;
461
+ prevNormal.y = normal.y;
462
+ prevNormal.z = normal.z;
463
+ setNormalized();
464
+ initNormals.push(normal.x, normal.y, normal.z);
465
+ } else if (j === last) {
466
+ initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z);
467
+ } else {
468
+ dx = px(j + 1) - px(j);
469
+ dy = py(j + 1) - py(j);
470
+ normal.x = dy * 1;
471
+ normal.y = -dx;
472
+ normal.z = dy * 0;
473
+ curNormal.x = normal.x;
474
+ curNormal.y = normal.y;
475
+ curNormal.z = normal.z;
476
+ normal.x += prevNormal.x;
477
+ normal.y += prevNormal.y;
478
+ normal.z += prevNormal.z;
479
+ setNormalized();
480
+ initNormals.push(normal.x, normal.y, normal.z);
481
+ prevNormal.x = curNormal.x;
482
+ prevNormal.y = curNormal.y;
483
+ prevNormal.z = curNormal.z;
484
+ }
485
+ }
486
+ for (let i = 0; i <= segments; i++) {
487
+ const phi = phiStart + i * inverseSegments * phiLength;
488
+ const sin = Math.sin(phi);
489
+ const cos = Math.cos(phi);
490
+ for (let j = 0; j <= last; j++) {
491
+ vertices.push(px(j) * sin, py(j), px(j) * cos);
492
+ uvs.push(i / segments, j / (points.length - 1));
493
+ const x = initNormals[3 * j + 0] * sin;
494
+ const y = initNormals[3 * j + 1];
495
+ const z = initNormals[3 * j + 0] * cos;
496
+ normals.push(x, y, z);
497
+ }
498
+ }
499
+ for (let i = 0; i < segments; i++) {
500
+ for (let j = 0; j < points.length - 1; j++) {
501
+ const base = j + i * points.length;
502
+ const a = base;
503
+ const b = base + points.length;
504
+ const c = base + points.length + 1;
505
+ const d = base + 1;
506
+ indices.push(a, b, d);
507
+ indices.push(c, d, b);
508
+ }
509
+ }
510
+ return pack("LatheGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
511
+ }
512
+ function torus(radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2) {
513
+ const parameters = { radius, tube, radialSegments, tubularSegments, arc };
514
+ radialSegments = Math.floor(radialSegments);
515
+ tubularSegments = Math.floor(tubularSegments);
516
+ const indices = [];
517
+ const vertices = [];
518
+ const normals = [];
519
+ const uvs = [];
520
+ for (let j = 0; j <= radialSegments; j++) {
521
+ for (let i = 0; i <= tubularSegments; i++) {
522
+ const u = i / tubularSegments * arc;
523
+ const v = j / radialSegments * Math.PI * 2;
524
+ const x = (radius + tube * Math.cos(v)) * Math.cos(u);
525
+ const y = (radius + tube * Math.cos(v)) * Math.sin(u);
526
+ const z = tube * Math.sin(v);
527
+ vertices.push(x, y, z);
528
+ const cx = radius * Math.cos(u);
529
+ const cy = radius * Math.sin(u);
530
+ const n = normalize3(x - cx, y - cy, z - 0);
531
+ normals.push(n[0], n[1], n[2]);
532
+ uvs.push(i / tubularSegments);
533
+ uvs.push(j / radialSegments);
534
+ }
535
+ }
536
+ for (let j = 1; j <= radialSegments; j++) {
537
+ for (let i = 1; i <= tubularSegments; i++) {
538
+ const a = (tubularSegments + 1) * j + i - 1;
539
+ const b = (tubularSegments + 1) * (j - 1) + i - 1;
540
+ const c = (tubularSegments + 1) * (j - 1) + i;
541
+ const d = (tubularSegments + 1) * j + i;
542
+ indices.push(a, b, d);
543
+ indices.push(b, c, d);
544
+ }
545
+ }
546
+ return pack("TorusGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
547
+ }
548
+ function difference(size, subtract) {
549
+ const [w, h, d] = size;
550
+ const hw = w / 2;
551
+ const hh = h / 2;
552
+ const hd = d / 2;
553
+ const holes = subtract.map((hole) => ({ x0: Math.max(-hw, Math.min(hole.min[0], hole.max[0])), x1: Math.min(hw, Math.max(hole.min[0], hole.max[0])), y0: Math.max(-hh, Math.min(hole.min[1], hole.max[1])), y1: Math.min(hh, Math.max(hole.min[1], hole.max[1])) })).filter((hole) => hole.x1 > hole.x0 && hole.y1 > hole.y0);
554
+ const cuts = (lo, hi, values) => [.../* @__PURE__ */ new Set([lo, hi, ...values.filter((v) => v > lo && v < hi)])].sort((a, b) => a - b);
555
+ const xs = cuts(-hw, hw, holes.flatMap((hole) => [hole.x0, hole.x1]));
556
+ const ys = cuts(-hh, hh, holes.flatMap((hole) => [hole.y0, hole.y1]));
557
+ const nx = xs.length - 1;
558
+ const ny = ys.length - 1;
559
+ const solid = (i, j) => {
560
+ if (i < 0 || j < 0 || i >= nx || j >= ny) return false;
561
+ const cx = (xs[i] + xs[i + 1]) / 2;
562
+ const cy = (ys[j] + ys[j + 1]) / 2;
563
+ return !holes.some((hole) => cx > hole.x0 && cx < hole.x1 && cy > hole.y0 && cy < hole.y1);
564
+ };
565
+ const positions = [];
566
+ const normals = [];
567
+ const uvs = [];
568
+ const indices = [];
569
+ const quad = (corners, normal, uv) => {
570
+ const base = positions.length / 3;
571
+ for (let k = 0; k < 4; k++) {
572
+ positions.push(...corners[k]);
573
+ normals.push(...normal);
574
+ uvs.push(...uv[k]);
575
+ }
576
+ indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
577
+ };
578
+ const fu = (x) => (x + hw) / w;
579
+ const fv = (y) => (y + hh) / h;
580
+ for (let j = 0; j < ny; j++) {
581
+ for (let i = 0; i < nx; i++) {
582
+ if (!solid(i, j)) continue;
583
+ const x0 = xs[i];
584
+ const x1 = xs[i + 1];
585
+ const y0 = ys[j];
586
+ const y1 = ys[j + 1];
587
+ quad([[x0, y0, hd], [x1, y0, hd], [x1, y1, hd], [x0, y1, hd]], [0, 0, 1], [[fu(x0), fv(y0)], [fu(x1), fv(y0)], [fu(x1), fv(y1)], [fu(x0), fv(y1)]]);
588
+ quad([[x1, y0, -hd], [x0, y0, -hd], [x0, y1, -hd], [x1, y1, -hd]], [0, 0, -1], [[1 - fu(x1), fv(y0)], [1 - fu(x0), fv(y0)], [1 - fu(x0), fv(y1)], [1 - fu(x1), fv(y1)]]);
589
+ const zu = (z) => (z + hd) / d;
590
+ if (!solid(i - 1, j)) quad([[x0, y0, -hd], [x0, y0, hd], [x0, y1, hd], [x0, y1, -hd]], [-1, 0, 0], [[zu(-hd), fv(y0)], [zu(hd), fv(y0)], [zu(hd), fv(y1)], [zu(-hd), fv(y1)]]);
591
+ if (!solid(i + 1, j)) quad([[x1, y0, hd], [x1, y0, -hd], [x1, y1, -hd], [x1, y1, hd]], [1, 0, 0], [[zu(hd), fv(y0)], [zu(-hd), fv(y0)], [zu(-hd), fv(y1)], [zu(hd), fv(y1)]]);
592
+ if (!solid(i, j - 1)) quad([[x0, y0, -hd], [x1, y0, -hd], [x1, y0, hd], [x0, y0, hd]], [0, -1, 0], [[fu(x0), zu(-hd)], [fu(x1), zu(-hd)], [fu(x1), zu(hd)], [fu(x0), zu(hd)]]);
593
+ if (!solid(i, j + 1)) quad([[x0, y1, hd], [x1, y1, hd], [x1, y1, -hd], [x0, y1, -hd]], [0, 1, 0], [[fu(x0), zu(hd)], [fu(x1), zu(hd)], [fu(x1), zu(-hd)], [fu(x0), zu(-hd)]]);
594
+ }
595
+ }
596
+ return {
597
+ type: "BufferGeometry",
598
+ parameters: { size: [w, h, d], subtract: holes.map((hole) => ({ min: [hole.x0, hole.y0], max: [hole.x1, hole.y1] })) },
599
+ positions: new Float32Array(positions),
600
+ normals: new Float32Array(normals),
601
+ uvs: new Float32Array(uvs),
602
+ indices: indexArray(indices),
603
+ groups: []
604
+ };
605
+ }
606
+ var Earcut = {
607
+ triangulate: function(data, holeIndices, dim = 2) {
608
+ const hasHoles = holeIndices && holeIndices.length;
609
+ const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
610
+ let outerNode = linkedList(data, 0, outerLen, dim, true);
611
+ const triangles = [];
612
+ if (!outerNode || outerNode.next === outerNode.prev) return triangles;
613
+ let minX, minY, maxX, maxY, x, y, invSize;
614
+ if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
615
+ if (data.length > 80 * dim) {
616
+ minX = maxX = data[0];
617
+ minY = maxY = data[1];
618
+ for (let i = dim; i < outerLen; i += dim) {
619
+ x = data[i];
620
+ y = data[i + 1];
621
+ if (x < minX) minX = x;
622
+ if (y < minY) minY = y;
623
+ if (x > maxX) maxX = x;
624
+ if (y > maxY) maxY = y;
625
+ }
626
+ invSize = Math.max(maxX - minX, maxY - minY);
627
+ invSize = invSize !== 0 ? 32767 / invSize : 0;
628
+ }
629
+ earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
630
+ return triangles;
631
+ }
632
+ };
633
+ function linkedList(data, start, end, dim, clockwise) {
634
+ let i, last;
635
+ if (clockwise === signedArea(data, start, end, dim) > 0) {
636
+ for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
637
+ } else {
638
+ for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
639
+ }
640
+ if (last && equals(last, last.next)) {
641
+ removeNode(last);
642
+ last = last.next;
643
+ }
644
+ return last;
645
+ }
646
+ function filterPoints(start, end) {
647
+ if (!start) return start;
648
+ if (!end) end = start;
649
+ let p = start, again;
650
+ do {
651
+ again = false;
652
+ if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
653
+ removeNode(p);
654
+ p = end = p.prev;
655
+ if (p === p.next) break;
656
+ again = true;
657
+ } else {
658
+ p = p.next;
659
+ }
660
+ } while (again || p !== end);
661
+ return end;
662
+ }
663
+ function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
664
+ if (!ear) return;
665
+ if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
666
+ let stop = ear, prev, next;
667
+ while (ear.prev !== ear.next) {
668
+ prev = ear.prev;
669
+ next = ear.next;
670
+ if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
671
+ triangles.push(prev.i / dim | 0);
672
+ triangles.push(ear.i / dim | 0);
673
+ triangles.push(next.i / dim | 0);
674
+ removeNode(ear);
675
+ ear = next.next;
676
+ stop = next.next;
677
+ continue;
678
+ }
679
+ ear = next;
680
+ if (ear === stop) {
681
+ if (!pass) {
682
+ earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
683
+ } else if (pass === 1) {
684
+ ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
685
+ earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
686
+ } else if (pass === 2) {
687
+ splitEarcut(ear, triangles, dim, minX, minY, invSize);
688
+ }
689
+ break;
690
+ }
691
+ }
692
+ }
693
+ function isEar(ear) {
694
+ const a = ear.prev, b = ear, c = ear.next;
695
+ if (area(a, b, c) >= 0) return false;
696
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
697
+ const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
698
+ let p = c.next;
699
+ while (p !== a) {
700
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
701
+ p = p.next;
702
+ }
703
+ return true;
704
+ }
705
+ function isEarHashed(ear, minX, minY, invSize) {
706
+ const a = ear.prev, b = ear, c = ear.next;
707
+ if (area(a, b, c) >= 0) return false;
708
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
709
+ const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
710
+ const minZ = zOrder(x0, y0, minX, minY, invSize), maxZ = zOrder(x1, y1, minX, minY, invSize);
711
+ let p = ear.prevZ, n = ear.nextZ;
712
+ while (p && p.z >= minZ && n && n.z <= maxZ) {
713
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
714
+ p = p.prevZ;
715
+ if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
716
+ n = n.nextZ;
717
+ }
718
+ while (p && p.z >= minZ) {
719
+ if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
720
+ p = p.prevZ;
721
+ }
722
+ while (n && n.z <= maxZ) {
723
+ if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
724
+ n = n.nextZ;
725
+ }
726
+ return true;
727
+ }
728
+ function cureLocalIntersections(start, triangles, dim) {
729
+ let p = start;
730
+ do {
731
+ const a = p.prev, b = p.next.next;
732
+ if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
733
+ triangles.push(a.i / dim | 0);
734
+ triangles.push(p.i / dim | 0);
735
+ triangles.push(b.i / dim | 0);
736
+ removeNode(p);
737
+ removeNode(p.next);
738
+ p = start = b;
739
+ }
740
+ p = p.next;
741
+ } while (p !== start);
742
+ return filterPoints(p);
743
+ }
744
+ function splitEarcut(start, triangles, dim, minX, minY, invSize) {
745
+ let a = start;
746
+ do {
747
+ let b = a.next.next;
748
+ while (b !== a.prev) {
749
+ if (a.i !== b.i && isValidDiagonal(a, b)) {
750
+ let c = splitPolygon(a, b);
751
+ a = filterPoints(a, a.next);
752
+ c = filterPoints(c, c.next);
753
+ earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
754
+ earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
755
+ return;
756
+ }
757
+ b = b.next;
758
+ }
759
+ a = a.next;
760
+ } while (a !== start);
761
+ }
762
+ function eliminateHoles(data, holeIndices, outerNode, dim) {
763
+ const queue = [];
764
+ let i, len, start, end, list2;
765
+ for (i = 0, len = holeIndices.length; i < len; i++) {
766
+ start = holeIndices[i] * dim;
767
+ end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
768
+ list2 = linkedList(data, start, end, dim, false);
769
+ if (list2 === list2.next) list2.steiner = true;
770
+ queue.push(getLeftmost(list2));
771
+ }
772
+ queue.sort(compareX);
773
+ for (i = 0; i < queue.length; i++) {
774
+ outerNode = eliminateHole(queue[i], outerNode);
775
+ }
776
+ return outerNode;
777
+ }
778
+ function compareX(a, b) {
779
+ return a.x - b.x;
780
+ }
781
+ function eliminateHole(hole, outerNode) {
782
+ const bridge = findHoleBridge(hole, outerNode);
783
+ if (!bridge) {
784
+ return outerNode;
785
+ }
786
+ const bridgeReverse = splitPolygon(bridge, hole);
787
+ filterPoints(bridgeReverse, bridgeReverse.next);
788
+ return filterPoints(bridge, bridge.next);
789
+ }
790
+ function findHoleBridge(hole, outerNode) {
791
+ let p = outerNode, qx = -Infinity, m;
792
+ const hx = hole.x, hy = hole.y;
793
+ do {
794
+ if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
795
+ const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
796
+ if (x <= hx && x > qx) {
797
+ qx = x;
798
+ m = p.x < p.next.x ? p : p.next;
799
+ if (x === hx) return m;
800
+ }
801
+ }
802
+ p = p.next;
803
+ } while (p !== outerNode);
804
+ if (!m) return null;
805
+ const stop = m, mx = m.x, my = m.y;
806
+ let tanMin = Infinity, tan;
807
+ p = m;
808
+ do {
809
+ if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
810
+ tan = Math.abs(hy - p.y) / (hx - p.x);
811
+ if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) {
812
+ m = p;
813
+ tanMin = tan;
814
+ }
815
+ }
816
+ p = p.next;
817
+ } while (p !== stop);
818
+ return m;
819
+ }
820
+ function sectorContainsSector(m, p) {
821
+ return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
822
+ }
823
+ function indexCurve(start, minX, minY, invSize) {
824
+ let p = start;
825
+ do {
826
+ if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
827
+ p.prevZ = p.prev;
828
+ p.nextZ = p.next;
829
+ p = p.next;
830
+ } while (p !== start);
831
+ p.prevZ.nextZ = null;
832
+ p.prevZ = null;
833
+ sortLinked(p);
834
+ }
835
+ function sortLinked(list2) {
836
+ let i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
837
+ do {
838
+ p = list2;
839
+ list2 = null;
840
+ tail = null;
841
+ numMerges = 0;
842
+ while (p) {
843
+ numMerges++;
844
+ q = p;
845
+ pSize = 0;
846
+ for (i = 0; i < inSize; i++) {
847
+ pSize++;
848
+ q = q.nextZ;
849
+ if (!q) break;
850
+ }
851
+ qSize = inSize;
852
+ while (pSize > 0 || qSize > 0 && q) {
853
+ if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
854
+ e = p;
855
+ p = p.nextZ;
856
+ pSize--;
857
+ } else {
858
+ e = q;
859
+ q = q.nextZ;
860
+ qSize--;
861
+ }
862
+ if (tail) tail.nextZ = e;
863
+ else list2 = e;
864
+ e.prevZ = tail;
865
+ tail = e;
866
+ }
867
+ p = q;
868
+ }
869
+ tail.nextZ = null;
870
+ inSize *= 2;
871
+ } while (numMerges > 1);
872
+ return list2;
873
+ }
874
+ function zOrder(x, y, minX, minY, invSize) {
875
+ x = (x - minX) * invSize | 0;
876
+ y = (y - minY) * invSize | 0;
877
+ x = (x | x << 8) & 16711935;
878
+ x = (x | x << 4) & 252645135;
879
+ x = (x | x << 2) & 858993459;
880
+ x = (x | x << 1) & 1431655765;
881
+ y = (y | y << 8) & 16711935;
882
+ y = (y | y << 4) & 252645135;
883
+ y = (y | y << 2) & 858993459;
884
+ y = (y | y << 1) & 1431655765;
885
+ return x | y << 1;
886
+ }
887
+ function getLeftmost(start) {
888
+ let p = start, leftmost = start;
889
+ do {
890
+ if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p;
891
+ p = p.next;
892
+ } while (p !== start);
893
+ return leftmost;
894
+ }
895
+ function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
896
+ return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && (ax - px) * (by - py) >= (bx - px) * (ay - py) && (bx - px) * (cy - py) >= (cx - px) * (by - py);
897
+ }
898
+ function isValidDiagonal(a, b) {
899
+ return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
900
+ (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
901
+ (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
902
+ equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0);
903
+ }
904
+ function area(p, q, r) {
905
+ return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
906
+ }
907
+ function equals(p1, p2) {
908
+ return p1.x === p2.x && p1.y === p2.y;
909
+ }
910
+ function intersects(p1, q1, p2, q2) {
911
+ const o1 = sign(area(p1, q1, p2));
912
+ const o2 = sign(area(p1, q1, q2));
913
+ const o3 = sign(area(p2, q2, p1));
914
+ const o4 = sign(area(p2, q2, q1));
915
+ if (o1 !== o2 && o3 !== o4) return true;
916
+ if (o1 === 0 && onSegment(p1, p2, q1)) return true;
917
+ if (o2 === 0 && onSegment(p1, q2, q1)) return true;
918
+ if (o3 === 0 && onSegment(p2, p1, q2)) return true;
919
+ if (o4 === 0 && onSegment(p2, q1, q2)) return true;
920
+ return false;
921
+ }
922
+ function onSegment(p, q, r) {
923
+ return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
924
+ }
925
+ function sign(num2) {
926
+ return num2 > 0 ? 1 : num2 < 0 ? -1 : 0;
927
+ }
928
+ function intersectsPolygon(a, b) {
929
+ let p = a;
930
+ do {
931
+ if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true;
932
+ p = p.next;
933
+ } while (p !== a);
934
+ return false;
935
+ }
936
+ function locallyInside(a, b) {
937
+ return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
938
+ }
939
+ function middleInside(a, b) {
940
+ let p = a, inside = false;
941
+ const px = (a.x + b.x) / 2, py = (a.y + b.y) / 2;
942
+ do {
943
+ if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)
944
+ inside = !inside;
945
+ p = p.next;
946
+ } while (p !== a);
947
+ return inside;
948
+ }
949
+ function splitPolygon(a, b) {
950
+ const a2 = new Node(a.i, a.x, a.y), b2 = new Node(b.i, b.x, b.y), an = a.next, bp = b.prev;
951
+ a.next = b;
952
+ b.prev = a;
953
+ a2.next = an;
954
+ an.prev = a2;
955
+ b2.next = a2;
956
+ a2.prev = b2;
957
+ bp.next = b2;
958
+ b2.prev = bp;
959
+ return b2;
960
+ }
961
+ function insertNode(i, x, y, last) {
962
+ const p = new Node(i, x, y);
963
+ if (!last) {
964
+ p.prev = p;
965
+ p.next = p;
966
+ } else {
967
+ p.next = last.next;
968
+ p.prev = last;
969
+ last.next.prev = p;
970
+ last.next = p;
971
+ }
972
+ return p;
973
+ }
974
+ function removeNode(p) {
975
+ p.next.prev = p.prev;
976
+ p.prev.next = p.next;
977
+ if (p.prevZ) p.prevZ.nextZ = p.nextZ;
978
+ if (p.nextZ) p.nextZ.prevZ = p.prevZ;
979
+ }
980
+ function Node(i, x, y) {
981
+ this.i = i;
982
+ this.x = x;
983
+ this.y = y;
984
+ this.prev = null;
985
+ this.next = null;
986
+ this.z = 0;
987
+ this.prevZ = null;
988
+ this.nextZ = null;
989
+ this.steiner = false;
990
+ }
991
+ function signedArea(data, start, end, dim) {
992
+ let sum = 0;
993
+ for (let i = start, j = end - dim; i < end; i += dim) {
994
+ sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
995
+ j = i;
996
+ }
997
+ return sum;
998
+ }
999
+ var equals2 = (a, b) => a.x === b.x && a.y === b.y;
1000
+ var linePoint = (v1, v2, t) => t === 1 ? { x: v2.x, y: v2.y } : { x: (v2.x - v1.x) * t + v1.x, y: (v2.y - v1.y) * t + v1.y };
1001
+ function pathPoints(points) {
1002
+ const out = [];
1003
+ let last;
1004
+ for (let i = 1; i < points.length; i++) {
1005
+ const v1 = { x: points[i - 1][0], y: points[i - 1][1] };
1006
+ const v2 = { x: points[i][0], y: points[i][1] };
1007
+ for (let d = 0; d <= 1; d++) {
1008
+ const point = linePoint(v1, v2, d / 1);
1009
+ if (last && equals2(last, point)) continue;
1010
+ out.push(point);
1011
+ last = point;
1012
+ }
1013
+ }
1014
+ return out;
1015
+ }
1016
+ function area2(contour) {
1017
+ const n = contour.length;
1018
+ let a = 0;
1019
+ for (let p = n - 1, q = 0; q < n; p = q++) a += contour[p].x * contour[q].y - contour[q].x * contour[p].y;
1020
+ return a * 0.5;
1021
+ }
1022
+ var isClockWise = (pts) => area2(pts) < 0;
1023
+ function removeDupEndPts(points) {
1024
+ const l = points.length;
1025
+ if (l > 2 && equals2(points[l - 1], points[0])) points.pop();
1026
+ }
1027
+ function triangulateShape(contour, holes) {
1028
+ const vertices = [];
1029
+ const holeIndices = [];
1030
+ const faces = [];
1031
+ removeDupEndPts(contour);
1032
+ for (const p of contour) vertices.push(p.x, p.y);
1033
+ let holeIndex = contour.length;
1034
+ holes.forEach(removeDupEndPts);
1035
+ for (let i = 0; i < holes.length; i++) {
1036
+ holeIndices.push(holeIndex);
1037
+ holeIndex += holes[i].length;
1038
+ for (const p of holes[i]) vertices.push(p.x, p.y);
1039
+ }
1040
+ const triangles = Earcut.triangulate(vertices, holeIndices);
1041
+ for (let i = 0; i < triangles.length; i += 3) faces.push(triangles.slice(i, i + 3));
1042
+ return faces;
1043
+ }
1044
+ function computeVertexNormals(positions, indices) {
1045
+ const normals = new Float32Array(positions.length);
1046
+ const get = (array, i) => [array[i * 3], array[i * 3 + 1], array[i * 3 + 2]];
1047
+ const face = (vA, vB, vC) => {
1048
+ const pA = get(positions, vA);
1049
+ const pB = get(positions, vB);
1050
+ const pC = get(positions, vC);
1051
+ const cb = [pC[0] - pB[0], pC[1] - pB[1], pC[2] - pB[2]];
1052
+ const ab = [pA[0] - pB[0], pA[1] - pB[1], pA[2] - pB[2]];
1053
+ return [cb[1] * ab[2] - cb[2] * ab[1], cb[2] * ab[0] - cb[0] * ab[2], cb[0] * ab[1] - cb[1] * ab[0]];
1054
+ };
1055
+ const set = (i, x, y, z) => {
1056
+ normals[i * 3] = x;
1057
+ normals[i * 3 + 1] = y;
1058
+ normals[i * 3 + 2] = z;
1059
+ };
1060
+ if (indices) {
1061
+ for (let i = 0; i < indices.length; i += 3) {
1062
+ const vA = indices[i];
1063
+ const vB = indices[i + 1];
1064
+ const vC = indices[i + 2];
1065
+ const cb = face(vA, vB, vC);
1066
+ for (const v of [vA, vB, vC]) {
1067
+ const n = get(normals, v);
1068
+ set(v, n[0] + cb[0], n[1] + cb[1], n[2] + cb[2]);
1069
+ }
1070
+ }
1071
+ } else {
1072
+ for (let i = 0; i < positions.length / 3; i += 3) {
1073
+ const cb = face(i, i + 1, i + 2);
1074
+ set(i, cb[0], cb[1], cb[2]);
1075
+ set(i + 1, cb[0], cb[1], cb[2]);
1076
+ set(i + 2, cb[0], cb[1], cb[2]);
1077
+ }
1078
+ }
1079
+ for (let i = 0; i < normals.length / 3; i++) {
1080
+ const [x, y, z] = get(normals, i);
1081
+ const s = 1 / (Math.sqrt(x * x + y * y + z * z) || 1);
1082
+ set(i, x * s, y * s, z * s);
1083
+ }
1084
+ return normals;
1085
+ }
1086
+ function extrude(outline, depth = 1, steps = 1, bevel = void 0) {
1087
+ const parameters = { shape: outline, depth, steps, bevel: bevel ?? {} };
1088
+ const verticesArray = [];
1089
+ const uvArray = [];
1090
+ const groups = [];
1091
+ const placeholder = [];
1092
+ const bevelEnabled = bevel !== false;
1093
+ const spec = bevel === false || bevel === void 0 ? {} : bevel;
1094
+ let bevelThickness = spec.thickness !== void 0 ? spec.thickness : 0.2;
1095
+ let bevelSize = spec.size !== void 0 ? spec.size : bevelThickness - 0.1;
1096
+ let bevelOffset = spec.offset !== void 0 ? spec.offset : 0;
1097
+ let bevelSegments = spec.segments !== void 0 ? spec.segments : 3;
1098
+ if (!bevelEnabled) {
1099
+ bevelSegments = 0;
1100
+ bevelThickness = 0;
1101
+ bevelSize = 0;
1102
+ bevelOffset = 0;
1103
+ }
1104
+ let vertices = pathPoints(outline.outer);
1105
+ const holes = (outline.holes ?? []).map(pathPoints);
1106
+ const reverse = !isClockWise(vertices);
1107
+ if (reverse) {
1108
+ vertices = vertices.reverse();
1109
+ for (let h = 0; h < holes.length; h++) {
1110
+ const ahole = holes[h];
1111
+ if (isClockWise(ahole)) holes[h] = ahole.reverse();
1112
+ }
1113
+ }
1114
+ const faces = triangulateShape(vertices, holes);
1115
+ const contour = vertices;
1116
+ for (const ahole of holes) vertices = vertices.concat(ahole);
1117
+ const scalePt2 = (pt, vec, size) => ({ x: pt.x + vec.x * size, y: pt.y + vec.y * size });
1118
+ const vlen = vertices.length;
1119
+ const flen = faces.length;
1120
+ const getBevelVec = (inPt, inPrev, inNext) => {
1121
+ let vTransX;
1122
+ let vTransY;
1123
+ let shrinkBy;
1124
+ const vPrevX = inPt.x - inPrev.x;
1125
+ const vPrevY = inPt.y - inPrev.y;
1126
+ const vNextX = inNext.x - inPt.x;
1127
+ const vNextY = inNext.y - inPt.y;
1128
+ const vPrevLensq = vPrevX * vPrevX + vPrevY * vPrevY;
1129
+ const collinear0 = vPrevX * vNextY - vPrevY * vNextX;
1130
+ if (Math.abs(collinear0) > Number.EPSILON) {
1131
+ const vPrevLen = Math.sqrt(vPrevLensq);
1132
+ const vNextLen = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
1133
+ const ptPrevShiftX = inPrev.x - vPrevY / vPrevLen;
1134
+ const ptPrevShiftY = inPrev.y + vPrevX / vPrevLen;
1135
+ const ptNextShiftX = inNext.x - vNextY / vNextLen;
1136
+ const ptNextShiftY = inNext.y + vNextX / vNextLen;
1137
+ const sf = ((ptNextShiftX - ptPrevShiftX) * vNextY - (ptNextShiftY - ptPrevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX);
1138
+ vTransX = ptPrevShiftX + vPrevX * sf - inPt.x;
1139
+ vTransY = ptPrevShiftY + vPrevY * sf - inPt.y;
1140
+ const vTransLensq = vTransX * vTransX + vTransY * vTransY;
1141
+ if (vTransLensq <= 2) return { x: vTransX, y: vTransY };
1142
+ shrinkBy = Math.sqrt(vTransLensq / 2);
1143
+ } else {
1144
+ let directionEq = false;
1145
+ if (vPrevX > Number.EPSILON) {
1146
+ if (vNextX > Number.EPSILON) directionEq = true;
1147
+ } else if (vPrevX < -Number.EPSILON) {
1148
+ if (vNextX < -Number.EPSILON) directionEq = true;
1149
+ } else if (Math.sign(vPrevY) === Math.sign(vNextY)) {
1150
+ directionEq = true;
1151
+ }
1152
+ if (directionEq) {
1153
+ vTransX = -vPrevY;
1154
+ vTransY = vPrevX;
1155
+ shrinkBy = Math.sqrt(vPrevLensq);
1156
+ } else {
1157
+ vTransX = vPrevX;
1158
+ vTransY = vPrevY;
1159
+ shrinkBy = Math.sqrt(vPrevLensq / 2);
1160
+ }
1161
+ }
1162
+ return { x: vTransX / shrinkBy, y: vTransY / shrinkBy };
1163
+ };
1164
+ const movements = (ring2) => {
1165
+ const out = [];
1166
+ for (let i = 0, il = ring2.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {
1167
+ if (j === il) j = 0;
1168
+ if (k === il) k = 0;
1169
+ out[i] = getBevelVec(ring2[i], ring2[j], ring2[k]);
1170
+ }
1171
+ return out;
1172
+ };
1173
+ const contourMovements = movements(contour);
1174
+ const holesMovements = [];
1175
+ let verticesMovements = contourMovements.concat();
1176
+ for (const ahole of holes) {
1177
+ const oneHoleMovements = movements(ahole);
1178
+ holesMovements.push(oneHoleMovements);
1179
+ verticesMovements = verticesMovements.concat(oneHoleMovements);
1180
+ }
1181
+ const v = (x, y, z) => {
1182
+ placeholder.push(x);
1183
+ placeholder.push(y);
1184
+ placeholder.push(z);
1185
+ };
1186
+ const bevelLayer = (b, zOf) => {
1187
+ const t = b / bevelSegments;
1188
+ const z = bevelThickness * Math.cos(t * Math.PI / 2);
1189
+ const bs2 = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;
1190
+ for (let i = 0; i < contour.length; i++) {
1191
+ const vert = scalePt2(contour[i], contourMovements[i], bs2);
1192
+ v(vert.x, vert.y, zOf(z));
1193
+ }
1194
+ for (let h = 0; h < holes.length; h++) {
1195
+ const ahole = holes[h];
1196
+ const oneHoleMovements = holesMovements[h];
1197
+ for (let i = 0; i < ahole.length; i++) {
1198
+ const vert = scalePt2(ahole[i], oneHoleMovements[i], bs2);
1199
+ v(vert.x, vert.y, zOf(z));
1200
+ }
1201
+ }
1202
+ };
1203
+ for (let b = 0; b < bevelSegments; b++) bevelLayer(b, (z) => -z);
1204
+ const bs = bevelSize + bevelOffset;
1205
+ for (let i = 0; i < vlen; i++) {
1206
+ const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
1207
+ v(vert.x, vert.y, 0);
1208
+ }
1209
+ for (let s = 1; s <= steps; s++) {
1210
+ for (let i = 0; i < vlen; i++) {
1211
+ const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
1212
+ v(vert.x, vert.y, depth / steps * s);
1213
+ }
1214
+ }
1215
+ for (let b = bevelSegments - 1; b >= 0; b--) bevelLayer(b, (z) => depth + z);
1216
+ const addVertex = (index) => {
1217
+ verticesArray.push(placeholder[index * 3 + 0]);
1218
+ verticesArray.push(placeholder[index * 3 + 1]);
1219
+ verticesArray.push(placeholder[index * 3 + 2]);
1220
+ };
1221
+ const addUV = (x, y) => {
1222
+ uvArray.push(x);
1223
+ uvArray.push(y);
1224
+ };
1225
+ const at = (index, axis) => verticesArray[index * 3 + axis];
1226
+ const f3 = (a, b, c) => {
1227
+ addVertex(a);
1228
+ addVertex(b);
1229
+ addVertex(c);
1230
+ const nextIndex = verticesArray.length / 3;
1231
+ for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1));
1232
+ };
1233
+ const f4 = (a, b, c, d) => {
1234
+ addVertex(a);
1235
+ addVertex(b);
1236
+ addVertex(d);
1237
+ addVertex(b);
1238
+ addVertex(c);
1239
+ addVertex(d);
1240
+ const nextIndex = verticesArray.length / 3;
1241
+ const iA = nextIndex - 6;
1242
+ const iB = nextIndex - 3;
1243
+ const iC = nextIndex - 2;
1244
+ const iD = nextIndex - 1;
1245
+ const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0));
1246
+ const uv = (index) => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)];
1247
+ const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)];
1248
+ for (const k of [0, 1, 3, 1, 2, 3]) addUV(...uvs[k]);
1249
+ };
1250
+ let start = verticesArray.length / 3;
1251
+ if (bevelEnabled) {
1252
+ let offset = 0;
1253
+ for (let i = 0; i < flen; i++) {
1254
+ const face = faces[i];
1255
+ f3(face[2] + offset, face[1] + offset, face[0] + offset);
1256
+ }
1257
+ const layer = steps + bevelSegments * 2;
1258
+ offset = vlen * layer;
1259
+ for (let i = 0; i < flen; i++) {
1260
+ const face = faces[i];
1261
+ f3(face[0] + offset, face[1] + offset, face[2] + offset);
1262
+ }
1263
+ } else {
1264
+ for (let i = 0; i < flen; i++) {
1265
+ const face = faces[i];
1266
+ f3(face[2], face[1], face[0]);
1267
+ }
1268
+ for (let i = 0; i < flen; i++) {
1269
+ const face = faces[i];
1270
+ f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps);
1271
+ }
1272
+ }
1273
+ groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 0 });
1274
+ start = verticesArray.length / 3;
1275
+ const sidewalls = (ring2, layeroffset2) => {
1276
+ let i = ring2.length;
1277
+ while (--i >= 0) {
1278
+ const j = i;
1279
+ let k = i - 1;
1280
+ if (k < 0) k = ring2.length - 1;
1281
+ for (let s = 0, sl = steps + bevelSegments * 2; s < sl; s++) {
1282
+ const slen1 = vlen * s;
1283
+ const slen2 = vlen * (s + 1);
1284
+ f4(layeroffset2 + j + slen1, layeroffset2 + k + slen1, layeroffset2 + k + slen2, layeroffset2 + j + slen2);
1285
+ }
1286
+ }
1287
+ };
1288
+ let layeroffset = 0;
1289
+ sidewalls(contour, layeroffset);
1290
+ layeroffset += contour.length;
1291
+ for (const ahole of holes) {
1292
+ sidewalls(ahole, layeroffset);
1293
+ layeroffset += ahole.length;
1294
+ }
1295
+ groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 1 });
1296
+ const positions = new Float32Array(verticesArray);
1297
+ return { type: "ExtrudeGeometry", parameters, positions, normals: computeVertexNormals(positions), uvs: new Float32Array(uvArray), groups };
1298
+ }
1299
+ var PARAMETER_KEYS = {
1300
+ box: ["size", "segments"],
1301
+ cylinder: ["radiusTop", "radiusBottom", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
1302
+ cone: ["radius", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
1303
+ sphere: ["radius", "widthSegments", "heightSegments", "phiStart", "phiLength", "thetaStart", "thetaLength"],
1304
+ plane: ["width", "height", "widthSegments", "heightSegments"],
1305
+ circle: ["radius", "segments", "thetaStart", "thetaLength"],
1306
+ ring: ["innerRadius", "outerRadius", "thetaSegments", "phiSegments", "thetaStart", "thetaLength"],
1307
+ lathe: ["points", "segments", "phiStart", "phiLength"],
1308
+ torus: ["radius", "tube", "radialSegments", "tubularSegments", "arc"],
1309
+ extrude: ["shape", "depth", "steps", "bevel"],
1310
+ difference: ["size", "subtract"],
1311
+ mesh: ["positions", "normals", "uvs", "indices"]
1312
+ };
1313
+ var SEGMENT_KEYS = {
1314
+ cylinder: ["radialSegments"],
1315
+ cone: ["radialSegments"],
1316
+ sphere: ["widthSegments", "heightSegments"],
1317
+ circle: ["segments"],
1318
+ ring: ["thetaSegments"],
1319
+ lathe: ["segments"],
1320
+ torus: ["radialSegments", "tubularSegments"]
1321
+ };
1322
+ var SEGMENT_DEFAULTS = { radialSegments: 32, widthSegments: 32, heightSegments: 16, segments: 12, thetaSegments: 32, tubularSegments: 48 };
1323
+ var segmentDefault = (kind, key) => kind === "circle" && key === "segments" ? 32 : kind === "torus" && key === "radialSegments" ? 12 : SEGMENT_DEFAULTS[key];
1324
+ function primitiveParameters(node, options = {}) {
1325
+ const out = {};
1326
+ const source = node;
1327
+ for (const key of PARAMETER_KEYS[node.kind]) if (source[key] !== void 0) out[key] = source[key];
1328
+ const detail = options.detail ?? 1;
1329
+ if (detail !== 1) {
1330
+ const floor = options.minSegments ?? 3;
1331
+ for (const key of SEGMENT_KEYS[node.kind] ?? []) {
1332
+ const given = out[key] ?? segmentDefault(node.kind, key);
1333
+ out[key] = Math.max(floor, Math.round(given * detail));
1334
+ }
1335
+ }
1336
+ return out;
1337
+ }
1338
+ function mesh(p) {
1339
+ const positions = new Float32Array(p.positions);
1340
+ const indices = p.indices ? indexArray(p.indices) : void 0;
1341
+ return {
1342
+ type: "BufferGeometry",
1343
+ parameters: {},
1344
+ positions,
1345
+ normals: p.normals ? new Float32Array(p.normals) : computeVertexNormals(positions, indices),
1346
+ uvs: p.uvs ? new Float32Array(p.uvs) : new Float32Array(positions.length / 3 * 2),
1347
+ ...indices ? { indices } : {},
1348
+ groups: []
1349
+ };
1350
+ }
1351
+ function make(kind, p) {
1352
+ switch (kind) {
1353
+ case "box": {
1354
+ const size = p.size ?? [1, 1, 1];
1355
+ const segments = p.segments ?? [1, 1, 1];
1356
+ return box(size[0], size[1], size[2], segments[0], segments[1], segments[2]);
1357
+ }
1358
+ case "cylinder":
1359
+ return cylinder(p.radiusTop, p.radiusBottom, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
1360
+ case "cone":
1361
+ return cone(p.radius, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
1362
+ case "sphere":
1363
+ return sphere(p.radius, p.widthSegments, p.heightSegments, p.phiStart, p.phiLength, p.thetaStart, p.thetaLength);
1364
+ case "plane":
1365
+ return plane(p.width, p.height, p.widthSegments, p.heightSegments);
1366
+ case "circle":
1367
+ return circle(p.radius, p.segments, p.thetaStart, p.thetaLength);
1368
+ case "ring":
1369
+ return ring(p.innerRadius, p.outerRadius, p.thetaSegments, p.phiSegments, p.thetaStart, p.thetaLength);
1370
+ case "lathe":
1371
+ return lathe(p.points, p.segments, p.phiStart, p.phiLength);
1372
+ case "torus":
1373
+ return torus(p.radius, p.tube, p.radialSegments, p.tubularSegments, p.arc);
1374
+ case "extrude":
1375
+ return extrude(p.shape, p.depth, p.steps, p.bevel);
1376
+ case "difference":
1377
+ return difference(p.size, p.subtract ?? []);
1378
+ case "mesh":
1379
+ return mesh(p);
1380
+ default:
1381
+ throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
1382
+ }
1383
+ }
1384
+ var templates = /* @__PURE__ */ new Map();
1385
+ var templateLimit = 4096;
1386
+ var stats = { hits: 0, misses: 0 };
1387
+ function buildPrimitive(node, options = {}) {
1388
+ const parameters = primitiveParameters(node, options);
1389
+ if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
1390
+ const key = `${node.kind}:${canonicalJson(parameters)}`;
1391
+ const cached = templates.get(key);
1392
+ if (cached) {
1393
+ stats.hits++;
1394
+ templates.delete(key);
1395
+ templates.set(key, cached);
1396
+ return cached;
1397
+ }
1398
+ stats.misses++;
1399
+ const built = make(node.kind, parameters);
1400
+ templates.set(key, built);
1401
+ if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
1402
+ return built;
1403
+ }
1404
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? null : "expected a finite number";
1405
+ var bool = (v) => typeof v === "boolean" ? null : "expected a boolean";
1406
+ var str = (v) => typeof v === "string" ? null : "expected a string";
1407
+ var int = (min) => (v) => typeof v === "number" && Number.isInteger(v) && v >= min ? null : `expected an integer >= ${min}`;
1408
+ var range = (lo, hi, loOpen = false) => (v) => typeof v === "number" && (loOpen ? v > lo : v >= lo) && v <= hi ? null : `expected a number in ${loOpen ? "(" : "["}${lo}, ${hi}]`;
1409
+ var tuple = (n, item = num) => (v) => {
1410
+ if (!Array.isArray(v) || v.length !== n) return `expected an array of ${n}`;
1411
+ for (const x of v) if (item(x)) return `expected an array of ${n} numbers`;
1412
+ return null;
1413
+ };
1414
+ var list = (item, min = 0) => (v) => {
1415
+ if (!Array.isArray(v)) return "expected an array";
1416
+ if (v.length < min) return `expected at least ${min} items`;
1417
+ for (let i = 0; i < v.length; i++) {
1418
+ const e = item(v[i]);
1419
+ if (e) return `[${i}]: ${e}`;
1420
+ }
1421
+ return null;
1422
+ };
1423
+ var color = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 16777215 || typeof v === "string" && /^#[0-9a-fA-F]{6}$/.test(v) ? null : 'expected 0xRRGGBB or "#rrggbb"';
1424
+ var oneOf = (values) => (v) => values.includes(v) ? null : `expected one of ${values.map((x) => JSON.stringify(x)).join(", ")}`;
1425
+ var object = (fields, required = []) => (v) => {
1426
+ if (!v || typeof v !== "object" || Array.isArray(v)) return "expected an object";
1427
+ const record = v;
1428
+ for (const key of required) if (record[key] === void 0) return `${key}: required`;
1429
+ for (const key of Object.keys(record)) {
1430
+ if (record[key] === void 0) continue;
1431
+ const check = fields[key];
1432
+ if (!check) return `${key}: unknown field`;
1433
+ const e = check(record[key]);
1434
+ if (e) return `${key}: ${e}`;
1435
+ }
1436
+ return null;
1437
+ };
1438
+ var ORDERS = ["XYZ", "YXZ", "ZXY", "ZYX", "YZX", "XZY"];
1439
+ var TRANSFORM = {
1440
+ position: tuple(3),
1441
+ rotation: tuple(3),
1442
+ rotationOrder: oneOf(ORDERS),
1443
+ scale: (v) => typeof v === "number" ? num(v) : tuple(3)(v),
1444
+ heading: num,
1445
+ matrix: tuple(16)
1446
+ };
1447
+ var UV = object({ u0: num, v0: num, u1: num, v1: num }, ["u0", "v0", "u1", "v1"]);
1448
+ var INSTANCE = object({ ...TRANSFORM, color });
1449
+ var COMMON = {
1450
+ ...TRANSFORM,
1451
+ kind: str,
1452
+ name: str,
1453
+ part: str,
1454
+ material: str,
1455
+ color,
1456
+ uv: UV,
1457
+ instances: list(INSTANCE),
1458
+ minDetail: range(0, 1, true),
1459
+ tags: list(str)
1460
+ };
1461
+ var vec2 = tuple(2);
1462
+ var BEVEL = (v) => v === false ? null : object({ thickness: num, size: num, offset: num, segments: int(1) })(v);
1463
+ var KINDS = {
1464
+ group: { fields: {} },
1465
+ box: { fields: { size: tuple(3), segments: tuple(3, int(1)) } },
1466
+ cylinder: { fields: { radiusTop: num, radiusBottom: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
1467
+ cone: { fields: { radius: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
1468
+ sphere: { fields: { radius: num, widthSegments: int(3), heightSegments: int(2), phiStart: num, phiLength: num, thetaStart: num, thetaLength: num } },
1469
+ plane: { fields: { width: num, height: num, widthSegments: int(1), heightSegments: int(1) } },
1470
+ circle: { fields: { radius: num, segments: int(3), thetaStart: num, thetaLength: num } },
1471
+ ring: { fields: { innerRadius: num, outerRadius: num, thetaSegments: int(3), phiSegments: int(1), thetaStart: num, thetaLength: num } },
1472
+ lathe: { fields: { points: list(vec2, 2), segments: int(1), phiStart: num, phiLength: num }, required: ["points"] },
1473
+ torus: { fields: { radius: num, tube: num, radialSegments: int(1), tubularSegments: int(1), arc: num } },
1474
+ extrude: {
1475
+ fields: { shape: object({ outer: list(vec2, 3), holes: list(list(vec2, 3)) }, ["outer"]), depth: num, steps: int(1), bevel: BEVEL },
1476
+ required: ["shape"]
1477
+ },
1478
+ difference: { fields: { size: tuple(3), subtract: list(object({ min: vec2, max: vec2 }, ["min", "max"])) }, required: ["size", "subtract"] },
1479
+ mesh: {
1480
+ fields: {
1481
+ positions: (v) => list(num)(v) ?? (v.length % 3 === 0 ? null : "expected a multiple of 3 numbers"),
1482
+ normals: list(num),
1483
+ uvs: list(num),
1484
+ indices: list(int(0))
1485
+ },
1486
+ required: ["positions"]
1487
+ },
1488
+ point: { fields: { data: () => null } },
1489
+ custom: { fields: { generator: str, params: () => null }, required: ["generator"] }
1490
+ };
1491
+
1492
+ // ../game-shapes-three/dist/index.js
1493
+ var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
1494
+ var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
1495
+ var isPrimitive = (value) => typeof value.type === "string";
1496
+ function threeParameters(THREE, p) {
1497
+ if (p.type === "LatheGeometry") {
1498
+ const points = p.parameters.points.map(({ x, y }) => new THREE.Vector2(x, y));
1499
+ return { ...p.parameters, points };
1500
+ }
1501
+ return { ...p.parameters };
1502
+ }
1503
+ function toGeometry(THREE, source, options = {}) {
1504
+ const copy = options.copy !== false;
1505
+ const primitive = isPrimitive(source) ? source : null;
1506
+ const ThreeClass = primitive && primitive.type !== "BufferGeometry" ? THREE[primitive.type] : void 0;
1507
+ const geometry = ThreeClass ? Reflect.construct(THREE.BufferGeometry, [], ThreeClass) : new THREE.BufferGeometry();
1508
+ if (primitive && ThreeClass) {
1509
+ Object.assign(geometry, { type: primitive.type, parameters: threeParameters(THREE, primitive) });
1510
+ }
1511
+ const attribute = (array, size) => copy ? new THREE.Float32BufferAttribute(array, size) : new THREE.BufferAttribute(array, size);
1512
+ const index = source.indices;
1513
+ const setIndex = () => {
1514
+ if (!index) return;
1515
+ geometry.setIndex(copy ? index instanceof Uint32Array ? new THREE.Uint32BufferAttribute(index, 1) : new THREE.Uint16BufferAttribute(index, 1) : new THREE.BufferAttribute(index, 1));
1516
+ };
1517
+ const type = primitive?.type ?? "BufferGeometry";
1518
+ if (INDEX_FIRST.has(type)) setIndex();
1519
+ geometry.setAttribute("position", attribute(source.positions, 3));
1520
+ if (UV_BEFORE_NORMAL.has(type)) {
1521
+ geometry.setAttribute("uv", attribute(source.uvs, 2));
1522
+ geometry.setAttribute("normal", attribute(source.normals, 3));
1523
+ } else {
1524
+ geometry.setAttribute("normal", attribute(source.normals, 3));
1525
+ geometry.setAttribute("uv", attribute(source.uvs, 2));
1526
+ }
1527
+ if (!primitive && source.colors) geometry.setAttribute("color", attribute(source.colors, 3));
1528
+ if (!INDEX_FIRST.has(type)) setIndex();
1529
+ if (primitive) for (const group of primitive.groups) geometry.addGroup(group.start, group.count, group.materialIndex);
1530
+ return geometry;
1531
+ }
1532
+ var SHAPE_GEOMETRY_TYPES = ["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry", "ExtrudeGeometry"];
1533
+ function linePoints(path) {
1534
+ if (path.autoClose || path.curves.length === 0) return null;
1535
+ const points = [];
1536
+ for (const [index, curve] of path.curves.entries()) {
1537
+ if (!curve.isLineCurve || !curve.v1 || !curve.v2) return null;
1538
+ if (index === 0) points.push([curve.v1.x, curve.v1.y]);
1539
+ else {
1540
+ const last = points[points.length - 1];
1541
+ if (last[0] !== curve.v1.x || last[1] !== curve.v1.y) return null;
1542
+ }
1543
+ points.push([curve.v2.x, curve.v2.y]);
1544
+ }
1545
+ return points;
1546
+ }
1547
+ function extrudeOf(shapes, options = {}) {
1548
+ if (Array.isArray(shapes) || !shapes || options.extrudePath !== void 0 || options.UVGenerator !== void 0) return null;
1549
+ const shape = shapes;
1550
+ const outer = linePoints(shape);
1551
+ if (!outer) return null;
1552
+ const holes = [];
1553
+ for (const hole of shape.holes ?? []) {
1554
+ const points = linePoints(hole);
1555
+ if (!points) return null;
1556
+ holes.push(points);
1557
+ }
1558
+ const outline = holes.length ? { outer, holes } : { outer };
1559
+ const bevel = options.bevelEnabled === false ? false : {
1560
+ ...options.bevelThickness !== void 0 ? { thickness: options.bevelThickness } : {},
1561
+ ...options.bevelSize !== void 0 ? { size: options.bevelSize } : {},
1562
+ ...options.bevelOffset !== void 0 ? { offset: options.bevelOffset } : {},
1563
+ ...options.bevelSegments !== void 0 ? { segments: options.bevelSegments } : {}
1564
+ };
1565
+ return extrude(outline, options.depth, options.steps, bevel);
1566
+ }
1567
+ var namespaces = /* @__PURE__ */ new WeakMap();
1568
+ var shims = /* @__PURE__ */ new WeakSet();
1569
+ function shapeGeometries(THREE, options = {}) {
1570
+ if (shims.has(THREE)) return THREE;
1571
+ const cache = options.cache !== false;
1572
+ const key = cache ? "cache" : "nocache";
1573
+ let byKey = namespaces.get(THREE);
1574
+ if (!byKey) {
1575
+ byKey = /* @__PURE__ */ new Map();
1576
+ namespaces.set(THREE, byKey);
1577
+ }
1578
+ const existing = byKey.get(key);
1579
+ if (existing) return existing;
1580
+ const made = (p) => toGeometry(THREE, p);
1581
+ const viaCache = (node, direct) => cache ? buildPrimitive(node) : direct();
1582
+ const defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
1583
+ const constructors = {
1584
+ BoxGeometry: (width, height, depth, ws = 1, hs = 1, ds = 1) => made(viaCache({ kind: "box", size: [width ?? 1, height ?? 1, depth ?? 1], segments: [ws, hs, ds] }, () => box(width, height, depth, ws, hs, ds))),
1585
+ CylinderGeometry: (radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) => made(viaCache({ kind: "cylinder", ...defined({ radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cylinder(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),
1586
+ ConeGeometry: (radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) => made(viaCache({ kind: "cone", ...defined({ radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cone(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),
1587
+ SphereGeometry: (radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) => made(viaCache({ kind: "sphere", ...defined({ radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }) }, () => sphere(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength))),
1588
+ PlaneGeometry: (width, height, widthSegments, heightSegments) => made(viaCache({ kind: "plane", ...defined({ width, height, widthSegments, heightSegments }) }, () => plane(width, height, widthSegments, heightSegments))),
1589
+ CircleGeometry: (radius, segments, thetaStart, thetaLength) => made(viaCache({ kind: "circle", ...defined({ radius, segments, thetaStart, thetaLength }) }, () => circle(radius, segments, thetaStart, thetaLength))),
1590
+ RingGeometry: (innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) => made(viaCache({ kind: "ring", ...defined({ innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }) }, () => ring(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength))),
1591
+ LatheGeometry: (points, segments, phiStart, phiLength) => {
1592
+ if (!points) return new THREE.LatheGeometry(void 0, segments, phiStart, phiLength);
1593
+ const profile = points.map((p) => [p.x, p.y]);
1594
+ return made(viaCache({ kind: "lathe", points: profile, ...defined({ segments, phiStart, phiLength }) }, () => lathe(profile, segments, phiStart, phiLength)));
1595
+ },
1596
+ TorusGeometry: (radius, tube, radialSegments, tubularSegments, arc) => made(viaCache({ kind: "torus", ...defined({ radius, tube, radialSegments, tubularSegments, arc }) }, () => torus(radius, tube, radialSegments, tubularSegments, arc))),
1597
+ ExtrudeGeometry: (shapes, extrudeOptions) => {
1598
+ const buffers = shapes === void 0 ? null : extrudeOf(shapes, extrudeOptions);
1599
+ if (!buffers) return new THREE.ExtrudeGeometry(shapes, extrudeOptions);
1600
+ const geometry = toGeometry(THREE, buffers);
1601
+ geometry.parameters = { shapes, options: extrudeOptions ?? {} };
1602
+ return geometry;
1603
+ }
1604
+ };
1605
+ const namespace = { ...THREE };
1606
+ for (const type of SHAPE_GEOMETRY_TYPES) {
1607
+ const make2 = constructors[type];
1608
+ const Constructor = function(...args) {
1609
+ return make2(...args);
1610
+ };
1611
+ Constructor.prototype = THREE[type].prototype;
1612
+ Object.defineProperty(Constructor, "name", { value: type });
1613
+ namespace[type] = Constructor;
1614
+ }
1615
+ const result = namespace;
1616
+ byKey.set(key, result);
1617
+ shims.add(result);
1618
+ return result;
1619
+ }
1620
+
95
1621
  // ../game-props3d/dist/index.js
96
1622
  var T = (id, interior, w, d, h, y, solid, group) => ({ id, interior, w, d, h, y, solid, group });
97
1623
  var TYPE_DEFS = [
@@ -172,19 +1698,19 @@ var GameAnimalsThree = (() => {
172
1698
  for (const key in owner) if (!skip.includes(key)) walk(owner[key], 0);
173
1699
  return found;
174
1700
  };
175
- var isVisibleInTree = (object) => {
176
- for (let node = object; node; node = node.parent) if (!node.visible) return false;
1701
+ var isVisibleInTree = (object2) => {
1702
+ for (let node = object2; node; node = node.parent) if (!node.visible) return false;
177
1703
  return true;
178
1704
  };
179
1705
  var createNodeSkin = (THREE, root, options = {}) => {
180
1706
  const referenced = new Set(options.nodes ?? []);
181
1707
  const nodes = [];
182
1708
  const indexOf = /* @__PURE__ */ new Map();
183
- root.traverse((object) => {
184
- const mesh2 = object;
185
- if (!mesh2.isMesh || object.children.length > 0 || referenced.has(object)) {
186
- indexOf.set(object, nodes.length);
187
- nodes.push(object);
1709
+ root.traverse((object2) => {
1710
+ const mesh22 = object2;
1711
+ if (!mesh22.isMesh || object2.children.length > 0 || referenced.has(object2)) {
1712
+ indexOf.set(object2, nodes.length);
1713
+ nodes.push(object2);
188
1714
  }
189
1715
  });
190
1716
  const positions = [];
@@ -193,7 +1719,7 @@ var GameAnimalsThree = (() => {
193
1719
  const surfaces = [];
194
1720
  const nodeIndices = [];
195
1721
  const samples = /* @__PURE__ */ new Map();
196
- const color = new THREE.Color();
1722
+ const color2 = new THREE.Color();
197
1723
  const vertex = new THREE.Vector3();
198
1724
  const normal = new THREE.Vector3();
199
1725
  const sampleOf = (texture) => {
@@ -207,22 +1733,22 @@ var GameAnimalsThree = (() => {
207
1733
  samples.set(texture, sample);
208
1734
  return sample;
209
1735
  };
210
- root.traverse((object) => {
211
- const mesh2 = object;
212
- if (!mesh2.isMesh || !isVisibleInTree(object)) return;
213
- const material2 = Array.isArray(mesh2.material) ? mesh2.material[0] : mesh2.material;
214
- const geometry2 = mesh2.geometry;
1736
+ root.traverse((object2) => {
1737
+ const mesh22 = object2;
1738
+ if (!mesh22.isMesh || !isVisibleInTree(object2)) return;
1739
+ const material2 = Array.isArray(mesh22.material) ? mesh22.material[0] : mesh22.material;
1740
+ const geometry2 = mesh22.geometry;
215
1741
  const position = geometry2.attributes.position;
216
1742
  const normalAttribute = geometry2.attributes.normal;
217
1743
  const uv = geometry2.attributes.uv;
218
1744
  const matrix = new THREE.Matrix4();
219
- let nodeIndex = indexOf.get(object);
1745
+ let nodeIndex = indexOf.get(object2);
220
1746
  if (nodeIndex === void 0) {
221
- nodeIndex = indexOf.get(object.parent) ?? 0;
222
- object.updateMatrix();
223
- matrix.copy(object.matrix);
1747
+ nodeIndex = indexOf.get(object2.parent) ?? 0;
1748
+ object2.updateMatrix();
1749
+ matrix.copy(object2.matrix);
224
1750
  }
225
- const normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix);
1751
+ const normalMatrix2 = new THREE.Matrix3().getNormalMatrix(matrix);
226
1752
  const sample = material2.map ? sampleOf(material2.map) : void 0;
227
1753
  const emissive = material2.emissive ? (material2.emissive.r + material2.emissive.g + material2.emissive.b) * (material2.emissiveIntensity ?? 1) : 0;
228
1754
  const index = geometry2.index ? geometry2.index.array : null;
@@ -231,10 +1757,10 @@ var GameAnimalsThree = (() => {
231
1757
  const at = index ? index[item] : item;
232
1758
  vertex.fromBufferAttribute(position, at).applyMatrix4(matrix);
233
1759
  positions.push(vertex.x, vertex.y, vertex.z);
234
- if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix).normalize();
1760
+ if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix2).normalize();
235
1761
  else normal.set(0, 1, 0);
236
1762
  normals.push(normal.x, normal.y, normal.z);
237
- color.copy(material2.color || color.set(1, 1, 1)).convertLinearToSRGB();
1763
+ color2.copy(material2.color || color2.set(1, 1, 1)).convertLinearToSRGB();
238
1764
  if (sample && uv && material2.map) {
239
1765
  const repeat = material2.map.repeat;
240
1766
  const u = (uv.getX(at) * repeat.x % 1 + 1) % 1;
@@ -242,10 +1768,10 @@ var GameAnimalsThree = (() => {
242
1768
  const pixelX = Math.min(sample.width - 1, u * sample.width | 0);
243
1769
  const pixelY = Math.min(sample.height - 1, (1 - v) * sample.height | 0);
244
1770
  const offset = (pixelY * sample.width + pixelX) * 4;
245
- color.multiply(new THREE.Color().setRGB(sample.data[offset] / 255, sample.data[offset + 1] / 255, sample.data[offset + 2] / 255, THREE.SRGBColorSpace));
1771
+ color2.multiply(new THREE.Color().setRGB(sample.data[offset] / 255, sample.data[offset + 1] / 255, sample.data[offset + 2] / 255, THREE.SRGBColorSpace));
246
1772
  }
247
- if (emissive > 0.3) color.lerp(material2.emissive, 0.7);
248
- tints.push(color.r, color.g, color.b);
1773
+ if (emissive > 0.3) color2.lerp(material2.emissive, 0.7);
1774
+ tints.push(color2.r, color2.g, color2.b);
249
1775
  surfaces.push(material2.roughness ?? 0.8, material2.metalness ?? 0, emissive > 0.3 ? 1 : 0);
250
1776
  nodeIndices.push(nodeIndex);
251
1777
  }
@@ -275,12 +1801,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
275
1801
  const depthMaterial = new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking });
276
1802
  depthMaterial.onBeforeCompile = skinVertex;
277
1803
  depthMaterial.customProgramCacheKey = () => `nodeSkinDepth${nodes.length}`;
278
- const mesh = new THREE.Mesh(geometry, material);
279
- mesh.customDepthMaterial = depthMaterial;
280
- mesh.frustumCulled = false;
1804
+ const mesh2 = new THREE.Mesh(geometry, material);
1805
+ mesh2.customDepthMaterial = depthMaterial;
1806
+ mesh2.frustumCulled = false;
281
1807
  root.updateMatrixWorld(true);
282
1808
  return {
283
- mesh,
1809
+ mesh: mesh2,
284
1810
  nodes,
285
1811
  matrices,
286
1812
  update() {
@@ -487,26 +2013,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
487
2013
  };
488
2014
  };
489
2015
  var addMesh = (THREE, parent, geometry, material, x, y, z, rx, ry, rz) => {
490
- const mesh = new THREE.Mesh(geometry, material);
491
- mesh.position.set(x || 0, y || 0, z || 0);
492
- if (rx || ry || rz) mesh.rotation.set(rx || 0, ry || 0, rz || 0);
493
- mesh.castShadow = true;
494
- mesh.receiveShadow = true;
495
- parent.add(mesh);
496
- return mesh;
2016
+ const mesh2 = new THREE.Mesh(geometry, material);
2017
+ mesh2.position.set(x || 0, y || 0, z || 0);
2018
+ if (rx || ry || rz) mesh2.rotation.set(rx || 0, ry || 0, rz || 0);
2019
+ mesh2.castShadow = true;
2020
+ mesh2.receiveShadow = true;
2021
+ parent.add(mesh2);
2022
+ return mesh2;
497
2023
  };
498
2024
  var createRigTools = (THREE, rig) => {
499
2025
  const tools = {
500
- material(color, extras) {
501
- const material = new THREE.MeshStandardMaterial(Object.assign({ color, roughness: 0.8, metalness: 0, flatShading: false }, extras || {}));
2026
+ material(color2, extras) {
2027
+ const material = new THREE.MeshStandardMaterial(Object.assign({ color: color2, roughness: 0.8, metalness: 0, flatShading: false }, extras || {}));
502
2028
  material.userData.base = material.color.clone();
503
2029
  rig.materials.push(material);
504
2030
  return material;
505
2031
  },
506
2032
  ball(parent, radius, material, x, y, z, sx, sy, sz) {
507
- const mesh = addMesh(THREE, parent, new THREE.SphereGeometry(radius, 16, 12), material, x, y, z);
508
- if (sx) mesh.scale.set(sx, sy, sz);
509
- return mesh;
2033
+ const mesh2 = addMesh(THREE, parent, new THREE.SphereGeometry(radius, 16, 12), material, x, y, z);
2034
+ if (sx) mesh2.scale.set(sx, sy, sz);
2035
+ return mesh2;
510
2036
  },
511
2037
  limb: (parent, radius1, radius2, length, material) => addMesh(THREE, parent, new THREE.CylinderGeometry(radius2, radius1, length, 14, 1).translate(0, -length / 2, 0), material),
512
2038
  joint(parent, radius, material, x, y, z) {
@@ -729,7 +2255,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
729
2255
  outline.push(new THREE.Vector2(Math.max(1e-3, point.y), -length / 2 + point.x * length));
730
2256
  }
731
2257
  outline.push(new THREE.Vector2(1e-3, length / 2));
732
- return new THREE.LatheGeometry(outline, segments || 22).rotateX(Math.PI / 2);
2258
+ return new (shapeGeometries(THREE)).LatheGeometry(outline, segments || 22).rotateX(Math.PI / 2);
733
2259
  };
734
2260
 
735
2261
  // src/quadruped.ts
@@ -854,19 +2380,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
854
2380
  const { L, R: radius } = spec;
855
2381
  const sideX = radius * (spec.sx || 1);
856
2382
  const sideY = radius * (spec.sy || 1);
857
- const list = [blood];
2383
+ const list2 = [blood];
858
2384
  const wound = (parent, size, x, y, z, sx, sy, sz) => tools.ball(parent, size, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 5902862, roughness: 0.45 })), x, y, z, sx || 1, sy || 1, sz || 1);
859
- list.push(wound(bodyGroup, radius * 0.28, sideX * 0.92, sideY * 0.15, L * 0.05, 0.35, 1, 1.3));
860
- list.push(wound(bodyGroup, radius * 0.2, -sideX * 0.9, sideY * 0.3, -L * 0.2, 0.35, 1, 1.1));
861
- list.push(wound(neck, neckSpec.r * 0.5, neckSpec.r * 0.85, neckSpec.len * 0.5, 0, 0.4, 1, 1));
2385
+ list2.push(wound(bodyGroup, radius * 0.28, sideX * 0.92, sideY * 0.15, L * 0.05, 0.35, 1, 1.3));
2386
+ list2.push(wound(bodyGroup, radius * 0.2, -sideX * 0.9, sideY * 0.3, -L * 0.2, 0.35, 1, 1.1));
2387
+ list2.push(wound(neck, neckSpec.r * 0.5, neckSpec.r * 0.85, neckSpec.len * 0.5, 0, 0.4, 1, 1));
862
2388
  const bone = tools.material(14274488, { roughness: 0.6 });
863
2389
  for (let index = 0; index < RIB_COUNT; index++) {
864
2390
  const rib = addMesh(THREE, bodyGroup, new THREE.TorusGeometry(radius * 0.95, radius * 0.035, 6, 16, Math.PI * 0.42), bone, sideX * 0.12, sideY * 0.05, L * 0.12 - index * L * 0.07, 0, Math.PI / 2, -0.3);
865
2391
  rib.scale.set(1, 1.02 * (spec.sy || 1), spec.sx || 1);
866
- list.push(rib);
2392
+ list2.push(rib);
867
2393
  }
868
- rig.infectionParts.push(...list);
869
- list.forEach((part) => part.visible = false);
2394
+ rig.infectionParts.push(...list2);
2395
+ list2.forEach((part) => part.visible = false);
870
2396
  };
871
2397
  var buildQuadruped = (THREE, rig, options) => {
872
2398
  const spec = rig.species.body;
@@ -906,9 +2432,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
906
2432
  const material = index % 4 === 1 || index % 4 === 2 ? secondary : primary;
907
2433
  const group = new THREE.Group();
908
2434
  rig.trunk.add(group);
909
- const sphere = tools.ball(group, radius, material, 0, radius, 0, 1, 0.78, 1.5);
910
- sphere.userData.joint = index % 3 === 0 ? 1 : 0;
911
- if (index % 3 === 0) rig.joints.push(sphere);
2435
+ const sphere2 = tools.ball(group, radius, material, 0, radius, 0, 1, 0.78, 1.5);
2436
+ sphere2.userData.joint = index % 3 === 0 ? 1 : 0;
2437
+ if (index % 3 === 0) rig.joints.push(sphere2);
912
2438
  rig.segments.push({ group, radius });
913
2439
  }
914
2440
  const head = new THREE.Group();
@@ -1397,7 +2923,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1397
2923
  animals?.setVisualState(entry.rig, visualStateOf(entry.rig.species.outcome, true, state === "dead"));
1398
2924
  };
1399
2925
  const clearRings = () => {
1400
- for (const ring of rings) ring.parent?.remove(ring);
2926
+ for (const ring2 of rings) ring2.parent?.remove(ring2);
1401
2927
  rings = [];
1402
2928
  };
1403
2929
  const refreshLayers = () => {
@@ -1407,18 +2933,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1407
2933
  animals.setJointView(entry.rig, host.bench.layer("joints"));
1408
2934
  animals.setSplit(entry.rig, host.bench.layer("split"));
1409
2935
  }
1410
- const ring = (radius, color, parent) => {
1411
- const mesh = new three.Mesh(new three.RingGeometry(radius - 0.15, radius, 96).rotateX(-Math.PI / 2), new three.MeshBasicMaterial({ color, transparent: true, opacity: 0.55, depthWrite: false }));
1412
- mesh.position.y = 0.03;
1413
- parent.add(mesh);
1414
- rings.push(mesh);
2936
+ const ring2 = (radius, color2, parent) => {
2937
+ const mesh2 = new three.Mesh(new three.RingGeometry(radius - 0.15, radius, 96).rotateX(-Math.PI / 2), new three.MeshBasicMaterial({ color: color2, transparent: true, opacity: 0.55, depthWrite: false }));
2938
+ mesh2.position.y = 0.03;
2939
+ parent.add(mesh2);
2940
+ rings.push(mesh2);
1415
2941
  };
1416
2942
  for (const entry of placed.values()) {
1417
2943
  if (!entry.rig.root.parent) continue;
1418
2944
  const radius = entry.rig.species.soundRangeMetres;
1419
- if (host.bench.layer("sound_range") && radius) ring(radius, 4500223, entry.rig.root);
2945
+ if (host.bench.layer("sound_range") && radius) ring2(radius, 4500223, entry.rig.root);
1420
2946
  const lure = entry.rig.species.outbreak?.carcass?.lureRadiusMetres;
1421
- if (host.bench.layer("carcass_lure") && lure) ring(lure, 16733491, entry.rig.root);
2947
+ if (host.bench.layer("carcass_lure") && lure) ring2(lure, 16733491, entry.rig.root);
1422
2948
  }
1423
2949
  };
1424
2950
  const addSkins = () => {
@@ -1430,8 +2956,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1430
2956
  const rig = animals.build(animal);
1431
2957
  const skin = animals.createSkin(rig);
1432
2958
  rig.root.position.set(SKIN_FIELD.x0 + index % SKIN_FIELD.columns * SKIN_FIELD.gap, 0, SKIN_FIELD.z0 - Math.floor(index / SKIN_FIELD.columns) * SKIN_FIELD.gap);
1433
- rig.root.traverse((object) => {
1434
- if (object.isMesh) object.visible = false;
2959
+ rig.root.traverse((object2) => {
2960
+ if (object2.isMesh) object2.visible = false;
1435
2961
  });
1436
2962
  skinGroup.add(rig.root, skin.mesh);
1437
2963
  skins.push({ rig, skin });
@@ -1516,11 +3042,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1516
3042
  const countMeshes = (only) => {
1517
3043
  let drawCalls = 0;
1518
3044
  let triangles = 0;
1519
- const count = (root) => root.traverse((object) => {
1520
- const mesh = object;
1521
- if (!mesh.isMesh || !mesh.visible) return;
3045
+ const count = (root) => root.traverse((object2) => {
3046
+ const mesh2 = object2;
3047
+ if (!mesh2.isMesh || !mesh2.visible) return;
1522
3048
  drawCalls += 1;
1523
- const geometry = mesh.geometry;
3049
+ const geometry = mesh2.geometry;
1524
3050
  triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3;
1525
3051
  });
1526
3052
  if (only !== "skins") for (const entry of placed.values()) count(entry.rig.root);
@@ -1576,7 +3102,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1576
3102
  { kind: "action", id: "bite_all", name: text.actions.bite_all },
1577
3103
  { kind: "action", id: "recover", name: text.actions.recover },
1578
3104
  { kind: "action", id: "call", name: text.actions.call },
1579
- { kind: "action", id: "skin_40", name: text.actions.skin_40 },
3105
+ { kind: "action", id: "skin_40", name: text.actions.skin_40, slow: true },
1580
3106
  { kind: "action", id: "clear_skins", name: text.actions.clear_skins }
1581
3107
  ],
1582
3108
  layers: () => ["joints", "split", "sound_range", "carcass_lure"].map((id) => ({ id, name: text.layers[id], default: false })),
@@ -1612,15 +3138,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1612
3138
  // src/index.ts
1613
3139
  var countTriangles = (root) => {
1614
3140
  let triangles = 0;
1615
- root.traverse((object) => {
1616
- const mesh = object;
1617
- if (!mesh.isMesh) return;
1618
- const geometry = mesh.geometry;
3141
+ root.traverse((object2) => {
3142
+ const mesh2 = object2;
3143
+ if (!mesh2.isMesh) return;
3144
+ const geometry = mesh2.geometry;
1619
3145
  triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3;
1620
3146
  });
1621
3147
  return Math.round(triangles);
1622
3148
  };
1623
- var createAnimalsThree = (THREE, options = {}) => {
3149
+ var createAnimalsThree = (host, options = {}) => {
3150
+ const THREE = shapeGeometries(host);
1624
3151
  const random = options.random ?? createStream(options.seed ?? 7);
1625
3152
  const documentOf = () => {
1626
3153
  const found = options.document ?? globalThis.document;
@@ -1637,8 +3164,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
1637
3164
  else if (species.body.kind === "bird") buildBird(THREE, rig, { extra: BIRD_EXTRAS[species.id] });
1638
3165
  else buildQuadruped(THREE, rig, { document: species.body.texture ? documentOf() : void 0, extra: QUADRUPED_EXTRAS[species.id] });
1639
3166
  rig.triangles = countTriangles(rig.root);
1640
- rig.root.traverse((object) => {
1641
- if (object.isMesh) object.userData.speciesId = species.id;
3167
+ markKitOrigin(rig.root, { owner: "game-animals-three", kind: "animal", id: species.id });
3168
+ rig.root.traverse((object2) => {
3169
+ if (object2.isMesh) object2.userData.speciesId = species.id;
1642
3170
  });
1643
3171
  return rig;
1644
3172
  },