@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.global.js
CHANGED
|
@@ -23,17 +23,22 @@ var GameVehicles = (() => {
|
|
|
23
23
|
// src/index.ts
|
|
24
24
|
var index_exports = {};
|
|
25
25
|
__export(index_exports, {
|
|
26
|
+
ARMOUR_LEVELS: () => ARMOUR_LEVELS,
|
|
26
27
|
CABIN_LEAK: () => CABIN_LEAK,
|
|
28
|
+
CRASH_RULES: () => CRASH_RULES,
|
|
27
29
|
JOINT_RULES: () => JOINT_RULES,
|
|
28
30
|
LEGACY_STATES: () => LEGACY_STATES,
|
|
29
31
|
LEGACY_ZONE_IDS: () => LEGACY_ZONE_IDS,
|
|
30
32
|
SEED_SALTS: () => SEED_SALTS,
|
|
33
|
+
armourLevelOf: () => armourLevelOf,
|
|
31
34
|
buildVehicleItem: () => buildVehicleItem,
|
|
32
35
|
buildVehicleModel: () => buildVehicleModel,
|
|
33
36
|
cabinBoxOf: () => cabinBoxOf,
|
|
34
37
|
cabinEnclosure: () => cabinEnclosure,
|
|
35
38
|
clamp: () => clamp,
|
|
36
39
|
crackedGlassMaterial: () => crackedGlassMaterial,
|
|
40
|
+
crashDeltaV: () => crashDeltaV,
|
|
41
|
+
crashVehicle: () => crashVehicle,
|
|
37
42
|
createCreaseQueue: () => createCreaseQueue,
|
|
38
43
|
createLod: () => createLod,
|
|
39
44
|
createTranslate: () => createTranslate,
|
|
@@ -57,7 +62,7 @@ var GameVehicles = (() => {
|
|
|
57
62
|
wrapThreeForLod: () => wrapThreeForLod
|
|
58
63
|
});
|
|
59
64
|
|
|
60
|
-
// ../game-shapes/dist/chunk-
|
|
65
|
+
// ../game-shapes/dist/chunk-M5N2SHNL.js
|
|
61
66
|
function canonicalJson(value) {
|
|
62
67
|
if (value === null) return "null";
|
|
63
68
|
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
@@ -1304,8 +1309,8 @@ var GameVehicles = (() => {
|
|
|
1304
1309
|
groups: []
|
|
1305
1310
|
};
|
|
1306
1311
|
}
|
|
1307
|
-
function make(
|
|
1308
|
-
switch (kind) {
|
|
1312
|
+
function make(p) {
|
|
1313
|
+
switch (p.kind) {
|
|
1309
1314
|
case "box": {
|
|
1310
1315
|
const size = p.size ?? [1, 1, 1];
|
|
1311
1316
|
const segments = p.segments ?? [1, 1, 1];
|
|
@@ -1334,7 +1339,7 @@ var GameVehicles = (() => {
|
|
|
1334
1339
|
case "mesh":
|
|
1335
1340
|
return mesh(p);
|
|
1336
1341
|
default:
|
|
1337
|
-
throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
|
|
1342
|
+
throw new Error(`game-shapes: unknown primitive kind "${String(p.kind)}"`);
|
|
1338
1343
|
}
|
|
1339
1344
|
}
|
|
1340
1345
|
var templates = /* @__PURE__ */ new Map();
|
|
@@ -1342,7 +1347,7 @@ var GameVehicles = (() => {
|
|
|
1342
1347
|
var stats = { hits: 0, misses: 0 };
|
|
1343
1348
|
function buildPrimitive(node, options = {}) {
|
|
1344
1349
|
const parameters = primitiveParameters(node, options);
|
|
1345
|
-
if (node.kind === "mesh" || templateLimit === 0) return make(node
|
|
1350
|
+
if (node.kind === "mesh" || templateLimit === 0) return make({ ...node, ...parameters });
|
|
1346
1351
|
const key = `${node.kind}:${canonicalJson(parameters)}`;
|
|
1347
1352
|
const cached = templates.get(key);
|
|
1348
1353
|
if (cached) {
|
|
@@ -1352,7 +1357,7 @@ var GameVehicles = (() => {
|
|
|
1352
1357
|
return cached;
|
|
1353
1358
|
}
|
|
1354
1359
|
stats.misses++;
|
|
1355
|
-
const built = make(node
|
|
1360
|
+
const built = make({ ...node, ...parameters });
|
|
1356
1361
|
templates.set(key, built);
|
|
1357
1362
|
if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
|
|
1358
1363
|
return built;
|
|
@@ -4877,6 +4882,7 @@ var GameVehicles = (() => {
|
|
|
4877
4882
|
}
|
|
4878
4883
|
|
|
4879
4884
|
// src/hood.ts
|
|
4885
|
+
var HOOD_LIFT = 8e-3;
|
|
4880
4886
|
function installHood(kit) {
|
|
4881
4887
|
const THREE = kit.THREE;
|
|
4882
4888
|
const V3 = THREE.Vector3;
|
|
@@ -4899,7 +4905,7 @@ var GameVehicles = (() => {
|
|
|
4899
4905
|
for (let i = 0; i < p.count; i++) {
|
|
4900
4906
|
p.setX(i, cx + (p.getX(i) - cx) * sx);
|
|
4901
4907
|
p.setZ(i, p.getZ(i) * sz);
|
|
4902
|
-
p.setY(i, p.getY(i) +
|
|
4908
|
+
p.setY(i, p.getY(i) + HOOD_LIFT);
|
|
4903
4909
|
}
|
|
4904
4910
|
geo.translate(-hx, -hy, 0);
|
|
4905
4911
|
const under = geo.clone();
|
|
@@ -4928,7 +4934,8 @@ var GameVehicles = (() => {
|
|
|
4928
4934
|
yMin: bb.min.y,
|
|
4929
4935
|
zw: bb.max.z,
|
|
4930
4936
|
label: o.label,
|
|
4931
|
-
noLatch: !!o.noLatch
|
|
4937
|
+
noLatch: !!o.noLatch,
|
|
4938
|
+
lift: HOOD_LIFT
|
|
4932
4939
|
};
|
|
4933
4940
|
c.part("hood", { key: "hood" }, piv, new V3((hx + bb.max.x) / 2, hy, 0), 0.9);
|
|
4934
4941
|
return c.hood;
|
|
@@ -6361,6 +6368,14 @@ var GameVehicles = (() => {
|
|
|
6361
6368
|
}
|
|
6362
6369
|
|
|
6363
6370
|
// src/fluids.ts
|
|
6371
|
+
var PUDDLE_COLORS = {
|
|
6372
|
+
coolant: 3123279,
|
|
6373
|
+
brake_fluid: 12093738,
|
|
6374
|
+
transmission_fluid: 10099746,
|
|
6375
|
+
power_steering_fluid: 9054768,
|
|
6376
|
+
washer_fluid: 2977744,
|
|
6377
|
+
battery_acid: 14211272
|
|
6378
|
+
};
|
|
6364
6379
|
function installFluids(kit) {
|
|
6365
6380
|
const THREE = kit.THREE;
|
|
6366
6381
|
const V3 = THREE.Vector3;
|
|
@@ -6580,6 +6595,13 @@ var GameVehicles = (() => {
|
|
|
6580
6595
|
transparent: true,
|
|
6581
6596
|
opacity: 0.9,
|
|
6582
6597
|
depthWrite: false
|
|
6598
|
+
}) : PUDDLE_COLORS[kind] ? new THREE.MeshStandardMaterial({
|
|
6599
|
+
color: PUDDLE_COLORS[kind],
|
|
6600
|
+
roughness: 0.05,
|
|
6601
|
+
metalness: 0.1,
|
|
6602
|
+
transparent: true,
|
|
6603
|
+
opacity: 0.7,
|
|
6604
|
+
depthWrite: false
|
|
6583
6605
|
}) : new THREE.MeshStandardMaterial({
|
|
6584
6606
|
color: 0,
|
|
6585
6607
|
roughness: 0.03,
|
|
@@ -7425,12 +7447,36 @@ var GameVehicles = (() => {
|
|
|
7425
7447
|
if (g.userData.crease && c.smoothLater) c.smoothLater(msh);
|
|
7426
7448
|
else if (g.userData.crease) crease(g, 42);
|
|
7427
7449
|
else g.computeVertexNormals();
|
|
7428
|
-
g.computeBoundingBox();
|
|
7429
7450
|
const sphere2 = g.boundingSphere ?? (g.boundingSphere = new THREE.Sphere());
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7451
|
+
if (pos.isInterleavedBufferAttribute || pos.normalized || Object.keys(g.morphAttributes).length > 0) {
|
|
7452
|
+
g.computeBoundingBox();
|
|
7453
|
+
g.boundingBox.getCenter(sphere2.center);
|
|
7454
|
+
let far2 = 0;
|
|
7455
|
+
for (let i = 0; i < pos.count; i++) far2 = Math.max(far2, sphere2.center.distanceToSquared(v.fromBufferAttribute(pos, i)));
|
|
7456
|
+
sphere2.radius = Math.sqrt(far2);
|
|
7457
|
+
} else {
|
|
7458
|
+
const box3 = g.boundingBox ?? (g.boundingBox = new THREE.Box3());
|
|
7459
|
+
let x0 = Infinity, y0 = Infinity, z0 = Infinity, x1 = -Infinity, y1 = -Infinity, z1 = -Infinity;
|
|
7460
|
+
for (let i = 0; i < pos.count * 3; i += 3) {
|
|
7461
|
+
const x = arr[i], y = arr[i + 1], z = arr[i + 2];
|
|
7462
|
+
if (x < x0) x0 = x;
|
|
7463
|
+
if (y < y0) y0 = y;
|
|
7464
|
+
if (z < z0) z0 = z;
|
|
7465
|
+
if (x > x1) x1 = x;
|
|
7466
|
+
if (y > y1) y1 = y;
|
|
7467
|
+
if (z > z1) z1 = z;
|
|
7468
|
+
}
|
|
7469
|
+
box3.min.set(x0, y0, z0);
|
|
7470
|
+
box3.max.set(x1, y1, z1);
|
|
7471
|
+
box3.getCenter(sphere2.center);
|
|
7472
|
+
const cx = sphere2.center.x, cy = sphere2.center.y, cz = sphere2.center.z;
|
|
7473
|
+
let far2 = 0;
|
|
7474
|
+
for (let i = 0; i < pos.count * 3; i += 3) {
|
|
7475
|
+
const dx = cx - arr[i], dy = cy - arr[i + 1], dz = cz - arr[i + 2];
|
|
7476
|
+
far2 = Math.max(far2, dx * dx + dy * dy + dz * dz);
|
|
7477
|
+
}
|
|
7478
|
+
sphere2.radius = Math.sqrt(far2);
|
|
7479
|
+
}
|
|
7434
7480
|
}
|
|
7435
7481
|
});
|
|
7436
7482
|
const dset = new Set(c.deform);
|
|
@@ -7834,7 +7880,8 @@ var GameVehicles = (() => {
|
|
|
7834
7880
|
const G = gm2.userData.glass;
|
|
7835
7881
|
if (!G || G.broken || !gm2.visible || !gm2.parent) return;
|
|
7836
7882
|
if (G.type === "laminated") {
|
|
7837
|
-
|
|
7883
|
+
for (let k2 = 0; k2 < 3; k2++) I.crackGlass(c, gm2, I.glassPt ? I.glassPt(c, gm2, c.rand) : gm2.position.clone(), 1.2);
|
|
7884
|
+
return;
|
|
7838
7885
|
}
|
|
7839
7886
|
I.shatterGlass(c, gm2, 1.4, new V3(0, 1, 0));
|
|
7840
7887
|
});
|
|
@@ -7845,19 +7892,6 @@ var GameVehicles = (() => {
|
|
|
7845
7892
|
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)));
|
|
7846
7893
|
c.hood = null;
|
|
7847
7894
|
}
|
|
7848
|
-
const W = m.width || 1.6;
|
|
7849
|
-
const gh = m.glasshouse;
|
|
7850
|
-
if (m.kind === "car" && gh) {
|
|
7851
|
-
;
|
|
7852
|
-
[1, -1].forEach(function(sd) {
|
|
7853
|
-
const dh = Math.max(0.35, (m.belt || 1) - (m.clearance || 0.3) - 0.08);
|
|
7854
|
-
const dx = gh.fb - 0.55;
|
|
7855
|
-
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));
|
|
7856
|
-
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));
|
|
7857
|
-
hole.userData.noFollow = true;
|
|
7858
|
-
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)));
|
|
7859
|
-
});
|
|
7860
|
-
}
|
|
7861
7895
|
if (m.rearBumper && m.rearBumper.parent)
|
|
7862
7896
|
fling(
|
|
7863
7897
|
c,
|
|
@@ -7865,20 +7899,6 @@ var GameVehicles = (() => {
|
|
|
7865
7899
|
c.dirToEffects(new V3(rv(-5, -3), rv(4, 6), rv(-1, 1))),
|
|
7866
7900
|
new V3(rv(-3, 3), rv(-6, 6), rv(-3, 3))
|
|
7867
7901
|
);
|
|
7868
|
-
if (c.wheels.length) {
|
|
7869
|
-
const wi = Math.floor(c.rand() * c.wheels.length);
|
|
7870
|
-
const wh = c.wheels[wi];
|
|
7871
|
-
c.wheels.splice(wi, 1);
|
|
7872
|
-
c.parts.forEach(function(p) {
|
|
7873
|
-
if (p.obj === wh) p.done = true;
|
|
7874
|
-
});
|
|
7875
|
-
fling(
|
|
7876
|
-
c,
|
|
7877
|
-
wh,
|
|
7878
|
-
c.dirToEffects(new V3(rv(-2, 2), rv(5, 7.5), (wh.position.z >= 0 ? 1 : -1) * rv(3, 5))),
|
|
7879
|
-
new V3(rv(-8, 8), rv(-3, 3), rv(-8, 8))
|
|
7880
|
-
);
|
|
7881
|
-
}
|
|
7882
7902
|
burnAll(c);
|
|
7883
7903
|
c.emitters = c.emitters.filter(function(e) {
|
|
7884
7904
|
return e.kind === "exhaust" ? false : true;
|
|
@@ -12673,6 +12693,17 @@ var GameVehicles = (() => {
|
|
|
12673
12693
|
return kit;
|
|
12674
12694
|
}
|
|
12675
12695
|
|
|
12696
|
+
// src/destructible/armour.ts
|
|
12697
|
+
var ARMOUR_LEVELS = {
|
|
12698
|
+
IIIA: { glassStops: 1700, glassHp: 6e3, glassTakes: 0.25, steel: 8 },
|
|
12699
|
+
III: { glassStops: 3600, glassHp: 9e3, glassTakes: 0.25, steel: 30 }
|
|
12700
|
+
};
|
|
12701
|
+
function armourLevelOf(meta, option) {
|
|
12702
|
+
if (option !== void 0) return option;
|
|
12703
|
+
if (meta.armor === "IIIA" || meta.armor === "III") return meta.armor;
|
|
12704
|
+
return meta.armored || meta.bullet ? "III" : null;
|
|
12705
|
+
}
|
|
12706
|
+
|
|
12676
12707
|
// src/destructible/model.ts
|
|
12677
12708
|
var JOINT_RULES = {
|
|
12678
12709
|
hinge: { strength: 5e3, tools: ["wrench", "screwdriver"], seconds: 3 },
|
|
@@ -12716,6 +12747,12 @@ var GameVehicles = (() => {
|
|
|
12716
12747
|
center: [(z.box.x0 + z.box.x1) / 2, (z.box.y0 + z.box.y1) / 2, (z.box.z0 + z.box.z1) / 2],
|
|
12717
12748
|
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)]
|
|
12718
12749
|
});
|
|
12750
|
+
var PART_FLUIDS = {
|
|
12751
|
+
gearbox: { fluid: "transmission_fluid", liters: 6 },
|
|
12752
|
+
brakes: { fluid: "brake_fluid", liters: 0.6 },
|
|
12753
|
+
steering: { fluid: "power_steering_fluid", liters: 1 },
|
|
12754
|
+
battery: { fluid: "battery_acid", liters: 3 }
|
|
12755
|
+
};
|
|
12719
12756
|
var skinOf = (kind) => kind === "plane" || kind === "heli" || kind === "boat" ? "aluminium" : "steel";
|
|
12720
12757
|
var isFender = (id) => /^fender/.test(id);
|
|
12721
12758
|
var roleOfZone = (z) => {
|
|
@@ -12773,7 +12810,8 @@ var GameVehicles = (() => {
|
|
|
12773
12810
|
const spec = c.spec;
|
|
12774
12811
|
const t = kit.t;
|
|
12775
12812
|
const skin = skinOf(kind);
|
|
12776
|
-
const
|
|
12813
|
+
const armoured = options.armour ?? null;
|
|
12814
|
+
const armour = armoured ? 12 : 1;
|
|
12777
12815
|
const mass = vehicleMass(c);
|
|
12778
12816
|
const W = m.width ?? 1.7;
|
|
12779
12817
|
const F = m.front ?? 2;
|
|
@@ -12839,8 +12877,8 @@ var GameVehicles = (() => {
|
|
|
12839
12877
|
}
|
|
12840
12878
|
if (role === "glass") {
|
|
12841
12879
|
const glass = (z.glass ?? [])[0];
|
|
12842
|
-
const laminated = glass?.userData?.glass?.type === "laminated" ||
|
|
12843
|
-
add({ id, role, name: label, material: "glass", box: box2, mass: 10 * area3, hp: laminated ? 4e3 : 450, zoneId: id, visual: false, tags: ["glass", ...tags] });
|
|
12880
|
+
const laminated = glass?.userData?.glass?.type === "laminated" || !!armoured;
|
|
12881
|
+
add({ id, role, name: label, material: "glass", box: box2, mass: 10 * area3, hp: armoured ? armoured.glassHp : laminated ? 4e3 : 450, zoneId: id, visual: false, tags: ["glass", ...tags] });
|
|
12844
12882
|
continue;
|
|
12845
12883
|
}
|
|
12846
12884
|
if (role === "light") {
|
|
@@ -12878,7 +12916,7 @@ var GameVehicles = (() => {
|
|
|
12878
12916
|
zoneId: id,
|
|
12879
12917
|
visual: false,
|
|
12880
12918
|
contains: { fluid: diesel ? "diesel" : "fuel", liters },
|
|
12881
|
-
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320 },
|
|
12919
|
+
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320, effect: "car_fuel_tank" },
|
|
12882
12920
|
ignitionC: diesel ? 330 : 260,
|
|
12883
12921
|
tags: ["fuel_tank", ...tags]
|
|
12884
12922
|
});
|
|
@@ -12914,8 +12952,8 @@ var GameVehicles = (() => {
|
|
|
12914
12952
|
if (claimed.has(pane)) continue;
|
|
12915
12953
|
const G = pane.userData.glass;
|
|
12916
12954
|
const bb = paneBox(c, pane, inverse);
|
|
12917
|
-
const laminated = G?.type === "laminated" ||
|
|
12918
|
-
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"] });
|
|
12955
|
+
const laminated = G?.type === "laminated" || !!armoured;
|
|
12956
|
+
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"] });
|
|
12919
12957
|
}
|
|
12920
12958
|
if (!hubZone) {
|
|
12921
12959
|
const h = Math.max(0.15, belt - clearance);
|
|
@@ -12971,7 +13009,7 @@ var GameVehicles = (() => {
|
|
|
12971
13009
|
visual: false,
|
|
12972
13010
|
mechanical: true,
|
|
12973
13011
|
contains: { fluid: diesel ? "diesel" : "fuel", liters },
|
|
12974
|
-
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320 },
|
|
13012
|
+
explodes: diesel ? void 0 : { energy: 12e5 * (liters / 55), radius: 5 + liters / 30, atC: 320, effect: "car_fuel_tank" },
|
|
12975
13013
|
ignitionC: diesel ? 330 : 260,
|
|
12976
13014
|
tags: ["fuel_tank"]
|
|
12977
13015
|
});
|
|
@@ -13248,38 +13286,121 @@ var GameVehicles = (() => {
|
|
|
13248
13286
|
}
|
|
13249
13287
|
}
|
|
13250
13288
|
void has;
|
|
13289
|
+
for (const part of parts) {
|
|
13290
|
+
const fluid = PART_FLUIDS[part.role];
|
|
13291
|
+
if (fluid && !part.contains && !(kind === "bike" && part.role === "steering")) part.contains = { ...fluid };
|
|
13292
|
+
}
|
|
13251
13293
|
return { parts, joints, hub: hubId, hitch };
|
|
13252
13294
|
}
|
|
13253
13295
|
|
|
13296
|
+
// src/destructible/subdivide-near.ts
|
|
13297
|
+
var SUBDIVIDE_DEPTH = 12;
|
|
13298
|
+
function subdivideNear(THREE, params) {
|
|
13299
|
+
const { mesh: mesh2, maxLen, near } = params;
|
|
13300
|
+
const src = mesh2.geometry.index ? mesh2.geometry.toNonIndexed() : mesh2.geometry;
|
|
13301
|
+
const a = src.attributes.position.array;
|
|
13302
|
+
const toWorld = mesh2.matrixWorld;
|
|
13303
|
+
const out = [];
|
|
13304
|
+
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2, (p[2] + q[2]) / 2];
|
|
13305
|
+
const split = (A, B, C, depth) => {
|
|
13306
|
+
const ab = Math.hypot(A[0] - B[0], A[1] - B[1], A[2] - B[2]);
|
|
13307
|
+
const bc = Math.hypot(B[0] - C[0], B[1] - C[1], B[2] - C[2]);
|
|
13308
|
+
const ca = Math.hypot(C[0] - A[0], C[1] - A[1], C[2] - A[2]);
|
|
13309
|
+
const longest = Math.max(ab, bc, ca);
|
|
13310
|
+
if (longest <= maxLen || depth > SUBDIVIDE_DEPTH) {
|
|
13311
|
+
out.push(...A, ...B, ...C);
|
|
13312
|
+
return;
|
|
13313
|
+
}
|
|
13314
|
+
if (longest === ab) {
|
|
13315
|
+
const D = mid(A, B);
|
|
13316
|
+
split(A, D, C, depth + 1);
|
|
13317
|
+
split(D, B, C, depth + 1);
|
|
13318
|
+
} else if (longest === bc) {
|
|
13319
|
+
const E = mid(B, C);
|
|
13320
|
+
split(A, B, E, depth + 1);
|
|
13321
|
+
split(A, E, C, depth + 1);
|
|
13322
|
+
} else {
|
|
13323
|
+
const F = mid(C, A);
|
|
13324
|
+
split(A, B, F, depth + 1);
|
|
13325
|
+
split(F, B, C, depth + 1);
|
|
13326
|
+
}
|
|
13327
|
+
};
|
|
13328
|
+
for (let i = 0; i < a.length; i += 9) {
|
|
13329
|
+
const A = [a[i], a[i + 1], a[i + 2]];
|
|
13330
|
+
const B = [a[i + 3], a[i + 4], a[i + 5]];
|
|
13331
|
+
const C = [a[i + 6], a[i + 7], a[i + 8]];
|
|
13332
|
+
const corners = [A, B, C].map((p) => new THREE.Vector3(p[0], p[1], p[2]).applyMatrix4(toWorld));
|
|
13333
|
+
if (near(corners)) split(A, B, C, 0);
|
|
13334
|
+
else out.push(...A, ...B, ...C);
|
|
13335
|
+
}
|
|
13336
|
+
const g = new THREE.BufferGeometry();
|
|
13337
|
+
g.setAttribute("position", new THREE.Float32BufferAttribute(out, 3));
|
|
13338
|
+
mesh2.geometry.dispose();
|
|
13339
|
+
mesh2.geometry = g;
|
|
13340
|
+
}
|
|
13341
|
+
|
|
13254
13342
|
// src/destructible/visual.ts
|
|
13255
13343
|
var HOLE_DECAL_SCALE = 2.4;
|
|
13256
13344
|
var DOOR_DEPTH = 0.07;
|
|
13257
|
-
var DOOR_EDGE_PIECES =
|
|
13345
|
+
var DOOR_EDGE_PIECES = 8;
|
|
13346
|
+
var DOOR_EDGE_REACH = 0.08;
|
|
13258
13347
|
var DOOR_CARD_COLOR = "#3b3e42";
|
|
13259
13348
|
var DOOR_LINER_COLOR = "#5a5e64";
|
|
13260
13349
|
var DOOR_LINER_DARK = 0.15;
|
|
13261
|
-
var DOOR_STRIP_THIN = 0.
|
|
13350
|
+
var DOOR_STRIP_THIN = 0.08;
|
|
13262
13351
|
var DOOR_STRIP_NEAR = 0.04;
|
|
13352
|
+
var DOOR_STRIP_TALL = 0.22;
|
|
13353
|
+
var STRIP_SMOOTH_DEG = 180;
|
|
13354
|
+
var DOOR_STRIP_VERTICES = 2e4;
|
|
13355
|
+
var DOOR_SILL = 0.25;
|
|
13263
13356
|
var DOOR_STRIP_OVER = 0.3;
|
|
13264
13357
|
var DOOR_TOP_OVER_PANE = 0.03;
|
|
13265
|
-
var DOOR_STRIP_SUB = 0.02;
|
|
13266
13358
|
var HOLE_TEXTURE_SIZE = 64;
|
|
13359
|
+
var LAMINATED_HP = 1e3;
|
|
13360
|
+
var HOOD_SINK = 2e-3;
|
|
13361
|
+
var HOOD_GAP_CLOSED = 0.016;
|
|
13267
13362
|
var DOOR_HANDLE_INSET = 0.12;
|
|
13268
13363
|
var DOOR_HANDLE_MIN = 0.07;
|
|
13269
13364
|
var DOOR_HANDLE_MAX = 0.24;
|
|
13270
13365
|
var DOOR_HANDLE_THIN = 0.07;
|
|
13271
|
-
var DOOR_HANDLE_OUTSIDE = 0.
|
|
13366
|
+
var DOOR_HANDLE_OUTSIDE = 0.15;
|
|
13272
13367
|
var PANE_SEAT_OUT = 0.05;
|
|
13273
13368
|
var PANE_SEAT_IN = -0.05;
|
|
13274
13369
|
var PANE_SEAT_REACH = 0.14;
|
|
13275
13370
|
var PANE_SEAT_SAMPLES = 24;
|
|
13276
13371
|
var PANE_SEAT_LIFT = 4e-3;
|
|
13277
13372
|
var PANE_FRAME_NEAR = 0.07;
|
|
13373
|
+
var PANE_MIN_SLOPE = 0.01;
|
|
13374
|
+
var PANE_EDGE_BAND = 0.03;
|
|
13375
|
+
var PANE_REACH_STEP = 0.01;
|
|
13376
|
+
var PANE_REACH_MAX = 0.25;
|
|
13377
|
+
var COWL_UNDER_GLASS = 0;
|
|
13378
|
+
var COWL_NEAR_PANE = 0.08;
|
|
13379
|
+
var COWL_PLATE = 0.06;
|
|
13380
|
+
var COWL_DASH = 0.5;
|
|
13381
|
+
var COWL_DEEP = 0.3;
|
|
13382
|
+
var WINDOW_FRAME_NEAR = 0.06;
|
|
13383
|
+
var WINDOW_FRAME_SPAN = 0.6;
|
|
13384
|
+
var DASHBOARD_COLOR = "#121314";
|
|
13385
|
+
var COWL_CUT_SUB = 0.1;
|
|
13386
|
+
var COWL_CUT_MARGIN = 0.08;
|
|
13387
|
+
var SKIN_UNDER_PANE = 0.025;
|
|
13388
|
+
var SKIN_EDGE_KEPT = 0.06;
|
|
13389
|
+
var SIDE_PANE_FACING = 0.6;
|
|
13390
|
+
var COWL_LONG = 0.8;
|
|
13391
|
+
var PANE_BAR_VERTICES = 48;
|
|
13392
|
+
var PANE_BAR_LENGTH = 0.3;
|
|
13393
|
+
var PANE_BORDER = 0.03;
|
|
13394
|
+
var PANE_BORDER_LIFT = 3e-3;
|
|
13278
13395
|
var DRAWN_MIRROR_SIZE = 0.3;
|
|
13279
13396
|
var DRAWN_MIRROR_REACH = 0.4;
|
|
13280
13397
|
var DRAWN_MIRROR_MOUNT = 0.2;
|
|
13281
13398
|
var MIRROR_GLASS_FIT = 0.76;
|
|
13399
|
+
var MIRROR_GLASS_PROUD = 1e-3;
|
|
13400
|
+
var MIRROR_FACE_DEPTH = 0.012;
|
|
13282
13401
|
var MIRROR_GLASS_THIN = 0.04;
|
|
13402
|
+
var DEFAULT_WHEEL_RADIUS = 0.3;
|
|
13403
|
+
var SIDEWALL_SHARE = 0.3;
|
|
13283
13404
|
var MIRROR_GLASS_MIN = 0.05;
|
|
13284
13405
|
var MIRROR_GLASS_COLOR = "#59636a";
|
|
13285
13406
|
var HOLE_RIM = { steel: "#b7bcc0", aluminium: "#d3d6d8", plastic: "#d9d6cc", rubber: "#141414", glass: "#eef3f5" };
|
|
@@ -13366,6 +13487,7 @@ var GameVehicles = (() => {
|
|
|
13366
13487
|
this.hood = this.nameHood();
|
|
13367
13488
|
this.seatPanes();
|
|
13368
13489
|
this.seatDoorHandles();
|
|
13490
|
+
this.seatHood();
|
|
13369
13491
|
this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
|
|
13370
13492
|
if (!this.warming.length) this.warming = null;
|
|
13371
13493
|
for (const p of model.parts) {
|
|
@@ -13566,7 +13688,8 @@ var GameVehicles = (() => {
|
|
|
13566
13688
|
const panes = I.windowsOver(this.c, z, inv);
|
|
13567
13689
|
const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
|
|
13568
13690
|
const mirrors = this.mirrorsOn(z);
|
|
13569
|
-
|
|
13691
|
+
const frames = this.framesAround(panes, inv);
|
|
13692
|
+
objs = [...pcs, ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors, ...frames];
|
|
13570
13693
|
}
|
|
13571
13694
|
} else {
|
|
13572
13695
|
const pcs = this.cutZone(z);
|
|
@@ -13611,7 +13734,10 @@ var GameVehicles = (() => {
|
|
|
13611
13734
|
run(x1, y0, x1, y1);
|
|
13612
13735
|
run(x1, y1, x0, y1);
|
|
13613
13736
|
run(x0, y1, x0, y0);
|
|
13614
|
-
const heights = loop.map(([x, y]) =>
|
|
13737
|
+
const heights = loop.map(([x, y]) => {
|
|
13738
|
+
const at = this.surface("z", side2, B, [[x, y]]);
|
|
13739
|
+
return at != null && Math.abs(at - flat) < DOOR_EDGE_REACH ? at : flat;
|
|
13740
|
+
});
|
|
13615
13741
|
const band = (lift) => {
|
|
13616
13742
|
const position = [];
|
|
13617
13743
|
const index = [];
|
|
@@ -13629,6 +13755,23 @@ var GameVehicles = (() => {
|
|
|
13629
13755
|
};
|
|
13630
13756
|
const paint = this.paint().clone();
|
|
13631
13757
|
paint.side = THREE.DoubleSide;
|
|
13758
|
+
const cardPosition = [];
|
|
13759
|
+
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);
|
|
13760
|
+
const cardZ = (z2) => z2 - side2 * DOOR_DEPTH;
|
|
13761
|
+
loop.forEach(([x, y], i) => {
|
|
13762
|
+
const j = (i + 1) % loop.length;
|
|
13763
|
+
cardPosition.push(mid[0], mid[1], cardZ(mid[2]), x, y, cardZ(heights[i]), loop[j][0], loop[j][1], cardZ(heights[j]));
|
|
13764
|
+
});
|
|
13765
|
+
const cardGeometry = new THREE.BufferGeometry();
|
|
13766
|
+
cardGeometry.setAttribute("position", new THREE.Float32BufferAttribute(cardPosition, 3));
|
|
13767
|
+
cardGeometry.computeVertexNormals();
|
|
13768
|
+
const cardMaterial = this.I.makeMaterial(DOOR_CARD_COLOR, { roughness: 0.85 }).clone();
|
|
13769
|
+
cardMaterial.side = THREE.DoubleSide;
|
|
13770
|
+
const card = new THREE.Mesh(cardGeometry, cardMaterial);
|
|
13771
|
+
card.userData.noFollow = true;
|
|
13772
|
+
card.castShadow = false;
|
|
13773
|
+
this.c.vehicle.add(card);
|
|
13774
|
+
piv.attach(card);
|
|
13632
13775
|
const edges = new THREE.Mesh(band(2e-3), paint);
|
|
13633
13776
|
const lining = new THREE.Mesh(band(2e-3), paint);
|
|
13634
13777
|
for (const m of [edges, lining]) {
|
|
@@ -13638,6 +13781,17 @@ var GameVehicles = (() => {
|
|
|
13638
13781
|
}
|
|
13639
13782
|
piv.attach(edges);
|
|
13640
13783
|
}
|
|
13784
|
+
/** 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. */
|
|
13785
|
+
framesAround(panes, inv) {
|
|
13786
|
+
const THREE = this.THREE;
|
|
13787
|
+
const boxes = panes.map((g) => this.I.localBox(this.c, g, inv).expandByScalar(WINDOW_FRAME_NEAR));
|
|
13788
|
+
return this.c.vehicle.children.filter((o) => {
|
|
13789
|
+
if (!o.isMesh || !o.visible || o.userData.glass || o.userData.decal || this.c.deform?.includes(o)) return false;
|
|
13790
|
+
const box2 = this.I.localBox(this.c, o, inv);
|
|
13791
|
+
if (box2.isEmpty()) return false;
|
|
13792
|
+
return boxes.some((b) => b.containsBox(box2) && box2.getSize(new THREE.Vector3()).x > b.getSize(new THREE.Vector3()).x * WINDOW_FRAME_SPAN);
|
|
13793
|
+
});
|
|
13794
|
+
}
|
|
13641
13795
|
/** A front door carries the mirror drawn at its corner. */
|
|
13642
13796
|
mirrorsOn(z) {
|
|
13643
13797
|
if (!z.mirror) return [];
|
|
@@ -13663,45 +13817,42 @@ var GameVehicles = (() => {
|
|
|
13663
13817
|
const B = z.box;
|
|
13664
13818
|
const x0 = B.x0 + 0.03;
|
|
13665
13819
|
const x1 = B.x1 - 0.03;
|
|
13666
|
-
const y0 = B.y0
|
|
13667
|
-
const
|
|
13820
|
+
const y0 = B.y0 + DOOR_SILL;
|
|
13821
|
+
const glassTop = (I) => {
|
|
13822
|
+
let top = -Infinity;
|
|
13823
|
+
for (const g of I.windowsOver(this.c, z, inv)) top = Math.max(top, I.localBox(this.c, g, inv).max.y);
|
|
13824
|
+
return top;
|
|
13825
|
+
};
|
|
13826
|
+
const y1 = Math.max(B.y1, glassTop(this.I)) + DOOR_STRIP_NEAR;
|
|
13668
13827
|
const deform = this.c.deform ?? [];
|
|
13669
13828
|
for (const o of [...this.c.vehicle.children]) {
|
|
13670
|
-
if (!o.isMesh || o === piv ||
|
|
13829
|
+
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;
|
|
13671
13830
|
const box2 = this.I.localBox(this.c, o, inv);
|
|
13672
13831
|
const size = box2.getSize(new THREE.Vector3());
|
|
13673
13832
|
const mid = box2.getCenter(new THREE.Vector3());
|
|
13674
|
-
if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y >
|
|
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;
|
|
13675
13834
|
const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
|
|
13676
13835
|
const v = new THREE.Vector3();
|
|
13677
13836
|
const sdf = (x, y, zz) => {
|
|
13678
13837
|
v.set(x, y, zz).applyMatrix4(toVehicle);
|
|
13679
13838
|
return Math.max(x0 - v.x, v.x - x1);
|
|
13680
13839
|
};
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
const side2 = z.side < 0 ? -1 : 1;
|
|
13698
|
-
const inward = -side2 * DOOR_DEPTH;
|
|
13699
|
-
card.position.z += inward;
|
|
13700
|
-
card.userData.noFollow = true;
|
|
13701
|
-
card.castShadow = false;
|
|
13702
|
-
this.c.vehicle.add(card);
|
|
13703
|
-
return card;
|
|
13704
|
-
});
|
|
13840
|
+
const over = new THREE.Mesh(o.geometry.clone(), o.material);
|
|
13841
|
+
over.position.copy(o.position);
|
|
13842
|
+
over.quaternion.copy(o.quaternion);
|
|
13843
|
+
over.scale.copy(o.scale);
|
|
13844
|
+
this.c.vehicle.add(over);
|
|
13845
|
+
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
|
+
over.userData.noFollow = true;
|
|
13854
|
+
piv.attach(over);
|
|
13855
|
+
}
|
|
13705
13856
|
}
|
|
13706
13857
|
mend(dirty) {
|
|
13707
13858
|
dirty.forEach((msh) => {
|
|
@@ -13726,14 +13877,58 @@ var GameVehicles = (() => {
|
|
|
13726
13877
|
ray.layers.enableAll();
|
|
13727
13878
|
for (const pane of c.glass) {
|
|
13728
13879
|
if (pane.userData.glass?.outline) continue;
|
|
13729
|
-
const
|
|
13880
|
+
const local = this.shellAround(pane, shell);
|
|
13881
|
+
if (!local) continue;
|
|
13882
|
+
const seat = this.paneSeat(pane, [local], ray);
|
|
13730
13883
|
if (!seat) continue;
|
|
13731
13884
|
const near = c.vehicle.children.filter((o) => o.isMesh && o !== pane && !o.userData.glass && !shell.includes(o) && this.onPane(o, pane, seat));
|
|
13732
13885
|
for (const m of [pane, ...near]) this.movePane(m, seat);
|
|
13733
|
-
const past = this.shellPast(pane,
|
|
13886
|
+
const past = this.shellPast(pane, [local], ray, seat.normal);
|
|
13734
13887
|
if (past > 0) for (const m of [pane, ...near]) this.movePane(m, { normal: seat.normal, samples: [{ at: seat.samples[0].at, by: past }] });
|
|
13888
|
+
this.reachShellBelow(pane, local, ray, seat.normal);
|
|
13889
|
+
this.paneBorder(pane, near, seat);
|
|
13890
|
+
this.keepUnderPane(pane, seat.normal);
|
|
13891
|
+
const across = new this.THREE.Vector3(0, 0, 1).transformDirection(this.c.vehicle.matrixWorld);
|
|
13892
|
+
if (Math.abs(seat.normal.dot(across)) < SIDE_PANE_FACING) {
|
|
13893
|
+
for (const body of shell) if (body.parent === this.c.vehicle) this.cutUnderPane(body, pane, seat.normal, SKIN_UNDER_PANE, SKIN_EDGE_KEPT);
|
|
13894
|
+
}
|
|
13735
13895
|
}
|
|
13736
13896
|
}
|
|
13897
|
+
/** 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. */
|
|
13898
|
+
shellAround(pane, shell) {
|
|
13899
|
+
const THREE = this.THREE;
|
|
13900
|
+
const box2 = new THREE.Box3().setFromObject(pane).expandByScalar(PANE_SEAT_REACH + PANE_SEAT_OUT);
|
|
13901
|
+
const out = [];
|
|
13902
|
+
const a = new THREE.Vector3();
|
|
13903
|
+
const b = new THREE.Vector3();
|
|
13904
|
+
const d = new THREE.Vector3();
|
|
13905
|
+
const tri = new THREE.Box3();
|
|
13906
|
+
for (const mesh2 of shell) {
|
|
13907
|
+
const pos = mesh2.geometry.attributes.position;
|
|
13908
|
+
const idx = mesh2.geometry.index;
|
|
13909
|
+
const count = idx ? idx.count : pos.count;
|
|
13910
|
+
const e = mesh2.matrixWorld.elements;
|
|
13911
|
+
const world = (v, i) => {
|
|
13912
|
+
const x = pos.getX(i);
|
|
13913
|
+
const y = pos.getY(i);
|
|
13914
|
+
const z = pos.getZ(i);
|
|
13915
|
+
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]);
|
|
13916
|
+
};
|
|
13917
|
+
for (let f = 0; f + 2 < count; f += 3) {
|
|
13918
|
+
world(a, idx ? idx.getX(f) : f);
|
|
13919
|
+
world(b, idx ? idx.getX(f + 1) : f + 1);
|
|
13920
|
+
world(d, idx ? idx.getX(f + 2) : f + 2);
|
|
13921
|
+
tri.makeEmpty().expandByPoint(a).expandByPoint(b).expandByPoint(d);
|
|
13922
|
+
if (tri.intersectsBox(box2)) out.push(a.x, a.y, a.z, b.x, b.y, b.z, d.x, d.y, d.z);
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
if (!out.length) return null;
|
|
13926
|
+
const g = new THREE.BufferGeometry();
|
|
13927
|
+
g.setAttribute("position", new THREE.Float32BufferAttribute(out, 3));
|
|
13928
|
+
const proxy = new THREE.Mesh(g, new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }));
|
|
13929
|
+
proxy.updateMatrixWorld(true);
|
|
13930
|
+
return proxy;
|
|
13931
|
+
}
|
|
13737
13932
|
/** The outward normal of a pane and how far the shell is at each sampled point (world). */
|
|
13738
13933
|
paneSeat(pane, shell, ray) {
|
|
13739
13934
|
const THREE = this.THREE;
|
|
@@ -13780,6 +13975,193 @@ var GameVehicles = (() => {
|
|
|
13780
13975
|
}
|
|
13781
13976
|
return samples.length >= 3 ? { normal, samples } : null;
|
|
13782
13977
|
}
|
|
13978
|
+
/** 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. */
|
|
13979
|
+
reachShellBelow(pane, shell, ray, normal) {
|
|
13980
|
+
const THREE = this.THREE;
|
|
13981
|
+
const down = new THREE.Vector3(0, -1, 0).applyQuaternion(this.c.root.getWorldQuaternion(new THREE.Quaternion()));
|
|
13982
|
+
down.addScaledVector(normal, -down.dot(normal));
|
|
13983
|
+
if (down.lengthSq() < PANE_MIN_SLOPE) return;
|
|
13984
|
+
down.normalize();
|
|
13985
|
+
const g = pane.geometry;
|
|
13986
|
+
const pos = g.attributes.position;
|
|
13987
|
+
const v = new THREE.Vector3();
|
|
13988
|
+
let lowest = -Infinity;
|
|
13989
|
+
for (let i = 0; i < pos.count; i++) lowest = Math.max(lowest, v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld).dot(down));
|
|
13990
|
+
const edge = [];
|
|
13991
|
+
const mid = new THREE.Vector3();
|
|
13992
|
+
for (let i = 0; i < pos.count; i++) {
|
|
13993
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
|
|
13994
|
+
if (v.dot(down) > lowest - PANE_EDGE_BAND) {
|
|
13995
|
+
edge.push(i);
|
|
13996
|
+
mid.add(v);
|
|
13997
|
+
}
|
|
13998
|
+
}
|
|
13999
|
+
if (!edge.length) return;
|
|
14000
|
+
mid.divideScalar(edge.length);
|
|
14001
|
+
let reach = 0;
|
|
14002
|
+
for (let d = PANE_REACH_STEP; d <= PANE_REACH_MAX; d += PANE_REACH_STEP) {
|
|
14003
|
+
const at = mid.clone().addScaledVector(down, d).addScaledVector(normal, PANE_SEAT_REACH);
|
|
14004
|
+
ray.set(at, normal.clone().negate());
|
|
14005
|
+
ray.far = PANE_SEAT_REACH * 2;
|
|
14006
|
+
if (ray.intersectObject(shell, false)[0]) {
|
|
14007
|
+
reach = d + PANE_REACH_STEP;
|
|
14008
|
+
break;
|
|
14009
|
+
}
|
|
14010
|
+
}
|
|
14011
|
+
if (!reach) return;
|
|
14012
|
+
const inverse = pane.matrixWorld.clone().invert();
|
|
14013
|
+
for (const i of edge) {
|
|
14014
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld).addScaledVector(down, reach).applyMatrix4(inverse);
|
|
14015
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
14016
|
+
}
|
|
14017
|
+
pos.needsUpdate = true;
|
|
14018
|
+
g.computeBoundingBox();
|
|
14019
|
+
g.computeBoundingSphere();
|
|
14020
|
+
}
|
|
14021
|
+
/** 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. */
|
|
14022
|
+
keepUnderPane(pane, normal) {
|
|
14023
|
+
const THREE = this.THREE;
|
|
14024
|
+
const paneBox2 = new THREE.Box3().setFromObject(pane).expandByScalar(COWL_NEAR_PANE);
|
|
14025
|
+
const pos = pane.geometry.attributes.position;
|
|
14026
|
+
const centre = new THREE.Vector3();
|
|
14027
|
+
const v = new THREE.Vector3();
|
|
14028
|
+
for (let i = 0; i < pos.count; i++) centre.add(v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld));
|
|
14029
|
+
centre.divideScalar(pos.count);
|
|
14030
|
+
const plane2 = centre.dot(normal);
|
|
14031
|
+
for (const o of this.c.vehicle.children) {
|
|
14032
|
+
if (!o.isMesh || o === pane || o.userData.glass || this.c.deform?.includes(o)) continue;
|
|
14033
|
+
const box2 = new THREE.Box3().setFromObject(o);
|
|
14034
|
+
const size = box2.getSize(new THREE.Vector3());
|
|
14035
|
+
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(box2)) continue;
|
|
14036
|
+
if (box2.getCenter(new THREE.Vector3()).dot(normal) - plane2 >= COWL_UNDER_GLASS) continue;
|
|
14037
|
+
if (size.y >= COWL_PLATE) {
|
|
14038
|
+
o.material = this.I.makeMaterial(DASHBOARD_COLOR, { roughness: 1, metalness: 0 });
|
|
14039
|
+
continue;
|
|
14040
|
+
}
|
|
14041
|
+
o.material = this.paint();
|
|
14042
|
+
this.cutUnderPane(o, pane, normal);
|
|
14043
|
+
}
|
|
14044
|
+
}
|
|
14045
|
+
/** Cuts away the part of a mesh that lies under a pane (behind its plane, inside its outline seen along its normal). */
|
|
14046
|
+
cutUnderPane(mesh2, pane, normal, behind = -Infinity, inset = 0) {
|
|
14047
|
+
const THREE = this.THREE;
|
|
14048
|
+
const outline = this.paneOutline(pane, normal);
|
|
14049
|
+
if (outline.length < 3) return;
|
|
14050
|
+
const u = new THREE.Vector3(0, 1, 0).cross(normal);
|
|
14051
|
+
if (u.lengthSq() < 1e-4) u.set(1, 0, 0);
|
|
14052
|
+
u.normalize();
|
|
14053
|
+
const w = normal.clone().cross(u);
|
|
14054
|
+
const cx = outline.reduce((sum, p) => sum + p.dot(u), 0) / outline.length;
|
|
14055
|
+
const cy = outline.reduce((sum, p) => sum + p.dot(w), 0) / outline.length;
|
|
14056
|
+
const poly = outline.map((p) => {
|
|
14057
|
+
const x = p.dot(u);
|
|
14058
|
+
const y = p.dot(w);
|
|
14059
|
+
const d = Math.hypot(x - cx, y - cy) || 1;
|
|
14060
|
+
const k = Math.max(0, d - inset) / d;
|
|
14061
|
+
return [cx + (x - cx) * k, cy + (y - cy) * k];
|
|
14062
|
+
});
|
|
14063
|
+
const plane2 = outline.reduce((sum, p) => sum + p.dot(normal), 0) / outline.length;
|
|
14064
|
+
const inside = (x, y) => {
|
|
14065
|
+
let hit = false;
|
|
14066
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
14067
|
+
const [xi, yi] = poly[i];
|
|
14068
|
+
const [xj, yj] = poly[j];
|
|
14069
|
+
if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) hit = !hit;
|
|
14070
|
+
}
|
|
14071
|
+
return hit;
|
|
14072
|
+
};
|
|
14073
|
+
const v = new THREE.Vector3();
|
|
14074
|
+
const toWorld = mesh2.matrixWorld;
|
|
14075
|
+
mesh2.updateMatrixWorld(true);
|
|
14076
|
+
const xs = poly.map((p) => p[0]);
|
|
14077
|
+
const ys = poly.map((p) => p[1]);
|
|
14078
|
+
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];
|
|
14079
|
+
subdivideNear(THREE, { mesh: mesh2, maxLen: COWL_CUT_SUB, near: (corners) => {
|
|
14080
|
+
const us = corners.map((c) => c.dot(u));
|
|
14081
|
+
const ws = corners.map((c) => c.dot(w));
|
|
14082
|
+
return Math.max(...us) >= x0 && Math.min(...us) <= x1 && Math.max(...ws) >= y0 && Math.min(...ws) <= y1;
|
|
14083
|
+
} });
|
|
14084
|
+
this.kit.cutMesh(
|
|
14085
|
+
mesh2,
|
|
14086
|
+
[
|
|
14087
|
+
{
|
|
14088
|
+
sdf: (x, y, z) => {
|
|
14089
|
+
v.set(x, y, z).applyMatrix4(toWorld);
|
|
14090
|
+
return inside(v.dot(u), v.dot(w)) && plane2 - v.dot(normal) > behind ? -1 : 1;
|
|
14091
|
+
}
|
|
14092
|
+
}
|
|
14093
|
+
],
|
|
14094
|
+
42,
|
|
14095
|
+
{}
|
|
14096
|
+
);
|
|
14097
|
+
}
|
|
14098
|
+
/** 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. */
|
|
14099
|
+
paneBorder(pane, near, seat) {
|
|
14100
|
+
const THREE = this.THREE;
|
|
14101
|
+
const bars = near.filter((o) => {
|
|
14102
|
+
if (o.geometry.attributes.position.count > PANE_BAR_VERTICES) return false;
|
|
14103
|
+
const size = new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3());
|
|
14104
|
+
return Math.max(size.x, size.y, size.z) > PANE_BAR_LENGTH;
|
|
14105
|
+
});
|
|
14106
|
+
if (!bars.length) return;
|
|
14107
|
+
const outline = this.paneOutline(pane, seat.normal);
|
|
14108
|
+
if (outline.length < 3) return;
|
|
14109
|
+
const centre = new THREE.Vector3();
|
|
14110
|
+
for (const p of outline) centre.add(p);
|
|
14111
|
+
centre.divideScalar(outline.length);
|
|
14112
|
+
const position = [];
|
|
14113
|
+
const toVehicle = this.I.worldInverseOf(this.c);
|
|
14114
|
+
const put = (p) => {
|
|
14115
|
+
const q = p.clone().addScaledVector(seat.normal, PANE_BORDER_LIFT).applyMatrix4(toVehicle);
|
|
14116
|
+
position.push(q.x, q.y, q.z);
|
|
14117
|
+
};
|
|
14118
|
+
const inward = (p) => p.clone().add(centre.clone().sub(p).normalize().multiplyScalar(PANE_BORDER));
|
|
14119
|
+
outline.forEach((a, i) => {
|
|
14120
|
+
const b = outline[(i + 1) % outline.length];
|
|
14121
|
+
const c = inward(b);
|
|
14122
|
+
const d = inward(a);
|
|
14123
|
+
for (const p of [a, b, c, a, c, d]) put(p);
|
|
14124
|
+
});
|
|
14125
|
+
if (!position.length) return;
|
|
14126
|
+
for (const o of bars) o.visible = false;
|
|
14127
|
+
const band = new THREE.BufferGeometry();
|
|
14128
|
+
band.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
|
|
14129
|
+
band.computeVertexNormals();
|
|
14130
|
+
const material = this.I.MATERIALS.dark.clone();
|
|
14131
|
+
material.side = THREE.DoubleSide;
|
|
14132
|
+
const mesh2 = new THREE.Mesh(band, material);
|
|
14133
|
+
mesh2.userData.noFollow = true;
|
|
14134
|
+
mesh2.castShadow = false;
|
|
14135
|
+
mesh2.raycast = () => {
|
|
14136
|
+
};
|
|
14137
|
+
this.c.vehicle.add(mesh2);
|
|
14138
|
+
}
|
|
14139
|
+
/** The outline of a pane, the convex hull of its vertices seen along its normal, in world space. */
|
|
14140
|
+
paneOutline(pane, normal) {
|
|
14141
|
+
const THREE = this.THREE;
|
|
14142
|
+
const pos = pane.geometry.attributes.position;
|
|
14143
|
+
const u = new THREE.Vector3(0, 1, 0).cross(normal);
|
|
14144
|
+
if (u.lengthSq() < 1e-4) u.set(1, 0, 0);
|
|
14145
|
+
u.normalize();
|
|
14146
|
+
const w = normal.clone().cross(u);
|
|
14147
|
+
const points = [];
|
|
14148
|
+
for (let i = 0; i < pos.count; i++) {
|
|
14149
|
+
const p = new THREE.Vector3().fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
|
|
14150
|
+
points.push({ x: p.dot(u), y: p.dot(w), p });
|
|
14151
|
+
}
|
|
14152
|
+
points.sort((a, b) => a.x - b.x || a.y - b.y);
|
|
14153
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
14154
|
+
const half = (list2) => {
|
|
14155
|
+
const out = [];
|
|
14156
|
+
for (const q of list2) {
|
|
14157
|
+
while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], q) <= 0) out.pop();
|
|
14158
|
+
out.push(q);
|
|
14159
|
+
}
|
|
14160
|
+
out.pop();
|
|
14161
|
+
return out;
|
|
14162
|
+
};
|
|
14163
|
+
return [...half(points), ...half([...points].reverse())].map((q) => q.p);
|
|
14164
|
+
}
|
|
13783
14165
|
/** How far the shell still stands past a seated pane, over its triangles. */
|
|
13784
14166
|
shellPast(pane, shell, ray, normal) {
|
|
13785
14167
|
const THREE = this.THREE;
|
|
@@ -13832,11 +14214,25 @@ var GameVehicles = (() => {
|
|
|
13832
14214
|
g.computeBoundingSphere();
|
|
13833
14215
|
mesh2.geometry = g;
|
|
13834
14216
|
}
|
|
14217
|
+
/** 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. */
|
|
14218
|
+
seatHood() {
|
|
14219
|
+
const hood = this.c.hood;
|
|
14220
|
+
if (!hood?.lift) return;
|
|
14221
|
+
const THREE = this.THREE;
|
|
14222
|
+
const box2 = new THREE.Box3().setFromObject(hood.piv).getSize(new THREE.Vector3());
|
|
14223
|
+
for (const part of hood.piv.children) {
|
|
14224
|
+
part.position.y -= hood.lift + HOOD_SINK;
|
|
14225
|
+
part.scale.multiply(new THREE.Vector3(1 + HOOD_GAP_CLOSED / Math.max(0.3, box2.x), 1, 1 + HOOD_GAP_CLOSED / Math.max(0.3, box2.z)));
|
|
14226
|
+
}
|
|
14227
|
+
}
|
|
13835
14228
|
/** A handle is at the free edge of its door, the end away from the hinge. */
|
|
13836
14229
|
seatDoorHandles() {
|
|
13837
14230
|
const THREE = this.THREE;
|
|
13838
14231
|
const c = this.c;
|
|
13839
14232
|
const inv = this.I.worldInverseOf(c);
|
|
14233
|
+
const bodyBox = new THREE.Box3();
|
|
14234
|
+
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));
|
|
14235
|
+
const outerHalf = Math.max(Math.abs(bodyBox.min.z), Math.abs(bodyBox.max.z));
|
|
13840
14236
|
for (const p of this.model.parts) {
|
|
13841
14237
|
if (p.role !== "door") continue;
|
|
13842
14238
|
const z = this.zone(p);
|
|
@@ -13850,8 +14246,7 @@ var GameVehicles = (() => {
|
|
|
13850
14246
|
const at = box2.getCenter(new THREE.Vector3());
|
|
13851
14247
|
const isHandle = size.x > DOOR_HANDLE_MIN && size.x < DOOR_HANDLE_MAX && size.y < DOOR_HANDLE_THIN && size.z < DOOR_HANDLE_THIN;
|
|
13852
14248
|
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;
|
|
13853
|
-
|
|
13854
|
-
if (here != null && Math.abs(here - at.z) > DOOR_HANDLE_OUTSIDE) continue;
|
|
14249
|
+
if (Math.abs(at.z) < outerHalf - DOOR_HANDLE_OUTSIDE) continue;
|
|
13855
14250
|
const skin = this.surface("z", z.side < 0 ? -1 : 1, B, [[free, at.y]]);
|
|
13856
14251
|
o.position.x += free - at.x;
|
|
13857
14252
|
if (skin != null) o.position.z += skin + (z.side < 0 ? -1 : 1) * (size.z / 2) - at.z;
|
|
@@ -13860,7 +14255,7 @@ var GameVehicles = (() => {
|
|
|
13860
14255
|
c.root.updateMatrixWorld(true);
|
|
13861
14256
|
}
|
|
13862
14257
|
/** The glass of a drawn mirror sits inside its rounded housing, without corners standing past it. */
|
|
13863
|
-
seatMirrorGlass(glass) {
|
|
14258
|
+
seatMirrorGlass(glass, drawn) {
|
|
13864
14259
|
const g = glass.geometry.clone();
|
|
13865
14260
|
g.computeBoundingBox();
|
|
13866
14261
|
const mid = g.boundingBox.getCenter(new this.THREE.Vector3());
|
|
@@ -13868,6 +14263,37 @@ var GameVehicles = (() => {
|
|
|
13868
14263
|
g.scale(MIRROR_GLASS_FIT, MIRROR_GLASS_FIT, MIRROR_GLASS_FIT);
|
|
13869
14264
|
g.translate(mid.x, mid.y, mid.z);
|
|
13870
14265
|
glass.geometry = g;
|
|
14266
|
+
const THREE = this.THREE;
|
|
14267
|
+
const volumeOf = (o) => {
|
|
14268
|
+
const size = new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3());
|
|
14269
|
+
return size.x * size.y * size.z;
|
|
14270
|
+
};
|
|
14271
|
+
const housing = drawn.filter((o) => o !== glass && o.isMesh && o.geometry).sort((a, b) => volumeOf(b) - volumeOf(a))[0];
|
|
14272
|
+
if (housing) {
|
|
14273
|
+
this.c.root.updateMatrixWorld(true);
|
|
14274
|
+
const inv = this.I.worldInverseOf(this.c);
|
|
14275
|
+
glass.updateMatrixWorld(true);
|
|
14276
|
+
const gb = this.I.localBox(this.c, glass, inv);
|
|
14277
|
+
const hb = this.I.localBox(this.c, housing, inv);
|
|
14278
|
+
const side2 = hb.getCenter(new THREE.Vector3()).x > gb.getCenter(new THREE.Vector3()).x ? -1 : 1;
|
|
14279
|
+
const face = side2 < 0 ? hb.min.x + MIRROR_FACE_DEPTH : hb.max.x - MIRROR_FACE_DEPTH;
|
|
14280
|
+
const shape = housing.geometry.clone();
|
|
14281
|
+
const pos = shape.attributes.position;
|
|
14282
|
+
const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, housing.matrixWorld);
|
|
14283
|
+
const back = toVehicle.clone().invert();
|
|
14284
|
+
const v = new THREE.Vector3();
|
|
14285
|
+
for (let i = 0; i < pos.count; i++) {
|
|
14286
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(toVehicle);
|
|
14287
|
+
if ((v.x - face) * side2 <= 0) continue;
|
|
14288
|
+
v.x = face;
|
|
14289
|
+
v.applyMatrix4(back);
|
|
14290
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
14291
|
+
}
|
|
14292
|
+
shape.computeVertexNormals();
|
|
14293
|
+
housing.geometry = shape;
|
|
14294
|
+
const outer = side2 < 0 ? gb.min.x : gb.max.x;
|
|
14295
|
+
glass.position.x += face + side2 * MIRROR_GLASS_PROUD - outer;
|
|
14296
|
+
}
|
|
13871
14297
|
glass.material = this.I.makeMaterial(MIRROR_GLASS_COLOR, { metalness: 0.95, roughness: 0.12 });
|
|
13872
14298
|
}
|
|
13873
14299
|
/** The mirror the model already draws around where the part says, if it draws one. */
|
|
@@ -13892,7 +14318,11 @@ var GameVehicles = (() => {
|
|
|
13892
14318
|
for (const o of drawn) {
|
|
13893
14319
|
if (!o.isMesh || o.material !== this.I.MATERIALS.chrome) continue;
|
|
13894
14320
|
const size = new this.THREE.Box3().setFromObject(o).getSize(new this.THREE.Vector3());
|
|
13895
|
-
|
|
14321
|
+
const [thin, mid] = size.toArray().sort((a, b) => a - b);
|
|
14322
|
+
if (thin < MIRROR_GLASS_THIN && mid > MIRROR_GLASS_MIN) {
|
|
14323
|
+
this.seatMirrorGlass(o, drawn);
|
|
14324
|
+
o.userData.mirrorGlass = true;
|
|
14325
|
+
}
|
|
13896
14326
|
}
|
|
13897
14327
|
this.extra.set(p.id, drawn);
|
|
13898
14328
|
return;
|
|
@@ -13904,14 +14334,26 @@ var GameVehicles = (() => {
|
|
|
13904
14334
|
g.name = `obj_${p.id}`;
|
|
13905
14335
|
g.position.set(cx, cy, cz);
|
|
13906
14336
|
this.c.vehicle.add(g);
|
|
13907
|
-
this.box(0.05, 0.
|
|
13908
|
-
const housing = this.box(0.
|
|
14337
|
+
this.box(0.05, 0.035, 0.05, mats.dark, 0, -0.04, -s * 0.06, g);
|
|
14338
|
+
const housing = this.box(0.08, 0.11, 0.125, mats.plastic ?? mats.dark, 0, 0, s * 0.0225, g);
|
|
13909
14339
|
housing.userData.mirrorHousing = true;
|
|
13910
|
-
const glass = this.box(0.095, 0.
|
|
14340
|
+
const glass = this.box(6e-3, 0.095, 0.115, mats.chrome ?? mats.dark, -0.043, 0, s * 0.0225, g);
|
|
13911
14341
|
glass.userData.mirrorGlass = true;
|
|
13912
14342
|
g.userData.rigid = true;
|
|
13913
14343
|
this.extra.set(p.id, [g]);
|
|
13914
14344
|
}
|
|
14345
|
+
/** 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. */
|
|
14346
|
+
shatterMirror(id) {
|
|
14347
|
+
let broke = false;
|
|
14348
|
+
for (const o of this.extra.get(id) ?? []) {
|
|
14349
|
+
o.traverse((m) => {
|
|
14350
|
+
if (!m.userData.mirrorGlass || !m.visible) return;
|
|
14351
|
+
m.visible = false;
|
|
14352
|
+
broke = true;
|
|
14353
|
+
});
|
|
14354
|
+
}
|
|
14355
|
+
return broke;
|
|
14356
|
+
}
|
|
13915
14357
|
makeBattery(p) {
|
|
13916
14358
|
const mats = this.I.MATERIALS;
|
|
13917
14359
|
const g = new this.THREE.Group();
|
|
@@ -13957,6 +14399,11 @@ var GameVehicles = (() => {
|
|
|
13957
14399
|
if (carrier) carrier.attach(m);
|
|
13958
14400
|
}
|
|
13959
14401
|
/** The door or lid a hinge group stands for. */
|
|
14402
|
+
/** 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. */
|
|
14403
|
+
extraIdOf(o) {
|
|
14404
|
+
for (const [id, objs] of this.extra) if (objs.includes(o)) return id;
|
|
14405
|
+
return void 0;
|
|
14406
|
+
}
|
|
13960
14407
|
plateIdOf(o) {
|
|
13961
14408
|
return o.userData.plateOf;
|
|
13962
14409
|
}
|
|
@@ -14013,6 +14460,7 @@ var GameVehicles = (() => {
|
|
|
14013
14460
|
best = g;
|
|
14014
14461
|
}
|
|
14015
14462
|
}
|
|
14463
|
+
if (p.hp > LAMINATED_HP && best.userData.glass) best.userData.glass.type = "laminated";
|
|
14016
14464
|
this.kit.hitGlass(this.c, best, pt, new V3(...dir), force);
|
|
14017
14465
|
return !!best.userData.glass?.broken;
|
|
14018
14466
|
}
|
|
@@ -14034,6 +14482,17 @@ var GameVehicles = (() => {
|
|
|
14034
14482
|
if (!z) return;
|
|
14035
14483
|
for (const w of z.wheels) if (w.parent) this.I.flattenTire(w, k);
|
|
14036
14484
|
}
|
|
14485
|
+
/** A tyre driven flat until it tore apart: the rubber is gone and the bare rim shows. */
|
|
14486
|
+
tyreTornOff(p) {
|
|
14487
|
+
const z = this.zone(p);
|
|
14488
|
+
if (!z) return;
|
|
14489
|
+
for (const w of z.wheels) {
|
|
14490
|
+
if (!w.parent || w.userData.tornOff) continue;
|
|
14491
|
+
w.userData.tornOff = true;
|
|
14492
|
+
for (const m of this.I.tireMeshes(w)) m.visible = false;
|
|
14493
|
+
w.position.y -= (w.userData.r ?? DEFAULT_WHEEL_RADIUS) * SIDEWALL_SHARE;
|
|
14494
|
+
}
|
|
14495
|
+
}
|
|
14037
14496
|
/** Burning rubber: the tyre sags and goes black as it melts. */
|
|
14038
14497
|
tyreMelt(p, melt) {
|
|
14039
14498
|
const z = this.zone(p);
|
|
@@ -14357,6 +14816,41 @@ var GameVehicles = (() => {
|
|
|
14357
14816
|
var SHIELDED = /* @__PURE__ */ new Set(["engine", "gearbox", "frame", "radiator", "fuel_tank", "oil_pan", "battery", "alternator", "brakes", "steering", "shocks"]);
|
|
14358
14817
|
var AMBIENT = 20;
|
|
14359
14818
|
var FLAME = 700;
|
|
14819
|
+
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 };
|
|
14820
|
+
var ARMOUR_STEEL_MM = { IIIA: 4, III: 7 };
|
|
14821
|
+
var SOFT_LEAD = 0.8;
|
|
14822
|
+
var TYRE_FLAT_HEALTH = 0.1;
|
|
14823
|
+
var TYRE_BURST_JOULES = 1500;
|
|
14824
|
+
var TYRE_BURST_CALIBER = 12;
|
|
14825
|
+
var TYRE_LEAK_PER_MM2 = 26e-5;
|
|
14826
|
+
var FLAT_TYRE_LIFE_M = 1500;
|
|
14827
|
+
var FLAT_TYRE_TOWN_SPEED = 8;
|
|
14828
|
+
var RUN_FLAT_LIFE_M = 8e4;
|
|
14829
|
+
var RUN_FLAT_SPEED = 22;
|
|
14830
|
+
var TYRE_RUNFLAT_HEALTH = 0.6;
|
|
14831
|
+
var CRUMPLE_BASE_M = 0.04;
|
|
14832
|
+
var CRUMPLE_PER_FORCE_M = 0.15;
|
|
14833
|
+
var CRUMPLE_MIN_FORCE = 0.3;
|
|
14834
|
+
var CRUMPLE_MAX_FORCE = 1.4;
|
|
14835
|
+
var RIM_HEALTH = 0.05;
|
|
14836
|
+
var RIM_GRIND_PER_M = 4e-4;
|
|
14837
|
+
var TYRE_FLAT_SAG = 0.24;
|
|
14838
|
+
var TYRE_FLAT_AIR = 0.2;
|
|
14839
|
+
var BRAKE_FLUID_NEEDED = 0.6;
|
|
14840
|
+
var BRAKE_NO_FLUID = 0.15;
|
|
14841
|
+
var ENGINE_WORKING_C = 90;
|
|
14842
|
+
var ENGINE_SETTLE_S = 30;
|
|
14843
|
+
var ENGINE_HEATING = 0.5;
|
|
14844
|
+
var ENGINE_STEAM_C = 120;
|
|
14845
|
+
var ENGINE_SEIZE_C = 150;
|
|
14846
|
+
var COOLANT_NEEDED = 0.4;
|
|
14847
|
+
var OIL_NEEDED = 0.3;
|
|
14848
|
+
var OIL_WEAR = 4e-3;
|
|
14849
|
+
var ENGINE_SEIZE_HP = 0.2;
|
|
14850
|
+
var EXPANDED_HOLE = 1.6;
|
|
14851
|
+
var MIN_SPENT = 0.08;
|
|
14852
|
+
var WELDED = /* @__PURE__ */ new Set(["roof", "pillar", "frame", "panel"]);
|
|
14853
|
+
var CRUMPLES = /* @__PURE__ */ new Set(["door", "hood", "lid", "fender", "bumper"]);
|
|
14360
14854
|
var FRACTURE = { glass: "shatter", plastic: "snap", rubber: "tear", steel: "none", aluminium: "none" };
|
|
14361
14855
|
var add3 = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
14362
14856
|
var r3 = (v) => [Math.round(v[0] * 1e5) / 1e5, Math.round(v[1] * 1e5) / 1e5, Math.round(v[2] * 1e5) / 1e5];
|
|
@@ -14368,6 +14862,8 @@ var GameVehicles = (() => {
|
|
|
14368
14862
|
let c = vehicleCtx;
|
|
14369
14863
|
const id = options.id ?? c.key;
|
|
14370
14864
|
const headless = !!options.headless;
|
|
14865
|
+
const armourId = armourLevelOf(c.meta, options.armour);
|
|
14866
|
+
const armourLevel = armourId ? { ...ARMOUR_LEVELS[armourId], ...options.armourLevels?.[armourId] } : null;
|
|
14371
14867
|
const seedBase = strHash(`${c.key}|${id}|${options.seed ?? 0}`);
|
|
14372
14868
|
let rand = rng(seedBase);
|
|
14373
14869
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
@@ -14425,7 +14921,7 @@ var GameVehicles = (() => {
|
|
|
14425
14921
|
return d.ignitionC ? 1 : 0;
|
|
14426
14922
|
};
|
|
14427
14923
|
const setup = () => {
|
|
14428
|
-
model = buildVehicleModel(kit, c, { hitch: options.hitch });
|
|
14924
|
+
model = buildVehicleModel(kit, c, { hitch: options.hitch, armour: armourLevel });
|
|
14429
14925
|
ps.clear();
|
|
14430
14926
|
for (const d of model.parts) {
|
|
14431
14927
|
const kg = burnKg(d);
|
|
@@ -14445,6 +14941,8 @@ var GameVehicles = (() => {
|
|
|
14445
14941
|
lost: false,
|
|
14446
14942
|
status: "intact",
|
|
14447
14943
|
flat: false,
|
|
14944
|
+
air: 1,
|
|
14945
|
+
airLoss: 0,
|
|
14448
14946
|
fireLoss: 0
|
|
14449
14947
|
});
|
|
14450
14948
|
}
|
|
@@ -14545,7 +15043,8 @@ var GameVehicles = (() => {
|
|
|
14545
15043
|
vis.breakLight(d, true);
|
|
14546
15044
|
break;
|
|
14547
15045
|
case "tyre":
|
|
14548
|
-
vis.
|
|
15046
|
+
if (status === "broken") vis.tyreTornOff(d);
|
|
15047
|
+
else vis.tyreFlat(d, 0.34);
|
|
14549
15048
|
break;
|
|
14550
15049
|
case "mirror":
|
|
14551
15050
|
case "bumper":
|
|
@@ -14575,7 +15074,8 @@ var GameVehicles = (() => {
|
|
|
14575
15074
|
const damage = (p, amount, dtype, source, out, cause) => {
|
|
14576
15075
|
if (p.lost || !(amount > 0)) return;
|
|
14577
15076
|
const before = p.hp;
|
|
14578
|
-
|
|
15077
|
+
const crumples = dtype === "blunt" && CRUMPLES.has(p.def.role) && (p.def.material === "steel" || p.def.material === "aluminium");
|
|
15078
|
+
p.hp = Math.max(WELDED.has(p.def.role) || crumples ? 1 : 0, p.hp - amount);
|
|
14579
15079
|
out.push(ev({ type: "damage", part: p.def.id, amount: r2(before - p.hp), hp: r2(p.hp), damageType: dtype, ...source ? { source } : {} }));
|
|
14580
15080
|
wear(p, amount, out);
|
|
14581
15081
|
syncZone(p);
|
|
@@ -14618,11 +15118,22 @@ var GameVehicles = (() => {
|
|
|
14618
15118
|
const p = v.clone().applyMatrix4(M);
|
|
14619
15119
|
return [p.x, p.y, p.z];
|
|
14620
15120
|
};
|
|
14621
|
-
const armour = () =>
|
|
15121
|
+
const armour = () => armourLevel?.steel ?? 1;
|
|
15122
|
+
const steelMm = (d) => {
|
|
15123
|
+
const base = STEEL_MM[d.role] ?? STEEL_MM.panel;
|
|
15124
|
+
return d.material === "steel" || d.material === "aluminium" ? base * (armourLevel ? ARMOUR_STEEL_MM[armourId === "III" ? "III" : "IIIA"] / STEEL_MM.panel : 1) : base;
|
|
15125
|
+
};
|
|
15126
|
+
const goesThrough = (h, d, p, energy, res) => {
|
|
15127
|
+
const piercing = h.piercing ?? 1;
|
|
15128
|
+
const metal = d.material === "steel" || d.material === "aluminium";
|
|
15129
|
+
const mm = h.penetration?.steel;
|
|
15130
|
+
if (metal && mm != null) return mm >= steelMm(d) * (p.dents.length > 3 ? 0.9 : 1);
|
|
15131
|
+
return energy * (metal || d.material === "glass" ? piercing : 1) >= res;
|
|
15132
|
+
};
|
|
14622
15133
|
const resist = (d, p) => {
|
|
14623
15134
|
let base = RESIST[d.material] ?? 100;
|
|
14624
15135
|
if (d.material === "glass" && d.hp > 1e3) base = 120;
|
|
14625
|
-
if (d.material === "glass" &&
|
|
15136
|
+
if (d.material === "glass" && armourLevel) base = armourLevel.glassStops;
|
|
14626
15137
|
const k = ROLE_RESIST[d.role] ?? 1;
|
|
14627
15138
|
const arm = d.material === "steel" || d.material === "aluminium" ? armour() : 1;
|
|
14628
15139
|
return base * k * arm * (p.dents.length > 3 ? 0.9 : 1);
|
|
@@ -14645,7 +15156,7 @@ var GameVehicles = (() => {
|
|
|
14645
15156
|
if (vis) {
|
|
14646
15157
|
const pos = new V3(d.box.center[0], Math.max(0.2, (c.meta.clearance ?? 0.3) * 0.8), d.box.center[2]);
|
|
14647
15158
|
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 });
|
|
14648
|
-
|
|
15159
|
+
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);
|
|
14649
15160
|
}
|
|
14650
15161
|
}
|
|
14651
15162
|
const out3 = dirToWorld(new V3(dirLocal.x * 0.5, dirLocal.y * 0.5 - 0.6, dirLocal.z * 0.5));
|
|
@@ -14682,7 +15193,7 @@ var GameVehicles = (() => {
|
|
|
14682
15193
|
exploded = true;
|
|
14683
15194
|
const d = tank.def;
|
|
14684
15195
|
const spec = d.explodes ?? { energy: 8e5, radius: 5, atC: 320 };
|
|
14685
|
-
out.push(ev({ type: "explode", part: d.id, point: toWorld(d.box.center), energy: spec.energy, radius: spec.radius }));
|
|
15196
|
+
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 } : {} }));
|
|
14686
15197
|
tank.liters = 0;
|
|
14687
15198
|
leaks.delete(d.id);
|
|
14688
15199
|
if (vis) kit.explode(c, { delay: 0.6 });
|
|
@@ -14782,7 +15293,7 @@ var GameVehicles = (() => {
|
|
|
14782
15293
|
const dir = dirToLocal(h.direction ?? [0, 0, -1]).normalize();
|
|
14783
15294
|
const n = h.normal ? dirToLocal(h.normal).normalize() : normalOf(d, v);
|
|
14784
15295
|
const res = resist(d, p);
|
|
14785
|
-
const through = E
|
|
15296
|
+
const through = goesThrough(h, d, p, E, res);
|
|
14786
15297
|
const cosInc = Math.abs(dir.dot(n));
|
|
14787
15298
|
const metal = d.material === "steel" || d.material === "aluminium";
|
|
14788
15299
|
const worldPoint = toWorld(v);
|
|
@@ -14793,13 +15304,21 @@ var GameVehicles = (() => {
|
|
|
14793
15304
|
vis?.spark([v.x, v.y, v.z], [refl.x, refl.y, refl.z], 6);
|
|
14794
15305
|
return;
|
|
14795
15306
|
}
|
|
14796
|
-
const
|
|
15307
|
+
const opens = (h.piercing ?? 1) < SOFT_LEAD ? EXPANDED_HOLE : 1;
|
|
15308
|
+
const holeDiam = Math.max(6e-3, cal / 1e3 * (d.material === "glass" ? 1.4 : 1) * opens);
|
|
15309
|
+
const energyLeft = () => {
|
|
15310
|
+
const metal2 = d.material === "steel" || d.material === "aluminium";
|
|
15311
|
+
const mm = h.penetration?.steel;
|
|
15312
|
+
const spent = metal2 && mm ? clamp(steelMm(d) / mm, MIN_SPENT, 1) : clamp(res / Math.max(E, 1), MIN_SPENT, 1);
|
|
15313
|
+
return E * (1 - spent);
|
|
15314
|
+
};
|
|
14797
15315
|
const push = (hole) => {
|
|
14798
15316
|
if (!hole) return;
|
|
14799
15317
|
const hp = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), diameter: r2(holeDiam), through };
|
|
14800
15318
|
p.holes.push(hp);
|
|
14801
|
-
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])))))) } : {} }));
|
|
15319
|
+
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])))))) } : {} }));
|
|
14802
15320
|
vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam, d);
|
|
15321
|
+
if (d.role === "mirror") vis?.shatterMirror(d.id);
|
|
14803
15322
|
startLeak(p, v, cal, out, dir);
|
|
14804
15323
|
};
|
|
14805
15324
|
switch (d.material) {
|
|
@@ -14812,7 +15331,7 @@ var GameVehicles = (() => {
|
|
|
14812
15331
|
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) };
|
|
14813
15332
|
p.cracks.push(crack);
|
|
14814
15333
|
out.push(ev({ type: "crack", part: d.id, crack, worldPoint }));
|
|
14815
|
-
damage(p, E * 0.5, "ballistic", h.source, out, worldPoint);
|
|
15334
|
+
damage(p, E * (armourLevel && !through ? armourLevel.glassTakes : 0.5), "ballistic", h.source, out, worldPoint);
|
|
14816
15335
|
} else damage(p, laminated ? E * 0.5 : p.hp, "ballistic", h.source, out, worldPoint);
|
|
14817
15336
|
break;
|
|
14818
15337
|
}
|
|
@@ -14820,8 +15339,8 @@ var GameVehicles = (() => {
|
|
|
14820
15339
|
push(through);
|
|
14821
15340
|
damage(p, E * 0.4, "ballistic", h.source, out, worldPoint);
|
|
14822
15341
|
if (through && !p.lost && !p.flat) {
|
|
14823
|
-
p
|
|
14824
|
-
|
|
15342
|
+
if (E >= TYRE_BURST_JOULES || cal >= TYRE_BURST_CALIBER) deflate(p, 1);
|
|
15343
|
+
else p.airLoss += TYRE_LEAK_PER_MM2 * Math.PI * (cal / 2) ** 2;
|
|
14825
15344
|
}
|
|
14826
15345
|
break;
|
|
14827
15346
|
}
|
|
@@ -14937,7 +15456,7 @@ var GameVehicles = (() => {
|
|
|
14937
15456
|
const electric = c.spec.fuel === "electric";
|
|
14938
15457
|
const tank = tankPart();
|
|
14939
15458
|
const fuel = tank ? tank.liters ?? 0 : electric ? 1 : 1;
|
|
14940
|
-
const engineOk = sound("engine") && !exploded && fuel > 0.05;
|
|
15459
|
+
const engineOk = sound("engine") && !exploded && !seized && fuel > 0.05;
|
|
14941
15460
|
const powered = sound("battery") && sound("alternator");
|
|
14942
15461
|
const gearOk = sound("gearbox");
|
|
14943
15462
|
const brakes = [...ps.values()].find((p) => p.def.role === "brakes");
|
|
@@ -15097,9 +15616,12 @@ var GameVehicles = (() => {
|
|
|
15097
15616
|
const state = () => stateCache ?? (stateCache = buildState());
|
|
15098
15617
|
const isSimulating = () => {
|
|
15099
15618
|
if (leaks.size > 0) return true;
|
|
15100
|
-
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
|
|
15619
|
+
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT || p.airLoss > 0 && p.air > 0)) return true;
|
|
15101
15620
|
return false;
|
|
15102
15621
|
};
|
|
15622
|
+
let engineHeat = ENGINE_WORKING_C;
|
|
15623
|
+
let seized = false;
|
|
15624
|
+
let steaming = false;
|
|
15103
15625
|
const surfaceRays = new THREE.Raycaster();
|
|
15104
15626
|
surfaceRays.layers.enableAll();
|
|
15105
15627
|
surfaceRays.camera = new THREE.PerspectiveCamera();
|
|
@@ -15123,14 +15645,24 @@ var GameVehicles = (() => {
|
|
|
15123
15645
|
if (pane) return ps.get(pane) ?? null;
|
|
15124
15646
|
for (let q = o; q && q !== c.vehicle; q = q.parent) {
|
|
15125
15647
|
if (vis?.plateIdOf(q)) return ps.get(vis.plateIdOf(q)) ?? null;
|
|
15648
|
+
const piece = vis?.extraIdOf(q);
|
|
15649
|
+
if (piece) return ps.get(piece) ?? null;
|
|
15126
15650
|
}
|
|
15127
15651
|
return locate(v, 0.08, (p) => p.def.role === "glass");
|
|
15128
15652
|
};
|
|
15653
|
+
const surfaceMeshes = [];
|
|
15654
|
+
const collectSurfaces = (o) => {
|
|
15655
|
+
if (!o.visible || o.userData.decal || o.userData.ghost || o.userData.noSurfaceRay) return;
|
|
15656
|
+
if (o.isMesh) surfaceMeshes.push(o);
|
|
15657
|
+
for (const child of o.children) collectSurfaces(child);
|
|
15658
|
+
};
|
|
15129
15659
|
const surfaceRay = (ray) => {
|
|
15130
15660
|
sync();
|
|
15131
15661
|
surfaceRays.set(new V3(ray.origin[0], ray.origin[1], ray.origin[2]), new V3(ray.direction[0], ray.direction[1], ray.direction[2]));
|
|
15132
15662
|
surfaceRays.far = ray.maxDistance;
|
|
15133
|
-
|
|
15663
|
+
surfaceMeshes.length = 0;
|
|
15664
|
+
for (const child of c.vehicle.children) collectSurfaces(child);
|
|
15665
|
+
for (const h of surfaceRays.intersectObjects(surfaceMeshes, false)) {
|
|
15134
15666
|
if (!h.face || !isDrawnSurface(h.object)) continue;
|
|
15135
15667
|
const p = partOfSurface(h.object, c.vehicle.worldToLocal(h.point.clone()));
|
|
15136
15668
|
if (!p || p.lost || ray.ignored?.(p.def.id)) continue;
|
|
@@ -15164,7 +15696,29 @@ var GameVehicles = (() => {
|
|
|
15164
15696
|
applyEffects();
|
|
15165
15697
|
return out;
|
|
15166
15698
|
};
|
|
15699
|
+
const runFlat = armourId != null;
|
|
15700
|
+
const tyreHealth = (p) => {
|
|
15701
|
+
if (p.lost) return RIM_HEALTH;
|
|
15702
|
+
if (p.flat) return runFlat ? TYRE_RUNFLAT_HEALTH : TYRE_FLAT_HEALTH;
|
|
15703
|
+
return Math.min(p.hp / p.def.hp, 0.2 + 0.8 * p.air);
|
|
15704
|
+
};
|
|
15705
|
+
const tyreOf = (rim) => {
|
|
15706
|
+
let best;
|
|
15707
|
+
let bestD = Infinity;
|
|
15708
|
+
for (const p of ps.values()) {
|
|
15709
|
+
if (p.def.role !== "tyre") continue;
|
|
15710
|
+
const d = Math.hypot(p.def.box.center[0] - rim.def.box.center[0], p.def.box.center[2] - rim.def.box.center[2]);
|
|
15711
|
+
if (d < bestD) [best, bestD] = [p, d];
|
|
15712
|
+
}
|
|
15713
|
+
return best;
|
|
15714
|
+
};
|
|
15715
|
+
const deflate = (p, share) => {
|
|
15716
|
+
p.air = Math.max(0, p.air - share);
|
|
15717
|
+
vis?.tyreFlat(p.def, TYRE_FLAT_SAG * (1 - p.air));
|
|
15718
|
+
if (p.air <= TYRE_FLAT_AIR) p.flat = true;
|
|
15719
|
+
};
|
|
15167
15720
|
const update = (dt) => {
|
|
15721
|
+
for (const p of ps.values()) if (p.airLoss > 0 && p.air > 0 && !p.lost) deflate(p, p.airLoss * dt);
|
|
15168
15722
|
clock += dt;
|
|
15169
15723
|
const out = queue;
|
|
15170
15724
|
queue = [];
|
|
@@ -15280,6 +15834,70 @@ var GameVehicles = (() => {
|
|
|
15280
15834
|
partName: (pid) => ps.get(pid)?.def.name ?? pid,
|
|
15281
15835
|
status: computeStatus,
|
|
15282
15836
|
animations: () => createVehicleClips(model, vis, kit, c),
|
|
15837
|
+
crumple: (point, direction, depth) => {
|
|
15838
|
+
if (!vis || depth <= 0) return;
|
|
15839
|
+
sync();
|
|
15840
|
+
const force = clamp((depth - CRUMPLE_BASE_M) / CRUMPLE_PER_FORCE_M, CRUMPLE_MIN_FORCE, CRUMPLE_MAX_FORCE);
|
|
15841
|
+
kit.impact(c, toLocal(point), dirToLocal(direction).normalize(), force, { maxDepth: depth });
|
|
15842
|
+
stateCache = void 0;
|
|
15843
|
+
},
|
|
15844
|
+
rollFlat: (meters, speed) => {
|
|
15845
|
+
for (const p of ps.values()) {
|
|
15846
|
+
if (p.def.role !== "tyre" || !p.flat || p.lost) continue;
|
|
15847
|
+
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);
|
|
15848
|
+
p.hp = Math.max(0, p.hp - p.def.hp * meters / life);
|
|
15849
|
+
if (p.hp > 0) continue;
|
|
15850
|
+
const at = toWorld(p.def.box.center);
|
|
15851
|
+
lose(p, "broken", queue, at);
|
|
15852
|
+
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) }));
|
|
15853
|
+
}
|
|
15854
|
+
let grinding = 0;
|
|
15855
|
+
for (const rim of ps.values()) {
|
|
15856
|
+
if (rim.def.role !== "rim" || rim.lost || !tyreOf(rim)?.lost) continue;
|
|
15857
|
+
grinding += 1;
|
|
15858
|
+
rim.hp = Math.max(1, rim.hp - rim.def.hp * RIM_GRIND_PER_M * meters);
|
|
15859
|
+
const [x, y, z] = rim.def.box.center;
|
|
15860
|
+
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) }));
|
|
15861
|
+
}
|
|
15862
|
+
stateCache = void 0;
|
|
15863
|
+
return grinding;
|
|
15864
|
+
},
|
|
15865
|
+
runEngine: (seconds, load) => {
|
|
15866
|
+
const engine = [...ps.values()].find((p) => p.def.role === "engine");
|
|
15867
|
+
if (!engine || engine.lost || seized) return engineHeat;
|
|
15868
|
+
const share = (fluid) => {
|
|
15869
|
+
const list2 = [...ps.values()].filter((p) => p.def.contains?.fluid === fluid);
|
|
15870
|
+
return list2.length ? Math.min(...list2.map((p) => (p.liters ?? 0) / p.def.contains.liters)) : 1;
|
|
15871
|
+
};
|
|
15872
|
+
const coolant = share("coolant");
|
|
15873
|
+
const oil = share("oil");
|
|
15874
|
+
const work = 0.4 + 0.6 * clamp(load, 0, 1);
|
|
15875
|
+
if (coolant < COOLANT_NEEDED) engineHeat += (COOLANT_NEEDED - coolant) / COOLANT_NEEDED * ENGINE_HEATING * work * seconds;
|
|
15876
|
+
else engineHeat += (ENGINE_WORKING_C - engineHeat) * Math.min(1, seconds / ENGINE_SETTLE_S);
|
|
15877
|
+
if (oil < OIL_NEEDED) engine.hp = Math.max(0, engine.hp - engine.def.hp * OIL_WEAR * work * seconds * ((OIL_NEEDED - oil) / OIL_NEEDED));
|
|
15878
|
+
if (engineHeat >= ENGINE_STEAM_C && vis && !steaming) {
|
|
15879
|
+
steaming = true;
|
|
15880
|
+
kit.addEmitter(c, new V3(engine.def.box.center[0], engine.def.box.center[1] + 0.4, 0), "white", { rate: 8, s: 1 });
|
|
15881
|
+
}
|
|
15882
|
+
if (engineHeat >= ENGINE_SEIZE_C || engine.hp <= engine.def.hp * ENGINE_SEIZE_HP) {
|
|
15883
|
+
seized = true;
|
|
15884
|
+
queue.push(ev({ type: "damage", part: engine.def.id, amount: 0, hp: r2(engine.hp), damageType: "heat" }));
|
|
15885
|
+
}
|
|
15886
|
+
stateCache = void 0;
|
|
15887
|
+
return engineHeat;
|
|
15888
|
+
},
|
|
15889
|
+
driveReport: () => {
|
|
15890
|
+
const health = (role) => {
|
|
15891
|
+
const list2 = [...ps.values()].filter((p) => p.def.role === role);
|
|
15892
|
+
if (!list2.length) return 1;
|
|
15893
|
+
return Math.min(...list2.map((p) => p.lost || !attached(p.def.id) ? 0 : p.hp / p.def.hp));
|
|
15894
|
+
};
|
|
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) }));
|
|
15896
|
+
const status = computeStatus();
|
|
15897
|
+
const brakeSystem = [...ps.values()].find((p) => p.def.role === "brakes" && p.def.contains);
|
|
15898
|
+
const fluidLeft = brakeSystem ? clamp((brakeSystem.liters ?? 0) / brakeSystem.def.contains.liters / BRAKE_FLUID_NEEDED, 0, 1) : 1;
|
|
15899
|
+
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") };
|
|
15900
|
+
},
|
|
15283
15901
|
partWorldCenter: (pid) => {
|
|
15284
15902
|
sync();
|
|
15285
15903
|
const d = ps.get(pid)?.def;
|
|
@@ -15363,6 +15981,34 @@ var GameVehicles = (() => {
|
|
|
15363
15981
|
};
|
|
15364
15982
|
}
|
|
15365
15983
|
|
|
15984
|
+
// src/destructible/crash.ts
|
|
15985
|
+
var CRASH_RULES = { energyToDamage: 0.3, hitSpacing: 0.6, minSpeed: 1.5, rows: [0, 0.3], crushPerMs: 0.035, maxCrush: 0.6 };
|
|
15986
|
+
function crashDeltaV(speed, massKg, otherMassKg) {
|
|
15987
|
+
if (!Number.isFinite(otherMassKg)) return speed;
|
|
15988
|
+
return speed * otherMassKg / (massKg + otherMassKg);
|
|
15989
|
+
}
|
|
15990
|
+
function crashVehicle(d, crash) {
|
|
15991
|
+
const rules = { ...CRASH_RULES, ...crash.rules };
|
|
15992
|
+
const deltaV = crashDeltaV(crash.speed, crash.massKg, crash.otherMassKg);
|
|
15993
|
+
const energy = 0.5 * crash.massKg * deltaV * deltaV;
|
|
15994
|
+
if (crash.speed < rules.minSpeed) return { deltaV, energy, events: [] };
|
|
15995
|
+
const [dx, , dz] = crash.direction;
|
|
15996
|
+
const length = Math.hypot(dx, dz) || 1;
|
|
15997
|
+
const across = [-dz / length, 0, dx / length];
|
|
15998
|
+
const hits = Math.max(1, Math.round(crash.contactWidth / rules.hitSpacing));
|
|
15999
|
+
const each = energy * rules.energyToDamage / (hits * rules.rows.length);
|
|
16000
|
+
const events = [];
|
|
16001
|
+
for (const up of rules.rows)
|
|
16002
|
+
for (let i = 0; i < hits; i++) {
|
|
16003
|
+
const offset = hits === 1 ? 0 : (i / (hits - 1) - 0.5) * crash.contactWidth;
|
|
16004
|
+
const point = [crash.point[0] + across[0] * offset, crash.point[1] + up, crash.point[2] + across[2] * offset];
|
|
16005
|
+
events.push(...d.hit({ type: "blunt", point, direction: [dx / length, 0, dz / length], energy: each, radius: rules.hitSpacing / 2, ...crash.source ? { source: crash.source } : {} }));
|
|
16006
|
+
}
|
|
16007
|
+
const crush = Math.min(rules.maxCrush, deltaV * rules.crushPerMs);
|
|
16008
|
+
d.crumple(crash.point, [dx / length, 0, dz / length], crush);
|
|
16009
|
+
return { deltaV, energy, events };
|
|
16010
|
+
}
|
|
16011
|
+
|
|
15366
16012
|
// src/destructible/enclosure.ts
|
|
15367
16013
|
var CABIN_LEAK = 0.02;
|
|
15368
16014
|
var HOLE_RATE_PER_AREA = 3e3;
|