@adatechnology/game-vehicles-three 0.4.0 → 0.5.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.
@@ -28,10 +28,12 @@ var GameVehiclesThree = (() => {
28
28
  PAGE_SCENARIO_ALIASES: () => PAGE_SCENARIO_ALIASES,
29
29
  PRUNE_BELOW: () => PRUNE_BELOW,
30
30
  VEHICLES_AREA_ID: () => VEHICLES_AREA_ID,
31
+ VEHICLE_LOD_DISTANCE: () => VEHICLE_LOD_DISTANCE,
31
32
  VEHICLE_SCENARIO_IDS: () => VEHICLE_SCENARIO_IDS,
32
33
  classColor: () => classColor,
33
34
  classify: () => classify,
34
35
  createCabinSmoke: () => createCabinSmoke,
36
+ createVehicleLod: () => createVehicleLod,
35
37
  createVehiclesTestModule: () => createVehiclesTestModule,
36
38
  createVehiclesThree: () => createVehiclesThree,
37
39
  defaultEffectIds: () => defaultEffectIds,
@@ -43,6 +45,7 @@ var GameVehiclesThree = (() => {
43
45
  });
44
46
 
45
47
  // ../game-contracts/dist/index.js
48
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
46
49
  var DEFAULT_IGNITION_RULES = {
47
50
  fillCurve: [
48
51
  [0, 0.6],
@@ -86,6 +89,38 @@ var GameVehiclesThree = (() => {
86
89
  const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
87
90
  return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
88
91
  };
92
+ var DEFERRED_QUEUE_ID = {
93
+ DESTRUCTION_EVENTS: "destruction.events",
94
+ BREACH_PAINT: "floorplan.breach-paint",
95
+ AUDIO_VOICES: "audio.voices",
96
+ VEHICLE_CREASE: "vehicles.crease"
97
+ };
98
+ var DEFERRED_PRIORITY = {
99
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
100
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
101
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
102
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
103
+ };
104
+ var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
105
+ var warnedKeys = () => {
106
+ const holder = globalThis;
107
+ return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
108
+ };
109
+ var isProduction = () => {
110
+ try {
111
+ return process.env.NODE_ENV === "production";
112
+ } catch {
113
+ return false;
114
+ }
115
+ };
116
+ var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
117
+ var warnOnce = (key, message) => {
118
+ if (isQuiet() || isProduction()) return;
119
+ const seen = warnedKeys();
120
+ if (seen.has(key)) return;
121
+ seen.add(key);
122
+ console.warn(`[game-kit] ${message}`);
123
+ };
89
124
  var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
90
125
  var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
91
126
  var deepMerge = (base, patch) => {
@@ -107,10 +142,13 @@ var GameVehiclesThree = (() => {
107
142
  for (const [id, patch] of Object.entries(patches ?? {})) {
108
143
  const entry = entries.get(id);
109
144
  if (entry) entries.set(id, deepMerge(entry, patch));
145
+ else warnOnce(`registry.patch.${id}`, `patch for "${id}" ignored: no entry has that id`);
110
146
  }
111
147
  };
112
148
  var removeIds = (entries, ids) => {
113
- for (const id of ids ?? []) entries.delete(id);
149
+ for (const id of ids ?? []) {
150
+ if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of "${id}" ignored: no entry has that id`);
151
+ }
114
152
  };
115
153
  var KIT_ORIGIN_KEY = "kitOrigin";
116
154
  function markKitOrigin(object2, origin) {
@@ -135,6 +173,43 @@ var GameVehiclesThree = (() => {
135
173
  return lx > fp.minX - radius && lx < fp.maxX + radius && lz > fp.minZ - radius && lz < fp.maxZ + radius;
136
174
  };
137
175
  }
176
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
177
+ var GAIT_IDS = ["walk", "run"];
178
+ var LANDING_IDS = ["light", "heavy"];
179
+ var BODY_SOUND_IDS = [
180
+ "jump",
181
+ "cloth_rustle",
182
+ "body_fall",
183
+ "fall_damage",
184
+ "player_hurt",
185
+ "player_breath_run",
186
+ "player_death",
187
+ "bite",
188
+ "grab",
189
+ "punch_swing",
190
+ "punch_hit_body",
191
+ "punch_hit_face",
192
+ "punch_hit_object",
193
+ "kick_swing",
194
+ "kick_hit",
195
+ "kick_hit_object",
196
+ "shove_two_hands",
197
+ "knife_swing",
198
+ "knife_stab",
199
+ "knife_slash",
200
+ "knife_hit_object",
201
+ "blunt_swing",
202
+ "blunt_hit",
203
+ "blunt_hit_object",
204
+ "axe_swing",
205
+ "axe_chop",
206
+ "axe_hit_object"
207
+ ];
208
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
209
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
210
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing2) => `land_${surface}_${landing2}`));
211
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
212
+ var SWING_LEAD_SECONDS = { punch: 0.07, kick: 0.09, shove: 0, blade: 0.05, blunt: 0.1, chop: 0.11 };
138
213
 
139
214
  // ../game-vehicles-fleet/dist/index.js
140
215
  var military = [
@@ -24324,7 +24399,7 @@ var GameVehiclesThree = (() => {
24324
24399
  c2.cylinder(0.03, 0.04, 0.08, MT.chrome, sx + 2.35, fl + 0.99, -0.75);
24325
24400
  c2.part("fuel_tank", trl("labels.oxygen_cylinder"), null, new V3(sx + 2.35, fl + 0.5, -0.75), 0.5);
24326
24401
  c2.roundBox(0.04, 0.26, 0.34, 0.02, MT.screen, 0.1, 1.7, 0.5);
24327
- const light = c2.roundBox(
24402
+ const lightPanel = c2.roundBox(
24328
24403
  2.3,
24329
24404
  0.02,
24330
24405
  W - 0.3,
@@ -24334,7 +24409,7 @@ var GameVehiclesThree = (() => {
24334
24409
  m.top - 0.06,
24335
24410
  0
24336
24411
  );
24337
- light.castShadow = false;
24412
+ lightPanel.castShadow = false;
24338
24413
  m.roofItems = beaconBar;
24339
24414
  }
24340
24415
  });
@@ -27893,15 +27968,17 @@ var GameVehiclesThree = (() => {
27893
27968
  }
27894
27969
  function createVehiclesThree(THREE, options) {
27895
27970
  const kit = options.kit;
27971
+ const fleet = options.fleet ?? { colorFromSeed, fleetDims, paintPalettes, paletteOf };
27972
+ const { colorFromSeed: colorFromSeed2, fleetDims: fleetDims2, paintPalettes: paintPalettes2, paletteOf: paletteOf2 } = fleet;
27896
27973
  const groupOf = options.groupOf || ((key) => kit.find(key)?.group === kit.config.defaultGroup ? "cars" : String(kit.find(key)?.group));
27897
27974
  const militaryKeys = /* @__PURE__ */ new Set();
27898
- const isMilitary = (key) => militaryKeys.has(key) || paletteOf(groupOf(key)) === paintPalettes.military;
27975
+ const isMilitary = (key) => militaryKeys.has(key) || paletteOf2(groupOf(key)) === paintPalettes2.military;
27899
27976
  const build = (key, o = {}) => {
27900
27977
  const quality = o.quality === "lite" ? "lite" : "high";
27901
27978
  const spec = kit.find(key);
27902
27979
  if (!spec) throw new Error(`createVehiclesThree: unknown vehicle "${key}"`);
27903
27980
  const seed = o.seed == null ? null : o.seed;
27904
- const color2 = o.color || (seed != null ? colorFromSeed(seed, isMilitary(key) ? paintPalettes.military : paintPalettes.civil) : null);
27981
+ const color2 = o.color || (seed != null ? colorFromSeed2(seed, isMilitary(key) ? paintPalettes2.military : paintPalettes2.civil) : null);
27905
27982
  const c = kit.build(key, o.state || "intact", void 0, { lod: quality === "lite" });
27906
27983
  c.root.updateMatrixWorld(true);
27907
27984
  const primary = primaryPaint(c);
@@ -27967,7 +28044,7 @@ var GameVehiclesThree = (() => {
27967
28044
  width: +size.x.toFixed(3),
27968
28045
  height: +size.y.toFixed(3),
27969
28046
  pruned,
27970
- expected: fleetDims(key),
28047
+ expected: fleetDims2(key),
27971
28048
  context: quality === "high" ? c : null,
27972
28049
  dispose() {
27973
28050
  group.traverse((m) => {
@@ -27992,7 +28069,7 @@ var GameVehiclesThree = (() => {
27992
28069
  }
27993
28070
  }
27994
28071
 
27995
- // ../game-shapes/dist/chunk-R4RY23RH.js
28072
+ // ../game-shapes/dist/chunk-IFXQIDSA.js
27996
28073
  function canonicalJson(value) {
27997
28074
  if (value === null) return "null";
27998
28075
  if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
@@ -29287,10 +29364,10 @@ var GameVehiclesThree = (() => {
29287
29364
  return cached;
29288
29365
  }
29289
29366
  stats.misses++;
29290
- const built = make(node.kind, parameters);
29291
- templates.set(key, built);
29367
+ const built2 = make(node.kind, parameters);
29368
+ templates.set(key, built2);
29292
29369
  if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
29293
- return built;
29370
+ return built2;
29294
29371
  }
29295
29372
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? null : "expected a finite number";
29296
29373
  var bool = (v) => typeof v === "boolean" ? null : "expected a boolean";
@@ -30468,7 +30545,7 @@ var GameVehiclesThree = (() => {
30468
30545
  }
30469
30546
  return ti.builtBlocks === total;
30470
30547
  }
30471
- function start(geo) {
30548
+ function start(geo, store = cache) {
30472
30549
  const pos = geo.attributes.position;
30473
30550
  const triangles = trianglesOf(geo);
30474
30551
  const ti = {
@@ -30481,7 +30558,7 @@ var GameVehiclesThree = (() => {
30481
30558
  blocks: new Float64Array(blockCount(triangles) * 6),
30482
30559
  builtBlocks: 0
30483
30560
  };
30484
- cache.set(geo, ti);
30561
+ store.set(geo, ti);
30485
30562
  return ti;
30486
30563
  }
30487
30564
  function triIndexOf(geo) {
@@ -30533,6 +30610,106 @@ var GameVehiclesThree = (() => {
30533
30610
  }
30534
30611
  return { box: box2, version: ti.positionVersion };
30535
30612
  }
30613
+ var rayCache = /* @__PURE__ */ new WeakMap();
30614
+ function rayIndexOf(geo) {
30615
+ const shared2 = cache.get(geo);
30616
+ if (shared2 && isCurrent(geo, shared2) && shared2.builtBlocks === blockCount(shared2.triangles)) return shared2;
30617
+ let ti = rayCache.get(geo);
30618
+ if (!ti || !isCurrent(geo, ti)) ti = start(geo, rayCache);
30619
+ advance(geo, ti, Infinity);
30620
+ return ti;
30621
+ }
30622
+ var BLOCK_SLACK = 1e-6;
30623
+ var scratch = /* @__PURE__ */ new WeakMap();
30624
+ function scratchOf(THREE) {
30625
+ let s = scratch.get(THREE);
30626
+ if (!s) {
30627
+ s = {
30628
+ inverse: new THREE.Matrix4(),
30629
+ local: new THREE.Ray(),
30630
+ box: new THREE.Box3(),
30631
+ a: new THREE.Vector3(),
30632
+ b: new THREE.Vector3(),
30633
+ c: new THREE.Vector3(),
30634
+ point: new THREE.Vector3(),
30635
+ world: new THREE.Vector3(),
30636
+ sphere: new THREE.Sphere()
30637
+ };
30638
+ scratch.set(THREE, s);
30639
+ }
30640
+ return s;
30641
+ }
30642
+ function indexable(THREE, mesh2) {
30643
+ const geo = mesh2.geometry;
30644
+ if (mesh2.raycast !== THREE.Mesh.prototype.raycast) return false;
30645
+ if (!mesh2.isMesh || mesh2.isSkinnedMesh || mesh2.isInstancedMesh || mesh2.isBatchedMesh || !geo || !geo.isBufferGeometry) return false;
30646
+ if (!mesh2.material || Array.isArray(mesh2.material)) return false;
30647
+ if (geo.morphAttributes && geo.morphAttributes.position && geo.morphAttributes.position.length) return false;
30648
+ return supports(geo);
30649
+ }
30650
+ function raycastMesh(THREE, raycaster, mesh2, out) {
30651
+ const geo = mesh2.geometry;
30652
+ const s = scratchOf(THREE);
30653
+ if (geo.boundingSphere === null) geo.computeBoundingSphere();
30654
+ s.sphere.copy(geo.boundingSphere).applyMatrix4(mesh2.matrixWorld);
30655
+ if (!raycaster.ray.intersectsSphere(s.sphere)) return;
30656
+ const ti = rayIndexOf(geo);
30657
+ if (!ti) return;
30658
+ s.inverse.copy(mesh2.matrixWorld).invert();
30659
+ s.local.copy(raycaster.ray).applyMatrix4(s.inverse);
30660
+ const material = mesh2.material;
30661
+ const back = material.side === THREE.BackSide;
30662
+ const cull = material.side === THREE.FrontSide;
30663
+ const P = geo.attributes.position;
30664
+ const ix = geo.index;
30665
+ const count2 = ix ? ix.count : P.count;
30666
+ const first = Math.max(0, geo.drawRange.start);
30667
+ const last = Math.min(count2, geo.drawRange.start + geo.drawRange.count);
30668
+ const t0 = Math.ceil(first / 3);
30669
+ const t1 = Math.floor(last / 3);
30670
+ const B = ti.boxes;
30671
+ const K = ti.blocks;
30672
+ for (let blk = Math.floor(t0 / TRI_BLOCK); blk * TRI_BLOCK < t1; blk++) {
30673
+ const o = blk * 6;
30674
+ s.box.min.set(K[o] - BLOCK_SLACK, K[o + 1] - BLOCK_SLACK, K[o + 2] - BLOCK_SLACK);
30675
+ s.box.max.set(K[o + 3] + BLOCK_SLACK, K[o + 4] + BLOCK_SLACK, K[o + 5] + BLOCK_SLACK);
30676
+ if (!s.local.intersectsBox(s.box)) continue;
30677
+ const end = Math.min(t1, (blk + 1) * TRI_BLOCK);
30678
+ for (let t = Math.max(t0, blk * TRI_BLOCK); t < end; t++) {
30679
+ const j = t * 6;
30680
+ s.box.min.set(B[j] - BLOCK_SLACK, B[j + 1] - BLOCK_SLACK, B[j + 2] - BLOCK_SLACK);
30681
+ s.box.max.set(B[j + 3] + BLOCK_SLACK, B[j + 4] + BLOCK_SLACK, B[j + 5] + BLOCK_SLACK);
30682
+ if (!s.local.intersectsBox(s.box)) continue;
30683
+ const a = ix ? ix.getX(t * 3) : t * 3;
30684
+ const b = ix ? ix.getX(t * 3 + 1) : t * 3 + 1;
30685
+ const c = ix ? ix.getX(t * 3 + 2) : t * 3 + 2;
30686
+ s.a.fromBufferAttribute(P, a);
30687
+ s.b.fromBufferAttribute(P, b);
30688
+ s.c.fromBufferAttribute(P, c);
30689
+ const hit = back ? s.local.intersectTriangle(s.c, s.b, s.a, true, s.point) : s.local.intersectTriangle(s.a, s.b, s.c, cull, s.point);
30690
+ if (hit === null) continue;
30691
+ s.world.copy(s.point).applyMatrix4(mesh2.matrixWorld);
30692
+ const distance = raycaster.ray.origin.distanceTo(s.world);
30693
+ if (distance < raycaster.near || distance > raycaster.far) continue;
30694
+ const normal = new THREE.Vector3();
30695
+ THREE.Triangle.getNormal(s.a, s.b, s.c, normal);
30696
+ out.push({ distance, point: s.world.clone(), object: mesh2, face: { a, b, c, normal, materialIndex: 0 }, faceIndex: Math.floor(t) });
30697
+ }
30698
+ }
30699
+ }
30700
+ function collect2(THREE, raycaster, object2, recursive, out) {
30701
+ if (object2.layers.test(raycaster.layers)) {
30702
+ if (indexable(THREE, object2)) raycastMesh(THREE, raycaster, object2, out);
30703
+ else object2.raycast(raycaster, out);
30704
+ }
30705
+ if (recursive) for (const child of object2.children) collect2(THREE, raycaster, child, true, out);
30706
+ }
30707
+ function raycastIndexed(THREE, raycaster, objects, recursive = false) {
30708
+ const out = [];
30709
+ for (const object2 of objects) collect2(THREE, raycaster, object2, recursive, out);
30710
+ out.sort((x, y) => x.distance - y.distance);
30711
+ return out;
30712
+ }
30536
30713
  function installGeometry(kit) {
30537
30714
  const THREE = kit.THREE;
30538
30715
  const V3 = THREE.Vector3;
@@ -31857,6 +32034,7 @@ var GameVehiclesThree = (() => {
31857
32034
  }
31858
32035
  kit.profX = profX;
31859
32036
  const _sr = new THREE.Raycaster();
32037
+ _sr.layers.enableAll();
31860
32038
  kit.surfaceAt = function(c, org, dir, o) {
31861
32039
  o = o || {};
31862
32040
  c.root.updateMatrixWorld(true);
@@ -31873,7 +32051,7 @@ var GameVehiclesThree = (() => {
31873
32051
  _sr.set(p0.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
31874
32052
  _sr.near = 0;
31875
32053
  _sr.far = o.max || 20;
31876
- const h = _sr.intersectObjects(cands, false)[0];
32054
+ const h = raycastIndexed(THREE, _sr, cands)[0];
31877
32055
  if (!h) return null;
31878
32056
  const n = h.face ? h.face.normal.clone().transformDirection(h.object.matrixWorld).transformDirection(inv) : d.clone().negate();
31879
32057
  if (n.dot(d) > 0) n.negate();
@@ -31893,6 +32071,7 @@ var GameVehiclesThree = (() => {
31893
32071
  const V3 = THREE.Vector3;
31894
32072
  const I = kit._;
31895
32073
  const { MATERIALS, crease, cutGeometry, pathShape, subdivide, tumble, uvProject } = I;
32074
+ I.paneSeals = /* @__PURE__ */ new WeakMap();
31896
32075
  kit.addGlass = function(c, geo, label, type, o) {
31897
32076
  o = o || {};
31898
32077
  if (!geo.attributes.uv || o.reuv) o.aspect = uvProject(geo, o.uvAxes);
@@ -31959,7 +32138,7 @@ var GameVehiclesThree = (() => {
31959
32138
  cy /= poly.length;
31960
32139
  const base = I.resolveLabel(label ? label(i, polys.length) : { key: "window" });
31961
32140
  const lab = { key: base.key, label: kit.t("parts.glass_side", { label: base.label, s }) };
31962
- kit.addGlass(c, geo, lab, o.type || "tempered", {
32141
+ const pane = kit.addGlass(c, geo, lab, o.type || "tempered", {
31963
32142
  mat: o.mat || (o.see === false ? MATERIALS.glassDark : MATERIALS.glass),
31964
32143
  pos: new V3(cx, cy, s * I.halfAt(Wg, T, cy)),
31965
32144
  outline: shp.getSpacedPoints(56),
@@ -31975,6 +32154,7 @@ var GameVehiclesThree = (() => {
31975
32154
  pts.pop();
31976
32155
  const seal = c.tube(pts, 0.011, MATERIALS.trim, { closed: true, seg: 72, rs: 5 });
31977
32156
  seal.castShadow = false;
32157
+ I.paneSeals.set(pane, seal);
31978
32158
  });
31979
32159
  });
31980
32160
  };
@@ -33671,6 +33851,42 @@ var GameVehiclesThree = (() => {
33671
33851
  };
33672
33852
  Object.assign(I, {});
33673
33853
  }
33854
+ var ROUTED_PARTICLE_TYPES = { smoke: "particle_puff", dust: "particle_puff", fire: "particle_glow" };
33855
+ var PUFF_LOOKS = {
33856
+ smokewhite: [10133927, 0.42],
33857
+ smokeblack: [1776412, 0.62],
33858
+ smokegray: [5593436, 0.5],
33859
+ smokesteam: [11975873, 0.3],
33860
+ dust: [7235160, 0.34],
33861
+ fire: [16777215, 0.9]
33862
+ };
33863
+ var LIFE = { dust: 1.6, smoke: 2.8, fire: 0.7 };
33864
+ var FIRE_COLOR = 16753968;
33865
+ var SMOKE_START_SCALE = 0.18;
33866
+ function routedParticleRequest(input) {
33867
+ const { type } = input;
33868
+ const look = PUFF_LOOKS[type + (input.variant ?? "")] ?? PUFF_LOOKS[type] ?? PUFF_LOOKS.smokewhite;
33869
+ const life = input.life ?? LIFE[type] ?? 99;
33870
+ const size = (input.size ?? 1) * (input.grow ?? 1);
33871
+ const isFire = type === "fire";
33872
+ const growth = isFire ? 1 : (SMOKE_START_SCALE + 1.5 * (1 - Math.exp(-life * 1.1)) + life * 0.12) / SMOKE_START_SCALE;
33873
+ return {
33874
+ effect: ROUTED_PARTICLE_TYPES[type],
33875
+ point: input.position,
33876
+ values: {
33877
+ vx: input.velocity[0],
33878
+ vy: input.velocity[1],
33879
+ vz: input.velocity[2],
33880
+ life,
33881
+ size: isFire ? size * 0.85 : size * SMOKE_START_SCALE,
33882
+ grow: growth,
33883
+ color: input.color ?? (isFire ? FIRE_COLOR : look[0]),
33884
+ alpha: input.alpha ?? look[1],
33885
+ gravity: isFire ? 0 : type === "dust" ? 0 : -0.08,
33886
+ drag: type === "dust" ? 2.5 : isFire ? 1.5 : 0.9
33887
+ }
33888
+ };
33889
+ }
33674
33890
  function installParticles(kit) {
33675
33891
  const THREE = kit.THREE;
33676
33892
  const V3 = THREE.Vector3;
@@ -33761,7 +33977,7 @@ var GameVehiclesThree = (() => {
33761
33977
  });
33762
33978
  }
33763
33979
  const list2 = fire ? FIRE_TEXTURES : SMOKE_TEXTURES;
33764
- const mat = new THREE.SpriteMaterial({
33980
+ const build = () => new THREE.SpriteMaterial({
33765
33981
  map: list2[Math.floor(Math.random() * list2.length)],
33766
33982
  color: new THREE.Color(col),
33767
33983
  transparent: true,
@@ -33769,6 +33985,7 @@ var GameVehiclesThree = (() => {
33769
33985
  opacity: op,
33770
33986
  blending: fire ? THREE.AdditiveBlending : THREE.NormalBlending
33771
33987
  });
33988
+ const mat = kit.particleMaterials ? kit.particleMaterials.own(build) : build();
33772
33989
  const dis = { value: 0 };
33773
33990
  mat.userData.dis = dis;
33774
33991
  mat.onBeforeCompile = function(sh) {
@@ -33796,11 +34013,37 @@ var GameVehiclesThree = (() => {
33796
34013
  function killParticle(c, i) {
33797
34014
  const P = c.particles[i];
33798
34015
  c.effects.remove(P.m);
33799
- if (P.own) P.m.material.dispose();
34016
+ if (P.own) {
34017
+ if (kit.particleMaterials) kit.particleMaterials.release(P.m.material);
34018
+ else P.m.material.dispose();
34019
+ }
33800
34020
  c.particles.splice(i, 1);
33801
34021
  }
34022
+ function routeParticle(c, effects, type, pos, vel, o) {
34023
+ c.effects.updateWorldMatrix(true, false);
34024
+ const at = pos.clone().applyMatrix4(c.effects.matrixWorld);
34025
+ const to = vel.clone().transformDirection(c.effects.matrixWorld).multiplyScalar(vel.length());
34026
+ const colour = o.col == null ? void 0 : new THREE.Color(o.col).getHex();
34027
+ effects.play(
34028
+ routedParticleRequest({
34029
+ type,
34030
+ position: [at.x, at.y, at.z],
34031
+ velocity: [to.x, to.y, to.z],
34032
+ ...o.variant ? { variant: o.variant } : {},
34033
+ ...colour !== void 0 ? { color: colour } : {},
34034
+ ...o.op != null ? { alpha: o.op } : {},
34035
+ ...o.s ? { size: o.s } : {},
34036
+ ...o.life ? { life: o.life } : {},
34037
+ ...o.grow ? { grow: o.grow } : {}
34038
+ })
34039
+ );
34040
+ return null;
34041
+ }
33802
34042
  kit.spawn = function(c, type, pos, vel, o) {
33803
34043
  o = o || {};
34044
+ const effects = ROUTED_PARTICLE_TYPES[type] ? kit.effects?.() : void 0;
34045
+ if (effects) return routeParticle(c, effects, type, pos, vel, o);
34046
+ if (ROUTED_PARTICLE_TYPES[type] && !kit.effects) warnOnce("game-vehicles.own-particles", "game-vehicles drew its smoke, fire and dust with sprites of its own because the host lent no effects world (options.effects); pass the host effects so the puffs share one budget");
33804
34047
  if (c.particles.length >= PARTICLE_LIMIT) {
33805
34048
  const idx = c.particles.findIndex(function(q) {
33806
34049
  return TRANSIENT_TYPES[q.type];
@@ -34070,6 +34313,7 @@ var GameVehiclesThree = (() => {
34070
34313
  };
34071
34314
  kit.defineFluid("stain", { fresh: "#4a4a44", dry: "#26241f", wipeable: false });
34072
34315
  const _fr = new THREE.Raycaster();
34316
+ _fr.layers.enableAll();
34073
34317
  kit.splat = function(c, p, n, r, o) {
34074
34318
  o = o || {};
34075
34319
  n = n.clone().normalize();
@@ -34509,31 +34753,55 @@ var GameVehiclesThree = (() => {
34509
34753
  };
34510
34754
  Object.assign(I, { _fr, buildDrip, DOWNHILL, PN, puddleGeometry, rimGeometry, puddle, shapePuddle, updateFluids });
34511
34755
  }
34756
+ function crackedGlassMaterial(THREE, texture) {
34757
+ return new THREE.MeshStandardMaterial({
34758
+ color: 16777215,
34759
+ map: texture,
34760
+ emissive: 6976118,
34761
+ emissiveMap: texture,
34762
+ transparent: true,
34763
+ roughness: 0.25,
34764
+ metalness: 0.1,
34765
+ side: THREE.DoubleSide,
34766
+ depthWrite: false
34767
+ });
34768
+ }
34512
34769
  function installGlassDamage(kit) {
34513
34770
  const THREE = kit.THREE;
34514
34771
  const V3 = THREE.Vector3;
34515
34772
  const PI = Math.PI;
34516
34773
  const I = kit._;
34517
34774
  const document2 = kit.document;
34518
- const { DOUBLE_SIDE, MATERIALS, crease, cutGeometry, mergeGeometries, subdivide, tumble } = I;
34519
- function glassUV(msh, pLocal) {
34775
+ const { MATERIALS, crease, cutGeometry, mergeGeometries, subdivide, tumble } = I;
34776
+ function glassUV(c, msh, pLocal) {
34520
34777
  const pos = msh.geometry.attributes.position;
34521
34778
  const uv = msh.geometry.attributes.uv;
34522
34779
  if (!uv) return [0.5, 0.5];
34523
- const inv = new THREE.Matrix4().copy(msh.matrix).invert();
34524
- const q = pLocal.clone().applyMatrix4(inv);
34525
- let best = 1e9;
34526
- let bi = 0;
34527
- const v = new V3();
34528
- for (let i = 0; i < pos.count; i++) {
34529
- v.fromBufferAttribute(pos, i);
34530
- const d = v.distanceToSquared(q);
34531
- if (d < best) {
34532
- best = d;
34533
- bi = i;
34534
- }
34780
+ c.root.updateMatrixWorld(true);
34781
+ const q = msh.worldToLocal(c.vehicle.localToWorld(pLocal.clone()));
34782
+ const idx = msh.geometry.index;
34783
+ const tri = new THREE.Triangle();
34784
+ const near = new V3();
34785
+ const bary = new V3();
34786
+ let best = Infinity;
34787
+ const out = [0.5, 0.5];
34788
+ const count2 = idx ? idx.count : pos.count;
34789
+ for (let f = 0; f + 2 < count2; f += 3) {
34790
+ const ia = idx ? idx.getX(f) : f;
34791
+ const ib = idx ? idx.getX(f + 1) : f + 1;
34792
+ const ic = idx ? idx.getX(f + 2) : f + 2;
34793
+ tri.a.fromBufferAttribute(pos, ia);
34794
+ tri.b.fromBufferAttribute(pos, ib);
34795
+ tri.c.fromBufferAttribute(pos, ic);
34796
+ tri.closestPointToPoint(q, near);
34797
+ const d = near.distanceToSquared(q);
34798
+ if (d >= best) continue;
34799
+ best = d;
34800
+ tri.getBarycoord(near, bary);
34801
+ out[0] = uv.getX(ia) * bary.x + uv.getX(ib) * bary.y + uv.getX(ic) * bary.z;
34802
+ out[1] = uv.getY(ia) * bary.x + uv.getY(ib) * bary.y + uv.getY(ic) * bary.z;
34535
34803
  }
34536
- return [uv.getX(bi), uv.getY(bi)];
34804
+ return out;
34537
34805
  }
34538
34806
  function drawWeb(x, cx, cy, s, ax, rnd) {
34539
34807
  const n = 16 + Math.floor(rnd() * 8);
@@ -34610,19 +34878,9 @@ var GameVehiclesThree = (() => {
34610
34878
  x0.fillStyle = "rgba(48,66,72,0.5)";
34611
34879
  x0.fillRect(0, 0, 256, 256);
34612
34880
  G2.tex = new THREE.CanvasTexture(G2.cv);
34613
- msh.material = new THREE.MeshStandardMaterial({
34614
- color: 16777215,
34615
- map: G2.tex,
34616
- emissive: 6976118,
34617
- emissiveMap: G2.tex,
34618
- transparent: true,
34619
- roughness: 0.25,
34620
- metalness: 0.1,
34621
- side: DOUBLE_SIDE,
34622
- depthWrite: false
34623
- });
34881
+ msh.material = crackedGlassMaterial(THREE, G2.tex);
34624
34882
  }
34625
- const uv = glassUV(msh, p);
34883
+ const uv = glassUV(c, msh, p);
34626
34884
  const x = G2.cv.getContext("2d");
34627
34885
  drawWeb(x, uv[0] * 256, (1 - uv[1]) * 256, sc != null ? sc : 0.6 + force * 0.5, Math.max(0.4, G2.aspect || 1), c.rand);
34628
34886
  G2.tex.needsUpdate = true;
@@ -34767,6 +35025,13 @@ var GameVehiclesThree = (() => {
34767
35025
  }
34768
35026
  const seatGeo = [];
34769
35027
  const d = dir.clone().normalize();
35028
+ if (msh.parent && msh.parent !== c.vehicle) {
35029
+ c.root.updateMatrixWorld(true);
35030
+ const toVehicle = new THREE.Matrix4().copy(c.vehicle.matrixWorld).invert().multiply(msh.parent.matrixWorld);
35031
+ cubesAt.forEach(function(q) {
35032
+ q.applyMatrix4(toVehicle);
35033
+ });
35034
+ }
34770
35035
  cubesAt.forEach(function(p, i) {
34771
35036
  if (i % 3 === 0 && c.seatTops.length && !c.rollState && !c.flipped) {
34772
35037
  let best = null;
@@ -34967,6 +35232,7 @@ var GameVehiclesThree = (() => {
34967
35232
  polygonOffsetUnits: -2
34968
35233
  });
34969
35234
  const _ray = new THREE.Raycaster();
35235
+ _ray.layers.enableAll();
34970
35236
  function findSurface(c, p, dir) {
34971
35237
  c.root.updateMatrixWorld(true);
34972
35238
  const Mw = c.vehicle.matrixWorld;
@@ -35048,6 +35314,7 @@ var GameVehiclesThree = (() => {
35048
35314
  const v0 = new V3();
35049
35315
  const w = new V3();
35050
35316
  const sc = new V3();
35317
+ const local = new V3();
35051
35318
  const cab = m.cabin && !c.flipped && !c.rollState ? m.cabin : null;
35052
35319
  c.deform.forEach(function(msh) {
35053
35320
  if (!msh.parent || !msh.visible) return;
@@ -35062,7 +35329,15 @@ var GameVehiclesThree = (() => {
35062
35329
  irel.copy(rel).invert();
35063
35330
  if (!g.userData.p0) g.userData.p0 = new Float32Array(pos.array);
35064
35331
  const p0 = g.userData.p0;
35332
+ local.copy(p).applyMatrix4(irel);
35333
+ const reach = H.R / Math.max(1e-6, Math.min(sc.x, sc.y, sc.z));
35334
+ const reach2 = reach * reach;
35335
+ const arr = pos.array;
35065
35336
  for (let i = 0; i < pos.count; i++) {
35337
+ const dx = arr[3 * i] - local.x;
35338
+ const dy = arr[3 * i + 1] - local.y;
35339
+ const dz = arr[3 * i + 2] - local.z;
35340
+ if (dx * dx + dy * dy + dz * dz >= reach2) continue;
35066
35341
  v.fromBufferAttribute(pos, i).applyMatrix4(rel);
35067
35342
  const a = dentField(v, H);
35068
35343
  if (a <= 0) continue;
@@ -35078,10 +35353,15 @@ var GameVehiclesThree = (() => {
35078
35353
  }
35079
35354
  if (changed) {
35080
35355
  pos.needsUpdate = true;
35081
- if (g.userData.crease) crease(g, 42);
35356
+ if (g.userData.crease && c.smoothLater) c.smoothLater(msh);
35357
+ else if (g.userData.crease) crease(g, 42);
35082
35358
  else g.computeVertexNormals();
35083
- g.computeBoundingSphere();
35084
35359
  g.computeBoundingBox();
35360
+ const sphere2 = g.boundingSphere ?? (g.boundingSphere = new THREE.Sphere());
35361
+ g.boundingBox.getCenter(sphere2.center);
35362
+ let far2 = 0;
35363
+ for (let i = 0; i < pos.count; i++) far2 = Math.max(far2, sphere2.center.distanceToSquared(v.fromBufferAttribute(pos, i)));
35364
+ sphere2.radius = Math.sqrt(far2);
35085
35365
  }
35086
35366
  });
35087
35367
  const dset = new Set(c.deform);
@@ -35165,13 +35445,14 @@ var GameVehiclesThree = (() => {
35165
35445
  kit.impact = impact;
35166
35446
  kit.partEffects = {};
35167
35447
  const _pr = new THREE.Raycaster();
35448
+ _pr.layers.enableAll();
35168
35449
  function probe(c, o, d, far) {
35169
35450
  c.root.updateMatrixWorld(true);
35170
35451
  const Mw = c.vehicle.matrixWorld;
35171
35452
  const inv = new THREE.Matrix4().copy(Mw).invert();
35172
35453
  _pr.set(o.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
35173
35454
  _pr.far = far || 10;
35174
- const hits = _pr.intersectObject(c.vehicle, true);
35455
+ const hits = raycastIndexed(THREE, _pr, [c.vehicle], true);
35175
35456
  for (let i = 0; i < hits.length; i++) {
35176
35457
  const h = hits[i];
35177
35458
  const ob = h.object;
@@ -36525,6 +36806,7 @@ var GameVehiclesThree = (() => {
36525
36806
  return hole;
36526
36807
  }
36527
36808
  const _zr = new THREE.Raycaster();
36809
+ _zr.layers.enableAll();
36528
36810
  const PROXY_MIN_TRIANGLES = 500;
36529
36811
  const STROKE_REACH = 0.6;
36530
36812
  const _pv = new V3();
@@ -36759,6 +37041,7 @@ var GameVehiclesThree = (() => {
36759
37041
  return out;
36760
37042
  }
36761
37043
  const _rt = new THREE.Raycaster();
37044
+ _rt.layers.enableAll();
36762
37045
  function scratchPath(c, s, len, ang, cands, B) {
36763
37046
  const n = s.n.clone().normalize();
36764
37047
  const t = Math.abs(n.y) < 0.9 ? new V3().crossVectors(new V3(0, 1, 0), n) : new V3().crossVectors(new V3(1, 0, 0), n);
@@ -40018,6 +40301,8 @@ var GameVehiclesThree = (() => {
40018
40301
  V3: THREE.Vector3,
40019
40302
  PI: Math.PI,
40020
40303
  document: doc,
40304
+ particleMaterials: opts.particleMaterials,
40305
+ effects: opts.effects,
40021
40306
  strings,
40022
40307
  t,
40023
40308
  matchers: createMatchers(strings),
@@ -40273,7 +40558,9 @@ var GameVehiclesThree = (() => {
40273
40558
  assignDeep(strings, resolveStrings(opts.locale));
40274
40559
  return kit;
40275
40560
  };
40276
- const { THREE: _three, lod: _lod, locale: _locale, document: _document, plugins, ...config } = opts;
40561
+ const { THREE: _three, lod: _lod, locale: _locale, document: _document, particleMaterials: _particleMaterials, effects: _effects, plugins, ...config } = opts;
40562
+ void _particleMaterials;
40563
+ void _effects;
40277
40564
  void _three;
40278
40565
  void _lod;
40279
40566
  void _locale;
@@ -40367,6 +40654,14 @@ var GameVehiclesThree = (() => {
40367
40654
  center: [b.center[0], b.center[1] - b.size[1] / 2 + Math.min(0.14, b.size[1]) / 2 + 0.04, b.center[2]],
40368
40655
  size: [b.size[0] * 0.5, Math.min(0.14, b.size[1]), b.size[2] * 0.95]
40369
40656
  });
40657
+ function paneBox(c, pane, inverse) {
40658
+ pane.geometry.computeBoundingBox();
40659
+ const box2 = pane.geometry.boundingBox.clone().applyMatrix4(pane.matrixWorld);
40660
+ box2.applyMatrix4(inverse);
40661
+ const center = [(box2.min.x + box2.max.x) / 2, (box2.min.y + box2.max.y) / 2, (box2.min.z + box2.max.z) / 2];
40662
+ const size = [box2.max.x - box2.min.x, box2.max.y - box2.min.y, box2.max.z - box2.min.z];
40663
+ return { center, size };
40664
+ }
40370
40665
  function buildVehicleModel(kit, c, options = {}) {
40371
40666
  const kind = kindOf(c);
40372
40667
  const m = c.meta;
@@ -40507,6 +40802,16 @@ var GameVehiclesThree = (() => {
40507
40802
  tags: [role, ...tags]
40508
40803
  });
40509
40804
  }
40805
+ const claimed = new Set(zones.filter((z) => roleOfZone(z) === "glass").flatMap((z) => z.glass ?? []));
40806
+ c.root.updateMatrixWorld(true);
40807
+ const inverse = c.vehicle.matrixWorld.clone().invert();
40808
+ for (const pane of c.glass ?? []) {
40809
+ if (claimed.has(pane)) continue;
40810
+ const G2 = pane.userData.glass;
40811
+ const bb = paneBox(c, pane, inverse);
40812
+ const laminated = G2?.type === "laminated" || m.bullet;
40813
+ add2({ id: `pane_${G2?.key ?? "glass"}`, role: "glass", name: String(G2?.label ?? G2?.key ?? "glass"), material: "glass", box: bb, mass: 10 * areaOf(bb.size), hp: laminated ? 4e3 : 450, panes: [pane], visual: false, tags: ["glass"] });
40814
+ }
40510
40815
  if (!hubZone) {
40511
40816
  const h = Math.max(0.15, belt - clearance);
40512
40817
  add2({
@@ -40840,6 +41145,76 @@ var GameVehiclesThree = (() => {
40840
41145
  void has;
40841
41146
  return { parts, joints, hub: hubId, hitch };
40842
41147
  }
41148
+ var HOLE_DECAL_SCALE = 2.4;
41149
+ var DOOR_DEPTH = 0.07;
41150
+ var DOOR_EDGE_PIECES = 16;
41151
+ var DOOR_CARD_COLOR = "#3b3e42";
41152
+ var DOOR_LINER_COLOR = "#5a5e64";
41153
+ var DOOR_LINER_DARK = 0.15;
41154
+ var DOOR_STRIP_THIN = 0.05;
41155
+ var DOOR_STRIP_NEAR = 0.04;
41156
+ var DOOR_STRIP_OVER = 0.3;
41157
+ var DOOR_TOP_OVER_PANE = 0.03;
41158
+ var DOOR_STRIP_SUB = 0.02;
41159
+ var HOLE_TEXTURE_SIZE = 64;
41160
+ var DOOR_HANDLE_INSET = 0.12;
41161
+ var DOOR_HANDLE_MIN = 0.07;
41162
+ var DOOR_HANDLE_MAX = 0.24;
41163
+ var DOOR_HANDLE_THIN = 0.07;
41164
+ var DOOR_HANDLE_OUTSIDE = 0.1;
41165
+ var PANE_SEAT_OUT = 0.05;
41166
+ var PANE_SEAT_IN = -0.05;
41167
+ var PANE_SEAT_REACH = 0.14;
41168
+ var PANE_SEAT_SAMPLES = 24;
41169
+ var PANE_SEAT_LIFT = 4e-3;
41170
+ var PANE_FRAME_NEAR = 0.07;
41171
+ var DRAWN_MIRROR_SIZE = 0.3;
41172
+ var DRAWN_MIRROR_REACH = 0.4;
41173
+ var DRAWN_MIRROR_MOUNT = 0.2;
41174
+ var MIRROR_GLASS_FIT = 0.76;
41175
+ var MIRROR_GLASS_THIN = 0.04;
41176
+ var MIRROR_GLASS_MIN = 0.05;
41177
+ var MIRROR_GLASS_COLOR = "#59636a";
41178
+ var HOLE_RIM = { steel: "#b7bcc0", aluminium: "#d3d6d8", plastic: "#d9d6cc", rubber: "#141414", glass: "#eef3f5" };
41179
+ var holeMaterials = /* @__PURE__ */ new WeakMap();
41180
+ var holeDisc = null;
41181
+ function holeDiscGeometry(THREE) {
41182
+ holeDisc = holeDisc ?? new THREE.CircleGeometry(1, 12);
41183
+ return holeDisc;
41184
+ }
41185
+ function holeMaterial(kit, material) {
41186
+ const key = kit;
41187
+ const cache2 = holeMaterials.get(key) ?? /* @__PURE__ */ new Map();
41188
+ holeMaterials.set(key, cache2);
41189
+ const known = cache2.get(material);
41190
+ if (known) return known;
41191
+ const I = kit._;
41192
+ const THREE = kit.THREE;
41193
+ const canvas = kit.document?.createElement?.("canvas");
41194
+ const x = canvas?.getContext?.("2d");
41195
+ let made = I.MATERIALS.dark;
41196
+ if (x) {
41197
+ canvas.width = canvas.height = HOLE_TEXTURE_SIZE;
41198
+ const mid = HOLE_TEXTURE_SIZE / 2;
41199
+ x.fillStyle = HOLE_RIM[material] ?? HOLE_RIM.steel;
41200
+ x.beginPath();
41201
+ for (let i = 0; i < 18; i++) {
41202
+ const a = i / 18 * Math.PI * 2;
41203
+ const r = mid * (i % 2 ? 0.98 : 0.76);
41204
+ x.lineTo(mid + Math.cos(a) * r, mid + Math.sin(a) * r);
41205
+ }
41206
+ x.closePath();
41207
+ x.fill();
41208
+ x.fillStyle = "#050505";
41209
+ x.beginPath();
41210
+ x.arc(mid, mid, mid * 0.42, 0, Math.PI * 2);
41211
+ x.fill();
41212
+ const map = new THREE.CanvasTexture(canvas);
41213
+ made = new THREE.MeshStandardMaterial({ map, transparent: true, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -2, roughness: 0.55, metalness: material === "steel" || material === "aluminium" ? 0.6 : 0 });
41214
+ }
41215
+ cache2.set(material, made);
41216
+ return made;
41217
+ }
40843
41218
  var VehicleVisuals = class {
40844
41219
  constructor(kit, c, model) {
40845
41220
  __publicField(this, "kit");
@@ -40853,11 +41228,17 @@ var GameVehiclesThree = (() => {
40853
41228
  __publicField(this, "gone", /* @__PURE__ */ new Set());
40854
41229
  __publicField(this, "syncPlates", /* @__PURE__ */ new Map());
40855
41230
  __publicField(this, "skins", /* @__PURE__ */ new Map());
41231
+ __publicField(this, "doorTops", /* @__PURE__ */ new Map());
40856
41232
  __publicField(this, "lightsOn", true);
40857
41233
  __publicField(this, "warming");
40858
41234
  __publicField(this, "tornThisFrame", false);
41235
+ __publicField(this, "queued", false);
41236
+ __publicField(this, "holdArmed", false);
41237
+ __publicField(this, "holdFrame", -1);
40859
41238
  __publicField(this, "smoothing", /* @__PURE__ */ new Map());
40860
41239
  __publicField(this, "hood");
41240
+ ;
41241
+ c.smoothLater = (mesh2) => this.smoothLater(mesh2);
40861
41242
  this.kit = kit;
40862
41243
  this.c = c;
40863
41244
  this.model = model;
@@ -40866,6 +41247,8 @@ var GameVehiclesThree = (() => {
40866
41247
  for (const p of model.parts) this.byId.set(p.id, p);
40867
41248
  c.root.updateMatrixWorld(true);
40868
41249
  this.hood = this.nameHood();
41250
+ this.seatPanes();
41251
+ this.seatDoorHandles();
40869
41252
  this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
40870
41253
  if (!this.warming.length) this.warming = null;
40871
41254
  for (const p of model.parts) {
@@ -40886,6 +41269,7 @@ var GameVehiclesThree = (() => {
40886
41269
  const id = z ? z.id : this.model.parts.find((p) => p.role === "hood")?.id;
40887
41270
  if (!id) return null;
40888
41271
  c.hood.piv.name = id;
41272
+ c.hood.piv.userData.plateOf = id;
40889
41273
  return { id, open: c.hood.open, sign: c.hood.sign || 1 };
40890
41274
  }
40891
41275
  mirrorObjects(p) {
@@ -40973,6 +41357,7 @@ var GameVehiclesThree = (() => {
40973
41357
  recess.userData.noFollow = true;
40974
41358
  const piv = new THREE.Group();
40975
41359
  piv.name = p.id;
41360
+ piv.userData.plateOf = p.id;
40976
41361
  piv.position.set(hx, cy, sk + s * 0.016);
40977
41362
  piv.userData.rigid = true;
40978
41363
  this.c.vehicle.add(piv);
@@ -40989,6 +41374,7 @@ var GameVehiclesThree = (() => {
40989
41374
  recess.userData.noFollow = true;
40990
41375
  const piv = new THREE.Group();
40991
41376
  piv.name = p.id;
41377
+ piv.userData.plateOf = p.id;
40992
41378
  piv.position.set(sk - 0.016, B.y1 - 0.05, 0);
40993
41379
  piv.userData.rigid = true;
40994
41380
  this.c.vehicle.add(piv);
@@ -41052,10 +41438,19 @@ var GameVehiclesThree = (() => {
41052
41438
  try {
41053
41439
  if (p.role === "door") {
41054
41440
  const dirty = /* @__PURE__ */ new Set();
41055
- const rect = [B.x0 + 0.03, B.x1 - 0.03, B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1), B.y1 - 0.02];
41056
- const pcs = I.cutPanel(this.c, z, rect, true, dirty).map((x) => x.mesh).filter((m) => !!m);
41441
+ const glassBox = new this.THREE.Box3();
41442
+ for (const g of I.windowsOver(this.c, z, inv)) glassBox.union(I.localBox(this.c, g, inv));
41443
+ const top = glassBox.isEmpty() ? B.y1 - 0.02 : Math.max(B.y1 - 0.02, glassBox.min.y + DOOR_TOP_OVER_PANE);
41444
+ this.doorTops.set(id, top);
41445
+ const rect = [B.x0 + 0.03, B.x1 - 0.03, B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1), top];
41446
+ const pcs = I.cutPanel(this.c, z, rect, true, dirty, { anyN: true }).map((x) => x.mesh).filter((m) => !!m);
41057
41447
  this.mend(dirty);
41058
- if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv), ...I.windowsOver(this.c, z, inv)];
41448
+ if (pcs.length) {
41449
+ const panes = I.windowsOver(this.c, z, inv);
41450
+ const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
41451
+ const mirrors = this.mirrorsOn(z);
41452
+ objs = [...pcs, ...this.doorCards(pcs, z), ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors];
41453
+ }
41059
41454
  } else {
41060
41455
  const pcs = this.cutZone(z);
41061
41456
  if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv)];
@@ -41073,10 +41468,124 @@ var GameVehiclesThree = (() => {
41073
41468
  const di = deform.indexOf(o);
41074
41469
  if (di >= 0) deform.splice(di, 1);
41075
41470
  }
41471
+ if (p.role === "door" && objs.length > 0) {
41472
+ this.doorEdges(piv, z, this.doorTops.get(id) ?? B.y1 - 0.02);
41473
+ this.liftDoorTrim(objs, z);
41474
+ this.cutStripsAcross(piv, z, inv);
41475
+ }
41076
41476
  piv.quaternion.copy(q);
41077
41477
  this.c.root.updateMatrixWorld(true);
41078
41478
  return objs.length > 0;
41079
41479
  }
41480
+ /** A door has edges: a band that follows the skin all round closes it to its card and hides where the cut followed the triangles of the shell; the same band, in the same paint as on a real car, lines the opening. */
41481
+ doorEdges(piv, z, y1) {
41482
+ const THREE = this.THREE;
41483
+ const B = z.box;
41484
+ const side2 = z.side < 0 ? -1 : 1;
41485
+ const flat = piv.position.z - side2 * 0.016;
41486
+ const x0 = B.x0 + 0.03;
41487
+ const x1 = B.x1 - 0.03;
41488
+ const y0 = B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1);
41489
+ const loop = [];
41490
+ const run = (ax, ay, bx, by) => {
41491
+ for (let i = 0; i < DOOR_EDGE_PIECES; i++) loop.push([ax + (bx - ax) * i / DOOR_EDGE_PIECES, ay + (by - ay) * i / DOOR_EDGE_PIECES]);
41492
+ };
41493
+ run(x0, y0, x1, y0);
41494
+ run(x1, y0, x1, y1);
41495
+ run(x1, y1, x0, y1);
41496
+ run(x0, y1, x0, y0);
41497
+ const heights = loop.map(([x, y]) => this.surface("z", side2, B, [[x, y]]) ?? flat);
41498
+ const band = (lift) => {
41499
+ const position = [];
41500
+ const index = [];
41501
+ loop.forEach(([x, y], i) => {
41502
+ position.push(x, y, heights[i] + side2 * lift, x, y, heights[i] + side2 * (lift - DOOR_DEPTH));
41503
+ const a = i * 2;
41504
+ const b = (i + 1) % loop.length * 2;
41505
+ index.push(a, a + 1, b, b, a + 1, b + 1);
41506
+ });
41507
+ const g = new THREE.BufferGeometry();
41508
+ g.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
41509
+ g.setIndex(index);
41510
+ g.computeVertexNormals();
41511
+ return g;
41512
+ };
41513
+ const paint = this.paint().clone();
41514
+ paint.side = THREE.DoubleSide;
41515
+ const edges = new THREE.Mesh(band(2e-3), paint);
41516
+ const lining = new THREE.Mesh(band(2e-3), paint);
41517
+ for (const m of [edges, lining]) {
41518
+ m.userData.noFollow = true;
41519
+ m.castShadow = false;
41520
+ this.c.vehicle.add(m);
41521
+ }
41522
+ piv.attach(edges);
41523
+ }
41524
+ /** A front door carries the mirror drawn at its corner. */
41525
+ mirrorsOn(z) {
41526
+ if (!z.mirror) return [];
41527
+ const own = (z.mirrors ?? []).filter((o) => o.parent);
41528
+ if (own.length) return own;
41529
+ const part = this.model.parts.find((q) => q.role === "mirror" && Math.sign(q.box.center[2]) === Math.sign(z.side));
41530
+ return part ? (this.extra.get(part.id) ?? []).filter((o) => o.parent) : [];
41531
+ }
41532
+ /** The panel drawn inside the door is lit like the rest of a cabin, not a black slab from end to end. */
41533
+ liftDoorTrim(objs, z) {
41534
+ const card = this.I.makeMaterial(DOOR_LINER_COLOR, { roughness: 0.85 });
41535
+ const inv = this.I.worldInverseOf(this.c);
41536
+ for (const o of objs) {
41537
+ if (!o.isMesh || o.userData.glass) continue;
41538
+ const box2 = this.I.localBox(this.c, o, inv);
41539
+ const size = box2.getSize(new this.THREE.Vector3());
41540
+ if (size.x > (z.box.x1 - z.box.x0) * 0.4 && size.y > (z.box.y1 - z.box.y0) * 0.3 && size.z < DOOR_DEPTH * 1.5 && o.material?.color?.getHSL({ h: 0, s: 0, l: 0 }).l < DOOR_LINER_DARK) o.material = card;
41541
+ }
41542
+ }
41543
+ /** A long strip drawn along the side of the car (the chrome of the belt): the part over the door goes with the door, and nothing stays across the opening. */
41544
+ cutStripsAcross(piv, z, inv) {
41545
+ const THREE = this.THREE;
41546
+ const B = z.box;
41547
+ const x0 = B.x0 + 0.03;
41548
+ const x1 = B.x1 - 0.03;
41549
+ const y0 = B.y0 - DOOR_STRIP_NEAR;
41550
+ const y1 = B.y1 + DOOR_STRIP_NEAR;
41551
+ const deform = this.c.deform ?? [];
41552
+ for (const o of [...this.c.vehicle.children]) {
41553
+ if (!o.isMesh || o === piv || deform.includes(o) || o.userData.glass || o.userData.decal || !o.visible) continue;
41554
+ const box2 = this.I.localBox(this.c, o, inv);
41555
+ const size = box2.getSize(new THREE.Vector3());
41556
+ const mid = box2.getCenter(new THREE.Vector3());
41557
+ if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_THIN || Math.min(box2.max.x, x1) - Math.max(box2.min.x, x0) < (x1 - x0) * DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
41558
+ const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
41559
+ const v = new THREE.Vector3();
41560
+ const sdf = (x, y, zz) => {
41561
+ v.set(x, y, zz).applyMatrix4(toVehicle);
41562
+ return Math.max(x0 - v.x, v.x - x1);
41563
+ };
41564
+ this.kit.cutMesh(o, [{ sdf }], 42, { sub: DOOR_STRIP_SUB });
41565
+ const from = Math.max(box2.min.x, x0);
41566
+ const to = Math.min(box2.max.x, x1);
41567
+ const piece = this.box(to - from, size.y, size.z, o.material, (from + to) / 2, mid.y, mid.z);
41568
+ piece.userData.noFollow = true;
41569
+ piv.attach(piece);
41570
+ }
41571
+ }
41572
+ /** The inside of a door: its skin brought in by the depth of the door, in the trim's dark plastic, so the open door is a door and not one sheet of paint. */
41573
+ doorCards(skins, z) {
41574
+ const cardMaterial = this.I.makeMaterial(DOOR_CARD_COLOR, { roughness: 0.8 });
41575
+ return skins.map((skin) => {
41576
+ const card = new this.THREE.Mesh(skin.geometry, cardMaterial);
41577
+ card.position.copy(skin.position);
41578
+ card.quaternion.copy(skin.quaternion);
41579
+ card.scale.copy(skin.scale);
41580
+ const side2 = z.side < 0 ? -1 : 1;
41581
+ const inward = -side2 * DOOR_DEPTH;
41582
+ card.position.z += inward;
41583
+ card.userData.noFollow = true;
41584
+ card.castShadow = false;
41585
+ this.c.vehicle.add(card);
41586
+ return card;
41587
+ });
41588
+ }
41080
41589
  mend(dirty) {
41081
41590
  dirty.forEach((msh) => {
41082
41591
  const g = msh.geometry;
@@ -41086,7 +41595,191 @@ var GameVehiclesThree = (() => {
41086
41595
  g.computeBoundingBox();
41087
41596
  });
41088
41597
  }
41598
+ /**
41599
+ * A windshield or rear window is drawn as a flat quad, and the body under its edge is curved: the pane and its frame stand off the shell or rise past the roof. Each one
41600
+ * comes to the shell along its own normal, by how far the shell is just outside each of its vertices.
41601
+ */
41602
+ seatPanes() {
41603
+ const THREE = this.THREE;
41604
+ const c = this.c;
41605
+ c.root.updateMatrixWorld(true);
41606
+ const shell = c.deform.filter((o) => o.isMesh && !o.userData.glass && c.paints?.has(o.material));
41607
+ if (!shell.length) return;
41608
+ const ray = new THREE.Raycaster();
41609
+ ray.layers.enableAll();
41610
+ for (const pane of c.glass) {
41611
+ if (pane.userData.glass?.outline) continue;
41612
+ const seat = this.paneSeat(pane, shell, ray);
41613
+ if (!seat) continue;
41614
+ const near = c.vehicle.children.filter((o) => o.isMesh && o !== pane && !o.userData.glass && !shell.includes(o) && this.onPane(o, pane, seat));
41615
+ for (const m of [pane, ...near]) this.movePane(m, seat);
41616
+ const past = this.shellPast(pane, shell, ray, seat.normal);
41617
+ if (past > 0) for (const m of [pane, ...near]) this.movePane(m, { normal: seat.normal, samples: [{ at: seat.samples[0].at, by: past }] });
41618
+ }
41619
+ }
41620
+ /** The outward normal of a pane and how far the shell is at each sampled point (world). */
41621
+ paneSeat(pane, shell, ray) {
41622
+ const THREE = this.THREE;
41623
+ const pos = pane.geometry.attributes.position;
41624
+ const box2 = new THREE.Box3().setFromObject(pane);
41625
+ const centre = box2.getCenter(new THREE.Vector3());
41626
+ const vehicleCentre = new THREE.Box3().setFromObject(this.c.root).getCenter(new THREE.Vector3());
41627
+ const tri = new THREE.Triangle();
41628
+ const normal = new THREE.Vector3();
41629
+ let largest = 0;
41630
+ const idx = pane.geometry.index;
41631
+ const count2 = idx ? idx.count : pos.count;
41632
+ for (let f = 0; f + 2 < count2; f += 3) {
41633
+ tri.a.fromBufferAttribute(pos, idx ? idx.getX(f) : f);
41634
+ tri.b.fromBufferAttribute(pos, idx ? idx.getX(f + 1) : f + 1);
41635
+ tri.c.fromBufferAttribute(pos, idx ? idx.getX(f + 2) : f + 2);
41636
+ if (tri.getArea() > largest) {
41637
+ largest = tri.getArea();
41638
+ tri.getNormal(normal);
41639
+ }
41640
+ }
41641
+ normal.transformDirection(pane.matrixWorld);
41642
+ if (normal.dot(centre.clone().sub(vehicleCentre)) < 0) normal.negate();
41643
+ const samples = [];
41644
+ const seen = /* @__PURE__ */ new Set();
41645
+ const v = new THREE.Vector3();
41646
+ const step = Math.max(1, Math.floor(pos.count / PANE_SEAT_SAMPLES));
41647
+ for (let i = 0; i < pos.count; i += step) {
41648
+ v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
41649
+ const key = `${Math.round(v.x * 100)},${Math.round(v.y * 100)},${Math.round(v.z * 100)}`;
41650
+ if (seen.has(key)) continue;
41651
+ seen.add(key);
41652
+ const away = v.clone().sub(centre);
41653
+ away.addScaledVector(normal, -away.dot(normal)).normalize();
41654
+ let by = -Infinity;
41655
+ for (const off of [PANE_SEAT_OUT, PANE_SEAT_IN, PANE_SEAT_IN * 3]) {
41656
+ const at = v.clone().addScaledVector(away, off);
41657
+ ray.set(at.addScaledVector(normal, PANE_SEAT_REACH), normal.clone().negate());
41658
+ ray.far = PANE_SEAT_REACH * 2;
41659
+ const hit = raycastIndexed(THREE, ray, shell)[0];
41660
+ if (hit) by = Math.max(by, PANE_SEAT_REACH - hit.distance);
41661
+ }
41662
+ if (by > -Infinity) samples.push({ at: v.clone(), by });
41663
+ }
41664
+ return samples.length >= 3 ? { normal, samples } : null;
41665
+ }
41666
+ /** How far the shell still stands past a seated pane, over its triangles. */
41667
+ shellPast(pane, shell, ray, normal) {
41668
+ const THREE = this.THREE;
41669
+ const pos = pane.geometry.attributes.position;
41670
+ const idx = pane.geometry.index;
41671
+ const count2 = idx ? idx.count : pos.count;
41672
+ const tri = new THREE.Triangle();
41673
+ const step = Math.max(1, Math.floor(count2 / 3 / PANE_SEAT_SAMPLES)) * 3;
41674
+ let past = 0;
41675
+ for (let f = 0; f + 2 < count2; f += step) {
41676
+ tri.a.fromBufferAttribute(pos, idx ? idx.getX(f) : f);
41677
+ tri.b.fromBufferAttribute(pos, idx ? idx.getX(f + 1) : f + 1);
41678
+ tri.c.fromBufferAttribute(pos, idx ? idx.getX(f + 2) : f + 2);
41679
+ const mid = tri.getMidpoint(new THREE.Vector3()).applyMatrix4(pane.matrixWorld);
41680
+ ray.set(mid.addScaledVector(normal, PANE_SEAT_REACH), normal.clone().negate());
41681
+ ray.far = PANE_SEAT_REACH;
41682
+ const hit = raycastIndexed(THREE, ray, shell)[0];
41683
+ if (hit) past = Math.max(past, PANE_SEAT_REACH - hit.distance + PANE_SEAT_LIFT);
41684
+ }
41685
+ return past;
41686
+ }
41687
+ /** A frame or a seal drawn on the pane: small, and close to its plane. */
41688
+ onPane(o, pane, seat) {
41689
+ const THREE = this.THREE;
41690
+ const box2 = new THREE.Box3().setFromObject(o);
41691
+ if (box2.isEmpty() || !new THREE.Box3().setFromObject(pane).expandByScalar(PANE_FRAME_NEAR).containsBox(box2)) return false;
41692
+ const at = box2.getCenter(new THREE.Vector3());
41693
+ return Math.abs(at.clone().sub(seat.samples[0].at).dot(seat.normal)) < PANE_SEAT_REACH;
41694
+ }
41695
+ movePane(mesh2, seat) {
41696
+ const THREE = this.THREE;
41697
+ const g = mesh2.geometry.clone();
41698
+ const pos = g.attributes.position;
41699
+ const inverse = mesh2.matrixWorld.clone().invert();
41700
+ const v = new THREE.Vector3();
41701
+ for (let i = 0; i < pos.count; i++) {
41702
+ v.fromBufferAttribute(pos, i).applyMatrix4(mesh2.matrixWorld);
41703
+ let weight = 0;
41704
+ let by = 0;
41705
+ for (const s of seat.samples) {
41706
+ const w = 1 / (s.at.distanceToSquared(v) + 1e-4);
41707
+ weight += w;
41708
+ by += s.by * w;
41709
+ }
41710
+ v.addScaledVector(seat.normal, by / weight + PANE_SEAT_LIFT).applyMatrix4(inverse);
41711
+ pos.setXYZ(i, v.x, v.y, v.z);
41712
+ }
41713
+ pos.needsUpdate = true;
41714
+ g.computeBoundingBox();
41715
+ g.computeBoundingSphere();
41716
+ mesh2.geometry = g;
41717
+ }
41718
+ /** A handle is at the free edge of its door, the end away from the hinge. */
41719
+ seatDoorHandles() {
41720
+ const THREE = this.THREE;
41721
+ const c = this.c;
41722
+ const inv = this.I.worldInverseOf(c);
41723
+ for (const p of this.model.parts) {
41724
+ if (p.role !== "door") continue;
41725
+ const z = this.zone(p);
41726
+ if (!z) continue;
41727
+ const B = z.box;
41728
+ const free = z.hinge === "rear" ? B.x1 - DOOR_HANDLE_INSET : B.x0 + DOOR_HANDLE_INSET;
41729
+ for (const o of c.vehicle.children) {
41730
+ if (!o.isMesh || o.material !== this.I.MATERIALS.chrome || o.userData.glass) continue;
41731
+ const box2 = this.I.localBox(c, o, inv);
41732
+ const size = box2.getSize(new THREE.Vector3());
41733
+ const at = box2.getCenter(new THREE.Vector3());
41734
+ const isHandle = size.x > DOOR_HANDLE_MIN && size.x < DOOR_HANDLE_MAX && size.y < DOOR_HANDLE_THIN && size.z < DOOR_HANDLE_THIN;
41735
+ if (!isHandle || Math.sign(at.z) !== Math.sign(z.side) || at.x < B.x0 || at.x > B.x1 || at.y < B.y0 || at.y > B.y1) continue;
41736
+ const here = this.surface("z", z.side < 0 ? -1 : 1, B, [[at.x, at.y]]);
41737
+ if (here != null && Math.abs(here - at.z) > DOOR_HANDLE_OUTSIDE) continue;
41738
+ const skin = this.surface("z", z.side < 0 ? -1 : 1, B, [[free, at.y]]);
41739
+ o.position.x += free - at.x;
41740
+ if (skin != null) o.position.z += skin + (z.side < 0 ? -1 : 1) * (size.z / 2) - at.z;
41741
+ }
41742
+ }
41743
+ c.root.updateMatrixWorld(true);
41744
+ }
41745
+ /** The glass of a drawn mirror sits inside its rounded housing, without corners standing past it. */
41746
+ seatMirrorGlass(glass) {
41747
+ const g = glass.geometry.clone();
41748
+ g.computeBoundingBox();
41749
+ const mid = g.boundingBox.getCenter(new this.THREE.Vector3());
41750
+ g.translate(-mid.x, -mid.y, -mid.z);
41751
+ g.scale(MIRROR_GLASS_FIT, MIRROR_GLASS_FIT, MIRROR_GLASS_FIT);
41752
+ g.translate(mid.x, mid.y, mid.z);
41753
+ glass.geometry = g;
41754
+ glass.material = this.I.makeMaterial(MIRROR_GLASS_COLOR, { metalness: 0.95, roughness: 0.12 });
41755
+ }
41756
+ /** The mirror the model already draws around where the part says, if it draws one. */
41757
+ drawnMirror(p) {
41758
+ const THREE = this.THREE;
41759
+ const [cx, cy, cz] = p.box.center;
41760
+ this.c.root.updateMatrixWorld(true);
41761
+ const inv = this.I.worldInverseOf(this.c);
41762
+ const small = this.c.vehicle.children.flatMap((o) => {
41763
+ if (o.userData.glass || o.userData.decal || !(o.isMesh || o.isGroup) || !o.visible || this.c.deform?.includes(o)) return [];
41764
+ const box2 = this.I.localBox(this.c, o, inv);
41765
+ if (box2.isEmpty()) return [];
41766
+ const size = box2.getSize(new THREE.Vector3());
41767
+ return Math.max(size.x, size.y, size.z) < DRAWN_MIRROR_SIZE ? [{ o, at: box2.getCenter(new THREE.Vector3()) }] : [];
41768
+ });
41769
+ const housing = small.filter(({ at }) => Math.hypot(at.x - cx, at.y - cy, at.z - cz) < DRAWN_MIRROR_REACH && Math.abs(at.z) > Math.abs(cz) - DRAWN_MIRROR_REACH / 2);
41770
+ return small.filter(({ at }) => housing.some((h) => h.at.distanceTo(at) < DRAWN_MIRROR_MOUNT)).map(({ o }) => o);
41771
+ }
41089
41772
  makeMirror(p) {
41773
+ const drawn = this.drawnMirror(p);
41774
+ if (drawn.length) {
41775
+ for (const o of drawn) {
41776
+ if (!o.isMesh || o.material !== this.I.MATERIALS.chrome) continue;
41777
+ const size = new this.THREE.Box3().setFromObject(o).getSize(new this.THREE.Vector3());
41778
+ if (size.x < MIRROR_GLASS_THIN && size.y > MIRROR_GLASS_MIN && size.z > MIRROR_GLASS_MIN) this.seatMirrorGlass(o);
41779
+ }
41780
+ this.extra.set(p.id, drawn);
41781
+ return;
41782
+ }
41090
41783
  const [cx, cy, cz] = p.box.center;
41091
41784
  const s = cz < 0 ? -1 : 1;
41092
41785
  const mats = this.I.MATERIALS;
@@ -41127,17 +41820,28 @@ var GameVehiclesThree = (() => {
41127
41820
  }
41128
41821
  // ---- marks -------------------------------------------------------------------------------------------------------------------------------
41129
41822
  /** A bullet hole: a dark disc lying on the surface at `point` (`c.vehicle` coordinates). */
41130
- holeDecal(point, normal, diameter) {
41823
+ holeDecal(point, normal, diameter, part) {
41131
41824
  const THREE = this.THREE;
41132
- const m = new THREE.Mesh(new THREE.CircleGeometry(Math.max(6e-3, diameter / 2), 10), this.I.MATERIALS.dark);
41825
+ const pane = part?.role === "glass" ? this.panesOf(part).find((g) => g.visible) : void 0;
41826
+ if (part?.role === "glass" && (!pane || pane.userData.glass?.broken)) return;
41827
+ const radius = Math.max(6e-3, diameter / 2) * HOLE_DECAL_SCALE;
41828
+ const m = new THREE.Mesh(holeDiscGeometry(THREE), holeMaterial(this.kit, part?.material ?? "steel"));
41829
+ m.scale.set(radius, radius, 1);
41133
41830
  const n = new THREE.Vector3(...normal);
41134
- m.position.set(...point).addScaledVector(n, 4e-3);
41135
- m.lookAt(m.position.clone().add(n));
41831
+ m.position.set(...point).addScaledVector(n, 3e-3);
41832
+ this.c.vehicle.add(m);
41833
+ m.lookAt(this.c.vehicle.localToWorld(m.position.clone().add(n)));
41136
41834
  m.userData.decal = true;
41137
41835
  m.userData.noFollow = true;
41836
+ m.renderOrder = 3;
41138
41837
  m.raycast = () => {
41139
41838
  };
41140
- this.c.vehicle.add(m);
41839
+ const carrier = (part && (this.plates.get(part.id) ?? (part.id === this.hood?.id ? this.c.hood?.piv : void 0))) ?? (pane && pane.parent !== this.c.vehicle ? pane.parent : null);
41840
+ if (carrier) carrier.attach(m);
41841
+ }
41842
+ /** The door or lid a hinge group stands for. */
41843
+ plateIdOf(o) {
41844
+ return o.userData.plateOf;
41141
41845
  }
41142
41846
  spark(point, dir, count2 = 8) {
41143
41847
  const V3 = this.THREE.Vector3;
@@ -41177,10 +41881,8 @@ var GameVehiclesThree = (() => {
41177
41881
  }
41178
41882
  /** Glass: cracks (laminated) or shatters (tempered); returns whether it broke. */
41179
41883
  hitGlass(p, point, dir, force) {
41180
- const z = this.zone(p);
41181
- if (!z) return false;
41182
41884
  const V3 = this.THREE.Vector3;
41183
- const list2 = z.glass.filter((g) => g.parent && g.visible);
41885
+ const list2 = this.panesOf(p).filter((g) => g.parent && g.visible);
41184
41886
  if (!list2.length) return false;
41185
41887
  const pt = new V3(...point);
41186
41888
  let best = list2[0];
@@ -41197,9 +41899,16 @@ var GameVehiclesThree = (() => {
41197
41899
  this.kit.hitGlass(this.c, best, pt, new V3(...dir), force);
41198
41900
  return !!best.userData.glass?.broken;
41199
41901
  }
41902
+ panesOf(p) {
41903
+ return p.panes ?? this.zone(p)?.glass ?? [];
41904
+ }
41200
41905
  glassBreak(p) {
41201
41906
  const z = this.zone(p);
41202
- if (!z) return;
41907
+ if (!z) {
41908
+ for (const g of p.panes ?? []) if (g.parent && g.visible) this.kit.hitGlass(this.c, g, g.position.clone(), new this.THREE.Vector3(0, -1, 0), 0.6);
41909
+ this.gone.add(p.id);
41910
+ return;
41911
+ }
41203
41912
  for (const g of z.glass) if (g.parent && g.visible) this.I.glassBand(this.c, z, g, 4);
41204
41913
  this.gone.add(p.id);
41205
41914
  }
@@ -41227,6 +41936,7 @@ var GameVehiclesThree = (() => {
41227
41936
  smoothLater(msh) {
41228
41937
  this.smoothing.set(msh, this.I.creaseTask(msh.geometry, 42));
41229
41938
  this.tornThisFrame = true;
41939
+ this.holdArmed = true;
41230
41940
  }
41231
41941
  /** Spends at most `budgetMs` finishing the pending normal smoothing. Call once per frame. */
41232
41942
  pump(budgetMs = 2.5) {
@@ -41234,6 +41944,7 @@ var GameVehiclesThree = (() => {
41234
41944
  this.tornThisFrame = false;
41235
41945
  return;
41236
41946
  }
41947
+ if (this.queued) return;
41237
41948
  if (!this.smoothing.size && !this.warming) return;
41238
41949
  const deadline = performance.now() + budgetMs;
41239
41950
  for (const [msh, task] of this.smoothing) {
@@ -41242,6 +41953,34 @@ var GameVehiclesThree = (() => {
41242
41953
  }
41243
41954
  this.warmIndexes(deadline);
41244
41955
  }
41956
+ setQueued(isQueued) {
41957
+ this.queued = isQueued;
41958
+ }
41959
+ /** Units the shared queue may run now: 0 in the frame of the cut (the first ask after it holds the work for that frame, or `pump` closes it when there is no frame). */
41960
+ pendingUnits(frame) {
41961
+ if (this.holdArmed && frame !== void 0) {
41962
+ this.holdArmed = false;
41963
+ this.holdFrame = frame;
41964
+ }
41965
+ if (frame !== void 0 && frame === this.holdFrame) return 0;
41966
+ if (frame === void 0 && this.tornThisFrame) return 0;
41967
+ return this.smoothing.size + (this.warming ? 1 : 0);
41968
+ }
41969
+ /** One slice of the pending work, for the shared queue. */
41970
+ runStep(deadline) {
41971
+ const next = this.smoothing.entries().next();
41972
+ if (!next.done) {
41973
+ const [msh, task] = next.value;
41974
+ if (msh.geometry !== task.geo || task.step(deadline)) this.smoothing.delete(msh);
41975
+ return true;
41976
+ }
41977
+ if (!this.warming) return false;
41978
+ this.warmIndexes(deadline);
41979
+ return true;
41980
+ }
41981
+ finishAll() {
41982
+ while (this.runStep(Infinity)) ;
41983
+ }
41245
41984
  /** Measures the triangles of the heavy body meshes once, a slice per frame, so the first panel torn off finds them ready (see `triIndexOf`). */
41246
41985
  warmIndexes(deadline) {
41247
41986
  if (!this.warming) return;
@@ -41413,6 +42152,7 @@ var GameVehiclesThree = (() => {
41413
42152
  }
41414
42153
  pieceTasks.forEach((task, mesh2) => this.smoothing.set(mesh2, task));
41415
42154
  this.tornThisFrame = true;
42155
+ this.holdArmed = true;
41416
42156
  dirty.forEach((msh) => {
41417
42157
  const g = msh.geometry;
41418
42158
  if (g.userData.crease || !g.index) this.smoothLater(msh);
@@ -41515,6 +42255,7 @@ var GameVehiclesThree = (() => {
41515
42255
  let clock = 0;
41516
42256
  let model;
41517
42257
  let vis = null;
42258
+ let releaseCrease;
41518
42259
  const ps = /* @__PURE__ */ new Map();
41519
42260
  let js = [];
41520
42261
  let leaks = /* @__PURE__ */ new Map();
@@ -41599,7 +42340,9 @@ var GameVehiclesThree = (() => {
41599
42340
  rand = rng(seedBase);
41600
42341
  igniteRand = rng(strHash(`${c.key}|${id}|ignition|${options.seed ?? 0}`));
41601
42342
  steerSign = rand() < 0.5 ? -1 : 1;
42343
+ releaseCrease?.();
41602
42344
  vis = headless ? null : new VehicleVisuals(kit, c, model);
42345
+ releaseCrease = vis && options.creaseQueue ? options.creaseQueue.track(vis) : void 0;
41603
42346
  recomputeGroups();
41604
42347
  };
41605
42348
  const recomputeGroups = () => {
@@ -41910,13 +42653,13 @@ var GameVehiclesThree = (() => {
41910
42653
  sync();
41911
42654
  const M = toFrameMatrix();
41912
42655
  const v = toLocal(h.point);
41913
- const p = locate(v, 0.2);
42656
+ const p = (h.part ? ps.get(h.part) : void 0) ?? locate(v, 0.2);
41914
42657
  if (!p) return;
41915
42658
  const d = p.def;
41916
42659
  const E = h.energy ?? 0;
41917
42660
  const cal = h.caliber ?? 9;
41918
42661
  const dir = dirToLocal(h.direction ?? [0, 0, -1]).normalize();
41919
- const n = normalOf(d, v);
42662
+ const n = h.normal ? dirToLocal(h.normal).normalize() : normalOf(d, v);
41920
42663
  const res = resist(d, p);
41921
42664
  const through = E >= res;
41922
42665
  const cosInc = Math.abs(dir.dot(n));
@@ -41935,7 +42678,7 @@ var GameVehiclesThree = (() => {
41935
42678
  const hp = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), diameter: r2(holeDiam), through };
41936
42679
  p.holes.push(hp);
41937
42680
  out.push(ev({ type: "hole", part: d.id, hole: hp, worldPoint, ...through ? { exitPoint: add3(worldPoint, r3(dirToWorld(dir.clone().multiplyScalar(Math.max(0.02, Math.min(d.box.size[0], d.box.size[1], d.box.size[2])))))) } : {} }));
41938
- vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam);
42681
+ vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam, d);
41939
42682
  startLeak(p, v, cal, out, dir);
41940
42683
  };
41941
42684
  switch (d.material) {
@@ -42236,6 +42979,46 @@ var GameVehiclesThree = (() => {
42236
42979
  for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
42237
42980
  return false;
42238
42981
  };
42982
+ const surfaceRays = new THREE.Raycaster();
42983
+ surfaceRays.layers.enableAll();
42984
+ surfaceRays.camera = new THREE.PerspectiveCamera();
42985
+ let meshParts = null;
42986
+ const paneParts = () => {
42987
+ if (meshParts) return meshParts;
42988
+ meshParts = /* @__PURE__ */ new Map();
42989
+ for (const d of model.parts) {
42990
+ if (d.role !== "glass") continue;
42991
+ const z = kit.zonesOf(c).find((q) => q.id === d.zoneId);
42992
+ for (const g of z?.glass ?? d.panes ?? []) meshParts.set(g, d.id);
42993
+ }
42994
+ return meshParts;
42995
+ };
42996
+ const isDrawnSurface = (o) => {
42997
+ for (let q = o; q && q !== c.vehicle; q = q.parent) if (!q.visible || q.userData.decal || q.userData.ghost) return false;
42998
+ return true;
42999
+ };
43000
+ const partOfSurface = (o, v) => {
43001
+ const pane = paneParts().get(o);
43002
+ if (pane) return ps.get(pane) ?? null;
43003
+ for (let q = o; q && q !== c.vehicle; q = q.parent) {
43004
+ if (vis?.plateIdOf(q)) return ps.get(vis.plateIdOf(q)) ?? null;
43005
+ }
43006
+ return locate(v, 0.08, (p) => p.def.role === "glass");
43007
+ };
43008
+ const surfaceRay = (ray) => {
43009
+ sync();
43010
+ surfaceRays.set(new V3(ray.origin[0], ray.origin[1], ray.origin[2]), new V3(ray.direction[0], ray.direction[1], ray.direction[2]));
43011
+ surfaceRays.far = ray.maxDistance;
43012
+ for (const h of surfaceRays.intersectObject(c.vehicle, true)) {
43013
+ if (!h.face || !isDrawnSurface(h.object)) continue;
43014
+ const p = partOfSurface(h.object, c.vehicle.worldToLocal(h.point.clone()));
43015
+ if (!p || p.lost || ray.ignored?.(p.def.id)) continue;
43016
+ const n = h.face.normal.clone().transformDirection(h.object.matrixWorld);
43017
+ if (n.dot(surfaceRays.ray.direction) > 0) n.negate();
43018
+ return { part: p.def.id, point: [h.point.x, h.point.y, h.point.z], normal: [n.x, n.y, n.z], distance: h.distance, pathLen: 0.02 };
43019
+ }
43020
+ return null;
43021
+ };
42239
43022
  const hit = (h) => {
42240
43023
  stateCache = void 0;
42241
43024
  const out = [];
@@ -42358,9 +43141,14 @@ var GameVehiclesThree = (() => {
42358
43141
  },
42359
43142
  describe,
42360
43143
  hit,
43144
+ surfaceRay,
42361
43145
  update,
42362
43146
  state,
42363
43147
  reset,
43148
+ dispose: () => {
43149
+ releaseCrease?.();
43150
+ releaseCrease = void 0;
43151
+ },
42364
43152
  disassemble,
42365
43153
  extinguish,
42366
43154
  serialize: () => ({
@@ -42396,6 +43184,59 @@ var GameVehiclesThree = (() => {
42396
43184
  root.userData.vehicle = vehicle;
42397
43185
  return { root, vehicle };
42398
43186
  }
43187
+ function createCreaseQueue() {
43188
+ const tracked = [];
43189
+ let cursor = 0;
43190
+ let lastFrame;
43191
+ let attached = false;
43192
+ const deferred = {
43193
+ id: DEFERRED_QUEUE_ID.VEHICLE_CREASE,
43194
+ pending(frame) {
43195
+ lastFrame = frame;
43196
+ let units = 0;
43197
+ for (const work of tracked) units += work.pendingUnits(frame);
43198
+ return units;
43199
+ },
43200
+ run(deadline, clock) {
43201
+ let ran = 0;
43202
+ let skipped = 0;
43203
+ while (tracked.length > 0 && skipped < tracked.length) {
43204
+ if (ran > 0 && clock() >= deadline) break;
43205
+ const work = tracked[cursor % tracked.length];
43206
+ cursor = (cursor + 1) % tracked.length;
43207
+ if (work.pendingUnits(lastFrame) > 0 && work.runStep(deadline)) {
43208
+ ran++;
43209
+ skipped = 0;
43210
+ } else skipped++;
43211
+ }
43212
+ return ran;
43213
+ },
43214
+ flush() {
43215
+ for (const work of tracked) work.finishAll();
43216
+ },
43217
+ attach() {
43218
+ attached = true;
43219
+ for (const work of tracked) work.setQueued(true);
43220
+ },
43221
+ detach() {
43222
+ attached = false;
43223
+ for (const work of tracked) work.setQueued(false);
43224
+ }
43225
+ };
43226
+ return {
43227
+ deferredQueue: () => deferred,
43228
+ track(work) {
43229
+ tracked.push(work);
43230
+ work.setQueued(attached);
43231
+ return () => {
43232
+ const at = tracked.indexOf(work);
43233
+ if (at < 0) return;
43234
+ tracked.splice(at, 1);
43235
+ work.setQueued(false);
43236
+ };
43237
+ }
43238
+ };
43239
+ }
42399
43240
  var CABIN_LEAK = 0.02;
42400
43241
  var HOLE_RATE_PER_AREA = 3e3;
42401
43242
  var CRACK_GAP_PER_METRE = 0.5;
@@ -42514,6 +43355,217 @@ var GameVehiclesThree = (() => {
42514
43355
  };
42515
43356
  }
42516
43357
 
43358
+ // ../game-render-three/dist/index.js
43359
+ var DEFAULT_ANGLE = Math.PI / 3;
43360
+ var EFFECT_STEP_SECONDS = 1 / 50;
43361
+ function isRenderPools(value) {
43362
+ const candidate = value;
43363
+ return typeof candidate === "object" && candidate !== null && typeof candidate.geometries?.acquire === "function" && typeof candidate.materials?.acquire === "function";
43364
+ }
43365
+ var BATCHED_LAYER = 30;
43366
+ var MATRIX_EPSILON = 1e-6;
43367
+ var ATTRIBUTES = ["position", "normal", "uv", "color"];
43368
+ var isShown = (object2, root) => {
43369
+ for (let node = object2; node; node = node.parent) {
43370
+ if (node === root) return true;
43371
+ if (!node.visible) return false;
43372
+ }
43373
+ return false;
43374
+ };
43375
+ var isBatchable = (mesh2) => {
43376
+ const flags = mesh2;
43377
+ if (flags.isInstancedMesh === true || flags.isSkinnedMesh === true || Array.isArray(mesh2.material)) return false;
43378
+ const geometry = mesh2.geometry;
43379
+ return geometry.attributes.position !== void 0 && Object.keys(geometry.morphAttributes).length === 0;
43380
+ };
43381
+ var signatureOf = (mesh2) => {
43382
+ const { attributes } = mesh2.geometry;
43383
+ const present = ATTRIBUTES.filter((name) => attributes[name] !== void 0).map((name) => `${name}${attributes[name].itemSize}`);
43384
+ return `${mesh2.material.uuid}|${present.join(",")}|${mesh2.castShadow ? 1 : 0}${mesh2.receiveShadow ? 1 : 0}`;
43385
+ };
43386
+ var sameMatrix = (a, b) => a.elements.every((value, index) => Math.abs(value - b.elements[index]) < MATRIX_EPSILON);
43387
+ function createStaticBatcher(THREE, root, options = {}) {
43388
+ const include = options.include ?? (() => true);
43389
+ const follow = options.followMeshes === true;
43390
+ root.updateMatrixWorld(true);
43391
+ const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert();
43392
+ const groups = /* @__PURE__ */ new Map();
43393
+ root.traverse((object2) => {
43394
+ const mesh2 = object2;
43395
+ if (!mesh2.isMesh || !isShown(mesh2, root) || !isBatchable(mesh2) || !include(mesh2)) return;
43396
+ const key = signatureOf(mesh2);
43397
+ groups.set(key, [...groups.get(key) ?? [], mesh2]);
43398
+ });
43399
+ const group = new THREE.Group();
43400
+ group.name = "static-batches";
43401
+ const sources = [];
43402
+ const pending = [...groups.values()].filter((meshes) => meshes.length >= 2);
43403
+ const mergeSome = (deadline) => {
43404
+ while (pending.length > 0) {
43405
+ const meshes = pending.shift();
43406
+ if (meshes.every((mesh2) => isShown(mesh2, root))) sources.push(...mergeGroup(THREE, { meshes, inverse: new THREE.Matrix4().copy(root.matrixWorld).invert(), group, follow }));
43407
+ if (performance.now() >= deadline) return;
43408
+ }
43409
+ };
43410
+ if (options.budgetMs === void 0) mergeSome(Number.POSITIVE_INFINITY);
43411
+ root.add(group);
43412
+ const scratch2 = new THREE.Matrix4();
43413
+ let released = 0;
43414
+ const release = (source) => {
43415
+ source.released = true;
43416
+ source.mesh.layers.mask = source.mask;
43417
+ const { index } = source.batch;
43418
+ const first = index.array[source.start];
43419
+ for (let at = source.start; at < source.start + source.count; at++) index.array[at] = first;
43420
+ index.needsUpdate = true;
43421
+ if (--source.batch.live === 0) source.batch.mesh.visible = false;
43422
+ released++;
43423
+ };
43424
+ const hasChanged = (source) => {
43425
+ const { mesh: mesh2 } = source;
43426
+ if (mesh2.geometry !== source.geometry || mesh2.material !== source.material || !isShown(mesh2, root)) return true;
43427
+ if (mesh2.geometry.attributes.position.version !== source.version) return true;
43428
+ if (follow || sameMatrix(mesh2.matrixWorld, source.world)) return false;
43429
+ if (!sameMatrix(scratch2.multiplyMatrices(inverse, mesh2.matrixWorld), source.local)) return true;
43430
+ source.world.copy(mesh2.matrixWorld);
43431
+ return false;
43432
+ };
43433
+ return {
43434
+ group,
43435
+ update() {
43436
+ if (pending.length > 0) mergeSome(performance.now() + (options.budgetMs ?? Number.POSITIVE_INFINITY));
43437
+ const before = released;
43438
+ inverse.copy(root.matrixWorld).invert();
43439
+ for (const source of sources) if (!source.released && hasChanged(source)) release(source);
43440
+ return released - before;
43441
+ },
43442
+ stats: () => ({ batches: group.children.length, batched: sources.filter((source) => !source.released).length, released }),
43443
+ dispose() {
43444
+ for (const source of sources) if (!source.released) source.mesh.layers.mask = source.mask;
43445
+ group.removeFromParent();
43446
+ for (const child of group.children) {
43447
+ ;
43448
+ child.geometry.dispose();
43449
+ child.skeleton?.dispose();
43450
+ }
43451
+ }
43452
+ };
43453
+ }
43454
+ function copyAttribute(name, attribute, target, offset, matrix, normalMatrix2) {
43455
+ const size = attribute.itemSize;
43456
+ const source = attribute.array;
43457
+ const flat = !attribute.isInterleavedBufferAttribute && source.length === attribute.count * size;
43458
+ if (name !== "position" && name !== "normal") {
43459
+ if (flat) target.set(source, offset * size);
43460
+ else for (let at = 0; at < attribute.count; at++) for (let k = 0; k < size; k++) target[(offset + at) * size + k] = attribute.getComponent(at, k);
43461
+ return;
43462
+ }
43463
+ const e = (name === "position" ? matrix : normalMatrix2).elements;
43464
+ const isNormal = name === "normal";
43465
+ for (let at = 0; at < attribute.count; at++) {
43466
+ const x = attribute.getX(at);
43467
+ const y = attribute.getY(at);
43468
+ const z = attribute.getZ(at);
43469
+ const out = (offset + at) * 3;
43470
+ if (isNormal) {
43471
+ const nx = e[0] * x + e[3] * y + e[6] * z;
43472
+ const ny = e[1] * x + e[4] * y + e[7] * z;
43473
+ const nz = e[2] * x + e[5] * y + e[8] * z;
43474
+ const length = Math.hypot(nx, ny, nz) || 1;
43475
+ target[out] = nx / length;
43476
+ target[out + 1] = ny / length;
43477
+ target[out + 2] = nz / length;
43478
+ } else {
43479
+ target[out] = e[0] * x + e[4] * y + e[8] * z + e[12];
43480
+ target[out + 1] = e[1] * x + e[5] * y + e[9] * z + e[13];
43481
+ target[out + 2] = e[2] * x + e[6] * y + e[10] * z + e[14];
43482
+ }
43483
+ }
43484
+ }
43485
+ function skinnedBatch(THREE, merged, material, meshes) {
43486
+ const vertices = merged.attributes.position.count;
43487
+ const skinIndex = new Uint16Array(vertices * 4);
43488
+ const skinWeight = new Float32Array(vertices * 4);
43489
+ let at = 0;
43490
+ meshes.forEach((mesh22, bone) => {
43491
+ for (let k = 0; k < mesh22.geometry.attributes.position.count; k++, at++) {
43492
+ skinIndex[at * 4] = bone;
43493
+ skinWeight[at * 4] = 1;
43494
+ }
43495
+ });
43496
+ merged.setAttribute("skinIndex", new THREE.BufferAttribute(skinIndex, 4));
43497
+ merged.setAttribute("skinWeight", new THREE.BufferAttribute(skinWeight, 4));
43498
+ const mesh2 = new THREE.SkinnedMesh(merged, material);
43499
+ const identities = meshes.map(() => new THREE.Matrix4());
43500
+ mesh2.bind(new THREE.Skeleton(meshes, identities), new THREE.Matrix4());
43501
+ mesh2.frustumCulled = false;
43502
+ return mesh2;
43503
+ }
43504
+ function mergeGroup(THREE, { meshes, inverse: rootInverse, group, follow }) {
43505
+ const identity = new THREE.Matrix4();
43506
+ const inverse = follow ? identity : rootInverse;
43507
+ const first = meshes[0];
43508
+ const names = ATTRIBUTES.filter((name) => first.geometry.attributes[name] !== void 0);
43509
+ const pieces = meshes.map((mesh22) => {
43510
+ const local = new THREE.Matrix4().multiplyMatrices(inverse, mesh22.matrixWorld);
43511
+ const vertices = mesh22.geometry.attributes.position.count;
43512
+ const indexCount = mesh22.geometry.index ? mesh22.geometry.index.count : vertices;
43513
+ return { mesh: mesh22, local, vertices, indexCount };
43514
+ });
43515
+ const totalVertices = pieces.reduce((sum, piece) => sum + piece.vertices, 0);
43516
+ const totalIndices = pieces.reduce((sum, piece) => sum + piece.indexCount, 0);
43517
+ const merged = new THREE.BufferGeometry();
43518
+ const normalMatrix2 = new THREE.Matrix3();
43519
+ for (const name of names) {
43520
+ const size = first.geometry.attributes[name].itemSize;
43521
+ const array = new Float32Array(totalVertices * size);
43522
+ let offset = 0;
43523
+ for (const piece of pieces) {
43524
+ const matrix = follow ? identity : piece.local;
43525
+ copyAttribute(name, piece.mesh.geometry.attributes[name], array, offset, matrix, normalMatrix2.getNormalMatrix(matrix));
43526
+ offset += piece.vertices;
43527
+ }
43528
+ merged.setAttribute(name, new THREE.BufferAttribute(array, size));
43529
+ }
43530
+ const indexArray2 = totalVertices > 65535 ? new Uint32Array(totalIndices) : new Uint16Array(totalIndices);
43531
+ const indexAttribute = new THREE.BufferAttribute(indexArray2, 1);
43532
+ merged.setIndex(indexAttribute);
43533
+ const mesh2 = follow ? skinnedBatch(THREE, merged, first.material, meshes) : new THREE.Mesh(merged, first.material);
43534
+ mesh2.name = `batch:${first.material.name || first.material.type}`;
43535
+ mesh2.castShadow = first.castShadow;
43536
+ mesh2.receiveShadow = first.receiveShadow;
43537
+ mesh2.raycast = () => void 0;
43538
+ merged.computeBoundingBox();
43539
+ merged.computeBoundingSphere();
43540
+ group.add(mesh2);
43541
+ const batch = { mesh: mesh2, live: pieces.length, index: indexAttribute };
43542
+ const sources = [];
43543
+ let vertexOffset = 0;
43544
+ let indexOffset = 0;
43545
+ for (const piece of pieces) {
43546
+ const index = piece.mesh.geometry.index;
43547
+ if (index) for (let at = 0; at < piece.indexCount; at++) indexArray2[indexOffset + at] = index.getX(at) + vertexOffset;
43548
+ else for (let at = 0; at < piece.indexCount; at++) indexArray2[indexOffset + at] = at + vertexOffset;
43549
+ sources.push({
43550
+ mesh: piece.mesh,
43551
+ geometry: piece.mesh.geometry,
43552
+ material: piece.mesh.material,
43553
+ version: piece.mesh.geometry.attributes.position.version,
43554
+ local: piece.local,
43555
+ world: piece.mesh.matrixWorld.clone(),
43556
+ mask: piece.mesh.layers.mask,
43557
+ batch,
43558
+ start: indexOffset,
43559
+ count: piece.indexCount,
43560
+ released: false
43561
+ });
43562
+ piece.mesh.layers.set(BATCHED_LAYER);
43563
+ vertexOffset += piece.vertices;
43564
+ indexOffset += piece.indexCount;
43565
+ }
43566
+ return sources;
43567
+ }
43568
+
42517
43569
  // ../game-vehicles-driving/dist/index.js
42518
43570
  var defaultArcadeSpec = {
42519
43571
  length: 4.3,
@@ -42929,6 +43981,169 @@ var GameVehiclesThree = (() => {
42929
43981
  return driver;
42930
43982
  }
42931
43983
 
43984
+ // src/vehicle-lod.ts
43985
+ var VEHICLE_LOD_DISTANCE = 14;
43986
+ var BATCH_BUDGET_MS = 2;
43987
+ var isShown2 = (object2, top) => {
43988
+ for (let node = object2; node && node !== top; node = node.parent) if (node.visible === false) return false;
43989
+ return true;
43990
+ };
43991
+ function createVehicleLod(kit, context, root, full) {
43992
+ const THREE = kit.THREE;
43993
+ let isUploaded = false;
43994
+ let modelKey = "";
43995
+ const culled = /* @__PURE__ */ new Map();
43996
+ const keyOf = () => full.children.filter((child) => child.name !== "static-batches").map((child) => child.uuid).join();
43997
+ const armUpload = () => {
43998
+ for (const [mesh2, wasCulled] of culled) mesh2.frustumCulled = wasCulled;
43999
+ culled.clear();
44000
+ modelKey = keyOf();
44001
+ isUploaded = false;
44002
+ full.traverse((object2) => {
44003
+ if (!object2.isMesh || culled.has(object2)) return;
44004
+ culled.set(object2, object2.frustumCulled);
44005
+ object2.frustumCulled = false;
44006
+ });
44007
+ const probe = [...culled.keys()][0];
44008
+ if (!probe) {
44009
+ isUploaded = true;
44010
+ return;
44011
+ }
44012
+ const before = probe.onAfterRender;
44013
+ probe.onAfterRender = function(...args) {
44014
+ isUploaded = true;
44015
+ probe.onAfterRender = before;
44016
+ before?.apply(this, args);
44017
+ };
44018
+ };
44019
+ armUpload();
44020
+ let lite = null;
44021
+ let isFused = false;
44022
+ let isLocked = false;
44023
+ let batcher = null;
44024
+ let shadow = null;
44025
+ const castsShadow = /* @__PURE__ */ new Set();
44026
+ const shadowProxyOf = (fused) => {
44027
+ const positions = [];
44028
+ const indices = [];
44029
+ for (const mesh2 of fused.children) {
44030
+ if (!mesh2.isMesh || !mesh2.castShadow) continue;
44031
+ const geometry2 = mesh2.geometry.clone().applyMatrix4(mesh2.matrix);
44032
+ const offset = positions.length / 3;
44033
+ const position = geometry2.attributes.position;
44034
+ for (let at = 0; at < position.count; at++) positions.push(position.getX(at), position.getY(at), position.getZ(at));
44035
+ const index = geometry2.index ? Array.from(geometry2.index.array) : Array.from({ length: position.count }, (_, at) => at);
44036
+ for (const vertex of index) indices.push(vertex + offset);
44037
+ geometry2.dispose();
44038
+ }
44039
+ const geometry = new THREE.BufferGeometry();
44040
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
44041
+ geometry.setIndex(indices);
44042
+ geometry.computeBoundingSphere();
44043
+ const proxy = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ colorWrite: false, depthWrite: false }));
44044
+ proxy.name = "vehicle-shadow";
44045
+ markKitOrigin(proxy, { owner: "game-vehicles-three", kind: "vehicle", id: root.name });
44046
+ proxy.castShadow = true;
44047
+ proxy.userData.skipLite = true;
44048
+ proxy.raycast = () => void 0;
44049
+ root.add(proxy);
44050
+ return proxy;
44051
+ };
44052
+ const drawFull = () => {
44053
+ if (!batcher && isLocked) return;
44054
+ if (!batcher) {
44055
+ lite ?? (lite = build());
44056
+ lite.visible = isFused;
44057
+ shadow ?? (shadow = shadowProxyOf(lite));
44058
+ shadow.visible = !isFused;
44059
+ full.traverse((object2) => {
44060
+ if (!object2.isMesh || !object2.castShadow) return;
44061
+ castsShadow.add(object2);
44062
+ object2.castShadow = false;
44063
+ });
44064
+ batcher = createStaticBatcher(THREE, full, { budgetMs: BATCH_BUDGET_MS });
44065
+ batcher.group.traverse((object2) => object2.userData.skipLite = true);
44066
+ markKitOrigin(batcher.group, { owner: "game-vehicles-three", kind: "vehicle", id: root.name });
44067
+ return;
44068
+ }
44069
+ batcher.update();
44070
+ if (!isLocked && batcher.stats().batched === 0 && batcher.stats().released > 0) {
44071
+ batcher.dispose();
44072
+ batcher = null;
44073
+ }
44074
+ };
44075
+ const build = () => {
44076
+ root.updateMatrixWorld(true);
44077
+ const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert();
44078
+ const textured = [];
44079
+ full.traverse((object2) => {
44080
+ if (!object2.isMesh || object2.userData.skipLite || !isShown2(object2, full)) return;
44081
+ const materials = Array.isArray(object2.material) ? object2.material : [object2.material];
44082
+ if (materials.some((material) => material?.map && !/plate/i.test(material.name ?? "")) && !/plate/i.test(object2.name ?? "")) textured.push(object2);
44083
+ });
44084
+ for (const mesh2 of textured) mesh2.userData.skipLite = true;
44085
+ const fusion = fuse(THREE, { ...context, vehicle: full }, inverse, null, primaryPaint(context), kit.lodHiddenMaterials());
44086
+ for (const mesh2 of textured) delete mesh2.userData.skipLite;
44087
+ const group = new THREE.Group();
44088
+ group.name = "vehicle-lite";
44089
+ markKitOrigin(group, { owner: "game-vehicles-three", kind: "vehicle", id: root.name });
44090
+ for (const mesh2 of liteMeshes(THREE, fusion, liteMaterials(THREE, kit.studioEnvironment()))) group.add(mesh2);
44091
+ for (const mesh2 of textured) {
44092
+ const copy = new THREE.Mesh(mesh2.geometry, mesh2.material);
44093
+ copy.matrixAutoUpdate = false;
44094
+ copy.matrix.multiplyMatrices(inverse, mesh2.matrixWorld);
44095
+ copy.castShadow = mesh2.castShadow;
44096
+ copy.receiveShadow = mesh2.receiveShadow;
44097
+ group.add(copy);
44098
+ }
44099
+ root.add(group);
44100
+ return group;
44101
+ };
44102
+ const show = (shouldFuse) => {
44103
+ if (shouldFuse === isFused) return;
44104
+ isFused = shouldFuse;
44105
+ full.visible = !shouldFuse;
44106
+ if (lite) lite.visible = shouldFuse;
44107
+ if (shadow) shadow.visible = !shouldFuse;
44108
+ };
44109
+ const api = {
44110
+ update(distance, touched, posed = false) {
44111
+ isLocked || (isLocked = touched);
44112
+ if (keyOf() !== modelKey) {
44113
+ api.dispose();
44114
+ armUpload();
44115
+ }
44116
+ if (!isUploaded) return show(false);
44117
+ if (culled.size > 0) {
44118
+ for (const [mesh2, wasCulled] of culled) mesh2.frustumCulled = wasCulled;
44119
+ culled.clear();
44120
+ }
44121
+ const shouldFuse = !isLocked && !posed && distance > (isFused ? VEHICLE_LOD_DISTANCE * 0.9 : VEHICLE_LOD_DISTANCE);
44122
+ if (shouldFuse && !lite) lite = build();
44123
+ show(shouldFuse);
44124
+ if (!shouldFuse) drawFull();
44125
+ },
44126
+ get fused() {
44127
+ return isFused;
44128
+ },
44129
+ dispose() {
44130
+ show(false);
44131
+ batcher?.dispose();
44132
+ batcher = null;
44133
+ for (const mesh2 of castsShadow) mesh2.castShadow = true;
44134
+ castsShadow.clear();
44135
+ shadow?.removeFromParent();
44136
+ shadow?.geometry.dispose();
44137
+ shadow = null;
44138
+ if (!lite) return;
44139
+ lite.removeFromParent();
44140
+ lite.traverse((object2) => object2.geometry?.dispose());
44141
+ lite = null;
44142
+ }
44143
+ };
44144
+ return api;
44145
+ }
44146
+
42932
44147
  // src/test-area/text.ts
42933
44148
  var scenarioIds = [
42934
44149
  "vehicles_9mm_windshield",
@@ -43320,7 +44535,7 @@ var GameVehiclesThree = (() => {
43320
44535
  ];
43321
44536
  };
43322
44537
 
43323
- // ../game-audio/dist/index.js
44538
+ // ../game-audio/dist/chunk-DZITVFMJ.js
43324
44539
  var DISTANCE_CURVES = {
43325
44540
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
43326
44541
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -43335,31 +44550,6 @@ var GameVehiclesThree = (() => {
43335
44550
  /** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */
43336
44551
  animals: { model: "linear", range: 70, scale: 0.25, minGain: 5e-3 }
43337
44552
  };
43338
- var mix_profiles_default = [
43339
- {
43340
- id: "game",
43341
- master: 0.5,
43342
- noise: { seconds: 1.5, create: "lazy" },
43343
- sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
43344
- },
43345
- {
43346
- id: "art_scene",
43347
- master: 0.85,
43348
- compressor: { threshold: -12, ratio: 5 },
43349
- reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
43350
- noise: { seconds: 2, create: "eager" },
43351
- sources: [
43352
- "cena:L1308 compressor -12 dB, ratio 5",
43353
- "cena:L1309 master 0.85",
43354
- "cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
43355
- "cena:L1311 wet 0.4",
43356
- "cena:L1312 noise 2 s made with the chain"
43357
- ]
43358
- }
43359
- ];
43360
- var MIX_PROFILES = Object.fromEntries(
43361
- mix_profiles_default.map((profile) => [profile.id, profile])
43362
- );
43363
44553
  var game_sfx_default = {
43364
44554
  sources: ["jogo:L62089-62107"],
43365
44555
  note: "The game's sfx table P: [lowpass cutoff Hz, peak gain, seconds]. Played as a noise burst with the game's distance curve (falloff game_sfx) and a 0.05 s tail. Team names in `team`.",
@@ -43492,6 +44682,31 @@ var GameVehiclesThree = (() => {
43492
44682
  var LIBRARY_SOUNDS = [horn_default, radio_static_default, siren_default];
43493
44683
  var LIBRARY_LOOPS = [engine_saw_default, rotor_blades_default, crowd_rumble_default];
43494
44684
  var LIBRARY_BANK = { id: "library", sounds: LIBRARY_SOUNDS, loops: LIBRARY_LOOPS };
44685
+ var mix_profiles_default = [
44686
+ {
44687
+ id: "game",
44688
+ master: 0.5,
44689
+ noise: { seconds: 1.5, create: "lazy" },
44690
+ sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
44691
+ },
44692
+ {
44693
+ id: "art_scene",
44694
+ master: 0.85,
44695
+ compressor: { threshold: -12, ratio: 5 },
44696
+ reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
44697
+ noise: { seconds: 2, create: "eager" },
44698
+ sources: [
44699
+ "cena:L1308 compressor -12 dB, ratio 5",
44700
+ "cena:L1309 master 0.85",
44701
+ "cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
44702
+ "cena:L1311 wet 0.4",
44703
+ "cena:L1312 noise 2 s made with the chain"
44704
+ ]
44705
+ }
44706
+ ];
44707
+ var MIX_PROFILES = Object.fromEntries(
44708
+ mix_profiles_default.map((profile) => [profile.id, profile])
44709
+ );
43495
44710
  var BENCH_BANK = {
43496
44711
  id: "audio_bench",
43497
44712
  sounds: [
@@ -43538,6 +44753,236 @@ var GameVehiclesThree = (() => {
43538
44753
  ]
43539
44754
  };
43540
44755
 
44756
+ // ../game-audio/dist/index.js
44757
+ var ATTACK = 2e-3;
44758
+ function lengthOf(d, attack) {
44759
+ if (typeof d === "number") return attack + d;
44760
+ if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
44761
+ return { ...d, add: (d.add ?? 0) + attack };
44762
+ }
44763
+ var envelope = (attack) => [
44764
+ [0, 1e-4, "set", "fixed"],
44765
+ [attack, 1, "exp"],
44766
+ ["1d", 1e-4, "exp", "fixed"]
44767
+ ];
44768
+ function sceneNoise(o) {
44769
+ const a = o.a ?? ATTACK;
44770
+ const over = typeof o.d === "number" ? o.d : 0;
44771
+ return {
44772
+ filters: {
44773
+ mode: "serial",
44774
+ bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
44775
+ },
44776
+ duration: lengthOf(o.d, a),
44777
+ gain: o.vol,
44778
+ ...o.delay === void 0 ? {} : { at: o.delay },
44779
+ ...o.wet === void 0 ? {} : { wet: o.wet },
44780
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
44781
+ envelope: envelope(a)
44782
+ };
44783
+ }
44784
+ function sceneTone(o) {
44785
+ return {
44786
+ source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
44787
+ duration: lengthOf(o.d, ATTACK),
44788
+ gain: o.vol,
44789
+ ...o.delay === void 0 ? {} : { at: o.delay },
44790
+ ...o.wet === void 0 ? {} : { wet: o.wet },
44791
+ envelope: envelope(ATTACK)
44792
+ };
44793
+ }
44794
+ function sceneVoice(o) {
44795
+ return {
44796
+ duration: lengthOf(o.d, 0),
44797
+ gain: o.vol,
44798
+ wet: 0.45,
44799
+ source: {
44800
+ kind: "formant_voice",
44801
+ wave: o.wave ?? "sawtooth",
44802
+ frequency: { from: o.f0, to: o.f1, curve: "linear", over: "duration" },
44803
+ vibrato: { rate: [4, 10], depth: 0.08 },
44804
+ formants: [
44805
+ { frequency: o.fA, q: 4 },
44806
+ { frequency: o.fB, q: 6 }
44807
+ ],
44808
+ breath: { frequency: o.fB, q: 1.1, gain: o.noise },
44809
+ mix: 3.2
44810
+ },
44811
+ envelope: [
44812
+ [0, 1e-4, "set", "fixed"],
44813
+ [0.1, 1, "exp"],
44814
+ ["0.6d", 1, "set"],
44815
+ ["1d", 1e-4, "exp", "fixed"]
44816
+ ]
44817
+ };
44818
+ }
44819
+ var TAKES = [
44820
+ { f: 1, t: 1, v: 1 },
44821
+ { f: 1.08, t: 0.9, v: 0.92 },
44822
+ { f: 0.93, t: 1.12, v: 1.05 },
44823
+ { f: 1.03, t: 1.05, v: 0.88 }
44824
+ ];
44825
+ var FOOTSTEP_FALLOFF = { model: "inverse_square", k: 0.03, cutoffK: 0.03, minGain: 4e-3 };
44826
+ var BODY_FALLOFF = { model: "inverse_square", k: 0.012, cutoffK: 0.015, minGain: 4e-3 };
44827
+ var SURFACES = {
44828
+ concrete: { heel: { f: 95, d: 0.05, vol: 0.55 }, roll: { type: "bandpass", f: 2300, q: 1.1, d: 0.05, vol: 0.38 }, grains: { count: 2, f: 4200, q: 2, spread: 0.04, vol: 0.08 } },
44829
+ asphalt: { heel: { f: 90, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 1700, q: 0.9, d: 0.06, vol: 0.36 }, grains: { count: 4, f: 3600, q: 2.5, spread: 0.06, vol: 0.1 } },
44830
+ wood: { heel: { f: 130, d: 0.08, vol: 0.55 }, roll: { type: "lowpass", f: 1300, q: 0.8, d: 0.06, vol: 0.3 }, ring: [{ f: 210, d: 0.1, vol: 0.18 }, { f: 470, d: 0.06, vol: 0.06 }] },
44831
+ tile: { heel: { f: 110, d: 0.04, vol: 0.45 }, roll: { type: "bandpass", f: 3600, q: 2, d: 0.03, vol: 0.42 }, ring: [{ f: 1900, d: 0.03, vol: 0.05 }] },
44832
+ dirt: { heel: { f: 75, d: 0.06, vol: 0.45 }, roll: { type: "lowpass", f: 750, q: 0.7, d: 0.09, vol: 0.4 }, grains: { count: 3, f: 1800, q: 1.5, spread: 0.07, vol: 0.06 } },
44833
+ grass: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "highpass", f: 2600, q: 0.7, d: 0.13, vol: 0.28 }, grains: { count: 4, f: 5200, q: 1.2, spread: 0.1, vol: 0.07 } },
44834
+ gravel: { heel: { f: 80, d: 0.05, vol: 0.4 }, roll: { type: "lowpass", f: 950, q: 0.8, d: 0.1, vol: 0.3 }, grains: { count: 8, f: 2600, q: 3, spread: 0.13, vol: 0.2 } },
44835
+ sand: { heel: { f: 65, d: 0.06, vol: 0.3 }, roll: { type: "bandpass", f: 1500, q: 0.6, d: 0.16, vol: 0.3 }, grains: { count: 5, f: 4800, q: 1.5, spread: 0.12, vol: 0.05 } },
44836
+ metal: { heel: { f: 120, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 2800, q: 1.5, d: 0.03, vol: 0.25 }, ring: [{ f: 420, d: 0.25, vol: 0.16 }, { f: 1130, d: 0.18, vol: 0.09 }, { f: 2470, d: 0.1, vol: 0.04 }] },
44837
+ water: { heel: { f: 70, d: 0.04, vol: 0.25 }, roll: { type: "bandpass", f: 3e3, q: 1.2, d: 0.1, vol: 0.18 }, splash: { f: 1100, f2: 380, d: 0.22, vol: 0.55 }, grains: { count: 4, f: 2400, q: 6, spread: 0.18, vol: 0.12 } },
44838
+ glass: { heel: { f: 90, d: 0.04, vol: 0.4 }, roll: { type: "highpass", f: 4500, q: 0.8, d: 0.06, vol: 0.2 }, grains: { count: 9, f: 6200, q: 5, spread: 0.12, vol: 0.22 }, ring: [{ f: 3900, d: 0.04, vol: 0.04 }, { f: 5300, d: 0.03, vol: 0.03 }] },
44839
+ carpet: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "lowpass", f: 420, q: 0.7, d: 0.07, vol: 0.3 } }
44840
+ };
44841
+ var GAITS = {
44842
+ walk: { vol: 1, bright: 1, toe: 0.075, short: 1 },
44843
+ run: { vol: 1.35, bright: 1.25, toe: 0.035, short: 0.75 }
44844
+ };
44845
+ var grainLayers = (g, take, start2, scale) => Array.from(
44846
+ { length: g.count },
44847
+ (_, i) => sceneNoise({ type: "bandpass", f: [g.f * take.f * 0.85, g.f * take.f * 1.15], q: g.q, d: [8e-3, 0.02], vol: g.vol * scale * take.v, delay: start2 + (i + 0.5) / g.count * g.spread * take.t })
44848
+ );
44849
+ var ringLayers = (rings, take, scale, at = 0) => rings.map((r) => sceneTone({ f: r.f * take.f, d: r.d * take.t, vol: r.vol * scale * take.v, delay: at }));
44850
+ function footLayers(surface, take, gait, weight = 1) {
44851
+ const s = SURFACES[surface];
44852
+ const toe = gait.toe * take.t;
44853
+ return [
44854
+ sceneTone({ f: s.heel.f * take.f * weight ** -0.3, f2: s.heel.f * take.f * 0.6, d: s.heel.d * gait.short * take.t * weight ** 0.5, vol: s.heel.vol * gait.vol * take.v * weight ** 0.5 }),
44855
+ sceneNoise({ type: "lowpass", f: 500 * gait.bright, d: 0.02 * take.t, vol: 0.18 * gait.vol * take.v * weight ** 0.5 }),
44856
+ sceneNoise({ type: s.roll.type, f: [s.roll.f * take.f * gait.bright * 0.92, s.roll.f * take.f * gait.bright * 1.08], q: s.roll.q, d: s.roll.d * gait.short * take.t * weight ** 0.3, vol: s.roll.vol * gait.vol * take.v, delay: toe }),
44857
+ ...s.grains ? grainLayers(s.grains, take, toe * 0.5, gait.vol * weight ** 0.5) : [],
44858
+ ...s.ring ? ringLayers(s.ring, take, gait.vol * weight ** 0.5) : [],
44859
+ ...s.splash ? [sceneNoise({ type: "bandpass", f: s.splash.f * take.f, f2: s.splash.f2 * take.f, q: 1.4, d: s.splash.d * take.t * weight ** 0.4, vol: s.splash.vol * gait.vol * take.v * weight ** 0.5 })] : []
44860
+ ];
44861
+ }
44862
+ var quieter = (level, layers) => layers.map((layer) => {
44863
+ const gain = layer.gain ?? 1;
44864
+ if (typeof gain === "number") return { ...layer, gain: gain * level };
44865
+ if (Array.isArray(gain)) return { ...layer, gain: [gain[0] * level, gain[1] * level] };
44866
+ return layer;
44867
+ });
44868
+ var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
44869
+ var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
44870
+ function takes(id, layersOf, falloff, rate = [0.96, 1.04], bus) {
44871
+ const sounds = TAKES.map((take, index) => recipe(takeId(id, index), layersOf(take, index), falloff, bus));
44872
+ return { sounds, set: { id, variants: sounds.map((sound) => sound.id), rate } };
44873
+ }
44874
+ var footstep = (surface, gait) => takes(footstepSoundId(surface, gait), (take) => footLayers(surface, take, GAITS[gait]), FOOTSTEP_FALLOFF);
44875
+ var LANDINGS = { light: { weight: 1.4, cloth: 0.12 }, heavy: { weight: 2.4, cloth: 0.22 } };
44876
+ var LANDING_LEVEL = 0.65;
44877
+ var landing = (surface, landing2) => takes(
44878
+ `land_${surface}_${landing2}`,
44879
+ (take) => quieter(LANDING_LEVEL, [
44880
+ // both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
44881
+ ...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
44882
+ ...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer) => ({ ...layer, at: (typeof layer.at === "number" ? layer.at : 0) + 0.018 * take.t })),
44883
+ sceneTone({ f: 60 * take.f, f2: 38, d: 0.14 * LANDINGS[landing2].weight * take.t, vol: 0.22 * LANDINGS[landing2].weight ** 0.7 * take.v, delay: 0.02 }),
44884
+ sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
44885
+ ]),
44886
+ FOOTSTEP_FALLOFF
44887
+ );
44888
+ var whoosh = (blow, from, to, d, vol, q = 1.6) => (take) => [
44889
+ // the air: a band sweeping up as the limb speeds up; it peaks `SWING_LEAD_SECONDS` in, the moment before the contact, then dies away
44890
+ sceneNoise({ type: "bandpass", f: from * take.f, f2: to * take.f, q, a: SWING_LEAD_SECONDS[blow], d: d * take.t, vol: vol * take.v }),
44891
+ sceneNoise({ type: "lowpass", f: 600 * take.f, a: SWING_LEAD_SECONDS[blow], d: d * take.t * 0.8, vol: vol * 0.35 * take.v })
44892
+ ];
44893
+ var fleshHit = (o) => (take) => [
44894
+ sceneNoise({ type: "bandpass", f: o.slapF * take.f, q: 1.2, d: 0.03 * take.t, vol: o.slap * take.v }),
44895
+ sceneNoise({ type: "highpass", f: 3500 * take.f, d: 0.012, vol: o.slap * 0.4 * take.v }),
44896
+ sceneTone({ f: o.thudF * take.f, f2: o.thudF * 0.5, d: o.thudD * take.t, vol: o.thud * take.v }),
44897
+ sceneNoise({ type: "lowpass", f: 600 * take.f, d: o.thudD * 1.2 * take.t, vol: o.thud * 0.5 * take.v }),
44898
+ ...o.crack ? [sceneNoise({ type: "highpass", f: 2500, d: 0.015, vol: o.crack * take.v, delay: 6e-3 }), sceneTone({ wave: "square", f: 900 * take.f, f2: 300, d: 0.02, vol: o.crack * 0.15 * take.v, delay: 6e-3 })] : [],
44899
+ ...o.wet ? [sceneNoise({ type: "bandpass", f: 1400 * take.f, f2: 500, q: 2.5, d: 0.12 * take.t, vol: o.wet * take.v, delay: 0.01 })] : []
44900
+ ];
44901
+ var knock = (o) => (take) => [
44902
+ sceneNoise({ type: "bandpass", f: o.clickF * take.f, q: 1.5, d: 0.018, vol: o.click * take.v }),
44903
+ ...o.tones.map(([f, d, vol]) => sceneTone({ f: f * take.f, d: d * take.t, vol: vol * take.v }))
44904
+ ];
44905
+ var voice = (o) => (take) => [
44906
+ sceneVoice({ f0: o.f0 * take.f, f1: o.f1 * take.f, d: o.d * take.t, fA: o.fA * take.f, fB: o.fB * take.f, noise: o.noise, vol: o.vol * take.v })
44907
+ ];
44908
+ var BODY = {
44909
+ jump: (take) => [
44910
+ sceneNoise({ type: "bandpass", f: 900 * take.f, f2: 1700 * take.f, q: 1, a: 0.03, d: 0.12, vol: 0.18 * take.v }),
44911
+ sceneNoise({ type: "bandpass", f: 2200 * take.f, q: 0.8, d: 0.04, vol: 0.12 * take.v }),
44912
+ sceneTone({ f: 85 * take.f, f2: 60, d: 0.04, vol: 0.2 * take.v })
44913
+ ],
44914
+ cloth_rustle: (take) => [
44915
+ { ...sceneNoise({ type: "bandpass", f: 2400 * take.f, q: 0.8, a: 0.06, d: 0.22 * take.t, vol: 0.12 * take.v }), modulators: [{ target: "gain", wave: "triangle", rate: [9, 14], depth: 0.5 }] }
44916
+ ],
44917
+ body_fall: (take) => [
44918
+ sceneTone({ f: 58 * take.f, f2: 35, d: 0.28 * take.t, vol: 0.9 * take.v }),
44919
+ sceneNoise({ type: "lowpass", f: 480 * take.f, d: 0.3 * take.t, vol: 0.55 * take.v }),
44920
+ sceneNoise({ type: "bandpass", f: 1300 * take.f, q: 1, d: 0.05, vol: 0.3 * take.v }),
44921
+ // the limbs a moment later, and the clothes
44922
+ sceneTone({ f: 75 * take.f, f2: 45, d: 0.12, vol: 0.4 * take.v, delay: 0.13 * take.t }),
44923
+ sceneNoise({ type: "bandpass", f: 2200, q: 0.8, d: 0.2, vol: 0.12 * take.v, delay: 0.05 })
44924
+ ],
44925
+ fall_damage: (take) => [
44926
+ ...BODY.body_fall(take),
44927
+ sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
44928
+ ...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer) => ({ ...layer, at: 0.08 }))
44929
+ ],
44930
+ player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
44931
+ player_breath_run: (take) => [
44932
+ sceneNoise({ type: "bandpass", f: 1200 * take.f, q: 1, a: 0.08, d: 0.22 * take.t, vol: 0.25 * take.v }),
44933
+ sceneNoise({ type: "bandpass", f: 900 * take.f, q: 1, a: 0.06, d: 0.26 * take.t, vol: 0.3 * take.v, delay: 0.3 * take.t })
44934
+ ],
44935
+ player_death: voice({ f0: 135, f1: 68, d: 1, fA: 560, fB: 980, noise: 0.35, vol: 0.4 }),
44936
+ bite: (take) => [
44937
+ sceneNoise({ type: "highpass", f: 2600, d: 0.015, vol: 0.4 * take.v }),
44938
+ sceneNoise({ type: "bandpass", f: 1600 * take.f, f2: 450, q: 2.2, d: 0.16 * take.t, vol: 0.55 * take.v, delay: 0.01 }),
44939
+ sceneNoise({ type: "bandpass", f: 3200 * take.f, f2: 1800, q: 1.2, d: 0.22 * take.t, vol: 0.25 * take.v, delay: 0.09 }),
44940
+ sceneTone({ f: 120 * take.f, f2: 70, d: 0.06, vol: 0.3 * take.v })
44941
+ ],
44942
+ grab: (take) => [
44943
+ sceneNoise({ type: "bandpass", f: 1900 * take.f, q: 0.9, a: 4e-3, d: 0.13 * take.t, vol: 0.32 * take.v }),
44944
+ sceneTone({ f: 95 * take.f, f2: 60, d: 0.07, vol: 0.3 * take.v }),
44945
+ sceneNoise({ type: "lowpass", f: 700, d: 0.08, vol: 0.2 * take.v })
44946
+ ],
44947
+ punch_swing: whoosh("punch", 450, 1500, 0.16, 0.9),
44948
+ punch_hit_body: fleshHit({ slap: 0.55, slapF: 1500, thud: 0.6, thudF: 115, thudD: 0.09 }),
44949
+ punch_hit_face: fleshHit({ slap: 0.75, slapF: 2300, thud: 0.45, thudF: 150, thudD: 0.07, crack: 0.25 }),
44950
+ punch_hit_object: knock({ clickF: 1800, click: 0.3, tones: [[210, 0.04, 0.25]] }),
44951
+ kick_swing: whoosh("kick", 280, 950, 0.22, 1, 1.3),
44952
+ kick_hit: fleshHit({ slap: 0.45, slapF: 1100, thud: 0.85, thudF: 82, thudD: 0.14 }),
44953
+ kick_hit_object: knock({ clickF: 1e3, click: 0.3, tones: [[92, 0.08, 0.5]] }),
44954
+ shove_two_hands: (take) => [
44955
+ sceneNoise({ type: "bandpass", f: 950 * take.f, q: 0.9, a: 0.01, d: 0.13 * take.t, vol: 0.38 * take.v }),
44956
+ sceneTone({ f: 100 * take.f, f2: 60, d: 0.1 * take.t, vol: 0.45 * take.v }),
44957
+ sceneNoise({ type: "bandpass", f: 2300, q: 0.8, d: 0.16, vol: 0.12 * take.v, delay: 0.03 })
44958
+ ],
44959
+ knife_swing: whoosh("blade", 1900, 4600, 0.12, 1.1, 2.2),
44960
+ knife_stab: fleshHit({ slap: 0.2, slapF: 1200, thud: 0.4, thudF: 160, thudD: 0.06, wet: 0.5 }),
44961
+ knife_slash: (take) => [
44962
+ sceneNoise({ type: "highpass", f: 3200 * take.f, f2: 1800, d: 0.13 * take.t, vol: 0.35 * take.v }),
44963
+ sceneNoise({ type: "bandpass", f: 1300 * take.f, f2: 600, q: 2.5, d: 0.12 * take.t, vol: 0.35 * take.v, delay: 0.02 })
44964
+ ],
44965
+ knife_hit_object: knock({ clickF: 3200, click: 0.25, tones: [[2800, 0.08, 0.07], [4150, 0.06, 0.05]] }),
44966
+ blunt_swing: whoosh("blunt", 220, 760, 0.28, 1.1, 1.5),
44967
+ blunt_hit: fleshHit({ slap: 0.35, slapF: 900, thud: 1, thudF: 70, thudD: 0.18, crack: 0.3 }),
44968
+ blunt_hit_object: knock({ clickF: 1400, click: 0.35, tones: [[300, 0.12, 0.25], [760, 0.08, 0.12]] }),
44969
+ axe_swing: whoosh("chop", 190, 620, 0.3, 1.2, 1.4),
44970
+ axe_chop: fleshHit({ slap: 0.3, slapF: 1400, thud: 0.75, thudF: 140, thudD: 0.1, crack: 0.35, wet: 0.45 }),
44971
+ axe_hit_object: knock({ clickF: 2200, click: 0.4, tones: [[1500, 0.15, 0.1], [2300, 0.1, 0.06], [140, 0.1, 0.35]] })
44972
+ };
44973
+ var BODY_BUS = { player_hurt: "voices", player_death: "voices", player_breath_run: "voices", fall_damage: "voices" };
44974
+ var built = [
44975
+ ...SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstep(surface, gait))),
44976
+ ...SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((l) => landing(surface, l))),
44977
+ ...BODY_SOUND_IDS.map((id) => takes(id, BODY[id], BODY_FALLOFF, id.endsWith("_swing") ? [0.94, 1.06] : [0.96, 1.04], BODY_BUS[id]))
44978
+ ];
44979
+ var BODY_FOLEY_BANK = {
44980
+ id: "body_foley",
44981
+ sounds: built.flatMap((entry) => entry.sounds),
44982
+ sets: built.map((entry) => entry.set)
44983
+ };
44984
+ var BODY_FOLEY_SET_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
44985
+
43541
44986
  // src/test-area/types.ts
43542
44987
  var defaultEffectIds = {
43543
44988
  ignite: "vehicle_fire",
@@ -43560,10 +45005,12 @@ var GameVehiclesThree = (() => {
43560
45005
  var ITEM_OF = { compact_hatch: "hatch", midsize_sedan: "sedan", midsize_pickup: "pickup", sport_bike: "sport_bike", box_truck: "box_truck", city_bus: "city_bus", battle_tank: "battle_tank", attack_helicopter: "attack_helicopter" };
43561
45006
  var itemId = (key) => ITEM_OF[key] ?? key;
43562
45007
  var LANE = { z: 11, from: -12, wall: 13 };
45008
+ var LEAF_REST_RADIANS = 0.02;
43563
45009
  var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
43564
45010
  var ZONE_COLOURS = { door: 5627221, hood: 14540083, lid: 14522675, bumper: 14505437, glass: 4500223, light: 16777215, wheel: 16742195 };
43565
45011
  var BODY_HEIGHT = 1.7;
43566
45012
  function createVehiclesTestModule(options = {}) {
45013
+ const fleet = options.fleet ?? { colorFromSeed, fleetDims, installFleet, paintPalettes, paletteOf };
43567
45014
  const locale = options.locale ?? "pt-BR";
43568
45015
  const text = TEXT[locale] ?? TEXT["pt-BR"];
43569
45016
  const keys = options.vehicles ?? DEFAULT_TEST_VEHICLES;
@@ -43588,23 +45035,29 @@ var GameVehiclesThree = (() => {
43588
45035
  engineLoop?.stop();
43589
45036
  engineLoop = void 0;
43590
45037
  };
45038
+ const particleMaterials = {
45039
+ own: (build) => isRenderPools(host?.pools) ? host.pools.materials.own(build) : build(),
45040
+ release: (material) => isRenderPools(host?.pools) ? host.pools.materials.release(material) : material.dispose?.()
45041
+ };
43591
45042
  const kitFor = (THREE) => {
43592
45043
  three = THREE;
43593
45044
  let kit = kits.get(THREE);
43594
45045
  if (!kit) {
43595
45046
  if (options.createKit) kit = options.createKit(THREE);
43596
45047
  else {
43597
- kit = createVehicleKit({ THREE, locale, ...options.document ? { document: options.document } : {} });
43598
- installFleet(kit);
45048
+ kit = createVehicleKit({ THREE, locale, particleMaterials, effects: () => host?.effects, ...options.document ? { document: options.document } : {} });
45049
+ fleet.installFleet(kit);
43599
45050
  }
43600
45051
  kits.set(THREE, kit);
43601
45052
  }
43602
45053
  return kit;
43603
45054
  };
43604
- const paletteFor = (key) => settings.palette === "civil" ? paintPalettes.civil : settings.palette === "military" ? paintPalettes.military : paletteOf(key);
45055
+ const paletteFor = (key) => settings.palette === "civil" ? fleet.paintPalettes.civil : settings.palette === "military" ? fleet.paintPalettes.military : fleet.paletteOf(key);
43605
45056
  const paintOf = (entry) => {
43606
45057
  if (!settings.paint) return;
43607
- const color2 = colorFromSeed(settings.seed + entry.index, paletteFor(entry.key));
45058
+ entry.lod?.dispose();
45059
+ delete entry.lod;
45060
+ const color2 = fleet.colorFromSeed(settings.seed + entry.index, paletteFor(entry.key));
43608
45061
  if (!entry.paint) {
43609
45062
  const vehicle = entry.item.vehicle;
43610
45063
  const primary = primaryPaint(entry.item.vehicle);
@@ -43626,19 +45079,40 @@ var GameVehiclesThree = (() => {
43626
45079
  slow: true,
43627
45080
  build: (THREE) => {
43628
45081
  const kit = kitFor(THREE);
43629
- const built = buildVehicleItem(kit, key, { lod: settings.quality === "lite", ...scenario ? { scenario } : {} });
43630
- built.root.userData.kit = kit;
43631
- const entry = { root: built.root, key, ...scenario ? { scenario } : {}, item: built, index: counter++, helpers: [] };
43632
- placed.set(built.root.uuid, entry);
45082
+ const built2 = buildVehicleItem(kit, key, { lod: settings.quality === "lite", ...scenario ? { scenario } : {} });
45083
+ built2.root.userData.kit = kit;
45084
+ const entry = { root: built2.root, key, ...scenario ? { scenario } : {}, item: built2, index: counter++, helpers: [] };
45085
+ placed.set(built2.root.uuid, entry);
43633
45086
  measure(entry);
43634
45087
  paintOf(entry);
43635
- return built.root;
45088
+ return built2.root;
43636
45089
  },
43637
45090
  impl: (root, id) => {
43638
45091
  const r = root;
43639
45092
  const kit = r.userData.kit;
43640
- const d = createVehicleDestructible(kit, r.userData.vehicle, { id, frame: root, name: text.items[itemKey] ?? key });
45093
+ const d = createVehicleDestructible(kit, r.userData.vehicle, { id, frame: root, name: text.items[itemKey] ?? key, creaseQueue });
43641
45094
  tapEffects(d);
45095
+ const setPartPose = d.setPartPose?.bind(d);
45096
+ {
45097
+ const rests = new Map(d.describe().parts.map((part) => [part.id, part.rotation ?? [0, 0, 0]]));
45098
+ d.setPartPose = (partId, position, rotation) => {
45099
+ const entry = [...placed.values()].find((candidate) => candidate.root === root);
45100
+ const rest = rests.get(partId) ?? [0, 0, 0];
45101
+ const isOpen = (rotation ?? [0, 0, 0]).some((angle, axis) => Math.abs(angle - (rest[axis] ?? 0)) > LEAF_REST_RADIANS);
45102
+ if (entry) {
45103
+ entry.posedParts ?? (entry.posedParts = /* @__PURE__ */ new Set());
45104
+ if (isOpen) entry.posedParts.add(partId);
45105
+ else entry.posedParts.delete(partId);
45106
+ }
45107
+ return setPartPose?.(partId, position, rotation);
45108
+ };
45109
+ }
45110
+ const hitBody = d.hit.bind(d);
45111
+ d.hit = (h) => {
45112
+ const entry = [...placed.values()].find((candidate) => candidate.root === root);
45113
+ if (entry) entry.touched = true;
45114
+ return hitBody(h);
45115
+ };
43642
45116
  cabins.track(id, d, root);
43643
45117
  r.userData.destructible = d;
43644
45118
  return d;
@@ -43651,10 +45125,10 @@ var GameVehiclesThree = (() => {
43651
45125
  if (!THREE) return void 0;
43652
45126
  if (!clipCache.has(key)) {
43653
45127
  const kit = kitFor(THREE);
43654
- const built = buildVehicleItem(kit, key);
43655
- const d = createVehicleDestructible(kit, built.vehicle, { id: `${itemId(key)}_clips`, frame: built.root, headless: true });
45128
+ const built2 = buildVehicleItem(kit, key);
45129
+ const d = createVehicleDestructible(kit, built2.vehicle, { id: `${itemId(key)}_clips`, frame: built2.root, headless: true });
43656
45130
  clipCache.set(key, d.animations());
43657
- kit.dispose(built.vehicle);
45131
+ kit.dispose(built2.vehicle);
43658
45132
  }
43659
45133
  return clipCache.get(key);
43660
45134
  };
@@ -43779,33 +45253,33 @@ var GameVehiclesThree = (() => {
43779
45253
  if (!host || !three) return;
43780
45254
  clearLane();
43781
45255
  const kit = kitFor(three);
43782
- const vehicles = createVehiclesThree(three, { kit });
43783
- const built = vehicles.build("compact_hatch", { quality: "lite", seed: settings.seed });
45256
+ const vehicles = createVehiclesThree(three, { kit, fleet });
45257
+ const built2 = vehicles.build("compact_hatch", { quality: "lite", seed: settings.seed });
43784
45258
  const wall = new three.Mesh(new three.BoxGeometry(0.6, 2, 6), new three.MeshStandardMaterial({ color: 7829367 }));
43785
45259
  wall.position.set(LANE.wall + 0.3, 1, LANE.z);
43786
45260
  const scene = host.scene;
43787
- scene.add(built.group, wall);
45261
+ scene.add(built2.group, wall);
43788
45262
  const mode = settings.driving === "semi" ? "semi" : "arcade";
43789
45263
  const surfaces = createSurfaces();
43790
45264
  const idle = { throttle: 0, brake: 0, steer: 0, handbrake: false };
43791
45265
  const state = { time: 0, exhaust: 0, ended: false, wasBlocked: false };
43792
- const arcade = createArcadeDriver({ length: built.length, width: built.width });
45266
+ const arcade = createArcadeDriver({ length: built2.length, width: built2.width });
43793
45267
  const semi = createSemiSimDriver({}, { surfaces });
43794
45268
  const driver = mode === "semi" ? semi : arcade;
43795
45269
  driver.place(LANE.from, LANE.z, 0);
43796
- const gap = () => LANE.wall - (driver.state.x + built.length / 2);
45270
+ const gap = () => LANE.wall - (driver.state.x + built2.length / 2);
43797
45271
  const decel = () => mode === "semi" ? 9 * surfaces.get(settings.surface).grip : arcade.spec.brakeAgainstMotion;
43798
45272
  const brakeAt = () => lane ? lane.speed() * lane.speed() / (2 * decel()) + 3 : 3;
43799
45273
  const pose = () => {
43800
- built.group.position.set(driver.state.x, 0, driver.state.z);
43801
- built.group.rotation.y = driver.state.yaw + Math.PI / 2;
45274
+ built2.group.position.set(driver.state.x, 0, driver.state.z);
45275
+ built2.group.rotation.y = driver.state.yaw + Math.PI / 2;
43802
45276
  };
43803
45277
  pose();
43804
45278
  engineLoop = audio()?.loop?.(soundIds.engine, { position: [driver.state.x, 0.5, driver.state.z] });
43805
45279
  const log = (key, value) => host?.log(fill(text, locale, text.lane[key], value.toFixed(1)));
43806
45280
  lane = {
43807
- car: built,
43808
- group: built.group,
45281
+ car: built2,
45282
+ group: built2.group,
43809
45283
  wall,
43810
45284
  mode,
43811
45285
  done: false,
@@ -43841,7 +45315,7 @@ var GameVehiclesThree = (() => {
43841
45315
  const fx = effects();
43842
45316
  if (fx && state.exhaust <= 0 && Math.abs(lane?.speed() ?? 0) > 0.5) {
43843
45317
  state.exhaust = 0.4;
43844
- fx.play({ effect: effectIds.exhaust, point: [driver.state.x - Math.cos(driver.state.yaw) * (built.length / 2), 0.3, driver.state.z + Math.sin(driver.state.yaw) * (built.length / 2)], scale: Math.min(1, Math.abs(lane?.speed() ?? 0) / 17) });
45318
+ fx.play({ effect: effectIds.exhaust, point: [driver.state.x - Math.cos(driver.state.yaw) * (built2.length / 2), 0.3, driver.state.z + Math.sin(driver.state.yaw) * (built2.length / 2)], scale: Math.min(1, Math.abs(lane?.speed() ?? 0) / 17) });
43845
45319
  }
43846
45320
  if (!state.ended && state.time > 1 && Math.abs(lane?.speed() ?? 0) < 0.05) {
43847
45321
  state.ended = true;
@@ -43885,6 +45359,16 @@ var GameVehiclesThree = (() => {
43885
45359
  break;
43886
45360
  }
43887
45361
  };
45362
+ const creaseQueue = createCreaseQueue();
45363
+ const updateLod = (entry) => {
45364
+ if (!host || settings.quality === "lite") return;
45365
+ entry.lod ?? (entry.lod = createVehicleLod(entry.root.userData.kit, entry.item.vehicle, entry.root, entry.root.children[0]));
45366
+ const at = host.player().position;
45367
+ const distance = Math.hypot(at[0] - entry.root.position.x, at[2] - entry.root.position.z);
45368
+ const c = entry.item.vehicle;
45369
+ if (c && (c.rollState || c.flipped || c.blast || c.exploded)) entry.touched = true;
45370
+ entry.lod.update(distance, entry.touched === true, entry.scenario !== void 0 || (entry.posedParts?.size ?? 0) > 0);
45371
+ };
43888
45372
  const frame = (dt) => {
43889
45373
  for (const [id, entry] of placed) {
43890
45374
  if (!entry.root.parent) {
@@ -43892,6 +45376,7 @@ var GameVehiclesThree = (() => {
43892
45376
  continue;
43893
45377
  }
43894
45378
  measure(entry);
45379
+ updateLod(entry);
43895
45380
  }
43896
45381
  if (host?.bench && boxKey() !== helpersShown && (host.bench.layer("damageables") || host.bench.layer("zones") || helpersShown !== "")) refreshLayers();
43897
45382
  lane?.step(dt);
@@ -43922,7 +45407,7 @@ var GameVehiclesThree = (() => {
43922
45407
  const sizeError = () => {
43923
45408
  let worst = 0;
43924
45409
  for (const entry of placed.values()) {
43925
- const want = fleetDims(entry.key);
45410
+ const want = fleet.fleetDims(entry.key);
43926
45411
  const got = entry.size;
43927
45412
  if (!want || !got || entry.scenario) continue;
43928
45413
  worst = Math.max(worst, Math.abs(got.length - want.length), Math.abs(got.width - want.width), Math.abs(got.height - want.height));
@@ -43944,15 +45429,15 @@ var GameVehiclesThree = (() => {
43944
45429
  const rowOf = (list2, z) => {
43945
45430
  let cursor = 0;
43946
45431
  const spots = list2.map((entry) => {
43947
- const width = fleetDims(entry.key)?.width ?? 2;
45432
+ const width = fleet.fleetDims(entry.key)?.width ?? 2;
43948
45433
  const x = cursor + width / 2;
43949
45434
  cursor += width + gap;
43950
45435
  return { id: entry.id, x };
43951
45436
  });
43952
45437
  return { spots, z, width: Math.max(0, cursor - gap) };
43953
45438
  };
43954
- const front = rowOf(entries.filter((e) => (fleetDims(e.key)?.width ?? 2) <= WIDE), 0);
43955
- const back = rowOf(entries.filter((e) => (fleetDims(e.key)?.width ?? 2) > WIDE), -16);
45439
+ const front = rowOf(entries.filter((e) => (fleet.fleetDims(e.key)?.width ?? 2) <= WIDE), 0);
45440
+ const back = rowOf(entries.filter((e) => (fleet.fleetDims(e.key)?.width ?? 2) > WIDE), -16);
43956
45441
  const placements = [front, back].flatMap((row) => row.spots.map(({ id, x }) => ({ item: id, position: [x - row.width / 2, 0, row.z], rotationY: 0 })));
43957
45442
  return { placements, view: { eye: [0, 5, Math.max(14, front.width * 0.6)], target: [0, 1, -2] } };
43958
45443
  },
@@ -43991,11 +45476,14 @@ var GameVehiclesThree = (() => {
43991
45476
  const stopChange = lent.bench?.onChange(onChange);
43992
45477
  const stopFrame = lent.onFrame(frame);
43993
45478
  const stopBlocker = lent.addBlocker?.(blocked);
45479
+ const stopCrease = lent.defer?.(creaseQueue.deferredQueue(), DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.VEHICLE_CREASE]);
45480
+ if (isRenderPools(lent.pools)) lent.pools.materials.register("vehicles.cracked-glass", () => crackedGlassMaterial(three, new three.DataTexture(new Uint8Array(4), 1, 1)));
43994
45481
  lent.audio?.register?.(LIBRARY_BANK);
43995
45482
  return () => {
43996
45483
  stopChange?.();
43997
45484
  stopFrame();
43998
45485
  stopBlocker?.();
45486
+ stopCrease?.();
43999
45487
  cabins.clear(effects());
44000
45488
  clearHelpers();
44001
45489
  clearLane();