@adatechnology/game-vehicles 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5471,8 +5471,20 @@ function installGlassDamage(kit) {
5471
5471
  if (!L || L.broken) return;
5472
5472
  L.broken = true;
5473
5473
  L.lens.forEach(function(m) {
5474
- m.visible = false;
5474
+ const rim = L.lensMaterial ? glassRim(m.geometry, c.rand) : null;
5475
+ if (!rim) {
5476
+ m.visible = false;
5477
+ return;
5478
+ }
5479
+ m.geometry.dispose();
5480
+ m.geometry = rim;
5481
+ m.material = MATERIALS.glassEdge;
5482
+ m.userData.lensRim = true;
5475
5483
  });
5484
+ if (L.lensMaterial)
5485
+ (L.reflector || []).forEach(function(m) {
5486
+ m.material = MATERIALS.broken;
5487
+ });
5476
5488
  L.inner.forEach(function(m) {
5477
5489
  m.material = MATERIALS.bulbBroken;
5478
5490
  });
@@ -5480,11 +5492,38 @@ function installGlassDamage(kit) {
5480
5492
  h.visible = false;
5481
5493
  });
5482
5494
  const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
5483
- I.burst(c, "lens", c.toEffects(p), 10, { dir: c.dirToEffects(dir.clone().negate()), speed: 1.4, variant: L.tint === "red" ? "red" : "clear" });
5484
- I.burst(c, "plastic", c.toEffects(p), 2, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
5495
+ const glass = L.lensMaterial !== "plastic";
5496
+ I.burst(c, "lens", c.toEffects(p), glass ? 10 : 5, { dir: c.dirToEffects(dir.clone().negate()), speed: glass ? 1.4 : 1.1, variant: L.tint === "red" ? "red" : "clear" });
5497
+ I.burst(c, "plastic", c.toEffects(p), glass ? 2 : 5, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
5485
5498
  }
5486
5499
  kit.breakLight = breakLight;
5487
- Object.assign(I, { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight });
5500
+ const crazedCache = /* @__PURE__ */ new Map();
5501
+ const CRAZED_OPACITY = 0.6;
5502
+ function crazed(material) {
5503
+ const known = crazedCache.get(material);
5504
+ if (known) return known;
5505
+ const m = I.crackLens(material).clone();
5506
+ m.opacity = Math.max(m.opacity ?? 1, CRAZED_OPACITY);
5507
+ m.transparent = true;
5508
+ m.userData.shared = true;
5509
+ crazedCache.set(material, m);
5510
+ return m;
5511
+ }
5512
+ function crackLight(c, L, dir, pierced) {
5513
+ if (!L || L.broken) return;
5514
+ if (!L.cracked) {
5515
+ L.cracked = true;
5516
+ for (const m of L.lens) if (m.material && !Array.isArray(m.material)) m.material = crazed(m.material);
5517
+ }
5518
+ const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
5519
+ I.burst(c, "lens", c.toEffects(p), 3, { dir: c.dirToEffects(dir.clone().negate()), speed: 0.9, variant: L.tint === "red" ? "red" : "clear" });
5520
+ if (!pierced || L.dark) return;
5521
+ L.dark = true;
5522
+ for (const m of L.inner) m.material = MATERIALS.bulbBroken;
5523
+ for (const h of L.halo ?? []) h.visible = false;
5524
+ }
5525
+ kit.crackLight = crackLight;
5526
+ Object.assign(I, { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight, crackLight });
5488
5527
  }
5489
5528
 
5490
5529
  // src/impact.ts
@@ -11037,6 +11076,8 @@ var MECHANICS = {
11037
11076
  heli: ["battery", "alternator", "gearbox"]
11038
11077
  };
11039
11078
  var HITCH_CHANCE = { car: 30, truck: 60, bus: 0, tractor: 100, quad: 40, bike: 0, tank: 0, boat: 0, plane: 0, heli: 0 };
