@adatechnology/game-interactables-three 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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;
@@ -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. */
@@ -337,6 +356,8 @@ interface ThreeAdapterOptions {
337
356
  /** `Reflector` and env map for `createMirrors`. */
338
357
  Reflector?: MirrorOptions['Reflector'];
339
358
  envMap?: MirrorOptions['envMap'];
359
+ /** The host's light budget: the lamps of every mesh api this adapter makes become sources of it (see `MeshApiOptions.lights`). */
360
+ lights?: LightBudget;
340
361
  }
341
362
  /** The package functions with `THREE` and the common options already bound. */
342
363
  interface ThreeAdapter {
@@ -395,4 +416,4 @@ type MergedGroup = ThreeNS.Group & {
395
416
  */
396
417
  declare function mergeParts(THREE: ThreeLib, root: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec, options?: MergeOptions): void;
397
418
 
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 };
419
+ 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-IFXQIDSA.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);
@@ -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),