@adatechnology/game-vehicles 0.4.6 → 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.
package/README.md CHANGED
@@ -258,6 +258,12 @@ come in `Hit.ignition` (the weapons build them from the core and jacket of the a
258
258
  easiest, a full one mostly leaks); diesel takes a spark far worse than petrol. `createVehicleDestructible(kit, vehicle, { fuel: 0.1, ignition: { fillCurve, fireNear, sparkTarget } })`;
259
259
  the defaults and every number are `IgnitionRules` of `@adatechnology/game-contracts`.
260
260
 
261
+ Cabin and smoke: `cabinEnclosure(car, { frame })` is the cabin (`meta.cabin`, from `kit.interior`) as an `Enclosure` in world space, for the host's
262
+ `EffectSink.enclose`: a shut car keeps the smoke out and seeps only at `CABIN_LEAK` (2 % per second), more with cracked glass. It is open where a door
263
+ or lid stands open (past 6 % of its swing), where a window shattered and where a door, glass, roof or panel was torn off or burnt away; a bullet hole
264
+ lets smoke in at the hole at a rate that grows with its area (a 9 mm hole about 0.2 a second). `null` for a vehicle with no cabin (a bike) or one
265
+ destroyed. `createCabinSmoke()` of `game-vehicles-three` keeps a host told every frame.
266
+
261
267
  ## Português
262
268
 
263
269
  Motor genérico de veículos procedurais para three.js: carrocerias, vidros, faróis, rodas, interiores, capô e compartimento do motor, **zonas de dano
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ToolKind, JointKind, Vec3, MaterialId, DestructibleImpl, AnimationClipSpec, IgnitionRules } from '@adatechnology/game-contracts';
1
+ import { MaterialId, Vec3, JointKind, ToolKind, DestructibleImpl, AnimationClipSpec, IgnitionRules, ObjectState, Enclosure } from '@adatechnology/game-contracts';
2
2
 
3
3
  /**
4
4
  * Light mode ("lite"): the same vehicles with fewer triangles, for a whole street of them. Port of the `[LOD]` hooks of the team's Frota Leve
@@ -917,4 +917,44 @@ interface VehicleItem {
917
917
  */
918
918
  declare function buildVehicleItem(kit: VehicleKit, key: string, options?: VehicleItemOptions): VehicleItem;
919
919
 
