@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.d.ts CHANGED
@@ -391,8 +391,11 @@ interface VehicleSpec {
391
391
  /** Fuel id (a key of `configure({ fuels })`) or `'electric'` / `'none'`. */
392
392
  fuel?: string;
393
393
  build?: BuildFunction;
394
+ /** What the headlights' lenses are made of (default `polycarbonate`, the lens of cars since the 1990s; `glass` on older ones): glass shatters, polycarbonate cracks. */
395
+ headlightLens?: HeadlightLens;
394
396
  [key: string]: any;
395
397
  }
398
+ type HeadlightLens = 'glass' | 'polycarbonate';
396
399
  /** Builds the geometry of a vehicle into the context. */
397
400
  type BuildFunction = (c: VehicleContext, kit: VehicleKit) => void;
398
401
  interface VehiclePart {
@@ -999,10 +1002,12 @@ interface VehicleDestructible extends DestructibleImpl {
999
1002
  crumple(point: Vec3, direction: Vec3, depth: number): void;
1000
1003
  }
1001
1004
  type DriveReport = {
1005
+ /** `rim`: the tyre is torn off and the bare rim is on the road. */
1002
1006
  readonly tyres: ReadonlyArray<{
1003
1007
  readonly x: number;
1004
1008
  readonly z: number;
1005
1009
  readonly health: number;
1010
+ readonly rim: boolean;
1006
1011
  }>;
1007
1012
  readonly brakes: number;
1008
1013
  readonly engine: number;
@@ -1122,4 +1127,4 @@ declare function cabinEnclosure(source: CabinSource, options?: CabinEnclosureOpt
1122
1127
  /** The material of a pane with cracks drawn on it. A host warms its program at load with any texture: the first crack is in the middle of a blast. */
1123
1128
  declare function crackedGlassMaterial(THREE: any, texture: unknown): any;
1124
1129
 
1125
- export { ARMOUR_LEVELS, type ArmourLevel, type ArmourLevelId, type Band, type BandId, type BandIndex, type BandOverride, type BuildFunction, type BuildOptions, type BuildState, type BuildZoneState, CABIN_LEAK, CRASH_RULES, type CabinBox, type CabinEnclosureOptions, type CabinSource, type CrashOptions, type CrashResult, type CrashRules, type CreaseQueue, type CreaseWork, type DamageMapOptions, type DamageZone, type Damageable, type DamageableKind, type DamageableMaterial, type DamageableSide, type DriveReport, type ExplodeOptions, type FlagEffect, type FluidDefinition, type ImpactOptions, JOINT_RULES, type JointDef, type KitConfiguration, type KitInternals, type KitTexts, LEGACY_STATES, LEGACY_ZONE_IDS, type LocalBox, type LocaleStrings, type LodSettings, type OverallHealth, type PartDef, type PartLabel, type PartRole, type PartialLocaleStrings, type ParticleEffectRequest, type ParticleEffects, type ParticleMaterials, type Plugin, type ResolvedConfiguration, SEED_SALTS, type ScenarioContext, type ScenarioDefinition, type ScenarioResult, type SplatOptions, type ThreeObject, type ThreeVector, type Translate, type UiHelpers, type VehicleContext, type VehicleDestructible, type VehicleDestructibleOptions, type VehicleItem, type VehicleItemOptions, type VehicleKit, type VehicleKitOptions, type VehicleMeta, type VehicleModel, type VehiclePart, type VehicleSpec, type VehicleStatus, type ZoneDamageEntry, type ZoneEffect, type ZoneState, armourLevelOf, buildVehicleItem, buildVehicleModel, cabinBoxOf, cabinEnclosure, clamp, crackedGlassMaterial, crashDeltaV, crashVehicle, createCreaseQueue, createLod, createTranslate, createVehicleDestructible, createVehicleKit, defaultDamageableKinds, defaultLodSettings, format, hash, legacyGlassId, legacyLightId, legacyZoneId, listLocales, registerLegacySeeds, registerLocale, resolveStrings, rng, smooth, strHash, swingOf, wrapThreeForLod };
1130
+ export { ARMOUR_LEVELS, type ArmourLevel, type ArmourLevelId, type Band, type BandId, type BandIndex, type BandOverride, type BuildFunction, type BuildOptions, type BuildState, type BuildZoneState, CABIN_LEAK, CRASH_RULES, type CabinBox, type CabinEnclosureOptions, type CabinSource, type CrashOptions, type CrashResult, type CrashRules, type CreaseQueue, type CreaseWork, type DamageMapOptions, type DamageZone, type Damageable, type DamageableKind, type DamageableMaterial, type DamageableSide, type DriveReport, type ExplodeOptions, type FlagEffect, type FluidDefinition, type HeadlightLens, type ImpactOptions, JOINT_RULES, type JointDef, type KitConfiguration, type KitInternals, type KitTexts, LEGACY_STATES, LEGACY_ZONE_IDS, type LocalBox, type LocaleStrings, type LodSettings, type OverallHealth, type PartDef, type PartLabel, type PartRole, type PartialLocaleStrings, type ParticleEffectRequest, type ParticleEffects, type ParticleMaterials, type Plugin, type ResolvedConfiguration, SEED_SALTS, type ScenarioContext, type ScenarioDefinition, type ScenarioResult, type SplatOptions, type ThreeObject, type ThreeVector, type Translate, type UiHelpers, type VehicleContext, type VehicleDestructible, type VehicleDestructibleOptions, type VehicleItem, type VehicleItemOptions, type VehicleKit, type VehicleKitOptions, type VehicleMeta, type VehicleModel, type VehiclePart, type VehicleSpec, type VehicleStatus, type ZoneDamageEntry, type ZoneEffect, type ZoneState, armourLevelOf, buildVehicleItem, buildVehicleModel, cabinBoxOf, cabinEnclosure, clamp, crackedGlassMaterial, crashDeltaV, crashVehicle, createCreaseQueue, createLod, createTranslate, createVehicleDestructible, createVehicleKit, defaultDamageableKinds, defaultLodSettings, format, hash, legacyGlassId, legacyLightId, legacyZoneId, listLocales, registerLegacySeeds, registerLocale, resolveStrings, rng, smooth, strHash, swingOf, wrapThreeForLod };
@@ -1451,6 +1451,7 @@ var GameVehicles = (() => {
1451
1451
  };
1452
1452
 
1453
1453
  // ../game-contracts/dist/index.js
1454
+ var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
1454
1455
  var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
1455
1456
  var DEFAULT_IGNITION_RULES = {
1456
1457
  fillCurve: [
@@ -7162,8 +7163,20 @@ var GameVehicles = (() => {
7162
7163
  if (!L || L.broken) return;
7163
7164
  L.broken = true;
7164
7165
  L.lens.forEach(function(m) {
7165
- m.visible = false;
7166
+ const rim = L.lensMaterial ? glassRim(m.geometry, c.rand) : null;
7167
+ if (!rim) {
7168
+ m.visible = false;
7169
+ return;
7170
+ }
7171
+ m.geometry.dispose();
7172
+ m.geometry = rim;
7173
+ m.material = MATERIALS.glassEdge;
7174
+ m.userData.lensRim = true;
7166
7175
  });
7176
+ if (L.lensMaterial)
7177
+ (L.reflector || []).forEach(function(m) {
7178
+ m.material = MATERIALS.broken;
7179
+ });
7167
7180
  L.inner.forEach(function(m) {
7168
7181
  m.material = MATERIALS.bulbBroken;
7169
7182
  });
@@ -7171,11 +7184,38 @@ var GameVehicles = (() => {
7171
7184
  h.visible = false;
7172
7185
  });
7173
7186
  const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
7174
- I.burst(c, "lens", c.toEffects(p), 10, { dir: c.dirToEffects(dir.clone().negate()), speed: 1.4, variant: L.tint === "red" ? "red" : "clear" });
7175
- I.burst(c, "plastic", c.toEffects(p), 2, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
7187
+ const glass = L.lensMaterial !== "plastic";
7188
+ 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" });
7189
+ I.burst(c, "plastic", c.toEffects(p), glass ? 2 : 5, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
7176
7190
  }
7177
7191
  kit.breakLight = breakLight;
7178
- Object.assign(I, { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight });
7192
+ const crazedCache = /* @__PURE__ */ new Map();
7193
+ const CRAZED_OPACITY = 0.6;
7194
+ function crazed(material) {
7195
+ const known = crazedCache.get(material);
7196
+ if (known) return known;
7197
+ const m = I.crackLens(material).clone();
7198
+ m.opacity = Math.max(m.opacity ?? 1, CRAZED_OPACITY);
7199
+ m.transparent = true;
7200
+ m.userData.shared = true;
7201
+ crazedCache.set(material, m);
7202
+ return m;
7203
+ }
7204
+ function crackLight(c, L, dir, pierced) {
7205
+ if (!L || L.broken) return;
7206
+ if (!L.cracked) {
7207
+ L.cracked = true;
7208
+ for (const m of L.lens) if (m.material && !Array.isArray(m.material)) m.material = crazed(m.material);
7209
+ }
7210
+ const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
7211
+ I.burst(c, "lens", c.toEffects(p), 3, { dir: c.dirToEffects(dir.clone().negate()), speed: 0.9, variant: L.tint === "red" ? "red" : "clear" });
7212
+ if (!pierced || L.dark) return;
7213
+ L.dark = true;
7214
+ for (const m of L.inner) m.material = MATERIALS.bulbBroken;
7215
+ for (const h of L.halo ?? []) h.visible = false;
7216
+ }
7217
+ kit.crackLight = crackLight;
7218
+ Object.assign(I, { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight, crackLight });
7179
7219
  }
7180
7220
 
7181
7221
  // src/impact.ts
@@ -12725,6 +12765,8 @@ var GameVehicles = (() => {
12725
12765
  heli: ["battery", "alternator", "gearbox"]
12726
12766
  };
12727
12767
  var HITCH_CHANCE = { car: 30, truck: 60, bus: 0, tractor: 100, quad: 40, bike: 0, tank: 0, boat: 0, plane: 0, heli: 0 };
12768
+ var GLASS_LENS_HP = 320;
12769
+ var PLASTIC_LENS_HP = 900;
12728
12770
  var STEEL_KG_M2 = 7;
12729
12771
  var ALU_KG_M2 = 3.2;
12730
12772
  var kindOf = (c) => {
@@ -12882,7 +12924,9 @@ var GameVehicles = (() => {
12882
12924
  continue;
12883
12925
  }
12884
12926
  if (role === "light") {
12885
- add({ id, role, name: label, material: "glass", box: box2, mass: 1.2 + 4 * area3, hp: 320, zoneId: id, visual: true, tags: ["light", ...tags] });
12927
+ const lens = /^head/.test(id) ? spec.headlightLens ?? "polycarbonate" : "acrylic";
12928
+ const glassLens = lens === "glass";
12929
+ add({ id, role, name: label, material: glassLens ? "glass" : "plastic", box: box2, mass: 1.2 + 4 * area3, hp: glassLens ? GLASS_LENS_HP : PLASTIC_LENS_HP, zoneId: id, visual: true, tags: ["light", `lens_${lens}`, ...tags] });
12886
12930
  continue;
12887
12931
  }
12888
12932
  if (role === "bumper") {
@@ -13339,21 +13383,188 @@ var GameVehicles = (() => {
13339
13383
  mesh2.geometry = g;
13340
13384
  }
13341
13385
 
13386
+ // src/destructible/door-open.ts
13387
+ var STRIP_SMOOTH_DEG = 180;
13388
+ var SKIN_BAND = 0.05;
13389
+ var BODY_FACING = 0.5;
13390
+ var FIELDS = 4;
13391
+ var SEAM_THIN = 0.03;
13392
+ var SEAM_AT_EDGE = 0.04;
13393
+ var SEAM_TALL = 0.4;
13394
+ var SLIVER_AREA = 1e-10;
13395
+ function cutStripOverDoor(vehicleKit, params) {
13396
+ const kit = vehicleKit;
13397
+ const strip = params.strip;
13398
+ const toVehicle = params.toVehicle;
13399
+ const { x0, x1 } = params;
13400
+ const THREE = kit.THREE;
13401
+ const at = new THREE.Vector3();
13402
+ const along = (x, y, z) => at.set(x, y, z).applyMatrix4(toVehicle).x;
13403
+ const piece = (tests) => {
13404
+ const mesh2 = new THREE.Mesh(strip.geometry.clone(), strip.material);
13405
+ kit.cutMesh(mesh2, tests, STRIP_SMOOTH_DEG, {});
13406
+ return mesh2;
13407
+ };
13408
+ const over = piece([{ sdf: (x, y, z) => along(x, y, z) - x0 }, { sdf: (x, y, z) => x1 - along(x, y, z) }]);
13409
+ const after = piece([{ sdf: (x, y, z) => along(x, y, z) - x1 }]);
13410
+ kit.cutMesh(strip, [{ sdf: (x, y, z) => x0 - along(x, y, z) }], STRIP_SMOOTH_DEG, {});
13411
+ const joined = joinTriangles(THREE, strip.geometry, after.geometry);
13412
+ strip.geometry.dispose();
13413
+ after.geometry.dispose();
13414
+ strip.geometry = joined;
13415
+ return over;
13416
+ }
13417
+ function joinTriangles(THREE, first, second) {
13418
+ const a = first.index ? first.toNonIndexed() : first;
13419
+ const b = second.index ? second.toNonIndexed() : second;
13420
+ const flags = [...solidTriangles(THREE, a), ...solidTriangles(THREE, b)];
13421
+ const both = new THREE.BufferGeometry();
13422
+ for (const name of Object.keys(a.attributes)) {
13423
+ const p = a.getAttribute(name);
13424
+ const q = b.getAttribute(name);
13425
+ if (!q || q.itemSize !== p.itemSize) continue;
13426
+ const array = new Float32Array(p.array.length + q.array.length);
13427
+ array.set(p.array, 0);
13428
+ array.set(q.array, p.array.length);
13429
+ both.setAttribute(name, new THREE.Float32BufferAttribute(array, p.itemSize));
13430
+ }
13431
+ if (a !== first) a.dispose();
13432
+ if (b !== second) b.dispose();
13433
+ const out = pickTriangles(THREE, both, flags, true) ?? new THREE.BufferGeometry();
13434
+ both.dispose();
13435
+ return out;
13436
+ }
13437
+ function solidTriangles(THREE, geometry) {
13438
+ const pos = geometry.getAttribute("position");
13439
+ const triangle = new THREE.Triangle();
13440
+ const flags = [];
13441
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
13442
+ triangle.a.fromBufferAttribute(pos, t * 3);
13443
+ triangle.b.fromBufferAttribute(pos, t * 3 + 1);
13444
+ triangle.c.fromBufferAttribute(pos, t * 3 + 2);
13445
+ flags.push(triangle.getArea() > SLIVER_AREA);
13446
+ }
13447
+ return flags;
13448
+ }
13449
+ function splitDoorSkin(three, meshes, toVehicleOf, side2, depth) {
13450
+ const THREE = three;
13451
+ const pieces = meshes;
13452
+ const toVehicle = toVehicleOf;
13453
+ const v = new THREE.Vector3();
13454
+ const corners = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
13455
+ const triangle = new THREE.Triangle();
13456
+ const centres = pieces.map((mesh2) => {
13457
+ const geometry = mesh2.geometry.index ? mesh2.geometry.toNonIndexed() : mesh2.geometry;
13458
+ const matrix = toVehicle(mesh2);
13459
+ const pos = geometry.getAttribute("position");
13460
+ const out = new Float32Array(pos.count / 3 * FIELDS);
13461
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
13462
+ let y = 0;
13463
+ let across = 0;
13464
+ for (let k = 0; k < 3; k++) {
13465
+ corners[k].fromBufferAttribute(pos, t * 3 + k).applyMatrix4(matrix);
13466
+ y += corners[k].y / 3;
13467
+ across += side2 * corners[k].z / 3;
13468
+ }
13469
+ out[t * FIELDS] = y;
13470
+ out[t * FIELDS + 1] = across;
13471
+ triangle.set(corners[0], corners[1], corners[2]).getNormal(v);
13472
+ out[t * FIELDS + 2] = v.y;
13473
+ out[t * FIELDS + 3] = v.x;
13474
+ }
13475
+ return { geometry, out };
13476
+ });
13477
+ const outer = /* @__PURE__ */ new Map();
13478
+ for (const { out } of centres)
13479
+ for (let t = 0; t * FIELDS < out.length; t++) {
13480
+ const band = Math.floor(out[t * FIELDS] / SKIN_BAND);
13481
+ outer.set(band, Math.max(outer.get(band) ?? -Infinity, out[t * FIELDS + 1]));
13482
+ }
13483
+ const surfaceAt = (y) => {
13484
+ const band = Math.floor(y / SKIN_BAND);
13485
+ return Math.max(outer.get(band - 1) ?? -Infinity, outer.get(band) ?? -Infinity, outer.get(band + 1) ?? -Infinity);
13486
+ };
13487
+ return centres.map(({ geometry, out }, i) => {
13488
+ const near = [];
13489
+ for (let t = 0; t * FIELDS < out.length; t++) {
13490
+ const f = t * FIELDS;
13491
+ const body = out[f + 1] < surfaceAt(out[f]) - depth && (out[f + 2] < -BODY_FACING || Math.abs(out[f + 3]) > BODY_FACING);
13492
+ near.push(!body);
13493
+ }
13494
+ const result = { skin: pickTriangles(THREE, geometry, near, true), deep: pickTriangles(THREE, geometry, near, false) };
13495
+ if (geometry !== pieces[i].geometry) geometry.dispose();
13496
+ return result;
13497
+ });
13498
+ }
13499
+ function leaveDeepSkinOnBody(three, meshes, params) {
13500
+ const THREE = three;
13501
+ const pieces = meshes;
13502
+ const toVehicle = params.toVehicle;
13503
+ const { side: side2, depth } = params;
13504
+ const split = splitDoorSkin(THREE, pieces, (mesh2) => new THREE.Matrix4().multiplyMatrices(toVehicle, mesh2.matrixWorld), side2, depth);
13505
+ split.forEach(({ skin, deep }, i) => {
13506
+ const piece = pieces[i];
13507
+ if (!deep || !skin || !piece.parent) return;
13508
+ const body = new THREE.Mesh(deep, piece.material);
13509
+ body.position.copy(piece.position);
13510
+ body.quaternion.copy(piece.quaternion);
13511
+ body.scale.copy(piece.scale);
13512
+ body.castShadow = piece.castShadow;
13513
+ body.receiveShadow = piece.receiveShadow;
13514
+ piece.parent.add(body);
13515
+ piece.geometry.dispose();
13516
+ piece.geometry = skin;
13517
+ });
13518
+ }
13519
+ function pickTriangles(THREE, geometry, flags, wanted) {
13520
+ const count = flags.filter((flag) => flag === wanted).length;
13521
+ if (!count) return null;
13522
+ const out = new THREE.BufferGeometry();
13523
+ for (const name of Object.keys(geometry.attributes)) {
13524
+ const attribute = geometry.getAttribute(name);
13525
+ const size = attribute.itemSize;
13526
+ const source = attribute.array;
13527
+ const array = new Float32Array(count * 3 * size);
13528
+ let w = 0;
13529
+ flags.forEach((flag, t) => {
13530
+ if (flag !== wanted) return;
13531
+ array.set(source.subarray(t * 3 * size, (t + 1) * 3 * size), w);
13532
+ w += 3 * size;
13533
+ });
13534
+ out.setAttribute(name, new THREE.Float32BufferAttribute(array, size));
13535
+ }
13536
+ out.computeBoundingBox();
13537
+ out.computeBoundingSphere();
13538
+ return out;
13539
+ }
13540
+ function isDoorSeam(three, params) {
13541
+ const THREE = three;
13542
+ const box2 = params.box;
13543
+ const { zone, side: side2, dark } = params;
13544
+ if (!dark || box2.isEmpty()) return false;
13545
+ const size = box2.getSize(new THREE.Vector3());
13546
+ const centre = box2.getCenter(new THREE.Vector3());
13547
+ 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;
13548
+ if (centre.y < zone.y0 || centre.y > zone.y1) return false;
13549
+ return Math.abs(centre.x - zone.x0) < SEAM_AT_EDGE || Math.abs(centre.x - zone.x1) < SEAM_AT_EDGE;
13550
+ }
13551
+
13342
13552
  // src/destructible/visual.ts
13343
13553
  var HOLE_DECAL_SCALE = 2.4;
13344
13554
  var DOOR_DEPTH = 0.07;
13555
+ var DOOR_SKIN_DEPTH = 0.09;
13556
+ var DOOR_FLOOR_DEPTH = 0.2;
13345
13557
  var DOOR_EDGE_PIECES = 8;
13346
13558
  var DOOR_EDGE_REACH = 0.08;
13347
13559
  var DOOR_CARD_COLOR = "#3b3e42";
13348
13560
  var DOOR_LINER_COLOR = "#5a5e64";
13349
13561
  var DOOR_LINER_DARK = 0.15;
13350
- var DOOR_STRIP_THIN = 0.08;
13562
+ var DOOR_STRIP_THIN = 0.13;
13351
13563
  var DOOR_STRIP_NEAR = 0.04;
13352
13564
  var DOOR_STRIP_TALL = 0.22;
13353
- var STRIP_SMOOTH_DEG = 180;
13354
- var DOOR_STRIP_VERTICES = 2e4;
13565
+ var DOOR_STRIP_VERTICES = 6e4;
13355
13566
  var DOOR_SILL = 0.25;
13356
- var DOOR_STRIP_OVER = 0.3;
13567
+ var DOOR_STRIP_OVER = 0.05;
13357
13568
  var DOOR_TOP_OVER_PANE = 0.03;
13358
13569
  var HOLE_TEXTURE_SIZE = 64;
13359
13570
  var LAMINATED_HP = 1e3;
@@ -13462,6 +13673,10 @@ var GameVehicles = (() => {
13462
13673
  __publicField(this, "skins", /* @__PURE__ */ new Map());
13463
13674
  /** How high each door's cut reaches. */
13464
13675
  __publicField(this, "doorTops", /* @__PURE__ */ new Map());
13676
+ /** 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. */
13677
+ __publicField(this, "seams", /* @__PURE__ */ new Map());
13678
+ /** Doors and lids standing open now. */
13679
+ __publicField(this, "openNow", /* @__PURE__ */ new Set());
13465
13680
  __publicField(this, "lightsOn", true);
13466
13681
  /** Heavy body meshes whose triangle index is still to build (null once done). */
13467
13682
  __publicField(this, "warming");
@@ -13643,6 +13858,11 @@ var GameVehicles = (() => {
13643
13858
  }
13644
13859
  panel.visible = open && !this.skins.get(id);
13645
13860
  recess.visible = open && !this.skins.get(id);
13861
+ if (open) this.openNow.add(id);
13862
+ else this.openNow.delete(id);
13863
+ this.seams.forEach((doors, seam) => {
13864
+ if (doors.has(id)) seam.visible = ![...doors].some((door) => this.openNow.has(door));
13865
+ });
13646
13866
  };
13647
13867
  const euler = piv.rotation;
13648
13868
  const quaternion = piv.quaternion;
@@ -13685,11 +13905,13 @@ var GameVehicles = (() => {
13685
13905
  const pcs = I.cutPanel(this.c, z, rect, true, dirty, { anyN: true }).map((x) => x.mesh).filter((m) => !!m);
13686
13906
  this.mend(dirty);
13687
13907
  if (pcs.length) {
13908
+ leaveDeepSkinOnBody(this.THREE, pcs, { toVehicle: inv, side: z.side < 0 ? -1 : 1, depth: DOOR_SKIN_DEPTH });
13688
13909
  const panes = I.windowsOver(this.c, z, inv);
13689
13910
  const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
13690
13911
  const mirrors = this.mirrorsOn(z);
13691
13912
  const frames = this.framesAround(panes, inv);
13692
- objs = [...pcs, ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors, ...frames];
13913
+ const followers = I.followersIn(this.c, z, inv).filter((o) => !this.takeSeam(o, id, z, inv) && !this.inFloor(o, piv, z, inv));
13914
+ objs = [...pcs, ...followers, ...panes, ...seals, ...mirrors, ...frames];
13693
13915
  }
13694
13916
  } else {
13695
13917
  const pcs = this.cutZone(z);
@@ -13717,6 +13939,25 @@ var GameVehicles = (() => {
13717
13939
  this.c.root.updateMatrixWorld(true);
13718
13940
  return objs.length > 0;
13719
13941
  }
13942
+ /** 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. */
13943
+ inFloor(o, piv, z, inv) {
13944
+ const side2 = z.side < 0 ? -1 : 1;
13945
+ const centre = this.I.localBox(this.c, o, inv).getCenter(new this.THREE.Vector3());
13946
+ const skin = side2 * (piv.position.z - side2 * 0.016);
13947
+ return centre.y < z.box.y0 + DOOR_SILL && side2 * centre.z < skin - DOOR_FLOOR_DEPTH;
13948
+ }
13949
+ /** 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. */
13950
+ takeSeam(o, id, z, inv) {
13951
+ const B = z.box;
13952
+ const box2 = this.I.localBox(this.c, o, inv);
13953
+ const color2 = o.material?.color;
13954
+ const dark = !!color2 && color2.getHSL({ h: 0, s: 0, l: 0 }).l < DOOR_LINER_DARK;
13955
+ if (!o.isMesh || !isDoorSeam(this.THREE, { box: box2, zone: { x0: B.x0, x1: B.x1, y0: B.y0, y1: B.y1 }, side: z.side, dark })) return false;
13956
+ const doors = this.seams.get(o) ?? /* @__PURE__ */ new Set();
13957
+ doors.add(id);
13958
+ this.seams.set(o, doors);
13959
+ return true;
13960
+ }
13720
13961
  /** 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. */
13721
13962
  doorEdges(piv, z, y1) {
13722
13963
  const THREE = this.THREE;
@@ -13738,13 +13979,15 @@ var GameVehicles = (() => {
13738
13979
  const at = this.surface("z", side2, B, [[x, y]]);
13739
13980
  return at != null && Math.abs(at - flat) < DOOR_EDGE_REACH ? at : flat;
13740
13981
  });
13741
- const band = (lift) => {
13982
+ const band = (lift, path, closed) => {
13742
13983
  const position = [];
13743
13984
  const index = [];
13744
- loop.forEach(([x, y], i) => {
13745
- position.push(x, y, heights[i] + side2 * lift, x, y, heights[i] + side2 * (lift - DOOR_DEPTH));
13985
+ path.forEach((at, i) => {
13986
+ const [x, y] = loop[at];
13987
+ position.push(x, y, heights[at] + side2 * lift, x, y, heights[at] + side2 * (lift - DOOR_DEPTH));
13988
+ if (!closed && i === path.length - 1) return;
13746
13989
  const a = i * 2;
13747
- const b = (i + 1) % loop.length * 2;
13990
+ const b = (i + 1) % path.length * 2;
13748
13991
  index.push(a, a + 1, b, b, a + 1, b + 1);
13749
13992
  });
13750
13993
  const g = new THREE.BufferGeometry();
@@ -13772,8 +14015,11 @@ var GameVehicles = (() => {
13772
14015
  card.castShadow = false;
13773
14016
  this.c.vehicle.add(card);
13774
14017
  piv.attach(card);
13775
- const edges = new THREE.Mesh(band(2e-3), paint);
13776
- const lining = new THREE.Mesh(band(2e-3), paint);
14018
+ const all = loop.map((_, i) => i);
14019
+ const top = 2 * DOOR_EDGE_PIECES;
14020
+ const sides = [...all.slice(3 * DOOR_EDGE_PIECES), ...all.slice(0, top + 1)];
14021
+ const edges = new THREE.Mesh(band(2e-3, all, true), paint);
14022
+ const lining = new THREE.Mesh(band(2e-3, sides, false), paint);
13777
14023
  for (const m of [edges, lining]) {
13778
14024
  m.userData.noFollow = true;
13779
14025
  m.castShadow = false;
@@ -13830,26 +14076,14 @@ var GameVehicles = (() => {
13830
14076
  const box2 = this.I.localBox(this.c, o, inv);
13831
14077
  const size = box2.getSize(new THREE.Vector3());
13832
14078
  const mid = box2.getCenter(new THREE.Vector3());
13833
- if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box2.max.x, x1) - Math.max(box2.min.x, x0) < (x1 - x0) * DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
14079
+ if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box2.max.x, x1) - Math.max(box2.min.x, x0) < DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
13834
14080
  const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
13835
- const v = new THREE.Vector3();
13836
- const sdf = (x, y, zz) => {
13837
- v.set(x, y, zz).applyMatrix4(toVehicle);
13838
- return Math.max(x0 - v.x, v.x - x1);
13839
- };
13840
- const over = new THREE.Mesh(o.geometry.clone(), o.material);
14081
+ const over = cutStripOverDoor(this.kit, { strip: o, toVehicle, x0, x1 });
13841
14082
  over.position.copy(o.position);
13842
14083
  over.quaternion.copy(o.quaternion);
13843
14084
  over.scale.copy(o.scale);
13844
14085
  this.c.vehicle.add(over);
13845
14086
  over.updateMatrixWorld(true);
13846
- const overToVehicle = new THREE.Matrix4().multiplyMatrices(inv, over.matrixWorld);
13847
- const w = new THREE.Vector3();
13848
- this.kit.cutMesh(over, [{ sdf: (x, y, zz) => {
13849
- w.set(x, y, zz).applyMatrix4(overToVehicle);
13850
- return -Math.max(x0 - w.x, w.x - x1);
13851
- } }], STRIP_SMOOTH_DEG, {});
13852
- this.kit.cutMesh(o, [{ sdf }], STRIP_SMOOTH_DEG, {});
13853
14087
  over.userData.noFollow = true;
13854
14088
  piv.attach(over);
13855
14089
  }
@@ -14430,11 +14664,18 @@ var GameVehicles = (() => {
14430
14664
  }
14431
14665
  }
14432
14666
  }
14667
+ /** 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. */
14668
+ crackLight(p, pierced) {
14669
+ const z = this.zone(p);
14670
+ if (!z) return;
14671
+ 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);
14672
+ }
14433
14673
  /** The lens of a light breaks and its unit stays (damaged) or goes (destroyed). */
14434
14674
  breakLight(p, gone) {
14435
14675
  const z = this.zone(p);
14436
14676
  if (!z) return;
14437
14677
  for (const L of z.lights) {
14678
+ L.lensMaterial = p.material;
14438
14679
  this.kit.breakLight(this.c, L, new this.THREE.Vector3(p.box.center[0] > 0 ? -1 : 1, 0, 0));
14439
14680
  if (gone) {
14440
14681
  if (L.group) L.group.visible = false;
@@ -15080,6 +15321,7 @@ var GameVehicles = (() => {
15080
15321
  wear(p, amount, out);
15081
15322
  syncZone(p);
15082
15323
  if (p.hp <= 0) lose(p, "broken", out, cause);
15324
+ else if (p.def.role === "light") vis?.crackLight(p.def, dtype === "ballistic");
15083
15325
  if (p.def.role === "tyre" && !p.flat && p.hp < p.def.hp * 0.6 && !p.lost) {
15084
15326
  p.flat = true;
15085
15327
  vis?.tyreFlat(p.def, 0.24);
@@ -15892,7 +16134,7 @@ var GameVehicles = (() => {
15892
16134
  if (!list2.length) return 1;
15893
16135
  return Math.min(...list2.map((p) => p.lost || !attached(p.def.id) ? 0 : p.hp / p.def.hp));
15894
16136
  };
15895
- 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) }));
16137
+ 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 }));
15896
16138
  const status = computeStatus();
15897
16139
  const brakeSystem = [...ps.values()].find((p) => p.def.role === "brakes" && p.def.contains);
15898
16140
  const fluidLeft = brakeSystem ? clamp((brakeSystem.liters ?? 0) / brakeSystem.def.contains.liters / BRAKE_FLUID_NEEDED, 0, 1) : 1;