11079
+ var GLASS_LENS_HP = 320;
11080
+ var PLASTIC_LENS_HP = 900;
11040
11081
  var STEEL_KG_M2 = 7;
11041
11082
  var ALU_KG_M2 = 3.2;
11042
11083
  var kindOf = (c) => {
@@ -11194,7 +11235,9 @@ function buildVehicleModel(kit, c, options = {}) {
11194
11235
  continue;
11195
11236
  }
11196
11237
  if (role === "light") {
11197
- add({ id, role, name: label, material: "glass", box, mass: 1.2 + 4 * area, hp: 320, zoneId: id, visual: true, tags: ["light", ...tags] });
11238
+ const lens = /^head/.test(id) ? spec.headlightLens ?? "polycarbonate" : "acrylic";
11239
+ const glassLens = lens === "glass";
11240
+ add({ id, role, name: label, material: glassLens ? "glass" : "plastic", box, mass: 1.2 + 4 * area, hp: glassLens ? GLASS_LENS_HP : PLASTIC_LENS_HP, zoneId: id, visual: true, tags: ["light", `lens_${lens}`, ...tags] });
11198
11241
  continue;
11199
11242
  }
11200
11243
  if (role === "bumper") {
@@ -11651,21 +11694,188 @@ function subdivideNear(THREE, params) {
11651
11694
  mesh.geometry = g;
11652
11695
  }
11653
11696
 
11697
+ // src/destructible/door-open.ts
11698
+ var STRIP_SMOOTH_DEG = 180;
11699
+ var SKIN_BAND = 0.05;
11700
+ var BODY_FACING = 0.5;
11701
+ var FIELDS = 4;
11702
+ var SEAM_THIN = 0.03;
11703
+ var SEAM_AT_EDGE = 0.04;
11704
+ var SEAM_TALL = 0.4;
11705
+ var SLIVER_AREA = 1e-10;
11706
+ function cutStripOverDoor(vehicleKit, params) {
11707
+ const kit = vehicleKit;
11708
+ const strip = params.strip;
11709
+ const toVehicle = params.toVehicle;
11710
+ const { x0, x1 } = params;
11711
+ const THREE = kit.THREE;
11712
+ const at = new THREE.Vector3();
11713
+ const along = (x, y, z) => at.set(x, y, z).applyMatrix4(toVehicle).x;
11714
+ const piece = (tests) => {
11715
+ const mesh = new THREE.Mesh(strip.geometry.clone(), strip.material);
11716
+ kit.cutMesh(mesh, tests, STRIP_SMOOTH_DEG, {});
11717
+ return mesh;
11718
+ };
11719
+ const over = piece([{ sdf: (x, y, z) => along(x, y, z) - x0 }, { sdf: (x, y, z) => x1 - along(x, y, z) }]);
11720
+ const after = piece([{ sdf: (x, y, z) => along(x, y, z) - x1 }]);
11721
+ kit.cutMesh(strip, [{ sdf: (x, y, z) => x0 - along(x, y, z) }], STRIP_SMOOTH_DEG, {});
11722
+ const joined = joinTriangles(THREE, strip.geometry, after.geometry);
11723
+ strip.geometry.dispose();
11724
+ after.geometry.dispose();
11725
+ strip.geometry = joined;
11726
+ return over;
11727
+ }
11728
+ function joinTriangles(THREE, first, second) {
11729
+ const a = first.index ? first.toNonIndexed() : first;
11730
+ const b = second.index ? second.toNonIndexed() : second;
11731
+ const flags = [...solidTriangles(THREE, a), ...solidTriangles(THREE, b)];
11732
+ const both = new THREE.BufferGeometry();
11733
+ for (const name of Object.keys(a.attributes)) {
11734
+ const p = a.getAttribute(name);
11735
+ const q = b.getAttribute(name);
11736
+ if (!q || q.itemSize !== p.itemSize) continue;
11737
+ const array = new Float32Array(p.array.length + q.array.length);
11738
+ array.set(p.array, 0);
11739
+ array.set(q.array, p.array.length);
11740
+ both.setAttribute(name, new THREE.Float32BufferAttribute(array, p.itemSize));
11741
+ }
11742
+ if (a !== first) a.dispose();
11743
+ if (b !== second) b.dispose();
11744
+ const out = pickTriangles(THREE, both, flags, true) ?? new THREE.BufferGeometry();
11745
+ both.dispose();
11746
+ return out;
11747
+ }
11748
+ function solidTriangles(THREE, geometry) {
11749
+ const pos = geometry.getAttribute("position");
11750
+ const triangle = new THREE.Triangle();
11751
+ const flags = [];
11752
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
11753
+ triangle.a.fromBufferAttribute(pos, t * 3);
11754
+ triangle.b.fromBufferAttribute(pos, t * 3 + 1);
11755
+ triangle.c.fromBufferAttribute(pos, t * 3 + 2);
11756
+ flags.push(triangle.getArea() > SLIVER_AREA);
11757
+ }
11758
+ return flags;
11759
+ }
11760
+ function splitDoorSkin(three, meshes, toVehicleOf, side2, depth) {
11761
+ const THREE = three;
11762
+ const pieces = meshes;
11763
+ const toVehicle = toVehicleOf;
11764
+ const v = new THREE.Vector3();
11765
+ const corners = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
11766
+ const triangle = new THREE.Triangle();
11767
+ const centres = pieces.map((mesh) => {
11768
+ const geometry = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry;
11769
+ const matrix = toVehicle(mesh);
11770
+ const pos = geometry.getAttribute("position");
11771
+ const out = new Float32Array(pos.count / 3 * FIELDS);
11772
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
11773
+ let y = 0;
11774
+ let across = 0;
11775
+ for (let k = 0; k < 3; k++) {
11776
+ corners[k].fromBufferAttribute(pos, t * 3 + k).applyMatrix4(matrix);
11777
+ y += corners[k].y / 3;
11778
+ across += side2 * corners[k].z / 3;
11779
+ }
11780
+ out[t * FIELDS] = y;
11781
+ out[t * FIELDS + 1] = across;
11782
+ triangle.set(corners[0], corners[1], corners[2]).getNormal(v);
11783
+ out[t * FIELDS + 2] = v.y;
11784
+ out[t * FIELDS + 3] = v.x;
11785
+ }
11786
+ return { geometry, out };
11787
+ });
11788
+ const outer = /* @__PURE__ */ new Map();
11789
+ for (const { out } of centres)
11790
+ for (let t = 0; t * FIELDS < out.length; t++) {
11791
+ const band = Math.floor(out[t * FIELDS] / SKIN_BAND);
11792
+ outer.set(band, Math.max(outer.get(band) ?? -Infinity, out[t * FIELDS + 1]));
11793
+ }
11794
+ const surfaceAt = (y) => {
11795
+ const band = Math.floor(y / SKIN_BAND);
11796
+ return Math.max(outer.get(band - 1) ?? -Infinity, outer.get(band) ?? -Infinity, outer.get(band + 1) ?? -Infinity);
11797
+ };
11798
+ return centres.map(({ geometry, out }, i) => {
11799
+ const near = [];
11800
+ for (let t = 0; t * FIELDS < out.length; t++) {
11801
+ const f = t * FIELDS;
11802
+ const body = out[f + 1] < surfaceAt(out[f]) - depth && (out[f + 2] < -BODY_FACING || Math.abs(out[f + 3]) > BODY_FACING);
11803
+ near.push(!body);
11804
+ }
11805
+ const result = { skin: pickTriangles(THREE, geometry, near, true), deep: pickTriangles(THREE, geometry, near, false) };
11806
+ if (geometry !== pieces[i].geometry) geometry.dispose();
11807
+ return result;
11808
+ });
11809
+ }
11810
+ function leaveDeepSkinOnBody(three, meshes, params) {
11811
+ const THREE = three;
11812
+ const pieces = meshes;
11813
+ const toVehicle = params.toVehicle;
11814
+ const { side: side2, depth } = params;
11815
+ const split = splitDoorSkin(THREE, pieces, (mesh) => new THREE.Matrix4().multiplyMatrices(toVehicle, mesh.matrixWorld), side2, depth);
11816
+ split.forEach(({ skin, deep }, i) => {
11817
+ const piece = pieces[i];
11818
+ if (!deep || !skin || !piece.parent) return;
11819
+ const body = new THREE.Mesh(deep, piece.material);
11820
+ body.position.copy(piece.position);
11821
+ body.quaternion.copy(piece.quaternion);
11822
+ body.scale.copy(piece.scale);
11823
+ body.castShadow = piece.castShadow;
11824
+ body.receiveShadow = piece.receiveShadow;
11825
+ piece.parent.add(body);
11826
+ piece.geometry.dispose();
11827
+ piece.geometry = skin;
11828
+ });
11829
+ }
11830
+ function pickTriangles(THREE, geometry, flags, wanted) {
11831
+ const count = flags.filter((flag) => flag === wanted).length;
11832
+ if (!count) return null;
11833
+ const out = new THREE.BufferGeometry();
11834
+ for (const name of Object.keys(geometry.attributes)) {
11835
+ const attribute = geometry.getAttribute(name);
11836
+ const size = attribute.itemSize;
11837
+ const source = attribute.array;
11838
+ const array = new Float32Array(count * 3 * size);
11839
+ let w = 0;
11840
+ flags.forEach((flag, t) => {
11841
+ if (flag !== wanted) return;
11842
+ array.set(source.subarray(t * 3 * size, (t + 1) * 3 * size), w);
11843
+ w += 3 * size;
11844
+ });
11845
+ out.setAttribute(name, new THREE.Float32BufferAttribute(array, size));
11846
+ }
11847
+ out.computeBoundingBox();
11848
+ out.computeBoundingSphere();
11849
+ return out;
11850
+ }
11851
+ function isDoorSeam(three, params) {
11852
+ const THREE = three;
11853
+ const box = params.box;
11854
+ const { zone, side: side2, dark } = params;
11855
+ if (!dark || box.isEmpty()) return false;
11856
+ const size = box.getSize(new THREE.Vector3());
11857
+ const centre = box.getCenter(new THREE.Vector3());
11858
+ if (Math.sign(centre.z) !== Math.sign(side2) || size.x > SEAM_THIN || size.z > SEAM_THIN || size.y < (zone.y1 - zone.y0) * SEAM_TALL) return false;
11859
+ if (centre.y < zone.y0 || centre.y > zone.y1) return false;
11860
+ return Math.abs(centre.x - zone.x0) < SEAM_AT_EDGE || Math.abs(centre.x - zone.x1) < SEAM_AT_EDGE;
11861
+ }
11862
+
11654
11863
  // src/destructible/visual.ts
