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