@adatechnology/game-vehicles 0.4.7 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +106 -3
- package/dist/index.global.js +1035 -105
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +917 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.global.js
CHANGED
|
@@ -23,13 +23,18 @@ var GameVehicles = (() => {
|
|
|
23
23
|
// src/index.ts
|
|
24
24
|
var index_exports = {};
|
|
25
25
|
__export(index_exports, {
|
|
26
|
+
CABIN_LEAK: () => CABIN_LEAK,
|
|
26
27
|
JOINT_RULES: () => JOINT_RULES,
|
|
27
28
|
LEGACY_STATES: () => LEGACY_STATES,
|
|
28
29
|
LEGACY_ZONE_IDS: () => LEGACY_ZONE_IDS,
|
|
29
30
|
SEED_SALTS: () => SEED_SALTS,
|
|
30
31
|
buildVehicleItem: () => buildVehicleItem,
|
|
31
32
|
buildVehicleModel: () => buildVehicleModel,
|
|
33
|
+
cabinBoxOf: () => cabinBoxOf,
|
|
34
|
+
cabinEnclosure: () => cabinEnclosure,
|
|
32
35
|
clamp: () => clamp,
|
|
36
|
+
crackedGlassMaterial: () => crackedGlassMaterial,
|
|
37
|
+
createCreaseQueue: () => createCreaseQueue,
|
|
33
38
|
createLod: () => createLod,
|
|
34
39
|
createTranslate: () => createTranslate,
|
|
35
40
|
createVehicleDestructible: () => createVehicleDestructible,
|
|
@@ -48,10 +53,11 @@ var GameVehicles = (() => {
|
|
|
48
53
|
rng: () => rng,
|
|
49
54
|
smooth: () => smooth,
|
|
50
55
|
strHash: () => strHash,
|
|
56
|
+
swingOf: () => swingOf,
|
|
51
57
|
wrapThreeForLod: () => wrapThreeForLod
|
|
52
58
|
});
|
|
53
59
|
|
|
54
|
-
// ../game-shapes/dist/chunk-
|
|
60
|
+
// ../game-shapes/dist/chunk-IFXQIDSA.js
|
|
55
61
|
function canonicalJson(value) {
|
|
56
62
|
if (value === null) return "null";
|
|
57
63
|
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
@@ -1439,6 +1445,127 @@ var GameVehicles = (() => {
|
|
|
1439
1445
|
custom: { fields: { generator: str, params: () => null }, required: ["generator"] }
|
|
1440
1446
|
};
|
|
1441
1447
|
|
|
1448
|
+
// ../game-contracts/dist/index.js
|
|
1449
|
+
var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
|
|
1450
|
+
var DEFAULT_IGNITION_RULES = {
|
|
1451
|
+
fillCurve: [
|
|
1452
|
+
[0, 0.6],
|
|
1453
|
+
[0.1, 1],
|
|
1454
|
+
[0.35, 0.6],
|
|
1455
|
+
[0.65, 0.3],
|
|
1456
|
+
[0.9, 0.15],
|
|
1457
|
+
[1, 0.12]
|
|
1458
|
+
],
|
|
1459
|
+
flameFillInfluence: 0.5,
|
|
1460
|
+
flameDefaults: { incendiary: 0.7, tracer: 0.2 },
|
|
1461
|
+
sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
|
|
1462
|
+
sparkMetalDefault: 0.5,
|
|
1463
|
+
sparkFluid: { diesel: 0.15 },
|
|
1464
|
+
fireNear: { radius: 1.5, chancePerSecond: 0.5 }
|
|
1465
|
+
};
|
|
1466
|
+
var resolveIgnitionRules = (rules) => ({
|
|
1467
|
+
...DEFAULT_IGNITION_RULES,
|
|
1468
|
+
...rules,
|
|
1469
|
+
flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
|
|
1470
|
+
sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
|
|
1471
|
+
sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
|
|
1472
|
+
fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
|
|
1473
|
+
});
|
|
1474
|
+
var fillFactor = (fill, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
|
|
1475
|
+
if (curve.length === 0) return 1;
|
|
1476
|
+
const f = Math.min(1, Math.max(0, fill));
|
|
1477
|
+
if (f <= curve[0][0]) return curve[0][1];
|
|
1478
|
+
for (let i = 1; i < curve.length; i++) {
|
|
1479
|
+
const [x1, y1] = curve[i];
|
|
1480
|
+
const [x0, y0] = curve[i - 1];
|
|
1481
|
+
if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
|
|
1482
|
+
}
|
|
1483
|
+
return curve[curve.length - 1][1];
|
|
1484
|
+
};
|
|
1485
|
+
var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
|
|
1486
|
+
var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
|
|
1487
|
+
const k = fillFactor(target.fill, rules.fillCurve);
|
|
1488
|
+
const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
|
|
1489
|
+
const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
|
|
1490
|
+
const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
|
|
1491
|
+
return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
|
|
1492
|
+
};
|
|
1493
|
+
var DEFERRED_QUEUE_ID = {
|
|
1494
|
+
DESTRUCTION_EVENTS: "destruction.events",
|
|
1495
|
+
BREACH_PAINT: "floorplan.breach-paint",
|
|
1496
|
+
AUDIO_VOICES: "audio.voices",
|
|
1497
|
+
VEHICLE_CREASE: "vehicles.crease"
|
|
1498
|
+
};
|
|
1499
|
+
var DEFERRED_PRIORITY = {
|
|
1500
|
+
[DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
|
|
1501
|
+
[DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
|
|
1502
|
+
[DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
|
|
1503
|
+
[DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
|
|
1504
|
+
};
|
|
1505
|
+
var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
|
|
1506
|
+
var warnedKeys = () => {
|
|
1507
|
+
const holder = globalThis;
|
|
1508
|
+
return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
|
|
1509
|
+
};
|
|
1510
|
+
var isProduction = () => {
|
|
1511
|
+
try {
|
|
1512
|
+
return process.env.NODE_ENV === "production";
|
|
1513
|
+
} catch {
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
|
|
1518
|
+
var warnOnce = (key, message) => {
|
|
1519
|
+
if (isQuiet() || isProduction()) return;
|
|
1520
|
+
const seen = warnedKeys();
|
|
1521
|
+
if (seen.has(key)) return;
|
|
1522
|
+
seen.add(key);
|
|
1523
|
+
console.warn(`[game-kit] ${message}`);
|
|
1524
|
+
};
|
|
1525
|
+
var KIT_ORIGIN_KEY = "kitOrigin";
|
|
1526
|
+
function markKitOrigin(object2, origin) {
|
|
1527
|
+
const node = object2;
|
|
1528
|
+
node.userData = node.userData ?? {};
|
|
1529
|
+
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
1530
|
+
return object2;
|
|
1531
|
+
}
|
|
1532
|
+
var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
|
|
1533
|
+
var GAIT_IDS = ["walk", "run"];
|
|
1534
|
+
var LANDING_IDS = ["light", "heavy"];
|
|
1535
|
+
var BODY_SOUND_IDS = [
|
|
1536
|
+
"jump",
|
|
1537
|
+
"cloth_rustle",
|
|
1538
|
+
"body_fall",
|
|
1539
|
+
"fall_damage",
|
|
1540
|
+
"player_hurt",
|
|
1541
|
+
"player_breath_run",
|
|
1542
|
+
"player_death",
|
|
1543
|
+
"bite",
|
|
1544
|
+
"grab",
|
|
1545
|
+
"punch_swing",
|
|
1546
|
+
"punch_hit_body",
|
|
1547
|
+
"punch_hit_face",
|
|
1548
|
+
"punch_hit_object",
|
|
1549
|
+
"kick_swing",
|
|
1550
|
+
"kick_hit",
|
|
1551
|
+
"kick_hit_object",
|
|
1552
|
+
"shove_two_hands",
|
|
1553
|
+
"knife_swing",
|
|
1554
|
+
"knife_stab",
|
|
1555
|
+
"knife_slash",
|
|
1556
|
+
"knife_hit_object",
|
|
1557
|
+
"blunt_swing",
|
|
1558
|
+
"blunt_hit",
|
|
1559
|
+
"blunt_hit_object",
|
|
1560
|
+
"axe_swing",
|
|
1561
|
+
"axe_chop",
|
|
1562
|
+
"axe_hit_object"
|
|
1563
|
+
];
|
|
1564
|
+
var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
|
|
1565
|
+
var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
|
|
1566
|
+
var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
|
|
1567
|
+
var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
|
|
1568
|
+
|
|
1442
1569
|
// ../game-shapes-three/dist/index.js
|
|
1443
1570
|
var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
|
|
1444
1571
|
var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
|
|
@@ -2455,7 +2582,7 @@ var GameVehicles = (() => {
|
|
|
2455
2582
|
}
|
|
2456
2583
|
return ti.builtBlocks === total;
|
|
2457
2584
|
}
|
|
2458
|
-
function start(geo) {
|
|
2585
|
+
function start(geo, store = cache) {
|
|
2459
2586
|
const pos = geo.attributes.position;
|
|
2460
2587
|
const triangles = trianglesOf(geo);
|
|
2461
2588
|
const ti = {
|
|
@@ -2468,7 +2595,7 @@ var GameVehicles = (() => {
|
|
|
2468
2595
|
blocks: new Float64Array(blockCount(triangles) * 6),
|
|
2469
2596
|
builtBlocks: 0
|
|
2470
2597
|
};
|
|
2471
|
-
|
|
2598
|
+
store.set(geo, ti);
|
|
2472
2599
|
return ti;
|
|
2473
2600
|
}
|
|
2474
2601
|
function triIndexOf(geo) {
|
|
@@ -2520,6 +2647,106 @@ var GameVehicles = (() => {
|
|
|
2520
2647
|
}
|
|
2521
2648
|
return { box: box2, version: ti.positionVersion };
|
|
2522
2649
|
}
|
|
2650
|
+
var rayCache = /* @__PURE__ */ new WeakMap();
|
|
2651
|
+
function rayIndexOf(geo) {
|
|
2652
|
+
const shared = cache.get(geo);
|
|
2653
|
+
if (shared && isCurrent(geo, shared) && shared.builtBlocks === blockCount(shared.triangles)) return shared;
|
|
2654
|
+
let ti = rayCache.get(geo);
|
|
2655
|
+
if (!ti || !isCurrent(geo, ti)) ti = start(geo, rayCache);
|
|
2656
|
+
advance(geo, ti, Infinity);
|
|
2657
|
+
return ti;
|
|
2658
|
+
}
|
|
2659
|
+
var BLOCK_SLACK = 1e-6;
|
|
2660
|
+
var scratch = /* @__PURE__ */ new WeakMap();
|
|
2661
|
+
function scratchOf(THREE) {
|
|
2662
|
+
let s = scratch.get(THREE);
|
|
2663
|
+
if (!s) {
|
|
2664
|
+
s = {
|
|
2665
|
+
inverse: new THREE.Matrix4(),
|
|
2666
|
+
local: new THREE.Ray(),
|
|
2667
|
+
box: new THREE.Box3(),
|
|
2668
|
+
a: new THREE.Vector3(),
|
|
2669
|
+
b: new THREE.Vector3(),
|
|
2670
|
+
c: new THREE.Vector3(),
|
|
2671
|
+
point: new THREE.Vector3(),
|
|
2672
|
+
world: new THREE.Vector3(),
|
|
2673
|
+
sphere: new THREE.Sphere()
|
|
2674
|
+
};
|
|
2675
|
+
scratch.set(THREE, s);
|
|
2676
|
+
}
|
|
2677
|
+
return s;
|
|
2678
|
+
}
|
|
2679
|
+
function indexable(THREE, mesh2) {
|
|
2680
|
+
const geo = mesh2.geometry;
|
|
2681
|
+
if (mesh2.raycast !== THREE.Mesh.prototype.raycast) return false;
|
|
2682
|
+
if (!mesh2.isMesh || mesh2.isSkinnedMesh || mesh2.isInstancedMesh || mesh2.isBatchedMesh || !geo || !geo.isBufferGeometry) return false;
|
|
2683
|
+
if (!mesh2.material || Array.isArray(mesh2.material)) return false;
|
|
2684
|
+
if (geo.morphAttributes && geo.morphAttributes.position && geo.morphAttributes.position.length) return false;
|
|
2685
|
+
return supports(geo);
|
|
2686
|
+
}
|
|
2687
|
+
function raycastMesh(THREE, raycaster, mesh2, out) {
|
|
2688
|
+
const geo = mesh2.geometry;
|
|
2689
|
+
const s = scratchOf(THREE);
|
|
2690
|
+
if (geo.boundingSphere === null) geo.computeBoundingSphere();
|
|
2691
|
+
s.sphere.copy(geo.boundingSphere).applyMatrix4(mesh2.matrixWorld);
|
|
2692
|
+
if (!raycaster.ray.intersectsSphere(s.sphere)) return;
|
|
2693
|
+
const ti = rayIndexOf(geo);
|
|
2694
|
+
if (!ti) return;
|
|
2695
|
+
s.inverse.copy(mesh2.matrixWorld).invert();
|
|
2696
|
+
s.local.copy(raycaster.ray).applyMatrix4(s.inverse);
|
|
2697
|
+
const material = mesh2.material;
|
|
2698
|
+
const back = material.side === THREE.BackSide;
|
|
2699
|
+
const cull = material.side === THREE.FrontSide;
|
|
2700
|
+
const P = geo.attributes.position;
|
|
2701
|
+
const ix = geo.index;
|
|
2702
|
+
const count = ix ? ix.count : P.count;
|
|
2703
|
+
const first = Math.max(0, geo.drawRange.start);
|
|
2704
|
+
const last = Math.min(count, geo.drawRange.start + geo.drawRange.count);
|
|
2705
|
+
const t0 = Math.ceil(first / 3);
|
|
2706
|
+
const t1 = Math.floor(last / 3);
|
|
2707
|
+
const B = ti.boxes;
|
|
2708
|
+
const K = ti.blocks;
|
|
2709
|
+
for (let blk = Math.floor(t0 / TRI_BLOCK); blk * TRI_BLOCK < t1; blk++) {
|
|
2710
|
+
const o = blk * 6;
|
|
2711
|
+
s.box.min.set(K[o] - BLOCK_SLACK, K[o + 1] - BLOCK_SLACK, K[o + 2] - BLOCK_SLACK);
|
|
2712
|
+
s.box.max.set(K[o + 3] + BLOCK_SLACK, K[o + 4] + BLOCK_SLACK, K[o + 5] + BLOCK_SLACK);
|
|
2713
|
+
if (!s.local.intersectsBox(s.box)) continue;
|
|
2714
|
+
const end = Math.min(t1, (blk + 1) * TRI_BLOCK);
|
|
2715
|
+
for (let t = Math.max(t0, blk * TRI_BLOCK); t < end; t++) {
|
|
2716
|
+
const j = t * 6;
|
|
2717
|
+
s.box.min.set(B[j] - BLOCK_SLACK, B[j + 1] - BLOCK_SLACK, B[j + 2] - BLOCK_SLACK);
|
|
2718
|
+
s.box.max.set(B[j + 3] + BLOCK_SLACK, B[j + 4] + BLOCK_SLACK, B[j + 5] + BLOCK_SLACK);
|
|
2719
|
+
if (!s.local.intersectsBox(s.box)) continue;
|
|
2720
|
+
const a = ix ? ix.getX(t * 3) : t * 3;
|
|
2721
|
+
const b = ix ? ix.getX(t * 3 + 1) : t * 3 + 1;
|
|
2722
|
+
const c = ix ? ix.getX(t * 3 + 2) : t * 3 + 2;
|
|
2723
|
+
s.a.fromBufferAttribute(P, a);
|
|
2724
|
+
s.b.fromBufferAttribute(P, b);
|
|
2725
|
+
s.c.fromBufferAttribute(P, c);
|
|
2726
|
+
const hit = back ? s.local.intersectTriangle(s.c, s.b, s.a, true, s.point) : s.local.intersectTriangle(s.a, s.b, s.c, cull, s.point);
|
|
2727
|
+
if (hit === null) continue;
|
|
2728
|
+
s.world.copy(s.point).applyMatrix4(mesh2.matrixWorld);
|
|
2729
|
+
const distance = raycaster.ray.origin.distanceTo(s.world);
|
|
2730
|
+
if (distance < raycaster.near || distance > raycaster.far) continue;
|
|
2731
|
+
const normal = new THREE.Vector3();
|
|
2732
|
+
THREE.Triangle.getNormal(s.a, s.b, s.c, normal);
|
|
2733
|
+
out.push({ distance, point: s.world.clone(), object: mesh2, face: { a, b, c, normal, materialIndex: 0 }, faceIndex: Math.floor(t) });
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
function collect(THREE, raycaster, object2, recursive, out) {
|
|
2738
|
+
if (object2.layers.test(raycaster.layers)) {
|
|
2739
|
+
if (indexable(THREE, object2)) raycastMesh(THREE, raycaster, object2, out);
|
|
2740
|
+
else object2.raycast(raycaster, out);
|
|
2741
|
+
}
|
|
2742
|
+
if (recursive) for (const child of object2.children) collect(THREE, raycaster, child, true, out);
|
|
2743
|
+
}
|
|
2744
|
+
function raycastIndexed(THREE, raycaster, objects, recursive = false) {
|
|
2745
|
+
const out = [];
|
|
2746
|
+
for (const object2 of objects) collect(THREE, raycaster, object2, recursive, out);
|
|
2747
|
+
out.sort((x, y) => x.distance - y.distance);
|
|
2748
|
+
return out;
|
|
2749
|
+
}
|
|
2523
2750
|
|
|
2524
2751
|
// src/geometry.ts
|
|
2525
2752
|
function installGeometry(kit) {
|
|
@@ -3259,58 +3486,6 @@ var GameVehicles = (() => {
|
|
|
3259
3486
|
Object.assign(I, { roundedBoxGeometry, taper, subdivide, crease, creaseTask, refreshBounds, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject });
|
|
3260
3487
|
}
|
|
3261
3488
|
|
|
3262
|
-
// ../game-contracts/dist/index.js
|
|
3263
|
-
var DEFAULT_IGNITION_RULES = {
|
|
3264
|
-
fillCurve: [
|
|
3265
|
-
[0, 0.6],
|
|
3266
|
-
[0.1, 1],
|
|
3267
|
-
[0.35, 0.6],
|
|
3268
|
-
[0.65, 0.3],
|
|
3269
|
-
[0.9, 0.15],
|
|
3270
|
-
[1, 0.12]
|
|
3271
|
-
],
|
|
3272
|
-
flameFillInfluence: 0.5,
|
|
3273
|
-
flameDefaults: { incendiary: 0.7, tracer: 0.2 },
|
|
3274
|
-
sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
|
|
3275
|
-
sparkMetalDefault: 0.5,
|
|
3276
|
-
sparkFluid: { diesel: 0.15 },
|
|
3277
|
-
fireNear: { radius: 1.5, chancePerSecond: 0.5 }
|
|
3278
|
-
};
|
|
3279
|
-
var resolveIgnitionRules = (rules) => ({
|
|
3280
|
-
...DEFAULT_IGNITION_RULES,
|
|
3281
|
-
...rules,
|
|
3282
|
-
flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
|
|
3283
|
-
sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
|
|
3284
|
-
sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
|
|
3285
|
-
fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
|
|
3286
|
-
});
|
|
3287
|
-
var fillFactor = (fill, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
|
|
3288
|
-
if (curve.length === 0) return 1;
|
|
3289
|
-
const f = Math.min(1, Math.max(0, fill));
|
|
3290
|
-
if (f <= curve[0][0]) return curve[0][1];
|
|
3291
|
-
for (let i = 1; i < curve.length; i++) {
|
|
3292
|
-
const [x1, y1] = curve[i];
|
|
3293
|
-
const [x0, y0] = curve[i - 1];
|
|
3294
|
-
if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
|
|
3295
|
-
}
|
|
3296
|
-
return curve[curve.length - 1][1];
|
|
3297
|
-
};
|
|
3298
|
-
var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
|
|
3299
|
-
var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
|
|
3300
|
-
const k = fillFactor(target.fill, rules.fillCurve);
|
|
3301
|
-
const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
|
|
3302
|
-
const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
|
|
3303
|
-
const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
|
|
3304
|
-
return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
|
|
3305
|
-
};
|
|
3306
|
-
var KIT_ORIGIN_KEY = "kitOrigin";
|
|
3307
|
-
function markKitOrigin(object2, origin) {
|
|
3308
|
-
const node = object2;
|
|
3309
|
-
node.userData = node.userData ?? {};
|
|
3310
|
-
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
3311
|
-
return object2;
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
3489
|
// src/context.ts
|
|
3315
3490
|
function installContext(kit) {
|
|
3316
3491
|
const THREE = kit.THREE;
|
|
@@ -3904,6 +4079,7 @@ var GameVehicles = (() => {
|
|
|
3904
4079
|
}
|
|
3905
4080
|
kit.profX = profX;
|
|
3906
4081
|
const _sr = new THREE.Raycaster();
|
|
4082
|
+
_sr.layers.enableAll();
|
|
3907
4083
|
kit.surfaceAt = function(c, org, dir, o) {
|
|
3908
4084
|
o = o || {};
|
|
3909
4085
|
c.root.updateMatrixWorld(true);
|
|
@@ -3920,7 +4096,7 @@ var GameVehicles = (() => {
|
|
|
3920
4096
|
_sr.set(p0.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
|
|
3921
4097
|
_sr.near = 0;
|
|
3922
4098
|
_sr.far = o.max || 20;
|
|
3923
|
-
const h =
|
|
4099
|
+
const h = raycastIndexed(THREE, _sr, cands)[0];
|
|
3924
4100
|
if (!h) return null;
|
|
3925
4101
|
const n = h.face ? h.face.normal.clone().transformDirection(h.object.matrixWorld).transformDirection(inv) : d.clone().negate();
|
|
3926
4102
|
if (n.dot(d) > 0) n.negate();
|
|
@@ -3942,6 +4118,7 @@ var GameVehicles = (() => {
|
|
|
3942
4118
|
const V3 = THREE.Vector3;
|
|
3943
4119
|
const I = kit._;
|
|
3944
4120
|
const { MATERIALS, crease, cutGeometry, pathShape, subdivide, tumble, uvProject } = I;
|
|
4121
|
+
I.paneSeals = /* @__PURE__ */ new WeakMap();
|
|
3945
4122
|
kit.addGlass = function(c, geo, label, type, o) {
|
|
3946
4123
|
o = o || {};
|
|
3947
4124
|
if (!geo.attributes.uv || o.reuv) o.aspect = uvProject(geo, o.uvAxes);
|
|
@@ -4008,7 +4185,7 @@ var GameVehicles = (() => {
|
|
|
4008
4185
|
cy /= poly.length;
|
|
4009
4186
|
const base = I.resolveLabel(label ? label(i, polys.length) : { key: "window" });
|
|
4010
4187
|
const lab = { key: base.key, label: kit.t("parts.glass_side", { label: base.label, s }) };
|
|
4011
|
-
kit.addGlass(c, geo, lab, o.type || "tempered", {
|
|
4188
|
+
const pane = kit.addGlass(c, geo, lab, o.type || "tempered", {
|
|
4012
4189
|
mat: o.mat || (o.see === false ? MATERIALS.glassDark : MATERIALS.glass),
|
|
4013
4190
|
pos: new V3(cx, cy, s * I.halfAt(Wg, T, cy)),
|
|
4014
4191
|
outline: shp.getSpacedPoints(56),
|
|
@@ -4024,6 +4201,7 @@ var GameVehicles = (() => {
|
|
|
4024
4201
|
pts.pop();
|
|
4025
4202
|
const seal = c.tube(pts, 0.011, MATERIALS.trim, { closed: true, seg: 72, rs: 5 });
|
|
4026
4203
|
seal.castShadow = false;
|
|
4204
|
+
I.paneSeals.set(pane, seal);
|
|
4027
4205
|
});
|
|
4028
4206
|
});
|
|
4029
4207
|
};
|
|
@@ -5733,6 +5911,44 @@ var GameVehicles = (() => {
|
|
|
5733
5911
|
Object.assign(I, {});
|
|
5734
5912
|
}
|
|
5735
5913
|
|
|
5914
|
+
// src/routed-particles.ts
|
|
5915
|
+
var ROUTED_PARTICLE_TYPES = { smoke: "particle_puff", dust: "particle_puff", fire: "particle_glow" };
|
|
5916
|
+
var PUFF_LOOKS = {
|
|
5917
|
+
smokewhite: [10133927, 0.42],
|
|
5918
|
+
smokeblack: [1776412, 0.62],
|
|
5919
|
+
smokegray: [5593436, 0.5],
|
|
5920
|
+
smokesteam: [11975873, 0.3],
|
|
5921
|
+
dust: [7235160, 0.34],
|
|
5922
|
+
fire: [16777215, 0.9]
|
|
5923
|
+
};
|
|
5924
|
+
var LIFE = { dust: 1.6, smoke: 2.8, fire: 0.7 };
|
|
5925
|
+
var FIRE_COLOR = 16753968;
|
|
5926
|
+
var SMOKE_START_SCALE = 0.18;
|
|
5927
|
+
function routedParticleRequest(input) {
|
|
5928
|
+
const { type } = input;
|
|
5929
|
+
const look = PUFF_LOOKS[type + (input.variant ?? "")] ?? PUFF_LOOKS[type] ?? PUFF_LOOKS.smokewhite;
|
|
5930
|
+
const life = input.life ?? LIFE[type] ?? 99;
|
|
5931
|
+
const size = (input.size ?? 1) * (input.grow ?? 1);
|
|
5932
|
+
const isFire = type === "fire";
|
|
5933
|
+
const growth = isFire ? 1 : (SMOKE_START_SCALE + 1.5 * (1 - Math.exp(-life * 1.1)) + life * 0.12) / SMOKE_START_SCALE;
|
|
5934
|
+
return {
|
|
5935
|
+
effect: ROUTED_PARTICLE_TYPES[type],
|
|
5936
|
+
point: input.position,
|
|
5937
|
+
values: {
|
|
5938
|
+
vx: input.velocity[0],
|
|
5939
|
+
vy: input.velocity[1],
|
|
5940
|
+
vz: input.velocity[2],
|
|
5941
|
+
life,
|
|
5942
|
+
size: isFire ? size * 0.85 : size * SMOKE_START_SCALE,
|
|
5943
|
+
grow: growth,
|
|
5944
|
+
color: input.color ?? (isFire ? FIRE_COLOR : look[0]),
|
|
5945
|
+
alpha: input.alpha ?? look[1],
|
|
5946
|
+
gravity: isFire ? 0 : type === "dust" ? 0 : -0.08,
|
|
5947
|
+
drag: type === "dust" ? 2.5 : isFire ? 1.5 : 0.9
|
|
5948
|
+
}
|
|
5949
|
+
};
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5736
5952
|
// src/particles.ts
|
|
5737
5953
|
function installParticles(kit) {
|
|
5738
5954
|
const THREE = kit.THREE;
|
|
@@ -5824,7 +6040,7 @@ var GameVehicles = (() => {
|
|
|
5824
6040
|
});
|
|
5825
6041
|
}
|
|
5826
6042
|
const list2 = fire ? FIRE_TEXTURES : SMOKE_TEXTURES;
|
|
5827
|
-
const
|
|
6043
|
+
const build = () => new THREE.SpriteMaterial({
|
|
5828
6044
|
map: list2[Math.floor(Math.random() * list2.length)],
|
|
5829
6045
|
color: new THREE.Color(col),
|
|
5830
6046
|
transparent: true,
|
|
@@ -5832,6 +6048,7 @@ var GameVehicles = (() => {
|
|
|
5832
6048
|
opacity: op,
|
|
5833
6049
|
blending: fire ? THREE.AdditiveBlending : THREE.NormalBlending
|
|
5834
6050
|
});
|
|
6051
|
+
const mat = kit.particleMaterials ? kit.particleMaterials.own(build) : build();
|
|
5835
6052
|
const dis = { value: 0 };
|
|
5836
6053
|
mat.userData.dis = dis;
|
|
5837
6054
|
mat.onBeforeCompile = function(sh) {
|
|
@@ -5859,11 +6076,37 @@ var GameVehicles = (() => {
|
|
|
5859
6076
|
function killParticle(c, i) {
|
|
5860
6077
|
const P = c.particles[i];
|
|
5861
6078
|
c.effects.remove(P.m);
|
|
5862
|
-
if (P.own)
|
|
6079
|
+
if (P.own) {
|
|
6080
|
+
if (kit.particleMaterials) kit.particleMaterials.release(P.m.material);
|
|
6081
|
+
else P.m.material.dispose();
|
|
6082
|
+
}
|
|
5863
6083
|
c.particles.splice(i, 1);
|
|
5864
6084
|
}
|
|
6085
|
+
function routeParticle(c, effects, type, pos, vel, o) {
|
|
6086
|
+
c.effects.updateWorldMatrix(true, false);
|
|
6087
|
+
const at = pos.clone().applyMatrix4(c.effects.matrixWorld);
|
|
6088
|
+
const to = vel.clone().transformDirection(c.effects.matrixWorld).multiplyScalar(vel.length());
|
|
6089
|
+
const colour = o.col == null ? void 0 : new THREE.Color(o.col).getHex();
|
|
6090
|
+
effects.play(
|
|
6091
|
+
routedParticleRequest({
|
|
6092
|
+
type,
|
|
6093
|
+
position: [at.x, at.y, at.z],
|
|
6094
|
+
velocity: [to.x, to.y, to.z],
|
|
6095
|
+
...o.variant ? { variant: o.variant } : {},
|
|
6096
|
+
...colour !== void 0 ? { color: colour } : {},
|
|
6097
|
+
...o.op != null ? { alpha: o.op } : {},
|
|
6098
|
+
...o.s ? { size: o.s } : {},
|
|
6099
|
+
...o.life ? { life: o.life } : {},
|
|
6100
|
+
...o.grow ? { grow: o.grow } : {}
|
|
6101
|
+
})
|
|
6102
|
+
);
|
|
6103
|
+
return null;
|
|
6104
|
+
}
|
|
5865
6105
|
kit.spawn = function(c, type, pos, vel, o) {
|
|
5866
6106
|
o = o || {};
|
|
6107
|
+
const effects = ROUTED_PARTICLE_TYPES[type] ? kit.effects?.() : void 0;
|
|
6108
|
+
if (effects) return routeParticle(c, effects, type, pos, vel, o);
|
|
6109
|
+
if (ROUTED_PARTICLE_TYPES[type] && !kit.effects) warnOnce("game-vehicles.own-particles", "game-vehicles drew its smoke, fire and dust with sprites of its own because the host lent no effects world (options.effects); pass the host effects so the puffs share one budget");
|
|
5867
6110
|
if (c.particles.length >= PARTICLE_LIMIT) {
|
|
5868
6111
|
const idx = c.particles.findIndex(function(q) {
|
|
5869
6112
|
return TRANSIENT_TYPES[q.type];
|
|
@@ -6135,6 +6378,7 @@ var GameVehicles = (() => {
|
|
|
6135
6378
|
};
|
|
6136
6379
|
kit.defineFluid("stain", { fresh: "#4a4a44", dry: "#26241f", wipeable: false });
|
|
6137
6380
|
const _fr = new THREE.Raycaster();
|
|
6381
|
+
_fr.layers.enableAll();
|
|
6138
6382
|
kit.splat = function(c, p, n, r, o) {
|
|
6139
6383
|
o = o || {};
|
|
6140
6384
|
n = n.clone().normalize();
|
|
@@ -6576,31 +6820,55 @@ var GameVehicles = (() => {
|
|
|
6576
6820
|
}
|
|
6577
6821
|
|
|
6578
6822
|
// src/glass-damage.ts
|
|
6823
|
+
function crackedGlassMaterial(THREE, texture) {
|
|
6824
|
+
return new THREE.MeshStandardMaterial({
|
|
6825
|
+
color: 16777215,
|
|
6826
|
+
map: texture,
|
|
6827
|
+
emissive: 6976118,
|
|
6828
|
+
emissiveMap: texture,
|
|
6829
|
+
transparent: true,
|
|
6830
|
+
roughness: 0.25,
|
|
6831
|
+
metalness: 0.1,
|
|
6832
|
+
side: THREE.DoubleSide,
|
|
6833
|
+
depthWrite: false
|
|
6834
|
+
});
|
|
6835
|
+
}
|
|
6579
6836
|
function installGlassDamage(kit) {
|
|
6580
6837
|
const THREE = kit.THREE;
|
|
6581
6838
|
const V3 = THREE.Vector3;
|
|
6582
6839
|
const PI = Math.PI;
|
|
6583
6840
|
const I = kit._;
|
|
6584
6841
|
const document2 = kit.document;
|
|
6585
|
-
const {
|
|
6586
|
-
function glassUV(msh, pLocal) {
|
|
6842
|
+
const { MATERIALS, crease, cutGeometry, mergeGeometries, subdivide, tumble } = I;
|
|
6843
|
+
function glassUV(c, msh, pLocal) {
|
|
6587
6844
|
const pos = msh.geometry.attributes.position;
|
|
6588
6845
|
const uv = msh.geometry.attributes.uv;
|
|
6589
6846
|
if (!uv) return [0.5, 0.5];
|
|
6590
|
-
|
|
6591
|
-
const q = pLocal.clone()
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
const
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6847
|
+
c.root.updateMatrixWorld(true);
|
|
6848
|
+
const q = msh.worldToLocal(c.vehicle.localToWorld(pLocal.clone()));
|
|
6849
|
+
const idx = msh.geometry.index;
|
|
6850
|
+
const tri = new THREE.Triangle();
|
|
6851
|
+
const near = new V3();
|
|
6852
|
+
const bary = new V3();
|
|
6853
|
+
let best = Infinity;
|
|
6854
|
+
const out = [0.5, 0.5];
|
|
6855
|
+
const count = idx ? idx.count : pos.count;
|
|
6856
|
+
for (let f = 0; f + 2 < count; f += 3) {
|
|
6857
|
+
const ia = idx ? idx.getX(f) : f;
|
|
6858
|
+
const ib = idx ? idx.getX(f + 1) : f + 1;
|
|
6859
|
+
const ic = idx ? idx.getX(f + 2) : f + 2;
|
|
6860
|
+
tri.a.fromBufferAttribute(pos, ia);
|
|
6861
|
+
tri.b.fromBufferAttribute(pos, ib);
|
|
6862
|
+
tri.c.fromBufferAttribute(pos, ic);
|
|
6863
|
+
tri.closestPointToPoint(q, near);
|
|
6864
|
+
const d = near.distanceToSquared(q);
|
|
6865
|
+
if (d >= best) continue;
|
|
6866
|
+
best = d;
|
|
6867
|
+
tri.getBarycoord(near, bary);
|
|
6868
|
+
out[0] = uv.getX(ia) * bary.x + uv.getX(ib) * bary.y + uv.getX(ic) * bary.z;
|
|
6869
|
+
out[1] = uv.getY(ia) * bary.x + uv.getY(ib) * bary.y + uv.getY(ic) * bary.z;
|
|
6602
6870
|
}
|
|
6603
|
-
return
|
|
6871
|
+
return out;
|
|
6604
6872
|
}
|
|
6605
6873
|
function drawWeb(x, cx, cy, s, ax, rnd) {
|
|
6606
6874
|
const n = 16 + Math.floor(rnd() * 8);
|
|
@@ -6677,19 +6945,9 @@ var GameVehicles = (() => {
|
|
|
6677
6945
|
x0.fillStyle = "rgba(48,66,72,0.5)";
|
|
6678
6946
|
x0.fillRect(0, 0, 256, 256);
|
|
6679
6947
|
G.tex = new THREE.CanvasTexture(G.cv);
|
|
6680
|
-
msh.material =
|
|
6681
|
-
color: 16777215,
|
|
6682
|
-
map: G.tex,
|
|
6683
|
-
emissive: 6976118,
|
|
6684
|
-
emissiveMap: G.tex,
|
|
6685
|
-
transparent: true,
|
|
6686
|
-
roughness: 0.25,
|
|
6687
|
-
metalness: 0.1,
|
|
6688
|
-
side: DOUBLE_SIDE,
|
|
6689
|
-
depthWrite: false
|
|
6690
|
-
});
|
|
6948
|
+
msh.material = crackedGlassMaterial(THREE, G.tex);
|
|
6691
6949
|
}
|
|
6692
|
-
const uv = glassUV(msh, p);
|
|
6950
|
+
const uv = glassUV(c, msh, p);
|
|
6693
6951
|
const x = G.cv.getContext("2d");
|
|
6694
6952
|
drawWeb(x, uv[0] * 256, (1 - uv[1]) * 256, sc != null ? sc : 0.6 + force * 0.5, Math.max(0.4, G.aspect || 1), c.rand);
|
|
6695
6953
|
G.tex.needsUpdate = true;
|
|
@@ -6834,6 +7092,13 @@ var GameVehicles = (() => {
|
|
|
6834
7092
|
}
|
|
6835
7093
|
const seatGeo = [];
|
|
6836
7094
|
const d = dir.clone().normalize();
|
|
7095
|
+
if (msh.parent && msh.parent !== c.vehicle) {
|
|
7096
|
+
c.root.updateMatrixWorld(true);
|
|
7097
|
+
const toVehicle = new THREE.Matrix4().copy(c.vehicle.matrixWorld).invert().multiply(msh.parent.matrixWorld);
|
|
7098
|
+
cubesAt.forEach(function(q) {
|
|
7099
|
+
q.applyMatrix4(toVehicle);
|
|
7100
|
+
});
|
|
7101
|
+
}
|
|
6837
7102
|
cubesAt.forEach(function(p, i) {
|
|
6838
7103
|
if (i % 3 === 0 && c.seatTops.length && !c.rollState && !c.flipped) {
|
|
6839
7104
|
let best = null;
|
|
@@ -7036,6 +7301,7 @@ var GameVehicles = (() => {
|
|
|
7036
7301
|
polygonOffsetUnits: -2
|
|
7037
7302
|
});
|
|
7038
7303
|
const _ray = new THREE.Raycaster();
|
|
7304
|
+
_ray.layers.enableAll();
|
|
7039
7305
|
function findSurface(c, p, dir) {
|
|
7040
7306
|
c.root.updateMatrixWorld(true);
|
|
7041
7307
|
const Mw = c.vehicle.matrixWorld;
|
|
@@ -7117,6 +7383,7 @@ var GameVehicles = (() => {
|
|
|
7117
7383
|
const v0 = new V3();
|
|
7118
7384
|
const w = new V3();
|
|
7119
7385
|
const sc = new V3();
|
|
7386
|
+
const local = new V3();
|
|
7120
7387
|
const cab = m.cabin && !c.flipped && !c.rollState ? m.cabin : null;
|
|
7121
7388
|
c.deform.forEach(function(msh) {
|
|
7122
7389
|
if (!msh.parent || !msh.visible) return;
|
|
@@ -7131,7 +7398,15 @@ var GameVehicles = (() => {
|
|
|
7131
7398
|
irel.copy(rel).invert();
|
|
7132
7399
|
if (!g.userData.p0) g.userData.p0 = new Float32Array(pos.array);
|
|
7133
7400
|
const p0 = g.userData.p0;
|
|
7401
|
+
local.copy(p).applyMatrix4(irel);
|
|
7402
|
+
const reach = H.R / Math.max(1e-6, Math.min(sc.x, sc.y, sc.z));
|
|
7403
|
+
const reach2 = reach * reach;
|
|
7404
|
+
const arr = pos.array;
|
|
7134
7405
|
for (let i = 0; i < pos.count; i++) {
|
|
7406
|
+
const dx = arr[3 * i] - local.x;
|
|
7407
|
+
const dy = arr[3 * i + 1] - local.y;
|
|
7408
|
+
const dz = arr[3 * i + 2] - local.z;
|
|
7409
|
+
if (dx * dx + dy * dy + dz * dz >= reach2) continue;
|
|
7135
7410
|
v.fromBufferAttribute(pos, i).applyMatrix4(rel);
|
|
7136
7411
|
const a = dentField(v, H);
|
|
7137
7412
|
if (a <= 0) continue;
|
|
@@ -7147,10 +7422,15 @@ var GameVehicles = (() => {
|
|
|
7147
7422
|
}
|
|
7148
7423
|
if (changed) {
|
|
7149
7424
|
pos.needsUpdate = true;
|
|
7150
|
-
if (g.userData.crease)
|
|
7425
|
+
if (g.userData.crease && c.smoothLater) c.smoothLater(msh);
|
|
7426
|
+
else if (g.userData.crease) crease(g, 42);
|
|
7151
7427
|
else g.computeVertexNormals();
|
|
7152
|
-
g.computeBoundingSphere();
|
|
7153
7428
|
g.computeBoundingBox();
|
|
7429
|
+
const sphere2 = g.boundingSphere ?? (g.boundingSphere = new THREE.Sphere());
|
|
7430
|
+
g.boundingBox.getCenter(sphere2.center);
|
|
7431
|
+
let far2 = 0;
|
|
7432
|
+
for (let i = 0; i < pos.count; i++) far2 = Math.max(far2, sphere2.center.distanceToSquared(v.fromBufferAttribute(pos, i)));
|
|
7433
|
+
sphere2.radius = Math.sqrt(far2);
|
|
7154
7434
|
}
|
|
7155
7435
|
});
|
|
7156
7436
|
const dset = new Set(c.deform);
|
|
@@ -7234,13 +7514,14 @@ var GameVehicles = (() => {
|
|
|
7234
7514
|
kit.impact = impact;
|
|
7235
7515
|
kit.partEffects = {};
|
|
7236
7516
|
const _pr = new THREE.Raycaster();
|
|
7517
|
+
_pr.layers.enableAll();
|
|
7237
7518
|
function probe(c, o, d, far) {
|
|
7238
7519
|
c.root.updateMatrixWorld(true);
|
|
7239
7520
|
const Mw = c.vehicle.matrixWorld;
|
|
7240
7521
|
const inv = new THREE.Matrix4().copy(Mw).invert();
|
|
7241
7522
|
_pr.set(o.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
|
|
7242
7523
|
_pr.far = far || 10;
|
|
7243
|
-
const hits = _pr
|
|
7524
|
+
const hits = raycastIndexed(THREE, _pr, [c.vehicle], true);
|
|
7244
7525
|
for (let i = 0; i < hits.length; i++) {
|
|
7245
7526
|
const h = hits[i];
|
|
7246
7527
|
const ob = h.object;
|
|
@@ -8606,6 +8887,7 @@ var GameVehicles = (() => {
|
|
|
8606
8887
|
return hole;
|
|
8607
8888
|
}
|
|
8608
8889
|
const _zr = new THREE.Raycaster();
|
|
8890
|
+
_zr.layers.enableAll();
|
|
8609
8891
|
const PROXY_MIN_TRIANGLES = 500;
|
|
8610
8892
|
const STROKE_REACH = 0.6;
|
|
8611
8893
|
const _pv = new V3();
|
|
@@ -8840,6 +9122,7 @@ var GameVehicles = (() => {
|
|
|
8840
9122
|
return out;
|
|
8841
9123
|
}
|
|
8842
9124
|
const _rt = new THREE.Raycaster();
|
|
9125
|
+
_rt.layers.enableAll();
|
|
8843
9126
|
function scratchPath(c, s, len, ang, cands, B) {
|
|
8844
9127
|
const n = s.n.clone().normalize();
|
|
8845
9128
|
const t = Math.abs(n.y) < 0.9 ? new V3().crossVectors(new V3(0, 1, 0), n) : new V3().crossVectors(new V3(1, 0, 0), n);
|
|
@@ -12121,6 +12404,8 @@ var GameVehicles = (() => {
|
|
|
12121
12404
|
V3: THREE.Vector3,
|
|
12122
12405
|
PI: Math.PI,
|
|
12123
12406
|
document: doc,
|
|
12407
|
+
particleMaterials: opts.particleMaterials,
|
|
12408
|
+
effects: opts.effects,
|
|
12124
12409
|
strings,
|
|
12125
12410
|
t,
|
|
12126
12411
|
matchers: createMatchers(strings),
|
|
@@ -12376,7 +12661,9 @@ var GameVehicles = (() => {
|
|
|
12376
12661
|
assignDeep(strings, resolveStrings(opts.locale));
|
|
12377
12662
|
return kit;
|
|
12378
12663
|
};
|
|
12379
|
-
const { THREE: _three, lod: _lod, locale: _locale, document: _document, plugins, ...config } = opts;
|
|
12664
|
+
const { THREE: _three, lod: _lod, locale: _locale, document: _document, particleMaterials: _particleMaterials, effects: _effects, plugins, ...config } = opts;
|
|
12665
|
+
void _particleMaterials;
|
|
12666
|
+
void _effects;
|
|
12380
12667
|
void _three;
|
|
12381
12668
|
void _lod;
|
|
12382
12669
|
void _locale;
|
|
@@ -12472,6 +12759,14 @@ var GameVehicles = (() => {
|
|
|
12472
12759
|
center: [b.center[0], b.center[1] - b.size[1] / 2 + Math.min(0.14, b.size[1]) / 2 + 0.04, b.center[2]],
|
|
12473
12760
|
size: [b.size[0] * 0.5, Math.min(0.14, b.size[1]), b.size[2] * 0.95]
|
|
12474
12761
|
});
|
|
12762
|
+
function paneBox(c, pane, inverse) {
|
|
12763
|
+
pane.geometry.computeBoundingBox();
|
|
12764
|
+
const box2 = pane.geometry.boundingBox.clone().applyMatrix4(pane.matrixWorld);
|
|
12765
|
+
box2.applyMatrix4(inverse);
|
|
12766
|
+
const center = [(box2.min.x + box2.max.x) / 2, (box2.min.y + box2.max.y) / 2, (box2.min.z + box2.max.z) / 2];
|
|
12767
|
+
const size = [box2.max.x - box2.min.x, box2.max.y - box2.min.y, box2.max.z - box2.min.z];
|
|
12768
|
+
return { center, size };
|
|
12769
|
+
}
|
|
12475
12770
|
function buildVehicleModel(kit, c, options = {}) {
|
|
12476
12771
|
const kind = kindOf(c);
|
|
12477
12772
|
const m = c.meta;
|
|
@@ -12612,6 +12907,16 @@ var GameVehicles = (() => {
|
|
|
12612
12907
|
tags: [role, ...tags]
|
|
12613
12908
|
});
|
|
12614
12909
|
}
|
|
12910
|
+
const claimed = new Set(zones.filter((z) => roleOfZone(z) === "glass").flatMap((z) => z.glass ?? []));
|
|
12911
|
+
c.root.updateMatrixWorld(true);
|
|
12912
|
+
const inverse = c.vehicle.matrixWorld.clone().invert();
|
|
12913
|
+
for (const pane of c.glass ?? []) {
|
|
12914
|
+
if (claimed.has(pane)) continue;
|
|
12915
|
+
const G = pane.userData.glass;
|
|
12916
|
+
const bb = paneBox(c, pane, inverse);
|
|
12917
|
+
const laminated = G?.type === "laminated" || m.bullet;
|
|
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"] });
|
|
12919
|
+
}
|
|
12615
12920
|
if (!hubZone) {
|
|
12616
12921
|
const h = Math.max(0.15, belt - clearance);
|
|
12617
12922
|
add({
|
|
@@ -12947,6 +13252,76 @@ var GameVehicles = (() => {
|
|
|
12947
13252
|
}
|
|
12948
13253
|
|
|
12949
13254
|
// src/destructible/visual.ts
|
|
13255
|
+
var HOLE_DECAL_SCALE = 2.4;
|
|
13256
|
+
var DOOR_DEPTH = 0.07;
|
|
13257
|
+
var DOOR_EDGE_PIECES = 16;
|
|
13258
|
+
var DOOR_CARD_COLOR = "#3b3e42";
|
|
13259
|
+
var DOOR_LINER_COLOR = "#5a5e64";
|
|
13260
|
+
var DOOR_LINER_DARK = 0.15;
|
|
13261
|
+
var DOOR_STRIP_THIN = 0.05;
|
|
13262
|
+
var DOOR_STRIP_NEAR = 0.04;
|
|
13263
|
+
var DOOR_STRIP_OVER = 0.3;
|
|
13264
|
+
var DOOR_TOP_OVER_PANE = 0.03;
|
|
13265
|
+
var DOOR_STRIP_SUB = 0.02;
|
|
13266
|
+
var HOLE_TEXTURE_SIZE = 64;
|
|
13267
|
+
var DOOR_HANDLE_INSET = 0.12;
|
|
13268
|
+
var DOOR_HANDLE_MIN = 0.07;
|
|
13269
|
+
var DOOR_HANDLE_MAX = 0.24;
|
|
13270
|
+
var DOOR_HANDLE_THIN = 0.07;
|
|
13271
|
+
var DOOR_HANDLE_OUTSIDE = 0.1;
|
|
13272
|
+
var PANE_SEAT_OUT = 0.05;
|
|
13273
|
+
var PANE_SEAT_IN = -0.05;
|
|
13274
|
+
var PANE_SEAT_REACH = 0.14;
|
|
13275
|
+
var PANE_SEAT_SAMPLES = 24;
|
|
13276
|
+
var PANE_SEAT_LIFT = 4e-3;
|
|
13277
|
+
var PANE_FRAME_NEAR = 0.07;
|
|
13278
|
+
var DRAWN_MIRROR_SIZE = 0.3;
|
|
13279
|
+
var DRAWN_MIRROR_REACH = 0.4;
|
|
13280
|
+
var DRAWN_MIRROR_MOUNT = 0.2;
|
|
13281
|
+
var MIRROR_GLASS_FIT = 0.76;
|
|
13282
|
+
var MIRROR_GLASS_THIN = 0.04;
|
|
13283
|
+
var MIRROR_GLASS_MIN = 0.05;
|
|
13284
|
+
var MIRROR_GLASS_COLOR = "#59636a";
|
|
13285
|
+
var HOLE_RIM = { steel: "#b7bcc0", aluminium: "#d3d6d8", plastic: "#d9d6cc", rubber: "#141414", glass: "#eef3f5" };
|
|
13286
|
+
var holeMaterials = /* @__PURE__ */ new WeakMap();
|
|
13287
|
+
var holeDisc = null;
|
|
13288
|
+
function holeDiscGeometry(THREE) {
|
|
13289
|
+
holeDisc = holeDisc ?? new THREE.CircleGeometry(1, 12);
|
|
13290
|
+
return holeDisc;
|
|
13291
|
+
}
|
|
13292
|
+
function holeMaterial(kit, material) {
|
|
13293
|
+
const key = kit;
|
|
13294
|
+
const cache2 = holeMaterials.get(key) ?? /* @__PURE__ */ new Map();
|
|
13295
|
+
holeMaterials.set(key, cache2);
|
|
13296
|
+
const known = cache2.get(material);
|
|
13297
|
+
if (known) return known;
|
|
13298
|
+
const I = kit._;
|
|
13299
|
+
const THREE = kit.THREE;
|
|
13300
|
+
const canvas = kit.document?.createElement?.("canvas");
|
|
13301
|
+
const x = canvas?.getContext?.("2d");
|
|
13302
|
+
let made = I.MATERIALS.dark;
|
|
13303
|
+
if (x) {
|
|
13304
|
+
canvas.width = canvas.height = HOLE_TEXTURE_SIZE;
|
|
13305
|
+
const mid = HOLE_TEXTURE_SIZE / 2;
|
|
13306
|
+
x.fillStyle = HOLE_RIM[material] ?? HOLE_RIM.steel;
|
|
13307
|
+
x.beginPath();
|
|
13308
|
+
for (let i = 0; i < 18; i++) {
|
|
13309
|
+
const a = i / 18 * Math.PI * 2;
|
|
13310
|
+
const r = mid * (i % 2 ? 0.98 : 0.76);
|
|
13311
|
+
x.lineTo(mid + Math.cos(a) * r, mid + Math.sin(a) * r);
|
|
13312
|
+
}
|
|
13313
|
+
x.closePath();
|
|
13314
|
+
x.fill();
|
|
13315
|
+
x.fillStyle = "#050505";
|
|
13316
|
+
x.beginPath();
|
|
13317
|
+
x.arc(mid, mid, mid * 0.42, 0, Math.PI * 2);
|
|
13318
|
+
x.fill();
|
|
13319
|
+
const map = new THREE.CanvasTexture(canvas);
|
|
13320
|
+
made = new THREE.MeshStandardMaterial({ map, transparent: true, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -2, roughness: 0.55, metalness: material === "steel" || material === "aluminium" ? 0.6 : 0 });
|
|
13321
|
+
}
|
|
13322
|
+
cache2.set(material, made);
|
|
13323
|
+
return made;
|
|
13324
|
+
}
|
|
12950
13325
|
var VehicleVisuals = class {
|
|
12951
13326
|
constructor(kit, c, model) {
|
|
12952
13327
|
__publicField(this, "kit");
|
|
@@ -12964,14 +13339,23 @@ var GameVehicles = (() => {
|
|
|
12964
13339
|
__publicField(this, "syncPlates", /* @__PURE__ */ new Map());
|
|
12965
13340
|
/** Doors and lids whose own skin was cut from the body and hung on the hinge (true), or that had none to take and keep the plate (false). */
|
|
12966
13341
|
__publicField(this, "skins", /* @__PURE__ */ new Map());
|
|
13342
|
+
/** How high each door's cut reaches. */
|
|
13343
|
+
__publicField(this, "doorTops", /* @__PURE__ */ new Map());
|
|
12967
13344
|
__publicField(this, "lightsOn", true);
|
|
12968
13345
|
/** Heavy body meshes whose triangle index is still to build (null once done). */
|
|
12969
13346
|
__publicField(this, "warming");
|
|
12970
13347
|
/** A panel was torn off since the last `pump`: that frame already paid for the cut, the smoothing starts on the next one. */
|
|
12971
13348
|
__publicField(this, "tornThisFrame", false);
|
|
13349
|
+
/** A shared crease queue owns the smoothing: `pump` only closes the frame of the cut. */
|
|
13350
|
+
__publicField(this, "queued", false);
|
|
13351
|
+
/** A panel was torn and the queue has not asked yet: the first `pendingUnits(frame)` after it holds the work for that frame. */
|
|
13352
|
+
__publicField(this, "holdArmed", false);
|
|
13353
|
+
__publicField(this, "holdFrame", -1);
|
|
12972
13354
|
/** Normal smoothing still running, by mesh: a mesh cut again gets a new task and the old one is dropped. */
|
|
12973
13355
|
__publicField(this, "smoothing", /* @__PURE__ */ new Map());
|
|
12974
13356
|
__publicField(this, "hood");
|
|
13357
|
+
;
|
|
13358
|
+
c.smoothLater = (mesh2) => this.smoothLater(mesh2);
|
|
12975
13359
|
this.kit = kit;
|
|
12976
13360
|
this.c = c;
|
|
12977
13361
|
this.model = model;
|
|
@@ -12980,6 +13364,8 @@ var GameVehicles = (() => {
|
|
|
12980
13364
|
for (const p of model.parts) this.byId.set(p.id, p);
|
|
12981
13365
|
c.root.updateMatrixWorld(true);
|
|
12982
13366
|
this.hood = this.nameHood();
|
|
13367
|
+
this.seatPanes();
|
|
13368
|
+
this.seatDoorHandles();
|
|
12983
13369
|
this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
|
|
12984
13370
|
if (!this.warming.length) this.warming = null;
|
|
12985
13371
|
for (const p of model.parts) {
|
|
@@ -13000,6 +13386,7 @@ var GameVehicles = (() => {
|
|
|
13000
13386
|
const id = z ? z.id : this.model.parts.find((p) => p.role === "hood")?.id;
|
|
13001
13387
|
if (!id) return null;
|
|
13002
13388
|
c.hood.piv.name = id;
|
|
13389
|
+
c.hood.piv.userData.plateOf = id;
|
|
13003
13390
|
return { id, open: c.hood.open, sign: c.hood.sign || 1 };
|
|
13004
13391
|
}
|
|
13005
13392
|
mirrorObjects(p) {
|
|
@@ -13087,6 +13474,7 @@ var GameVehicles = (() => {
|
|
|
13087
13474
|
recess.userData.noFollow = true;
|
|
13088
13475
|
const piv = new THREE.Group();
|
|
13089
13476
|
piv.name = p.id;
|
|
13477
|
+
piv.userData.plateOf = p.id;
|
|
13090
13478
|
piv.position.set(hx, cy, sk + s * 0.016);
|
|
13091
13479
|
piv.userData.rigid = true;
|
|
13092
13480
|
this.c.vehicle.add(piv);
|
|
@@ -13103,6 +13491,7 @@ var GameVehicles = (() => {
|
|
|
13103
13491
|
recess.userData.noFollow = true;
|
|
13104
13492
|
const piv = new THREE.Group();
|
|
13105
13493
|
piv.name = p.id;
|
|
13494
|
+
piv.userData.plateOf = p.id;
|
|
13106
13495
|
piv.position.set(sk - 0.016, B.y1 - 0.05, 0);
|
|
13107
13496
|
piv.userData.rigid = true;
|
|
13108
13497
|
this.c.vehicle.add(piv);
|
|
@@ -13166,10 +13555,19 @@ var GameVehicles = (() => {
|
|
|
13166
13555
|
try {
|
|
13167
13556
|
if (p.role === "door") {
|
|
13168
13557
|
const dirty = /* @__PURE__ */ new Set();
|
|
13169
|
-
const
|
|
13170
|
-
const
|
|
13558
|
+
const glassBox = new this.THREE.Box3();
|
|
13559
|
+
for (const g of I.windowsOver(this.c, z, inv)) glassBox.union(I.localBox(this.c, g, inv));
|
|
13560
|
+
const top = glassBox.isEmpty() ? B.y1 - 0.02 : Math.max(B.y1 - 0.02, glassBox.min.y + DOOR_TOP_OVER_PANE);
|
|
13561
|
+
this.doorTops.set(id, top);
|
|
13562
|
+
const rect = [B.x0 + 0.03, B.x1 - 0.03, B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1), top];
|
|
13563
|
+
const pcs = I.cutPanel(this.c, z, rect, true, dirty, { anyN: true }).map((x) => x.mesh).filter((m) => !!m);
|
|
13171
13564
|
this.mend(dirty);
|
|
13172
|
-
if (pcs.length)
|
|
13565
|
+
if (pcs.length) {
|
|
13566
|
+
const panes = I.windowsOver(this.c, z, inv);
|
|
13567
|
+
const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
|
|
13568
|
+
const mirrors = this.mirrorsOn(z);
|
|
13569
|
+
objs = [...pcs, ...this.doorCards(pcs, z), ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors];
|
|
13570
|
+
}
|
|
13173
13571
|
} else {
|
|
13174
13572
|
const pcs = this.cutZone(z);
|
|
13175
13573
|
if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv)];
|
|
@@ -13187,10 +13585,124 @@ var GameVehicles = (() => {
|
|
|
13187
13585
|
const di = deform.indexOf(o);
|
|
13188
13586
|
if (di >= 0) deform.splice(di, 1);
|
|
13189
13587
|
}
|
|
13588
|
+
if (p.role === "door" && objs.length > 0) {
|
|
13589
|
+
this.doorEdges(piv, z, this.doorTops.get(id) ?? B.y1 - 0.02);
|
|
13590
|
+
this.liftDoorTrim(objs, z);
|
|
13591
|
+
this.cutStripsAcross(piv, z, inv);
|
|
13592
|
+
}
|
|
13190
13593
|
piv.quaternion.copy(q);
|
|
13191
13594
|
this.c.root.updateMatrixWorld(true);
|
|
13192
13595
|
return objs.length > 0;
|
|
13193
13596
|
}
|
|
13597
|
+
/** A door has edges: a band that follows the skin all round closes it to its card and hides where the cut followed the triangles of the shell; the same band, in the same paint as on a real car, lines the opening. */
|
|
13598
|
+
doorEdges(piv, z, y1) {
|
|
13599
|
+
const THREE = this.THREE;
|
|
13600
|
+
const B = z.box;
|
|
13601
|
+
const side2 = z.side < 0 ? -1 : 1;
|
|
13602
|
+
const flat = piv.position.z - side2 * 0.016;
|
|
13603
|
+
const x0 = B.x0 + 0.03;
|
|
13604
|
+
const x1 = B.x1 - 0.03;
|
|
13605
|
+
const y0 = B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1);
|
|
13606
|
+
const loop = [];
|
|
13607
|
+
const run = (ax, ay, bx, by) => {
|
|
13608
|
+
for (let i = 0; i < DOOR_EDGE_PIECES; i++) loop.push([ax + (bx - ax) * i / DOOR_EDGE_PIECES, ay + (by - ay) * i / DOOR_EDGE_PIECES]);
|
|
13609
|
+
};
|
|
13610
|
+
run(x0, y0, x1, y0);
|
|
13611
|
+
run(x1, y0, x1, y1);
|
|
13612
|
+
run(x1, y1, x0, y1);
|
|
13613
|
+
run(x0, y1, x0, y0);
|
|
13614
|
+
const heights = loop.map(([x, y]) => this.surface("z", side2, B, [[x, y]]) ?? flat);
|
|
13615
|
+
const band = (lift) => {
|
|
13616
|
+
const position = [];
|
|
13617
|
+
const index = [];
|
|
13618
|
+
loop.forEach(([x, y], i) => {
|
|
13619
|
+
position.push(x, y, heights[i] + side2 * lift, x, y, heights[i] + side2 * (lift - DOOR_DEPTH));
|
|
13620
|
+
const a = i * 2;
|
|
13621
|
+
const b = (i + 1) % loop.length * 2;
|
|
13622
|
+
index.push(a, a + 1, b, b, a + 1, b + 1);
|
|
13623
|
+
});
|
|
13624
|
+
const g = new THREE.BufferGeometry();
|
|
13625
|
+
g.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
|
|
13626
|
+
g.setIndex(index);
|
|
13627
|
+
g.computeVertexNormals();
|
|
13628
|
+
return g;
|
|
13629
|
+
};
|
|
13630
|
+
const paint = this.paint().clone();
|
|
13631
|
+
paint.side = THREE.DoubleSide;
|
|
13632
|
+
const edges = new THREE.Mesh(band(2e-3), paint);
|
|
13633
|
+
const lining = new THREE.Mesh(band(2e-3), paint);
|
|
13634
|
+
for (const m of [edges, lining]) {
|
|
13635
|
+
m.userData.noFollow = true;
|
|
13636
|
+
m.castShadow = false;
|
|
13637
|
+
this.c.vehicle.add(m);
|
|
13638
|
+
}
|
|
13639
|
+
piv.attach(edges);
|
|
13640
|
+
}
|
|
13641
|
+
/** A front door carries the mirror drawn at its corner. */
|
|
13642
|
+
mirrorsOn(z) {
|
|
13643
|
+
if (!z.mirror) return [];
|
|
13644
|
+
const own = (z.mirrors ?? []).filter((o) => o.parent);
|
|
13645
|
+
if (own.length) return own;
|
|
13646
|
+
const part = this.model.parts.find((q) => q.role === "mirror" && Math.sign(q.box.center[2]) === Math.sign(z.side));
|
|
13647
|
+
return part ? (this.extra.get(part.id) ?? []).filter((o) => o.parent) : [];
|
|
13648
|
+
}
|
|
13649
|
+
/** The panel drawn inside the door is lit like the rest of a cabin, not a black slab from end to end. */
|
|
13650
|
+
liftDoorTrim(objs, z) {
|
|
13651
|
+
const card = this.I.makeMaterial(DOOR_LINER_COLOR, { roughness: 0.85 });
|
|
13652
|
+
const inv = this.I.worldInverseOf(this.c);
|
|
13653
|
+
for (const o of objs) {
|
|
13654
|
+
if (!o.isMesh || o.userData.glass) continue;
|
|
13655
|
+
const box2 = this.I.localBox(this.c, o, inv);
|
|
13656
|
+
const size = box2.getSize(new this.THREE.Vector3());
|
|
13657
|
+
if (size.x > (z.box.x1 - z.box.x0) * 0.4 && size.y > (z.box.y1 - z.box.y0) * 0.3 && size.z < DOOR_DEPTH * 1.5 && o.material?.color?.getHSL({ h: 0, s: 0, l: 0 }).l < DOOR_LINER_DARK) o.material = card;
|
|
13658
|
+
}
|
|
13659
|
+
}
|
|
13660
|
+
/** A long strip drawn along the side of the car (the chrome of the belt): the part over the door goes with the door, and nothing stays across the opening. */
|
|
13661
|
+
cutStripsAcross(piv, z, inv) {
|
|
13662
|
+
const THREE = this.THREE;
|
|
13663
|
+
const B = z.box;
|
|
13664
|
+
const x0 = B.x0 + 0.03;
|
|
13665
|
+
const x1 = B.x1 - 0.03;
|
|
13666
|
+
const y0 = B.y0 - DOOR_STRIP_NEAR;
|
|
13667
|
+
const y1 = B.y1 + DOOR_STRIP_NEAR;
|
|
13668
|
+
const deform = this.c.deform ?? [];
|
|
13669
|
+
for (const o of [...this.c.vehicle.children]) {
|
|
13670
|
+
if (!o.isMesh || o === piv || deform.includes(o) || o.userData.glass || o.userData.decal || !o.visible) continue;
|
|
13671
|
+
const box2 = this.I.localBox(this.c, o, inv);
|
|
13672
|
+
const size = box2.getSize(new THREE.Vector3());
|
|
13673
|
+
const mid = box2.getCenter(new THREE.Vector3());
|
|
13674
|
+
if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_THIN || 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
|
+
const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
|
|
13676
|
+
const v = new THREE.Vector3();
|
|
13677
|
+
const sdf = (x, y, zz) => {
|
|
13678
|
+
v.set(x, y, zz).applyMatrix4(toVehicle);
|
|
13679
|
+
return Math.max(x0 - v.x, v.x - x1);
|
|
13680
|
+
};
|
|
13681
|
+
this.kit.cutMesh(o, [{ sdf }], 42, { sub: DOOR_STRIP_SUB });
|
|
13682
|
+
const from = Math.max(box2.min.x, x0);
|
|
13683
|
+
const to = Math.min(box2.max.x, x1);
|
|
13684
|
+
const piece = this.box(to - from, size.y, size.z, o.material, (from + to) / 2, mid.y, mid.z);
|
|
13685
|
+
piece.userData.noFollow = true;
|
|
13686
|
+
piv.attach(piece);
|
|
13687
|
+
}
|
|
13688
|
+
}
|
|
13689
|
+
/** The inside of a door: its skin brought in by the depth of the door, in the trim's dark plastic, so the open door is a door and not one sheet of paint. */
|
|
13690
|
+
doorCards(skins, z) {
|
|
13691
|
+
const cardMaterial = this.I.makeMaterial(DOOR_CARD_COLOR, { roughness: 0.8 });
|
|
13692
|
+
return skins.map((skin) => {
|
|
13693
|
+
const card = new this.THREE.Mesh(skin.geometry, cardMaterial);
|
|
13694
|
+
card.position.copy(skin.position);
|
|
13695
|
+
card.quaternion.copy(skin.quaternion);
|
|
13696
|
+
card.scale.copy(skin.scale);
|
|
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
|
+
});
|
|
13705
|
+
}
|
|
13194
13706
|
mend(dirty) {
|
|
13195
13707
|
dirty.forEach((msh) => {
|
|
13196
13708
|
const g = msh.geometry;
|
|
@@ -13200,7 +13712,191 @@ var GameVehicles = (() => {
|
|
|
13200
13712
|
g.computeBoundingBox();
|
|
13201
13713
|
});
|
|
13202
13714
|
}
|
|
13715
|
+
/**
|
|
13716
|
+
* A windshield or rear window is drawn as a flat quad, and the body under its edge is curved: the pane and its frame stand off the shell or rise past the roof. Each one
|
|
13717
|
+
* comes to the shell along its own normal, by how far the shell is just outside each of its vertices.
|
|
13718
|
+
*/
|
|
13719
|
+
seatPanes() {
|
|
13720
|
+
const THREE = this.THREE;
|
|
13721
|
+
const c = this.c;
|
|
13722
|
+
c.root.updateMatrixWorld(true);
|
|
13723
|
+
const shell = c.deform.filter((o) => o.isMesh && !o.userData.glass && c.paints?.has(o.material));
|
|
13724
|
+
if (!shell.length) return;
|
|
13725
|
+
const ray = new THREE.Raycaster();
|
|
13726
|
+
ray.layers.enableAll();
|
|
13727
|
+
for (const pane of c.glass) {
|
|
13728
|
+
if (pane.userData.glass?.outline) continue;
|
|
13729
|
+
const seat = this.paneSeat(pane, shell, ray);
|
|
13730
|
+
if (!seat) continue;
|
|
13731
|
+
const near = c.vehicle.children.filter((o) => o.isMesh && o !== pane && !o.userData.glass && !shell.includes(o) && this.onPane(o, pane, seat));
|
|
13732
|
+
for (const m of [pane, ...near]) this.movePane(m, seat);
|
|
13733
|
+
const past = this.shellPast(pane, shell, ray, seat.normal);
|
|
13734
|
+
if (past > 0) for (const m of [pane, ...near]) this.movePane(m, { normal: seat.normal, samples: [{ at: seat.samples[0].at, by: past }] });
|
|
13735
|
+
}
|
|
13736
|
+
}
|
|
13737
|
+
/** The outward normal of a pane and how far the shell is at each sampled point (world). */
|
|
13738
|
+
paneSeat(pane, shell, ray) {
|
|
13739
|
+
const THREE = this.THREE;
|
|
13740
|
+
const pos = pane.geometry.attributes.position;
|
|
13741
|
+
const box2 = new THREE.Box3().setFromObject(pane);
|
|
13742
|
+
const centre = box2.getCenter(new THREE.Vector3());
|
|
13743
|
+
const vehicleCentre = new THREE.Box3().setFromObject(this.c.root).getCenter(new THREE.Vector3());
|
|
13744
|
+
const tri = new THREE.Triangle();
|
|
13745
|
+
const normal = new THREE.Vector3();
|
|
13746
|
+
let largest = 0;
|
|
13747
|
+
const idx = pane.geometry.index;
|
|
13748
|
+
const count = idx ? idx.count : pos.count;
|
|
13749
|
+
for (let f = 0; f + 2 < count; f += 3) {
|
|
13750
|
+
tri.a.fromBufferAttribute(pos, idx ? idx.getX(f) : f);
|
|
13751
|
+
tri.b.fromBufferAttribute(pos, idx ? idx.getX(f + 1) : f + 1);
|
|
13752
|
+
tri.c.fromBufferAttribute(pos, idx ? idx.getX(f + 2) : f + 2);
|
|
13753
|
+
if (tri.getArea() > largest) {
|
|
13754
|
+
largest = tri.getArea();
|
|
13755
|
+
tri.getNormal(normal);
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
13758
|
+
normal.transformDirection(pane.matrixWorld);
|
|
13759
|
+
if (normal.dot(centre.clone().sub(vehicleCentre)) < 0) normal.negate();
|
|
13760
|
+
const samples = [];
|
|
13761
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13762
|
+
const v = new THREE.Vector3();
|
|
13763
|
+
const step = Math.max(1, Math.floor(pos.count / PANE_SEAT_SAMPLES));
|
|
13764
|
+
for (let i = 0; i < pos.count; i += step) {
|
|
13765
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(pane.matrixWorld);
|
|
13766
|
+
const key = `${Math.round(v.x * 100)},${Math.round(v.y * 100)},${Math.round(v.z * 100)}`;
|
|
13767
|
+
if (seen.has(key)) continue;
|
|
13768
|
+
seen.add(key);
|
|
13769
|
+
const away = v.clone().sub(centre);
|
|
13770
|
+
away.addScaledVector(normal, -away.dot(normal)).normalize();
|
|
13771
|
+
let by = -Infinity;
|
|
13772
|
+
for (const off of [PANE_SEAT_OUT, PANE_SEAT_IN, PANE_SEAT_IN * 3]) {
|
|
13773
|
+
const at = v.clone().addScaledVector(away, off);
|
|
13774
|
+
ray.set(at.addScaledVector(normal, PANE_SEAT_REACH), normal.clone().negate());
|
|
13775
|
+
ray.far = PANE_SEAT_REACH * 2;
|
|
13776
|
+
const hit = raycastIndexed(THREE, ray, shell)[0];
|
|
13777
|
+
if (hit) by = Math.max(by, PANE_SEAT_REACH - hit.distance);
|
|
13778
|
+
}
|
|
13779
|
+
if (by > -Infinity) samples.push({ at: v.clone(), by });
|
|
13780
|
+
}
|
|
13781
|
+
return samples.length >= 3 ? { normal, samples } : null;
|
|
13782
|
+
}
|
|
13783
|
+
/** How far the shell still stands past a seated pane, over its triangles. */
|
|
13784
|
+
shellPast(pane, shell, ray, normal) {
|
|
13785
|
+
const THREE = this.THREE;
|
|
13786
|
+
const pos = pane.geometry.attributes.position;
|
|
13787
|
+
const idx = pane.geometry.index;
|
|
13788
|
+
const count = idx ? idx.count : pos.count;
|
|
13789
|
+
const tri = new THREE.Triangle();
|
|
13790
|
+
const step = Math.max(1, Math.floor(count / 3 / PANE_SEAT_SAMPLES)) * 3;
|
|
13791
|
+
let past = 0;
|
|
13792
|
+
for (let f = 0; f + 2 < count; f += step) {
|
|
13793
|
+
tri.a.fromBufferAttribute(pos, idx ? idx.getX(f) : f);
|
|
13794
|
+
tri.b.fromBufferAttribute(pos, idx ? idx.getX(f + 1) : f + 1);
|
|
13795
|
+
tri.c.fromBufferAttribute(pos, idx ? idx.getX(f + 2) : f + 2);
|
|
13796
|
+
const mid = tri.getMidpoint(new THREE.Vector3()).applyMatrix4(pane.matrixWorld);
|
|
13797
|
+
ray.set(mid.addScaledVector(normal, PANE_SEAT_REACH), normal.clone().negate());
|
|
13798
|
+
ray.far = PANE_SEAT_REACH;
|
|
13799
|
+
const hit = raycastIndexed(THREE, ray, shell)[0];
|
|
13800
|
+
if (hit) past = Math.max(past, PANE_SEAT_REACH - hit.distance + PANE_SEAT_LIFT);
|
|
13801
|
+
}
|
|
13802
|
+
return past;
|
|
13803
|
+
}
|
|
13804
|
+
/** A frame or a seal drawn on the pane: small, and close to its plane. */
|
|
13805
|
+
onPane(o, pane, seat) {
|
|
13806
|
+
const THREE = this.THREE;
|
|
13807
|
+
const box2 = new THREE.Box3().setFromObject(o);
|
|
13808
|
+
if (box2.isEmpty() || !new THREE.Box3().setFromObject(pane).expandByScalar(PANE_FRAME_NEAR).containsBox(box2)) return false;
|
|
13809
|
+
const at = box2.getCenter(new THREE.Vector3());
|
|
13810
|
+
return Math.abs(at.clone().sub(seat.samples[0].at).dot(seat.normal)) < PANE_SEAT_REACH;
|
|
13811
|
+
}
|
|
13812
|
+
movePane(mesh2, seat) {
|
|
13813
|
+
const THREE = this.THREE;
|
|
13814
|
+
const g = mesh2.geometry.clone();
|
|
13815
|
+
const pos = g.attributes.position;
|
|
13816
|
+
const inverse = mesh2.matrixWorld.clone().invert();
|
|
13817
|
+
const v = new THREE.Vector3();
|
|
13818
|
+
for (let i = 0; i < pos.count; i++) {
|
|
13819
|
+
v.fromBufferAttribute(pos, i).applyMatrix4(mesh2.matrixWorld);
|
|
13820
|
+
let weight = 0;
|
|
13821
|
+
let by = 0;
|
|
13822
|
+
for (const s of seat.samples) {
|
|
13823
|
+
const w = 1 / (s.at.distanceToSquared(v) + 1e-4);
|
|
13824
|
+
weight += w;
|
|
13825
|
+
by += s.by * w;
|
|
13826
|
+
}
|
|
13827
|
+
v.addScaledVector(seat.normal, by / weight + PANE_SEAT_LIFT).applyMatrix4(inverse);
|
|
13828
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
13829
|
+
}
|
|
13830
|
+
pos.needsUpdate = true;
|
|
13831
|
+
g.computeBoundingBox();
|
|
13832
|
+
g.computeBoundingSphere();
|
|
13833
|
+
mesh2.geometry = g;
|
|
13834
|
+
}
|
|
13835
|
+
/** A handle is at the free edge of its door, the end away from the hinge. */
|
|
13836
|
+
seatDoorHandles() {
|
|
13837
|
+
const THREE = this.THREE;
|
|
13838
|
+
const c = this.c;
|
|
13839
|
+
const inv = this.I.worldInverseOf(c);
|
|
13840
|
+
for (const p of this.model.parts) {
|
|
13841
|
+
if (p.role !== "door") continue;
|
|
13842
|
+
const z = this.zone(p);
|
|
13843
|
+
if (!z) continue;
|
|
13844
|
+
const B = z.box;
|
|
13845
|
+
const free = z.hinge === "rear" ? B.x1 - DOOR_HANDLE_INSET : B.x0 + DOOR_HANDLE_INSET;
|
|
13846
|
+
for (const o of c.vehicle.children) {
|
|
13847
|
+
if (!o.isMesh || o.material !== this.I.MATERIALS.chrome || o.userData.glass) continue;
|
|
13848
|
+
const box2 = this.I.localBox(c, o, inv);
|
|
13849
|
+
const size = box2.getSize(new THREE.Vector3());
|
|
13850
|
+
const at = box2.getCenter(new THREE.Vector3());
|
|
13851
|
+
const isHandle = size.x > DOOR_HANDLE_MIN && size.x < DOOR_HANDLE_MAX && size.y < DOOR_HANDLE_THIN && size.z < DOOR_HANDLE_THIN;
|
|
13852
|
+
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
|
+
const here = this.surface("z", z.side < 0 ? -1 : 1, B, [[at.x, at.y]]);
|
|
13854
|
+
if (here != null && Math.abs(here - at.z) > DOOR_HANDLE_OUTSIDE) continue;
|
|
13855
|
+
const skin = this.surface("z", z.side < 0 ? -1 : 1, B, [[free, at.y]]);
|
|
13856
|
+
o.position.x += free - at.x;
|
|
13857
|
+
if (skin != null) o.position.z += skin + (z.side < 0 ? -1 : 1) * (size.z / 2) - at.z;
|
|
13858
|
+
}
|
|
13859
|
+
}
|
|
13860
|
+
c.root.updateMatrixWorld(true);
|
|
13861
|
+
}
|
|
13862
|
+
/** The glass of a drawn mirror sits inside its rounded housing, without corners standing past it. */
|
|
13863
|
+
seatMirrorGlass(glass) {
|
|
13864
|
+
const g = glass.geometry.clone();
|
|
13865
|
+
g.computeBoundingBox();
|
|
13866
|
+
const mid = g.boundingBox.getCenter(new this.THREE.Vector3());
|
|
13867
|
+
g.translate(-mid.x, -mid.y, -mid.z);
|
|
13868
|
+
g.scale(MIRROR_GLASS_FIT, MIRROR_GLASS_FIT, MIRROR_GLASS_FIT);
|
|
13869
|
+
g.translate(mid.x, mid.y, mid.z);
|
|
13870
|
+
glass.geometry = g;
|
|
13871
|
+
glass.material = this.I.makeMaterial(MIRROR_GLASS_COLOR, { metalness: 0.95, roughness: 0.12 });
|
|
13872
|
+
}
|
|
13873
|
+
/** The mirror the model already draws around where the part says, if it draws one. */
|
|
13874
|
+
drawnMirror(p) {
|
|
13875
|
+
const THREE = this.THREE;
|
|
13876
|
+
const [cx, cy, cz] = p.box.center;
|
|
13877
|
+
this.c.root.updateMatrixWorld(true);
|
|
13878
|
+
const inv = this.I.worldInverseOf(this.c);
|
|
13879
|
+
const small = this.c.vehicle.children.flatMap((o) => {
|
|
13880
|
+
if (o.userData.glass || o.userData.decal || !(o.isMesh || o.isGroup) || !o.visible || this.c.deform?.includes(o)) return [];
|
|
13881
|
+
const box2 = this.I.localBox(this.c, o, inv);
|
|
13882
|
+
if (box2.isEmpty()) return [];
|
|
13883
|
+
const size = box2.getSize(new THREE.Vector3());
|
|
13884
|
+
return Math.max(size.x, size.y, size.z) < DRAWN_MIRROR_SIZE ? [{ o, at: box2.getCenter(new THREE.Vector3()) }] : [];
|
|
13885
|
+
});
|
|
13886
|
+
const housing = small.filter(({ at }) => Math.hypot(at.x - cx, at.y - cy, at.z - cz) < DRAWN_MIRROR_REACH && Math.abs(at.z) > Math.abs(cz) - DRAWN_MIRROR_REACH / 2);
|
|
13887
|
+
return small.filter(({ at }) => housing.some((h) => h.at.distanceTo(at) < DRAWN_MIRROR_MOUNT)).map(({ o }) => o);
|
|
13888
|
+
}
|
|
13203
13889
|
makeMirror(p) {
|
|
13890
|
+
const drawn = this.drawnMirror(p);
|
|
13891
|
+
if (drawn.length) {
|
|
13892
|
+
for (const o of drawn) {
|
|
13893
|
+
if (!o.isMesh || o.material !== this.I.MATERIALS.chrome) continue;
|
|
13894
|
+
const size = new this.THREE.Box3().setFromObject(o).getSize(new this.THREE.Vector3());
|
|
13895
|
+
if (size.x < MIRROR_GLASS_THIN && size.y > MIRROR_GLASS_MIN && size.z > MIRROR_GLASS_MIN) this.seatMirrorGlass(o);
|
|
13896
|
+
}
|
|
13897
|
+
this.extra.set(p.id, drawn);
|
|
13898
|
+
return;
|
|
13899
|
+
}
|
|
13204
13900
|
const [cx, cy, cz] = p.box.center;
|
|
13205
13901
|
const s = cz < 0 ? -1 : 1;
|
|
13206
13902
|
const mats = this.I.MATERIALS;
|
|
@@ -13241,17 +13937,28 @@ var GameVehicles = (() => {
|
|
|
13241
13937
|
}
|
|
13242
13938
|
// ---- marks -------------------------------------------------------------------------------------------------------------------------------
|
|
13243
13939
|
/** A bullet hole: a dark disc lying on the surface at `point` (`c.vehicle` coordinates). */
|
|
13244
|
-
holeDecal(point, normal, diameter) {
|
|
13940
|
+
holeDecal(point, normal, diameter, part) {
|
|
13245
13941
|
const THREE = this.THREE;
|
|
13246
|
-
const
|
|
13942
|
+
const pane = part?.role === "glass" ? this.panesOf(part).find((g) => g.visible) : void 0;
|
|
13943
|
+
if (part?.role === "glass" && (!pane || pane.userData.glass?.broken)) return;
|
|
13944
|
+
const radius = Math.max(6e-3, diameter / 2) * HOLE_DECAL_SCALE;
|
|
13945
|
+
const m = new THREE.Mesh(holeDiscGeometry(THREE), holeMaterial(this.kit, part?.material ?? "steel"));
|
|
13946
|
+
m.scale.set(radius, radius, 1);
|
|
13247
13947
|
const n = new THREE.Vector3(...normal);
|
|
13248
|
-
m.position.set(...point).addScaledVector(n,
|
|
13249
|
-
|
|
13948
|
+
m.position.set(...point).addScaledVector(n, 3e-3);
|
|
13949
|
+
this.c.vehicle.add(m);
|
|
13950
|
+
m.lookAt(this.c.vehicle.localToWorld(m.position.clone().add(n)));
|
|
13250
13951
|
m.userData.decal = true;
|
|
13251
13952
|
m.userData.noFollow = true;
|
|
13953
|
+
m.renderOrder = 3;
|
|
13252
13954
|
m.raycast = () => {
|
|
13253
13955
|
};
|
|
13254
|
-
this.c.vehicle.
|
|
13956
|
+
const carrier = (part && (this.plates.get(part.id) ?? (part.id === this.hood?.id ? this.c.hood?.piv : void 0))) ?? (pane && pane.parent !== this.c.vehicle ? pane.parent : null);
|
|
13957
|
+
if (carrier) carrier.attach(m);
|
|
13958
|
+
}
|
|
13959
|
+
/** The door or lid a hinge group stands for. */
|
|
13960
|
+
plateIdOf(o) {
|
|
13961
|
+
return o.userData.plateOf;
|
|
13255
13962
|
}
|
|
13256
13963
|
spark(point, dir, count = 8) {
|
|
13257
13964
|
const V3 = this.THREE.Vector3;
|
|
@@ -13291,10 +13998,8 @@ var GameVehicles = (() => {
|
|
|
13291
13998
|
}
|
|
13292
13999
|
/** Glass: cracks (laminated) or shatters (tempered); returns whether it broke. */
|
|
13293
14000
|
hitGlass(p, point, dir, force) {
|
|
13294
|
-
const z = this.zone(p);
|
|
13295
|
-
if (!z) return false;
|
|
13296
14001
|
const V3 = this.THREE.Vector3;
|
|
13297
|
-
const list2 =
|
|
14002
|
+
const list2 = this.panesOf(p).filter((g) => g.parent && g.visible);
|
|
13298
14003
|
if (!list2.length) return false;
|
|
13299
14004
|
const pt = new V3(...point);
|
|
13300
14005
|
let best = list2[0];
|
|
@@ -13311,9 +14016,16 @@ var GameVehicles = (() => {
|
|
|
13311
14016
|
this.kit.hitGlass(this.c, best, pt, new V3(...dir), force);
|
|
13312
14017
|
return !!best.userData.glass?.broken;
|
|
13313
14018
|
}
|
|
14019
|
+
panesOf(p) {
|
|
14020
|
+
return p.panes ?? this.zone(p)?.glass ?? [];
|
|
14021
|
+
}
|
|
13314
14022
|
glassBreak(p) {
|
|
13315
14023
|
const z = this.zone(p);
|
|
13316
|
-
if (!z)
|
|
14024
|
+
if (!z) {
|
|
14025
|
+
for (const g of p.panes ?? []) if (g.parent && g.visible) this.kit.hitGlass(this.c, g, g.position.clone(), new this.THREE.Vector3(0, -1, 0), 0.6);
|
|
14026
|
+
this.gone.add(p.id);
|
|
14027
|
+
return;
|
|
14028
|
+
}
|
|
13317
14029
|
for (const g of z.glass) if (g.parent && g.visible) this.I.glassBand(this.c, z, g, 4);
|
|
13318
14030
|
this.gone.add(p.id);
|
|
13319
14031
|
}
|
|
@@ -13341,6 +14053,7 @@ var GameVehicles = (() => {
|
|
|
13341
14053
|
smoothLater(msh) {
|
|
13342
14054
|
this.smoothing.set(msh, this.I.creaseTask(msh.geometry, 42));
|
|
13343
14055
|
this.tornThisFrame = true;
|
|
14056
|
+
this.holdArmed = true;
|
|
13344
14057
|
}
|
|
13345
14058
|
/** Spends at most `budgetMs` finishing the pending normal smoothing. Call once per frame. */
|
|
13346
14059
|
pump(budgetMs = 2.5) {
|
|
@@ -13348,6 +14061,7 @@ var GameVehicles = (() => {
|
|
|
13348
14061
|
this.tornThisFrame = false;
|
|
13349
14062
|
return;
|
|
13350
14063
|
}
|
|
14064
|
+
if (this.queued) return;
|
|
13351
14065
|
if (!this.smoothing.size && !this.warming) return;
|
|
13352
14066
|
const deadline = performance.now() + budgetMs;
|
|
13353
14067
|
for (const [msh, task] of this.smoothing) {
|
|
@@ -13356,6 +14070,34 @@ var GameVehicles = (() => {
|
|
|
13356
14070
|
}
|
|
13357
14071
|
this.warmIndexes(deadline);
|
|
13358
14072
|
}
|
|
14073
|
+
setQueued(isQueued) {
|
|
14074
|
+
this.queued = isQueued;
|
|
14075
|
+
}
|
|
14076
|
+
/** Units the shared queue may run now: 0 in the frame of the cut (the first ask after it holds the work for that frame, or `pump` closes it when there is no frame). */
|
|
14077
|
+
pendingUnits(frame) {
|
|
14078
|
+
if (this.holdArmed && frame !== void 0) {
|
|
14079
|
+
this.holdArmed = false;
|
|
14080
|
+
this.holdFrame = frame;
|
|
14081
|
+
}
|
|
14082
|
+
if (frame !== void 0 && frame === this.holdFrame) return 0;
|
|
14083
|
+
if (frame === void 0 && this.tornThisFrame) return 0;
|
|
14084
|
+
return this.smoothing.size + (this.warming ? 1 : 0);
|
|
14085
|
+
}
|
|
14086
|
+
/** One slice of the pending work, for the shared queue. */
|
|
14087
|
+
runStep(deadline) {
|
|
14088
|
+
const next = this.smoothing.entries().next();
|
|
14089
|
+
if (!next.done) {
|
|
14090
|
+
const [msh, task] = next.value;
|
|
14091
|
+
if (msh.geometry !== task.geo || task.step(deadline)) this.smoothing.delete(msh);
|
|
14092
|
+
return true;
|
|
14093
|
+
}
|
|
14094
|
+
if (!this.warming) return false;
|
|
14095
|
+
this.warmIndexes(deadline);
|
|
14096
|
+
return true;
|
|
14097
|
+
}
|
|
14098
|
+
finishAll() {
|
|
14099
|
+
while (this.runStep(Infinity)) ;
|
|
14100
|
+
}
|
|
13359
14101
|
/** Measures the triangles of the heavy body meshes once, a slice per frame, so the first panel torn off finds them ready (see `triIndexOf`). */
|
|
13360
14102
|
warmIndexes(deadline) {
|
|
13361
14103
|
if (!this.warming) return;
|
|
@@ -13527,6 +14269,7 @@ var GameVehicles = (() => {
|
|
|
13527
14269
|
}
|
|
13528
14270
|
pieceTasks.forEach((task, mesh2) => this.smoothing.set(mesh2, task));
|
|
13529
14271
|
this.tornThisFrame = true;
|
|
14272
|
+
this.holdArmed = true;
|
|
13530
14273
|
dirty.forEach((msh) => {
|
|
13531
14274
|
const g = msh.geometry;
|
|
13532
14275
|
if (g.userData.crease || !g.index) this.smoothLater(msh);
|
|
@@ -13633,6 +14376,7 @@ var GameVehicles = (() => {
|
|
|
13633
14376
|
let clock = 0;
|
|
13634
14377
|
let model;
|
|
13635
14378
|
let vis = null;
|
|
14379
|
+
let releaseCrease;
|
|
13636
14380
|
const ps = /* @__PURE__ */ new Map();
|
|
13637
14381
|
let js = [];
|
|
13638
14382
|
let leaks = /* @__PURE__ */ new Map();
|
|
@@ -13717,7 +14461,9 @@ var GameVehicles = (() => {
|
|
|
13717
14461
|
rand = rng(seedBase);
|
|
13718
14462
|
igniteRand = rng(strHash(`${c.key}|${id}|ignition|${options.seed ?? 0}`));
|
|
13719
14463
|
steerSign = rand() < 0.5 ? -1 : 1;
|
|
14464
|
+
releaseCrease?.();
|
|
13720
14465
|
vis = headless ? null : new VehicleVisuals(kit, c, model);
|
|
14466
|
+
releaseCrease = vis && options.creaseQueue ? options.creaseQueue.track(vis) : void 0;
|
|
13721
14467
|
recomputeGroups();
|
|
13722
14468
|
};
|
|
13723
14469
|
const recomputeGroups = () => {
|
|
@@ -14028,13 +14774,13 @@ var GameVehicles = (() => {
|
|
|
14028
14774
|
sync();
|
|
14029
14775
|
const M = toFrameMatrix();
|
|
14030
14776
|
const v = toLocal(h.point);
|
|
14031
|
-
const p = locate(v, 0.2);
|
|
14777
|
+
const p = (h.part ? ps.get(h.part) : void 0) ?? locate(v, 0.2);
|
|
14032
14778
|
if (!p) return;
|
|
14033
14779
|
const d = p.def;
|
|
14034
14780
|
const E = h.energy ?? 0;
|
|
14035
14781
|
const cal = h.caliber ?? 9;
|
|
14036
14782
|
const dir = dirToLocal(h.direction ?? [0, 0, -1]).normalize();
|
|
14037
|
-
const n = normalOf(d, v);
|
|
14783
|
+
const n = h.normal ? dirToLocal(h.normal).normalize() : normalOf(d, v);
|
|
14038
14784
|
const res = resist(d, p);
|
|
14039
14785
|
const through = E >= res;
|
|
14040
14786
|
const cosInc = Math.abs(dir.dot(n));
|
|
@@ -14053,7 +14799,7 @@ var GameVehicles = (() => {
|
|
|
14053
14799
|
const hp = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), diameter: r2(holeDiam), through };
|
|
14054
14800
|
p.holes.push(hp);
|
|
14055
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])))))) } : {} }));
|
|
14056
|
-
vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam);
|
|
14802
|
+
vis?.holeDecal([v.x, v.y, v.z], [n.x, n.y, n.z], holeDiam, d);
|
|
14057
14803
|
startLeak(p, v, cal, out, dir);
|
|
14058
14804
|
};
|
|
14059
14805
|
switch (d.material) {
|
|
@@ -14354,6 +15100,46 @@ var GameVehicles = (() => {
|
|
|
14354
15100
|
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
|
|
14355
15101
|
return false;
|
|
14356
15102
|
};
|
|
15103
|
+
const surfaceRays = new THREE.Raycaster();
|
|
15104
|
+
surfaceRays.layers.enableAll();
|
|
15105
|
+
surfaceRays.camera = new THREE.PerspectiveCamera();
|
|
15106
|
+
let meshParts = null;
|
|
15107
|
+
const paneParts = () => {
|
|
15108
|
+
if (meshParts) return meshParts;
|
|
15109
|
+
meshParts = /* @__PURE__ */ new Map();
|
|
15110
|
+
for (const d of model.parts) {
|
|
15111
|
+
if (d.role !== "glass") continue;
|
|
15112
|
+
const z = kit.zonesOf(c).find((q) => q.id === d.zoneId);
|
|
15113
|
+
for (const g of z?.glass ?? d.panes ?? []) meshParts.set(g, d.id);
|
|
15114
|
+
}
|
|
15115
|
+
return meshParts;
|
|
15116
|
+
};
|
|
15117
|
+
const isDrawnSurface = (o) => {
|
|
15118
|
+
for (let q = o; q && q !== c.vehicle; q = q.parent) if (!q.visible || q.userData.decal || q.userData.ghost) return false;
|
|
15119
|
+
return true;
|
|
15120
|
+
};
|
|
15121
|
+
const partOfSurface = (o, v) => {
|
|
15122
|
+
const pane = paneParts().get(o);
|
|
15123
|
+
if (pane) return ps.get(pane) ?? null;
|
|
15124
|
+
for (let q = o; q && q !== c.vehicle; q = q.parent) {
|
|
15125
|
+
if (vis?.plateIdOf(q)) return ps.get(vis.plateIdOf(q)) ?? null;
|
|
15126
|
+
}
|
|
15127
|
+
return locate(v, 0.08, (p) => p.def.role === "glass");
|
|
15128
|
+
};
|
|
15129
|
+
const surfaceRay = (ray) => {
|
|
15130
|
+
sync();
|
|
15131
|
+
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
|
+
surfaceRays.far = ray.maxDistance;
|
|
15133
|
+
for (const h of surfaceRays.intersectObject(c.vehicle, true)) {
|
|
15134
|
+
if (!h.face || !isDrawnSurface(h.object)) continue;
|
|
15135
|
+
const p = partOfSurface(h.object, c.vehicle.worldToLocal(h.point.clone()));
|
|
15136
|
+
if (!p || p.lost || ray.ignored?.(p.def.id)) continue;
|
|
15137
|
+
const n = h.face.normal.clone().transformDirection(h.object.matrixWorld);
|
|
15138
|
+
if (n.dot(surfaceRays.ray.direction) > 0) n.negate();
|
|
15139
|
+
return { part: p.def.id, point: [h.point.x, h.point.y, h.point.z], normal: [n.x, n.y, n.z], distance: h.distance, pathLen: 0.02 };
|
|
15140
|
+
}
|
|
15141
|
+
return null;
|
|
15142
|
+
};
|
|
14357
15143
|
const hit = (h) => {
|
|
14358
15144
|
stateCache = void 0;
|
|
14359
15145
|
const out = [];
|
|
@@ -14476,9 +15262,14 @@ var GameVehicles = (() => {
|
|
|
14476
15262
|
},
|
|
14477
15263
|
describe,
|
|
14478
15264
|
hit,
|
|
15265
|
+
surfaceRay,
|
|
14479
15266
|
update,
|
|
14480
15267
|
state,
|
|
14481
15268
|
reset,
|
|
15269
|
+
dispose: () => {
|
|
15270
|
+
releaseCrease?.();
|
|
15271
|
+
releaseCrease = void 0;
|
|
15272
|
+
},
|
|
14482
15273
|
disassemble,
|
|
14483
15274
|
extinguish,
|
|
14484
15275
|
serialize: () => ({
|
|
@@ -14516,6 +15307,145 @@ var GameVehicles = (() => {
|
|
|
14516
15307
|
root.userData.vehicle = vehicle;
|
|
14517
15308
|
return { root, vehicle };
|
|
14518
15309
|
}
|
|
15310
|
+
|
|
15311
|
+
// src/destructible/crease-queue.ts
|
|
15312
|
+
function createCreaseQueue() {
|
|
15313
|
+
const tracked = [];
|
|
15314
|
+
let cursor = 0;
|
|
15315
|
+
let lastFrame;
|
|
15316
|
+
let attached = false;
|
|
15317
|
+
const deferred = {
|
|
15318
|
+
id: DEFERRED_QUEUE_ID.VEHICLE_CREASE,
|
|
15319
|
+
pending(frame) {
|
|
15320
|
+
lastFrame = frame;
|
|
15321
|
+
let units = 0;
|
|
15322
|
+
for (const work of tracked) units += work.pendingUnits(frame);
|
|
15323
|
+
return units;
|
|
15324
|
+
},
|
|
15325
|
+
run(deadline, clock) {
|
|
15326
|
+
let ran = 0;
|
|
15327
|
+
let skipped = 0;
|
|
15328
|
+
while (tracked.length > 0 && skipped < tracked.length) {
|
|
15329
|
+
if (ran > 0 && clock() >= deadline) break;
|
|
15330
|
+
const work = tracked[cursor % tracked.length];
|
|
15331
|
+
cursor = (cursor + 1) % tracked.length;
|
|
15332
|
+
if (work.pendingUnits(lastFrame) > 0 && work.runStep(deadline)) {
|
|
15333
|
+
ran++;
|
|
15334
|
+
skipped = 0;
|
|
15335
|
+
} else skipped++;
|
|
15336
|
+
}
|
|
15337
|
+
return ran;
|
|
15338
|
+
},
|
|
15339
|
+
flush() {
|
|
15340
|
+
for (const work of tracked) work.finishAll();
|
|
15341
|
+
},
|
|
15342
|
+
attach() {
|
|
15343
|
+
attached = true;
|
|
15344
|
+
for (const work of tracked) work.setQueued(true);
|
|
15345
|
+
},
|
|
15346
|
+
detach() {
|
|
15347
|
+
attached = false;
|
|
15348
|
+
for (const work of tracked) work.setQueued(false);
|
|
15349
|
+
}
|
|
15350
|
+
};
|
|
15351
|
+
return {
|
|
15352
|
+
deferredQueue: () => deferred,
|
|
15353
|
+
track(work) {
|
|
15354
|
+
tracked.push(work);
|
|
15355
|
+
work.setQueued(attached);
|
|
15356
|
+
return () => {
|
|
15357
|
+
const at = tracked.indexOf(work);
|
|
15358
|
+
if (at < 0) return;
|
|
15359
|
+
tracked.splice(at, 1);
|
|
15360
|
+
work.setQueued(false);
|
|
15361
|
+
};
|
|
15362
|
+
}
|
|
15363
|
+
};
|
|
15364
|
+
}
|
|
15365
|
+
|
|
15366
|
+
// src/destructible/enclosure.ts
|
|
15367
|
+
var CABIN_LEAK = 0.02;
|
|
15368
|
+
var HOLE_RATE_PER_AREA = 3e3;
|
|
15369
|
+
var CRACK_GAP_PER_METRE = 0.5;
|
|
15370
|
+
var THROUGH = 0.18;
|
|
15371
|
+
var HOLE_PATCH = 0.12;
|
|
15372
|
+
var FULL_SWING = 1.15;
|
|
15373
|
+
var SHELL_ROLES = /* @__PURE__ */ new Set(["door", "glass", "roof", "lid", "panel", "pillar"]);
|
|
15374
|
+
var GONE = /* @__PURE__ */ new Set(["broken", "melted", "ashes"]);
|
|
15375
|
+
function cabinBoxOf(vehicle) {
|
|
15376
|
+
const cabin = vehicle.meta.cabin;
|
|
15377
|
+
if (!cabin || !(cabin.x1 > cabin.x0) || !(cabin.y1 > cabin.y0) || !(cabin.z > 0)) return null;
|
|
15378
|
+
return cabin;
|
|
15379
|
+
}
|
|
15380
|
+
function swingOf(vehicle, partId) {
|
|
15381
|
+
const pivot = vehicle.vehicle?.getObjectByName?.(partId);
|
|
15382
|
+
const q = pivot?.quaternion;
|
|
15383
|
+
if (!q) return 0;
|
|
15384
|
+
const angle = 2 * Math.acos(Math.min(1, Math.abs(q.w)));
|
|
15385
|
+
return Math.min(1, angle / FULL_SWING);
|
|
15386
|
+
}
|
|
15387
|
+
var overlaps = (b, c, margin) => Math.abs(b.center[0] - (c.x0 + c.x1) / 2) <= b.size[0] / 2 + (c.x1 - c.x0) / 2 + margin && Math.abs(b.center[1] - (c.y0 + c.y1) / 2) <= b.size[1] / 2 + (c.y1 - c.y0) / 2 + margin && Math.abs(b.center[2]) <= b.size[2] / 2 + c.z + margin;
|
|
15388
|
+
function cabinEnclosure(source, options = {}) {
|
|
15389
|
+
const c = source.vehicle;
|
|
15390
|
+
const cabin = cabinBoxOf(c);
|
|
15391
|
+
const node = c.vehicle;
|
|
15392
|
+
if (!cabin || !node?.localToWorld) return null;
|
|
15393
|
+
const state = source.state();
|
|
15394
|
+
if (state.destroyed) return null;
|
|
15395
|
+
const V3 = node.position.constructor;
|
|
15396
|
+
const Quat = node.quaternion.constructor;
|
|
15397
|
+
const mid = [(cabin.x0 + cabin.x1) / 2, (cabin.y0 + cabin.y1) / 2, 0];
|
|
15398
|
+
const half = [(cabin.x1 - cabin.x0) / 2, (cabin.y1 - cabin.y0) / 2, cabin.z];
|
|
15399
|
+
const ajar = options.ajar ?? 0.06;
|
|
15400
|
+
const byId = new Map(state.parts.map((p) => [p.id, p]));
|
|
15401
|
+
const mainGroup = state.groups[0] ? new Set(state.groups[0]) : null;
|
|
15402
|
+
const openings = [];
|
|
15403
|
+
let crackLength = 0;
|
|
15404
|
+
const toCabin = (b) => ({
|
|
15405
|
+
center: [b.center[0] - mid[0], b.center[1] - mid[1], b.center[2] - mid[2]],
|
|
15406
|
+
halfSize: [b.size[0] / 2 + THROUGH, b.size[1] / 2 + THROUGH, b.size[2] / 2 + THROUGH]
|
|
15407
|
+
});
|
|
15408
|
+
node.updateWorldMatrix?.(true, false);
|
|
15409
|
+
const frame = options.frame ?? c.root;
|
|
15410
|
+
frame?.updateWorldMatrix?.(true, false);
|
|
15411
|
+
const inverse = node.matrixWorld.clone().invert();
|
|
15412
|
+
for (const def of source.model.parts) {
|
|
15413
|
+
if (!SHELL_ROLES.has(def.role) || !overlaps(def.box, cabin, 0.12)) continue;
|
|
15414
|
+
const part = byId.get(def.id);
|
|
15415
|
+
if (!part) continue;
|
|
15416
|
+
const gone = GONE.has(part.status) || mainGroup !== null && !mainGroup.has(def.id);
|
|
15417
|
+
const swung = (def.role === "door" || def.role === "lid") && swingOf(c, def.id) > ajar;
|
|
15418
|
+
if (gone || swung) {
|
|
15419
|
+
openings.push(toCabin(def.box));
|
|
15420
|
+
continue;
|
|
15421
|
+
}
|
|
15422
|
+
for (const hole of part.holes) {
|
|
15423
|
+
if (!hole.through && def.material !== "glass") continue;
|
|
15424
|
+
const area3 = Math.PI * (hole.diameter / 2) ** 2;
|
|
15425
|
+
const p = new V3(hole.point[0], hole.point[1], hole.point[2]);
|
|
15426
|
+
if (frame?.localToWorld) frame.localToWorld(p);
|
|
15427
|
+
p.applyMatrix4(inverse);
|
|
15428
|
+
openings.push({
|
|
15429
|
+
center: [p.x - mid[0], p.y - mid[1], p.z - mid[2]],
|
|
15430
|
+
halfSize: [HOLE_PATCH, HOLE_PATCH, HOLE_PATCH],
|
|
15431
|
+
// the patch is far bigger than the hole: only a share of what crosses it passes, more for a bigger hole
|
|
15432
|
+
rate: Math.min(4, area3 * HOLE_RATE_PER_AREA)
|
|
15433
|
+
});
|
|
15434
|
+
}
|
|
15435
|
+
for (const crack of part.cracks) crackLength += crack.length;
|
|
15436
|
+
}
|
|
15437
|
+
const center = node.localToWorld(new V3(mid[0], mid[1], mid[2]));
|
|
15438
|
+
const q = node.getWorldQuaternion(new Quat());
|
|
15439
|
+
const scale = node.getWorldScale(new V3());
|
|
15440
|
+
const leak = (options.leak ?? CABIN_LEAK) * (1 + crackLength * CRACK_GAP_PER_METRE);
|
|
15441
|
+
return {
|
|
15442
|
+
center: [center.x, center.y, center.z],
|
|
15443
|
+
halfSize: [half[0] * Math.abs(scale.x), half[1] * Math.abs(scale.y), half[2] * Math.abs(scale.z)],
|
|
15444
|
+
orientation: [q.x, q.y, q.z, q.w],
|
|
15445
|
+
openings: openings.map((o) => ({ ...o, center: [o.center[0] * scale.x, o.center[1] * scale.y, o.center[2] * scale.z], halfSize: [o.halfSize[0] * Math.abs(scale.x), o.halfSize[1] * Math.abs(scale.y), o.halfSize[2] * Math.abs(scale.z)] })),
|
|
15446
|
+
leak
|
|
15447
|
+
};
|
|
15448
|
+
}
|
|
14519
15449
|
return __toCommonJS(index_exports);
|
|
14520
15450
|
})();
|
|
14521
15451
|
//# sourceMappingURL=index.global.js.map
|