@adatechnology/game-interactables-three 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1713 @@
1
+ "use strict";
2
+ var GameInteractablesThree = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ MATERIAL_KEYS: () => MATERIAL_KEYS,
25
+ applyPose: () => applyPose,
26
+ attachSway: () => attachSway,
27
+ catenary: () => catenary,
28
+ createCord: () => createCord,
29
+ createDefaultMaterials: () => createDefaultMaterials,
30
+ createEffects: () => createEffects,
31
+ createMeshApi: () => createMeshApi,
32
+ createMirrors: () => createMirrors,
33
+ createScreen: () => createScreen,
34
+ createThreeAdapter: () => createThreeAdapter,
35
+ mergeParts: () => mergeParts,
36
+ patchSwayShader: () => patchSwayShader,
37
+ poseOf: () => poseOf2,
38
+ rememberRest: () => rememberRest,
39
+ swayMaterial: () => swayMaterial,
40
+ syncObject: () => syncObject
41
+ });
42
+
43
+ // src/effects.ts
44
+ var POOLS = ["smoke", "steam", "fire", "water", "spark"];
45
+ var WEIGHT = { smoke: 0.27, steam: 0.15, fire: 0.2, water: 0.28, spark: 0.1 };
46
+ var VERT = (
47
+ /* glsl */
48
+ `
49
+ attribute float aSize;
50
+ attribute float aAlpha;
51
+ attribute vec3 aColor;
52
+ uniform float uScale;
53
+ varying float vAlpha;
54
+ varying vec3 vColor;
55
+ void main() {
56
+ vAlpha = aAlpha;
57
+ vColor = aColor;
58
+ vec4 mv = modelViewMatrix * vec4(position, 1.0);
59
+ gl_PointSize = max(aSize * uScale / max(-mv.z, 0.05), 1.0);
60
+ gl_Position = projectionMatrix * mv;
61
+ }
62
+ `
63
+ );
64
+ var FRAG = (
65
+ /* glsl */
66
+ `
67
+ uniform sampler2D uMap;
68
+ varying float vAlpha;
69
+ varying vec3 vColor;
70
+ void main() {
71
+ vec4 t = texture2D(uMap, gl_PointCoord);
72
+ gl_FragColor = vec4(vColor, vAlpha * t.a);
73
+ }
74
+ `
75
+ );
76
+ function mulberry(seed) {
77
+ let a = seed >>> 0;
78
+ return () => {
79
+ a = a + 1831565813 >>> 0;
80
+ let t = a;
81
+ t = Math.imul(t ^ t >>> 15, t | 1);
82
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
83
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
84
+ };
85
+ }
86
+ function makeSprite(THREE, doc) {
87
+ const N = 64;
88
+ try {
89
+ const canvas = doc?.createElement("canvas");
90
+ const ctx = canvas?.getContext?.("2d");
91
+ if (canvas && ctx && ctx.createRadialGradient) {
92
+ canvas.width = N;
93
+ canvas.height = N;
94
+ const g = ctx.createRadialGradient(N / 2, N / 2, 0, N / 2, N / 2, N / 2);
95
+ g.addColorStop(0, "rgba(255,255,255,1)");
96
+ g.addColorStop(0.4, "rgba(255,255,255,0.55)");
97
+ g.addColorStop(1, "rgba(255,255,255,0)");
98
+ ctx.fillStyle = g;
99
+ ctx.fillRect(0, 0, N, N);
100
+ const tex2 = new THREE.CanvasTexture(canvas);
101
+ return tex2;
102
+ }
103
+ } catch {
104
+ }
105
+ const data = new Uint8Array(N * N * 4);
106
+ for (let y = 0; y < N; y++) {
107
+ for (let x = 0; x < N; x++) {
108
+ const d = Math.hypot(x + 0.5 - N / 2, y + 0.5 - N / 2) / (N / 2);
109
+ const a = d >= 1 ? 0 : Math.pow(1 - d, 1.6);
110
+ const i = (y * N + x) * 4;
111
+ data[i] = data[i + 1] = data[i + 2] = 255;
112
+ data[i + 3] = Math.round(a * 255);
113
+ }
114
+ }
115
+ const tex = new THREE.DataTexture(data, N, N, THREE.RGBAFormat);
116
+ tex.needsUpdate = true;
117
+ return tex;
118
+ }
119
+ function makeStreaks(THREE) {
120
+ const W = 16;
121
+ const H = 64;
122
+ const data = new Uint8Array(W * H * 4);
123
+ const hash = (x, y) => {
124
+ let h = Math.imul(x + 1, 374761393) ^ Math.imul(y + 7, 668265263);
125
+ h = Math.imul(h ^ h >>> 13, 1274126177);
126
+ return ((h ^ h >>> 16) >>> 0) / 4294967296;
127
+ };
128
+ for (let y = 0; y < H; y++) {
129
+ for (let x = 0; x < W; x++) {
130
+ const col = 0.35 + 0.65 * hash(x, 0);
131
+ const row = 0.55 + 0.45 * Math.sin((y / H * 3 + hash(x, 1) * 6) * Math.PI * 2);
132
+ const i = (y * W + x) * 4;
133
+ data[i] = data[i + 1] = data[i + 2] = 255;
134
+ data[i + 3] = Math.round(255 * Math.min(1, col * row * 1.3));
135
+ }
136
+ }
137
+ const tex = new THREE.DataTexture(data, W, H, THREE.RGBAFormat);
138
+ tex.wrapS = THREE.RepeatWrapping;
139
+ tex.wrapT = THREE.RepeatWrapping;
140
+ tex.needsUpdate = true;
141
+ return tex;
142
+ }
143
+ function createEffects(THREE, a, b = {}) {
144
+ const legacy = !!a.isObject3D;
145
+ const options = legacy ? { ...b, scene: a } : a;
146
+ const scene = options.scene;
147
+ if (!scene) throw new Error("createEffects: options.scene is required");
148
+ if (!legacy && options.camera === void 0) throw new Error("createEffects: options.camera is required (pass null for a 90 degree lens)");
149
+ const total = Math.max(POOLS.length, Math.floor(options.maxParticles ?? 800));
150
+ const rand = mulberry(options.seed ?? 1);
151
+ const wind = [options.wind?.[0] ?? 0, options.wind?.[1] ?? 0];
152
+ const sprite = makeSprite(THREE, options.document);
153
+ let viewHeight = options.viewHeight ?? 720;
154
+ let cameraSource = options.camera ?? null;
155
+ let camera = typeof cameraSource === "function" ? cameraSource() : cameraSource;
156
+ const scale = { value: 0 };
157
+ const refreshScale = (cam) => {
158
+ const fov = cam ? 2 * Math.atan(Math.tan(Math.min(Math.max(cam.fov, 1), 175) * Math.PI / 360) / Math.max(cam.zoom ?? 1, 0.01)) : Math.PI / 2;
159
+ scale.value = viewHeight / (2 * Math.tan(fov / 2));
160
+ };
161
+ refreshScale(camera);
162
+ const pools = {};
163
+ const objects = {};
164
+ let used = 0;
165
+ POOLS.forEach((kind, i) => {
166
+ const cap = i === POOLS.length - 1 ? Math.max(1, total - used) : Math.max(1, Math.floor(total * WEIGHT[kind]));
167
+ used += cap;
168
+ const geo = new THREE.BufferGeometry();
169
+ const aPos = new THREE.BufferAttribute(new Float32Array(cap * 3), 3);
170
+ const aSize = new THREE.BufferAttribute(new Float32Array(cap), 1);
171
+ const aAlpha = new THREE.BufferAttribute(new Float32Array(cap), 1);
172
+ const aColor = new THREE.BufferAttribute(new Float32Array(cap * 3), 3);
173
+ for (const a2 of [aPos, aSize, aAlpha, aColor]) a2.setUsage(THREE.DynamicDrawUsage);
174
+ geo.setAttribute("position", aPos);
175
+ geo.setAttribute("aSize", aSize);
176
+ geo.setAttribute("aAlpha", aAlpha);
177
+ geo.setAttribute("aColor", aColor);
178
+ geo.setDrawRange(0, 0);
179
+ const additive = kind === "fire" || kind === "spark";
180
+ const mat = new THREE.ShaderMaterial({
181
+ uniforms: { uMap: { value: sprite }, uScale: scale },
182
+ vertexShader: VERT,
183
+ fragmentShader: FRAG,
184
+ transparent: true,
185
+ depthWrite: false,
186
+ blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending
187
+ });
188
+ const points = new THREE.Points(geo, mat);
189
+ points.frustumCulled = false;
190
+ points.name = `effects:${kind}`;
191
+ scene.add(points);
192
+ objects[kind] = points;
193
+ pools[kind] = {
194
+ kind,
195
+ cap,
196
+ count: 0,
197
+ pos: new Float32Array(cap * 3),
198
+ vel: new Float32Array(cap * 3),
199
+ age: new Float32Array(cap),
200
+ life: new Float32Array(cap),
201
+ size0: new Float32Array(cap),
202
+ size1: new Float32Array(cap),
203
+ tint: new Float32Array(cap),
204
+ flags: new Uint8Array(cap),
205
+ ground: new Float32Array(cap).fill(-1e9),
206
+ aSize,
207
+ aAlpha,
208
+ aColor,
209
+ aPos,
210
+ points
211
+ };
212
+ });
213
+ const debt = /* @__PURE__ */ new Map();
214
+ let stamp = 0;
215
+ let clock = 0;
216
+ const spawn = (p, x, y, z, vx, vy, vz, life, s0, s1, tint, flag = 0, ground = -1e9) => {
217
+ if (p.count >= p.cap) return;
218
+ const i = p.count++;
219
+ p.pos[i * 3] = x;
220
+ p.pos[i * 3 + 1] = y;
221
+ p.pos[i * 3 + 2] = z;
222
+ p.vel[i * 3] = vx;
223
+ p.vel[i * 3 + 1] = vy;
224
+ p.vel[i * 3 + 2] = vz;
225
+ p.age[i] = 0;
226
+ p.life[i] = life;
227
+ p.size0[i] = s0;
228
+ p.size1[i] = s1;
229
+ p.tint[i] = tint;
230
+ p.flags[i] = flag;
231
+ p.ground[i] = ground;
232
+ };
233
+ const emitFrom = (e, n) => {
234
+ const k = Math.min(1, Math.max(0, e.intensity));
235
+ const size = e.size > 0 ? e.size : 0.2;
236
+ const [px, py, pz] = e.pos;
237
+ for (let j = 0; j < n; j++) {
238
+ switch (e.kind) {
239
+ case "smoke":
240
+ spawn(pools.smoke, px + (rand() - 0.5) * size * 0.6, py, pz + (rand() - 0.5) * size * 0.6, (rand() - 0.5) * 0.2, 0.45 + rand() * 0.45, (rand() - 0.5) * 0.2, 2.8 + rand() * 1.8, size * 0.6, size * 2.6, 0.3 + rand() * 0.25, 0);
241
+ break;
242
+ case "steam":
243
+ spawn(pools.steam, px + (rand() - 0.5) * size * 0.5, py, pz + (rand() - 0.5) * size * 0.5, (rand() - 0.5) * 0.12, 0.3 + rand() * 0.3, (rand() - 0.5) * 0.12, 1.3 + rand() * 1.1, size * 0.3, size * 1.3, 0.92, 0);
244
+ break;
245
+ case "fire":
246
+ spawn(pools.fire, px + (rand() - 0.5) * size * 0.7, py, pz + (rand() - 0.5) * size * 0.7, (rand() - 0.5) * 0.25, 0.8 + rand() * 0.9 * k, (rand() - 0.5) * 0.25, 0.35 + rand() * 0.4, size * 0.75, size * 0.15, rand(), 0);
247
+ break;
248
+ case "flame": {
249
+ const a2 = rand() * Math.PI * 2;
250
+ const r = size * 0.8;
251
+ spawn(pools.fire, px + Math.cos(a2) * r, py + 0.01, pz + Math.sin(a2) * r, 0, 0.06 + rand() * 0.1, 0, 0.14 + rand() * 0.12, size * 0.55, size * 0.15, rand(), 1);
252
+ break;
253
+ }
254
+ case "spark": {
255
+ const a2 = rand() * Math.PI * 2;
256
+ const up = 0.4 + rand() * 0.6;
257
+ const sp = 1.2 + rand() * 2;
258
+ spawn(pools.spark, px, py, pz, Math.cos(a2) * sp * (1 - up * 0.5), up * sp * 1.4, Math.sin(a2) * sp * (1 - up * 0.5), 0.35 + rand() * 0.5, size * 0.18, size * 0.05, rand(), 0);
259
+ break;
260
+ }
261
+ case "water":
262
+ break;
263
+ }
264
+ }
265
+ };
266
+ const arc = { x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0, t: 0, stopY: 0, r0: 0, r1: 0 };
267
+ const floorY = options.floorY ?? 0;
268
+ const arcOf = (e, k) => {
269
+ let [dx, dy, dz] = e.dir;
270
+ const len = Math.hypot(dx, dy, dz);
271
+ if (len < 1e-6) {
272
+ dx = 0;
273
+ dy = -1;
274
+ dz = 0;
275
+ } else {
276
+ dx /= len;
277
+ dy /= len;
278
+ dz /= len;
279
+ }
280
+ const speed = 0.7 + 1.8 * k;
281
+ const stopY = e.reach !== void 0 ? Math.max(e.pos[1] - e.reach, floorY) : floorY;
282
+ const drop = e.pos[1] - stopY;
283
+ if (drop < 0.02) return false;
284
+ const vy = dy * speed;
285
+ arc.x = e.pos[0];
286
+ arc.y = e.pos[1];
287
+ arc.z = e.pos[2];
288
+ arc.vx = dx * speed;
289
+ arc.vy = vy;
290
+ arc.vz = dz * speed;
291
+ arc.t = Math.min(2.5, (vy + Math.sqrt(vy * vy + 19.6 * drop)) / 9.8);
292
+ arc.stopY = stopY;
293
+ const size = e.size > 0 ? e.size : 0.05;
294
+ if (size < 0.1) {
295
+ arc.r0 = Math.max(4e-3, size * 0.5);
296
+ arc.r1 = arc.r0 * 0.55;
297
+ } else {
298
+ arc.r0 = size * 0.45;
299
+ arc.r1 = arc.r0 * 1.2;
300
+ }
301
+ return true;
302
+ };
303
+ const emitWater = (e, k, nSpray, nSplash, nMist) => {
304
+ const size = e.size > 0 ? e.size : 0.05;
305
+ const wide = size >= 0.1;
306
+ for (let j = 0; j < nSpray; j++) {
307
+ const s = rand();
308
+ const t = s * arc.t;
309
+ const r = (arc.r0 + (arc.r1 - arc.r0) * s) * (0.7 + rand() * 0.5);
310
+ const a2 = rand() * Math.PI * 2;
311
+ const ox = Math.cos(a2) * r;
312
+ const oz = Math.sin(a2) * r;
313
+ spawn(
314
+ pools.water,
315
+ arc.x + arc.vx * t + ox,
316
+ arc.y + arc.vy * t - 4.9 * t * t,
317
+ arc.z + arc.vz * t + oz,
318
+ arc.vx + (wide ? ox * 1.2 : 0) + (rand() - 0.5) * 0.25,
319
+ arc.vy - 9.8 * t,
320
+ arc.vz + (wide ? oz * 1.2 : 0) + (rand() - 0.5) * 0.25,
321
+ 0.22 + rand() * 0.25,
322
+ 0.011 + rand() * 0.012,
323
+ 9e-3 + rand() * 8e-3,
324
+ rand(),
325
+ 2,
326
+ arc.stopY
327
+ );
328
+ }
329
+ const ex = arc.x + arc.vx * arc.t;
330
+ const ez = arc.z + arc.vz * arc.t;
331
+ for (let j = 0; j < nSplash; j++) {
332
+ const a2 = rand() * Math.PI * 2;
333
+ const r = arc.r1 * Math.sqrt(rand());
334
+ const sp = (wide ? 0.5 : 0.25) + rand() * 0.7;
335
+ spawn(pools.water, ex + Math.cos(a2) * r, arc.stopY + 5e-3, ez + Math.sin(a2) * r, Math.cos(a2) * sp, 0.9 + rand() * 1.3, Math.sin(a2) * sp, 0.3 + rand() * 0.25, 0.014 + rand() * 0.018, 0.01, rand(), 3, arc.stopY);
336
+ }
337
+ for (let j = 0; j < nMist; j++) {
338
+ const a2 = rand() * Math.PI * 2;
339
+ const r = arc.r1 * 1.4 * Math.sqrt(rand());
340
+ spawn(pools.water, ex + Math.cos(a2) * r, arc.stopY + 0.02, ez + Math.sin(a2) * r, Math.cos(a2) * 0.15, 0.18 + rand() * 0.15, Math.sin(a2) * 0.15, 0.7 + rand() * 0.5, 0.06 + size * 0.2, 0.2 + size * 0.5, rand(), 4, arc.stopY);
341
+ }
342
+ void k;
343
+ };
344
+ const emitDrip = (e, n) => {
345
+ const size = e.size > 0 ? e.size : 0.05;
346
+ const stopY = e.reach !== void 0 ? Math.max(e.pos[1] - e.reach, floorY) : floorY;
347
+ for (let j = 0; j < n; j++) {
348
+ const sx = (rand() - 0.5) * 0.01;
349
+ const sz = (rand() - 0.5) * 0.01;
350
+ spawn(pools.water, e.pos[0] + sx, e.pos[1], e.pos[2] + sz, e.dir[0] * 0.08, e.dir[1] * 0.08, e.dir[2] * 0.08, 1.4, size * 0.22, size * 0.2, rand(), 1, stopY);
351
+ }
352
+ };
353
+ const RATE = { smoke: 42, steam: 48, fire: 150, flame: 55, spark: 40, water: 0 };
354
+ const SEG = 12;
355
+ const SIDES = 8;
356
+ const maxStreams = Math.max(0, Math.floor(options.maxStreams ?? 6));
357
+ const streaksTex = makeStreaks(THREE);
358
+ const streamMat = new THREE.MeshBasicMaterial({ color: 10276095, map: streaksTex, vertexColors: true, transparent: true, opacity: 0.8, depthWrite: false, side: THREE.DoubleSide });
359
+ const streamIndex = [];
360
+ for (let i = 0; i < SEG; i++) {
361
+ for (let m = 0; m < SIDES; m++) {
362
+ const a2 = i * (SIDES + 1) + m;
363
+ const b2 = a2 + SIDES + 1;
364
+ streamIndex.push(a2, b2, a2 + 1, a2 + 1, b2, b2 + 1);
365
+ }
366
+ }
367
+ const group = new THREE.Group();
368
+ group.name = "effects:meshes";
369
+ scene.add(group);
370
+ const streams = [];
371
+ for (let i = 0; i < maxStreams; i++) {
372
+ const geo = new THREE.BufferGeometry();
373
+ const n = (SEG + 1) * (SIDES + 1);
374
+ geo.setAttribute("position", new THREE.BufferAttribute(new Float32Array(n * 3), 3).setUsage(THREE.DynamicDrawUsage));
375
+ geo.setAttribute("color", new THREE.BufferAttribute(new Float32Array(n * 4), 4).setUsage(THREE.DynamicDrawUsage));
376
+ const uv = new Float32Array(n * 2);
377
+ geo.setAttribute("uv", new THREE.BufferAttribute(uv, 2).setUsage(THREE.DynamicDrawUsage));
378
+ geo.setIndex(streamIndex);
379
+ const mesh = new THREE.Mesh(geo, streamMat);
380
+ mesh.frustumCulled = false;
381
+ mesh.visible = false;
382
+ mesh.name = `effects:stream${i}`;
383
+ group.add(mesh);
384
+ streams.push(mesh);
385
+ }
386
+ const writeStream = (mesh, k) => {
387
+ const geo = mesh.geometry;
388
+ const pos = geo.getAttribute("position").array;
389
+ const col = geo.getAttribute("color").array;
390
+ const uv = geo.getAttribute("uv").array;
391
+ for (let i = 0; i <= SEG; i++) {
392
+ const s = i / SEG;
393
+ const t = s * arc.t;
394
+ const cx = arc.x + arc.vx * t;
395
+ const cy = arc.y + arc.vy * t - 4.9 * t * t;
396
+ const cz = arc.z + arc.vz * t;
397
+ let tx = arc.vx;
398
+ let ty = arc.vy - 9.8 * t;
399
+ let tz = arc.vz;
400
+ const tl = Math.hypot(tx, ty, tz) || 1;
401
+ tx /= tl;
402
+ ty /= tl;
403
+ tz /= tl;
404
+ let bx = tz;
405
+ const by = 0;
406
+ let bz = -tx;
407
+ const bl = Math.hypot(bx, bz);
408
+ if (bl < 1e-4) {
409
+ bx = 1;
410
+ bz = 0;
411
+ } else {
412
+ bx /= bl;
413
+ bz /= bl;
414
+ }
415
+ const nx = ty * bz - tz * by;
416
+ const ny = tz * bx - tx * bz;
417
+ const nz = tx * by - ty * bx;
418
+ const r = arc.r0 + (arc.r1 - arc.r0) * s;
419
+ const alpha = Math.min(1, s * 7) * (0.95 - 0.3 * s) * Math.min(1, 0.55 + k);
420
+ for (let m = 0; m <= SIDES; m++) {
421
+ const a2 = m / SIDES * Math.PI * 2;
422
+ const ca = Math.cos(a2) * r;
423
+ const sa = Math.sin(a2) * r;
424
+ const v = i * (SIDES + 1) + m;
425
+ pos[v * 3] = cx + bx * ca + nx * sa;
426
+ pos[v * 3 + 1] = cy + by * ca + ny * sa;
427
+ pos[v * 3 + 2] = cz + bz * ca + nz * sa;
428
+ col[v * 4] = col[v * 4 + 1] = col[v * 4 + 2] = 1;
429
+ col[v * 4 + 3] = alpha;
430
+ uv[v * 2] = m / SIDES * 3;
431
+ uv[v * 2 + 1] = s * arc.t * 2.2;
432
+ }
433
+ }
434
+ ;
435
+ geo.getAttribute("position").needsUpdate = true;
436
+ geo.getAttribute("color").needsUpdate = true;
437
+ geo.getAttribute("uv").needsUpdate = true;
438
+ mesh.visible = true;
439
+ };
440
+ const CONES = 16;
441
+ const maxFlames = Math.max(0, Math.floor(options.maxFlames ?? 16));
442
+ const flameGeo = new THREE.BufferGeometry();
443
+ const flamePos = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage);
444
+ const flameCol = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage);
445
+ flameGeo.setAttribute("position", flamePos);
446
+ flameGeo.setAttribute("color", flameCol);
447
+ const flameIdx = [];
448
+ for (let i = 0; i < maxFlames * CONES; i++) {
449
+ const v = i * 4;
450
+ flameIdx.push(v, v + 1, v + 3, v + 1, v + 2, v + 3, v + 2, v, v + 3);
451
+ }
452
+ flameGeo.setIndex(flameIdx);
453
+ flameGeo.setDrawRange(0, 0);
454
+ const flames = new THREE.Mesh(
455
+ flameGeo,
456
+ new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide })
457
+ );
458
+ flames.frustumCulled = false;
459
+ flames.visible = false;
460
+ flames.name = "effects:flames";
461
+ group.add(flames);
462
+ const hashOf = (id) => {
463
+ let h = 2166136261;
464
+ for (let i = 0; i < id.length; i++) h = Math.imul(h ^ id.charCodeAt(i), 16777619);
465
+ return (h >>> 0) % 1e3 / 1e3;
466
+ };
467
+ const writeFlame = (slot, e, time) => {
468
+ const pa = flamePos.array;
469
+ const ca = flameCol.array;
470
+ const k = Math.min(1, Math.max(0, e.intensity));
471
+ const size = e.size > 0 ? e.size : 0.05;
472
+ const R = size * 0.8;
473
+ const bw = Math.max(4e-3, size * 0.13);
474
+ const phase = hashOf(e.id) * 40;
475
+ for (let c = 0; c < CONES; c++) {
476
+ const a2 = c / CONES * Math.PI * 2;
477
+ const ux = Math.cos(a2);
478
+ const uz = Math.sin(a2);
479
+ const bx = e.pos[0] + ux * R;
480
+ const bz = e.pos[2] + uz * R;
481
+ const by = e.pos[1];
482
+ const h = (0.025 + size * 0.45) * k * (0.55 + 0.45 * Math.abs(Math.sin(time * 19 + c * 2.3 + phase)));
483
+ const v = (slot * CONES + c) * 4;
484
+ const tx = -uz * bw;
485
+ const tz = ux * bw;
486
+ pa.set([bx + tx, by, bz + tz, bx - tx, by, bz - tz, bx + ux * bw * 0.9, by, bz + uz * bw * 0.9, bx - ux * h * 0.12, by + h, bz - uz * h * 0.12], v * 3);
487
+ const lo = 0.3 * k;
488
+ ca.set([0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.02 * lo, 0.1 * lo, 0.5 * lo], v * 3);
489
+ }
490
+ };
491
+ const advance = (p, dt) => {
492
+ let i = 0;
493
+ const swayX = wind[0];
494
+ const swayZ = wind[1];
495
+ while (i < p.count) {
496
+ p.age[i] += dt;
497
+ const age = p.age[i];
498
+ const life = p.life[i];
499
+ if (age >= life || p.kind === "water" && p.pos[i * 3 + 1] < p.ground[i]) {
500
+ const last = --p.count;
501
+ if (i !== last) {
502
+ p.pos.copyWithin(i * 3, last * 3, last * 3 + 3);
503
+ p.vel.copyWithin(i * 3, last * 3, last * 3 + 3);
504
+ p.age[i] = p.age[last];
505
+ p.life[i] = p.life[last];
506
+ p.size0[i] = p.size0[last];
507
+ p.size1[i] = p.size1[last];
508
+ p.tint[i] = p.tint[last];
509
+ p.flags[i] = p.flags[last];
510
+ p.ground[i] = p.ground[last];
511
+ }
512
+ continue;
513
+ }
514
+ const j = i * 3;
515
+ switch (p.kind) {
516
+ case "smoke":
517
+ case "steam":
518
+ p.vel[j] += (swayX - p.vel[j]) * Math.min(1, dt * 0.8);
519
+ p.vel[j + 2] += (swayZ - p.vel[j + 2]) * Math.min(1, dt * 0.8);
520
+ break;
521
+ case "water":
522
+ if (p.flags[i] !== 4) p.vel[j + 1] -= 9.8 * dt;
523
+ break;
524
+ case "spark":
525
+ p.vel[j + 1] -= 7 * dt;
526
+ break;
527
+ case "fire":
528
+ p.vel[j] += (swayX * 0.1 - p.vel[j]) * Math.min(1, dt * 2);
529
+ break;
530
+ }
531
+ p.pos[j] += p.vel[j] * dt;
532
+ p.pos[j + 1] += p.vel[j + 1] * dt;
533
+ p.pos[j + 2] += p.vel[j + 2] * dt;
534
+ if (p.kind === "water" && p.pos[j + 1] < p.ground[i]) {
535
+ p.pos[j + 1] = p.ground[i];
536
+ p.age[i] = p.life[i];
537
+ }
538
+ i++;
539
+ }
540
+ };
541
+ const write = (p) => {
542
+ const n = p.count;
543
+ const pa = p.aPos.array;
544
+ const sa = p.aSize.array;
545
+ const aa = p.aAlpha.array;
546
+ const ca = p.aColor.array;
547
+ for (let i = 0; i < n; i++) {
548
+ const u = p.age[i] / p.life[i];
549
+ const j = i * 3;
550
+ pa[j] = p.pos[j];
551
+ pa[j + 1] = p.pos[j + 1];
552
+ pa[j + 2] = p.pos[j + 2];
553
+ sa[i] = p.size0[i] + (p.size1[i] - p.size0[i]) * u;
554
+ let alpha;
555
+ switch (p.kind) {
556
+ case "smoke":
557
+ alpha = 0.55 * Math.min(1, u * 8) * (1 - u);
558
+ ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i];
559
+ break;
560
+ case "steam":
561
+ alpha = 0.4 * Math.min(1, u * 6) * (1 - u);
562
+ ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i];
563
+ break;
564
+ case "fire":
565
+ alpha = (p.flags[i] ? 0.95 : 0.8) * (1 - u * u);
566
+ if (p.flags[i]) {
567
+ ca[j] = 0.25 + 0.15 * p.tint[i];
568
+ ca[j + 1] = 0.5 + 0.2 * p.tint[i];
569
+ ca[j + 2] = 1;
570
+ } else {
571
+ ca[j] = 1;
572
+ ca[j + 1] = Math.max(0.15, 0.85 - u * 0.7 - p.tint[i] * 0.1);
573
+ ca[j + 2] = Math.max(0, 0.25 - u * 0.25);
574
+ }
575
+ break;
576
+ case "spark":
577
+ alpha = 1 - u;
578
+ ca[j] = 1;
579
+ ca[j + 1] = 0.75 - u * 0.4;
580
+ ca[j + 2] = 0.3;
581
+ break;
582
+ default: {
583
+ const f = p.flags[i];
584
+ alpha = f === 4 ? 0.16 * (1 - u) : f === 3 ? 0.8 * (1 - u * 0.5) : f === 2 ? 0.65 * (1 - u * 0.6) : 0.8;
585
+ ca[j] = f === 4 ? 0.85 : 0.5;
586
+ ca[j + 1] = f === 4 ? 0.92 : 0.74;
587
+ ca[j + 2] = 1;
588
+ }
589
+ }
590
+ aa[i] = alpha;
591
+ }
592
+ p.aPos.needsUpdate = true;
593
+ p.aSize.needsUpdate = true;
594
+ p.aAlpha.needsUpdate = true;
595
+ p.aColor.needsUpdate = true;
596
+ p.points.geometry.setDrawRange(0, n);
597
+ };
598
+ return {
599
+ objects,
600
+ meshes: { group, streams, flames },
601
+ update(dt, emitters, cam) {
602
+ camera = cam ?? (typeof cameraSource === "function" ? cameraSource() : cameraSource);
603
+ refreshScale(camera);
604
+ const step = Math.min(Math.max(dt, 0), 0.1);
605
+ stamp++;
606
+ clock += step;
607
+ streaksTex.offset.y = -(clock * 2.4) % 1;
608
+ let nStreams = 0;
609
+ let nFlames = 0;
610
+ const due = (key, rate) => {
611
+ let d = debt.get(key);
612
+ if (!d) {
613
+ d = { carry: 0, stamp };
614
+ debt.set(key, d);
615
+ }
616
+ d.stamp = stamp;
617
+ d.carry += rate * step;
618
+ const n = Math.floor(d.carry);
619
+ d.carry -= n;
620
+ return n;
621
+ };
622
+ for (let i = 0; i < emitters.length; i++) {
623
+ const e = emitters[i];
624
+ if (!(e.intensity > 0)) continue;
625
+ const k = Math.min(1, e.intensity);
626
+ const size = e.size > 0 ? e.size : 0.05;
627
+ if (e.kind === "water") {
628
+ if (k < 0.25) {
629
+ const n2 = due(e.id, 3 + k * 10);
630
+ if (n2 > 0) emitDrip(e, n2);
631
+ } else if (arcOf(e, k)) {
632
+ const nSpray = due(e.id, (60 + 260 * Math.min(size, 0.4)) * k);
633
+ const nSplash = due(`${e.id}#s`, (45 + 90 * Math.min(size, 0.4)) * k);
634
+ const nMist = due(`${e.id}#m`, 6 * k * (1 + size * 4));
635
+ emitWater(e, k, nSpray, nSplash, nMist);
636
+ if (nStreams < streams.length) writeStream(streams[nStreams++], k);
637
+ }
638
+ continue;
639
+ }
640
+ if (e.kind === "flame" && nFlames < maxFlames) writeFlame(nFlames++, e, clock);
641
+ const n = due(e.id, RATE[e.kind] * k);
642
+ if (n > 0) emitFrom(e, n);
643
+ }
644
+ for (let i = nStreams; i < streams.length; i++) streams[i].visible = false;
645
+ flames.visible = nFlames > 0;
646
+ flameGeo.setDrawRange(0, nFlames * CONES * 9);
647
+ flamePos.needsUpdate = true;
648
+ flameCol.needsUpdate = true;
649
+ if (debt.size > emitters.length * 3 + 8) {
650
+ for (const [id, d] of debt) if (d.stamp !== stamp) debt.delete(id);
651
+ }
652
+ for (const kind of POOLS) {
653
+ advance(pools[kind], step);
654
+ write(pools[kind]);
655
+ }
656
+ },
657
+ setWind(x, z) {
658
+ wind[0] = x;
659
+ wind[1] = z;
660
+ },
661
+ setCamera(cam) {
662
+ cameraSource = cam;
663
+ camera = typeof cam === "function" ? cam() : cam;
664
+ refreshScale(camera);
665
+ },
666
+ setViewHeight(px) {
667
+ viewHeight = px;
668
+ refreshScale(camera);
669
+ },
670
+ stats() {
671
+ const byKind = {};
672
+ let alive = 0;
673
+ let capacity = 0;
674
+ for (const kind of POOLS) {
675
+ byKind[kind] = pools[kind].count;
676
+ alive += pools[kind].count;
677
+ capacity += pools[kind].cap;
678
+ }
679
+ return { alive, capacity, byKind };
680
+ },
681
+ clear() {
682
+ for (const kind of POOLS) {
683
+ pools[kind].count = 0;
684
+ write(pools[kind]);
685
+ }
686
+ debt.clear();
687
+ for (const m of streams) m.visible = false;
688
+ flames.visible = false;
689
+ },
690
+ dispose() {
691
+ for (const kind of POOLS) {
692
+ scene.remove(objects[kind]);
693
+ objects[kind].geometry.dispose();
694
+ objects[kind].material.dispose();
695
+ }
696
+ scene.remove(group);
697
+ for (const m of streams) m.geometry.dispose();
698
+ flameGeo.dispose();
699
+ flames.material.dispose();
700
+ streamMat.dispose();
701
+ streaksTex.dispose();
702
+ sprite.dispose();
703
+ }
704
+ };
705
+ }
706
+
707
+ // src/cord.ts
708
+ function catenary(a, b, sag, n = 12) {
709
+ const count = Math.max(2, Math.floor(n));
710
+ const span = Math.hypot(b[0] - a[0], b[2] - a[2]) || Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
711
+ const depth = Math.max(0, sag);
712
+ const out = [];
713
+ let shape = () => 0;
714
+ if (depth > 1e-6 && span > 1e-6) {
715
+ let lo = 1e-4;
716
+ let hi = span * 1e3;
717
+ for (let i = 0; i < 80; i++) {
718
+ const c2 = (lo + hi) / 2;
719
+ const d = c2 * (Math.cosh(span / (2 * c2)) - 1);
720
+ if (d > depth) lo = c2;
721
+ else hi = c2;
722
+ }
723
+ const c = (lo + hi) / 2;
724
+ shape = (s) => c * (Math.cosh((s - 0.5) * span / c) - Math.cosh(span / (2 * c)));
725
+ }
726
+ for (let i = 0; i < count; i++) {
727
+ const s = i / (count - 1);
728
+ out.push([a[0] + (b[0] - a[0]) * s, a[1] + (b[1] - a[1]) * s + shape(s), a[2] + (b[2] - a[2]) * s]);
729
+ }
730
+ return out;
731
+ }
732
+ function createCord(THREE, points, radius, material, radialSegments = 5) {
733
+ if (points.length < 2) throw new Error("createCord needs at least two points");
734
+ const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(p[0], p[1], p[2])), false, "centripetal");
735
+ const segments = Math.max(4, (points.length - 1) * 4);
736
+ const mesh = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material);
737
+ mesh.name = "cord";
738
+ mesh.castShadow = true;
739
+ return mesh;
740
+ }
741
+
742
+ // src/types.ts
743
+ var MATERIAL_KEYS = ["plain", "metal", "wood", "glass", "fabric", "plant", "water", "emissive", "atlas_picture", "atlas_floor"];
744
+
745
+ // ../game-interactables/dist/index.js
746
+ var DEFAULT_AXIS = [0, 1, 0];
747
+ function poseOf(motion, t) {
748
+ const tt = Math.max(0, Math.min(1, t));
749
+ const axis = motion.axis ?? DEFAULT_AXIS;
750
+ const pose = { position: [0, 0, 0], rotation: { axis: [axis[0], axis[1], axis[2]], angle: 0 }, scale: [1, 1, 1] };
751
+ switch (motion.kind) {
752
+ case "hinge":
753
+ case "rocker":
754
+ case "knob":
755
+ pose.rotation.angle = (motion.angle ?? Math.PI / 2) * tt;
756
+ break;
757
+ case "slide": {
758
+ const d = (motion.distance ?? 0.4) * tt;
759
+ pose.position = [axis[0] * d, axis[1] * d, axis[2] * d];
760
+ break;
761
+ }
762
+ case "scale": {
763
+ const k = 1 + ((motion.scale ?? 0.2) - 1) * tt;
764
+ pose.scale = [axis[0] !== 0 ? k : 1, axis[1] !== 0 ? k : 1, axis[2] !== 0 ? k : 1];
765
+ break;
766
+ }
767
+ }
768
+ return pose;
769
+ }
770
+
771
+ // src/pose.ts
772
+ function poseOf2(motion, t) {
773
+ const k = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0));
774
+ const pose = poseOf(motion, k);
775
+ const [x, y, z] = pose.rotation.axis;
776
+ const l = Math.hypot(x, y, z) || 1;
777
+ const s = Math.sin(pose.rotation.angle / 2);
778
+ return {
779
+ position: [pose.position[0], pose.position[1], pose.position[2]],
780
+ quaternion: [x / l * s, y / l * s, z / l * s, Math.cos(pose.rotation.angle / 2)],
781
+ scale: [pose.scale[0], pose.scale[1], pose.scale[2]]
782
+ };
783
+ }
784
+ function rememberRest(group) {
785
+ let rest = group.userData.rest;
786
+ if (!rest) {
787
+ const p = group.position;
788
+ const q = group.quaternion;
789
+ const s = group.scale;
790
+ rest = { position: [p.x, p.y, p.z], quaternion: [q.x, q.y, q.z, q.w], scale: [s.x, s.y, s.z] };
791
+ group.userData.rest = rest;
792
+ }
793
+ return rest;
794
+ }
795
+ function applyPose(THREE, group, motion, t) {
796
+ const rest = rememberRest(group);
797
+ const pose = poseOf2(motion, t);
798
+ const restQ = new THREE.Quaternion(rest.quaternion[0], rest.quaternion[1], rest.quaternion[2], rest.quaternion[3]);
799
+ const delta = new THREE.Quaternion(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]);
800
+ group.quaternion.copy(restQ).multiply(delta);
801
+ const offset = new THREE.Vector3(pose.position[0], pose.position[1], pose.position[2]).applyQuaternion(restQ);
802
+ group.position.set(rest.position[0] + offset.x, rest.position[1] + offset.y, rest.position[2] + offset.z);
803
+ group.scale.set(rest.scale[0] * pose.scale[0], rest.scale[1] * pose.scale[1], rest.scale[2] * pose.scale[2]);
804
+ group.updateMatrix();
805
+ return group;
806
+ }
807
+ function syncObject(THREE, world, objectId, parts, options = {}) {
808
+ const obj = world.get(objectId);
809
+ if (!obj) return;
810
+ const motionOf = /* @__PURE__ */ new Map();
811
+ for (const p of obj.spec.parts ?? []) {
812
+ motionOf.set(p.id, p.motion);
813
+ const g = parts[p.id];
814
+ if (!g) continue;
815
+ const merged = g.userData.merged;
816
+ if (merged) {
817
+ merged.apply?.(p.motion, world.partT(objectId, p.id));
818
+ continue;
819
+ }
820
+ applyPose(THREE, g, p.motion, world.partT(objectId, p.id));
821
+ }
822
+ for (const soft of obj.spec.soft ?? []) {
823
+ const key = soft.part ?? soft.id;
824
+ const g = parts[key] ?? parts[soft.id];
825
+ if (!g) continue;
826
+ const { ax, az } = world.sway(objectId, soft.id);
827
+ const batch = g.userData.merged;
828
+ if (batch?.kind === "sway") {
829
+ const i = batch.index?.[soft.id];
830
+ if (i !== void 0) batch.setAt?.(i, ax, az);
831
+ if (options.time !== void 0) batch.setTime?.(options.time);
832
+ continue;
833
+ }
834
+ let shaded = false;
835
+ g.traverse((o) => {
836
+ const sway = o.userData.sway;
837
+ if (sway) {
838
+ sway.set(ax, az, options.time);
839
+ shaded = true;
840
+ }
841
+ });
842
+ if (shaded) continue;
843
+ rememberRest(g);
844
+ if (!motionOf.has(key)) applyPose(THREE, g, { kind: "hinge", angle: 0 }, 0);
845
+ const rot = new THREE.Quaternion().setFromEuler(new THREE.Euler(ax, 0, az, "XYZ"));
846
+ const anchor = new THREE.Vector3(soft.anchor[0], soft.anchor[1], soft.anchor[2]);
847
+ g.position.sub(anchor).applyQuaternion(rot).add(anchor);
848
+ g.quaternion.premultiply(rot);
849
+ g.updateMatrix();
850
+ }
851
+ }
852
+
853
+ // src/merge.ts
854
+ var QROT = (
855
+ /* glsl */
856
+ `
857
+ vec3 batchRotate(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); }
858
+ `
859
+ );
860
+ function patchProgram(material, key, patchShader, inherit) {
861
+ const previous = material.onBeforeCompile;
862
+ material.onBeforeCompile = (shader, renderer) => {
863
+ previous?.call(material, shader, renderer);
864
+ patchShader(shader);
865
+ };
866
+ const inherited = inherit?.customProgramCacheKey;
867
+ material.customProgramCacheKey = inherited && inherit ? () => `${inherited.call(inherit)}|${key}` : () => key;
868
+ return material;
869
+ }
870
+ function cloneKeepingHooks(base) {
871
+ const clone = base.clone();
872
+ if (base.defines) clone.defines = { ...clone.defines ?? {}, ...base.defines };
873
+ if (base.onBeforeCompile !== Object.getPrototypeOf(base).onBeforeCompile) clone.onBeforeCompile = base.onBeforeCompile;
874
+ clone.userData = { ...base.userData, mergedFrom: base.uuid };
875
+ return clone;
876
+ }
877
+ function patchedSet(THREE, base, key, patchShader) {
878
+ return {
879
+ material: patchProgram(cloneKeepingHooks(base), key, patchShader, base),
880
+ depth: patchProgram(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), key, patchShader),
881
+ distance: patchProgram(new THREE.MeshDistanceMaterial(), key, patchShader)
882
+ };
883
+ }
884
+ function concat(THREE, pieces, extraSizes) {
885
+ let verts = 0;
886
+ let idx = 0;
887
+ for (const p of pieces) {
888
+ verts += p.geometry.getAttribute("position").count;
889
+ idx += p.geometry.index.count;
890
+ }
891
+ const pos = new Float32Array(verts * 3);
892
+ const nor = new Float32Array(verts * 3);
893
+ const col = new Float32Array(verts * 3);
894
+ const uv = new Float32Array(verts * 2);
895
+ const index = new Uint32Array(idx);
896
+ const extra = {};
897
+ for (const [k, n] of Object.entries(extraSizes)) extra[k] = new Float32Array(verts * n);
898
+ const v = new THREE.Vector3();
899
+ const n3 = new THREE.Matrix3();
900
+ let vo = 0;
901
+ let io = 0;
902
+ for (const p of pieces) {
903
+ const g = p.geometry;
904
+ const count = g.getAttribute("position").count;
905
+ n3.getNormalMatrix(p.matrix);
906
+ const gp = g.getAttribute("position");
907
+ const gn = g.getAttribute("normal");
908
+ const gc = g.getAttribute("color");
909
+ const gu = g.getAttribute("uv");
910
+ for (let i = 0; i < count; i++) {
911
+ v.fromBufferAttribute(gp, i).applyMatrix4(p.matrix);
912
+ pos.set([v.x, v.y, v.z], (vo + i) * 3);
913
+ v.fromBufferAttribute(gn, i).applyMatrix3(n3).normalize();
914
+ nor.set([v.x, v.y, v.z], (vo + i) * 3);
915
+ col.set([gc.getX(i), gc.getY(i), gc.getZ(i)], (vo + i) * 3);
916
+ uv.set([gu.getX(i), gu.getY(i)], (vo + i) * 2);
917
+ }
918
+ for (const [k, values] of Object.entries(p.extra)) extra[k].set(values, vo * extraSizes[k]);
919
+ const gi = g.index;
920
+ for (let i = 0; i < gi.count; i++) index[io + i] = gi.getX(i) + vo;
921
+ vo += count;
922
+ io += gi.count;
923
+ }
924
+ const out = new THREE.BufferGeometry();
925
+ out.setAttribute("position", new THREE.BufferAttribute(pos, 3));
926
+ out.setAttribute("normal", new THREE.BufferAttribute(nor, 3));
927
+ out.setAttribute("color", new THREE.BufferAttribute(col, 3));
928
+ out.setAttribute("uv", new THREE.BufferAttribute(uv, 2));
929
+ for (const [k, n] of Object.entries(extraSizes)) out.setAttribute(k, new THREE.BufferAttribute(extra[k], n));
930
+ out.setIndex(new THREE.BufferAttribute(verts > 65535 ? index : new Uint16Array(index), 1));
931
+ out.computeBoundingSphere();
932
+ out.computeBoundingBox();
933
+ return out;
934
+ }
935
+ function meshesOf(group) {
936
+ return group.children.filter((c) => c.isMesh);
937
+ }
938
+ function mergeParts(THREE, root, parts, spec, options = {}) {
939
+ const amplitude = options.swayAmplitude ?? 0.04;
940
+ const skip = /* @__PURE__ */ new Set();
941
+ for (const m of spec.mirrors ?? []) if (m.part) skip.add(m.part);
942
+ for (const [id, g] of Object.entries(parts)) if (Math.abs(g.rotation.y) > 1e-9 || g.userData.merged) skip.add(id);
943
+ const take = (ids) => ids.map((id) => parts[id]);
944
+ const retire = (ids, merged) => {
945
+ for (const id of ids) {
946
+ const g = parts[id];
947
+ g.removeFromParent();
948
+ for (const m of meshesOf(g)) m.geometry.dispose();
949
+ parts[id] = merged;
950
+ }
951
+ root.add(merged);
952
+ };
953
+ const byMaterial = (groups, extra) => {
954
+ const map = /* @__PURE__ */ new Map();
955
+ for (const g of groups) {
956
+ g.updateMatrix();
957
+ for (const mesh of meshesOf(g)) {
958
+ const key = `${String(mesh.userData.material)}|${mesh.material.uuid}`;
959
+ let e = map.get(key);
960
+ if (!e) {
961
+ e = { base: mesh.material, pieces: [], mesh };
962
+ map.set(key, e);
963
+ }
964
+ e.pieces.push({ geometry: mesh.geometry, matrix: g.matrix, extra: extra(g, mesh.geometry.getAttribute("position").count) });
965
+ }
966
+ }
967
+ return map;
968
+ };
969
+ const finishMesh = (name, key, geometry, template, set, group) => {
970
+ const mesh = new THREE.Mesh(geometry, set.material);
971
+ mesh.name = `${name}:${key}`;
972
+ mesh.userData.material = key;
973
+ mesh.castShadow = template.castShadow;
974
+ mesh.receiveShadow = template.receiveShadow;
975
+ mesh.customDepthMaterial = set.depth;
976
+ mesh.customDistanceMaterial = set.distance;
977
+ group.add(mesh);
978
+ };
979
+ const cloths = (spec.soft ?? []).filter((s) => s.kind === "cloth" && parts[s.part ?? s.id] && !skip.has(s.part ?? s.id));
980
+ const clothPart = (s) => s.part ?? s.id;
981
+ const toggled = /* @__PURE__ */ new Set();
982
+ for (const i of spec.interactions ?? []) {
983
+ const a = i.action;
984
+ if (a.do === "toggle_part" && a.part) toggled.add(a.part);
985
+ }
986
+ const swing = cloths.filter((s) => !toggled.has(clothPart(s)));
987
+ if (swing.length >= 2) {
988
+ const ids = swing.map(clothPart);
989
+ const index = {};
990
+ swing.forEach((s, i) => index[s.id] = i);
991
+ const indexOfPart = new Map(swing.map((s, i) => [clothPart(s), i]));
992
+ const anchorOfPart = new Map(swing.map((s) => [clothPart(s), s.anchor[1]]));
993
+ const n = swing.length;
994
+ const uniforms = { time: { value: 0 }, axz: { value: Array.from({ length: n }, () => new THREE.Vector2()) } };
995
+ const groups = take(ids);
996
+ const partIdOf = new Map(groups.map((g, i) => [g, ids[i]]));
997
+ const plan = byMaterial(groups, (g, count) => {
998
+ const id = partIdOf.get(g);
999
+ return { swayId: new Array(count).fill(indexOf(indexOfPart, id)), swayAnchor: new Array(count).fill(anchorOfPart.get(id) ?? 0) };
1000
+ });
1001
+ const merged = new THREE.Group();
1002
+ merged.name = "merge:cloth";
1003
+ const patch2 = (shader) => {
1004
+ shader.uniforms.swayTime = uniforms.time;
1005
+ shader.uniforms.swayAxz = uniforms.axz;
1006
+ shader.vertexShader = `attribute float swayId;
1007
+ attribute float swayAnchor;
1008
+ uniform float swayTime;
1009
+ uniform vec2 swayAxz[${n}];
1010
+ ` + shader.vertexShader.replace(
1011
+ "#include <begin_vertex>",
1012
+ `#include <begin_vertex>
1013
+ {
1014
+ vec2 swayA = swayAxz[int(swayId + 0.5)];
1015
+ float swayHang = max(swayAnchor - position.y, 0.0);
1016
+ float swayFlutter = ${amplitude.toFixed(4)} * sin(swayTime * 1.9 + swayId * 1.7 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);
1017
+ transformed.x += swayHang * (sin(swayA.y) + swayFlutter);
1018
+ transformed.z += swayHang * (-sin(swayA.x) + swayFlutter * 0.6);
1019
+ transformed.y += swayHang * (1.0 - cos(swayA.y) * cos(swayA.x));
1020
+ }`
1021
+ );
1022
+ };
1023
+ for (const e of plan.values()) {
1024
+ const key = String(e.mesh.userData.material);
1025
+ const set = patchedSet(THREE, e.base, `swayBatch:${n}:${amplitude}`, patch2);
1026
+ finishMesh("merge:cloth", key, concat(THREE, e.pieces, { swayId: 1, swayAnchor: 1 }), e.mesh, set, merged);
1027
+ }
1028
+ const batch = {
1029
+ kind: "sway",
1030
+ index,
1031
+ setAt(i, ax, az) {
1032
+ uniforms.axz.value[i].set(ax, az);
1033
+ },
1034
+ setTime(t) {
1035
+ uniforms.time.value = t;
1036
+ }
1037
+ };
1038
+ merged.userData.merged = batch;
1039
+ retire(ids, merged);
1040
+ for (const id of ids) skip.add(id);
1041
+ }
1042
+ const bySignature = /* @__PURE__ */ new Map();
1043
+ for (const p of spec.parts ?? []) {
1044
+ if (!p.group || !parts[p.id] || skip.has(p.id)) continue;
1045
+ const sig = `${p.group}|${JSON.stringify(p.motion)}`;
1046
+ const list = bySignature.get(sig);
1047
+ if (list) list.push(p.id);
1048
+ else bySignature.set(sig, [p.id]);
1049
+ }
1050
+ for (const [sig, ids] of bySignature) {
1051
+ if (ids.length < 2) continue;
1052
+ const motion = (spec.parts ?? []).find((p) => p.id === ids[0]).motion;
1053
+ const uniforms = { q: { value: new THREE.Vector4(0, 0, 0, 1) }, pos: { value: new THREE.Vector3() }, scale: { value: new THREE.Vector3(1, 1, 1) } };
1054
+ const groups = take(ids);
1055
+ const plan = byMaterial(groups, (g, count) => {
1056
+ const pv = new Array(count * 3);
1057
+ for (let i = 0; i < count; i++) {
1058
+ pv[i * 3] = g.position.x;
1059
+ pv[i * 3 + 1] = g.position.y;
1060
+ pv[i * 3 + 2] = g.position.z;
1061
+ }
1062
+ return { batchPivot: pv };
1063
+ });
1064
+ const merged = new THREE.Group();
1065
+ merged.name = `merge:${sig.split("|")[0]}`;
1066
+ const patch2 = (shader) => {
1067
+ shader.uniforms.batchQ = uniforms.q;
1068
+ shader.uniforms.batchPos = uniforms.pos;
1069
+ shader.uniforms.batchScale = uniforms.scale;
1070
+ let src = `attribute vec3 batchPivot;
1071
+ uniform vec4 batchQ;
1072
+ uniform vec3 batchPos;
1073
+ uniform vec3 batchScale;
1074
+ ${QROT}${shader.vertexShader}`;
1075
+ src = src.replace("#include <begin_vertex>", "#include <begin_vertex>\ntransformed = batchPivot + batchRotate(batchQ, (position - batchPivot) * batchScale) + batchPos;");
1076
+ src = src.replace("#include <beginnormal_vertex>", "#include <beginnormal_vertex>\nobjectNormal = batchRotate(batchQ, objectNormal);");
1077
+ shader.vertexShader = src;
1078
+ };
1079
+ for (const e of plan.values()) {
1080
+ const key = String(e.mesh.userData.material);
1081
+ const set = patchedSet(THREE, e.base, "poseBatch", patch2);
1082
+ finishMesh(merged.name, key, concat(THREE, e.pieces, { batchPivot: 3 }), e.mesh, set, merged);
1083
+ }
1084
+ const batch = {
1085
+ kind: "pose",
1086
+ ids,
1087
+ apply(m, t) {
1088
+ const pose = poseOf2(m, t);
1089
+ uniforms.q.value.set(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3]);
1090
+ uniforms.pos.value.set(pose.position[0], pose.position[1], pose.position[2]);
1091
+ uniforms.scale.value.set(pose.scale[0], pose.scale[1], pose.scale[2]);
1092
+ }
1093
+ };
1094
+ batch.apply(motion, 0);
1095
+ merged.userData.merged = batch;
1096
+ retire(ids, merged);
1097
+ }
1098
+ }
1099
+ function indexOf(map, id) {
1100
+ return map.get(id) ?? 0;
1101
+ }
1102
+
1103
+ // src/mesh.ts
1104
+ function createDefaultMaterials(THREE, quality = "high") {
1105
+ const defined = (o) => {
1106
+ const out = {};
1107
+ for (const k of Object.keys(o)) if (o[k] !== void 0) out[k] = o[k];
1108
+ return out;
1109
+ };
1110
+ const std = (p) => quality === "game" ? new THREE.MeshLambertMaterial(defined({ vertexColors: true, transparent: p.transparent, opacity: p.opacity, depthWrite: p.depthWrite, side: p.side })) : new THREE.MeshStandardMaterial(defined({ vertexColors: true, roughness: 0.85, metalness: 0, ...p }));
1111
+ const solid = quality === "game" ? std({}) : null;
1112
+ return {
1113
+ plain: solid ?? std({}),
1114
+ metal: solid ?? std({ roughness: 0.35, metalness: 0.7 }),
1115
+ wood: solid ?? std({ roughness: 0.8 }),
1116
+ glass: std({ roughness: 0.05, transparent: true, opacity: 0.35, depthWrite: false }),
1117
+ fabric: solid ?? std({ roughness: 1 }),
1118
+ plant: std({ roughness: 0.9, side: THREE.DoubleSide }),
1119
+ water: std({ roughness: 0.1, transparent: true, opacity: 0.7, depthWrite: false }),
1120
+ emissive: new THREE.MeshBasicMaterial({ vertexColors: true }),
1121
+ atlas_picture: std({ roughness: 0.9 }),
1122
+ atlas_floor: std({ roughness: 1 })
1123
+ };
1124
+ }
1125
+ var IDENT_UV = { u0: 0, v0: 0, u1: 1, v1: 1 };
1126
+ function createMeshApi(THREE, options = {}) {
1127
+ const quality = options.quality ?? "high";
1128
+ const shadows = options.shadows !== false;
1129
+ const materials = { ...createDefaultMaterials(THREE, quality), ...stripUndefined(options.materials) };
1130
+ if (options.doubleSidedPlanes) for (const k of MATERIAL_KEYS) materials[k].side = THREE.DoubleSide;
1131
+ const root = { id: null, pivot: [0, 0, 0], ry: 0, acc: /* @__PURE__ */ new Map(), lights: [] };
1132
+ const partTargets = /* @__PURE__ */ new Map();
1133
+ const stack = [];
1134
+ let current = root;
1135
+ let finished = false;
1136
+ const templates = /* @__PURE__ */ new Map();
1137
+ const template = (key, make) => {
1138
+ let t = templates.get(key);
1139
+ if (!t) {
1140
+ const g = make();
1141
+ t = {
1142
+ pos: g.getAttribute("position").array,
1143
+ nor: g.getAttribute("normal").array,
1144
+ uv: g.getAttribute("uv").array,
1145
+ idx: g.index ? g.index.array : Array.from({ length: g.getAttribute("position").count }, (_, i) => i)
1146
+ };
1147
+ templates.set(key, t);
1148
+ }
1149
+ return t;
1150
+ };
1151
+ const segs = quality === "high" ? { cyl: 12, sphereW: 12, sphereH: 8 } : { cyl: 8, sphereW: 8, sphereH: 6 };
1152
+ const tBox = () => template("box", () => new THREE.BoxGeometry(1, 1, 1));
1153
+ const tCyl = (shape) => {
1154
+ switch (shape) {
1155
+ case "cyl6":
1156
+ return template("cyl6", () => new THREE.CylinderGeometry(1, 1, 1, 6));
1157
+ case "cone":
1158
+ return template("cone", () => new THREE.CylinderGeometry(0, 1, 1, segs.cyl));
1159
+ case "sphere":
1160
+ return template("sphere", () => new THREE.SphereGeometry(1, segs.sphereW, segs.sphereH));
1161
+ default:
1162
+ return template("cyl", () => new THREE.CylinderGeometry(1, 1, 1, segs.cyl));
1163
+ }
1164
+ };
1165
+ const tPlane = () => template("plane", () => new THREE.PlaneGeometry(1, 1));
1166
+ const tLeaf = () => {
1167
+ let t = templates.get("leaf");
1168
+ if (!t) {
1169
+ const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0];
1170
+ const pos = [];
1171
+ const nor = [];
1172
+ const uv = [];
1173
+ const idx = [];
1174
+ for (const side of [1, -1]) {
1175
+ const base = pos.length / 3;
1176
+ for (let i = 0; i < 4; i++) {
1177
+ pos.push(f[i * 3], f[i * 3 + 1], f[i * 3 + 2]);
1178
+ nor.push(0, 0, side);
1179
+ uv.push(f[i * 3] + 0.5, f[i * 3 + 1]);
1180
+ }
1181
+ if (side === 1) idx.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
1182
+ else idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
1183
+ }
1184
+ t = { pos: new Float32Array(pos), nor: new Float32Array(nor), uv: new Float32Array(uv), idx };
1185
+ templates.set("leaf", t);
1186
+ }
1187
+ return t;
1188
+ };
1189
+ const m4 = new THREE.Matrix4();
1190
+ const mItem = new THREE.Matrix4();
1191
+ const mTotal = new THREE.Matrix4();
1192
+ const n3 = new THREE.Matrix3();
1193
+ const v = new THREE.Vector3();
1194
+ const quat = new THREE.Quaternion();
1195
+ const euler = new THREE.Euler();
1196
+ const scl = new THREE.Vector3();
1197
+ const color = new THREE.Color();
1198
+ const canonical = /* @__PURE__ */ new Map();
1199
+ for (const k of MATERIAL_KEYS) {
1200
+ const first = MATERIAL_KEYS.find((o) => materials[o] === materials[k]) ?? k;
1201
+ canonical.set(k, first);
1202
+ }
1203
+ const accOf = (requested) => {
1204
+ const key = canonical.get(requested) ?? requested;
1205
+ let a = current.acc.get(key);
1206
+ if (!a) {
1207
+ a = { pos: [], nor: [], col: [], uv: [], idx: [] };
1208
+ current.acc.set(key, a);
1209
+ }
1210
+ return a;
1211
+ };
1212
+ const frame = (M) => {
1213
+ mItem.makeRotationY(M?.ry ?? 0);
1214
+ mItem.setPosition(M?.x ?? 0, 0, M?.z ?? 0);
1215
+ return mItem;
1216
+ };
1217
+ const emit = (requested, M, local, t, hexColor, uv) => {
1218
+ const key = requested in materials ? requested : "plain";
1219
+ const a = accOf(key);
1220
+ const total = mTotal.copy(frame(M)).multiply(local);
1221
+ n3.getNormalMatrix(total);
1222
+ const base = a.pos.length / 3;
1223
+ color.setHex(hexColor);
1224
+ const count = t.pos.length / 3;
1225
+ const e = total.elements;
1226
+ const nm = n3.elements;
1227
+ const u = uv ?? IDENT_UV;
1228
+ for (let i = 0; i < count; i++) {
1229
+ const x = t.pos[i * 3];
1230
+ const y = t.pos[i * 3 + 1];
1231
+ const z = t.pos[i * 3 + 2];
1232
+ a.pos.push(e[0] * x + e[4] * y + e[8] * z + e[12], e[1] * x + e[5] * y + e[9] * z + e[13], e[2] * x + e[6] * y + e[10] * z + e[14]);
1233
+ const nx = t.nor[i * 3];
1234
+ const ny = t.nor[i * 3 + 1];
1235
+ const nz = t.nor[i * 3 + 2];
1236
+ v.set(nm[0] * nx + nm[3] * ny + nm[6] * nz, nm[1] * nx + nm[4] * ny + nm[7] * nz, nm[2] * nx + nm[5] * ny + nm[8] * nz).normalize();
1237
+ a.nor.push(v.x, v.y, v.z);
1238
+ a.col.push(color.r, color.g, color.b);
1239
+ a.uv.push(u.u0 + t.uv[i * 2] * (u.u1 - u.u0), u.v0 + t.uv[i * 2 + 1] * (u.v1 - u.v0));
1240
+ }
1241
+ for (let i = 0; i < t.idx.length; i++) a.idx.push(base + t.idx[i]);
1242
+ };
1243
+ const compose = (x, y, z, rx, ry, rz, sx, sy, sz) => {
1244
+ euler.set(rx, ry, rz, "YXZ");
1245
+ quat.setFromEuler(euler);
1246
+ return m4.compose(v.set(x, y, z), quat, scl.set(sx, sy, sz));
1247
+ };
1248
+ const api = {
1249
+ box(M, material, x, y, z, w, h, d, col, ry, rx, rz) {
1250
+ emit(material, M, compose(x, y + h / 2, z, rx, ry, rz, w, h, d), tBox(), col);
1251
+ },
1252
+ cylinder(M, material, x, y, z, r, h, col, rx, rz, shape) {
1253
+ const tilted = rx !== 0 || rz !== 0;
1254
+ const cy = shape === "sphere" ? y + h / 2 : tilted ? y : y + h / 2;
1255
+ const sy = shape === "sphere" ? h / 2 : h;
1256
+ emit(material, M, compose(x, cy, z, rx, 0, rz, r, sy, r), tCyl(shape), col);
1257
+ },
1258
+ leaf(M, x, y, z, w, h, col, ry, rx) {
1259
+ emit("plant", M, compose(x, y, z, rx, ry, 0, w, h, 1), tLeaf(), col);
1260
+ },
1261
+ plane(M, material, x, y, z, w, h, col, ry, rx, rz, uv) {
1262
+ emit(material, M, compose(x, y, z, rx, ry, rz, w, h, 1), tPlane(), col, uv);
1263
+ },
1264
+ light(M, x, y, z, col, range) {
1265
+ const fr = frame(M);
1266
+ v.set(x, y, z).applyMatrix4(fr);
1267
+ current.lights.push({ x: v.x, y: v.y, z: v.z, color: col, range });
1268
+ },
1269
+ part(id, pivot, ry, draw) {
1270
+ let target = partTargets.get(id);
1271
+ if (!target) {
1272
+ target = { id, pivot: [pivot[0], pivot[1], pivot[2]], ry, acc: /* @__PURE__ */ new Map(), lights: [] };
1273
+ partTargets.set(id, target);
1274
+ }
1275
+ stack.push(current);
1276
+ current = target;
1277
+ try {
1278
+ draw();
1279
+ } finally {
1280
+ current = stack.pop() ?? root;
1281
+ }
1282
+ }
1283
+ };
1284
+ const build = (target, into, materialFor) => {
1285
+ for (const [key, a] of target.acc) {
1286
+ if (!a.idx.length) continue;
1287
+ const g = new THREE.BufferGeometry();
1288
+ g.setAttribute("position", new THREE.Float32BufferAttribute(a.pos, 3));
1289
+ g.setAttribute("normal", new THREE.Float32BufferAttribute(a.nor, 3));
1290
+ g.setAttribute("color", new THREE.Float32BufferAttribute(a.col, 3));
1291
+ g.setAttribute("uv", new THREE.Float32BufferAttribute(a.uv, 2));
1292
+ g.setIndex(a.pos.length / 3 > 65535 ? new THREE.Uint32BufferAttribute(a.idx, 1) : new THREE.Uint16BufferAttribute(a.idx, 1));
1293
+ g.computeBoundingSphere();
1294
+ g.computeBoundingBox();
1295
+ const mesh = new THREE.Mesh(g, materialFor?.(key, target.id) ?? materials[key]);
1296
+ mesh.name = target.id === null ? `static:${key}` : `part:${target.id}`;
1297
+ if (target.id !== null) mesh.userData.part = target.id;
1298
+ mesh.userData.material = key;
1299
+ mesh.castShadow = shadows;
1300
+ mesh.receiveShadow = shadows;
1301
+ into.add(mesh);
1302
+ }
1303
+ for (const l of target.lights) {
1304
+ const light = new THREE.PointLight(l.color, 1, l.range);
1305
+ light.position.set(l.x, l.y, l.z);
1306
+ into.add(light);
1307
+ lightsOut.push(light);
1308
+ }
1309
+ };
1310
+ const lightsOut = [];
1311
+ return {
1312
+ api,
1313
+ materials,
1314
+ finish(spec, finishOptions) {
1315
+ const materialFor = finishOptions?.materialFor ?? options.materialFor;
1316
+ if (finished) throw new Error("createMeshApi: finish() was already called");
1317
+ finished = true;
1318
+ const group = new THREE.Group();
1319
+ group.name = "interactable";
1320
+ const parts = {};
1321
+ build(root, group, materialFor);
1322
+ for (const [id, target] of partTargets) {
1323
+ const g = new THREE.Group();
1324
+ g.name = `part:${id}`;
1325
+ g.userData.part = id;
1326
+ g.position.set(target.pivot[0], target.pivot[1], target.pivot[2]);
1327
+ g.rotation.y = target.ry;
1328
+ g.updateMatrix();
1329
+ build(target, g, materialFor);
1330
+ rememberRest(g);
1331
+ group.add(g);
1332
+ parts[id] = g;
1333
+ }
1334
+ if (spec && (options.mergeParts ?? quality === "game")) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude });
1335
+ if (spec) tagDestruction(group, parts, spec);
1336
+ return { group, parts, lights: lightsOut };
1337
+ }
1338
+ };
1339
+ }
1340
+ function tagDestruction(group, parts, spec) {
1341
+ const body = spec.destruction;
1342
+ const partGroups = new Set(Object.values(parts));
1343
+ if (body) {
1344
+ for (const child of group.children) {
1345
+ if (!partGroups.has(child) && child.isMesh) child.userData.destructionMaterial = body.material;
1346
+ }
1347
+ }
1348
+ const done = /* @__PURE__ */ new Set();
1349
+ for (const ps of spec.parts ?? []) {
1350
+ const g = parts[ps.id];
1351
+ const d = ps.destruction;
1352
+ if (!g || !d || done.has(g)) continue;
1353
+ done.add(g);
1354
+ for (const o of [g, ...g.children]) {
1355
+ if (o !== g && !o.isMesh) continue;
1356
+ o.userData.destructionMaterial = d.material;
1357
+ o.userData.destructionPart = ps.id;
1358
+ if (d.piece) o.userData.destructionPiece = d.piece;
1359
+ if (d.joint) o.userData.destructionJoint = d.joint;
1360
+ }
1361
+ }
1362
+ }
1363
+ function stripUndefined(o) {
1364
+ const out = {};
1365
+ if (o) {
1366
+ for (const k of Object.keys(o)) if (o[k] !== void 0) out[k] = o[k];
1367
+ }
1368
+ return out;
1369
+ }
1370
+
1371
+ // src/mirrors.ts
1372
+ function createMirrors(THREE, options = {}) {
1373
+ const max = Math.max(0, Math.floor(options.max ?? 1));
1374
+ const resolution = options.resolution ?? 512;
1375
+ const offset = options.offset ?? 1e-3;
1376
+ const envMaterial = new THREE.MeshStandardMaterial({ color: 14673642, metalness: 1, roughness: 0.04, envMap: options.envMap ?? null });
1377
+ const entries = /* @__PURE__ */ new Map();
1378
+ const slots = [];
1379
+ const geometry = new THREE.PlaneGeometry(1, 1);
1380
+ const worldNormal = (e, out) => {
1381
+ if (e.normal) return out.set(e.normal[0], e.normal[1], e.normal[2]).normalize();
1382
+ e.mesh.updateWorldMatrix(true, false);
1383
+ return out.set(0, 0, 1).transformDirection(e.mesh.matrixWorld);
1384
+ };
1385
+ const slotFor = (id) => {
1386
+ if (!options.Reflector) return null;
1387
+ const existing = slots.find((s2) => s2.id === id);
1388
+ if (existing) return existing;
1389
+ const free = slots.find((s2) => s2.id === null);
1390
+ if (free) return free;
1391
+ if (slots.length >= max) return null;
1392
+ const reflector = new options.Reflector(geometry, { textureWidth: resolution, textureHeight: resolution, clipBias: 3e-3 });
1393
+ reflector.name = "mirror:reflector";
1394
+ if (options.keepInfo !== false) {
1395
+ const original = reflector.onBeforeRender;
1396
+ reflector.onBeforeRender = function(renderer, scene, camera, geo, mat, grp) {
1397
+ const info = renderer.info;
1398
+ if (!info.autoReset) return original.call(this, renderer, scene, camera, geo, mat, grp);
1399
+ const before = { calls: info.render.calls, triangles: info.render.triangles, points: info.render.points, lines: info.render.lines };
1400
+ original.call(this, renderer, scene, camera, geo, mat, grp);
1401
+ info.render.calls += before.calls;
1402
+ info.render.triangles += before.triangles;
1403
+ info.render.points += before.points;
1404
+ info.render.lines += before.lines;
1405
+ };
1406
+ }
1407
+ reflector.visible = false;
1408
+ const s = { reflector, id: null };
1409
+ slots.push(s);
1410
+ return s;
1411
+ };
1412
+ const release = (s) => {
1413
+ s.id = null;
1414
+ s.reflector.visible = false;
1415
+ };
1416
+ const pos = new THREE.Vector3();
1417
+ const quat = new THREE.Quaternion();
1418
+ const scl = new THREE.Vector3();
1419
+ const n = new THREE.Vector3();
1420
+ const toEye = new THREE.Vector3();
1421
+ const box = new THREE.Box3();
1422
+ const size = new THREE.Vector3();
1423
+ const facing = new THREE.Vector3();
1424
+ const flip = new THREE.Quaternion(0, 1, 0, 0);
1425
+ return {
1426
+ envMaterial,
1427
+ register(id, mesh, normal) {
1428
+ const previous = entries.get(id);
1429
+ entries.set(id, { mesh, normal: normal ?? null, original: previous ? previous.original : mesh.material });
1430
+ mesh.material = envMaterial;
1431
+ },
1432
+ unregister(id) {
1433
+ const e = entries.get(id);
1434
+ const s = slots.find((x) => x.id === id);
1435
+ if (s) release(s);
1436
+ if (e) e.mesh.material = e.original;
1437
+ entries.delete(id);
1438
+ },
1439
+ update(camera, candidates) {
1440
+ const eye = camera.getWorldPosition(toEye);
1441
+ const wanted = [];
1442
+ for (const c of candidates) {
1443
+ const e = entries.get(c.id);
1444
+ if (!e) continue;
1445
+ n.set(c.normal[0], c.normal[1], c.normal[2]);
1446
+ const dx = eye.x - c.center[0];
1447
+ const dy = eye.y - c.center[1];
1448
+ const dz = eye.z - c.center[2];
1449
+ if (n.x * dx + n.y * dy + n.z * dz <= 0) continue;
1450
+ wanted.push({ id: c.id, d: dx * dx + dy * dy + dz * dz });
1451
+ }
1452
+ wanted.sort((a, b) => a.d - b.d);
1453
+ const chosen = /* @__PURE__ */ new Set();
1454
+ for (let i = 0; i < wanted.length && chosen.size < max; i++) chosen.add(wanted[i].id);
1455
+ for (const s of slots) if (s.id !== null && !chosen.has(s.id)) release(s);
1456
+ for (const id of chosen) {
1457
+ const s = slotFor(id);
1458
+ if (!s) continue;
1459
+ const e = entries.get(id);
1460
+ e.mesh.updateWorldMatrix(true, false);
1461
+ e.mesh.matrixWorld.decompose(pos, quat, scl);
1462
+ if (!e.mesh.geometry.boundingBox) e.mesh.geometry.computeBoundingBox();
1463
+ box.copy(e.mesh.geometry.boundingBox);
1464
+ box.getSize(size);
1465
+ worldNormal(e, n);
1466
+ s.reflector.position.copy(pos).addScaledVector(n, offset);
1467
+ s.reflector.quaternion.copy(quat);
1468
+ if (facing.set(0, 0, 1).applyQuaternion(quat).dot(n) < 0) s.reflector.quaternion.multiply(flip);
1469
+ s.reflector.scale.set(Math.max(size.x * scl.x, 1e-3), Math.max(size.y * scl.y, 1e-3), 1);
1470
+ s.id = id;
1471
+ s.reflector.visible = true;
1472
+ if (!s.reflector.parent) {
1473
+ let top = e.mesh;
1474
+ while (top.parent) top = top.parent;
1475
+ (options.scene ?? top).add(s.reflector);
1476
+ }
1477
+ }
1478
+ return slots.filter((s) => s.id !== null).map((s) => s.id);
1479
+ },
1480
+ active() {
1481
+ return slots.filter((s) => s.id !== null).map((s) => s.id);
1482
+ },
1483
+ dispose() {
1484
+ for (const s of slots) s.reflector.removeFromParent();
1485
+ slots.length = 0;
1486
+ for (const e of entries.values()) e.mesh.material = e.original;
1487
+ entries.clear();
1488
+ envMaterial.dispose();
1489
+ geometry.dispose();
1490
+ }
1491
+ };
1492
+ }
1493
+
1494
+ // src/screen.ts
1495
+ var BARS = ["#c0c0c0", "#c0c000", "#00c0c0", "#00c000", "#c000c0", "#c00000", "#0000c0"];
1496
+ function mulberry2(seed) {
1497
+ let a = seed >>> 0;
1498
+ return () => {
1499
+ a = a + 1831565813 >>> 0;
1500
+ let t = a;
1501
+ t = Math.imul(t ^ t >>> 15, t | 1);
1502
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1503
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1504
+ };
1505
+ }
1506
+ function createScreen(THREE, options) {
1507
+ const w = options.w ?? 128;
1508
+ const h = options.h ?? 72;
1509
+ const canvas = options.document.createElement("canvas");
1510
+ canvas.width = w;
1511
+ canvas.height = h;
1512
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
1513
+ const rand = mulberry2(options.seed ?? 1);
1514
+ const period = 1 / Math.max(1, options.fps ?? 15);
1515
+ const intensity = options.intensity ?? 1;
1516
+ const texture = new THREE.CanvasTexture(canvas);
1517
+ texture.colorSpace = THREE.SRGBColorSpace;
1518
+ const material = new THREE.MeshStandardMaterial({
1519
+ color: 328965,
1520
+ roughness: 0.25,
1521
+ metalness: 0,
1522
+ map: texture,
1523
+ emissive: 16777215,
1524
+ emissiveMap: texture,
1525
+ emissiveIntensity: 0
1526
+ });
1527
+ let on = false;
1528
+ let channel = 0;
1529
+ let clock = 0;
1530
+ let acc = 0;
1531
+ const fill = (style, x, y, rw, rh) => {
1532
+ if (!ctx) return;
1533
+ ctx.fillStyle = style;
1534
+ ctx.fillRect(x, y, rw, rh);
1535
+ };
1536
+ const drawBars = () => {
1537
+ const n = BARS.length;
1538
+ for (let i = 0; i < n; i++) fill(BARS[(i + channel) % n], Math.floor(i * w / n), 0, Math.ceil(w / n), Math.floor(h * 0.75));
1539
+ fill("#101010", 0, Math.floor(h * 0.75), w, h - Math.floor(h * 0.75));
1540
+ const x = clock * 20 % (w + 12) - 12;
1541
+ fill("#ffffff", x, Math.floor(h * 0.8), 12, Math.floor(h * 0.1));
1542
+ };
1543
+ const drawNoise = () => {
1544
+ const cell = Math.max(2, Math.floor(w / 48));
1545
+ for (let y = 0; y < h; y += cell) {
1546
+ for (let x = 0; x < w; x += cell) {
1547
+ const g = Math.floor(rand() * 220 + 20);
1548
+ fill(`rgb(${g},${g},${g})`, x, y, cell, cell);
1549
+ }
1550
+ }
1551
+ };
1552
+ const drawScene = () => {
1553
+ const hue = channel * 40 % 360;
1554
+ fill(`hsl(${(200 + hue) % 360},60%,${55 + 10 * Math.sin(clock * 0.3)}%)`, 0, 0, w, Math.floor(h * 0.65));
1555
+ fill(`hsl(${(110 + hue) % 360},40%,32%)`, 0, Math.floor(h * 0.65), w, h - Math.floor(h * 0.65));
1556
+ const sx = (0.5 + 0.4 * Math.sin(clock * 0.5)) * w;
1557
+ const sy = (0.3 - 0.15 * Math.cos(clock * 0.5)) * h;
1558
+ fill("#ffe680", sx - 5, sy - 5, 10, 10);
1559
+ const bx = clock * 25 % (w + 16) - 16;
1560
+ fill("#e03030", bx, Math.floor(h * 0.7), 16, 10);
1561
+ };
1562
+ const draw = () => {
1563
+ if (!on) {
1564
+ fill("#000000", 0, 0, w, h);
1565
+ } else if (channel % 3 === 0) {
1566
+ drawBars();
1567
+ } else if (channel % 3 === 1) {
1568
+ drawNoise();
1569
+ } else {
1570
+ drawScene();
1571
+ }
1572
+ texture.needsUpdate = true;
1573
+ };
1574
+ const apply = () => {
1575
+ material.emissiveIntensity = on ? intensity : 0;
1576
+ material.color.setHex(on ? 1118481 : 0);
1577
+ draw();
1578
+ };
1579
+ apply();
1580
+ return {
1581
+ material,
1582
+ texture,
1583
+ canvas,
1584
+ get on() {
1585
+ return on;
1586
+ },
1587
+ get channel() {
1588
+ return channel;
1589
+ },
1590
+ setOn(value) {
1591
+ on = !!value;
1592
+ acc = 0;
1593
+ apply();
1594
+ },
1595
+ setChannel(n) {
1596
+ channel = Math.max(0, Math.floor(n));
1597
+ if (on) draw();
1598
+ },
1599
+ update(dt) {
1600
+ if (!on) return;
1601
+ clock += dt;
1602
+ acc += dt;
1603
+ if (acc >= period) {
1604
+ acc %= period;
1605
+ draw();
1606
+ }
1607
+ },
1608
+ dispose() {
1609
+ texture.dispose();
1610
+ material.dispose();
1611
+ }
1612
+ };
1613
+ }
1614
+
1615
+ // src/sway.ts
1616
+ var VERTEX_PATCH = (
1617
+ /* glsl */
1618
+ `
1619
+ #include <begin_vertex>
1620
+ {
1621
+ float swayHang = max(swayAnchorY - position.y, 0.0);
1622
+ float swayFlutter = swayAmplitude * sin(swayTime * 1.9 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);
1623
+ transformed.x += swayHang * (sin(swayAz) + swayFlutter);
1624
+ transformed.z += swayHang * (-sin(swayAx) + swayFlutter * 0.6);
1625
+ transformed.y += swayHang * (1.0 - cos(swayAz) * cos(swayAx));
1626
+ }
1627
+ `
1628
+ );
1629
+ var VERTEX_UNIFORMS = (
1630
+ /* glsl */
1631
+ `
1632
+ uniform float swayTime;
1633
+ uniform float swayAx;
1634
+ uniform float swayAz;
1635
+ uniform float swayAnchorY;
1636
+ uniform float swayAmplitude;
1637
+ `
1638
+ );
1639
+ function patchSwayShader(shader, uniforms, amplitude) {
1640
+ shader.uniforms.swayTime = uniforms.time;
1641
+ shader.uniforms.swayAx = uniforms.ax;
1642
+ shader.uniforms.swayAz = uniforms.az;
1643
+ shader.uniforms.swayAnchorY = uniforms.anchorY;
1644
+ shader.uniforms.swayAmplitude = { value: amplitude };
1645
+ shader.vertexShader = VERTEX_UNIFORMS + shader.vertexShader.replace("#include <begin_vertex>", VERTEX_PATCH);
1646
+ }
1647
+ function patch(material, uniforms, amplitude) {
1648
+ const previous = material.onBeforeCompile;
1649
+ material.onBeforeCompile = (shader, renderer) => {
1650
+ previous?.call(material, shader, renderer);
1651
+ patchSwayShader(shader, uniforms, amplitude);
1652
+ };
1653
+ material.customProgramCacheKey = () => `sway:${amplitude}`;
1654
+ return material;
1655
+ }
1656
+ function swayMaterial(THREE, base, options = {}) {
1657
+ const amplitude = options.amplitude ?? 0.04;
1658
+ const uniforms = { time: { value: 0 }, ax: { value: 0 }, az: { value: 0 }, anchorY: { value: options.anchorY ?? 0 } };
1659
+ const handle = {
1660
+ uniforms,
1661
+ set(ax, az, time) {
1662
+ uniforms.ax.value = ax;
1663
+ uniforms.az.value = az;
1664
+ if (time !== void 0) uniforms.time.value = time;
1665
+ }
1666
+ };
1667
+ const material = patch(base.clone(), uniforms, amplitude);
1668
+ const depth = patch(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), uniforms, amplitude);
1669
+ const distance = patch(new THREE.MeshDistanceMaterial(), uniforms, amplitude);
1670
+ material.userData.sway = handle;
1671
+ material.userData.depthMaterial = depth;
1672
+ material.userData.distanceMaterial = distance;
1673
+ return material;
1674
+ }
1675
+ function attachSway(THREE, mesh, options = {}) {
1676
+ let anchorY = options.anchorY;
1677
+ if (anchorY === void 0) {
1678
+ if (!mesh.geometry.boundingBox) mesh.geometry.computeBoundingBox();
1679
+ anchorY = mesh.geometry.boundingBox ? mesh.geometry.boundingBox.max.y : 0;
1680
+ }
1681
+ const base = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
1682
+ const m = swayMaterial(THREE, base, { ...options, anchorY });
1683
+ m.userData.sway.uniforms.anchorY.value = anchorY;
1684
+ mesh.material = m;
1685
+ mesh.customDepthMaterial = m.userData.depthMaterial;
1686
+ mesh.customDistanceMaterial = m.userData.distanceMaterial;
1687
+ mesh.userData.sway = m.userData.sway;
1688
+ return m.userData.sway;
1689
+ }
1690
+
1691
+ // src/adapter.ts
1692
+ function createThreeAdapter(THREE, options = {}) {
1693
+ return {
1694
+ THREE,
1695
+ createMeshApi: (o = {}) => createMeshApi(THREE, { quality: options.quality, materials: options.materials, ...o }),
1696
+ applyPose: (group, motion, t) => applyPose(THREE, group, motion, t),
1697
+ syncObject: (world, objectId, parts, o) => syncObject(THREE, world, objectId, parts, o),
1698
+ swayMaterial: (base, o) => swayMaterial(THREE, base, o),
1699
+ attachSway: (mesh, o) => attachSway(THREE, mesh, o),
1700
+ createEffects: ((a, o = {}) => a.isObject3D ? createEffects(THREE, a, { document: options.document, ...o }) : createEffects(THREE, { document: options.document, ...a })),
1701
+ createScreen: (o = {}) => {
1702
+ const document = o.document ?? options.document;
1703
+ if (!document) throw new Error("createScreen needs a document (option of createThreeAdapter or of the call)");
1704
+ return createScreen(THREE, { ...o, document });
1705
+ },
1706
+ createMirrors: (o = {}) => createMirrors(THREE, { Reflector: options.Reflector, envMap: options.envMap, ...o }),
1707
+ createCord: (points, radius, material) => createCord(THREE, points, radius, material),
1708
+ catenary
1709
+ };
1710
+ }
1711
+ return __toCommonJS(index_exports);
1712
+ })();
1713
+ //# sourceMappingURL=index.global.js.map