11655
11864
  var HOLE_DECAL_SCALE = 2.4;
11656
11865
  var DOOR_DEPTH = 0.07;
11866
+ var DOOR_SKIN_DEPTH = 0.09;
11867
+ var DOOR_FLOOR_DEPTH = 0.2;
11657
11868
  var DOOR_EDGE_PIECES = 8;
11658
11869
  var DOOR_EDGE_REACH = 0.08;
11659
11870
  var DOOR_CARD_COLOR = "#3b3e42";
11660
11871
  var DOOR_LINER_COLOR = "#5a5e64";
11661
11872
  var DOOR_LINER_DARK = 0.15;
11662
- var DOOR_STRIP_THIN = 0.08;
11873
+ var DOOR_STRIP_THIN = 0.13;
11663
11874
  var DOOR_STRIP_NEAR = 0.04;
11664
11875
  var DOOR_STRIP_TALL = 0.22;
11665
- var STRIP_SMOOTH_DEG = 180;
11666
- var DOOR_STRIP_VERTICES = 2e4;
11876
+ var DOOR_STRIP_VERTICES = 6e4;
11667
11877
  var DOOR_SILL = 0.25;
11668
- var DOOR_STRIP_OVER = 0.3;
11878
+ var DOOR_STRIP_OVER = 0.05;
11669
11879
  var DOOR_TOP_OVER_PANE = 0.03;