920
- export { type Band, type BandId, type BandIndex, type BandOverride, type BuildFunction, type BuildOptions, type BuildState, type BuildZoneState, type DamageMapOptions, type DamageZone, type Damageable, type DamageableKind, type DamageableMaterial, type DamageableSide, 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 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, buildVehicleItem, buildVehicleModel, clamp, createLod, createTranslate, createVehicleDestructible, createVehicleKit, defaultDamageableKinds, defaultLodSettings, format, hash, legacyGlassId, legacyLightId, legacyZoneId, listLocales, registerLegacySeeds, registerLocale, resolveStrings, rng, smooth, strHash, wrapThreeForLod };
920
+ /**
921
+ * The cabin of a vehicle as a closed space for the smoke, dust and steam of the host's effects (`EffectSink.enclose` of the contracts): a shut car
922
+ * keeps the smoke out. It is open where it is in the real world: a door standing open, a window shattered, a door, glass or roof torn off or burnt
923
+ * away; a bullet hole lets a little in at a rate that grows with its size, and the gaps of a shut door let a trace in.
924
+ */
925
+
926
+ /** The cabin box a vehicle with an interior records (`kit.interior`), in the vehicle's own space: x along it, `z` the inner half width. */
927
+ interface CabinBox {
928
+ readonly x0: number;
929
+ readonly x1: number;
930
+ readonly y0: number;
931
+ readonly y1: number;
932
+ readonly z: number;
933
+ }
934
+ /** What the cabin enclosure reads of a vehicle: its parts and their state (any `DestructibleImpl` of a vehicle has these). */
935
+ interface CabinSource {
936
+ readonly vehicle: VehicleContext;
937
+ readonly model: VehicleModel;
938
+ state(): ObjectState;
939
+ }
940
+ interface CabinEnclosureOptions {
941
+ /** The object the destructible answers in (its `frame` option, where the host placed the vehicle): the holes of `state()` are in its space. Default `vehicle.root`. */
942
+ readonly frame?: any;
943
+ /** Gaps of the shut doors and vents, share per second of the smoke pressing on the closed shell (default {@link CABIN_LEAK}). */
944
+ readonly leak?: number;
945
+ /** How open a door or lid has to be (share of its full swing) to count as open (default 0.06: ajar). */
946
+ readonly ajar?: number;
947
+ }
948
+ /** The gaps of a shut car (door seals, the vents, the dash): about 2 % per second of what presses on it seeps in. */
949
+ declare const CABIN_LEAK = 0.02;
950
+ /** The cabin box of a vehicle, or `null` when it has none (a bike, a vehicle with no interior). */
951
+ declare function cabinBoxOf(vehicle: VehicleContext): CabinBox | null;
952
+ /** How far a door or lid stands open, 0 shut to 1 at the end of its clip: the turn of its drawn hinge from rest (the room poses it by the part id). */
953
+ declare function swingOf(vehicle: VehicleContext, partId: string): number;
954
+ /**
955
+ * The cabin of `source` as an `Enclosure` in world space, or `null` when there is no closed cabin (no interior, the vehicle exploded, every shell
956
+ * part gone). Call it each frame, or when the vehicle moves or changes, and hand it to `EffectSink.enclose`.
957
+ */
958
+ declare function cabinEnclosure(source: CabinSource, options?: CabinEnclosureOptions): Enclosure | null;
959
+
960
+ export { type Band, type BandId, type BandIndex, type BandOverride, type BuildFunction, type BuildOptions, type BuildState, type BuildZoneState, CABIN_LEAK, type CabinBox, type CabinEnclosureOptions, type CabinSource, type DamageMapOptions, type DamageZone, type Damageable, type DamageableKind, type DamageableMaterial, type DamageableSide, 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 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, buildVehicleItem, buildVehicleModel, cabinBoxOf, cabinEnclosure, clamp, createLod, createTranslate, createVehicleDestructible, createVehicleKit, defaultDamageableKinds, defaultLodSettings, format, hash, legacyGlassId, legacyLightId, legacyZoneId, listLocales, registerLegacySeeds, registerLocale, resolveStrings, rng, smooth, strHash, swingOf, wrapThreeForLod };
@@ -23,12 +23,15 @@ var GameVehicles = (() => {
23
23
  // src/index.ts
24
24
  var index_exports = {};
25
25
  __export(index_exports, {
26
+ CABIN_LEAK: () => CABIN_LEAK,
26
27
  JOINT_RULES: () => JOINT_RULES,
27
28
  LEGACY_STATES: () => LEGACY_STATES,
28
29
  LEGACY_ZONE_IDS: () => LEGACY_ZONE_IDS,
29
30
  SEED_SALTS: () => SEED_SALTS,
30
31
  buildVehicleItem: () => buildVehicleItem,
31
32
  buildVehicleModel: () => buildVehicleModel,
33
+ cabinBoxOf: () => cabinBoxOf,
34
+ cabinEnclosure: () => cabinEnclosure,
32
35
  clamp: () => clamp,
33
36
  createLod: () => createLod,
34
37
  createTranslate: () => createTranslate,
@@ -48,6 +51,7 @@ var GameVehicles = (() => {
48
51
  rng: () => rng,
49
52
  smooth: () => smooth,
50
53
  strHash: () => strHash,
54
+ swingOf: () => swingOf,
51
55
  wrapThreeForLod: () => wrapThreeForLod
52
56
  });
53
57
 
@@ -6661,8 +6665,15 @@ var GameVehicles = (() => {
6661
6665
  x.arc(cx, cy, 26 * s, 0, PI * 2);
6662
6666
  x.fill();
6663
6667
  }
6668
+ const NO_CANVAS = new Proxy(function() {
6669
+ }, { get: () => NO_CANVAS, apply: () => NO_CANVAS, set: () => true });
6664
6670
  function crackGlass(c, msh, p, force, sc) {
6665
6671
  const G = msh.userData.glass;
6672
+ if (G.cracksFreed) {
6673
+ drawWeb(NO_CANVAS, 0, 0, sc != null ? sc : 0.6 + force * 0.5, Math.max(0.4, G.aspect || 1), c.rand);
6674
+ G.hits++;
6675
+ return;
6676
+ }
6666
6677
  if (!G.cv) {
6667
6678
  G.cv = document2.createElement("canvas");
6668
6679
  G.cv.width = G.cv.height = 256;
@@ -6745,6 +6756,14 @@ var GameVehicles = (() => {
6745
6756
  const G = msh.userData.glass;
6746
6757
  if (G.broken) return;
6747
6758
  G.broken = true;
6759
+ const freeCracks = function() {
6760
+ if (!G.tex) return;
6761
+ if (msh.material !== MATERIALS.glassEdge && msh.material?.map === G.tex) msh.material.dispose();
6762
+ G.tex.dispose();
6763
+ G.tex = null;
6764
+ G.cv = null;
6765
+ G.cracksFreed = true;
6766
+ };
6748
6767
  const cubesAt = [];
6749
6768
  if (G.outline && G.T !== void 0) {
6750
6769
  const out = G.outline;
@@ -6778,6 +6797,7 @@ var GameVehicles = (() => {
6778
6797
  geo.computeVertexNormals();
6779
6798
  msh.geometry.dispose();
6780
6799
  msh.geometry = geo;
6800
+ freeCracks();
6781
6801
  msh.material = MATERIALS.glassEdge;
6782
6802
  let minx = 1e9;
6783
6803
  let maxx = -1e9;
@@ -6801,8 +6821,12 @@ var GameVehicles = (() => {
6801
6821
  if (rim) {
6802
6822
  msh.geometry.dispose();
6803
6823
  msh.geometry = rim;
6824
+ freeCracks();
6804
6825
  msh.material = MATERIALS.glassEdge;
6805
- } else msh.visible = false;
6826
+ } else {
6827
+ freeCracks();
6828
+ msh.visible = false;
6829
+ }
6806
6830
  for (let j = 0; j < 36; j++)
6807
6831
  cubesAt.push(
6808
6832
  new V3(
@@ -14496,6 +14520,90 @@ var GameVehicles = (() => {
14496
14520
  root.userData.vehicle = vehicle;
14497
14521
  return { root, vehicle };
14498
14522
  }
14523
+
14524
+ // src/destructible/enclosure.ts
14525
+ var CABIN_LEAK = 0.02;
14526
+ var HOLE_RATE_PER_AREA = 3e3;
14527
+ var CRACK_GAP_PER_METRE = 0.5;
14528
+ var THROUGH = 0.18;
14529
+ var HOLE_PATCH = 0.12;
14530
+ var FULL_SWING = 1.15;
14531
+ var SHELL_ROLES = /* @__PURE__ */ new Set(["door", "glass", "roof", "lid", "panel", "pillar"]);
14532
+ var GONE = /* @__PURE__ */ new Set(["broken", "melted", "ashes"]);
14533
+ function cabinBoxOf(vehicle) {
14534
+ const cabin = vehicle.meta.cabin;
14535
+ if (!cabin || !(cabin.x1 > cabin.x0) || !(cabin.y1 > cabin.y0) || !(cabin.z > 0)) return null;
14536
+ return cabin;
14537
+ }
14538
+ function swingOf(vehicle, partId) {
14539
+ const pivot = vehicle.vehicle?.getObjectByName?.(partId);
14540
+ const q = pivot?.quaternion;
14541
+ if (!q) return 0;
14542
+ const angle = 2 * Math.acos(Math.min(1, Math.abs(q.w)));
14543
+ return Math.min(1, angle / FULL_SWING);
14544
+ }
14545
+ var overlaps = (b, c, margin) => Math.abs(b.center[0] - (c.x0 + c.x1) / 2) <= b.size[0] / 2 + (c.x1 - c.x0) / 2 + margin && Math.abs(b.center[1] - (c.y0 + c.y1) / 2) <= b.size[1] / 2 + (c.y1 - c.y0) / 2 + margin && Math.abs(b.center[2]) <= b.size[2] / 2 + c.z + margin;
14546
+ function cabinEnclosure(source, options = {}) {
14547
+ const c = source.vehicle;
14548
+ const cabin = cabinBoxOf(c);
14549
+ const node = c.vehicle;
14550
+ if (!cabin || !node?.localToWorld) return null;
14551
+ const state = source.state();
14552
+ if (state.destroyed) return null;
14553
+ const V3 = node.position.constructor;
14554
+ const Quat = node.quaternion.constructor;
14555
+ const mid = [(cabin.x0 + cabin.x1) / 2, (cabin.y0 + cabin.y1) / 2, 0];
14556
+ const half = [(cabin.x1 - cabin.x0) / 2, (cabin.y1 - cabin.y0) / 2, cabin.z];
14557
+ const ajar = options.ajar ?? 0.06;
14558
+ const byId = new Map(state.parts.map((p) => [p.id, p]));
14559
+ const mainGroup = state.groups[0] ? new Set(state.groups[0]) : null;
14560
+ const openings = [];
14561
+ let crackLength = 0;
14562
+ const toCabin = (b) => ({
14563
+ center: [b.center[0] - mid[0], b.center[1] - mid[1], b.center[2] - mid[2]],
14564
+ halfSize: [b.size[0] / 2 + THROUGH, b.size[1] / 2 + THROUGH, b.size[2] / 2 + THROUGH]
14565
+ });
14566
+ node.updateWorldMatrix?.(true, false);
14567
+ const frame = options.frame ?? c.root;
14568
+ frame?.updateWorldMatrix?.(true, false);
14569
+ const inverse = node.matrixWorld.clone().invert();
14570
+ for (const def of source.model.parts) {
14571
+ if (!SHELL_ROLES.has(def.role) || !overlaps(def.box, cabin, 0.12)) continue;
14572
+ const part = byId.get(def.id);
14573
+ if (!part) continue;
14574
+ const gone = GONE.has(part.status) || mainGroup !== null && !mainGroup.has(def.id);
14575
+ const swung = (def.role === "door" || def.role === "lid") && swingOf(c, def.id) > ajar;
14576
+ if (gone || swung) {
14577
+ openings.push(toCabin(def.box));
14578
+ continue;
14579
+ }
14580
+ for (const hole of part.holes) {
14581
+ if (!hole.through && def.material !== "glass") continue;
14582
+ const area3 = Math.PI * (hole.diameter / 2) ** 2;
14583
+ const p = new V3(hole.point[0], hole.point[1], hole.point[2]);
14584
+ if (frame?.localToWorld) frame.localToWorld(p);
14585
+ p.applyMatrix4(inverse);
14586
+ openings.push({
14587
+ center: [p.x - mid[0], p.y - mid[1], p.z - mid[2]],
14588
+ halfSize: [HOLE_PATCH, HOLE_PATCH, HOLE_PATCH],
14589
+ // the patch is far bigger than the hole: only a share of what crosses it passes, more for a bigger hole
14590
+ rate: Math.min(4, area3 * HOLE_RATE_PER_AREA)
14591
+ });
14592
+ }
14593
+ for (const crack of part.cracks) crackLength += crack.length;
14594
+ }
14595
+ const center = node.localToWorld(new V3(mid[0], mid[1], mid[2]));
14596
+ const q = node.getWorldQuaternion(new Quat());
14597
+ const scale = node.getWorldScale(new V3());
14598
+ const leak = (options.leak ?? CABIN_LEAK) * (1 + crackLength * CRACK_GAP_PER_METRE);
14599
+ return {
14600
+ center: [center.x, center.y, center.z],
14601
+ halfSize: [half[0] * Math.abs(scale.x), half[1] * Math.abs(scale.y), half[2] * Math.abs(scale.z)],
14602
+ orientation: [q.x, q.y, q.z, q.w],
14603
+ openings: openings.map((o) => ({ ...o, center: [o.center[0] * scale.x, o.center[1] * scale.y, o.center[2] * scale.z], halfSize: [o.halfSize[0] * Math.abs(scale.x), o.halfSize[1] * Math.abs(scale.y), o.halfSize[2] * Math.abs(scale.z)] })),
14604
+ leak
14605
+ };
14606
+ }
14499
14607
  return __toCommonJS(index_exports);
14500
14608
  })();
14501
14609
  //# sourceMappingURL=index.global.js.map