@adatechnology/game-vehicles 0.6.0 → 0.6.1
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 +105 -3
- package/dist/index.global.js +753 -107
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +747 -101
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3188,6 +3188,7 @@ function installInterior(kit) {
|
|
|
3188
3188
|
}
|
|
3189
3189
|
|
|
3190
3190
|
// src/hood.ts
|
|
3191
|
+
var HOOD_LIFT = 8e-3;
|
|
3191
3192
|
function installHood(kit) {
|
|
3192
3193
|
const THREE = kit.THREE;
|
|
3193
3194
|
const V3 = THREE.Vector3;
|
|
@@ -3210,7 +3211,7 @@ function installHood(kit) {
|
|
|
3210
3211
|
for (let i = 0; i < p.count; i++) {
|
|
3211
3212
|
p.setX(i, cx + (p.getX(i) - cx) * sx);
|
|
3212
3213
|
p.setZ(i, p.getZ(i) * sz);
|
|
3213
|
-
p.setY(i, p.getY(i) +
|
|
3214
|
+
p.setY(i, p.getY(i) + HOOD_LIFT);
|
|
3214
3215
|
}
|
|
3215
3216
|
geo.translate(-hx, -hy, 0);
|
|
3216
3217
|
const under = geo.clone();
|
|
@@ -3239,7 +3240,8 @@ function installHood(kit) {
|
|
|
3239
3240
|
yMin: bb.min.y,
|
|
3240
3241
|
zw: bb.max.z,
|
|
3241
3242
|
label: o.label,
|
|
3242
|
-
noLatch: !!o.noLatch
|
|
3243
|
+
noLatch: !!o.noLatch,
|
|
3244
|
+
lift: HOOD_LIFT
|
|
3243
3245
|
};
|
|
3244
3246
|
c.part("hood", { key: "hood" }, piv, new V3((hx + bb.max.x) / 2, hy, 0), 0.9);
|
|
3245
3247
|
return c.hood;
|
|
@@ -4675,6 +4677,14 @@ function installParticles(kit) {
|
|
|
4675
4677
|
}
|
|
4676
4678
|
|
|
4677
4679
|
// src/fluids.ts
|
|
4680
|
+
var PUDDLE_COLORS = {
|
|
4681
|
+
coolant: 3123279,
|
|
4682
|
+
brake_fluid: 12093738,
|
|
4683
|
+
transmission_fluid: 10099746,
|
|
4684
|
+
power_steering_fluid: 9054768,
|
|
4685
|
+
washer_fluid: 2977744,
|
|
4686
|
+
battery_acid: 14211272
|
|
4687
|
+
};
|
|
4678
4688
|
function installFluids(kit) {
|
|
4679
4689
|
const THREE = kit.THREE;
|
|
4680
4690
|
const V3 = THREE.Vector3;
|
|
@@ -4894,6 +4904,13 @@ function installFluids(kit) {
|
|
|
4894
4904
|
transparent: true,
|
|
4895
4905
|
opacity: 0.9,
|
|
4896
4906
|
depthWrite: false
|
|
4907
|
+
}) : PUDDLE_COLORS[kind] ? new THREE.MeshStandardMaterial({
|
|
4908
|
+
color: PUDDLE_COLORS[kind],
|
|
4909
|
+
roughness: 0.05,
|
|
4910
|
+
metalness: 0.1,
|
|
4911
|
+
transparent: true,
|
|
4912
|
+
opacity: 0.7,
|
|
4913
|
+
depthWrite: false
|
|
4897
4914
|
}) : new THREE.MeshStandardMaterial({
|
|
4898
4915
|
color: 0,
|
|
4899
4916
|
roughness: 0.03,
|
|
@@ -5739,12 +5756,36 @@ function installImpact(kit) {
|
|
|
5739
5756
|
if (g.userData.crease && c.smoothLater) c.smoothLater(msh);
|
|
5740
5757
|
else if (g.userData.crease) crease(g, 42);
|
|
5741
5758
|
else g.computeVertexNormals();
|
|
5742
|
-
g.computeBoundingBox();
|
|
5743
5759
|
const sphere = g.boundingSphere ?? (g.boundingSphere = new THREE.Sphere());
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5760
|
+
if (pos.isInterleavedBufferAttribute || pos.normalized || Object.keys(g.morphAttributes).length > 0) {
|
|
5761
|
+
g.computeBoundingBox();
|
|
5762
|
+
g.boundingBox.getCenter(sphere.center);
|
|
5763
|
+
let far2 = 0;
|
|
5764
|
+
for (let i = 0; i < pos.count; i++) far2 = Math.max(far2, sphere.center.distanceToSquared(v.fromBufferAttribute(pos, i)));
|
|
5765
|
+
sphere.radius = Math.sqrt(far2);
|
|
5766
|
+
} else {
|
|
5767
|
+
const box2 = g.boundingBox ?? (g.boundingBox = new THREE.Box3());
|
|
5768
|
+
let x0 = Infinity, y0 = Infinity, z0 = Infinity, x1 = -Infinity, y1 = -Infinity, z1 = -Infinity;
|
|
5769
|
+
for (let i = 0; i < pos.count * 3; i += 3) {
|
|
5770
|
+
const x = arr[i], y = arr[i + 1], z = arr[i + 2];
|
|
5771
|
+
if (x < x0) x0 = x;
|
|
5772
|
+
if (y < y0) y0 = y;
|
|
5773
|
+
if (z < z0) z0 = z;
|
|
5774
|
+
if (x > x1) x1 = x;
|
|
5775
|
+
if (y > y1) y1 = y;
|
|
5776
|
+
if (z > z1) z1 = z;
|
|
5777
|
+
}
|
|
5778
|
+
box2.min.set(x0, y0, z0);
|
|
5779
|
+
box2.max.set(x1, y1, z1);
|
|
5780
|
+
box2.getCenter(sphere.center);
|
|
5781
|
+
const cx = sphere.center.x, cy = sphere.center.y, cz = sphere.center.z;
|
|
5782
|
+
let far2 = 0;
|
|
5783
|
+
for (let i = 0; i < pos.count * 3; i += 3) {
|
|
5784
|
+
const dx = cx - arr[i], dy = cy - arr[i + 1], dz = cz - arr[i + 2];
|
|
5785
|
+
far2 = Math.max(far2, dx * dx + dy * dy + dz * dz);
|
|
5786
|
+
}
|
|
5787
|
+
sphere.radius = Math.sqrt(far2);
|
|
5788
|
+
}
|
|
5748
5789
|
}
|
|
5749
5790
|
});
|
|
5750
5791
|
const dset = new Set(c.deform);
|
|
@@ -6148,7 +6189,8 @@ function installExplosion(kit) {
|
|
|
6148
6189
|
const G = gm2.userData.glass;
|
|
6149
6190
|
if (!G || G.broken || !gm2.visible || !gm2.parent) return;
|
|
6150
6191
|
if (G.type === "laminated") {
|
|
6151
|
-
|
|
6192
|
+
for (let k2 = 0; k2 < 3; k2++) I.crackGlass(c, gm2, I.glassPt ? I.glassPt(c, gm2, c.rand) : gm2.position.clone(), 1.2);
|
|
6193
|
+
return;
|
|
6152
6194
|
}
|
|
6153
6195
|
I.shatterGlass(c, gm2, 1.4, new V3(0, 1, 0));
|
|
6154
6196
|
});
|
|
@@ -6159,19 +6201,6 @@ function installExplosion(kit) {
|
|
|
6159
6201
|
fling(c, c.hood.piv, new V3(rv(1, 3), rv(6, 8.5), rv(-1.5, 1.5)), new V3(rv(-4, 4), rv(-2, 2), rv(3, 7)));
|
|
6160
6202
|
c.hood = null;
|
|
6161
6203
|
}
|
|
6162
|
-
const W = m.width || 1.6;
|
|
6163
|
-
const gh = m.glasshouse;
|
|
6164
|
-
if (m.kind === "car" && gh) {
|
|
6165
|
-
;
|
|
6166
|
-
[1, -1].forEach(function(sd) {
|
|
6167
|
-
const dh = Math.max(0.35, (m.belt || 1) - (m.clearance || 0.3) - 0.08);
|
|
6168
|
-
const dx = gh.fb - 0.55;
|
|
6169
|
-
const door = c.roundBox(0.95, dh, 0.045, 0.03, c.paint(m.color || "#777"), dx, (m.clearance || 0.3) + dh / 2 + 0.04, sd * (W / 2 + 0.01));
|
|
6170
|
-
const hole = c.roundBox(0.9, dh * 0.95, 0.02, 0.02, MATERIALS.dark, dx, (m.clearance || 0.3) + dh / 2 + 0.04, sd * (W / 2 - 0.01));
|
|
6171
|
-
hole.userData.noFollow = true;
|
|
6172
|
-
fling(c, door, c.dirToEffects(new V3(rv(-1, 1), rv(3.5, 6), sd * rv(4, 6.5))), new V3(rv(-5, 5), rv(-4, 4), rv(-5, 5)));
|
|
6173
|
-
});
|
|
6174
|
-
}
|
|
6175
6204
|
if (m.rearBumper && m.rearBumper.parent)
|
|
6176
6205
|
fling(
|
|
6177
6206
|
c,
|
|
@@ -6179,20 +6208,6 @@ function installExplosion(kit) {
|
|
|
6179
6208
|
c.dirToEffects(new V3(rv(-5, -3), rv(4, 6), rv(-1, 1))),
|
|
6180
6209
|
new V3(rv(-3, 3), rv(-6, 6), rv(-3, 3))
|
|
6181
6210
|
);
|
|
6182
|
-
if (c.wheels.length) {
|
|
6183
|
-
const wi = Math.floor(c.rand() * c.wheels.length);
|
|
6184
|
-
const wh = c.wheels[wi];
|
|
6185
|
-
c.wheels.splice(wi, 1);
|
|
6186
|
-
c.parts.forEach(function(p) {
|
|
6187
|
-
if (p.obj === wh) p.done = true;
|
|
6188
|
-
});
|
|
6189
|
-
fling(
|
|
6190
|
-
c,
|
|
6191
|
-
wh,
|
|
6192
|
-
c.dirToEffects(new V3(rv(-2, 2), rv(5, 7.5), (wh.position.z >= 0 ? 1 : -1) * rv(3, 5))),
|
|
6193
|
-
new V3(rv(-8, 8), rv(-3, 3), rv(-8, 8))
|
|
6194
|
-
);
|
|
6195
|
-
}
|
|
6196
6211
|
burnAll(c);
|
|
6197
6212
|
c.emitters = c.emitters.filter(function(e) {
|
|
6198
6213
|
return e.kind === "exhaust" ? false : true;
|
|
@@ -10987,6 +11002,17 @@ function createVehicleKit(options) {
|
|
|
10987
11002
|
return kit;
|
|
10988
11003
|
}
|
|
10989
11004
|
|
|
11005
|
+
// src/destructible/armour.ts
|
|
11006
|
+
var ARMOUR_LEVELS = {
|
|
11007
|
+
IIIA: { glassStops: 1700, glassHp: 6e3, glassTakes: 0.25, steel: 8 },
|
|
11008
|
+
III: { glassStops: 3600, glassHp: 9e3, glassTakes: 0.25, steel: 30 }
|
|
11009
|
+
};
|
|
11010
|
+
function armourLevelOf(meta, option) {
|
|
11011
|
+
if (option !== void 0) return option;
|
|
11012
|
+
if (meta.armor === "IIIA" || meta.armor === "III") return meta.armor;
|
|
11013
|
+
return meta.armored || meta.bullet ? "III" : null;
|
|
11014
|
+
}
|
|
11015
|
+
|
|
10990
11016
|
// src/destructible/impl.ts
|
|
10991
11017
|
import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
|
|
10992
11018
|
|
|
@@ -11033,6 +11059,12 @@ var boxOf = (z) => ({
|
|
|
11033
11059
|
center: [(z.box.x0 + z.box.x1) / 2, (z.box.y0 + z.box.y1) / 2, (z.box.z0 + z.box.z1) / 2],
|
|
11034
11060
|
size: [Math.max(0.02, z.box.x1 - z.box.x0), Math.max(0.02, z.box.y1 - z.box.y0), Math.max(0.02, z.box.z1 - z.box.z0)]
|
|
11035
11061
|
});
|
|
11062
|
+
var PART_FLUIDS = {
|
|
11063
|
+
gearbox: { fluid: "transmission_fluid", liters: 6 },
|
|
11064
|
+
brakes: { fluid: "brake_fluid", liters: 0.6 },
|
|
11065
|
+
steering: { fluid: "power_steering_fluid", liters: 1 },
|
|
11066
|
+
battery: { fluid: "battery_acid", liters: 3 }
|
|
11067
|
+
};
|
|
11036
11068
|
var skinOf = (kind) => kind === "plane" || kind === "heli" || kind === "boat" ? "aluminium" : "steel";
|
|
11037
11069
|
var isFender = (id) => /^fender/.test(id);
|
|
11038
11070
|
var roleOfZone = (z) => {
|
|
@@ -11090,7 +11122,8 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11090
11122
|
const spec = c.spec;
|
|
11091
11123
|
const t = kit.t;
|
|
11092
11124
|
const skin = skinOf(kind);
|
|
11093
|
-
const
|
|
11125
|
+
const armoured = options.armour ?? null;
|
|
11126
|
+
const armour = armoured ? 12 : 1;
|
|
11094
11127
|
const mass = vehicleMass(c);
|
|
11095
11128
|
const W = m.width ?? 1.7;
|
|
11096
11129
|
const F = m.front ?? 2;
|
|
@@ -11156,8 +11189,8 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11156
11189
|
}
|
|
11157
11190
|
if (role === "glass") {
|
|
11158
11191
|
const glass = (z.glass ?? [])[0];
|
|
11159
|
-
const laminated = glass?.userData?.glass?.type === "laminated" ||
|
|
11160
|
-
add({ id, role, name: label, material: "glass", box, mass: 10 * area, hp: laminated ? 4e3 : 450, zoneId: id, visual: false, tags: ["glass", ...tags] });
|
|
11192
|
+
const laminated = glass?.userData?.glass?.type === "laminated" || !!armoured;
|
|
11193
|
+
add({ id, role, name: label, material: "glass", box, mass: 10 * area, hp: armoured ? armoured.glassHp : laminated ? 4e3 : 450, zoneId: id, visual: false, tags: ["glass", ...tags] });
|
|
11161
11194
|
continue;
|
|
11162
11195
|
}
|
|
11163
11196
|
if (role === "light") {
|
|
@@ -11195,7 +11228,7 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11195
11228
|
zoneId: id,
|
|
11196
11229
|
visual: false,
|
|
11197
11230
|
contains: { fluid: diesel ? "diesel" : "fuel", liters },
|
|
11198
|
-
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320 },
|
|
11231
|
+
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320, effect: "car_fuel_tank" },
|
|
11199
11232
|
ignitionC: diesel ? 330 : 260,
|
|
11200
11233
|
tags: ["fuel_tank", ...tags]
|
|
11201
11234
|
});
|
|
@@ -11231,8 +11264,8 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11231
11264
|
if (claimed.has(pane)) continue;
|
|
11232
11265
|
const G = pane.userData.glass;
|
|
11233
11266
|
const bb = paneBox(c, pane, inverse);
|
|
11234
|
-
const laminated = G?.type === "laminated" ||
|
|
11235
|
-
add({ id: `pane_${G?.key ?? "glass"}`, role: "glass", name: String(G?.label ?? G?.key ?? "glass"), material: "glass", box: bb, mass: 10 * areaOf(bb.size), hp: laminated ? 4e3 : 450, panes: [pane], visual: false, tags: ["glass"] });
|
|
11267
|
+
const laminated = G?.type === "laminated" || !!armoured;
|
|
11268
|
+
add({ id: `pane_${G?.key ?? "glass"}`, role: "glass", name: String(G?.label ?? G?.key ?? "glass"), material: "glass", box: bb, mass: 10 * areaOf(bb.size), hp: armoured ? armoured.glassHp : laminated ? 4e3 : 450, panes: [pane], visual: false, tags: ["glass"] });
|
|
11236
11269
|
}
|
|
11237
11270
|
if (!hubZone) {
|
|
11238
11271
|
const h = Math.max(0.15, belt - clearance);
|
|
@@ -11288,7 +11321,7 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11288
11321
|
visual: false,
|
|
11289
11322
|
mechanical: true,
|
|
11290
11323
|
contains: { fluid: diesel ? "diesel" : "fuel", liters },
|
|
11291
|
-
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320 },
|
|
11324
|
+
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320, effect: "car_fuel_tank" },
|
|
11292
11325
|
ignitionC: diesel ? 330 : 260,
|
|
11293
11326
|
tags: ["fuel_tank"]
|
|
11294
11327
|
});
|
|
@@ -11565,38 +11598,121 @@ function buildVehicleModel(kit, c, options = {}) {
|
|
|
11565
11598
|
}
|
|
11566
11599
|
}
|
|
11567
11600
|
void has;
|
|
11601
|
+
for (const part of parts) {
|
|
11602
|
+
const fluid = PART_FLUIDS[part.role];
|
|
11603
|
+
if (fluid && !part.contains && !(kind === "bike" && part.role === "steering")) part.contains = { ...fluid };
|
|
11604
|
+
}
|
|
11568
11605
|
return { parts, joints, hub: hubId, hitch };
|
|
11569
11606
|
}
|
|
11570
11607
|
|
|
11608
|
+
// src/destructible/subdivide-near.ts
|
|
11609
|
+
var SUBDIVIDE_DEPTH = 12;
|
|
11610
|
+
function subdivideNear(THREE, params) {
|
|
11611
|
+
const { mesh, maxLen, near } = params;
|
|
11612
|
+
const src = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry;
|
|
11613
|
+
const a = src.attributes.position.array;
|
|
11614
|
+
const toWorld = mesh.matrixWorld;
|
|
11615
|
+
const out = [];
|
|
11616
|
+
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2, (p[2] + q[2]) / 2];
|
|
11617
|
+
const split = (A, B, C, depth) => {
|
|
11618
|
+
const ab = Math.hypot(A[0] - B[0], A[1] - B[1], A[2] - B[2]);
|
|
11619
|
+
const bc = Math.hypot(B[0] - C[0], B[1] - C[1], B[2] - C[2]);
|
|
11620
|
+
const ca = Math.hypot(C[0] - A[0], C[1] - A[1], C[2] - A[2]);
|
|
11621
|
+
const longest = Math.max(ab, bc, ca);
|
|
11622
|
+
if (longest <= maxLen || depth > SUBDIVIDE_DEPTH) {
|
|
11623
|
+
out.push(...A, ...B, ...C);
|
|
11624
|
+
return;
|
|
11625
|
+
}
|
|
11626
|
+
if (longest === ab) {
|
|
11627
|
+
const D = mid(A, B);
|
|
11628
|
+
split(A, D, C, depth + 1);
|
|
11629
|
+
split(D, B, C, depth + 1);
|
|
11630
|
+
} else if (longest === bc) {
|
|
11631
|
+
const E = mid(B, C);
|
|
11632
|
+
split(A, B, E, depth + 1);
|
|
11633
|
+
split(A, E, C, depth + 1);
|
|
11634
|
+
} else {
|
|
11635
|
+
const F = mid(C, A);
|
|
11636
|
+
split(A, B, F, depth + 1);
|
|
11637
|
+
split(F, B, C, depth + 1);
|
|
11638
|
+
}
|
|
11639
|
+
};
|
|
11640
|
+
for (let i = 0; i < a.length; i += 9) {
|
|
11641
|
+
const A = [a[i], a[i + 1], a[i + 2]];
|
|
11642
|
+
const B = [a[i + 3], a[i + 4], a[i + 5]];
|
|
11643
|
+
const C = [a[i + 6], a[i + 7], a[i + 8]];
|
|
11644
|
+
const corners = [A, B, C].map((p) => new THREE.Vector3(p[0], p[1], p[2]).applyMatrix4(toWorld));
|
|
11645
|
+
if (near(corners)) split(A, B, C, 0);
|
|
11646
|
+
else out.push(...A, ...B, ...C);
|
|
11647
|
+
}
|
|
11648
|
+
const g = new THREE.BufferGeometry();
|
|
11649
|
+
g.setAttribute("position", new THREE.Float32BufferAttribute(out, 3));
|
|
11650
|
+
mesh.geometry.dispose();
|
|
11651
|
+
mesh.geometry = g;
|
|
11652
|
+
}
|
|
11653
|
+
|
|
11571
11654
|
// src/destructible/visual.ts
|
|
11572
11655
|
var HOLE_DECAL_SCALE = 2.4;
|
|
11573
11656
|
var DOOR_DEPTH = 0.07;
|
|
11574
|
-
var DOOR_EDGE_PIECES =
|
|
11657
|
+
var DOOR_EDGE_PIECES = 8;
|
|
11658
|
+
var DOOR_EDGE_REACH = 0.08;
|
|
11575
11659
|
var DOOR_CARD_COLOR = "#3b3e42";
|
|
11576
11660
|
var DOOR_LINER_COLOR = "#5a5e64";
|
|
11577
11661
|
var DOOR_LINER_DARK = 0.15;
|
|
11578
|
-
var DOOR_STRIP_THIN = 0.
|
|
11662
|
+
var DOOR_STRIP_THIN = 0.08;
|
|
11579
11663
|
var DOOR_STRIP_NEAR = 0.04;
|
|
11664
|
+
var DOOR_STRIP_TALL = 0.22;
|
|
11665
|
+
var STRIP_SMOOTH_DEG = 180;
|
|
11666
|
+
var DOOR_STRIP_VERTICES = 2e4;
|
|
11667
|
+
var DOOR_SILL = 0.25;
|
|
11580
11668
|
var DOOR_STRIP_OVER = 0.3;
|
|
11581
11669
|
var DOOR_TOP_OVER_PANE = 0.03;
|
|
11582
|
-
var DOOR_STRIP_SUB = 0.02;
|
|
11583
11670
|
var HOLE_TEXTURE_SIZE = 64;
|
|
11671
|
+
var LAMINATED_HP = 1e3;
|
|
11672
|
+
var HOOD_SINK = 2e-3;
|
|
11673
|
+
var HOOD_GAP_CLOSED = 0.016;
|
|
11584
11674
|
var DOOR_HANDLE_INSET = 0.12;
|
|
11585
11675
|
var DOOR_HANDLE_MIN = 0.07;
|
|
11586
11676
|
var DOOR_HANDLE_MAX = 0.24;
|
|
11587
11677
|
var DOOR_HANDLE_THIN = 0.07;
|
|
11588
|
-
var DOOR_HANDLE_OUTSIDE = 0.
|
|
11678
|
+
var DOOR_HANDLE_OUTSIDE = 0.15;
|
|
11589
11679
|
var PANE_SEAT_OUT = 0.05;
|
|
11590
11680
|
var PANE_SEAT_IN = -0.05;
|
|
11591
11681
|
var PANE_SEAT_REACH = 0.14;
|
|
11592
11682
|
var PANE_SEAT_SAMPLES = 24;
|
|
11593
11683
|
var PANE_SEAT_LIFT = 4e-3;
|
|
11594
11684
|
var PANE_FRAME_NEAR = 0.07;
|
|
11685
|
+
var PANE_MIN_SLOPE = 0.01;
|
|
11686
|
+
var PANE_EDGE_BAND = 0.03;
|
|
11687
|
+
var PANE_REACH_STEP = 0.01;
|
|
11688
|
+
var PANE_REACH_MAX = 0.25;
|
|
11689
|
+
var COWL_UNDER_GLASS = 0;
|
|
11690
|
+
var COWL_NEAR_PANE = 0.08;
|
|
11691
|
+
var COWL_PLATE = 0.06;
|
|
11692
|
+
var COWL_DASH = 0.5;
|
|
11693
|
+
var COWL_DEEP = 0.3;
|
|
11694
|
+
var WINDOW_FRAME_NEAR = 0.06;
|
|
11695
|
+
var WINDOW_FRAME_SPAN = 0.6;
|
|
11696
|
+
var DASHBOARD_COLOR = "#121314";
|
|
11697
|
+
var COWL_CUT_SUB = 0.1;
|
|
11698
|
+
var COWL_CUT_MARGIN = 0.08;
|
|
11699
|
+
var SKIN_UNDER_PANE = 0.025;
|
|
11700
|
+
var SKIN_EDGE_KEPT = 0.06;
|
|
11701
|
+
var SIDE_PANE_FACING = 0.6;
|
|
11702
|
+
var COWL_LONG = 0.8;
|
|
11703
|
+
var PANE_BAR_VERTICES = 48;
|
|
11704
|
+
var PANE_BAR_LENGTH = 0.3;
|
|
11705
|
+
var PANE_BORDER = 0.03;
|
|
11706
|
+
var PANE_BORDER_LIFT = 3e-3;
|
|
11595
11707
|
var DRAWN_MIRROR_SIZE = 0.3;
|
|
11596
11708
|
var DRAWN_MIRROR_REACH = 0.4;
|
|
11597
11709
|
var DRAWN_MIRROR_MOUNT = 0.2;
|
|
11598
11710
|
var MIRROR_GLASS_FIT = 0.76;
|
|
11711
|
+
var MIRROR_GLASS_PROUD = 1e-3;
|
|
11712
|
+
var MIRROR_FACE_DEPTH = 0.012;
|
|
11599
11713
|
var MIRROR_GLASS_THIN = 0.04;
|
|
11714
|
+
var DEFAULT_WHEEL_RADIUS = 0.3;
|
|
11715
|
+
var SIDEWALL_SHARE = 0.3;
|
|
11600
11716
|
var MIRROR_GLASS_MIN = 0.05;
|
|
11601
11717
|
var MIRROR_GLASS_COLOR = "#59636a";
|
|
11602
11718
|
var HOLE_RIM = { steel: "#b7bcc0", aluminium: "#d3d6d8", plastic: "#d9d6cc", rubber: "#141414", glass: "#eef3f5" };
|
|
@@ -11683,6 +11799,7 @@ var VehicleVisuals = class {
|
|
|
11683
11799
|
this.hood = this.nameHood();
|
|
11684
11800
|
this.seatPanes();
|
|
11685
11801
|
this.seatDoorHandles();
|
|
11802
|
+
this.seatHood();
|
|
11686
11803
|
this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
|
|
11687
11804
|
if (!this.warming.length) this.warming = null;
|
|
11688
11805
|
for (const p of model.parts) {
|
|
@@ -11883,7 +12000,8 @@ var VehicleVisuals = class {
|
|
|
11883
12000
|
const panes = I.windowsOver(this.c, z, inv);
|
|
11884
12001
|
const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
|
|
11885
12002
|
const mirrors = this.mirrorsOn(z);
|
|
11886
|
-
|
|
12003
|
+
const frames = this.framesAround(panes, inv);
|
|
12004
|
+
objs = [...pcs, ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors, ...frames];
|
|
11887
12005
|
}
|
|
11888
12006
|
} else {
|
|
11889
12007
|
const pcs = this.cutZone(z);
|
|
@@ -11928,7 +12046,10 @@ var VehicleVisuals = class {
|
|
|
11928
12046
|
run(x1, y0, x1, y1);
|
|
11929
12047
|
run(x1, y1, x0, y1);
|
|
11930
12048
|
run(x0, y1, x0, y0);
|
|
11931
|
-
const heights = loop.map(([x, y]) =>
|
|
12049
|
+
const heights = loop.map(([x, y]) => {
|
|
12050
|
+
const at = this.surface("z", side2, B, [[x, y]]);
|
|
12051
|
+
return at != null && Math.abs(at - flat) < DOOR_EDGE_REACH ? at : flat;
|
|
12052
|
+
});
|
|
11932
12053
|
const band = (lift) => {
|
|
11933
12054
|
const position = [];
|
|
11934
12055
|
const index = [];
|
|
@@ -11946,6 +12067,23 @@ var VehicleVisuals = class {
|
|
|
11946
12067
|
};
|
|
11947
12068
|
const paint = this.paint().clone();
|
|
11948
12069
|
paint.side = THREE.DoubleSide;
|
|
12070
|
+
const cardPosition = [];
|
|
12071
|
+
const mid = loop.reduce((acc, [x, y], i) => [acc[0] + x, acc[1] + y, acc[2] + heights[i]], [0, 0, 0]).map((v) => v / loop.length);
|
|
12072
|
+
const cardZ = (z2) => z2 - side2 * DOOR_DEPTH;
|
|
12073
|
+
loop.forEach(([x, y], i) => {
|
|
12074
|
+
const j = (i + 1) % loop.length;
|
|
12075
|
+
cardPosition.push(mid[0], mid[1], cardZ(mid[2]), x, y, cardZ(heights[i]), loop[j][0], loop[j][1], cardZ(heights[j]));
|
|
12076
|
+
});
|
|
12077
|
+
const cardGeometry = new THREE.BufferGeometry();
|
|
12078
|
+
cardGeometry.setAttribute("position", new THREE.Float32BufferAttribute(cardPosition, 3));
|
|
12079
|
+
cardGeometry.computeVertexNormals();
|
|
12080
|
+
const cardMaterial = this.I.makeMaterial(DOOR_CARD_COLOR, { roughness: 0.85 }).clone();
|
|
12081
|
+
cardMaterial.side = THREE.DoubleSide;
|
|
12082
|
+
const card = new THREE.Mesh(cardGeometry, cardMaterial);
|
|
12083
|
+
card.userData.noFollow = true;
|
|
12084
|
+
card.castShadow = false;
|
|
12085
|
+
this.c.vehicle.add(card);
|
|
12086
|
+
piv.attach(card);
|
|
11949
12087
|
const edges = new THREE.Mesh(band(2e-3), paint);
|
|
11950
12088
|
const lining = new THREE.Mesh(band(2e-3), paint);
|
|
11951
12089
|
for (const m of [edges, lining]) {
|
|
@@ -11955,6 +12093,17 @@ var VehicleVisuals = class {
|
|
|
11955
12093
|
}
|
|
11956
12094
|
piv.attach(edges);
|
|
11957
12095
|
}
|
|
12096
|
+
/** The frame the model draws round a door's window (a dark strip over the glass) goes with the door: left behind it hangs in the air over the opening. */
|
|
12097
|
+
framesAround(panes, inv) {
|
|
12098
|
+
const THREE = this.THREE;
|
|
12099
|
+
const boxes = panes.map((g) => this.I.localBox(this.c, g, inv).expandByScalar(WINDOW_FRAME_NEAR));
|
|
12100
|
+
return this.c.vehicle.children.filter((o) => {
|
|
12101
|
+
if (!o.isMesh || !o.visible || o.userData.glass || o.userData.decal || this.c.deform?.includes(o)) return false;
|
|
12102
|
+
const box = this.I.localBox(this.c, o, inv);
|
|
12103
|
+
if (box.isEmpty()) return false;
|
|
12104
|
+
return boxes.some((b) => b.containsBox(box) && box.getSize(new THREE.Vector3()).x > b.getSize(new THREE.Vector3()).x * WINDOW_FRAME_SPAN);
|
|
12105
|
+
});
|
|
12106
|
+
}
|
|
11958
12107
|
/** A front door carries the mirror drawn at its corner. */
|
|
11959
12108
|
mirrorsOn(z) {
|
|
11960
12109
|
if (!z.mirror) return [];
|
|
@@ -11980,45 +12129,42 @@ var VehicleVisuals = class {
|
|
|
11980
12129
|
const B = z.box;
|
|
11981
12130
|
const x0 = B.x0 + 0.03;
|
|
11982
12131
|
const x1 = B.x1 - 0.03;
|
|
11983
|
-
const y0 = B.y0
|
|
11984
|
-
const
|
|
12132
|
+
const y0 = B.y0 + DOOR_SILL;
|
|
12133
|
+
const glassTop = (I) => {
|
|
12134
|
+
let top = -Infinity;
|
|
12135
|
+
for (const g of I.windowsOver(this.c, z, inv)) top = Math.max(top, I.localBox(this.c, g, inv).max.y);
|
|
12136
|
+
return top;
|
|
12137
|
+
};
|
|
12138
|
+
const y1 = Math.max(B.y1, glassTop(this.I)) + DOOR_STRIP_NEAR;
|
|
11985
12139
|
const deform = this.c.deform ?? [];
|
|
11986
12140
|
for (const o of [...this.c.vehicle.children]) {
|
|
11987
|
-
if (!o.isMesh || o === piv ||
|
|
12141
|
+
if (!o.isMesh || o === piv || o.userData.glass || o.userData.decal || !o.visible || deform.includes(o) && o.geometry.attributes.position.count > DOOR_STRIP_VERTICES) continue;
|
|
11988
12142
|
const box = this.I.localBox(this.c, o, inv);
|
|
11989
12143
|
const size = box.getSize(new THREE.Vector3());
|
|
11990
12144
|
const mid = box.getCenter(new THREE.Vector3());
|
|
11991
|
-
if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y >
|
|
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;
|
|
11992
12146
|
const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
|
|
11993
12147
|
const v = new THREE.Vector3();
|
|
11994
12148
|
const sdf = (x, y, zz) => {
|
|
11995
12149
|
v.set(x, y, zz).applyMatrix4(toVehicle);
|
|
11996
12150
|
return Math.max(x0 - v.x, v.x - x1);
|
|
11997
12151
|
};
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
const side2 = z.side < 0 ? -1 : 1;
|
|
12015
|
-
const inward = -side2 * DOOR_DEPTH;
|
|
12016
|
-
card.position.z += inward;
|
|
12017
|
-
card.userData.noFollow = true;
|
|
12018
|
-
card.castShadow = false;
|
|
12019
|
-
this.c.vehicle.add(card);
|
|
12020
|
-
return card;
|
|
12021
|
-
});
|
|
12152
|
+
const over = new THREE.Mesh(o.geometry.clone(), o.material);
|
|
12153
|
+
over.position.copy(o.position);
|
|
12154
|
+
over.quaternion.copy(o.quaternion);
|
|
12155
|
+
over.scale.copy(o.scale);
|
|
12156
|
+
this.c.vehicle.add(over);
|
|
12157
|
+
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
|
+
over.userData.noFollow = true;
|
|
12166
|
+
piv.attach(over);
|
|
12167
|
+
}
|
|
12022
12168
|
}
|
|
12023
12169
|
mend(dirty) {
|
|
12024
12170
|
dirty.forEach((msh) => {
|
|
@@ -12043,14 +12189,58 @@ var VehicleVisuals = class {
|
|
|
12043
12189
|
ray.layers.enableAll();
|
|
12044
12190
|
for (const pane of c.glass) {
|
|
12045
12191
|
if (pane.userData.glass?.outline) continue;
|
|
12046
|
-
const
|
|
12192
|
+
const local = this.shellAround(pane, shell);
|
|
12193
|
+
if (!local) continue;
|
|
12194
|
+
const seat = this.paneSeat(pane, [local], ray);
|
|
12047
12195
|
if (!seat) continue;
|
|
12048
12196
|
const near = c.vehicle.children.filter((o) => o.isMesh && o !== pane && !o.userData.glass && !shell.includes(o) && this.onPane(o, pane, seat));
|
|
12049
12197
|
for (const m of [pane, ...near]) this.movePane(m, seat);
|
|
12050
|
-
const past = this.shellPast(pane,
|
|
12198
|
+
const past = this.shellPast(pane, [local], ray, seat.normal);
|
|
12051
12199
|
if (past > 0) for (const m of [pane, ...near]) this.movePane(m, { normal: seat.normal, samples: [{ at: seat.samples[0].at, by: past }] });
|
|
12200
|
+
this.reachShellBelow(pane, local, ray, seat.normal);
|
|
12201
|
+
this.paneBorder(pane, near, seat);
|
|
12202
|
+
this.keepUnderPane(pane, seat.normal);
|
|
12203
|
+
const across = new this.THREE.Vector3(0, 0, 1).transformDirection(this.c.vehicle.matrixWorld);
|
|
12204
|
+
if (Math.abs(seat.normal.dot(across)) < SIDE_PANE_FACING) {
|
|
12205
|
+
for (const body of shell) if (body.parent === this.c.vehicle) this.cutUnderPane(body, pane, seat.normal, SKIN_UNDER_PANE, SKIN_EDGE_KEPT);
|
|
12206
|
+
}
|
|
12052
12207
|
}
|
|
12053
12208
|
}
|
|
12209
|
+
/** Only the triangles of the shell around a pane, in world space: seating casts many rays and the whole hull has too many triangles for each. */
|
|
12210
|
+
shellAround(pane, shell) {
|
|
12211
|
+
const THREE = this.THREE;
|
|
12212
|
+
const box = new THREE.Box3().setFromObject(pane).expandByScalar(PANE_SEAT_REACH + PANE_SEAT_OUT);
|
|
12213
|
+
const out = [];
|
|
12214
|
+
const a = new THREE.Vector3();
|
|
12215
|
+
const b = new THREE.Vector3();
|
|
12216
|
+
const d = new THREE.Vector3();
|
|
12217
|
+
const tri = new THREE.Box3();
|
|
12218
|
+
for (const mesh of shell) {
|
|
12219
|
+
const pos = mesh.geometry.attributes.position;
|
|
12220
|
+
const idx = mesh.geometry.index;
|
|
12221
|
+
const count = idx ? idx.count : pos.count;
|
|
12222
|
+
const e = mesh.matrixWorld.elements;
|
|
12223
|
+
const world = (v, i) => {
|
|
12224
|
+
const x = pos.getX(i);
|
|
12225
|
+
const y = pos.getY(i);
|
|
12226
|
+
const z = pos.getZ(i);
|
|
12227
|
+
v.set(e[0] * x + e[4] * y + e[8] * z + e[12], e[1] * x + e[5] * y + e[9] * z + e[13], e[2] * x + e[6] * y + e[10] * z + e[14]);
|
|
12228
|
+
};
|
|
12229
|
+
for (let f = 0; f + 2 < count; f += 3) {
|
|
12230
|
+
world(a, idx ? idx.getX(f) : f);
|
|
12231
|
+
world(b, idx ? idx.getX(f + 1) : f + 1);
|
|
12232
|
+
world(d, idx ? idx.getX(f + 2) : f + 2);
|
|
12233
|
+
tri.makeEmpty().expandByPoint(a).expandByPoint(b).expandByPoint(d);
|
|
12234
|
+
if (tri.intersectsBox(box)) out.push(a.x, a.y, a.z, b.x, b.y, b.z, d.x, d.y, d.z);
|
|
12235
|
+
}
|
|
12236
|
+
}
|
|
12237
|
+
if (!out.length) return null;
|
|
12238
|
+
const g = new THREE.BufferGeometry();
|
|
12239
|
+
g.setAttribute("position", new THREE.Float32BufferAttribute(out, 3));
|
|
12240
|
+
const proxy = new THREE.Mesh(g, new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }));
|
|
12241
|
+
proxy.updateMatrixWorld(true);
|
|
12242
|
+
return proxy;
|
|
12243
|
+
}
|
|
12054
12244
|
/** The outward normal of a pane and how far the shell is at each sampled point (world). */
|
|
12055
12245
|
paneSeat(pane, shell, ray) {
|
|
12056
12246
|
const THREE = this.THREE;
|
|
@@ -12097,6 +12287,193 @@ var VehicleVisuals = class {
|
|
|
12097
12287
|
}
|
|
12098
12288
|
return samples.length >= 3 ? { normal, samples } : null;
|
|
12099
12289
|
}
|
|
12290
|
+
/** The lower edge of a pane comes down to the shell: left short, the dashboard shows through the slit as a black bar under the glass. */
|
|
12291
|
+
reachShellBelow(pane, shell, ray, normal) {
|
|
12292
|
+
const THREE = this.THREE;
|
|
12293
|
+
const down = new THREE.Vector3(0, -1, 0).applyQuaternion(this.c.root.getWorldQuaternion(new THREE.Quaternion()));
|
|
12294
|
+
down.addScaledVector(normal, -down.dot(normal));
|
|
12295
|
+
if (down.lengthSq() < PANE_MIN_SLOPE) return;
|
|
12296
|
+
down.normalize();
|
|
12297
|
+
const g = pane.geometry;
|
|
12298
|
+
const pos = g.attributes.position;
|
|
12299
|
+
const v = new THREE.Vector3();
|
|
12300
|
+
let lowest = -Infinity;
|
|
12301
|
+
for (let i = 0; i < pos.count; i++) lowest = Math.max(lowest, v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld).dot(down));
|
|
12302
|
+
const edge = [];
|
|
12303
|
+
const mid = new THREE.Vector3();
|
|
12304
|
+
for (let i = 0; i < pos.count; i++) {
|
|
12305
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
|
|
12306
|
+
if (v.dot(down) > lowest - PANE_EDGE_BAND) {
|
|
12307
|
+
edge.push(i);
|
|
12308
|
+
mid.add(v);
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12311
|
+
if (!edge.length) return;
|
|
12312
|
+
mid.divideScalar(edge.length);
|
|
12313
|
+
let reach = 0;
|
|
12314
|
+
for (let d = PANE_REACH_STEP; d <= PANE_REACH_MAX; d += PANE_REACH_STEP) {
|
|
12315
|
+
const at = mid.clone().addScaledVector(down, d).addScaledVector(normal, PANE_SEAT_REACH);
|
|
12316
|
+
ray.set(at, normal.clone().negate());
|
|
12317
|
+
ray.far = PANE_SEAT_REACH * 2;
|
|
12318
|
+
if (ray.intersectObject(shell, false)[0]) {
|
|
12319
|
+
reach = d + PANE_REACH_STEP;
|
|
12320
|
+
break;
|
|
12321
|
+
}
|
|
12322
|
+
}
|
|
12323
|
+
if (!reach) return;
|
|
12324
|
+
const inverse = pane.matrixWorld.clone().invert();
|
|
12325
|
+
for (const i of edge) {
|
|
12326
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld).addScaledVector(down, reach).applyMatrix4(inverse);
|
|
12327
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
12328
|
+
}
|
|
12329
|
+
pos.needsUpdate = true;
|
|
12330
|
+
g.computeBoundingBox();
|
|
12331
|
+
g.computeBoundingSphere();
|
|
12332
|
+
}
|
|
12333
|
+
/** The plate drawn right under a windshield stays only outside the glass, in the body's paint: under the glass it would show inside the cabin. */
|
|
12334
|
+
keepUnderPane(pane, normal) {
|
|
12335
|
+
const THREE = this.THREE;
|
|
12336
|
+
const paneBox2 = new THREE.Box3().setFromObject(pane).expandByScalar(COWL_NEAR_PANE);
|
|
12337
|
+
const pos = pane.geometry.attributes.position;
|
|
12338
|
+
const centre = new THREE.Vector3();
|
|
12339
|
+
const v = new THREE.Vector3();
|
|
12340
|
+
for (let i = 0; i < pos.count; i++) centre.add(v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld));
|
|
12341
|
+
centre.divideScalar(pos.count);
|
|
12342
|
+
const plane = centre.dot(normal);
|
|
12343
|
+
for (const o of this.c.vehicle.children) {
|
|
12344
|
+
if (!o.isMesh || o === pane || o.userData.glass || this.c.deform?.includes(o)) continue;
|
|
12345
|
+
const box = new THREE.Box3().setFromObject(o);
|
|
12346
|
+
const size = box.getSize(new THREE.Vector3());
|
|
12347
|
+
if (o.geometry.attributes.position.count <= PANE_BAR_VERTICES || size.y > COWL_DASH || Math.max(size.x, size.z) < COWL_LONG || Math.min(size.x, size.z) < COWL_DEEP || !paneBox2.intersectsBox(box)) continue;
|
|
12348
|
+
if (box.getCenter(new THREE.Vector3()).dot(normal) - plane >= COWL_UNDER_GLASS) continue;
|
|
12349
|
+
if (size.y >= COWL_PLATE) {
|
|
12350
|
+
o.material = this.I.makeMaterial(DASHBOARD_COLOR, { roughness: 1, metalness: 0 });
|
|
12351
|
+
continue;
|
|
12352
|
+
}
|
|
12353
|
+
o.material = this.paint();
|
|
12354
|
+
this.cutUnderPane(o, pane, normal);
|
|
12355
|
+
}
|
|
12356
|
+
}
|
|
12357
|
+
/** Cuts away the part of a mesh that lies under a pane (behind its plane, inside its outline seen along its normal). */
|
|
12358
|
+
cutUnderPane(mesh, pane, normal, behind = -Infinity, inset = 0) {
|
|
12359
|
+
const THREE = this.THREE;
|
|
12360
|
+
const outline = this.paneOutline(pane, normal);
|
|
12361
|
+
if (outline.length < 3) return;
|
|
12362
|
+
const u = new THREE.Vector3(0, 1, 0).cross(normal);
|
|
12363
|
+
if (u.lengthSq() < 1e-4) u.set(1, 0, 0);
|
|
12364
|
+
u.normalize();
|
|
12365
|
+
const w = normal.clone().cross(u);
|
|
12366
|
+
const cx = outline.reduce((sum, p) => sum + p.dot(u), 0) / outline.length;
|
|
12367
|
+
const cy = outline.reduce((sum, p) => sum + p.dot(w), 0) / outline.length;
|
|
12368
|
+
const poly = outline.map((p) => {
|
|
12369
|
+
const x = p.dot(u);
|
|
12370
|
+
const y = p.dot(w);
|
|
12371
|
+
const d = Math.hypot(x - cx, y - cy) || 1;
|
|
12372
|
+
const k = Math.max(0, d - inset) / d;
|
|
12373
|
+
return [cx + (x - cx) * k, cy + (y - cy) * k];
|
|
12374
|
+
});
|
|
12375
|
+
const plane = outline.reduce((sum, p) => sum + p.dot(normal), 0) / outline.length;
|
|
12376
|
+
const inside = (x, y) => {
|
|
12377
|
+
let hit = false;
|
|
12378
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
12379
|
+
const [xi, yi] = poly[i];
|
|
12380
|
+
const [xj, yj] = poly[j];
|
|
12381
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) hit = !hit;
|
|
12382
|
+
}
|
|
12383
|
+
return hit;
|
|
12384
|
+
};
|
|
12385
|
+
const v = new THREE.Vector3();
|
|
12386
|
+
const toWorld = mesh.matrixWorld;
|
|
12387
|
+
mesh.updateMatrixWorld(true);
|
|
12388
|
+
const xs = poly.map((p) => p[0]);
|
|
12389
|
+
const ys = poly.map((p) => p[1]);
|
|
12390
|
+
const [x0, x1, y0, y1] = [Math.min(...xs) - COWL_CUT_MARGIN, Math.max(...xs) + COWL_CUT_MARGIN, Math.min(...ys) - COWL_CUT_MARGIN, Math.max(...ys) + COWL_CUT_MARGIN];
|
|
12391
|
+
subdivideNear(THREE, { mesh, maxLen: COWL_CUT_SUB, near: (corners) => {
|
|
12392
|
+
const us = corners.map((c) => c.dot(u));
|
|
12393
|
+
const ws = corners.map((c) => c.dot(w));
|
|
12394
|
+
return Math.max(...us) >= x0 && Math.min(...us) <= x1 && Math.max(...ws) >= y0 && Math.min(...ws) <= y1;
|
|
12395
|
+
} });
|
|
12396
|
+
this.kit.cutMesh(
|
|
12397
|
+
mesh,
|
|
12398
|
+
[
|
|
12399
|
+
{
|
|
12400
|
+
sdf: (x, y, z) => {
|
|
12401
|
+
v.set(x, y, z).applyMatrix4(toWorld);
|
|
12402
|
+
return inside(v.dot(u), v.dot(w)) && plane - v.dot(normal) > behind ? -1 : 1;
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
],
|
|
12406
|
+
42,
|
|
12407
|
+
{}
|
|
12408
|
+
);
|
|
12409
|
+
}
|
|
12410
|
+
/** The straight bars drawn round a quad pane stand off its edges: they give way to a thin dark band that lies on the glass, as on a bonded windshield. */
|
|
12411
|
+
paneBorder(pane, near, seat) {
|
|
12412
|
+
const THREE = this.THREE;
|
|
12413
|
+
const bars = near.filter((o) => {
|
|
12414
|
+
if (o.geometry.attributes.position.count > PANE_BAR_VERTICES) return false;
|
|
12415
|
+
const size = new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3());
|
|
12416
|
+
return Math.max(size.x, size.y, size.z) > PANE_BAR_LENGTH;
|
|
12417
|
+
});
|
|
12418
|
+
if (!bars.length) return;
|
|
12419
|
+
const outline = this.paneOutline(pane, seat.normal);
|
|
12420
|
+
if (outline.length < 3) return;
|
|
12421
|
+
const centre = new THREE.Vector3();
|
|
12422
|
+
for (const p of outline) centre.add(p);
|
|
12423
|
+
centre.divideScalar(outline.length);
|
|
12424
|
+
const position = [];
|
|
12425
|
+
const toVehicle = this.I.worldInverseOf(this.c);
|
|
12426
|
+
const put = (p) => {
|
|
12427
|
+
const q = p.clone().addScaledVector(seat.normal, PANE_BORDER_LIFT).applyMatrix4(toVehicle);
|
|
12428
|
+
position.push(q.x, q.y, q.z);
|
|
12429
|
+
};
|
|
12430
|
+
const inward = (p) => p.clone().add(centre.clone().sub(p).normalize().multiplyScalar(PANE_BORDER));
|
|
12431
|
+
outline.forEach((a, i) => {
|
|
12432
|
+
const b = outline[(i + 1) % outline.length];
|
|
12433
|
+
const c = inward(b);
|
|
12434
|
+
const d = inward(a);
|
|
12435
|
+
for (const p of [a, b, c, a, c, d]) put(p);
|
|
12436
|
+
});
|
|
12437
|
+
if (!position.length) return;
|
|
12438
|
+
for (const o of bars) o.visible = false;
|
|
12439
|
+
const band = new THREE.BufferGeometry();
|
|
12440
|
+
band.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
|
|
12441
|
+
band.computeVertexNormals();
|
|
12442
|
+
const material = this.I.MATERIALS.dark.clone();
|
|
12443
|
+
material.side = THREE.DoubleSide;
|
|
12444
|
+
const mesh = new THREE.Mesh(band, material);
|
|
12445
|
+
mesh.userData.noFollow = true;
|
|
12446
|
+
mesh.castShadow = false;
|
|
12447
|
+
mesh.raycast = () => {
|
|
12448
|
+
};
|
|
12449
|
+
this.c.vehicle.add(mesh);
|
|
12450
|
+
}
|
|
12451
|
+
/** The outline of a pane, the convex hull of its vertices seen along its normal, in world space. */
|
|
12452
|
+
paneOutline(pane, normal) {
|
|
12453
|
+
const THREE = this.THREE;
|
|
12454
|
+
const pos = pane.geometry.attributes.position;
|
|
12455
|
+
const u = new THREE.Vector3(0, 1, 0).cross(normal);
|
|
12456
|
+
if (u.lengthSq() < 1e-4) u.set(1, 0, 0);
|
|
12457
|
+
u.normalize();
|
|
12458
|
+
const w = normal.clone().cross(u);
|
|
12459
|
+
const points = [];
|
|
12460
|
+
for (let i = 0; i < pos.count; i++) {
|
|
12461
|
+
const p = new THREE.Vector3().fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
|
|
12462
|
+
points.push({ x: p.dot(u), y: p.dot(w), p });
|
|
12463
|
+
}
|
|
12464
|
+
points.sort((a, b) => a.x - b.x || a.y - b.y);
|
|
12465
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
12466
|
+
const half = (list) => {
|
|
12467
|
+
const out = [];
|
|
12468
|
+
for (const q of list) {
|
|
12469
|
+
while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], q) <= 0) out.pop();
|
|
12470
|
+
out.push(q);
|
|
12471
|
+
}
|
|
12472
|
+
out.pop();
|
|
12473
|
+
return out;
|
|
12474
|
+
};
|
|
12475
|
+
return [...half(points), ...half([...points].reverse())].map((q) => q.p);
|
|
12476
|
+
}
|
|
12100
12477
|
/** How far the shell still stands past a seated pane, over its triangles. */
|
|
12101
12478
|
shellPast(pane, shell, ray, normal) {
|
|
12102
12479
|
const THREE = this.THREE;
|
|
@@ -12149,11 +12526,25 @@ var VehicleVisuals = class {
|
|
|
12149
12526
|
g.computeBoundingSphere();
|
|
12150
12527
|
mesh.geometry = g;
|
|
12151
12528
|
}
|
|
12529
|
+
/** A closed hood is flush with the fenders beside it: the height it was drawn with to keep clear of the hull goes, the hull has its hole. */
|
|
12530
|
+
seatHood() {
|
|
12531
|
+
const hood = this.c.hood;
|
|
12532
|
+
if (!hood?.lift) return;
|
|
12533
|
+
const THREE = this.THREE;
|
|
12534
|
+
const box = new THREE.Box3().setFromObject(hood.piv).getSize(new THREE.Vector3());
|
|
12535
|
+
for (const part of hood.piv.children) {
|
|
12536
|
+
part.position.y -= hood.lift + HOOD_SINK;
|
|
12537
|
+
part.scale.multiply(new THREE.Vector3(1 + HOOD_GAP_CLOSED / Math.max(0.3, box.x), 1, 1 + HOOD_GAP_CLOSED / Math.max(0.3, box.z)));
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12152
12540
|
/** A handle is at the free edge of its door, the end away from the hinge. */
|
|
12153
12541
|
seatDoorHandles() {
|
|
12154
12542
|
const THREE = this.THREE;
|
|
12155
12543
|
const c = this.c;
|
|
12156
12544
|
const inv = this.I.worldInverseOf(c);
|
|
12545
|
+
const bodyBox = new THREE.Box3();
|
|
12546
|
+
for (const o of c.deform) if (o.isMesh && !o.userData.glass && c.paints?.has(o.material)) bodyBox.union(this.I.localBox(c, o, inv));
|
|
12547
|
+
const outerHalf = Math.max(Math.abs(bodyBox.min.z), Math.abs(bodyBox.max.z));
|
|
12157
12548
|
for (const p of this.model.parts) {
|
|
12158
12549
|
if (p.role !== "door") continue;
|
|
12159
12550
|
const z = this.zone(p);
|
|
@@ -12167,8 +12558,7 @@ var VehicleVisuals = class {
|
|
|
12167
12558
|
const at = box.getCenter(new THREE.Vector3());
|
|
12168
12559
|
const isHandle = size.x > DOOR_HANDLE_MIN && size.x < DOOR_HANDLE_MAX && size.y < DOOR_HANDLE_THIN && size.z < DOOR_HANDLE_THIN;
|
|
12169
12560
|
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;
|
|
12170
|
-
|
|
12171
|
-
if (here != null && Math.abs(here - at.z) > DOOR_HANDLE_OUTSIDE) continue;
|
|
12561
|
+
if (Math.abs(at.z) < outerHalf - DOOR_HANDLE_OUTSIDE) continue;
|
|
12172
12562
|
const skin = this.surface("z", z.side < 0 ? -1 : 1, B, [[free, at.y]]);
|
|
12173
12563
|
o.position.x += free - at.x;
|
|
12174
12564
|
if (skin != null) o.position.z += skin + (z.side < 0 ? -1 : 1) * (size.z / 2) - at.z;
|
|
@@ -12177,7 +12567,7 @@ var VehicleVisuals = class {
|
|
|
12177
12567
|
c.root.updateMatrixWorld(true);
|
|
12178
12568
|
}
|
|
12179
12569
|
/** The glass of a drawn mirror sits inside its rounded housing, without corners standing past it. */
|
|
12180
|
-
seatMirrorGlass(glass) {
|
|
12570
|
+
seatMirrorGlass(glass, drawn) {
|
|
12181
12571
|
const g = glass.geometry.clone();
|
|
12182
12572
|
g.computeBoundingBox();
|
|
12183
12573
|
const mid = g.boundingBox.getCenter(new this.THREE.Vector3());
|
|
@@ -12185,6 +12575,37 @@ var VehicleVisuals = class {
|
|
|
12185
12575
|
g.scale(MIRROR_GLASS_FIT, MIRROR_GLASS_FIT, MIRROR_GLASS_FIT);
|
|
12186
12576
|
g.translate(mid.x, mid.y, mid.z);
|
|
12187
12577
|
glass.geometry = g;
|
|
12578
|
+
const THREE = this.THREE;
|
|
12579
|
+
const volumeOf = (o) => {
|
|
12580
|
+
const size = new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3());
|
|
12581
|
+
return size.x * size.y * size.z;
|
|
12582
|
+
};
|
|
12583
|
+
const housing = drawn.filter((o) => o !== glass && o.isMesh && o.geometry).sort((a, b) => volumeOf(b) - volumeOf(a))[0];
|
|
12584
|
+
if (housing) {
|
|
12585
|
+
this.c.root.updateMatrixWorld(true);
|
|
12586
|
+
const inv = this.I.worldInverseOf(this.c);
|
|
12587
|
+
glass.updateMatrixWorld(true);
|
|
12588
|
+
const gb = this.I.localBox(this.c, glass, inv);
|
|
12589
|
+
const hb = this.I.localBox(this.c, housing, inv);
|
|
12590
|
+
const side2 = hb.getCenter(new THREE.Vector3()).x > gb.getCenter(new THREE.Vector3()).x ? -1 : 1;
|
|
12591
|
+
const face = side2 < 0 ? hb.min.x + MIRROR_FACE_DEPTH : hb.max.x - MIRROR_FACE_DEPTH;
|
|
12592
|
+
const shape = housing.geometry.clone();
|
|
12593
|
+
const pos = shape.attributes.position;
|
|
12594
|
+
const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, housing.matrixWorld);
|
|
12595
|
+
const back = toVehicle.clone().invert();
|
|
12596
|
+
const v = new THREE.Vector3();
|
|
12597
|
+
for (let i = 0; i < pos.count; i++) {
|
|
12598
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(toVehicle);
|
|
12599
|
+
if ((v.x - face) * side2 <= 0) continue;
|
|
12600
|
+
v.x = face;
|
|
12601
|
+
v.applyMatrix4(back);
|
|
12602
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
12603
|
+
}
|
|
12604
|
+
shape.computeVertexNormals();
|
|
12605
|
+
housing.geometry = shape;
|
|
12606
|
+
const outer = side2 < 0 ? gb.min.x : gb.max.x;
|
|
12607
|
+
glass.position.x += face + side2 * MIRROR_GLASS_PROUD - outer;
|
|
12608
|
+
}
|
|
12188
12609
|
glass.material = this.I.makeMaterial(MIRROR_GLASS_COLOR, { metalness: 0.95, roughness: 0.12 });
|
|
12189
12610
|
}
|
|
12190
12611
|
/** The mirror the model already draws around where the part says, if it draws one. */
|
|
@@ -12209,7 +12630,11 @@ var VehicleVisuals = class {
|
|
|
12209
12630
|
for (const o of drawn) {
|
|
12210
12631
|
if (!o.isMesh || o.material !== this.I.MATERIALS.chrome) continue;
|
|
12211
12632
|
const size = new this.THREE.Box3().setFromObject(o).getSize(new this.THREE.Vector3());
|
|
12212
|
-
|
|
12633
|
+
const [thin, mid] = size.toArray().sort((a, b) => a - b);
|
|
12634
|
+
if (thin < MIRROR_GLASS_THIN && mid > MIRROR_GLASS_MIN) {
|
|
12635
|
+
this.seatMirrorGlass(o, drawn);
|
|
12636
|
+
o.userData.mirrorGlass = true;
|
|
12637
|
+
}
|
|
12213
12638
|
}
|
|
12214
12639
|
this.extra.set(p.id, drawn);
|
|
12215
12640
|
return;
|
|
@@ -12221,14 +12646,26 @@ var VehicleVisuals = class {
|
|
|
12221
12646
|
g.name = `obj_${p.id}`;
|
|
12222
12647
|
g.position.set(cx, cy, cz);
|
|
12223
12648
|
this.c.vehicle.add(g);
|
|
12224
|
-
this.box(0.05, 0.
|
|
12225
|
-
const housing = this.box(0.
|
|
12649
|
+
this.box(0.05, 0.035, 0.05, mats.dark, 0, -0.04, -s * 0.06, g);
|
|
12650
|
+
const housing = this.box(0.08, 0.11, 0.125, mats.plastic ?? mats.dark, 0, 0, s * 0.0225, g);
|
|
12226
12651
|
housing.userData.mirrorHousing = true;
|
|
12227
|
-
const glass = this.box(0.095, 0.
|
|
12652
|
+
const glass = this.box(6e-3, 0.095, 0.115, mats.chrome ?? mats.dark, -0.043, 0, s * 0.0225, g);
|
|
12228
12653
|
glass.userData.mirrorGlass = true;
|
|
12229
12654
|
g.userData.rigid = true;
|
|
12230
12655
|
this.extra.set(p.id, [g]);
|
|
12231
12656
|
}
|
|
12657
|
+
/** A round through the mirror shatters its glass: the shards drop out of the housing, which stays on with the hole. Returns whether there was glass to break. */
|
|
12658
|
+
shatterMirror(id) {
|
|
12659
|
+
let broke = false;
|
|
12660
|
+
for (const o of this.extra.get(id) ?? []) {
|
|
12661
|
+
o.traverse((m) => {
|
|
12662
|
+
if (!m.userData.mirrorGlass || !m.visible) return;
|
|
12663
|
+
m.visible = false;
|
|
12664
|
+
broke = true;
|
|
12665
|
+
});
|
|
12666
|
+
}
|
|
12667
|
+
return broke;
|
|
12668
|
+
}
|
|
12232
12669
|
makeBattery(p) {
|
|
12233
12670
|
const mats = this.I.MATERIALS;
|
|
12234
12671
|
const g = new this.THREE.Group();
|
|
@@ -12274,6 +12711,11 @@ var VehicleVisuals = class {
|
|
|
12274
12711
|
if (carrier) carrier.attach(m);
|
|
12275
12712
|
}
|
|
12276
12713
|
/** The door or lid a hinge group stands for. */
|
|
12714
|
+
/** The part a separately drawn piece stands for (a mirror on the door, a battery under the hood), so a shot at it lands on that part. */
|
|
12715
|
+
extraIdOf(o) {
|
|
12716
|
+
for (const [id, objs] of this.extra) if (objs.includes(o)) return id;
|
|
12717
|
+
return void 0;
|
|
12718
|
+
}
|
|
12277
12719
|
plateIdOf(o) {
|
|
12278
12720
|
return o.userData.plateOf;
|
|
12279
12721
|
}
|
|
@@ -12330,6 +12772,7 @@ var VehicleVisuals = class {
|
|
|
12330
12772
|
best = g;
|
|
12331
12773
|
}
|
|
12332
12774
|
}
|
|
12775
|
+
if (p.hp > LAMINATED_HP && best.userData.glass) best.userData.glass.type = "laminated";
|
|
12333
12776
|
this.kit.hitGlass(this.c, best, pt, new V3(...dir), force);
|
|
12334
12777
|
return !!best.userData.glass?.broken;
|
|
12335
12778
|
}
|
|
@@ -12351,6 +12794,17 @@ var VehicleVisuals = class {
|
|
|
12351
12794
|
if (!z) return;
|
|
12352
12795
|
for (const w of z.wheels) if (w.parent) this.I.flattenTire(w, k);
|
|
12353
12796
|
}
|
|
12797
|
+
/** A tyre driven flat until it tore apart: the rubber is gone and the bare rim shows. */
|
|
12798
|
+
tyreTornOff(p) {
|
|
12799
|
+
const z = this.zone(p);
|
|
12800
|
+
if (!z) return;
|
|
12801
|
+
for (const w of z.wheels) {
|
|
12802
|
+
if (!w.parent || w.userData.tornOff) continue;
|
|
12803
|
+
w.userData.tornOff = true;
|
|
12804
|
+
for (const m of this.I.tireMeshes(w)) m.visible = false;
|
|
12805
|
+
w.position.y -= (w.userData.r ?? DEFAULT_WHEEL_RADIUS) * SIDEWALL_SHARE;
|
|
12806
|
+
}
|
|
12807
|
+
}
|
|
12354
12808
|
/** Burning rubber: the tyre sags and goes black as it melts. */
|
|
12355
12809
|
tyreMelt(p, melt) {
|
|
12356
12810
|
const z = this.zone(p);
|
|
@@ -12674,6 +13128,41 @@ var BLUNT = { steel: 0.8, aluminium: 1, glass: 1.5, plastic: 1.2, rubber: 0.3 };
|
|
|
12674
13128
|
var SHIELDED = /* @__PURE__ */ new Set(["engine", "gearbox", "frame", "radiator", "fuel_tank", "oil_pan", "battery", "alternator", "brakes", "steering", "shocks"]);
|
|
12675
13129
|
var AMBIENT = 20;
|
|
12676
13130
|
var FLAME = 700;
|
|
13131
|
+
var STEEL_MM = { panel: 0.8, door: 1.6, hood: 0.8, lid: 0.8, roof: 0.8, fender: 0.8, pillar: 2, frame: 4, rim: 3, engine: 40, gearbox: 25, fuel_tank: 1, radiator: 1, battery: 2, brakes: 10, steering: 8, shocks: 6, oil_pan: 2, alternator: 4, hitch: 8, other: 2 };
|
|
13132
|
+
var ARMOUR_STEEL_MM = { IIIA: 4, III: 7 };
|
|
13133
|
+
var SOFT_LEAD = 0.8;
|
|
13134
|
+
var TYRE_FLAT_HEALTH = 0.1;
|
|
13135
|
+
var TYRE_BURST_JOULES = 1500;
|
|
13136
|
+
var TYRE_BURST_CALIBER = 12;
|
|
13137
|
+
var TYRE_LEAK_PER_MM2 = 26e-5;
|
|
13138
|
+
var FLAT_TYRE_LIFE_M = 1500;
|
|
13139
|
+
var FLAT_TYRE_TOWN_SPEED = 8;
|
|
13140
|
+
var RUN_FLAT_LIFE_M = 8e4;
|
|
13141
|
+
var RUN_FLAT_SPEED = 22;
|
|
13142
|
+
var TYRE_RUNFLAT_HEALTH = 0.6;
|
|
13143
|
+
var CRUMPLE_BASE_M = 0.04;
|
|
13144
|
+
var CRUMPLE_PER_FORCE_M = 0.15;
|
|
13145
|
+
var CRUMPLE_MIN_FORCE = 0.3;
|
|
13146
|
+
var CRUMPLE_MAX_FORCE = 1.4;
|
|
13147
|
+
var RIM_HEALTH = 0.05;
|
|
13148
|
+
var RIM_GRIND_PER_M = 4e-4;
|
|
13149
|
+
var TYRE_FLAT_SAG = 0.24;
|
|
13150
|
+
var TYRE_FLAT_AIR = 0.2;
|
|
13151
|
+
var BRAKE_FLUID_NEEDED = 0.6;
|
|
13152
|
+
var BRAKE_NO_FLUID = 0.15;
|
|
13153
|
+
var ENGINE_WORKING_C = 90;
|
|
13154
|
+
var ENGINE_SETTLE_S = 30;
|
|
13155
|
+
var ENGINE_HEATING = 0.5;
|
|
13156
|
+
var ENGINE_STEAM_C = 120;
|
|
13157
|
+
var ENGINE_SEIZE_C = 150;
|
|
13158
|
+
var COOLANT_NEEDED = 0.4;
|
|
13159
|
+
var OIL_NEEDED = 0.3;
|
|
13160
|
+
var OIL_WEAR = 4e-3;
|
|
13161
|
+
var ENGINE_SEIZE_HP = 0.2;
|
|
13162
|
+
var EXPANDED_HOLE = 1.6;
|
|
13163
|
+
var MIN_SPENT = 0.08;
|
|
13164
|
+
var WELDED = /* @__PURE__ */ new Set(["roof", "pillar", "frame", "panel"]);
|
|
13165
|
+
var CRUMPLES = /* @__PURE__ */ new Set(["door", "hood", "lid", "fender", "bumper"]);
|
|
12677
13166
|
var FRACTURE = { glass: "shatter", plastic: "snap", rubber: "tear", steel: "none", aluminium: "none" };
|
|
12678
13167
|
var add3 = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
12679
13168
|
var r3 = (v) => [Math.round(v[0] * 1e5) / 1e5, Math.round(v[1] * 1e5) / 1e5, Math.round(v[2] * 1e5) / 1e5];
|
|
@@ -12685,6 +13174,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12685
13174
|
let c = vehicleCtx;
|
|
12686
13175
|
const id = options.id ?? c.key;
|
|
12687
13176
|
const headless = !!options.headless;
|
|
13177
|
+
const armourId = armourLevelOf(c.meta, options.armour);
|
|
13178
|
+
const armourLevel = armourId ? { ...ARMOUR_LEVELS[armourId], ...options.armourLevels?.[armourId] } : null;
|
|
12688
13179
|
const seedBase = strHash(`${c.key}|${id}|${options.seed ?? 0}`);
|
|
12689
13180
|
let rand = rng(seedBase);
|
|
12690
13181
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
@@ -12742,7 +13233,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12742
13233
|
return d.ignitionC ? 1 : 0;
|
|
12743
13234
|
};
|
|
12744
13235
|
const setup = () => {
|
|
12745
|
-
model = buildVehicleModel(kit, c, { hitch: options.hitch });
|
|
13236
|
+
model = buildVehicleModel(kit, c, { hitch: options.hitch, armour: armourLevel });
|
|
12746
13237
|
ps.clear();
|
|
12747
13238
|
for (const d of model.parts) {
|
|
12748
13239
|
const kg = burnKg(d);
|
|
@@ -12762,6 +13253,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12762
13253
|
lost: false,
|
|
12763
13254
|
status: "intact",
|
|
12764
13255
|
flat: false,
|
|
13256
|
+
air: 1,
|
|
13257
|
+
airLoss: 0,
|
|
12765
13258
|
fireLoss: 0
|
|
12766
13259
|
});
|
|
12767
13260
|
}
|
|
@@ -12862,7 +13355,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12862
13355
|
vis.breakLight(d, true);
|
|
12863
13356
|
break;
|
|
12864
13357
|
case "tyre":
|
|
12865
|
-
vis.
|
|
13358
|
+
if (status === "broken") vis.tyreTornOff(d);
|
|
13359
|
+
else vis.tyreFlat(d, 0.34);
|
|
12866
13360
|
break;
|
|
12867
13361
|
case "mirror":
|
|
12868
13362
|
case "bumper":
|
|
@@ -12892,7 +13386,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12892
13386
|
const damage = (p, amount, dtype, source, out, cause) => {
|
|
12893
13387
|
if (p.lost || !(amount > 0)) return;
|
|
12894
13388
|
const before = p.hp;
|
|
12895
|
-
|
|
13389
|
+
const crumples = dtype === "blunt" && CRUMPLES.has(p.def.role) && (p.def.material === "steel" || p.def.material === "aluminium");
|
|
13390
|
+
p.hp = Math.max(WELDED.has(p.def.role) || crumples ? 1 : 0, p.hp - amount);
|
|
12896
13391
|
out.push(ev({ type: "damage", part: p.def.id, amount: r2(before - p.hp), hp: r2(p.hp), damageType: dtype, ...source ? { source } : {} }));
|
|
12897
13392
|
wear(p, amount, out);
|
|
12898
13393
|
syncZone(p);
|
|
@@ -12935,11 +13430,22 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12935
13430
|
const p = v.clone().applyMatrix4(M);
|
|
12936
13431
|
return [p.x, p.y, p.z];
|
|
12937
13432
|
};
|
|
12938
|
-
const armour = () =>
|
|
13433
|
+
const armour = () => armourLevel?.steel ?? 1;
|
|
13434
|
+
const steelMm = (d) => {
|
|
13435
|
+
const base = STEEL_MM[d.role] ?? STEEL_MM.panel;
|
|
13436
|
+
return d.material === "steel" || d.material === "aluminium" ? base * (armourLevel ? ARMOUR_STEEL_MM[armourId === "III" ? "III" : "IIIA"] / STEEL_MM.panel : 1) : base;
|
|
13437
|
+
};
|
|
13438
|
+
const goesThrough = (h, d, p, energy, res) => {
|
|
13439
|
+
const piercing = h.piercing ?? 1;
|
|
13440
|
+
const metal = d.material === "steel" || d.material === "aluminium";
|
|
13441
|
+
const mm = h.penetration?.steel;
|
|
13442
|
+
if (metal && mm != null) return mm >= steelMm(d) * (p.dents.length > 3 ? 0.9 : 1);
|
|
13443
|
+
return energy * (metal || d.material === "glass" ? piercing : 1) >= res;
|
|
13444
|
+
};
|
|
12939
13445
|
const resist = (d, p) => {
|
|
12940
13446
|
let base = RESIST[d.material] ?? 100;
|
|
12941
13447
|
if (d.material === "glass" && d.hp > 1e3) base = 120;
|
|
12942
|
-
if (d.material === "glass" &&
|
|
13448
|
+
if (d.material === "glass" && armourLevel) base = armourLevel.glassStops;
|
|
12943
13449
|
const k = ROLE_RESIST[d.role] ?? 1;
|
|
12944
13450
|
const arm = d.material === "steel" || d.material === "aluminium" ? armour() : 1;
|
|
12945
13451
|
return base * k * arm * (p.dents.length > 3 ? 0.9 : 1);
|
|
@@ -12962,7 +13468,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12962
13468
|
if (vis) {
|
|
12963
13469
|
const pos = new V3(d.box.center[0], Math.max(0.2, (c.meta.clearance ?? 0.3) * 0.8), d.box.center[2]);
|
|
12964
13470
|
if (fluid === "coolant") kit.addEmitter(c, new V3(d.box.center[0], d.box.center[1] + 0.2, d.box.center[2]), "white", { rate: 5, s: 0.8 });
|
|
12965
|
-
|
|
13471
|
+
if ((c.meta.kind ?? "car") !== "boat") l.pud = I.puddle(c, fluidKind(fluid), pos, fluid === "oil" ? 0.8 : fluid === "brake_fluid" ? 0.4 : 1.1);
|
|
12966
13472
|
}
|
|
12967
13473
|
}
|
|
12968
13474
|
const out3 = dirToWorld(new V3(dirLocal.x * 0.5, dirLocal.y * 0.5 - 0.6, dirLocal.z * 0.5));
|
|
@@ -12999,7 +13505,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
12999
13505
|
exploded = true;
|
|
13000
13506
|
const d = tank.def;
|
|
13001
13507
|
const spec = d.explodes ?? { energy: 8e5, radius: 5, atC: 320 };
|
|
13002
|
-
out.push(ev({ type: "explode", part: d.id, point: toWorld(d.box.center), energy: spec.energy, radius: spec.radius }));
|
|
13508
|
+
out.push(ev({ type: "explode", part: d.id, point: toWorld(d.box.center), energy: spec.energy, radius: spec.radius, ..."effect" in spec && spec.effect !== void 0 ? { effect: spec.effect } : {} }));
|
|
13003
13509
|
tank.liters = 0;
|
|
13004
13510
|
leaks.delete(d.id);
|
|
13005
13511
|
if (vis) kit.explode(c, { delay: 0.6 });
|
|
@@ -13099,7 +13605,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13099
13605
|
const dir = dirToLocal(h.direction ?? [0, 0, -1]).normalize();
|
|
13100
13606
|
const n = h.normal ? dirToLocal(h.normal).normalize() : normalOf(d, v);
|
|
13101
13607
|
const res = resist(d, p);
|
|
13102
|
-
const through = E
|
|
13608
|
+
const through = goesThrough(h, d, p, E, res);
|
|
13103
13609
|
const cosInc = Math.abs(dir.dot(n));
|
|
13104
13610
|
const metal = d.material === "steel" || d.material === "aluminium";
|
|
13105
13611
|
const worldPoint = toWorld(v);
|
|
@@ -13110,13 +13616,21 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13110
13616
|
vis?.spark([v.x, v.y, v.z], [refl.x, refl.y, refl.z], 6);
|
|
13111
13617
|
return;
|
|
13112
13618
|
}
|
|
13113
|
-
const
|
|
13619
|
+
const opens = (h.piercing ?? 1) < SOFT_LEAD ? EXPANDED_HOLE : 1;
|
|
13620
|
+
const holeDiam = Math.max(6e-3, cal / 1e3 * (d.material === "glass" ? 1.4 : 1) * opens);
|
|
13621
|
+
const energyLeft = () => {
|
|
13622
|
+
const metal2 = d.material === "steel" || d.material === "aluminium";
|
|
13623
|
+
const mm = h.penetration?.steel;
|
|
13624
|
+
const spent = metal2 && mm ? clamp(steelMm(d) / mm, MIN_SPENT, 1) : clamp(res / Math.max(E, 1), MIN_SPENT, 1);
|
|
13625
|
+
return E * (1 - spent);
|
|
13626
|
+
};
|
|
13114
13627
|
const push = (hole) => {
|
|
13115
13628
|
if (!hole) return;
|
|
13116
13629
|
const hp = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), diameter: r2(holeDiam), through };
|
|
13117
13630
|
p.holes.push(hp);
|
|
13118
|
-
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])))))) } : {} }));
|
|
13631
|
+
out.push(ev({ type: "hole", part: d.id, hole: hp, worldPoint, ...through ? { energyLeft: r2(energyLeft()) } : {}, ...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])))))) } : {} }));
|
|
13119
13632
|
vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam, d);
|
|
13633
|
+
if (d.role === "mirror") vis?.shatterMirror(d.id);
|
|
13120
13634
|
startLeak(p, v, cal, out, dir);
|
|
13121
13635
|
};
|
|
13122
13636
|
switch (d.material) {
|
|
@@ -13129,7 +13643,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13129
13643
|
const crack = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), length: r2(0.15 + force * 0.35), rays: 6 + Math.round(force * 6) };
|
|
13130
13644
|
p.cracks.push(crack);
|
|
13131
13645
|
out.push(ev({ type: "crack", part: d.id, crack, worldPoint }));
|
|
13132
|
-
damage(p, E * 0.5, "ballistic", h.source, out, worldPoint);
|
|
13646
|
+
damage(p, E * (armourLevel && !through ? armourLevel.glassTakes : 0.5), "ballistic", h.source, out, worldPoint);
|
|
13133
13647
|
} else damage(p, laminated ? E * 0.5 : p.hp, "ballistic", h.source, out, worldPoint);
|
|
13134
13648
|
break;
|
|
13135
13649
|
}
|
|
@@ -13137,8 +13651,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13137
13651
|
push(through);
|
|
13138
13652
|
damage(p, E * 0.4, "ballistic", h.source, out, worldPoint);
|
|
13139
13653
|
if (through && !p.lost && !p.flat) {
|
|
13140
|
-
p
|
|
13141
|
-
|
|
13654
|
+
if (E >= TYRE_BURST_JOULES || cal >= TYRE_BURST_CALIBER) deflate(p, 1);
|
|
13655
|
+
else p.airLoss += TYRE_LEAK_PER_MM2 * Math.PI * (cal / 2) ** 2;
|
|
13142
13656
|
}
|
|
13143
13657
|
break;
|
|
13144
13658
|
}
|
|
@@ -13254,7 +13768,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13254
13768
|
const electric = c.spec.fuel === "electric";
|
|
13255
13769
|
const tank = tankPart();
|
|
13256
13770
|
const fuel = tank ? tank.liters ?? 0 : electric ? 1 : 1;
|
|
13257
|
-
const engineOk = sound("engine") && !exploded && fuel > 0.05;
|
|
13771
|
+
const engineOk = sound("engine") && !exploded && !seized && fuel > 0.05;
|
|
13258
13772
|
const powered = sound("battery") && sound("alternator");
|
|
13259
13773
|
const gearOk = sound("gearbox");
|
|
13260
13774
|
const brakes = [...ps.values()].find((p) => p.def.role === "brakes");
|
|
@@ -13414,9 +13928,12 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13414
13928
|
const state = () => stateCache ?? (stateCache = buildState());
|
|
13415
13929
|
const isSimulating = () => {
|
|
13416
13930
|
if (leaks.size > 0) return true;
|
|
13417
|
-
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
|
|
13931
|
+
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT || p.airLoss > 0 && p.air > 0)) return true;
|
|
13418
13932
|
return false;
|
|
13419
13933
|
};
|
|
13934
|
+
let engineHeat = ENGINE_WORKING_C;
|
|
13935
|
+
let seized = false;
|
|
13936
|
+
let steaming = false;
|
|
13420
13937
|
const surfaceRays = new THREE.Raycaster();
|
|
13421
13938
|
surfaceRays.layers.enableAll();
|
|
13422
13939
|
surfaceRays.camera = new THREE.PerspectiveCamera();
|
|
@@ -13440,14 +13957,24 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13440
13957
|
if (pane) return ps.get(pane) ?? null;
|
|
13441
13958
|
for (let q = o; q && q !== c.vehicle; q = q.parent) {
|
|
13442
13959
|
if (vis?.plateIdOf(q)) return ps.get(vis.plateIdOf(q)) ?? null;
|
|
13960
|
+
const piece = vis?.extraIdOf(q);
|
|
13961
|
+
if (piece) return ps.get(piece) ?? null;
|
|
13443
13962
|
}
|
|
13444
13963
|
return locate(v, 0.08, (p) => p.def.role === "glass");
|
|
13445
13964
|
};
|
|
13965
|
+
const surfaceMeshes = [];
|
|
13966
|
+
const collectSurfaces = (o) => {
|
|
13967
|
+
if (!o.visible || o.userData.decal || o.userData.ghost || o.userData.noSurfaceRay) return;
|
|
13968
|
+
if (o.isMesh) surfaceMeshes.push(o);
|
|
13969
|
+
for (const child of o.children) collectSurfaces(child);
|
|
13970
|
+
};
|
|
13446
13971
|
const surfaceRay = (ray) => {
|
|
13447
13972
|
sync();
|
|
13448
13973
|
surfaceRays.set(new V3(ray.origin[0], ray.origin[1], ray.origin[2]), new V3(ray.direction[0], ray.direction[1], ray.direction[2]));
|
|
13449
13974
|
surfaceRays.far = ray.maxDistance;
|
|
13450
|
-
|
|
13975
|
+
surfaceMeshes.length = 0;
|
|
13976
|
+
for (const child of c.vehicle.children) collectSurfaces(child);
|
|
13977
|
+
for (const h of surfaceRays.intersectObjects(surfaceMeshes, false)) {
|
|
13451
13978
|
if (!h.face || !isDrawnSurface(h.object)) continue;
|
|
13452
13979
|
const p = partOfSurface(h.object, c.vehicle.worldToLocal(h.point.clone()));
|
|
13453
13980
|
if (!p || p.lost || ray.ignored?.(p.def.id)) continue;
|
|
@@ -13481,7 +14008,29 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13481
14008
|
applyEffects();
|
|
13482
14009
|
return out;
|
|
13483
14010
|
};
|
|
14011
|
+
const runFlat = armourId != null;
|
|
14012
|
+
const tyreHealth = (p) => {
|
|
14013
|
+
if (p.lost) return RIM_HEALTH;
|
|
14014
|
+
if (p.flat) return runFlat ? TYRE_RUNFLAT_HEALTH : TYRE_FLAT_HEALTH;
|
|
14015
|
+
return Math.min(p.hp / p.def.hp, 0.2 + 0.8 * p.air);
|
|
14016
|
+
};
|
|
14017
|
+
const tyreOf = (rim) => {
|
|
14018
|
+
let best;
|
|
14019
|
+
let bestD = Infinity;
|
|
14020
|
+
for (const p of ps.values()) {
|
|
14021
|
+
if (p.def.role !== "tyre") continue;
|
|
14022
|
+
const d = Math.hypot(p.def.box.center[0] - rim.def.box.center[0], p.def.box.center[2] - rim.def.box.center[2]);
|
|
14023
|
+
if (d < bestD) [best, bestD] = [p, d];
|
|
14024
|
+
}
|
|
14025
|
+
return best;
|
|
14026
|
+
};
|
|
14027
|
+
const deflate = (p, share) => {
|
|
14028
|
+
p.air = Math.max(0, p.air - share);
|
|
14029
|
+
vis?.tyreFlat(p.def, TYRE_FLAT_SAG * (1 - p.air));
|
|
14030
|
+
if (p.air <= TYRE_FLAT_AIR) p.flat = true;
|
|
14031
|
+
};
|
|
13484
14032
|
const update = (dt) => {
|
|
14033
|
+
for (const p of ps.values()) if (p.airLoss > 0 && p.air > 0 && !p.lost) deflate(p, p.airLoss * dt);
|
|
13485
14034
|
clock += dt;
|
|
13486
14035
|
const out = queue;
|
|
13487
14036
|
queue = [];
|
|
@@ -13597,6 +14146,70 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
|
|
|
13597
14146
|
partName: (pid) => ps.get(pid)?.def.name ?? pid,
|
|
13598
14147
|
status: computeStatus,
|
|
13599
14148
|
animations: () => createVehicleClips(model, vis, kit, c),
|
|
14149
|
+
crumple: (point, direction, depth) => {
|
|
14150
|
+
if (!vis || depth <= 0) return;
|
|
14151
|
+
sync();
|
|
14152
|
+
const force = clamp((depth - CRUMPLE_BASE_M) / CRUMPLE_PER_FORCE_M, CRUMPLE_MIN_FORCE, CRUMPLE_MAX_FORCE);
|
|
14153
|
+
kit.impact(c, toLocal(point), dirToLocal(direction).normalize(), force, { maxDepth: depth });
|
|
14154
|
+
stateCache = void 0;
|
|
14155
|
+
},
|
|
14156
|
+
rollFlat: (meters, speed) => {
|
|
14157
|
+
for (const p of ps.values()) {
|
|
14158
|
+
if (p.def.role !== "tyre" || !p.flat || p.lost) continue;
|
|
14159
|
+
const life = runFlat ? RUN_FLAT_LIFE_M * Math.min(1, RUN_FLAT_SPEED / Math.max(speed, 1)) : FLAT_TYRE_LIFE_M * clamp(FLAT_TYRE_TOWN_SPEED / Math.max(speed, 1), 0.2, 1.5);
|
|
14160
|
+
p.hp = Math.max(0, p.hp - p.def.hp * meters / life);
|
|
14161
|
+
if (p.hp > 0) continue;
|
|
14162
|
+
const at = toWorld(p.def.box.center);
|
|
14163
|
+
lose(p, "broken", queue, at);
|
|
14164
|
+
queue.push(ev({ type: "debris", part: p.def.id, kind: "fibers", point: at, normal: [0, 1, 0], count: 12, speed: Math.max(2, speed * 0.5) }));
|
|
14165
|
+
}
|
|
14166
|
+
let grinding = 0;
|
|
14167
|
+
for (const rim of ps.values()) {
|
|
14168
|
+
if (rim.def.role !== "rim" || rim.lost || !tyreOf(rim)?.lost) continue;
|
|
14169
|
+
grinding += 1;
|
|
14170
|
+
rim.hp = Math.max(1, rim.hp - rim.def.hp * RIM_GRIND_PER_M * meters);
|
|
14171
|
+
const [x, y, z] = rim.def.box.center;
|
|
14172
|
+
queue.push(ev({ type: "debris", part: rim.def.id, kind: "sparks", point: toWorld([x, y - rim.def.box.size[1] / 2, z]), normal: [0, 1, 0], count: Math.min(30, Math.ceil(4 + speed)), speed: Math.max(2, speed * 0.4) }));
|
|
14173
|
+
}
|
|
14174
|
+
stateCache = void 0;
|
|
14175
|
+
return grinding;
|
|
14176
|
+
},
|
|
14177
|
+
runEngine: (seconds, load) => {
|
|
14178
|
+
const engine = [...ps.values()].find((p) => p.def.role === "engine");
|
|
14179
|
+
if (!engine || engine.lost || seized) return engineHeat;
|
|
14180
|
+
const share = (fluid) => {
|
|
14181
|
+
const list = [...ps.values()].filter((p) => p.def.contains?.fluid === fluid);
|
|
14182
|
+
return list.length ? Math.min(...list.map((p) => (p.liters ?? 0) / p.def.contains.liters)) : 1;
|
|
14183
|
+
};
|
|
14184
|
+
const coolant = share("coolant");
|
|
14185
|
+
const oil = share("oil");
|
|
14186
|
+
const work = 0.4 + 0.6 * clamp(load, 0, 1);
|
|
14187
|
+
if (coolant < COOLANT_NEEDED) engineHeat += (COOLANT_NEEDED - coolant) / COOLANT_NEEDED * ENGINE_HEATING * work * seconds;
|
|
14188
|
+
else engineHeat += (ENGINE_WORKING_C - engineHeat) * Math.min(1, seconds / ENGINE_SETTLE_S);
|
|
14189
|
+
if (oil < OIL_NEEDED) engine.hp = Math.max(0, engine.hp - engine.def.hp * OIL_WEAR * work * seconds * ((OIL_NEEDED - oil) / OIL_NEEDED));
|
|
14190
|
+
if (engineHeat >= ENGINE_STEAM_C && vis && !steaming) {
|
|
14191
|
+
steaming = true;
|
|
14192
|
+
kit.addEmitter(c, new V3(engine.def.box.center[0], engine.def.box.center[1] + 0.4, 0), "white", { rate: 8, s: 1 });
|
|
14193
|
+
}
|
|
14194
|
+
if (engineHeat >= ENGINE_SEIZE_C || engine.hp <= engine.def.hp * ENGINE_SEIZE_HP) {
|
|
14195
|
+
seized = true;
|
|
14196
|
+
queue.push(ev({ type: "damage", part: engine.def.id, amount: 0, hp: r2(engine.hp), damageType: "heat" }));
|
|
14197
|
+
}
|
|
14198
|
+
stateCache = void 0;
|
|
14199
|
+
return engineHeat;
|
|
14200
|
+
},
|
|
14201
|
+
driveReport: () => {
|
|
14202
|
+
const health = (role) => {
|
|
14203
|
+
const list = [...ps.values()].filter((p) => p.def.role === role);
|
|
14204
|
+
if (!list.length) return 1;
|
|
14205
|
+
return Math.min(...list.map((p) => p.lost || !attached(p.def.id) ? 0 : p.hp / p.def.hp));
|
|
14206
|
+
};
|
|
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) }));
|
|
14208
|
+
const status = computeStatus();
|
|
14209
|
+
const brakeSystem = [...ps.values()].find((p) => p.def.role === "brakes" && p.def.contains);
|
|
14210
|
+
const fluidLeft = brakeSystem ? clamp((brakeSystem.liters ?? 0) / brakeSystem.def.contains.liters / BRAKE_FLUID_NEEDED, 0, 1) : 1;
|
|
14211
|
+
return { tyres, brakes: Math.min(1, 1 / status.brakeFactor) * (BRAKE_NO_FLUID + (1 - BRAKE_NO_FLUID) * fluidLeft), engine: status.canStart ? health("engine") : 0, suspension: health("shocks") };
|
|
14212
|
+
},
|
|
13600
14213
|
partWorldCenter: (pid) => {
|
|
13601
14214
|
sync();
|
|
13602
14215
|
const d = ps.get(pid)?.def;
|
|
@@ -13681,6 +14294,34 @@ function createCreaseQueue() {
|
|
|
13681
14294
|
};
|
|
13682
14295
|
}
|
|
13683
14296
|
|
|
14297
|
+
// src/destructible/crash.ts
|
|
14298
|
+
var CRASH_RULES = { energyToDamage: 0.3, hitSpacing: 0.6, minSpeed: 1.5, rows: [0, 0.3], crushPerMs: 0.035, maxCrush: 0.6 };
|
|
14299
|
+
function crashDeltaV(speed, massKg, otherMassKg) {
|
|
14300
|
+
if (!Number.isFinite(otherMassKg)) return speed;
|
|
14301
|
+
return speed * otherMassKg / (massKg + otherMassKg);
|
|
14302
|
+
}
|
|
14303
|
+
function crashVehicle(d, crash) {
|
|
14304
|
+
const rules = { ...CRASH_RULES, ...crash.rules };
|
|
14305
|
+
const deltaV = crashDeltaV(crash.speed, crash.massKg, crash.otherMassKg);
|
|
14306
|
+
const energy = 0.5 * crash.massKg * deltaV * deltaV;
|
|
14307
|
+
if (crash.speed < rules.minSpeed) return { deltaV, energy, events: [] };
|
|
14308
|
+
const [dx, , dz] = crash.direction;
|
|
14309
|
+
const length = Math.hypot(dx, dz) || 1;
|
|
14310
|
+
const across = [-dz / length, 0, dx / length];
|
|
14311
|
+
const hits = Math.max(1, Math.round(crash.contactWidth / rules.hitSpacing));
|
|
14312
|
+
const each = energy * rules.energyToDamage / (hits * rules.rows.length);
|
|
14313
|
+
const events = [];
|
|
14314
|
+
for (const up of rules.rows)
|
|
14315
|
+
for (let i = 0; i < hits; i++) {
|
|
14316
|
+
const offset = hits === 1 ? 0 : (i / (hits - 1) - 0.5) * crash.contactWidth;
|
|
14317
|
+
const point = [crash.point[0] + across[0] * offset, crash.point[1] + up, crash.point[2] + across[2] * offset];
|
|
14318
|
+
events.push(...d.hit({ type: "blunt", point, direction: [dx / length, 0, dz / length], energy: each, radius: rules.hitSpacing / 2, ...crash.source ? { source: crash.source } : {} }));
|
|
14319
|
+
}
|
|
14320
|
+
const crush = Math.min(rules.maxCrush, deltaV * rules.crushPerMs);
|
|
14321
|
+
d.crumple(crash.point, [dx / length, 0, dz / length], crush);
|
|
14322
|
+
return { deltaV, energy, events };
|
|
14323
|
+
}
|
|
14324
|
+
|
|
13684
14325
|
// src/destructible/enclosure.ts
|
|
13685
14326
|
var CABIN_LEAK = 0.02;
|
|
13686
14327
|
var HOLE_RATE_PER_AREA = 3e3;
|
|
@@ -13765,17 +14406,22 @@ function cabinEnclosure(source, options = {}) {
|
|
|
13765
14406
|
};
|
|
13766
14407
|
}
|
|
13767
14408
|
export {
|
|
14409
|
+
ARMOUR_LEVELS,
|
|
13768
14410
|
CABIN_LEAK,
|
|
14411
|
+
CRASH_RULES,
|
|
13769
14412
|
JOINT_RULES,
|
|
13770
14413
|
LEGACY_STATES,
|
|
13771
14414
|
LEGACY_ZONE_IDS,
|
|
13772
14415
|
SEED_SALTS,
|
|
14416
|
+
armourLevelOf,
|
|
13773
14417
|
buildVehicleItem,
|
|
13774
14418
|
buildVehicleModel,
|
|
13775
14419
|
cabinBoxOf,
|
|
13776
14420
|
cabinEnclosure,
|
|
13777
14421
|
clamp,
|
|
13778
14422
|
crackedGlassMaterial,
|
|
14423
|
+
crashDeltaV,
|
|
14424
|
+
crashVehicle,
|
|
13779
14425
|
createCreaseQueue,
|
|
13780
14426
|
createLod,
|
|
13781
14427
|
createTranslate,
|