@adatechnology/game-vehicles 0.5.0 → 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/dist/index.d.ts +66 -3
- package/dist/index.global.js +947 -105
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +829 -52
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.global.js
CHANGED
|
@@ -33,6 +33,8 @@ var GameVehicles = (() => {
|
|
|
33
33
|
cabinBoxOf: () => cabinBoxOf,
|
|
34
34
|
cabinEnclosure: () => cabinEnclosure,
|
|
35
35
|
clamp: () => clamp,
|
|
36
|
+
crackedGlassMaterial: () => crackedGlassMaterial,
|
|
37
|
+
createCreaseQueue: () => createCreaseQueue,
|
|
36
38
|
createLod: () => createLod,
|
|
37
39
|
createTranslate: () => createTranslate,
|
|
38
40
|
createVehicleDestructible: () => createVehicleDestructible,
|
|
@@ -55,7 +57,7 @@ var GameVehicles = (() => {
|
|
|
55
57
|
wrapThreeForLod: () => wrapThreeForLod
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
// ../game-shapes/dist/chunk-
|
|
60
|
+
// ../game-shapes/dist/chunk-IFXQIDSA.js
|
|
59
61
|
function canonicalJson(value) {
|
|
60
62
|
if (value === null) return "null";
|
|
61
63
|
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
@@ -1443,6 +1445,127 @@ var GameVehicles = (() => {
|
|
|
1443
1445
|
custom: { fields: { generator: str, params: () => null }, required: ["generator"] }
|
|
1444
1446
|
};
|
|
1445
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
|
+
|
|
1446
1569
|
// ../game-shapes-three/dist/index.js
|
|
1447
1570
|
var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
|
|
1448
1571
|
var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
|
|
@@ -2459,7 +2582,7 @@ var GameVehicles = (() => {
|
|
|
2459
2582
|
}
|
|
2460
2583
|
return ti.builtBlocks === total;
|
|
2461
2584
|
}
|
|
2462
|
-
function start(geo) {
|
|
2585
|
+
function start(geo, store = cache) {
|
|
2463
2586
|
const pos = geo.attributes.position;
|
|
2464
2587
|
const triangles = trianglesOf(geo);
|
|
2465
2588
|
const ti = {
|
|
@@ -2472,7 +2595,7 @@ var GameVehicles = (() => {
|
|
|
2472
2595
|
blocks: new Float64Array(blockCount(triangles) * 6),
|
|
2473
2596
|
builtBlocks: 0
|
|
2474
2597
|
};
|
|
2475
|
-
|
|
2598
|
+
store.set(geo, ti);
|
|
2476
2599
|
return ti;
|
|
2477
2600
|
}
|
|
2478
2601
|
function triIndexOf(geo) {
|
|
@@ -2524,6 +2647,106 @@ var GameVehicles = (() => {
|
|
|
2524
2647
|
}
|
|
2525
2648
|
return { box: box2, version: ti.positionVersion };
|
|
2526
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
|
+
}
|
|
2527
2750
|
|
|
2528
2751
|
// src/geometry.ts
|
|
2529
2752
|
function installGeometry(kit) {
|
|
@@ -3263,58 +3486,6 @@ var GameVehicles = (() => {
|
|
|
3263
3486
|
Object.assign(I, { roundedBoxGeometry, taper, subdivide, crease, creaseTask, refreshBounds, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject });
|
|
3264
3487
|
}
|
|
3265
3488
|
|
|
3266
|
-
// ../game-contracts/dist/index.js
|
|
3267
|
-
var DEFAULT_IGNITION_RULES = {
|
|
3268
|
-
fillCurve: [
|
|
3269
|
-
[0, 0.6],
|
|
3270
|
-
[0.1, 1],
|
|
3271
|
-
[0.35, 0.6],
|
|
3272
|
-
[0.65, 0.3],
|
|
3273
|
-
[0.9, 0.15],
|
|
3274
|
-
[1, 0.12]
|
|
3275
|
-
],
|
|
3276
|
-
flameFillInfluence: 0.5,
|
|
3277
|
-
flameDefaults: { incendiary: 0.7, tracer: 0.2 },
|
|
3278
|
-
sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
|
|
3279
|
-
sparkMetalDefault: 0.5,
|
|
3280
|
-
sparkFluid: { diesel: 0.15 },
|
|
3281
|
-
fireNear: { radius: 1.5, chancePerSecond: 0.5 }
|
|
3282
|
-
};
|
|
3283
|
-
var resolveIgnitionRules = (rules) => ({
|
|
3284
|
-
...DEFAULT_IGNITION_RULES,
|
|
3285
|
-
...rules,
|
|
3286
|
-
flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
|
|
3287
|
-
sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
|
|
3288
|
-
sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
|
|
3289
|
-
fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
|
|
3290
|
-
});
|
|
3291
|
-
var fillFactor = (fill, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
|
|
3292
|
-
if (curve.length === 0) return 1;
|
|
3293
|
-
const f = Math.min(1, Math.max(0, fill));
|
|
3294
|
-
if (f <= curve[0][0]) return curve[0][1];
|
|
3295
|
-
for (let i = 1; i < curve.length; i++) {
|
|
3296
|
-
const [x1, y1] = curve[i];
|
|
3297
|
-
const [x0, y0] = curve[i - 1];
|
|
3298
|
-
if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
|
|
3299
|
-
}
|
|
3300
|
-
return curve[curve.length - 1][1];
|
|
3301
|
-
};
|
|
3302
|
-
var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
|
|
3303
|
-
var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
|
|
3304
|
-
const k = fillFactor(target.fill, rules.fillCurve);
|
|
3305
|
-
const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
|
|
3306
|
-
const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
|
|
3307
|
-
const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
|
|
3308
|
-
return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
|
|
3309
|
-
};
|
|
3310
|
-
var KIT_ORIGIN_KEY = "kitOrigin";
|
|
3311
|
-
function markKitOrigin(object2, origin) {
|
|
3312
|
-
const node = object2;
|
|
3313
|
-
node.userData = node.userData ?? {};
|
|
3314
|
-
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
3315
|
-
return object2;
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
3489
|
// src/context.ts
|
|
3319
3490
|
function installContext(kit) {
|
|
3320
3491
|
const THREE = kit.THREE;
|
|
@@ -3908,6 +4079,7 @@ var GameVehicles = (() => {
|
|
|
3908
4079
|
}
|
|
3909
4080
|
kit.profX = profX;
|
|
3910
4081
|
const _sr = new THREE.Raycaster();
|
|
4082
|
+
_sr.layers.enableAll();
|
|
3911
4083
|
kit.surfaceAt = function(c, org, dir, o) {
|
|
3912
4084
|
o = o || {};
|
|
3913
4085
|
c.root.updateMatrixWorld(true);
|
|
@@ -3924,7 +4096,7 @@ var GameVehicles = (() => {
|
|
|
3924
4096
|
_sr.set(p0.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
|
|
3925
4097
|
_sr.near = 0;
|
|
3926
4098
|
_sr.far = o.max || 20;
|
|
3927
|
-
const h =
|
|
4099
|
+
const h = raycastIndexed(THREE, _sr, cands)[0];
|
|
3928
4100
|
if (!h) return null;
|
|
3929
4101
|
const n = h.face ? h.face.normal.clone().transformDirection(h.object.matrixWorld).transformDirection(inv) : d.clone().negate();
|
|
3930
4102
|
if (n.dot(d) > 0) n.negate();
|
|
@@ -3946,6 +4118,7 @@ var GameVehicles = (() => {
|
|
|
3946
4118
|
const V3 = THREE.Vector3;
|
|
3947
4119
|
const I = kit._;
|
|
3948
4120
|
const { MATERIALS, crease, cutGeometry, pathShape, subdivide, tumble, uvProject } = I;
|
|
4121
|
+
I.paneSeals = /* @__PURE__ */ new WeakMap();
|
|
3949
4122
|
kit.addGlass = function(c, geo, label, type, o) {
|
|
3950
4123
|
o = o || {};
|
|
3951
4124
|
if (!geo.attributes.uv || o.reuv) o.aspect = uvProject(geo, o.uvAxes);
|
|
@@ -4012,7 +4185,7 @@ var GameVehicles = (() => {
|
|
|
4012
4185
|
cy /= poly.length;
|
|
4013
4186
|
const base = I.resolveLabel(label ? label(i, polys.length) : { key: "window" });
|
|
4014
4187
|
const lab = { key: base.key, label: kit.t("parts.glass_side", { label: base.label, s }) };
|
|
4015
|
-
kit.addGlass(c, geo, lab, o.type || "tempered", {
|
|
4188
|
+
const pane = kit.addGlass(c, geo, lab, o.type || "tempered", {
|
|
4016
4189
|
mat: o.mat || (o.see === false ? MATERIALS.glassDark : MATERIALS.glass),
|
|
4017
4190
|
pos: new V3(cx, cy, s * I.halfAt(Wg, T, cy)),
|
|
4018
4191
|
outline: shp.getSpacedPoints(56),
|
|
@@ -4028,6 +4201,7 @@ var GameVehicles = (() => {
|
|
|
4028
4201
|
pts.pop();
|
|
4029
4202
|
const seal = c.tube(pts, 0.011, MATERIALS.trim, { closed: true, seg: 72, rs: 5 });
|
|
4030
4203
|
seal.castShadow = false;
|
|
4204
|
+
I.paneSeals.set(pane, seal);
|
|
4031
4205
|
});
|
|
4032
4206
|
});
|
|
4033
4207
|
};
|
|
@@ -5737,6 +5911,44 @@ var GameVehicles = (() => {
|
|
|
5737
5911
|
Object.assign(I, {});
|
|
5738
5912
|
}
|
|
5739
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
|
+
|
|
5740
5952
|
// src/particles.ts
|
|
5741
5953
|
function installParticles(kit) {
|
|
5742
5954
|
const THREE = kit.THREE;
|
|
@@ -5828,7 +6040,7 @@ var GameVehicles = (() => {
|
|
|
5828
6040
|
});
|
|
5829
6041
|
}
|
|
5830
6042
|
const list2 = fire ? FIRE_TEXTURES : SMOKE_TEXTURES;
|
|
5831
|
-
const
|
|
6043
|
+
const build = () => new THREE.SpriteMaterial({
|
|
5832
6044
|
map: list2[Math.floor(Math.random() * list2.length)],
|
|
5833
6045
|
color: new THREE.Color(col),
|
|
5834
6046
|
transparent: true,
|
|
@@ -5836,6 +6048,7 @@ var GameVehicles = (() => {
|
|
|
5836
6048
|
opacity: op,
|
|
5837
6049
|
blending: fire ? THREE.AdditiveBlending : THREE.NormalBlending
|
|
5838
6050
|
});
|
|
6051
|
+
const mat = kit.particleMaterials ? kit.particleMaterials.own(build) : build();
|
|
5839
6052
|
const dis = { value: 0 };
|
|
5840
6053
|
mat.userData.dis = dis;
|
|
5841
6054
|
mat.onBeforeCompile = function(sh) {
|
|
@@ -5863,11 +6076,37 @@ var GameVehicles = (() => {
|
|
|
5863
6076
|
function killParticle(c, i) {
|
|
5864
6077
|
const P = c.particles[i];
|
|
5865
6078
|
c.effects.remove(P.m);
|
|
5866
|
-
if (P.own)
|
|
6079
|
+
if (P.own) {
|
|
6080
|
+
if (kit.particleMaterials) kit.particleMaterials.release(P.m.material);
|
|
6081
|
+
else P.m.material.dispose();
|
|
6082
|
+
}
|
|
5867
6083
|
c.particles.splice(i, 1);
|
|
5868
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
|
+
}
|
|
5869
6105
|
kit.spawn = function(c, type, pos, vel, o) {
|
|
5870
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");
|
|
5871
6110
|
if (c.particles.length >= PARTICLE_LIMIT) {
|
|
5872
6111
|
const idx = c.particles.findIndex(function(q) {
|
|
5873
6112
|
return TRANSIENT_TYPES[q.type];
|
|
@@ -6139,6 +6378,7 @@ var GameVehicles = (() => {
|
|
|
6139
6378
|
};
|
|
6140
6379
|
kit.defineFluid("stain", { fresh: "#4a4a44", dry: "#26241f", wipeable: false });
|
|
6141
6380
|
const _fr = new THREE.Raycaster();
|
|
6381
|
+
_fr.layers.enableAll();
|
|
6142
6382
|
kit.splat = function(c, p, n, r, o) {
|
|
6143
6383
|
o = o || {};
|
|
6144
6384
|
n = n.clone().normalize();
|
|
@@ -6580,31 +6820,55 @@ var GameVehicles = (() => {
|
|
|
6580
6820
|
}
|
|
6581
6821
|
|
|
6582
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
|
+
}
|
|
6583
6836
|
function installGlassDamage(kit) {
|
|
6584
6837
|
const THREE = kit.THREE;
|
|
6585
6838
|
const V3 = THREE.Vector3;
|
|
6586
6839
|
const PI = Math.PI;
|
|
6587
6840
|
const I = kit._;
|
|
6588
6841
|
const document2 = kit.document;
|
|
6589
|
-
const {
|
|
6590
|
-
function glassUV(msh, pLocal) {
|
|
6842
|
+
const { MATERIALS, crease, cutGeometry, mergeGeometries, subdivide, tumble } = I;
|
|
6843
|
+
function glassUV(c, msh, pLocal) {
|
|
6591
6844
|
const pos = msh.geometry.attributes.position;
|
|
6592
6845
|
const uv = msh.geometry.attributes.uv;
|
|
6593
6846
|
if (!uv) return [0.5, 0.5];
|
|
6594
|
-
|
|
6595
|
-
const q = pLocal.clone()
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
const
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
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;
|
|
6606
6870
|
}
|
|
6607
|
-
return
|
|
6871
|
+
return out;
|
|
6608
6872
|
}
|
|
6609
6873
|
function drawWeb(x, cx, cy, s, ax, rnd) {
|
|
6610
6874
|
const n = 16 + Math.floor(rnd() * 8);
|
|
@@ -6681,19 +6945,9 @@ var GameVehicles = (() => {
|
|
|
6681
6945
|
x0.fillStyle = "rgba(48,66,72,0.5)";
|
|
6682
6946
|
x0.fillRect(0, 0, 256, 256);
|
|
6683
6947
|
G.tex = new THREE.CanvasTexture(G.cv);
|
|
6684
|
-
msh.material =
|
|
6685
|
-
color: 16777215,
|
|
6686
|
-
map: G.tex,
|
|
6687
|
-
emissive: 6976118,
|
|
6688
|
-
emissiveMap: G.tex,
|
|
6689
|
-
transparent: true,
|
|
6690
|
-
roughness: 0.25,
|
|
6691
|
-
metalness: 0.1,
|
|
6692
|
-
side: DOUBLE_SIDE,
|
|
6693
|
-
depthWrite: false
|
|
6694
|
-
});
|
|
6948
|
+
msh.material = crackedGlassMaterial(THREE, G.tex);
|
|
6695
6949
|
}
|
|
6696
|
-
const uv = glassUV(msh, p);
|
|
6950
|
+
const uv = glassUV(c, msh, p);
|
|
6697
6951
|
const x = G.cv.getContext("2d");
|
|
6698
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);
|
|
6699
6953
|
G.tex.needsUpdate = true;
|
|
@@ -6838,6 +7092,13 @@ var GameVehicles = (() => {
|
|
|
6838
7092
|
}
|
|
6839
7093
|
const seatGeo = [];
|
|
6840
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
|
+
}
|
|
6841
7102
|
cubesAt.forEach(function(p, i) {
|
|
6842
7103
|
if (i % 3 === 0 && c.seatTops.length && !c.rollState && !c.flipped) {
|
|
6843
7104
|
let best = null;
|
|
@@ -7040,6 +7301,7 @@ var GameVehicles = (() => {
|
|
|
7040
7301
|
polygonOffsetUnits: -2
|
|
7041
7302
|
});
|
|
7042
7303
|
const _ray = new THREE.Raycaster();
|
|
7304
|
+
_ray.layers.enableAll();
|
|
7043
7305
|
function findSurface(c, p, dir) {
|
|
7044
7306
|
c.root.updateMatrixWorld(true);
|
|
7045
7307
|
const Mw = c.vehicle.matrixWorld;
|
|
@@ -7121,6 +7383,7 @@ var GameVehicles = (() => {
|
|
|
7121
7383
|
const v0 = new V3();
|
|
7122
7384
|
const w = new V3();
|
|
7123
7385
|
const sc = new V3();
|
|
7386
|
+
const local = new V3();
|
|
7124
7387
|
const cab = m.cabin && !c.flipped && !c.rollState ? m.cabin : null;
|
|
7125
7388
|
c.deform.forEach(function(msh) {
|
|
7126
7389
|
if (!msh.parent || !msh.visible) return;
|
|
@@ -7135,7 +7398,15 @@ var GameVehicles = (() => {
|
|
|
7135
7398
|
irel.copy(rel).invert();
|
|
7136
7399
|
if (!g.userData.p0) g.userData.p0 = new Float32Array(pos.array);
|
|
7137
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;
|
|
7138
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;
|
|
7139
7410
|
v.fromBufferAttribute(pos, i).applyMatrix4(rel);
|
|
7140
7411
|
const a = dentField(v, H);
|
|
7141
7412
|
if (a <= 0) continue;
|
|
@@ -7151,10 +7422,15 @@ var GameVehicles = (() => {
|
|
|
7151
7422
|
}
|
|
7152
7423
|
if (changed) {
|
|
7153
7424
|
pos.needsUpdate = true;
|
|
7154
|
-
if (g.userData.crease)
|
|
7425
|
+
if (g.userData.crease && c.smoothLater) c.smoothLater(msh);
|
|
7426
|
+
else if (g.userData.crease) crease(g, 42);
|
|
7155
7427
|
else g.computeVertexNormals();
|
|
7156
|
-
g.computeBoundingSphere();
|
|
7157
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);
|
|
7158
7434
|
}
|
|
7159
7435
|
});
|
|
7160
7436
|
const dset = new Set(c.deform);
|
|
@@ -7238,13 +7514,14 @@ var GameVehicles = (() => {
|
|
|
7238
7514
|
kit.impact = impact;
|
|
7239
7515
|
kit.partEffects = {};
|
|
7240
7516
|
const _pr = new THREE.Raycaster();
|
|
7517
|
+
_pr.layers.enableAll();
|
|
7241
7518
|
function probe(c, o, d, far) {
|
|
7242
7519
|
c.root.updateMatrixWorld(true);
|
|
7243
7520
|
const Mw = c.vehicle.matrixWorld;
|
|
7244
7521
|
const inv = new THREE.Matrix4().copy(Mw).invert();
|
|
7245
7522
|
_pr.set(o.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
|
|
7246
7523
|
_pr.far = far || 10;
|
|
7247
|
-
const hits = _pr
|
|
7524
|
+
const hits = raycastIndexed(THREE, _pr, [c.vehicle], true);
|
|
7248
7525
|
for (let i = 0; i < hits.length; i++) {
|
|
7249
7526
|
const h = hits[i];
|
|
7250
7527
|
const ob = h.object;
|
|
@@ -8610,6 +8887,7 @@ var GameVehicles = (() => {
|
|
|
8610
8887
|
return hole;
|
|
8611
8888
|
}
|
|
8612
8889
|
const _zr = new THREE.Raycaster();
|
|
8890
|
+
_zr.layers.enableAll();
|
|
8613
8891
|
const PROXY_MIN_TRIANGLES = 500;
|
|
8614
8892
|
const STROKE_REACH = 0.6;
|
|
8615
8893
|
const _pv = new V3();
|
|
@@ -8844,6 +9122,7 @@ var GameVehicles = (() => {
|
|
|
8844
9122
|
return out;
|
|
8845
9123
|
}
|
|
8846
9124
|
const _rt = new THREE.Raycaster();
|
|
9125
|
+
_rt.layers.enableAll();
|
|
8847
9126
|
function scratchPath(c, s, len, ang, cands, B) {
|
|
8848
9127
|
const n = s.n.clone().normalize();
|
|
8849
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);
|
|
@@ -12125,6 +12404,8 @@ var GameVehicles = (() => {
|
|
|
12125
12404
|
V3: THREE.Vector3,
|
|
12126
12405
|
PI: Math.PI,
|
|
12127
12406
|
document: doc,
|
|
12407
|
+
particleMaterials: opts.particleMaterials,
|
|
12408
|
+
effects: opts.effects,
|
|
12128
12409
|
strings,
|
|
12129
12410
|
t,
|
|
12130
12411
|
matchers: createMatchers(strings),
|
|
@@ -12380,7 +12661,9 @@ var GameVehicles = (() => {
|
|
|
12380
12661
|
assignDeep(strings, resolveStrings(opts.locale));
|
|
12381
12662
|
return kit;
|
|
12382
12663
|
};
|
|
12383
|
-
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;
|
|
12384
12667
|
void _three;
|
|
12385
12668
|
void _lod;
|
|
12386
12669
|
void _locale;
|
|
@@ -12476,6 +12759,14 @@ var GameVehicles = (() => {
|
|
|
12476
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]],
|
|
12477
12760
|
size: [b.size[0] * 0.5, Math.min(0.14, b.size[1]), b.size[2] * 0.95]
|
|
12478
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
|
+
}
|
|
12479
12770
|
function buildVehicleModel(kit, c, options = {}) {
|
|
12480
12771
|
const kind = kindOf(c);
|
|
12481
12772
|
const m = c.meta;
|
|
@@ -12616,6 +12907,16 @@ var GameVehicles = (() => {
|
|
|
12616
12907
|
tags: [role, ...tags]
|
|
12617
12908
|
});
|
|
12618
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
|
+
}
|
|
12619
12920
|
if (!hubZone) {
|
|
12620
12921
|
const h = Math.max(0.15, belt - clearance);
|
|
12621
12922
|
add({
|
|
@@ -12951,6 +13252,76 @@ var GameVehicles = (() => {
|
|
|
12951
13252
|
}
|
|
12952
13253
|
|
|
12953
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
|
+
}
|
|
12954
13325
|
var VehicleVisuals = class {
|
|
12955
13326
|
constructor(kit, c, model) {
|
|
12956
13327
|
__publicField(this, "kit");
|
|
@@ -12968,14 +13339,23 @@ var GameVehicles = (() => {
|
|
|
12968
13339
|
__publicField(this, "syncPlates", /* @__PURE__ */ new Map());
|
|
12969
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). */
|
|
12970
13341
|
__publicField(this, "skins", /* @__PURE__ */ new Map());
|
|
13342
|
+
/** How high each door's cut reaches. */
|
|
13343
|
+
__publicField(this, "doorTops", /* @__PURE__ */ new Map());
|
|
12971
13344
|
__publicField(this, "lightsOn", true);
|
|
12972
13345
|
/** Heavy body meshes whose triangle index is still to build (null once done). */
|
|
12973
13346
|
__publicField(this, "warming");
|
|
12974
13347
|
/** A panel was torn off since the last `pump`: that frame already paid for the cut, the smoothing starts on the next one. */
|
|
12975
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);
|
|
12976
13354
|
/** Normal smoothing still running, by mesh: a mesh cut again gets a new task and the old one is dropped. */
|
|
12977
13355
|
__publicField(this, "smoothing", /* @__PURE__ */ new Map());
|
|
12978
13356
|
__publicField(this, "hood");
|
|
13357
|
+
;
|
|
13358
|
+
c.smoothLater = (mesh2) => this.smoothLater(mesh2);
|
|
12979
13359
|
this.kit = kit;
|
|
12980
13360
|
this.c = c;
|
|
12981
13361
|
this.model = model;
|
|
@@ -12984,6 +13364,8 @@ var GameVehicles = (() => {
|
|
|
12984
13364
|
for (const p of model.parts) this.byId.set(p.id, p);
|
|
12985
13365
|
c.root.updateMatrixWorld(true);
|
|
12986
13366
|
this.hood = this.nameHood();
|
|
13367
|
+
this.seatPanes();
|
|
13368
|
+
this.seatDoorHandles();
|
|
12987
13369
|
this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
|
|
12988
13370
|
if (!this.warming.length) this.warming = null;
|
|
12989
13371
|
for (const p of model.parts) {
|
|
@@ -13004,6 +13386,7 @@ var GameVehicles = (() => {
|
|
|
13004
13386
|
const id = z ? z.id : this.model.parts.find((p) => p.role === "hood")?.id;
|
|
13005
13387
|
if (!id) return null;
|
|
13006
13388
|
c.hood.piv.name = id;
|
|
13389
|
+
c.hood.piv.userData.plateOf = id;
|
|
13007
13390
|
return { id, open: c.hood.open, sign: c.hood.sign || 1 };
|
|
13008
13391
|
}
|
|
13009
13392
|
mirrorObjects(p) {
|
|
@@ -13091,6 +13474,7 @@ var GameVehicles = (() => {
|
|
|
13091
13474
|
recess.userData.noFollow = true;
|
|
13092
13475
|
const piv = new THREE.Group();
|
|
13093
13476
|
piv.name = p.id;
|
|
13477
|
+
piv.userData.plateOf = p.id;
|
|
13094
13478
|
piv.position.set(hx, cy, sk + s * 0.016);
|
|
13095
13479
|
piv.userData.rigid = true;
|
|
13096
13480
|
this.c.vehicle.add(piv);
|
|
@@ -13107,6 +13491,7 @@ var GameVehicles = (() => {
|
|
|
13107
13491
|
recess.userData.noFollow = true;
|
|
13108
13492
|
const piv = new THREE.Group();
|
|
13109
13493
|
piv.name = p.id;
|
|
13494
|
+
piv.userData.plateOf = p.id;
|
|
13110
13495
|
piv.position.set(sk - 0.016, B.y1 - 0.05, 0);
|
|
13111
13496
|
piv.userData.rigid = true;
|
|
13112
13497
|
this.c.vehicle.add(piv);
|
|
@@ -13170,10 +13555,19 @@ var GameVehicles = (() => {
|
|
|
13170
13555
|
try {
|
|
13171
13556
|
if (p.role === "door") {
|
|
13172
13557
|
const dirty = /* @__PURE__ */ new Set();
|
|
13173
|
-
const
|
|
13174
|
-
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);
|
|
13175
13564
|
this.mend(dirty);
|
|
13176
|
-
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
|
+
}
|
|
13177
13571
|
} else {
|
|
13178
13572
|
const pcs = this.cutZone(z);
|
|
13179
13573
|
if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv)];
|
|
@@ -13191,10 +13585,124 @@ var GameVehicles = (() => {
|
|
|
13191
13585
|
const di = deform.indexOf(o);
|
|
13192
13586
|
if (di >= 0) deform.splice(di, 1);
|
|
13193
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
|
+
}
|
|
13194
13593
|
piv.quaternion.copy(q);
|
|
13195
13594
|
this.c.root.updateMatrixWorld(true);
|
|
13196
13595
|
return objs.length > 0;
|
|
13197
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
|
+
}
|
|
13198
13706
|
mend(dirty) {
|
|
13199
13707
|
dirty.forEach((msh) => {
|
|
13200
13708
|
const g = msh.geometry;
|
|
@@ -13204,7 +13712,191 @@ var GameVehicles = (() => {
|
|
|
13204
13712
|
g.computeBoundingBox();
|
|
13205
13713
|
});
|
|
13206
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
|
+
}
|
|
13207
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
|
+
}
|
|
13208
13900
|
const [cx, cy, cz] = p.box.center;
|
|
13209
13901
|
const s = cz < 0 ? -1 : 1;
|
|
13210
13902
|
const mats = this.I.MATERIALS;
|
|
@@ -13245,17 +13937,28 @@ var GameVehicles = (() => {
|
|
|
13245
13937
|
}
|
|
13246
13938
|
// ---- marks -------------------------------------------------------------------------------------------------------------------------------
|
|
13247
13939
|
/** A bullet hole: a dark disc lying on the surface at `point` (`c.vehicle` coordinates). */
|
|
13248
|
-
holeDecal(point, normal, diameter) {
|
|
13940
|
+
holeDecal(point, normal, diameter, part) {
|
|
13249
13941
|
const THREE = this.THREE;
|
|
13250
|
-
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);
|
|
13251
13947
|
const n = new THREE.Vector3(...normal);
|
|
13252
|
-
m.position.set(...point).addScaledVector(n,
|
|
13253
|
-
|
|
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)));
|
|
13254
13951
|
m.userData.decal = true;
|
|
13255
13952
|
m.userData.noFollow = true;
|
|
13953
|
+
m.renderOrder = 3;
|
|
13256
13954
|
m.raycast = () => {
|
|
13257
13955
|
};
|
|
13258
|
-
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;
|
|
13259
13962
|
}
|
|
13260
13963
|
spark(point, dir, count = 8) {
|
|
13261
13964
|
const V3 = this.THREE.Vector3;
|
|
@@ -13295,10 +13998,8 @@ var GameVehicles = (() => {
|
|
|
13295
13998
|
}
|
|
13296
13999
|
/** Glass: cracks (laminated) or shatters (tempered); returns whether it broke. */
|
|
13297
14000
|
hitGlass(p, point, dir, force) {
|
|
13298
|
-
const z = this.zone(p);
|
|
13299
|
-
if (!z) return false;
|
|
13300
14001
|
const V3 = this.THREE.Vector3;
|
|
13301
|
-
const list2 =
|
|
14002
|
+
const list2 = this.panesOf(p).filter((g) => g.parent && g.visible);
|
|
13302
14003
|
if (!list2.length) return false;
|
|
13303
14004
|
const pt = new V3(...point);
|
|
13304
14005
|
let best = list2[0];
|
|
@@ -13315,9 +14016,16 @@ var GameVehicles = (() => {
|
|
|
13315
14016
|
this.kit.hitGlass(this.c, best, pt, new V3(...dir), force);
|
|
13316
14017
|
return !!best.userData.glass?.broken;
|
|
13317
14018
|
}
|
|
14019
|
+
panesOf(p) {
|
|
14020
|
+
return p.panes ?? this.zone(p)?.glass ?? [];
|
|
14021
|
+
}
|
|
13318
14022
|
glassBreak(p) {
|
|
13319
14023
|
const z = this.zone(p);
|
|
13320
|
-
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
|
+
}
|
|
13321
14029
|
for (const g of z.glass) if (g.parent && g.visible) this.I.glassBand(this.c, z, g, 4);
|
|
13322
14030
|
this.gone.add(p.id);
|
|
13323
14031
|
}
|
|
@@ -13345,6 +14053,7 @@ var GameVehicles = (() => {
|
|
|
13345
14053
|
smoothLater(msh) {
|
|
13346
14054
|
this.smoothing.set(msh, this.I.creaseTask(msh.geometry, 42));
|
|
13347
14055
|
this.tornThisFrame = true;
|
|
14056
|
+
this.holdArmed = true;
|
|
13348
14057
|
}
|
|
13349
14058
|
/** Spends at most `budgetMs` finishing the pending normal smoothing. Call once per frame. */
|
|
13350
14059
|
pump(budgetMs = 2.5) {
|
|
@@ -13352,6 +14061,7 @@ var GameVehicles = (() => {
|
|
|
13352
14061
|
this.tornThisFrame = false;
|
|
13353
14062
|
return;
|
|
13354
14063
|
}
|
|
14064
|
+
if (this.queued) return;
|
|
13355
14065
|
if (!this.smoothing.size && !this.warming) return;
|
|
13356
14066
|
const deadline = performance.now() + budgetMs;
|
|
13357
14067
|
for (const [msh, task] of this.smoothing) {
|
|
@@ -13360,6 +14070,34 @@ var GameVehicles = (() => {
|
|
|
13360
14070
|
}
|
|
13361
14071
|
this.warmIndexes(deadline);
|
|
13362
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
|
+
}
|
|
13363
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`). */
|
|
13364
14102
|
warmIndexes(deadline) {
|
|
13365
14103
|
if (!this.warming) return;
|
|
@@ -13531,6 +14269,7 @@ var GameVehicles = (() => {
|
|
|
13531
14269
|
}
|
|
13532
14270
|
pieceTasks.forEach((task, mesh2) => this.smoothing.set(mesh2, task));
|
|
13533
14271
|
this.tornThisFrame = true;
|
|
14272
|
+
this.holdArmed = true;
|
|
13534
14273
|
dirty.forEach((msh) => {
|
|
13535
14274
|
const g = msh.geometry;
|
|
13536
14275
|
if (g.userData.crease || !g.index) this.smoothLater(msh);
|
|
@@ -13637,6 +14376,7 @@ var GameVehicles = (() => {
|
|
|
13637
14376
|
let clock = 0;
|
|
13638
14377
|
let model;
|
|
13639
14378
|
let vis = null;
|
|
14379
|
+
let releaseCrease;
|
|
13640
14380
|
const ps = /* @__PURE__ */ new Map();
|
|
13641
14381
|
let js = [];
|
|
13642
14382
|
let leaks = /* @__PURE__ */ new Map();
|
|
@@ -13721,7 +14461,9 @@ var GameVehicles = (() => {
|
|
|
13721
14461
|
rand = rng(seedBase);
|
|
13722
14462
|
igniteRand = rng(strHash(`${c.key}|${id}|ignition|${options.seed ?? 0}`));
|
|
13723
14463
|
steerSign = rand() < 0.5 ? -1 : 1;
|
|
14464
|
+
releaseCrease?.();
|
|
13724
14465
|
vis = headless ? null : new VehicleVisuals(kit, c, model);
|
|
14466
|
+
releaseCrease = vis && options.creaseQueue ? options.creaseQueue.track(vis) : void 0;
|
|
13725
14467
|
recomputeGroups();
|
|
13726
14468
|
};
|
|
13727
14469
|
const recomputeGroups = () => {
|
|
@@ -14032,13 +14774,13 @@ var GameVehicles = (() => {
|
|
|
14032
14774
|
sync();
|
|
14033
14775
|
const M = toFrameMatrix();
|
|
14034
14776
|
const v = toLocal(h.point);
|
|
14035
|
-
const p = locate(v, 0.2);
|
|
14777
|
+
const p = (h.part ? ps.get(h.part) : void 0) ?? locate(v, 0.2);
|
|
14036
14778
|
if (!p) return;
|
|
14037
14779
|
const d = p.def;
|
|
14038
14780
|
const E = h.energy ?? 0;
|
|
14039
14781
|
const cal = h.caliber ?? 9;
|
|
14040
14782
|
const dir = dirToLocal(h.direction ?? [0, 0, -1]).normalize();
|
|
14041
|
-
const n = normalOf(d, v);
|
|
14783
|
+
const n = h.normal ? dirToLocal(h.normal).normalize() : normalOf(d, v);
|
|
14042
14784
|
const res = resist(d, p);
|
|
14043
14785
|
const through = E >= res;
|
|
14044
14786
|
const cosInc = Math.abs(dir.dot(n));
|
|
@@ -14057,7 +14799,7 @@ var GameVehicles = (() => {
|
|
|
14057
14799
|
const hp = { point: r3(fromVehicle(v, M)), normal: r3(fromVehicle(n, new THREE.Matrix4().extractRotation(M))), diameter: r2(holeDiam), through };
|
|
14058
14800
|
p.holes.push(hp);
|
|
14059
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])))))) } : {} }));
|
|
14060
|
-
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);
|
|
14061
14803
|
startLeak(p, v, cal, out, dir);
|
|
14062
14804
|
};
|
|
14063
14805
|
switch (d.material) {
|
|
@@ -14358,6 +15100,46 @@ var GameVehicles = (() => {
|
|
|
14358
15100
|
for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
|
|
14359
15101
|
return false;
|
|
14360
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
|
+
};
|
|
14361
15143
|
const hit = (h) => {
|
|
14362
15144
|
stateCache = void 0;
|
|
14363
15145
|
const out = [];
|
|
@@ -14480,9 +15262,14 @@ var GameVehicles = (() => {
|
|
|
14480
15262
|
},
|
|
14481
15263
|
describe,
|
|
14482
15264
|
hit,
|
|
15265
|
+
surfaceRay,
|
|
14483
15266
|
update,
|
|
14484
15267
|
state,
|
|
14485
15268
|
reset,
|
|
15269
|
+
dispose: () => {
|
|
15270
|
+
releaseCrease?.();
|
|
15271
|
+
releaseCrease = void 0;
|
|
15272
|
+
},
|
|
14486
15273
|
disassemble,
|
|
14487
15274
|
extinguish,
|
|
14488
15275
|
serialize: () => ({
|
|
@@ -14521,6 +15308,61 @@ var GameVehicles = (() => {
|
|
|
14521
15308
|
return { root, vehicle };
|
|
14522
15309
|
}
|
|
14523
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
|
+
|
|
14524
15366
|
// src/destructible/enclosure.ts
|
|
14525
15367
|
var CABIN_LEAK = 0.02;
|
|
14526
15368
|
var HOLE_RATE_PER_AREA = 3e3;
|