11670
11880
  var HOLE_TEXTURE_SIZE = 64;
11671
11881
  var LAMINATED_HP = 1e3;
@@ -11774,6 +11984,10 @@ var VehicleVisuals = class {
11774
11984
  __publicField(this, "skins", /* @__PURE__ */ new Map());
11775
11985
  /** How high each door's cut reaches. */
11776
11986
  __publicField(this, "doorTops", /* @__PURE__ */ new Map());
11987
+ /** The shut lines drawn on the body at a door's edges, by line: the doors whose edge it marks. Shown only while all of them are closed. */
11988
+ __publicField(this, "seams", /* @__PURE__ */ new Map());
11989
+ /** Doors and lids standing open now. */
11990
+ __publicField(this, "openNow", /* @__PURE__ */ new Set());
11777
11991
  __publicField(this, "lightsOn", true);
11778
11992
  /** Heavy body meshes whose triangle index is still to build (null once done). */
11779
11993
  __publicField(this, "warming");
@@ -11955,6 +12169,11 @@ var VehicleVisuals = class {
11955
12169
  }
11956
12170
  panel.visible = open && !this.skins.get(id);
11957
12171
  recess.visible = open && !this.skins.get(id);
12172
+ if (open) this.openNow.add(id);
12173
+ else this.openNow.delete(id);
12174
+ this.seams.forEach((doors, seam) => {
12175
+ if (doors.has(id)) seam.visible = ![...doors].some((door) => this.openNow.has(door));
12176
+ });
11958
12177
  };
