@adatechnology/game-interactables-three 0.1.15 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,22 @@
1
1
  import { ActiveEmitter, ObjectSpec, Motion, World } from '@adatechnology/game-interactables';
2
+ import { LightBudget, SourceHandle } from '@adatechnology/game-render-three';
2
3
  import * as ThreeNS from 'three';
3
4
  import { PropsApi } from '@adatechnology/game-props3d';
5
+ import { catenary } from '@adatechnology/game-shapes-three';
6
+ export { catenary, createCord } from '@adatechnology/game-shapes-three';
7
+
8
+ /** The kinds of particle pool of the effects, as `effects.ts` draws them. */
9
+ type PoolKind = 'smoke' | 'steam' | 'fire' | 'spark' | 'water';
10
+ /** What `EffectSink.play` of `game-contracts` takes, as a structural type. */
11
+ interface ParticleEffectRequest {
12
+ readonly effect: string;
13
+ readonly point: [number, number, number];
14
+ readonly values: Readonly<Record<string, number>>;
15
+ }
16
+ /** The host's effects world as far as the particles need it (`EffectSink` of `game-contracts` fits). */
17
+ interface ParticleEffects {
18
+ play(request: ParticleEffectRequest): unknown;
19
+ }
4
20
 
5
21
  /** The host's `THREE` namespace (or any object with the same classes). Always passed in, never imported at runtime. */
6
22
  type ThreeLib = typeof ThreeNS;
@@ -21,7 +37,7 @@ interface HouseholdApi extends PropsApi {
21
37
  }
22
38
  /** Anything that can create a canvas (the global `document`, or a fake in tests). */
23
39
  interface CanvasFactory {
24
- createElement(tag: 'canvas'): any;
40
+ createElement(tag: 'canvas'): HTMLCanvasElement;
25
41
  }
26
42
 
