@adatechnology/game-destruction-three 0.7.2 → 0.8.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 +10 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.global.js +558 -144
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +282 -49
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.global.js
CHANGED
|
@@ -35,6 +35,24 @@ var GameDestructionThree = (() => {
|
|
|
35
35
|
specFromObject3D: () => specFromObject3D
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
// ../game-contracts/dist/index.js
|
|
39
|
+
var DEFAULT_BLAST_SHOVE = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 };
|
|
40
|
+
function blastShove(blast, target, options = {}) {
|
|
41
|
+
const o = { ...DEFAULT_BLAST_SHOVE, ...options };
|
|
42
|
+
const dx = target.position[0] - blast.point[0];
|
|
43
|
+
const dy = target.position[1] - blast.point[1];
|
|
44
|
+
const dz = target.position[2] - blast.point[2];
|
|
45
|
+
const distance = Math.hypot(dx, dy, dz);
|
|
46
|
+
if (!(blast.radius > 0) || distance > blast.radius) return void 0;
|
|
47
|
+
const falloff = (1 - distance / blast.radius) ** o.falloffPower;
|
|
48
|
+
const mass = Math.max(target.mass, 0.05);
|
|
49
|
+
const speed = Math.min(o.maxSpeed, Math.sqrt(2 * blast.energy * (blast.reach ?? 1) * o.coupling / mass)) * falloff;
|
|
50
|
+
if (!(speed >= o.minSpeed)) return void 0;
|
|
51
|
+
const direction = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0];
|
|
52
|
+
const length = Math.hypot(direction[0], direction[1], direction[2]);
|
|
53
|
+
return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff };
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
// ../game-vfx/dist/index.js
|
|
39
57
|
var __defProp2 = Object.defineProperty;
|
|
40
58
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -3085,6 +3103,17 @@ var GameDestructionThree = (() => {
|
|
|
3085
3103
|
])
|
|
3086
3104
|
]
|
|
3087
3105
|
};
|
|
3106
|
+
var FUEL_LEAK_ONLY = {
|
|
3107
|
+
id: "fuel_leak_only",
|
|
3108
|
+
pageId: "vazamento",
|
|
3109
|
+
team: "vehicles",
|
|
3110
|
+
budget: { particles: 200, lights: 0 },
|
|
3111
|
+
loop: 11,
|
|
3112
|
+
view: FUEL_LEAK.view,
|
|
3113
|
+
setup: FUEL_LEAK.setup,
|
|
3114
|
+
timeline: [decal(POOL[0], 0, POOL[2], 2.2, 2040368, { grow: 3, life: 60, ar: 0.6, fluid: "oil" })],
|
|
3115
|
+
tick: [emit("drip", "normal", 12, { p: TANK, box: [0.05, 0, 0.05], sp: 0, life: 1, size: 0.03, c0: 5921402, a: 0.9, g: 9.8, fi: 0, gnd: 1 })]
|
|
3116
|
+
};
|
|
3088
3117
|
var CAR_EXPLOSION = {
|
|
3089
3118
|
id: "car_explosion",
|
|
3090
3119
|
pageId: "explosao",
|
|
@@ -3438,6 +3467,8 @@ var GameDestructionThree = (() => {
|
|
|
3438
3467
|
];
|
|
3439
3468
|
var PI = Math.PI;
|
|
3440
3469
|
var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
|
|
3470
|
+
var DEFAULT_SMOKE = { puffs: 2, radius: 0.01, opacity: 0.14, life: 0.7, drift: 0.2, rise: 0.06, growth: 2.1, flash: 1 };
|
|
3471
|
+
var DEFAULT_SPREAD = { drift: 0.1, rise: 0.04, side: 0.06 };
|
|
3441
3472
|
var FLASH_SECONDS = 0.055;
|
|
3442
3473
|
var FLASH_RAMP = [0.6, 0.9];
|
|
3443
3474
|
var FLASH_OPACITY = 0.95;
|
|
@@ -3445,8 +3476,10 @@ var GameDestructionThree = (() => {
|
|
|
3445
3476
|
var FLAME_ADDITIVE = 3;
|
|
3446
3477
|
var ORIGIN2 = [0, 0, 0];
|
|
3447
3478
|
var SIZE = variable("size");
|
|
3479
|
+
var FLASH_SCALE = variable("flash");
|
|
3480
|
+
var fallback = (name, defaultValue) => ite(ne(variable(name), void 0), variable(name), defaultValue);
|
|
3448
3481
|
var wide = (width) => mul(sub(rand(), 0.5), width);
|
|
3449
|
-
var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
|
|
3482
|
+
var puff2 = (point, velocity, world, radius, life, opacity, growth = SMOKE.growth) => mesh({
|
|
3450
3483
|
shape: "sphere",
|
|
3451
3484
|
p: point,
|
|
3452
3485
|
v: velocity,
|
|
@@ -3461,7 +3494,7 @@ var GameDestructionThree = (() => {
|
|
|
3461
3494
|
lit: true,
|
|
3462
3495
|
g: SMOKE.gravity,
|
|
3463
3496
|
drag: SMOKE.drag,
|
|
3464
|
-
grow:
|
|
3497
|
+
grow: growth
|
|
3465
3498
|
});
|
|
3466
3499
|
var flameBit = (point, velocity, world, radius, life) => [
|
|
3467
3500
|
set("bitPoint", point),
|
|
@@ -3494,37 +3527,55 @@ var GameDestructionThree = (() => {
|
|
|
3494
3527
|
id: "viewmodel_muzzle_flash",
|
|
3495
3528
|
pageId: "event:viewmodel_muzzle_flash",
|
|
3496
3529
|
team: "weapons",
|
|
3497
|
-
budget: { particles:
|
|
3530
|
+
budget: { particles: 14, lights: 1 },
|
|
3498
3531
|
loop: 0.1,
|
|
3532
|
+
setup: [
|
|
3533
|
+
set("size", fallback("size", 1)),
|
|
3534
|
+
set("puffs", fallback("puffs", DEFAULT_SMOKE.puffs)),
|
|
3535
|
+
set("radius", fallback("radius", DEFAULT_SMOKE.radius)),
|
|
3536
|
+
set("opacity", fallback("opacity", DEFAULT_SMOKE.opacity)),
|
|
3537
|
+
set("life", fallback("life", DEFAULT_SMOKE.life)),
|
|
3538
|
+
set("drift", fallback("drift", DEFAULT_SMOKE.drift)),
|
|
3539
|
+
set("rise", fallback("rise", DEFAULT_SMOKE.rise)),
|
|
3540
|
+
set("growth", fallback("growth", DEFAULT_SMOKE.growth)),
|
|
3541
|
+
set("flash", fallback("flash", DEFAULT_SMOKE.flash)),
|
|
3542
|
+
set("driftSpread", fallback("driftSpread", DEFAULT_SPREAD.drift)),
|
|
3543
|
+
set("riseSpread", fallback("riseSpread", DEFAULT_SPREAD.rise)),
|
|
3544
|
+
set("sideSpread", fallback("sideSpread", DEFAULT_SPREAD.side)),
|
|
3545
|
+
set("lifeSpread", ite(ne(variable("lifeSpread"), void 0), variable("lifeSpread"), mul(variable("life"), 0.3)))
|
|
3546
|
+
],
|
|
3499
3547
|
timeline: [
|
|
3500
3548
|
set("angles", []),
|
|
3501
3549
|
repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
|
|
3502
3550
|
set("roll", mul(rand(), PI)),
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3551
|
+
when(gt(FLASH_SCALE, 0), [
|
|
3552
|
+
flash(ORIGIN2, 16756832, mul(4, SIZE, FLASH_SCALE), FLASH_SECONDS, mul(2, SIZE, FLASH_SCALE), true, true),
|
|
3553
|
+
flashPiece("cone_open", mul(0.013, SIZE, FLASH_SCALE), mul(0.085, SIZE, FLASH_SCALE), 10, [mul(0.042, SIZE, FLASH_SCALE), 0, 0], [0, 0, -PI / 2], 16757575),
|
|
3554
|
+
repeat(
|
|
3555
|
+
4,
|
|
3556
|
+
set("angle", pick(variable("angles"), INDEX)),
|
|
3557
|
+
flashPiece(
|
|
3558
|
+
"cone",
|
|
3559
|
+
mul(5e-3, SIZE, FLASH_SCALE),
|
|
3560
|
+
mul(0.04, SIZE, FLASH_SCALE),
|
|
3561
|
+
6,
|
|
3562
|
+
[mul(0.012, SIZE, FLASH_SCALE), mul(cos(variable("angle")), 0.016, SIZE, FLASH_SCALE), mul(sin(variable("angle")), 0.016, SIZE, FLASH_SCALE)],
|
|
3563
|
+
[variable("angle"), 0, -PI / 2],
|
|
3564
|
+
16757575
|
|
3565
|
+
)
|
|
3566
|
+
),
|
|
3567
|
+
flashPiece("sphere", mul(0.016, SIZE, FLASH_SCALE), void 0, 12, [mul(8e-3, SIZE, FLASH_SCALE), 0, 0], [0, 0, 0], 16773568)
|
|
3568
|
+
]),
|
|
3519
3569
|
repeat(
|
|
3520
|
-
|
|
3570
|
+
variable("puffs"),
|
|
3521
3571
|
puff2(
|
|
3522
3572
|
ORIGIN2,
|
|
3523
|
-
[add(
|
|
3573
|
+
[add(variable("drift"), mul(rand(), variable("driftSpread"))), add(variable("rise"), mul(rand(), variable("riseSpread"))), mul(sub(rand(), 0.5), variable("sideSpread"))],
|
|
3524
3574
|
[0, 0, 0],
|
|
3525
|
-
mul(
|
|
3526
|
-
add(
|
|
3527
|
-
|
|
3575
|
+
mul(variable("radius"), SIZE),
|
|
3576
|
+
add(variable("life"), mul(rand(), variable("lifeSpread"))),
|
|
3577
|
+
variable("opacity"),
|
|
3578
|
+
variable("growth")
|
|
3528
3579
|
)
|
|
3529
3580
|
)
|
|
3530
3581
|
]
|
|
@@ -3572,9 +3623,20 @@ var GameDestructionThree = (() => {
|
|
|
3572
3623
|
]
|
|
3573
3624
|
};
|
|
3574
3625
|
var VIEWMODEL_EFFECTS = [MUZZLE_FLASH2, SMOKE_PUFF, BACKBLAST, FLAME_JET];
|
|
3626
|
+
var aliasOf = (base, id, changes) => ({ ...base, id, pageId: `vehicle:${id}`, anchor: CAR_ORIGIN2, ...changes });
|
|
3627
|
+
var VEHICLE_EFFECTS = [
|
|
3628
|
+
/** The engine on fire: `engine_smoke` at its worst condition (black smoke, flames in the hood, a light). */
|
|
3629
|
+
aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
|
|
3630
|
+
/** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
|
|
3631
|
+
aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
|
|
3632
|
+
/** A hole in the tank: the page's petrol leak without the fire and the blast (the pool and the drips). A normal bullet does only this. */
|
|
3633
|
+
aliasOf(FUEL_LEAK_ONLY, "vehicle_leak", {}),
|
|
3634
|
+
/** The leak caught fire (an incendiary or tracer round, a spark, a fire near): the page's whole sequence, the fire running along the pool to the tank and the blast. */
|
|
3635
|
+
aliasOf(FUEL_LEAK, "vehicle_leak_ignited", {})
|
|
3636
|
+
];
|
|
3575
3637
|
var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
|
|
3576
3638
|
var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
|
|
3577
|
-
var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS].map((effect) => [effect.id, effect])) };
|
|
3639
|
+
var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
|
|
3578
3640
|
var LEHMER_MULTIPLIER = 16807;
|
|
3579
3641
|
var LEHMER_MODULUS = 2147483647;
|
|
3580
3642
|
var HASH_MIX = 2654435761;
|
|
@@ -7743,6 +7805,79 @@ void main(){
|
|
|
7743
7805
|
}
|
|
7744
7806
|
};
|
|
7745
7807
|
}
|
|
7808
|
+
var SPHERE_DEFAULT_WIDTH = 8;
|
|
7809
|
+
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
7810
|
+
function geometryOf(THREE, particle) {
|
|
7811
|
+
switch (particle.shape) {
|
|
7812
|
+
case "cone":
|
|
7813
|
+
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
|
|
7814
|
+
case "cone_open":
|
|
7815
|
+
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
|
|
7816
|
+
case "sphere":
|
|
7817
|
+
return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
7818
|
+
case "icosahedron":
|
|
7819
|
+
return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
function materialOf(THREE, particle) {
|
|
7823
|
+
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
7824
|
+
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
7825
|
+
}
|
|
7826
|
+
function createMeshLayerProbes(THREE) {
|
|
7827
|
+
const geometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
|
|
7828
|
+
return [{ lit: true, additive: false }, { lit: false, additive: false }, { lit: false, additive: true }].map(
|
|
7829
|
+
(kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
|
|
7830
|
+
);
|
|
7831
|
+
}
|
|
7832
|
+
function createMeshLayer(THREE, pool) {
|
|
7833
|
+
const group = new THREE.Group();
|
|
7834
|
+
group.name = "workshop-meshes";
|
|
7835
|
+
const drawn = /* @__PURE__ */ new Map();
|
|
7836
|
+
const frame = new THREE.Quaternion();
|
|
7837
|
+
const euler = new THREE.Euler();
|
|
7838
|
+
const turn = new THREE.Quaternion();
|
|
7839
|
+
const offset = new THREE.Vector3();
|
|
7840
|
+
function release(mesh2) {
|
|
7841
|
+
mesh2.removeFromParent();
|
|
7842
|
+
mesh2.geometry.dispose();
|
|
7843
|
+
mesh2.material.dispose();
|
|
7844
|
+
}
|
|
7845
|
+
return {
|
|
7846
|
+
group,
|
|
7847
|
+
sync() {
|
|
7848
|
+
const live = /* @__PURE__ */ new Set();
|
|
7849
|
+
for (const particle of pool.particles) {
|
|
7850
|
+
live.add(particle.id);
|
|
7851
|
+
let mesh2 = drawn.get(particle.id);
|
|
7852
|
+
if (!mesh2) {
|
|
7853
|
+
mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
|
|
7854
|
+
mesh2.frustumCulled = false;
|
|
7855
|
+
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7856
|
+
drawn.set(particle.id, mesh2);
|
|
7857
|
+
group.add(mesh2);
|
|
7858
|
+
}
|
|
7859
|
+
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
7860
|
+
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
7861
|
+
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
7862
|
+
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
7863
|
+
mesh2.quaternion.copy(frame).multiply(turn);
|
|
7864
|
+
mesh2.scale.setScalar(particle.scale);
|
|
7865
|
+
mesh2.material.opacity = particle.opacity;
|
|
7866
|
+
}
|
|
7867
|
+
for (const [id, mesh2] of drawn) {
|
|
7868
|
+
if (live.has(id)) continue;
|
|
7869
|
+
release(mesh2);
|
|
7870
|
+
drawn.delete(id);
|
|
7871
|
+
}
|
|
7872
|
+
return pool.count;
|
|
7873
|
+
},
|
|
7874
|
+
dispose() {
|
|
7875
|
+
for (const mesh2 of drawn.values()) release(mesh2);
|
|
7876
|
+
drawn.clear();
|
|
7877
|
+
group.removeFromParent();
|
|
7878
|
+
}
|
|
7879
|
+
};
|
|
7880
|
+
}
|
|
7746
7881
|
var clamp5 = (value, min2, max2) => value < min2 ? min2 : value > max2 ? max2 : value;
|
|
7747
7882
|
function layer(container, name, blend) {
|
|
7748
7883
|
const element = container.ownerDocument.createElement("div");
|
|
@@ -7912,73 +8047,6 @@ void main() {
|
|
|
7912
8047
|
}
|
|
7913
8048
|
};
|
|
7914
8049
|
}
|
|
7915
|
-
var SPHERE_DEFAULT_WIDTH = 8;
|
|
7916
|
-
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
7917
|
-
function geometryOf(THREE, particle) {
|
|
7918
|
-
switch (particle.shape) {
|
|
7919
|
-
case "cone":
|
|
7920
|
-
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
|
|
7921
|
-
case "cone_open":
|
|
7922
|
-
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
|
|
7923
|
-
case "sphere":
|
|
7924
|
-
return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
7925
|
-
case "icosahedron":
|
|
7926
|
-
return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
|
|
7927
|
-
}
|
|
7928
|
-
}
|
|
7929
|
-
function materialOf(THREE, particle) {
|
|
7930
|
-
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
7931
|
-
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
7932
|
-
}
|
|
7933
|
-
function createMeshLayer(THREE, pool) {
|
|
7934
|
-
const group = new THREE.Group();
|
|
7935
|
-
group.name = "workshop-meshes";
|
|
7936
|
-
const drawn = /* @__PURE__ */ new Map();
|
|
7937
|
-
const frame = new THREE.Quaternion();
|
|
7938
|
-
const euler = new THREE.Euler();
|
|
7939
|
-
const turn = new THREE.Quaternion();
|
|
7940
|
-
const offset = new THREE.Vector3();
|
|
7941
|
-
function release(mesh2) {
|
|
7942
|
-
mesh2.removeFromParent();
|
|
7943
|
-
mesh2.geometry.dispose();
|
|
7944
|
-
mesh2.material.dispose();
|
|
7945
|
-
}
|
|
7946
|
-
return {
|
|
7947
|
-
group,
|
|
7948
|
-
sync() {
|
|
7949
|
-
const live = /* @__PURE__ */ new Set();
|
|
7950
|
-
for (const particle of pool.particles) {
|
|
7951
|
-
live.add(particle.id);
|
|
7952
|
-
let mesh2 = drawn.get(particle.id);
|
|
7953
|
-
if (!mesh2) {
|
|
7954
|
-
mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
|
|
7955
|
-
mesh2.frustumCulled = false;
|
|
7956
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7957
|
-
drawn.set(particle.id, mesh2);
|
|
7958
|
-
group.add(mesh2);
|
|
7959
|
-
}
|
|
7960
|
-
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
7961
|
-
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
7962
|
-
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
7963
|
-
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
7964
|
-
mesh2.quaternion.copy(frame).multiply(turn);
|
|
7965
|
-
mesh2.scale.setScalar(particle.scale);
|
|
7966
|
-
mesh2.material.opacity = particle.opacity;
|
|
7967
|
-
}
|
|
7968
|
-
for (const [id, mesh2] of drawn) {
|
|
7969
|
-
if (live.has(id)) continue;
|
|
7970
|
-
release(mesh2);
|
|
7971
|
-
drawn.delete(id);
|
|
7972
|
-
}
|
|
7973
|
-
return pool.count;
|
|
7974
|
-
},
|
|
7975
|
-
dispose() {
|
|
7976
|
-
for (const mesh2 of drawn.values()) release(mesh2);
|
|
7977
|
-
drawn.clear();
|
|
7978
|
-
group.removeFromParent();
|
|
7979
|
-
}
|
|
7980
|
-
};
|
|
7981
|
-
}
|
|
7982
8050
|
var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
|
|
7983
8051
|
void main(){vC=pcol;vA=alpha;vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=clamp(size*uScale/-mv.z,1.,512.);gl_Position=projectionMatrix*mv;}`;
|
|
7984
8052
|
var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
|
|
@@ -8343,6 +8411,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8343
8411
|
const requested = options.vfx;
|
|
8344
8412
|
const world = isWorld(requested) ? requested : createVfx({ profile: options.quality ?? "medium", reducedMotion, ...requested });
|
|
8345
8413
|
const pools = world.pools();
|
|
8414
|
+
let anchors;
|
|
8415
|
+
const releaseAnchors = () => {
|
|
8416
|
+
if (!anchors) return;
|
|
8417
|
+
anchors.meshes[0]?.geometry.dispose();
|
|
8418
|
+
for (const mesh2 of anchors.meshes) mesh2.material.dispose();
|
|
8419
|
+
anchors = void 0;
|
|
8420
|
+
};
|
|
8346
8421
|
const root = new THREE.Group();
|
|
8347
8422
|
options.scene.add(root);
|
|
8348
8423
|
const direction = new THREE.Vector3(...options.lightDirection ?? LAB_LIGHT_DIRECTION).normalize();
|
|
@@ -8459,6 +8534,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8459
8534
|
syncAll();
|
|
8460
8535
|
overlay?.update(screenNow());
|
|
8461
8536
|
},
|
|
8537
|
+
warmUpProbes() {
|
|
8538
|
+
releaseAnchors();
|
|
8539
|
+
const meshes = createMeshLayerProbes(THREE);
|
|
8540
|
+
for (const mesh2 of meshes) mesh2.frustumCulled = false;
|
|
8541
|
+
const group = new THREE.Group();
|
|
8542
|
+
group.add(...meshes);
|
|
8543
|
+
anchors = { meshes, group };
|
|
8544
|
+
return { group, dispose: () => {
|
|
8545
|
+
} };
|
|
8546
|
+
},
|
|
8462
8547
|
applyCamera() {
|
|
8463
8548
|
const shake = screenNow().shake;
|
|
8464
8549
|
if (reducedMotion || !(shake > SHAKE_FLOOR)) return () => {
|
|
@@ -8477,6 +8562,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8477
8562
|
},
|
|
8478
8563
|
screen: screenNow,
|
|
8479
8564
|
dispose() {
|
|
8565
|
+
releaseAnchors();
|
|
8480
8566
|
stopMarks();
|
|
8481
8567
|
overlay?.dispose();
|
|
8482
8568
|
workshopLayers?.dispose();
|
|
@@ -8618,6 +8704,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8618
8704
|
const sc = new this.THREE.Vector3(Math.max(5e-3, sz[0]), Math.max(5e-3, sz[1]), Math.max(3e-3, sz[2]));
|
|
8619
8705
|
mesh2.scale.copy(sc);
|
|
8620
8706
|
mesh2.castShadow = true;
|
|
8707
|
+
mesh2.userData.destructionDebris = true;
|
|
8621
8708
|
this.o.scene.add(mesh2);
|
|
8622
8709
|
const live = {
|
|
8623
8710
|
mesh: mesh2,
|
|
@@ -8825,6 +8912,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8825
8912
|
mesh2.position.set(x, y, z);
|
|
8826
8913
|
mesh2.scale.setScalar(1e-3);
|
|
8827
8914
|
mesh2.renderOrder = 1;
|
|
8915
|
+
mesh2.userData.destructionDebris = true;
|
|
8828
8916
|
this.scene.add(mesh2);
|
|
8829
8917
|
this.spots.push({ mesh: mesh2, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
|
|
8830
8918
|
}
|
|
@@ -9335,12 +9423,68 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9335
9423
|
}
|
|
9336
9424
|
return { min: [center[0] - hx, center[1] - hy, center[2] - hz], max: [center[0] + hx, center[1] + hy, center[2] + hz] };
|
|
9337
9425
|
};
|
|
9426
|
+
var FLEXURE_DEFAULTS = {
|
|
9427
|
+
enabled: true,
|
|
9428
|
+
roles: ["beam", "slab", "roof", "stair"],
|
|
9429
|
+
scope: "rebar",
|
|
9430
|
+
crackFactor: 1.5,
|
|
9431
|
+
rebarYieldMPa: 500,
|
|
9432
|
+
cover: 0.03,
|
|
9433
|
+
neutralAxisLimit: 0.45,
|
|
9434
|
+
failRotationDeg: 10,
|
|
9435
|
+
loadFactor: 0.64,
|
|
9436
|
+
massFactor: 0.5,
|
|
9437
|
+
tntJoules: 4184e3,
|
|
9438
|
+
impulse: 200,
|
|
9439
|
+
reflection: 2,
|
|
9440
|
+
reflectionNear: 4,
|
|
9441
|
+
nearScaled: 0.5
|
|
9442
|
+
};
|
|
9443
|
+
var FLEXURAL_ROLES = FLEXURE_DEFAULTS.roles;
|
|
9444
|
+
var SOIL_DEFAULTS = {
|
|
9445
|
+
safety: 3,
|
|
9446
|
+
pileSafety: 2,
|
|
9447
|
+
rigidity: 0.82,
|
|
9448
|
+
influence: 2,
|
|
9449
|
+
imperfection: 1 / 200,
|
|
9450
|
+
cells: 4,
|
|
9451
|
+
sinkRate: 0.05,
|
|
9452
|
+
tiltRate: 1.5,
|
|
9453
|
+
pileElasticGPa: 25,
|
|
9454
|
+
footingWidth: 0.6
|
|
9455
|
+
};
|
|
9456
|
+
var WIND_DEFAULTS = {
|
|
9457
|
+
speed: 40,
|
|
9458
|
+
direction: [1, 0, 0],
|
|
9459
|
+
terrain: "III",
|
|
9460
|
+
class: "auto",
|
|
9461
|
+
topography: 1,
|
|
9462
|
+
statistical: 1,
|
|
9463
|
+
drag: 1.3,
|
|
9464
|
+
damping: 0.016,
|
|
9465
|
+
airDensity: 1.226,
|
|
9466
|
+
minHeight: 5,
|
|
9467
|
+
bandHeight: 3,
|
|
9468
|
+
stiffnessFactor: 0.8,
|
|
9469
|
+
beamFactor: 0.4,
|
|
9470
|
+
flange: 0.1,
|
|
9471
|
+
driftLimit: 1 / 500,
|
|
9472
|
+
loadFactor: 1.4,
|
|
9473
|
+
chord: false,
|
|
9474
|
+
meanRatio: 0.69,
|
|
9475
|
+
gustFactor: { A: 1, B: 0.98, C: 0.95 },
|
|
9476
|
+
gust: { components: 400, maxHz: 2, scale: 1200, seed: 1 },
|
|
9477
|
+
peakDecay: 60,
|
|
9478
|
+
tuning: "den-hartog"
|
|
9479
|
+
};
|
|
9338
9480
|
var materials_default = [
|
|
9339
9481
|
{
|
|
9340
9482
|
id: "steel",
|
|
9341
9483
|
density: 7850,
|
|
9342
9484
|
hpPerKg: 400,
|
|
9343
9485
|
penetration: 110,
|
|
9486
|
+
penetrationClass: "steel",
|
|
9487
|
+
penetrationScale: 1.3,
|
|
9344
9488
|
ductility: 0.9,
|
|
9345
9489
|
fracture: "none",
|
|
9346
9490
|
damageScale: {
|
|
@@ -9370,6 +9514,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9370
9514
|
density: 7200,
|
|
9371
9515
|
hpPerKg: 150,
|
|
9372
9516
|
penetration: 160,
|
|
9517
|
+
penetrationClass: "steel",
|
|
9518
|
+
penetrationScale: 1,
|
|
9373
9519
|
ductility: 0.2,
|
|
9374
9520
|
fracture: "snap",
|
|
9375
9521
|
fragmentSize: [
|
|
@@ -9403,6 +9549,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9403
9549
|
density: 2700,
|
|
9404
9550
|
hpPerKg: 250,
|
|
9405
9551
|
penetration: 60,
|
|
9552
|
+
penetrationClass: "steel",
|
|
9553
|
+
penetrationScale: 3,
|
|
9406
9554
|
ductility: 0.85,
|
|
9407
9555
|
fracture: "none",
|
|
9408
9556
|
damageScale: {
|
|
@@ -9431,6 +9579,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9431
9579
|
density: 8960,
|
|
9432
9580
|
hpPerKg: 300,
|
|
9433
9581
|
penetration: 70,
|
|
9582
|
+
penetrationClass: "steel",
|
|
9583
|
+
penetrationScale: 1.5,
|
|
9434
9584
|
ductility: 0.95,
|
|
9435
9585
|
fracture: "none",
|
|
9436
9586
|
damageScale: {
|
|
@@ -9459,6 +9609,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9459
9609
|
density: 500,
|
|
9460
9610
|
hpPerKg: 90,
|
|
9461
9611
|
penetration: 9,
|
|
9612
|
+
penetrationClass: "wood",
|
|
9613
|
+
penetrationScale: 1.5,
|
|
9462
9614
|
ductility: 0.35,
|
|
9463
9615
|
fracture: "splinter",
|
|
9464
9616
|
fragmentSize: [
|
|
@@ -9495,6 +9647,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9495
9647
|
density: 750,
|
|
9496
9648
|
hpPerKg: 140,
|
|
9497
9649
|
penetration: 14,
|
|
9650
|
+
penetrationClass: "wood",
|
|
9651
|
+
penetrationScale: 1,
|
|
9498
9652
|
ductility: 0.3,
|
|
9499
9653
|
fracture: "splinter",
|
|
9500
9654
|
fragmentSize: [
|
|
@@ -9530,6 +9684,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9530
9684
|
density: 600,
|
|
9531
9685
|
hpPerKg: 70,
|
|
9532
9686
|
penetration: 7,
|
|
9687
|
+
penetrationClass: "wood",
|
|
9688
|
+
penetrationScale: 2,
|
|
9533
9689
|
ductility: 0.3,
|
|
9534
9690
|
fracture: "snap",
|
|
9535
9691
|
fragmentSize: [
|
|
@@ -9565,6 +9721,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9565
9721
|
density: 750,
|
|
9566
9722
|
hpPerKg: 45,
|
|
9567
9723
|
penetration: 6,
|
|
9724
|
+
penetrationClass: "wood",
|
|
9725
|
+
penetrationScale: 2.5,
|
|
9568
9726
|
ductility: 0.15,
|
|
9569
9727
|
fracture: "snap",
|
|
9570
9728
|
fragmentSize: [
|
|
@@ -9693,6 +9851,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9693
9851
|
density: 2300,
|
|
9694
9852
|
hpPerKg: 14,
|
|
9695
9853
|
penetration: 18,
|
|
9854
|
+
penetrationClass: "concrete",
|
|
9855
|
+
penetrationScale: 1,
|
|
9696
9856
|
ductility: 0.03,
|
|
9697
9857
|
fracture: "crumble",
|
|
9698
9858
|
fragmentSize: [
|
|
@@ -9712,13 +9872,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9712
9872
|
"masonry",
|
|
9713
9873
|
"brittle"
|
|
9714
9874
|
],
|
|
9715
|
-
compressive: 444e3
|
|
9875
|
+
compressive: 444e3,
|
|
9876
|
+
strength: {
|
|
9877
|
+
compressiveMPa: 25,
|
|
9878
|
+
tensileMPa: 2.56,
|
|
9879
|
+
elasticGPa: 28
|
|
9880
|
+
}
|
|
9716
9881
|
},
|
|
9717
9882
|
{
|
|
9718
9883
|
id: "brick",
|
|
9719
9884
|
density: 1900,
|
|
9720
9885
|
hpPerKg: 12,
|
|
9721
9886
|
penetration: 14,
|
|
9887
|
+
penetrationClass: "brick",
|
|
9888
|
+
penetrationScale: 1,
|
|
9722
9889
|
ductility: 0.05,
|
|
9723
9890
|
fracture: "crumble",
|
|
9724
9891
|
fragmentSize: [
|
|
@@ -9735,13 +9902,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9735
9902
|
"masonry",
|
|
9736
9903
|
"brittle"
|
|
9737
9904
|
],
|
|
9738
|
-
compressive: 4e4
|
|
9905
|
+
compressive: 4e4,
|
|
9906
|
+
strength: {
|
|
9907
|
+
compressiveMPa: 2.35,
|
|
9908
|
+
tensileMPa: 0.1,
|
|
9909
|
+
elasticGPa: 2.35
|
|
9910
|
+
}
|
|
9739
9911
|
},
|
|
9740
9912
|
{
|
|
9741
9913
|
id: "plaster",
|
|
9742
9914
|
density: 850,
|
|
9743
9915
|
hpPerKg: 18,
|
|
9744
9916
|
penetration: 3,
|
|
9917
|
+
penetrationClass: "brick",
|
|
9918
|
+
penetrationScale: 3,
|
|
9745
9919
|
ductility: 0.05,
|
|
9746
9920
|
fracture: "crumble",
|
|
9747
9921
|
fragmentSize: [
|
|
@@ -9765,6 +9939,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9765
9939
|
density: 2700,
|
|
9766
9940
|
hpPerKg: 25,
|
|
9767
9941
|
penetration: 25,
|
|
9942
|
+
penetrationClass: "concrete",
|
|
9943
|
+
penetrationScale: 0.9,
|
|
9768
9944
|
ductility: 0.03,
|
|
9769
9945
|
fracture: "crumble",
|
|
9770
9946
|
fragmentSize: [
|
|
@@ -10028,6 +10204,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10028
10204
|
density: 650,
|
|
10029
10205
|
hpPerKg: 5,
|
|
10030
10206
|
penetration: 10,
|
|
10207
|
+
penetrationClass: "wood",
|
|
10208
|
+
penetrationScale: 1.2,
|
|
10031
10209
|
ductility: 0.5,
|
|
10032
10210
|
fracture: "splinter",
|
|
10033
10211
|
fragmentSize: [
|
|
@@ -10279,6 +10457,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10279
10457
|
for (const [key, id] of HINTS) if (n.includes(key)) return id;
|
|
10280
10458
|
return void 0;
|
|
10281
10459
|
};
|
|
10460
|
+
var partVolume = (shape) => {
|
|
10461
|
+
if (shape.kind === "box") return shape.size[0] * shape.size[1] * shape.size[2];
|
|
10462
|
+
if (shape.kind === "cylinder") return Math.PI * shape.radius * shape.radius * shape.height;
|
|
10463
|
+
return 4 / 3 * Math.PI * shape.radius ** 3;
|
|
10464
|
+
};
|
|
10465
|
+
var partMass = (part, material) => part.mass ?? material.density * partVolume(part.shape) * (part.fill ?? 1);
|
|
10282
10466
|
var partThickness = (part) => {
|
|
10283
10467
|
const dims = shapeDims(part.shape);
|
|
10284
10468
|
const min2 = Math.min(dims[0], dims[1], dims[2]);
|
|
@@ -10306,6 +10490,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10306
10490
|
};
|
|
10307
10491
|
|
|
10308
10492
|
// src/spec.ts
|
|
10493
|
+
function containsOf(value) {
|
|
10494
|
+
if (!value || typeof value !== "object") return void 0;
|
|
10495
|
+
const v = value;
|
|
10496
|
+
return typeof v.fluid === "string" && typeof v.liters === "number" && v.liters > 0 ? { fluid: v.fluid, liters: v.liters } : void 0;
|
|
10497
|
+
}
|
|
10309
10498
|
function partHintOf(value) {
|
|
10310
10499
|
if (typeof value === "string") return value ? { id: value } : null;
|
|
10311
10500
|
if (!value || typeof value !== "object") return null;
|
|
@@ -10326,11 +10515,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10326
10515
|
function sizeOf(box) {
|
|
10327
10516
|
return [box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z];
|
|
10328
10517
|
}
|
|
10518
|
+
function specFrameInverse(THREE, root) {
|
|
10519
|
+
root.updateMatrixWorld(true);
|
|
10520
|
+
const position = new THREE.Vector3();
|
|
10521
|
+
const quaternion = new THREE.Quaternion();
|
|
10522
|
+
root.matrixWorld.decompose(position, quaternion, new THREE.Vector3());
|
|
10523
|
+
return new THREE.Matrix4().compose(position, quaternion, new THREE.Vector3(1, 1, 1)).invert();
|
|
10524
|
+
}
|
|
10329
10525
|
function specFromObject3D(THREE, root, options = {}) {
|
|
10330
|
-
const
|
|
10526
|
+
const fallback2 = options.fallback ?? "plastic";
|
|
10331
10527
|
const minSize = options.minSize ?? 0.015;
|
|
10332
|
-
root
|
|
10333
|
-
const inv = root.matrixWorld.clone().invert();
|
|
10528
|
+
const inv = specFrameInverse(THREE, root);
|
|
10334
10529
|
const entries = [];
|
|
10335
10530
|
const byKey = /* @__PURE__ */ new Map();
|
|
10336
10531
|
const visit = (o) => {
|
|
@@ -10344,11 +10539,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10344
10539
|
const matName = mats[0]?.name ?? "";
|
|
10345
10540
|
const ud = o.userData ?? {};
|
|
10346
10541
|
const hint = partHintOf(ud.destructionPart);
|
|
10347
|
-
const material = options.materialOf?.(o) ?? (typeof ud.destructionMaterial === "string" ? ud.destructionMaterial : void 0) ?? hint?.material ?? guessMaterial(`${o.name} ${matName} ${typeof ud.material === "string" ? ud.material : ""}`) ??
|
|
10542
|
+
const material = options.materialOf?.(o) ?? (typeof ud.destructionMaterial === "string" ? ud.destructionMaterial : void 0) ?? hint?.material ?? guessMaterial(`${o.name} ${matName} ${typeof ud.material === "string" ? ud.material : ""}`) ?? fallback2;
|
|
10348
10543
|
const explicit = hint?.id ?? null;
|
|
10349
10544
|
const hollow = ud.hollow === true || /hollow/i.test(o.name);
|
|
10545
|
+
const skin = ud.destructionSkin === true;
|
|
10350
10546
|
const fill = options.fillOf?.(o, material);
|
|
10351
10547
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10548
|
+
const contains = containsOf(ud.destructionContains);
|
|
10352
10549
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10353
10550
|
if (existing) {
|
|
10354
10551
|
const before = sizeOf(existing.box);
|
|
@@ -10358,9 +10555,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10358
10555
|
existing.meshes.push(o);
|
|
10359
10556
|
if (ns[0] * ns[1] * ns[2] > bv) existing.material = material;
|
|
10360
10557
|
existing.hollow = existing.hollow || hollow;
|
|
10558
|
+
existing.skin = existing.skin && skin;
|
|
10361
10559
|
if (fill !== void 0) existing.fill = fill;
|
|
10560
|
+
if (contains) existing.contains = contains;
|
|
10362
10561
|
} else {
|
|
10363
|
-
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, tags, fixedId: !!explicit, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
10562
|
+
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
10364
10563
|
entries.push(e);
|
|
10365
10564
|
if (explicit) byKey.set(explicit, e);
|
|
10366
10565
|
}
|
|
@@ -10379,7 +10578,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10379
10578
|
const largest = (e) => Math.max(...sizeOf(e.box));
|
|
10380
10579
|
const kept = [];
|
|
10381
10580
|
const tiny = [];
|
|
10382
|
-
for (const e of entries) (!e.fixedId && largest(e) < minSize ? tiny : kept).push(e);
|
|
10581
|
+
for (const e of entries) (e.skin || !e.fixedId && largest(e) < minSize ? tiny : kept).push(e);
|
|
10383
10582
|
for (const t of tiny) {
|
|
10384
10583
|
let best = null;
|
|
10385
10584
|
let bestV = 0;
|
|
@@ -10433,11 +10632,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10433
10632
|
part.thickness = Math.max(minSide, 5e-4);
|
|
10434
10633
|
}
|
|
10435
10634
|
if (e.tags) part.tags = [...e.tags];
|
|
10635
|
+
if (e.contains) part.contains = { ...e.contains };
|
|
10436
10636
|
const ao = options.anchorBelow ?? -1;
|
|
10437
10637
|
if (ao >= 0 && e.box.min.y <= ao) part.anchored = true;
|
|
10438
10638
|
parts.push(part);
|
|
10439
10639
|
meshesOf[id] = e.meshes;
|
|
10440
10640
|
}
|
|
10641
|
+
const knownMass = root.userData?.destructionMass;
|
|
10642
|
+
if (typeof knownMass === "number" && knownMass > 0) {
|
|
10643
|
+
const shares = parts.map((p) => {
|
|
10644
|
+
const material = MATERIALS2.find((m) => m.id === p.material);
|
|
10645
|
+
return material ? partMass(p, material) : 0;
|
|
10646
|
+
});
|
|
10647
|
+
const total = shares.reduce((a, b) => a + b, 0);
|
|
10648
|
+
if (total > 0) parts.forEach((p, i) => p.mass = knownMass * shares[i] / total);
|
|
10649
|
+
}
|
|
10441
10650
|
const spec = { parts };
|
|
10442
10651
|
if (options.name !== void 0) spec.name = options.name;
|
|
10443
10652
|
else if (root.name) spec.name = root.name;
|
|
@@ -10489,6 +10698,46 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10489
10698
|
return last[1];
|
|
10490
10699
|
}
|
|
10491
10700
|
|
|
10701
|
+
// src/skin-dent.ts
|
|
10702
|
+
function shellDisplacementAt(THREE, worldPoint, shells, out) {
|
|
10703
|
+
const triangle = new THREE.Triangle();
|
|
10704
|
+
const corners = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
|
|
10705
|
+
const local2 = new THREE.Vector3();
|
|
10706
|
+
const closest = new THREE.Vector3();
|
|
10707
|
+
const weights = new THREE.Vector3();
|
|
10708
|
+
const moved = new THREE.Vector3();
|
|
10709
|
+
let nearest = Infinity;
|
|
10710
|
+
out.set(0, 0, 0);
|
|
10711
|
+
let nearestScale = 1;
|
|
10712
|
+
for (const shell of shells) {
|
|
10713
|
+
shell.mesh.updateWorldMatrix(true, false);
|
|
10714
|
+
shell.mesh.worldToLocal(local2.copy(worldPoint));
|
|
10715
|
+
const index = shell.mesh.geometry.index;
|
|
10716
|
+
const triangles = index ? index.count / 3 : shell.before.length / 9;
|
|
10717
|
+
for (let t = 0; t < triangles; t++) {
|
|
10718
|
+
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
10719
|
+
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
10720
|
+
triangle.set(corners[0], corners[1], corners[2]);
|
|
10721
|
+
triangle.closestPointToPoint(local2, closest);
|
|
10722
|
+
const distance = closest.distanceToSquared(local2);
|
|
10723
|
+
if (distance >= nearest) continue;
|
|
10724
|
+
nearest = distance;
|
|
10725
|
+
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10726
|
+
triangle.getBarycoord(closest, weights);
|
|
10727
|
+
moved.set(0, 0, 0);
|
|
10728
|
+
ids.forEach((id, corner) => {
|
|
10729
|
+
const weight = corner === 0 ? weights.x : corner === 1 ? weights.y : weights.z;
|
|
10730
|
+
moved.x += weight * (shell.after[id * 3] - shell.before[id * 3]);
|
|
10731
|
+
moved.y += weight * (shell.after[id * 3 + 1] - shell.before[id * 3 + 1]);
|
|
10732
|
+
moved.z += weight * (shell.after[id * 3 + 2] - shell.before[id * 3 + 2]);
|
|
10733
|
+
});
|
|
10734
|
+
out.copy(moved).transformDirection(shell.mesh.matrixWorld);
|
|
10735
|
+
out.multiplyScalar(moved.length() * (shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1));
|
|
10736
|
+
}
|
|
10737
|
+
}
|
|
10738
|
+
return Math.sqrt(nearest) * nearestScale;
|
|
10739
|
+
}
|
|
10740
|
+
|
|
10492
10741
|
// src/visual.ts
|
|
10493
10742
|
var POS_ATTR = "position";
|
|
10494
10743
|
function positionAttr(geo) {
|
|
@@ -10553,6 +10802,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10553
10802
|
}
|
|
10554
10803
|
};
|
|
10555
10804
|
var tmpV = (THREE) => new THREE.Vector3();
|
|
10805
|
+
var MIN_DENT_VERTICES = 10;
|
|
10806
|
+
var WIDEN_MARGIN = 1.1;
|
|
10807
|
+
var SKIN_FOLLOW = 0.85;
|
|
10808
|
+
var SKIN_SLACK = 5e-4;
|
|
10809
|
+
var SKIN_LAYER_SLACK = 4;
|
|
10810
|
+
var NEAREST_MARGIN = 1.5;
|
|
10556
10811
|
var Visuals = class {
|
|
10557
10812
|
constructor(THREE, lowQuality, decalCap) {
|
|
10558
10813
|
__publicField(this, "THREE", THREE);
|
|
@@ -10721,14 +10976,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10721
10976
|
* Push the vertices of the mesh within `dent.radius` of `worldPoint` inwards along `worldNormal` by `depth * (1 - (d/r)^2)^2`.
|
|
10722
10977
|
* Returns false when nothing was changed (too far, low quality on a heavy mesh, or no editable positions).
|
|
10723
10978
|
*/
|
|
10724
|
-
applyDent(mv, worldPoint, worldNormal, dent) {
|
|
10979
|
+
applyDent(mv, worldPoint, worldNormal, dent, reachWorld = 0) {
|
|
10725
10980
|
const mesh2 = mv.mesh;
|
|
10726
10981
|
if (this.lowQuality && this.vertexCount(mv) > 2e4) return false;
|
|
10727
10982
|
mesh2.updateWorldMatrix(true, false);
|
|
10728
10983
|
const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10729
10984
|
const lp = mesh2.worldToLocal(this.a.copy(worldPoint));
|
|
10730
10985
|
const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
|
|
10731
|
-
const r = dent.radius / s;
|
|
10986
|
+
const r = Math.max(dent.radius, reachWorld) / s;
|
|
10732
10987
|
const depth = dent.depth / s;
|
|
10733
10988
|
const geo = mesh2.geometry;
|
|
10734
10989
|
let bb;
|
|
@@ -10741,34 +10996,110 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10741
10996
|
if (!this.ownGeometry(mv)) return false;
|
|
10742
10997
|
const work = mv.host ? mv.host.work : mv.work;
|
|
10743
10998
|
const pos = positionAttr(mesh2.geometry);
|
|
10744
|
-
const r2 = r * r;
|
|
10745
|
-
let touched = false;
|
|
10746
|
-
const px = lp.x;
|
|
10747
|
-
const py = lp.y;
|
|
10748
|
-
const pz = lp.z;
|
|
10749
|
-
const nx = ln.x;
|
|
10750
|
-
const ny = ln.y;
|
|
10751
|
-
const nz = ln.z;
|
|
10752
10999
|
const from = mv.range ? mv.range[0] * 3 : 0;
|
|
10753
11000
|
const to = mv.range ? mv.range[1] * 3 : work.length;
|
|
11001
|
+
const reach = r;
|
|
11002
|
+
const reach2 = reach * reach;
|
|
10754
11003
|
for (let i = from; i < to; i += 3) {
|
|
10755
|
-
const dx = work[i] -
|
|
10756
|
-
const dy = work[i + 1] -
|
|
10757
|
-
const dz = work[i + 2] -
|
|
11004
|
+
const dx = work[i] - lp.x;
|
|
11005
|
+
const dy = work[i + 1] - lp.y;
|
|
11006
|
+
const dz = work[i + 2] - lp.z;
|
|
10758
11007
|
const d2 = dx * dx + dy * dy + dz * dz;
|
|
10759
|
-
if (d2 >=
|
|
10760
|
-
const f = 1 - d2 /
|
|
11008
|
+
if (d2 >= reach2) continue;
|
|
11009
|
+
const f = 1 - d2 / reach2;
|
|
10761
11010
|
const k = depth * f * f;
|
|
10762
|
-
work[i] -=
|
|
10763
|
-
work[i + 1] -=
|
|
10764
|
-
work[i + 2] -=
|
|
10765
|
-
touched = true;
|
|
11011
|
+
work[i] -= ln.x * k;
|
|
11012
|
+
work[i + 1] -= ln.y * k;
|
|
11013
|
+
work[i + 2] -= ln.z * k;
|
|
10766
11014
|
}
|
|
10767
|
-
if (!touched) return false;
|
|
10768
11015
|
mv.touched = true;
|
|
10769
11016
|
this.writePositions(mv, pos);
|
|
10770
11017
|
return true;
|
|
10771
11018
|
}
|
|
11019
|
+
/**
|
|
11020
|
+
* World radius a dent needs on this mesh to reach `MIN_DENT_VERTICES` vertices (the dent's own radius when the mesh is fine enough). A part with several meshes
|
|
11021
|
+
* (a shell and its label) must dent them all with the largest of these, so the skin and the shell follow the same smooth surface and never cross.
|
|
11022
|
+
*/
|
|
11023
|
+
dentReach(mv, worldPoint, dent) {
|
|
11024
|
+
const mesh2 = mv.mesh;
|
|
11025
|
+
mesh2.updateWorldMatrix(true, false);
|
|
11026
|
+
const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
11027
|
+
const lp = mesh2.worldToLocal(this.a.copy(worldPoint));
|
|
11028
|
+
const source = (mv.host ? mv.host.work : mv.work) ?? positionAttr(mesh2.geometry)?.array;
|
|
11029
|
+
if (!source) return dent.radius;
|
|
11030
|
+
let bounds;
|
|
11031
|
+
if (mv.host) bounds = this.localBox(mv, this.lbox);
|
|
11032
|
+
else {
|
|
11033
|
+
if (!mesh2.geometry.boundingBox) mesh2.geometry.computeBoundingBox();
|
|
11034
|
+
bounds = mesh2.geometry.boundingBox;
|
|
11035
|
+
}
|
|
11036
|
+
if (bounds && !bounds.isEmpty() && bounds.distanceToPoint(lp) > dent.radius / s) return dent.radius;
|
|
11037
|
+
const from = mv.range ? mv.range[0] * 3 : 0;
|
|
11038
|
+
const to = mv.range ? mv.range[1] * 3 : source.length;
|
|
11039
|
+
const r2 = (dent.radius / s) ** 2;
|
|
11040
|
+
const distances = [];
|
|
11041
|
+
let inside = 0;
|
|
11042
|
+
for (let i = from; i < to; i += 3) {
|
|
11043
|
+
const dx = source[i] - lp.x;
|
|
11044
|
+
const dy = source[i + 1] - lp.y;
|
|
11045
|
+
const dz = source[i + 2] - lp.z;
|
|
11046
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
11047
|
+
if (d2 < r2) inside++;
|
|
11048
|
+
distances.push(d2);
|
|
11049
|
+
}
|
|
11050
|
+
if (inside >= MIN_DENT_VERTICES || distances.length < MIN_DENT_VERTICES) return dent.radius;
|
|
11051
|
+
distances.sort((x, y) => x - y);
|
|
11052
|
+
const nearest = Math.sqrt(distances[0]);
|
|
11053
|
+
return Math.max(dent.radius, Math.sqrt(distances[MIN_DENT_VERTICES - 1]) * s * WIDEN_MARGIN, nearest * s * NEAREST_MARGIN);
|
|
11054
|
+
}
|
|
11055
|
+
/**
|
|
11056
|
+
* Dent all the meshes of a part. The shells take the dent as `applyDent` gives it; a skin (label, coat: `userData.destructionSkin`) sits a hair above its shell
|
|
11057
|
+
* and moves with the shell's surface instead (see `shellDisplacementAt`), so the shell never shows through it whatever the two meshes' vertex grids are.
|
|
11058
|
+
*/
|
|
11059
|
+
applyPartDent(meshes, worldPoint, worldNormal, dent, reachWorld) {
|
|
11060
|
+
const isSkin = (mv) => mv.mesh.userData.destructionSkin === true && !mv.host;
|
|
11061
|
+
const shells = meshes.filter((mv) => !isSkin(mv));
|
|
11062
|
+
const snapshots = [];
|
|
11063
|
+
for (const mv of shells) {
|
|
11064
|
+
const heavy = this.lowQuality && this.vertexCount(mv) > 2e4;
|
|
11065
|
+
const plain = !mv.host && !heavy && this.ownGeometry(mv) && mv.work;
|
|
11066
|
+
const before = plain ? new Float32Array(mv.work) : null;
|
|
11067
|
+
this.applyDent(mv, worldPoint, worldNormal, dent, reachWorld);
|
|
11068
|
+
if (before) snapshots.push({ mesh: mv.mesh, before, after: mv.work });
|
|
11069
|
+
}
|
|
11070
|
+
const displacement = this.t;
|
|
11071
|
+
const vertex = this.a;
|
|
11072
|
+
for (const mv of meshes) {
|
|
11073
|
+
if (!isSkin(mv)) continue;
|
|
11074
|
+
if (snapshots.length === 0) {
|
|
11075
|
+
this.applyDent(mv, worldPoint, worldNormal, dent, reachWorld);
|
|
11076
|
+
continue;
|
|
11077
|
+
}
|
|
11078
|
+
if (!this.ownGeometry(mv)) continue;
|
|
11079
|
+
const work = mv.work;
|
|
11080
|
+
const mesh2 = mv.mesh;
|
|
11081
|
+
mesh2.updateWorldMatrix(true, false);
|
|
11082
|
+
const toLocal = this.m4.copy(mesh2.matrixWorld).invert();
|
|
11083
|
+
const scale = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
11084
|
+
let touched = false;
|
|
11085
|
+
for (let i = 0; i < work.length; i += 3) {
|
|
11086
|
+
vertex.set(work[i], work[i + 1], work[i + 2]).applyMatrix4(mesh2.matrixWorld);
|
|
11087
|
+
if (vertex.distanceTo(worldPoint) > reachWorld) continue;
|
|
11088
|
+
const height = shellDisplacementAt(this.THREE, vertex, snapshots, displacement);
|
|
11089
|
+
const length = displacement.length();
|
|
11090
|
+
if (length === 0) continue;
|
|
11091
|
+
const falloff = 1 - (vertex.distanceTo(worldPoint) / reachWorld) ** 2;
|
|
11092
|
+
const smooth = dent.depth * falloff * falloff;
|
|
11093
|
+
const pull = Math.max(0, Math.min(length, smooth) * SKIN_FOLLOW - SKIN_SLACK - height * SKIN_LAYER_SLACK);
|
|
11094
|
+
displacement.transformDirection(toLocal).multiplyScalar(pull / scale);
|
|
11095
|
+
work[i] += displacement.x;
|
|
11096
|
+
work[i + 1] += displacement.y;
|
|
11097
|
+
work[i + 2] += displacement.z;
|
|
11098
|
+
touched = true;
|
|
11099
|
+
}
|
|
11100
|
+
if (touched) this.writePositions(mv, positionAttr(mesh2.geometry));
|
|
11101
|
+
}
|
|
11102
|
+
}
|
|
10772
11103
|
/** Copy `work` (dents) into the geometry, adding the melt sag when the mesh is melting. */
|
|
10773
11104
|
writePositions(mv, pos) {
|
|
10774
11105
|
if (mv.host) {
|
|
@@ -10815,6 +11146,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10815
11146
|
return { s, pos, quat: quat2 };
|
|
10816
11147
|
}
|
|
10817
11148
|
addDecal(mv, decal2) {
|
|
11149
|
+
decal2.obj.userData.destructionDebris = true;
|
|
10818
11150
|
mv.mesh.add(decal2.obj);
|
|
10819
11151
|
mv.decals.push(decal2);
|
|
10820
11152
|
this.decalCount++;
|
|
@@ -11399,6 +11731,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11399
11731
|
__publicField(this, "tmpQ");
|
|
11400
11732
|
__publicField(this, "v1");
|
|
11401
11733
|
__publicField(this, "v2");
|
|
11734
|
+
// The last warm-up keeps its materials until the next one: a material that does not use lights keeps the program of the lights it was first drawn with, so each warm-up needs materials of its own.
|
|
11735
|
+
__publicField(this, "warmProbes");
|
|
11402
11736
|
this.world = o.world;
|
|
11403
11737
|
this.scene = o.scene;
|
|
11404
11738
|
this.quality = o.quality ?? "medium";
|
|
@@ -11496,6 +11830,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11496
11830
|
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes, hosts));
|
|
11497
11831
|
}
|
|
11498
11832
|
this.bounds.set(objectId, bound);
|
|
11833
|
+
for (const pv of bound.parts.values()) this.tagPart(pv, objectId);
|
|
11834
|
+
}
|
|
11835
|
+
/**
|
|
11836
|
+
* Label the meshes of a part so a host can go from a raycast hit to the world: `userData.destructionPart` (the part id) and
|
|
11837
|
+
* `userData.destructionObject` (the id of the world object that part belongs to now; a loose piece has an id of its own). Parts drawn by a range
|
|
11838
|
+
* of a merged mesh share that mesh with others and are left alone until they get a mesh of their own. Decals are marked `destructionDebris`.
|
|
11839
|
+
*/
|
|
11840
|
+
tagPart(pv, objectId) {
|
|
11841
|
+
for (const mv of pv.meshes) {
|
|
11842
|
+
if (mv.merged && !mv.loose) continue;
|
|
11843
|
+
mv.mesh.traverse((c) => {
|
|
11844
|
+
if (!c.isMesh) return;
|
|
11845
|
+
const ud = c.userData;
|
|
11846
|
+
if (ud.destructionDebris) return;
|
|
11847
|
+
if (partHintOf(ud.destructionPart)?.id !== pv.id) ud.destructionPart = pv.id;
|
|
11848
|
+
ud.destructionObject = objectId;
|
|
11849
|
+
});
|
|
11850
|
+
}
|
|
11499
11851
|
}
|
|
11500
11852
|
makePartVis(home, id, material, meshes, hosts) {
|
|
11501
11853
|
return { id, home, material, meshes: meshes.map((m) => this.visuals.makeMeshVis(m.mesh, home, m.range, hosts)), charApplied: 0, glowApplied: 0, meltApplied: 0, gone: "none" };
|
|
@@ -11509,13 +11861,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11509
11861
|
specParts = void 0;
|
|
11510
11862
|
}
|
|
11511
11863
|
if (!specParts) return;
|
|
11512
|
-
root
|
|
11864
|
+
const frame = specFrameInverse(this.THREE, root);
|
|
11513
11865
|
const e = new this.THREE.Euler();
|
|
11514
11866
|
const q = new this.THREE.Quaternion();
|
|
11515
11867
|
for (const m of meshes) {
|
|
11516
11868
|
this.box.setFromObject(m);
|
|
11517
11869
|
if (this.box.isEmpty()) continue;
|
|
11518
|
-
const c =
|
|
11870
|
+
const c = this.box.getCenter(this.v1).applyMatrix4(frame);
|
|
11519
11871
|
let best = null;
|
|
11520
11872
|
let bestVol = Infinity;
|
|
11521
11873
|
for (const p of specParts) {
|
|
@@ -11552,10 +11904,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11552
11904
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11553
11905
|
// Pieces
|
|
11554
11906
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11555
|
-
/** Half extents and centre of the box around some meshes, in the
|
|
11907
|
+
/** Half extents and centre of the box around some meshes, in the metric frame of `root` (its pose removed, its scale kept). */
|
|
11556
11908
|
measure(root, meshes) {
|
|
11557
|
-
root
|
|
11558
|
-
const inv = root.matrixWorld.clone().invert();
|
|
11909
|
+
const inv = specFrameInverse(this.THREE, root);
|
|
11559
11910
|
const total = new this.THREE.Box3();
|
|
11560
11911
|
const m4 = new this.THREE.Matrix4();
|
|
11561
11912
|
const lb = new this.THREE.Box3();
|
|
@@ -11630,6 +11981,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11630
11981
|
dt = clamp6(dt, 0, 0.1);
|
|
11631
11982
|
this.time += dt;
|
|
11632
11983
|
this.drainQueue();
|
|
11984
|
+
for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
|
|
11633
11985
|
this.updateActive(dt);
|
|
11634
11986
|
this.updateLeaks(dt);
|
|
11635
11987
|
this.pieceSys.update(dt);
|
|
@@ -11648,6 +12000,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11648
12000
|
}
|
|
11649
12001
|
}
|
|
11650
12002
|
}
|
|
12003
|
+
/**
|
|
12004
|
+
* The ground under an object settled or tilted: its parts are drawn under a node with the soil's offset (`ObjectState.soil.offset`), so the host's
|
|
12005
|
+
* root keeps the pose the host gave and the world keeps getting it from `syncTransform`.
|
|
12006
|
+
*/
|
|
12007
|
+
onSettle(objectId) {
|
|
12008
|
+
const b = this.bounds.get(objectId);
|
|
12009
|
+
const soil = this.world.state(objectId)?.soil;
|
|
12010
|
+
if (!b || !soil) return;
|
|
12011
|
+
let node = b.soilNode;
|
|
12012
|
+
if (!node) {
|
|
12013
|
+
node = new this.THREE.Group();
|
|
12014
|
+
node.name = "destruction-soil";
|
|
12015
|
+
for (const c of [...b.root.children]) node.add(c);
|
|
12016
|
+
b.root.add(node);
|
|
12017
|
+
b.soilNode = node;
|
|
12018
|
+
}
|
|
12019
|
+
const { position: p, rotation: q } = soil.offset;
|
|
12020
|
+
node.position.set(p[0], p[1], p[2]);
|
|
12021
|
+
node.quaternion.set(q[0], q[1], q[2], q[3]);
|
|
12022
|
+
node.updateMatrixWorld(true);
|
|
12023
|
+
}
|
|
11651
12024
|
syncTransforms() {
|
|
11652
12025
|
for (const b of this.bounds.values()) this.syncOne(b);
|
|
11653
12026
|
}
|
|
@@ -11702,6 +12075,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11702
12075
|
case "detach":
|
|
11703
12076
|
this.onDetach(ev);
|
|
11704
12077
|
break;
|
|
12078
|
+
case "settle":
|
|
12079
|
+
this.onSettle(ev.object);
|
|
12080
|
+
break;
|
|
11705
12081
|
case "collapse":
|
|
11706
12082
|
this.splitLoose(ev.object);
|
|
11707
12083
|
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
@@ -11793,7 +12169,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11793
12169
|
same.direction = ev.direction;
|
|
11794
12170
|
} else {
|
|
11795
12171
|
if (this.leaks.length >= 24) this.leaks.shift();
|
|
11796
|
-
this.leaks.push({ object: ev.object, part: ev.part, fluid: ev.fluid, point: ev.point, direction: ev.direction, rate: ev.rate, pressurized: ev.pressurized, acc: 0 });
|
|
12172
|
+
this.leaks.push({ object: ev.object, part: ev.part, fluid: ev.fluid, point: ev.point, direction: ev.direction, rate: ev.rate, pressurized: ev.pressurized, acc: 0, opened: this.world.part(ev.object, ev.part)?.holes.some((h) => h.through) ?? false });
|
|
11797
12173
|
}
|
|
11798
12174
|
void key;
|
|
11799
12175
|
break;
|
|
@@ -11870,10 +12246,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11870
12246
|
const def = this.world.materials.get(pv.material);
|
|
11871
12247
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
11872
12248
|
const wn = this.worldDir(b, ev.dent.normal);
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
}
|
|
12249
|
+
const shown = pv.meshes.filter((mv) => this.visuals.shown(mv));
|
|
12250
|
+
const reach = shown.reduce((widest, mv) => Math.max(widest, this.visuals.dentReach(mv, wp, ev.dent)), ev.dent.radius);
|
|
12251
|
+
this.visuals.applyPartDent(shown, wp, wn, ev.dent, reach);
|
|
11877
12252
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [wn.x, wn.y, wn.z], 4 + Math.min(8, ev.dent.depth * 150), 3.2);
|
|
11878
12253
|
this.sound(impactKey(def), ev.worldPoint, clamp6(0.3 + ev.dent.depth * 12, 0.3, 1));
|
|
11879
12254
|
}
|
|
@@ -12069,6 +12444,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12069
12444
|
}
|
|
12070
12445
|
}
|
|
12071
12446
|
this.bounds.set(newId, nb);
|
|
12447
|
+
for (const pv of nb.parts.values()) this.tagPart(pv, newId);
|
|
12072
12448
|
const meshes = this.meshesOf(nb);
|
|
12073
12449
|
if (meshes.length === 0) return;
|
|
12074
12450
|
const { half, center } = this.measure(group, meshes);
|
|
@@ -12096,24 +12472,40 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12096
12472
|
const report = options.report;
|
|
12097
12473
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
12098
12474
|
const energy = ev.energy * confinement;
|
|
12099
|
-
|
|
12475
|
+
const reachOf = (objectId) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? 1;
|
|
12476
|
+
const shove = (pc) => {
|
|
12100
12477
|
this.pieceSys.centerOf(pc, c);
|
|
12101
|
-
const
|
|
12102
|
-
|
|
12103
|
-
const dz = c.z - p[2];
|
|
12104
|
-
const d = Math.hypot(dx, dy, dz);
|
|
12105
|
-
if (d > ev.radius) continue;
|
|
12106
|
-
const fall = (1 - d / ev.radius) ** 2;
|
|
12107
|
-
const reach = report?.find((r) => r.object === pc.objectId)?.blast?.shelter ?? 1;
|
|
12108
|
-
const speed = Math.min(30, Math.sqrt(2 * energy * reach * 0.02 / pc.mass)) * fall;
|
|
12109
|
-
const inv = d > 1e-4 ? 1 / d : 0;
|
|
12110
|
-
const dir = d > 1e-4 ? [dx * inv, dy * inv + 0.3, dz * inv] : [0, 1, 0];
|
|
12478
|
+
const hit = blastShove({ point: p, energy, radius: ev.radius, reach: reachOf(pc.objectId) }, { position: [c.x, c.y, c.z], mass: pc.mass }, this.o.blastShove);
|
|
12479
|
+
if (!hit) return;
|
|
12111
12480
|
const spread = pc.halfExtents[0];
|
|
12112
|
-
|
|
12113
|
-
|
|
12481
|
+
const momentum = hit.speed * pc.mass;
|
|
12482
|
+
this.pieceSys.impulse(pc, [hit.direction[0] * momentum, hit.direction[1] * momentum, hit.direction[2] * momentum], [c.x + (this.rng() - 0.5) * spread, c.y + spread * 0.5, c.z + (this.rng() - 0.5) * spread]);
|
|
12483
|
+
};
|
|
12484
|
+
for (const pc of [...this.pieceSys.list]) shove(pc);
|
|
12485
|
+
if (this.o.shoveable) this.loosen(p, ev.radius, energy, reachOf);
|
|
12114
12486
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
12115
12487
|
this.sound("explode", p, clamp6(0.5 + ev.radius / 10, 0.5, 1));
|
|
12116
12488
|
}
|
|
12489
|
+
/** The bound objects without a piece that the host lets a blast move (`shoveable`): the ones it reaches with enough speed become pieces and get the shove. */
|
|
12490
|
+
loosen(point, radius, energy, reachOf) {
|
|
12491
|
+
const shoveable = this.o.shoveable;
|
|
12492
|
+
if (!shoveable) return;
|
|
12493
|
+
for (const b of [...this.bounds.values()]) {
|
|
12494
|
+
if (b.piece || this.pieceSys.get(b.id) || !shoveable(b.id) || !this.world.has(b.id)) continue;
|
|
12495
|
+
const { half, center } = this.measure(b.root, this.meshesOf(b));
|
|
12496
|
+
const world = center.clone().applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
12497
|
+
const mass = this.massOf(b.id);
|
|
12498
|
+
const hit = blastShove({ point, energy, radius, reach: reachOf(b.id) }, { position: [world.x, world.y, world.z], mass }, this.o.blastShove);
|
|
12499
|
+
if (!hit) continue;
|
|
12500
|
+
const piece = this.makePiece(b.id, { mass });
|
|
12501
|
+
const body = this.pieceSys.get(b.id);
|
|
12502
|
+
if (!body) continue;
|
|
12503
|
+
this.o.onLoosened?.(b.id, piece);
|
|
12504
|
+
const spread = half[0];
|
|
12505
|
+
const momentum = hit.speed * mass;
|
|
12506
|
+
this.pieceSys.impulse(body, [hit.direction[0] * momentum, hit.direction[1] * momentum, hit.direction[2] * momentum], [world.x + (this.rng() - 0.5) * spread, world.y + spread * 0.5, world.z + (this.rng() - 0.5) * spread]);
|
|
12507
|
+
}
|
|
12508
|
+
}
|
|
12117
12509
|
playExplosion(ev, options) {
|
|
12118
12510
|
const named = options.effect ?? ev.effect;
|
|
12119
12511
|
const direction = options.direction;
|
|
@@ -12214,7 +12606,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12214
12606
|
for (let i = this.leaks.length - 1; i >= 0; i--) {
|
|
12215
12607
|
const l = this.leaks[i];
|
|
12216
12608
|
const ps = this.world.part(l.object, l.part);
|
|
12217
|
-
if (!ps || ps.liters === void 0 || ps.liters <= 0) {
|
|
12609
|
+
if (!ps || ps.liters === void 0 || ps.liters <= 0 || l.opened && !ps.holes.some((h) => h.through)) {
|
|
12218
12610
|
this.leaks.splice(i, 1);
|
|
12219
12611
|
continue;
|
|
12220
12612
|
}
|
|
@@ -12265,6 +12657,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12265
12657
|
for (const [pid, pv] of [...b.parts]) {
|
|
12266
12658
|
if (pv.home !== home) continue;
|
|
12267
12659
|
this.visuals.restorePart(pv);
|
|
12660
|
+
this.tagPart(pv, home);
|
|
12268
12661
|
if (b !== homeBound && homeBound) {
|
|
12269
12662
|
b.parts.delete(pid);
|
|
12270
12663
|
homeBound.parts.set(pid, pv);
|
|
@@ -12306,10 +12699,31 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12306
12699
|
const { puffs, sparks, glows, chunks } = this.vfx.stats();
|
|
12307
12700
|
return puffs + sparks + glows + chunks;
|
|
12308
12701
|
}
|
|
12702
|
+
warmUpProbes() {
|
|
12703
|
+
this.releaseWarmProbes();
|
|
12704
|
+
const { kit } = this.visuals;
|
|
12705
|
+
const T = this.THREE;
|
|
12706
|
+
const geometry = new T.BufferGeometry();
|
|
12707
|
+
geometry.setAttribute("position", new T.BufferAttribute(new Float32Array(6), 3));
|
|
12708
|
+
const materials = [kit.discMaterial().clone(), kit.patchMaterial(8421504).clone(), kit.lineMaterial(0).clone()];
|
|
12709
|
+
const group = new T.Group();
|
|
12710
|
+
group.add(new T.Mesh(kit.discGeometry(), materials[0]), new T.Mesh(kit.discGeometry(), materials[1]), new T.LineSegments(geometry, materials[2]));
|
|
12711
|
+
for (const probe of group.children) probe.frustumCulled = false;
|
|
12712
|
+
this.warmProbes = { group, geometry, materials };
|
|
12713
|
+
return { group, dispose: () => {
|
|
12714
|
+
} };
|
|
12715
|
+
}
|
|
12716
|
+
releaseWarmProbes() {
|
|
12717
|
+
if (!this.warmProbes) return;
|
|
12718
|
+
this.warmProbes.geometry.dispose();
|
|
12719
|
+
for (const material of this.warmProbes.materials) material.dispose();
|
|
12720
|
+
this.warmProbes = void 0;
|
|
12721
|
+
}
|
|
12309
12722
|
rebarCount() {
|
|
12310
12723
|
return this.cages.count;
|
|
12311
12724
|
}
|
|
12312
12725
|
dispose() {
|
|
12726
|
+
this.releaseWarmProbes();
|
|
12313
12727
|
if (this.disposed) return;
|
|
12314
12728
|
this.unsubscribe();
|
|
12315
12729
|
this.clear();
|