11959
12178
  const euler = piv.rotation;
11960
12179
  const quaternion = piv.quaternion;
@@ -11997,11 +12216,13 @@ var VehicleVisuals = class {
11997
12216
  const pcs = I.cutPanel(this.c, z, rect, true, dirty, { anyN: true }).map((x) => x.mesh).filter((m) => !!m);
11998
12217
  this.mend(dirty);
11999
12218
  if (pcs.length) {
12219
+ leaveDeepSkinOnBody(this.THREE, pcs, { toVehicle: inv, side: z.side < 0 ? -1 : 1, depth: DOOR_SKIN_DEPTH });
12000
12220
  const panes = I.windowsOver(this.c, z, inv);
12001
12221
  const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
12002
12222
  const mirrors = this.mirrorsOn(z);
12003
12223
  const frames = this.framesAround(panes, inv);
12004
- objs = [...pcs, ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors, ...frames];
12224
+ const followers = I.followersIn(this.c, z, inv).filter((o) => !this.takeSeam(o, id, z, inv) && !this.inFloor(o, piv, z, inv));
12225
+ objs = [...pcs, ...followers, ...panes, ...seals, ...mirrors, ...frames];
12005
12226
  }
12006
12227
  } else {
12007
12228
  const pcs = this.cutZone(z);
@@ -12029,6 +12250,25 @@ var VehicleVisuals = class {
12029
12250
  this.c.root.updateMatrixWorld(true);
12030
12251
  return objs.length > 0;
12031
12252
  }
12253
+ /** A thin part at the foot of the opening, well inside the skin (a step, a sill trim, a rail on the floor), is the floor's: it does not swing with the door. */
12254
+ inFloor(o, piv, z, inv) {
12255
+ const side2 = z.side < 0 ? -1 : 1;
12256
+ const centre = this.I.localBox(this.c, o, inv).getCenter(new this.THREE.Vector3());
12257
+ const skin = side2 * (piv.position.z - side2 * 0.016);
12258
+ return centre.y < z.box.y0 + DOOR_SILL && side2 * centre.z < skin - DOOR_FLOOR_DEPTH;
12259
+ }
12260
+ /** A shut line drawn at the door's edge stays on the body, hidden while a door it marks stands open; answers whether `o` is one. */
12261
+ takeSeam(o, id, z, inv) {
12262
+ const B = z.box;
12263
+ const box = this.I.localBox(this.c, o, inv);
12264
+ const color = o.material?.color;
12265
+ const dark = !!color && color.getHSL({ h: 0, s: 0, l: 0 }).l < DOOR_LINER_DARK;
12266
+ if (!o.isMesh || !isDoorSeam(this.THREE, { box, zone: { x0: B.x0, x1: B.x1, y0: B.y0, y1: B.y1 }, side: z.side, dark })) return false;
12267
+ const doors = this.seams.get(o) ?? /* @__PURE__ */ new Set();
12268
+ doors.add(id);
12269
+ this.seams.set(o, doors);
12270
+ return true;
12271
+ }
12032
12272
  /** 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. */
12033
12273
  doorEdges(piv, z, y1) {
12034
12274
  const THREE = this.THREE;
@@ -12050,13 +12290,15 @@ var VehicleVisuals = class {
12050
12290
  const at = this.surface("z", side2, B, [[x, y]]);
12051
12291
  return at != null && Math.abs(at - flat) < DOOR_EDGE_REACH ? at : flat;
12052
12292
  });
12053
- const band = (lift) => {
12293
+ const band = (lift, path, closed) => {
12054
12294
  const position = [];
12055
12295
  const index = [];
12056
- loop.forEach(([x, y], i) => {
12057
- position.push(x, y, heights[i] + side2 * lift, x, y, heights[i] + side2 * (lift - DOOR_DEPTH));
12296
+ path.forEach((at, i) => {
12297
+ const [x, y] = loop[at];
12298
+ position.push(x, y, heights[at] + side2 * lift, x, y, heights[at] + side2 * (lift - DOOR_DEPTH));
12299
+ if (!closed && i === path.length - 1) return;
12058
12300
  const a = i * 2;
12059
- const b = (i + 1) % loop.length * 2;
12301
+ const b = (i + 1) % path.length * 2;
12060
12302
  index.push(a, a + 1, b, b, a + 1, b + 1);
12061
12303
  });
12062
12304
  const g = new THREE.BufferGeometry();
@@ -12084,8 +12326,11 @@ var VehicleVisuals = class {
12084
12326
  card.castShadow = false;
12085
12327
  this.c.vehicle.add(card);
12086
12328
  piv.attach(card);
12087
- const edges = new THREE.Mesh(band(2e-3), paint);
12088
- const lining = new THREE.Mesh(band(2e-3), paint);
12329
+ const all = loop.map((_, i) => i);
12330
+ const top = 2 * DOOR_EDGE_PIECES;
12331
+ const sides = [...all.slice(3 * DOOR_EDGE_PIECES), ...all.slice(0, top + 1)];
12332
+ const edges = new THREE.Mesh(band(2e-3, all, true), paint);
12333
+ const lining = new THREE.Mesh(band(2e-3, sides, false), paint);
12089
12334
  for (const m of [edges, lining]) {
12090
12335
  m.userData.noFollow = true;
12091
12336
  m.castShadow = false;
@@ -12142,26 +12387,14 @@ var VehicleVisuals = class {
12142
12387
  const box = this.I.localBox(this.c, o, inv);
12143
12388
  const size = box.getSize(new THREE.Vector3());
12144
12389
  const mid = box.getCenter(new THREE.Vector3());
12145
- if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box.max.x, x1) - Math.max(box.min.x, x0) < (x1 - x0) * DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
12390
+ if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box.max.x, x1) - Math.max(box.min.x, x0) < DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
12146
12391
  const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
12147
- const v = new THREE.Vector3();
12148
- const sdf = (x, y, zz) => {
12149
- v.set(x, y, zz).applyMatrix4(toVehicle);
12150
- return Math.max(x0 - v.x, v.x - x1);
12151
- };
12152
- const over = new THREE.Mesh(o.geometry.clone(), o.material);
12392
+ const over = cutStripOverDoor(this.kit, { strip: o, toVehicle, x0, x1 });
12153
12393
  over.position.copy(o.position);
12154
12394
  over.quaternion.copy(o.quaternion);
12155
12395
  over.scale.copy(o.scale);
12156
12396
  this.c.vehicle.add(over);
12157
12397
  over.updateMatrixWorld(true);
12158
- const overToVehicle = new THREE.Matrix4().multiplyMatrices(inv, over.matrixWorld);
12159
- const w = new THREE.Vector3();
12160
- this.kit.cutMesh(over, [{ sdf: (x, y, zz) => {
12161
- w.set(x, y, zz).applyMatrix4(overToVehicle);
12162
- return -Math.max(x0 - w.x, w.x - x1);
12163
- } }], STRIP_SMOOTH_DEG, {});
12164
- this.kit.cutMesh(o, [{ sdf }], STRIP_SMOOTH_DEG, {});
12165
12398
  over.userData.noFollow = true;
12166
12399
  piv.attach(over);
12167
12400
  }
@@ -12742,11 +12975,18 @@ var VehicleVisuals = class {
12742
12975
  }
12743
12976
  }
12744
12977
  }
12978
+ /** The lens of a light is holed or cracked and stays; `pierced`: a round went through it to the bulb, and the lamp goes dark. */
12979
+ crackLight(p, pierced) {
12980
+ const z = this.zone(p);
12981
+ if (!z) return;
12982
+ for (const L of z.lights) this.kit.crackLight(this.c, L, new this.THREE.Vector3(p.box.center[0] > 0 ? -1 : 1, 0, 0), pierced);
12983
+ }
12745
12984
  /** The lens of a light breaks and its unit stays (damaged) or goes (destroyed). */
12746
12985
  breakLight(p, gone) {
12747
12986
  const z = this.zone(p);
12748
12987
  if (!z) return;
12749
12988
  for (const L of z.lights) {
12989
+ L.lensMaterial = p.material;
12750
12990
  this.kit.breakLight(this.c, L, new this.THREE.Vector3(p.box.center[0] > 0 ? -1 : 1, 0, 0));
12751
12991
  if (gone) {
12752
12992
  if (L.group) L.group.visible = false;
@@ -13392,6 +13632,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
13392
13632
  wear(p, amount, out);
13393
13633
  syncZone(p);
13394
13634
  if (p.hp <= 0) lose(p, "broken", out, cause);
13635
+ else if (p.def.role === "light") vis?.crackLight(p.def, dtype === "ballistic");
13395
13636
  if (p.def.role === "tyre" && !p.flat && p.hp < p.def.hp * 0.6 && !p.lost) {
13396
13637
  p.flat = true;
13397
13638
  vis?.tyreFlat(p.def, 0.24);
@@ -14204,7 +14445,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
14204
14445
  if (!list.length) return 1;
14205
14446
  return Math.min(...list.map((p) => p.lost || !attached(p.def.id) ? 0 : p.hp / p.def.hp));
14206
14447
  };
14207
- const tyres = [...ps.values()].filter((p) => p.def.role === "tyre").map((p) => ({ x: p.def.box.center[0], z: p.def.box.center[2], health: tyreHealth(p) }));
14448
+ const tyres = [...ps.values()].filter((p) => p.def.role === "tyre").map((p) => ({ x: p.def.box.center[0], z: p.def.box.center[2], health: tyreHealth(p), rim: p.lost }));
14208
14449
  const status = computeStatus();
14209
14450
  const brakeSystem = [...ps.values()].find((p) => p.def.role === "brakes" && p.def.contains);
14210
14451
  const fluidLeft = brakeSystem ? clamp((brakeSystem.liters ?? 0) / brakeSystem.def.contains.liters / BRAKE_FLUID_NEEDED, 0, 1) : 1;