27
43
  interface EffectsOptions {
@@ -48,6 +64,11 @@ interface EffectsOptions {
48
64
  maxStreams?: number;
49
65
  /** Most gas flame rings drawn at once (all rings share one draw call). Default 16. */
50
66
  maxFlames?: number;
67
+ /**
68
+ * The host's effects world, read at each particle. With one, smoke, steam, fire and sparks go through it and its one budget (`maxParticles` then only caps water);
69
+ * without one every kind is drawn here with its own cap (deprecated, removed in the next major).
70
+ */
71
+ effects?: () => ParticleEffects | undefined;
51
72
  }
52
73
  /** What the effects read from a camera. */
53
74
  interface PerspectiveLike {
@@ -60,7 +81,7 @@ interface EffectsStats {
60
81
  capacity: number;
61
82
  byKind: Record<PoolKind, number>;
62
83
  }
63
- type PoolKind = 'smoke' | 'steam' | 'fire' | 'water' | 'spark';
84
+
64
85
  interface Effects {
65
86
  /**
66
87
  * Spawns for the active emitters and advances every particle by `dt` seconds, sizing the sprites for the camera given to
@@ -91,15 +112,6 @@ declare function createEffects(THREE: ThreeLib, options: EffectsOptions): Effect
91
112
  /** @deprecated Pass `{ scene, camera, ... }` as the one options argument. */
92
113
  declare function createEffects(THREE: ThreeLib, scene: ThreeNS.Object3D, options?: Partial<EffectsOptions>): Effects;
93
114
 
94
- /**
95
- * Points of a hanging cord between `a` and `b` (n points including both ends, n >= 2). The deepest point sits `sag` meters below
96
- * the straight line between the ends, on a real catenary (y = a cosh(x/a)); when the ends differ in height the curve is
97
- * measured against the straight line between them.
98
- */
99
- declare function catenary(a: Vec3, b: Vec3, sag: number, n?: number): Vec3[];
100
- /** A thin tube through `points` (Catmull-Rom smoothed) for wires, cords and conduits. */
101
- declare function createCord(THREE: ThreeLib, points: readonly Vec3[], radius: number, material: ThreeNS.Material, radialSegments?: number): ThreeNS.Mesh;
102
-
103
115
  type MaterialMap = Partial<Record<MaterialKey, ThreeNS.Material>>;
104
116
  interface MeshApiOptions {
105
117
  /** Materials per key. Missing keys get a default (vertex-colored) material. */
@@ -123,14 +135,21 @@ interface MeshApiOptions {
123
135
  * clothes) keep what you return, including its `onBeforeCompile`, `customProgramCacheKey`, `defines` and `userData`.
124
136
  */
125
137
  materialFor?: (key: MaterialKey, partId: string | null) => ThreeNS.Material | undefined;
138
+ /**
139
+ * The host's light budget: each `api.light` becomes a source of it (it competes for a slot by distance, and following its bulb) instead of a `PointLight` of its own, so lamps coming and going
140
+ * never change the lit materials' program key. Without it the lamps are real lights (deprecated, removed in the next major).
141
+ */
142
+ lights?: LightBudget;
126
143
  }
127
144
  interface MeshResult {
128
145
  /** Root group: static meshes (one per material) plus one child group per part (merged parts share one group, see `mergeParts`). */
129
146
  group: ThreeNS.Group;
130
147
  /** Part groups by id. Origin at the pivot, rotated by the part's `ry`; `userData.rest` holds the rest pose. A merged part maps to the shared group (`userData.merged`). */
131
148
  parts: Record<string, ThreeNS.Group>;
132
- /** Real lights requested by the builders (already children of their group). */
149
+ /** Real lights requested by the builders (already children of their group). Empty with `MeshApiOptions.lights`. */
133
150
  lights: ThreeNS.Light[];
151
+ /** With `MeshApiOptions.lights`: the lamps as sources of the budget (`remove()` one when its lamp breaks or the object goes). Empty without it. */
152
+ sources: SourceHandle[];
134
153
  }
135
154
  interface FinishOptions {
136
155
  /** Same as `MeshApiOptions.materialFor`; wins over it for this call. */
@@ -225,12 +244,7 @@ interface Rest {
225
244
  scale: Vec3;
226
245
  }
227
246
  /** Remembers the rest transform of a part group in `userData.rest` (once). */
228
- declare function rememberRest(group: {
229
- position: any;
230
- quaternion: any;
231
- scale: any;
232
- userData: Record<string, any>;
233
- }): Rest;
247
+ declare function rememberRest(group: Pick<ThreeNS.Object3D, 'position' | 'quaternion' | 'scale' | 'userData'>): Rest;
234
248
  /**
235
249
  * Poses `group` for a motion at `t`. The rest pose (position at the pivot, rotation `ry`) is saved in `group.userData.rest` the first
236
250
  * time, so calling this every frame never drifts. The motion axis lives in the part frame (after `ry`).
@@ -264,7 +278,7 @@ interface ScreenOptions {
264
278
  interface Screen {
265
279
  material: ThreeNS.MeshStandardMaterial;
266
280
  texture: ThreeNS.CanvasTexture;
267
- canvas: any;
281
+ canvas: HTMLCanvasElement;
268
282
  readonly on: boolean;
269
283
  readonly channel: number;
270
284
  setOn(on: boolean): void;
@@ -307,7 +321,7 @@ interface SwayHandle {
307
321
  }
308
322
  /** Patches a vertex shader so the vertices hang from the anchor line and swing with the shared uniforms. */
309
323
  declare function patchSwayShader(shader: {
310
- uniforms: Record<string, any>;
324
+ uniforms: Record<string, ThreeNS.IUniform>;
311
325
  vertexShader: string;
312
326
  }, uniforms: SwayUniforms, amplitude: number): void;
313
327
  /**
@@ -316,13 +330,15 @@ declare function patchSwayShader(shader: {
316
330
  * directional / spot shadow maps and point-light shadow cubes follow the animation. Assign it to a mesh, and set
317
331
  * `mesh.customDepthMaterial` / `mesh.customDistanceMaterial` from the material (or just use `attachSway`).
318
332
  */
319
- declare function swayMaterial(THREE: ThreeLib, base: ThreeNS.Material, options?: SwayOptions): ThreeNS.Material & {
333
+ /** A material `swayMaterial` made: it carries its sway handle and the shadow materials that follow it. */
334
+ type SwayMaterial = ThreeNS.Material & {
320
335
  userData: {
321
336
  sway: SwayHandle;
322
337
  depthMaterial: ThreeNS.MeshDepthMaterial;
323
338
  distanceMaterial: ThreeNS.MeshDistanceMaterial;
324
339
  };
325
340
  };
341
+ declare function swayMaterial(THREE: ThreeLib, base: ThreeNS.Material, options?: SwayOptions): SwayMaterial;
326
342
  /**
327
343
  * Makes a mesh sway: replaces its material with `swayMaterial(...)` and sets `customDepthMaterial` and `customDistanceMaterial`.
328
344
  * `mesh.userData.sway` is the handle `syncObject` drives. Returns the handle.
@@ -337,6 +353,8 @@ interface ThreeAdapterOptions {
337
353
  /** `Reflector` and env map for `createMirrors`. */
338
354
  Reflector?: MirrorOptions['Reflector'];
339
355
  envMap?: MirrorOptions['envMap'];
356
+ /** The host's light budget: the lamps of every mesh api this adapter makes become sources of it (see `MeshApiOptions.lights`). */
357
+ lights?: LightBudget;
340
358
  }
341
359
  /** The package functions with `THREE` and the common options already bound. */
342
360
  interface ThreeAdapter {
@@ -395,4 +413,4 @@ type MergedGroup = ThreeNS.Group & {
395
413
  */
396
414
  declare function mergeParts(THREE: ThreeLib, root: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec, options?: MergeOptions): void;
397
415
 
398
- export { type CanvasFactory, type Effects, type EffectsOptions, type EffectsStats, type FinishOptions, type HouseholdApi, MATERIAL_KEYS, type MaterialKey, type MaterialMap, type MergeOptions, type MergedGroup, type MeshApi, type MeshApiOptions, type MeshResult, type MirrorCandidate, type MirrorOptions, type Mirrors, type PerspectiveLike, type PoolKind, type Pose, type PoseBatch, type Quality, type ReflectorConstructor, type Screen, type ScreenOptions, type SwayBatch, type SwayHandle, type SwayOptions, type SwayUniforms, type SyncOptions, type ThreeAdapter, type ThreeAdapterOptions, type ThreeLib, type Vec3, applyPose, attachSway, catenary, createCord, createDefaultMaterials, createEffects, createMeshApi, createMirrors, createScreen, createThreeAdapter, mergeParts, patchSwayShader, poseOf, rememberRest, swayMaterial, syncObject };
416
+ export { type CanvasFactory, type Effects, type EffectsOptions, type EffectsStats, type FinishOptions, type HouseholdApi, MATERIAL_KEYS, type MaterialKey, type MaterialMap, type MergeOptions, type MergedGroup, type MeshApi, type MeshApiOptions, type MeshResult, type MirrorCandidate, type MirrorOptions, type Mirrors, type PerspectiveLike, type PoolKind, type Pose, type PoseBatch, type Quality, type ReflectorConstructor, type Screen, type ScreenOptions, type SwayBatch, type SwayHandle, type SwayOptions, type SwayUniforms, type SyncOptions, type ThreeAdapter, type ThreeAdapterOptions, type ThreeLib, type Vec3, applyPose, attachSway, createDefaultMaterials, createEffects, createMeshApi, createMirrors, createScreen, createThreeAdapter, mergeParts, patchSwayShader, poseOf, rememberRest, swayMaterial, syncObject };
@@ -40,6 +40,117 @@ var GameInteractablesThree = (() => {
40
40
  syncObject: () => syncObject
41
41
  });
42
42
 
43
+ // ../game-contracts/dist/index.js
44
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
45
+ var DEFERRED_QUEUE_ID = {
46
+ DESTRUCTION_EVENTS: "destruction.events",
47
+ BREACH_PAINT: "floorplan.breach-paint",
48
+ AUDIO_VOICES: "audio.voices",
49
+ VEHICLE_CREASE: "vehicles.crease"
50
+ };
51
+ var DEFERRED_PRIORITY = {
52
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
53
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
54
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
55
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
56
+ };
57
+ var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
58
+ var warnedKeys = () => {
59
+ const holder = globalThis;
60
+ return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
61
+ };
62
+ var isProduction = () => {
63
+ try {
64
+ return process.env.NODE_ENV === "production";
65
+ } catch {
66
+ return false;
67
+ }
68
+ };
69
+ var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
70
+ var warnOnce = (key, message) => {
71
+ if (isQuiet() || isProduction()) return;
72
+ const seen = warnedKeys();
73
+ if (seen.has(key)) return;
74
+ seen.add(key);
75
+ console.warn(`[game-kit] ${message}`);
76
+ };
77
+ var KIT_ORIGIN_KEY = "kitOrigin";
78
+ function markKitOrigin(object2, origin) {
79
+ const node = object2;
80
+ node.userData = node.userData ?? {};
81
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
82
+ return object2;
83
+ }
84
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
85
+ var GAIT_IDS = ["walk", "run"];
86
+ var LANDING_IDS = ["light", "heavy"];
87
+ var BODY_SOUND_IDS = [
88
+ "jump",
89
+ "cloth_rustle",
90
+ "body_fall",
91
+ "fall_damage",
92
+ "player_hurt",
93
+ "player_breath_run",
94
+ "player_death",
95
+ "bite",
96
+ "grab",
97
+ "punch_swing",
98
+ "punch_hit_body",
99
+ "punch_hit_face",
100
+ "punch_hit_object",
101
+ "kick_swing",
102
+ "kick_hit",
103
+ "kick_hit_object",
104
+ "shove_two_hands",
105
+ "knife_swing",
106
+ "knife_stab",
107
+ "knife_slash",
108
+ "knife_hit_object",
109
+ "blunt_swing",
110
+ "blunt_hit",
111
+ "blunt_hit_object",
112
+ "axe_swing",
113
+ "axe_chop",
114
+ "axe_hit_object"
115
+ ];
116
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
117
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
118
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
119
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
120
+
121
+ // src/routed-particles.ts
122
+ var channel = (value) => Math.round(Math.min(1, Math.max(0, value)) * 255);
123
+ var rgb = (red, green, blue) => channel(red) << 16 | channel(green) << 8 | channel(blue);
124
+ var EFFECT_OF = { smoke: "particle_puff", steam: "particle_puff", fire: "particle_glow", spark: "particle_streak" };
125
+ var isRoutedKind = (kind) => EFFECT_OF[kind] !== void 0;
126
+ function routedParticleRequest(particle) {
127
+ const { kind, flag, tint } = particle;
128
+ const look = lookOf(kind, flag, tint);
129
+ return {
130
+ effect: EFFECT_OF[kind],
131
+ point: particle.position,
132
+ values: {
133
+ vx: particle.velocity[0],
134
+ vy: particle.velocity[1],
135
+ vz: particle.velocity[2],
136
+ life: particle.life,
137
+ size: particle.startSize,
138
+ grow: particle.endSize / particle.startSize,
139
+ color: look.color,
140
+ alpha: look.alpha,
141
+ gravity: 0,
142
+ drag: 0
143
+ }
144
+ };
145
+ }
146
+ function lookOf(kind, flag, tint) {
147
+ if (kind === "smoke") return { color: rgb(tint, tint, tint), alpha: 0.55 };
148
+ if (kind === "steam") return { color: rgb(tint, tint, tint), alpha: 0.4 };
149
+ if (kind === "spark") return { color: rgb(1, 0.75, 0.3), alpha: 1 };
150
+ if (flag) return { color: rgb(0.25 + 0.15 * tint, 0.5 + 0.2 * tint, 1), alpha: 0.95 };
151
+ return { color: rgb(1, Math.max(0.15, 0.85 - tint * 0.1), 0.25), alpha: 0.8 };
152
+ }
153
+
43
154
  // src/effects.ts
44
155
  var POOLS = ["smoke", "steam", "fire", "water", "spark"];
45
156
  var WEIGHT = { smoke: 0.27, steam: 0.15, fire: 0.2, water: 0.28, spark: 0.1 };
@@ -214,6 +325,12 @@ void main() {
214
325
  let stamp = 0;
215
326
  let clock = 0;
216
327
  const spawn = (p, x, y, z, vx, vy, vz, life, s0, s1, tint, flag = 0, ground = -1e9) => {
328
+ const sink = isRoutedKind(p.kind) ? options.effects?.() : void 0;
329
+ if (sink) {
330
+ sink.play(routedParticleRequest({ kind: p.kind, flag, position: [x, y, z], velocity: [vx, vy, vz], life, startSize: s0, endSize: s1, tint }));
331
+ return;
332
+ }
333
+ if (isRoutedKind(p.kind)) warnOnce("game-interactables-three.own-particles", "game-interactables-three drew its smoke, steam, fire and sparks with pools of its own because the host lent no effects world (options.effects); pass the host effects so the particles share one budget");
217
334
  if (p.count >= p.cap) return;
218
335
  const i = p.count++;
219
336
  p.pos[i * 3] = x;
@@ -704,51 +821,7 @@ void main() {
704
821
  };
705
822
  }
706
823
 
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 mesh2 = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material);
737
- mesh2.name = "cord";
738
- mesh2.castShadow = true;
739
- return mesh2;
740
- }
741
-
742
- // ../game-contracts/dist/index.js
743
- var KIT_ORIGIN_KEY = "kitOrigin";
744
- function markKitOrigin(object2, origin) {
745
- const node = object2;
746
- node.userData = node.userData ?? {};
747
- node.userData[KIT_ORIGIN_KEY] = { ...origin };
748
- return object2;
749
- }
750
-
751
- // ../game-shapes/dist/chunk-R4RY23RH.js
824
+ // ../game-shapes/dist/chunk-M5N2SHNL.js
752
825
  var __defProp2 = Object.defineProperty;
753
826
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
754
827
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -2166,8 +2239,8 @@ void main() {
2166
2239
  groups: []
2167
2240
  };
2168
2241
  }
2169
- function make(kind, p) {
2170
- switch (kind) {
2242
+ function make(p) {
2243
+ switch (p.kind) {
2171
2244
  case "box": {
2172
2245
  const size = p.size ?? [1, 1, 1];
2173
2246
  const segments = p.segments ?? [1, 1, 1];
@@ -2196,7 +2269,7 @@ void main() {
2196
2269
  case "mesh":
2197
2270
  return mesh(p);
2198
2271
  default:
2199
- throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
2272
+ throw new Error(`game-shapes: unknown primitive kind "${String(p.kind)}"`);
2200
2273
  }
2201
2274
  }
2202
2275
  var templates = /* @__PURE__ */ new Map();
@@ -2204,7 +2277,7 @@ void main() {
2204
2277
  var stats = { hits: 0, misses: 0 };
2205
2278
  function buildPrimitive(node, options = {}) {
2206
2279
  const parameters = primitiveParameters(node, options);
2207
- if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
2280
+ if (node.kind === "mesh" || templateLimit === 0) return make({ ...node, ...parameters });
2208
2281
  const key = `${node.kind}:${canonicalJson(parameters)}`;
2209
2282
  const cached = templates.get(key);
2210
2283
  if (cached) {
@@ -2214,7 +2287,7 @@ void main() {
2214
2287
  return cached;
2215
2288
  }
2216
2289
  stats.misses++;
2217
- const built = make(node.kind, parameters);
2290
+ const built = make({ ...node, ...parameters });
2218
2291
  templates.set(key, built);
2219
2292
  if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
2220
2293
  return built;
@@ -2230,8 +2303,20 @@ void main() {
2230
2303
  };
2231
2304
  }
2232
2305
  var streamFor = (seed, generator, path) => mulberry32(parseInt(hash64(`${String(seed)}\0${generator}\0${path}`).slice(0, 8), 16));
2233
- var generators = /* @__PURE__ */ new Map();
2234
- var getGenerator = (name) => generators.get(name);
2306
+ var GENERATOR_NAME = /^[a-zA-Z][\w.:-]*$/;
2307
+ var generatorsOver = (generators) => ({
2308
+ register(name, generator) {
2309
+ if (!GENERATOR_NAME.test(name)) throw new Error(`game-shapes: generator name "${name}" must start with a letter and use letters, digits, ".", ":", "-" or "_"`);
2310
+ generators.set(name, generator);
2311
+ },
2312
+ unregister: (name) => generators.delete(name),
2313
+ get: (name) => generators.get(name),
2314
+ list: () => [...generators.keys()].sort(),
2315
+ record: () => Object.fromEntries(generators)
2316
+ });
2317
+ var defaultGenerators;
2318
+ var defaultShapeGenerators = () => defaultGenerators ?? (defaultGenerators = generatorsOver(/* @__PURE__ */ new Map()));
2319
+ var getGenerator = (name) => defaultShapeGenerators().get(name);
2235
2320
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? null : "expected a finite number";
2236
2321
  var bool = (v) => typeof v === "boolean" ? null : "expected a boolean";
2237
2322
  var str = (v) => typeof v === "string" ? null : "expected a string";
@@ -2457,7 +2542,7 @@ void main() {
2457
2542
  __publicField(this, "vertices", 0);
2458
2543
  }
2459
2544
  };
2460
- function bake(acc, t, world, rgb, u, indexed) {
2545
+ function bake(acc, t, world, rgb2, u, indexed) {
2461
2546
  const e = world;
2462
2547
  const nm = normalMatrix(world);
2463
2548
  const affine = isAffine(world);
@@ -2468,7 +2553,7 @@ void main() {
2468
2553
  acc.positions.reserve(emitted * 3);
2469
2554
  acc.normals.reserve(emitted * 3);
2470
2555
  acc.uvs.reserve(emitted * 2);
2471
- if (rgb) acc.colors.reserve(emitted * 3);
2556
+ if (rgb2) acc.colors.reserve(emitted * 3);
2472
2557
  const P = acc.positions.array;
2473
2558
  const N = acc.normals.array;
2474
2559
  const U = acc.uvs.array;
@@ -2508,10 +2593,10 @@ void main() {
2508
2593
  N[ni++] = vz * s;
2509
2594
  U[ui++] = u.u0 + tu[i * 2] * du;
2510
2595
  U[ui++] = u.v0 + tu[i * 2 + 1] * dv;
2511
- if (rgb) {
2512
- C[ci++] = rgb[0];
2513
- C[ci++] = rgb[1];
2514
- C[ci++] = rgb[2];
2596
+ if (rgb2) {
2597
+ C[ci++] = rgb2[0];
2598
+ C[ci++] = rgb2[1];
2599
+ C[ci++] = rgb2[2];
2515
2600
  }
2516
2601
  }
2517
2602
  acc.positions.length = pi;
@@ -2567,7 +2652,7 @@ void main() {
2567
2652
  return r * Math.max(sx, sy, sz);
2568
2653
  };
2569
2654
  function expandCustom(node, settings, detail, path) {
2570
- const generator = settings.generators?.[node.generator] ?? getGenerator(node.generator);
2655
+ const generator = settings.generators?.[node.generator] ?? (settings.isolatedGenerators ? void 0 : getGenerator(node.generator));
2571
2656
  if (!generator) throw new Error(`game-shapes: no generator "${node.generator}" (register it with registerGenerator, in the worker too) at ${path}`);
2572
2657
  const generated = generator(node.params, { random: streamFor(settings.seed, node.generator, path), seed: settings.seed, detail, path });
2573
2658
  const { kind: _kind, generator: _generator, params: _params, ...common } = node;
@@ -2749,9 +2834,9 @@ void main() {
2749
2834
  for (const group of autoGroups.values()) {
2750
2835
  const template = buildPrimitive(group.node, level);
2751
2836
  const n = group.matrices.length / 16;
2752
- const rgb = colorChannels(group.color, space ?? "srgb");
2837
+ const rgb2 = colorChannels(group.color, space ?? "srgb");
2753
2838
  const colors = new Float32Array(n * 3);
2754
- for (let i = 0; i < n; i++) colors.set(rgb, i * 3);
2839
+ for (let i = 0; i < n; i++) colors.set(rgb2, i * 3);
2755
2840
  instanced.push({
2756
2841
  material: group.material,
2757
2842
  part: null,
@@ -2775,6 +2860,7 @@ void main() {
2775
2860
  ...options.materialAlias ? { alias: options.materialAlias } : {},
2776
2861
  seed: options.seed ?? 0,
2777
2862
  ...options.generators ? { generators: options.generators } : {},
2863
+ ...options.isolatedGenerators ? { isolatedGenerators: true } : {},
2778
2864
  ...options.instancing ? { autoMin: Math.max(2, options.instancing.minCount) } : {}
2779
2865
  };
2780
2866
  const specs = [{ distance: 0, detail: 1, index: 0 }, ...(schema.lod ?? []).map((rule, index) => ({ ...rule, index: index + 1 }))];
@@ -2807,6 +2893,56 @@ void main() {
2807
2893
  };
2808
2894
  }
2809
2895
 
2896
+ // ../game-shapes-three/dist/index.js
2897
+ function catenary(a, b, sag, n = 12) {
2898
+ const count = Math.max(2, Math.floor(n));
2899
+ 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]);
2900
+ const depth = Math.max(0, sag);
2901
+ const out = [];
2902
+ let shape = () => 0;
2903
+ if (depth > 1e-6 && span > 1e-6) {
2904
+ let lo = 1e-4;
2905
+ let hi = span * 1e3;
2906
+ for (let i = 0; i < 80; i++) {
2907
+ const c2 = (lo + hi) / 2;
2908
+ const d = c2 * (Math.cosh(span / (2 * c2)) - 1);
2909
+ if (d > depth) lo = c2;
2910
+ else hi = c2;
2911
+ }
2912
+ const c = (lo + hi) / 2;
2913
+ shape = (s) => c * (Math.cosh((s - 0.5) * span / c) - Math.cosh(span / (2 * c)));
2914
+ }
2915
+ for (let i = 0; i < count; i++) {
2916
+ const s = i / (count - 1);
2917
+ 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]);
2918
+ }
2919
+ return out;
2920
+ }
2921
+ function createCord(THREE, points, radius, material, radialSegments = 5) {
2922
+ if (points.length < 2) throw new Error("createCord needs at least two points");
2923
+ const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(p[0], p[1], p[2])), false, "centripetal");
2924
+ const segments = Math.max(4, (points.length - 1) * 4);
2925
+ const mesh2 = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material);
2926
+ mesh2.name = "cord";
2927
+ mesh2.castShadow = true;
2928
+ return mesh2;
2929
+ }
2930
+
2931
+ // ../game-render-three/dist/index.js
2932
+ var DEFAULT_ANGLE = Math.PI / 3;
2933
+ var LIGHT_PRIORITY = {
2934
+ blast: 900,
2935
+ rocket: 800,
2936
+ muzzle: 750,
2937
+ fire: 600,
2938
+ flare: 500,
2939
+ headlight: 400,
2940
+ weaponLight: 400,
2941
+ lamp: 200,
2942
+ street: 150
2943
+ };
2944
+ var EFFECT_STEP_SECONDS = 1 / 50;
2945
+
2810
2946
  // ../game-props3d/dist/index.js
2811
2947
  var T = (id, interior, w, d, h, y, solid, group) => ({ id, interior, w, d, h, y, solid, group });
2812
2948
  var TYPE_DEFS = [
@@ -3390,6 +3526,7 @@ ${QROT}${shader.vertexShader}`;
3390
3526
  if (first !== k) materialAlias[k] = first;
3391
3527
  }
3392
3528
  const lightsOut = [];
3529
+ const sourcesOut = [];
3393
3530
  const build = (batches, points, id, into, materialFor) => {
3394
3531
  for (const batch of batches) {
3395
3532
  const g = new THREE.BufferGeometry();
@@ -3411,6 +3548,14 @@ ${QROT}${shader.vertexShader}`;
3411
3548
  }
3412
3549
  for (const point of points) {
3413
3550
  const l = point.data;
3551
+ if (options.lights) {
3552
+ const bulb = new THREE.Object3D();
3553
+ bulb.name = "lamp";
3554
+ bulb.position.set(point.position[0], point.position[1], point.position[2]);
3555
+ into.add(bulb);
3556
+ sourcesOut.push(options.lights.addSource({ owner: "interactables.lamp", priority: LIGHT_PRIORITY.lamp, color: l.color, intensity: 1, distance: l.range, position: [0, 0, 0], follow: bulb }));
3557
+ continue;
3558
+ }
3414
3559
  const light = new THREE.PointLight(l.color, 1, l.range);
3415
3560
  light.position.set(point.position[0], point.position[1], point.position[2]);
3416
3561
  into.add(light);
@@ -3454,7 +3599,7 @@ ${QROT}${shader.vertexShader}`;
3454
3599
  }
3455
3600
  if (spec && (options.mergeParts ?? quality === "game")) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude });
3456
3601
  if (spec) tagDestruction(group, parts, spec);
3457
- return { group, parts, lights: lightsOut };
3602
+ return { group, parts, lights: lightsOut, sources: sourcesOut };
3458
3603
  }
3459
3604
  };
3460
3605
  }
@@ -3646,7 +3791,7 @@ ${QROT}${shader.vertexShader}`;
3646
3791
  emissiveIntensity: 0
3647
3792
  });
3648
3793
  let on = false;
3649
- let channel = 0;
3794
+ let channel2 = 0;
3650
3795
  let clock = 0;
3651
3796
  let acc = 0;
3652
3797
  const fill = (style, x, y, rw, rh) => {
@@ -3656,7 +3801,7 @@ ${QROT}${shader.vertexShader}`;
3656
3801
  };
3657
3802
  const drawBars = () => {
3658
3803
  const n = BARS.length;
3659
- 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));
3804
+ for (let i = 0; i < n; i++) fill(BARS[(i + channel2) % n], Math.floor(i * w / n), 0, Math.ceil(w / n), Math.floor(h * 0.75));
3660
3805
  fill("#101010", 0, Math.floor(h * 0.75), w, h - Math.floor(h * 0.75));
3661
3806
  const x = clock * 20 % (w + 12) - 12;
3662
3807
  fill("#ffffff", x, Math.floor(h * 0.8), 12, Math.floor(h * 0.1));
@@ -3671,7 +3816,7 @@ ${QROT}${shader.vertexShader}`;
3671
3816
  }
3672
3817
  };
3673
3818
  const drawScene = () => {
3674
- const hue = channel * 40 % 360;
3819
+ const hue = channel2 * 40 % 360;
3675
3820
  fill(`hsl(${(200 + hue) % 360},60%,${55 + 10 * Math.sin(clock * 0.3)}%)`, 0, 0, w, Math.floor(h * 0.65));
3676
3821
  fill(`hsl(${(110 + hue) % 360},40%,32%)`, 0, Math.floor(h * 0.65), w, h - Math.floor(h * 0.65));
3677
3822
  const sx = (0.5 + 0.4 * Math.sin(clock * 0.5)) * w;
@@ -3683,9 +3828,9 @@ ${QROT}${shader.vertexShader}`;
3683
3828
  const draw = () => {
3684
3829
  if (!on) {
3685
3830
  fill("#000000", 0, 0, w, h);
3686
- } else if (channel % 3 === 0) {
3831
+ } else if (channel2 % 3 === 0) {
3687
3832
  drawBars();
3688
- } else if (channel % 3 === 1) {
3833
+ } else if (channel2 % 3 === 1) {
3689
3834
  drawNoise();
3690
3835
  } else {
3691
3836
  drawScene();
@@ -3706,7 +3851,7 @@ ${QROT}${shader.vertexShader}`;
3706
3851
  return on;
3707
3852
  },
3708
3853
  get channel() {
3709
- return channel;
3854
+ return channel2;
3710
3855
  },
3711
3856
  setOn(value) {
3712
3857
  on = !!value;
@@ -3714,7 +3859,7 @@ ${QROT}${shader.vertexShader}`;
3714
3859
  apply();
3715
3860
  },
3716
3861
  setChannel(n) {
3717
- channel = Math.max(0, Math.floor(n));
3862
+ channel2 = Math.max(0, Math.floor(n));
3718
3863
  if (on) draw();
3719
3864
  },
3720
3865
  update(dt) {
@@ -3813,7 +3958,7 @@ uniform float swayAmplitude;
3813
3958
  function createThreeAdapter(THREE, options = {}) {
3814
3959
  return {
3815
3960
  THREE,
3816
- createMeshApi: (o = {}) => createMeshApi(THREE, { quality: options.quality, materials: options.materials, ...o }),
3961
+ createMeshApi: (o = {}) => createMeshApi(THREE, { quality: options.quality, materials: options.materials, ...options.lights ? { lights: options.lights } : {}, ...o }),
3817
3962
  applyPose: (group, motion, t) => applyPose(THREE, group, motion, t),
3818
3963
  syncObject: (world, objectId, parts, o) => syncObject(THREE, world, objectId, parts, o),
3819
3964
  swayMaterial: (base, o) => swayMaterial(THREE, base, o),