@adatechnology/game-destruction-three 0.7.3 → 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 +550 -145
- 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
|
]
|
|
@@ -3578,8 +3629,10 @@ var GameDestructionThree = (() => {
|
|
|
3578
3629
|
aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
|
|
3579
3630
|
/** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
|
|
3580
3631
|
aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
|
|
3581
|
-
/** A hole in the tank: the page's
|
|
3582
|
-
aliasOf(
|
|
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", {})
|
|
3583
3636
|
];
|
|
3584
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];
|
|
3585
3638
|
var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
|
|
@@ -7752,6 +7805,79 @@ void main(){
|
|
|
7752
7805
|
}
|
|
7753
7806
|
};
|
|
7754
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
|
+
}
|
|
7755
7881
|
var clamp5 = (value, min2, max2) => value < min2 ? min2 : value > max2 ? max2 : value;
|
|
7756
7882
|
function layer(container, name, blend) {
|
|
7757
7883
|
const element = container.ownerDocument.createElement("div");
|
|
@@ -7921,73 +8047,6 @@ void main() {
|
|
|
7921
8047
|
}
|
|
7922
8048
|
};
|
|
7923
8049
|
}
|
|
7924
|
-
var SPHERE_DEFAULT_WIDTH = 8;
|
|
7925
|
-
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
7926
|
-
function geometryOf(THREE, particle) {
|
|
7927
|
-
switch (particle.shape) {
|
|
7928
|
-
case "cone":
|
|
7929
|
-
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
|
|
7930
|
-
case "cone_open":
|
|
7931
|
-
return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
|
|
7932
|
-
case "sphere":
|
|
7933
|
-
return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
7934
|
-
case "icosahedron":
|
|
7935
|
-
return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
|
|
7936
|
-
}
|
|
7937
|
-
}
|
|
7938
|
-
function materialOf(THREE, particle) {
|
|
7939
|
-
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
7940
|
-
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
7941
|
-
}
|
|
7942
|
-
function createMeshLayer(THREE, pool) {
|
|
7943
|
-
const group = new THREE.Group();
|
|
7944
|
-
group.name = "workshop-meshes";
|
|
7945
|
-
const drawn = /* @__PURE__ */ new Map();
|
|
7946
|
-
const frame = new THREE.Quaternion();
|
|
7947
|
-
const euler = new THREE.Euler();
|
|
7948
|
-
const turn = new THREE.Quaternion();
|
|
7949
|
-
const offset = new THREE.Vector3();
|
|
7950
|
-
function release(mesh2) {
|
|
7951
|
-
mesh2.removeFromParent();
|
|
7952
|
-
mesh2.geometry.dispose();
|
|
7953
|
-
mesh2.material.dispose();
|
|
7954
|
-
}
|
|
7955
|
-
return {
|
|
7956
|
-
group,
|
|
7957
|
-
sync() {
|
|
7958
|
-
const live = /* @__PURE__ */ new Set();
|
|
7959
|
-
for (const particle of pool.particles) {
|
|
7960
|
-
live.add(particle.id);
|
|
7961
|
-
let mesh2 = drawn.get(particle.id);
|
|
7962
|
-
if (!mesh2) {
|
|
7963
|
-
mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
|
|
7964
|
-
mesh2.frustumCulled = false;
|
|
7965
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7966
|
-
drawn.set(particle.id, mesh2);
|
|
7967
|
-
group.add(mesh2);
|
|
7968
|
-
}
|
|
7969
|
-
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
7970
|
-
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
7971
|
-
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
7972
|
-
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
7973
|
-
mesh2.quaternion.copy(frame).multiply(turn);
|
|
7974
|
-
mesh2.scale.setScalar(particle.scale);
|
|
7975
|
-
mesh2.material.opacity = particle.opacity;
|
|
7976
|
-
}
|
|
7977
|
-
for (const [id, mesh2] of drawn) {
|
|
7978
|
-
if (live.has(id)) continue;
|
|
7979
|
-
release(mesh2);
|
|
7980
|
-
drawn.delete(id);
|
|
7981
|
-
}
|
|
7982
|
-
return pool.count;
|
|
7983
|
-
},
|
|
7984
|
-
dispose() {
|
|
7985
|
-
for (const mesh2 of drawn.values()) release(mesh2);
|
|
7986
|
-
drawn.clear();
|
|
7987
|
-
group.removeFromParent();
|
|
7988
|
-
}
|
|
7989
|
-
};
|
|
7990
|
-
}
|
|
7991
8050
|
var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
|
|
7992
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;}`;
|
|
7993
8052
|
var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
|
|
@@ -8352,6 +8411,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8352
8411
|
const requested = options.vfx;
|
|
8353
8412
|
const world = isWorld(requested) ? requested : createVfx({ profile: options.quality ?? "medium", reducedMotion, ...requested });
|
|
8354
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
|
+
};
|
|
8355
8421
|
const root = new THREE.Group();
|
|
8356
8422
|
options.scene.add(root);
|
|
8357
8423
|
const direction = new THREE.Vector3(...options.lightDirection ?? LAB_LIGHT_DIRECTION).normalize();
|
|
@@ -8468,6 +8534,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8468
8534
|
syncAll();
|
|
8469
8535
|
overlay?.update(screenNow());
|
|
8470
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
|
+
},
|
|
8471
8547
|
applyCamera() {
|
|
8472
8548
|
const shake = screenNow().shake;
|
|
8473
8549
|
if (reducedMotion || !(shake > SHAKE_FLOOR)) return () => {
|
|
@@ -8486,6 +8562,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8486
8562
|
},
|
|
8487
8563
|
screen: screenNow,
|
|
8488
8564
|
dispose() {
|
|
8565
|
+
releaseAnchors();
|
|
8489
8566
|
stopMarks();
|
|
8490
8567
|
overlay?.dispose();
|
|
8491
8568
|
workshopLayers?.dispose();
|
|
@@ -8627,6 +8704,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8627
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]));
|
|
8628
8705
|
mesh2.scale.copy(sc);
|
|
8629
8706
|
mesh2.castShadow = true;
|
|
8707
|
+
mesh2.userData.destructionDebris = true;
|
|
8630
8708
|
this.o.scene.add(mesh2);
|
|
8631
8709
|
const live = {
|
|
8632
8710
|
mesh: mesh2,
|
|
@@ -8834,6 +8912,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8834
8912
|
mesh2.position.set(x, y, z);
|
|
8835
8913
|
mesh2.scale.setScalar(1e-3);
|
|
8836
8914
|
mesh2.renderOrder = 1;
|
|
8915
|
+
mesh2.userData.destructionDebris = true;
|
|
8837
8916
|
this.scene.add(mesh2);
|
|
8838
8917
|
this.spots.push({ mesh: mesh2, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
|
|
8839
8918
|
}
|
|
@@ -9344,12 +9423,68 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9344
9423
|
}
|
|
9345
9424
|
return { min: [center[0] - hx, center[1] - hy, center[2] - hz], max: [center[0] + hx, center[1] + hy, center[2] + hz] };
|
|
9346
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
|
+
};
|
|
9347
9480
|
var materials_default = [
|
|
9348
9481
|
{
|
|
9349
9482
|
id: "steel",
|
|
9350
9483
|
density: 7850,
|
|
9351
9484
|
hpPerKg: 400,
|
|
9352
9485
|
penetration: 110,
|
|
9486
|
+
penetrationClass: "steel",
|
|
9487
|
+
penetrationScale: 1.3,
|
|
9353
9488
|
ductility: 0.9,
|
|
9354
9489
|
fracture: "none",
|
|
9355
9490
|
damageScale: {
|
|
@@ -9379,6 +9514,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9379
9514
|
density: 7200,
|
|
9380
9515
|
hpPerKg: 150,
|
|
9381
9516
|
penetration: 160,
|
|
9517
|
+
penetrationClass: "steel",
|
|
9518
|
+
penetrationScale: 1,
|
|
9382
9519
|
ductility: 0.2,
|
|
9383
9520
|
fracture: "snap",
|
|
9384
9521
|
fragmentSize: [
|
|
@@ -9412,6 +9549,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9412
9549
|
density: 2700,
|
|
9413
9550
|
hpPerKg: 250,
|
|
9414
9551
|
penetration: 60,
|
|
9552
|
+
penetrationClass: "steel",
|
|
9553
|
+
penetrationScale: 3,
|
|
9415
9554
|
ductility: 0.85,
|
|
9416
9555
|
fracture: "none",
|
|
9417
9556
|
damageScale: {
|
|
@@ -9440,6 +9579,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9440
9579
|
density: 8960,
|
|
9441
9580
|
hpPerKg: 300,
|
|
9442
9581
|
penetration: 70,
|
|
9582
|
+
penetrationClass: "steel",
|
|
9583
|
+
penetrationScale: 1.5,
|
|
9443
9584
|
ductility: 0.95,
|
|
9444
9585
|
fracture: "none",
|
|
9445
9586
|
damageScale: {
|
|
@@ -9468,6 +9609,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9468
9609
|
density: 500,
|
|
9469
9610
|
hpPerKg: 90,
|
|
9470
9611
|
penetration: 9,
|
|
9612
|
+
penetrationClass: "wood",
|
|
9613
|
+
penetrationScale: 1.5,
|
|
9471
9614
|
ductility: 0.35,
|
|
9472
9615
|
fracture: "splinter",
|
|
9473
9616
|
fragmentSize: [
|
|
@@ -9504,6 +9647,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9504
9647
|
density: 750,
|
|
9505
9648
|
hpPerKg: 140,
|
|
9506
9649
|
penetration: 14,
|
|
9650
|
+
penetrationClass: "wood",
|
|
9651
|
+
penetrationScale: 1,
|
|
9507
9652
|
ductility: 0.3,
|
|
9508
9653
|
fracture: "splinter",
|
|
9509
9654
|
fragmentSize: [
|
|
@@ -9539,6 +9684,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9539
9684
|
density: 600,
|
|
9540
9685
|
hpPerKg: 70,
|
|
9541
9686
|
penetration: 7,
|
|
9687
|
+
penetrationClass: "wood",
|
|
9688
|
+
penetrationScale: 2,
|
|
9542
9689
|
ductility: 0.3,
|
|
9543
9690
|
fracture: "snap",
|
|
9544
9691
|
fragmentSize: [
|
|
@@ -9574,6 +9721,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9574
9721
|
density: 750,
|
|
9575
9722
|
hpPerKg: 45,
|
|
9576
9723
|
penetration: 6,
|
|
9724
|
+
penetrationClass: "wood",
|
|
9725
|
+
penetrationScale: 2.5,
|
|
9577
9726
|
ductility: 0.15,
|
|
9578
9727
|
fracture: "snap",
|
|
9579
9728
|
fragmentSize: [
|
|
@@ -9702,6 +9851,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9702
9851
|
density: 2300,
|
|
9703
9852
|
hpPerKg: 14,
|
|
9704
9853
|
penetration: 18,
|
|
9854
|
+
penetrationClass: "concrete",
|
|
9855
|
+
penetrationScale: 1,
|
|
9705
9856
|
ductility: 0.03,
|
|
9706
9857
|
fracture: "crumble",
|
|
9707
9858
|
fragmentSize: [
|
|
@@ -9721,13 +9872,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9721
9872
|
"masonry",
|
|
9722
9873
|
"brittle"
|
|
9723
9874
|
],
|
|
9724
|
-
compressive: 444e3
|
|
9875
|
+
compressive: 444e3,
|
|
9876
|
+
strength: {
|
|
9877
|
+
compressiveMPa: 25,
|
|
9878
|
+
tensileMPa: 2.56,
|
|
9879
|
+
elasticGPa: 28
|
|
9880
|
+
}
|
|
9725
9881
|
},
|
|
9726
9882
|
{
|
|
9727
9883
|
id: "brick",
|
|
9728
9884
|
density: 1900,
|
|
9729
9885
|
hpPerKg: 12,
|
|
9730
9886
|
penetration: 14,
|
|
9887
|
+
penetrationClass: "brick",
|
|
9888
|
+
penetrationScale: 1,
|
|
9731
9889
|
ductility: 0.05,
|
|
9732
9890
|
fracture: "crumble",
|
|
9733
9891
|
fragmentSize: [
|
|
@@ -9744,13 +9902,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9744
9902
|
"masonry",
|
|
9745
9903
|
"brittle"
|
|
9746
9904
|
],
|
|
9747
|
-
compressive: 4e4
|
|
9905
|
+
compressive: 4e4,
|
|
9906
|
+
strength: {
|
|
9907
|
+
compressiveMPa: 2.35,
|
|
9908
|
+
tensileMPa: 0.1,
|
|
9909
|
+
elasticGPa: 2.35
|
|
9910
|
+
}
|
|
9748
9911
|
},
|
|
9749
9912
|
{
|
|
9750
9913
|
id: "plaster",
|
|
9751
9914
|
density: 850,
|
|
9752
9915
|
hpPerKg: 18,
|
|
9753
9916
|
penetration: 3,
|
|
9917
|
+
penetrationClass: "brick",
|
|
9918
|
+
penetrationScale: 3,
|
|
9754
9919
|
ductility: 0.05,
|
|
9755
9920
|
fracture: "crumble",
|
|
9756
9921
|
fragmentSize: [
|
|
@@ -9774,6 +9939,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9774
9939
|
density: 2700,
|
|
9775
9940
|
hpPerKg: 25,
|
|
9776
9941
|
penetration: 25,
|
|
9942
|
+
penetrationClass: "concrete",
|
|
9943
|
+
penetrationScale: 0.9,
|
|
9777
9944
|
ductility: 0.03,
|
|
9778
9945
|
fracture: "crumble",
|
|
9779
9946
|
fragmentSize: [
|
|
@@ -10037,6 +10204,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10037
10204
|
density: 650,
|
|
10038
10205
|
hpPerKg: 5,
|
|
10039
10206
|
penetration: 10,
|
|
10207
|
+
penetrationClass: "wood",
|
|
10208
|
+
penetrationScale: 1.2,
|
|
10040
10209
|
ductility: 0.5,
|
|
10041
10210
|
fracture: "splinter",
|
|
10042
10211
|
fragmentSize: [
|
|
@@ -10288,6 +10457,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10288
10457
|
for (const [key, id] of HINTS) if (n.includes(key)) return id;
|
|
10289
10458
|
return void 0;
|
|
10290
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);
|
|
10291
10466
|
var partThickness = (part) => {
|
|
10292
10467
|
const dims = shapeDims(part.shape);
|
|
10293
10468
|
const min2 = Math.min(dims[0], dims[1], dims[2]);
|
|
@@ -10315,6 +10490,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10315
10490
|
};
|
|
10316
10491
|
|
|
10317
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
|
+
}
|
|
10318
10498
|
function partHintOf(value) {
|
|
10319
10499
|
if (typeof value === "string") return value ? { id: value } : null;
|
|
10320
10500
|
if (!value || typeof value !== "object") return null;
|
|
@@ -10335,11 +10515,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10335
10515
|
function sizeOf(box) {
|
|
10336
10516
|
return [box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z];
|
|
10337
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
|
+
}
|
|
10338
10525
|
function specFromObject3D(THREE, root, options = {}) {
|
|
10339
|
-
const
|
|
10526
|
+
const fallback2 = options.fallback ?? "plastic";
|
|
10340
10527
|
const minSize = options.minSize ?? 0.015;
|
|
10341
|
-
root
|
|
10342
|
-
const inv = root.matrixWorld.clone().invert();
|
|
10528
|
+
const inv = specFrameInverse(THREE, root);
|
|
10343
10529
|
const entries = [];
|
|
10344
10530
|
const byKey = /* @__PURE__ */ new Map();
|
|
10345
10531
|
const visit = (o) => {
|
|
@@ -10353,11 +10539,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10353
10539
|
const matName = mats[0]?.name ?? "";
|
|
10354
10540
|
const ud = o.userData ?? {};
|
|
10355
10541
|
const hint = partHintOf(ud.destructionPart);
|
|
10356
|
-
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;
|
|
10357
10543
|
const explicit = hint?.id ?? null;
|
|
10358
10544
|
const hollow = ud.hollow === true || /hollow/i.test(o.name);
|
|
10545
|
+
const skin = ud.destructionSkin === true;
|
|
10359
10546
|
const fill = options.fillOf?.(o, material);
|
|
10360
10547
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10548
|
+
const contains = containsOf(ud.destructionContains);
|
|
10361
10549
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10362
10550
|
if (existing) {
|
|
10363
10551
|
const before = sizeOf(existing.box);
|
|
@@ -10367,9 +10555,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10367
10555
|
existing.meshes.push(o);
|
|
10368
10556
|
if (ns[0] * ns[1] * ns[2] > bv) existing.material = material;
|
|
10369
10557
|
existing.hollow = existing.hollow || hollow;
|
|
10558
|
+
existing.skin = existing.skin && skin;
|
|
10370
10559
|
if (fill !== void 0) existing.fill = fill;
|
|
10560
|
+
if (contains) existing.contains = contains;
|
|
10371
10561
|
} else {
|
|
10372
|
-
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 };
|
|
10373
10563
|
entries.push(e);
|
|
10374
10564
|
if (explicit) byKey.set(explicit, e);
|
|
10375
10565
|
}
|
|
@@ -10388,7 +10578,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10388
10578
|
const largest = (e) => Math.max(...sizeOf(e.box));
|
|
10389
10579
|
const kept = [];
|
|
10390
10580
|
const tiny = [];
|
|
10391
|
-
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);
|
|
10392
10582
|
for (const t of tiny) {
|
|
10393
10583
|
let best = null;
|
|
10394
10584
|
let bestV = 0;
|
|
@@ -10442,11 +10632,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10442
10632
|
part.thickness = Math.max(minSide, 5e-4);
|
|
10443
10633
|
}
|
|
10444
10634
|
if (e.tags) part.tags = [...e.tags];
|
|
10635
|
+
if (e.contains) part.contains = { ...e.contains };
|
|
10445
10636
|
const ao = options.anchorBelow ?? -1;
|
|
10446
10637
|
if (ao >= 0 && e.box.min.y <= ao) part.anchored = true;
|
|
10447
10638
|
parts.push(part);
|
|
10448
10639
|
meshesOf[id] = e.meshes;
|
|
10449
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
|
+
}
|
|
10450
10650
|
const spec = { parts };
|
|
10451
10651
|
if (options.name !== void 0) spec.name = options.name;
|
|
10452
10652
|
else if (root.name) spec.name = root.name;
|
|
@@ -10498,6 +10698,46 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10498
10698
|
return last[1];
|
|
10499
10699
|
}
|
|
10500
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
|
+
|
|
10501
10741
|
// src/visual.ts
|
|
10502
10742
|
var POS_ATTR = "position";
|
|
10503
10743
|
function positionAttr(geo) {
|
|
@@ -10562,6 +10802,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10562
10802
|
}
|
|
10563
10803
|
};
|
|
10564
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;
|
|
10565
10811
|
var Visuals = class {
|
|
10566
10812
|
constructor(THREE, lowQuality, decalCap) {
|
|
10567
10813
|
__publicField(this, "THREE", THREE);
|
|
@@ -10730,14 +10976,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10730
10976
|
* Push the vertices of the mesh within `dent.radius` of `worldPoint` inwards along `worldNormal` by `depth * (1 - (d/r)^2)^2`.
|
|
10731
10977
|
* Returns false when nothing was changed (too far, low quality on a heavy mesh, or no editable positions).
|
|
10732
10978
|
*/
|
|
10733
|
-
applyDent(mv, worldPoint, worldNormal, dent) {
|
|
10979
|
+
applyDent(mv, worldPoint, worldNormal, dent, reachWorld = 0) {
|
|
10734
10980
|
const mesh2 = mv.mesh;
|
|
10735
10981
|
if (this.lowQuality && this.vertexCount(mv) > 2e4) return false;
|
|
10736
10982
|
mesh2.updateWorldMatrix(true, false);
|
|
10737
10983
|
const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10738
10984
|
const lp = mesh2.worldToLocal(this.a.copy(worldPoint));
|
|
10739
10985
|
const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
|
|
10740
|
-
const r = dent.radius / s;
|
|
10986
|
+
const r = Math.max(dent.radius, reachWorld) / s;
|
|
10741
10987
|
const depth = dent.depth / s;
|
|
10742
10988
|
const geo = mesh2.geometry;
|
|
10743
10989
|
let bb;
|
|
@@ -10750,34 +10996,110 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10750
10996
|
if (!this.ownGeometry(mv)) return false;
|
|
10751
10997
|
const work = mv.host ? mv.host.work : mv.work;
|
|
10752
10998
|
const pos = positionAttr(mesh2.geometry);
|
|
10753
|
-
const r2 = r * r;
|
|
10754
|
-
let touched = false;
|
|
10755
|
-
const px = lp.x;
|
|
10756
|
-
const py = lp.y;
|
|
10757
|
-
const pz = lp.z;
|
|
10758
|
-
const nx = ln.x;
|
|
10759
|
-
const ny = ln.y;
|
|
10760
|
-
const nz = ln.z;
|
|
10761
10999
|
const from = mv.range ? mv.range[0] * 3 : 0;
|
|
10762
11000
|
const to = mv.range ? mv.range[1] * 3 : work.length;
|
|
11001
|
+
const reach = r;
|
|
11002
|
+
const reach2 = reach * reach;
|
|
10763
11003
|
for (let i = from; i < to; i += 3) {
|
|
10764
|
-
const dx = work[i] -
|
|
10765
|
-
const dy = work[i + 1] -
|
|
10766
|
-
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;
|
|
10767
11007
|
const d2 = dx * dx + dy * dy + dz * dz;
|
|
10768
|
-
if (d2 >=
|
|
10769
|
-
const f = 1 - d2 /
|
|
11008
|
+
if (d2 >= reach2) continue;
|
|
11009
|
+
const f = 1 - d2 / reach2;
|
|
10770
11010
|
const k = depth * f * f;
|
|
10771
|
-
work[i] -=
|
|
10772
|
-
work[i + 1] -=
|
|
10773
|
-
work[i + 2] -=
|
|
10774
|
-
touched = true;
|
|
11011
|
+
work[i] -= ln.x * k;
|
|
11012
|
+
work[i + 1] -= ln.y * k;
|
|
11013
|
+
work[i + 2] -= ln.z * k;
|
|
10775
11014
|
}
|
|
10776
|
-
if (!touched) return false;
|
|
10777
11015
|
mv.touched = true;
|
|
10778
11016
|
this.writePositions(mv, pos);
|
|
10779
11017
|
return true;
|
|
10780
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
|
+
}
|
|
10781
11103
|
/** Copy `work` (dents) into the geometry, adding the melt sag when the mesh is melting. */
|
|
10782
11104
|
writePositions(mv, pos) {
|
|
10783
11105
|
if (mv.host) {
|
|
@@ -10824,6 +11146,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10824
11146
|
return { s, pos, quat: quat2 };
|
|
10825
11147
|
}
|
|
10826
11148
|
addDecal(mv, decal2) {
|
|
11149
|
+
decal2.obj.userData.destructionDebris = true;
|
|
10827
11150
|
mv.mesh.add(decal2.obj);
|
|
10828
11151
|
mv.decals.push(decal2);
|
|
10829
11152
|
this.decalCount++;
|
|
@@ -11408,6 +11731,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11408
11731
|
__publicField(this, "tmpQ");
|
|
11409
11732
|
__publicField(this, "v1");
|
|
11410
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");
|
|
11411
11736
|
this.world = o.world;
|
|
11412
11737
|
this.scene = o.scene;
|
|
11413
11738
|
this.quality = o.quality ?? "medium";
|
|
@@ -11505,6 +11830,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11505
11830
|
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes, hosts));
|
|
11506
11831
|
}
|
|
11507
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
|
+
}
|
|
11508
11851
|
}
|
|
11509
11852
|
makePartVis(home, id, material, meshes, hosts) {
|
|
11510
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" };
|
|
@@ -11518,13 +11861,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11518
11861
|
specParts = void 0;
|
|
11519
11862
|
}
|
|
11520
11863
|
if (!specParts) return;
|
|
11521
|
-
root
|
|
11864
|
+
const frame = specFrameInverse(this.THREE, root);
|
|
11522
11865
|
const e = new this.THREE.Euler();
|
|
11523
11866
|
const q = new this.THREE.Quaternion();
|
|
11524
11867
|
for (const m of meshes) {
|
|
11525
11868
|
this.box.setFromObject(m);
|
|
11526
11869
|
if (this.box.isEmpty()) continue;
|
|
11527
|
-
const c =
|
|
11870
|
+
const c = this.box.getCenter(this.v1).applyMatrix4(frame);
|
|
11528
11871
|
let best = null;
|
|
11529
11872
|
let bestVol = Infinity;
|
|
11530
11873
|
for (const p of specParts) {
|
|
@@ -11561,10 +11904,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11561
11904
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11562
11905
|
// Pieces
|
|
11563
11906
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11564
|
-
/** 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). */
|
|
11565
11908
|
measure(root, meshes) {
|
|
11566
|
-
root
|
|
11567
|
-
const inv = root.matrixWorld.clone().invert();
|
|
11909
|
+
const inv = specFrameInverse(this.THREE, root);
|
|
11568
11910
|
const total = new this.THREE.Box3();
|
|
11569
11911
|
const m4 = new this.THREE.Matrix4();
|
|
11570
11912
|
const lb = new this.THREE.Box3();
|
|
@@ -11639,6 +11981,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11639
11981
|
dt = clamp6(dt, 0, 0.1);
|
|
11640
11982
|
this.time += dt;
|
|
11641
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);
|
|
11642
11985
|
this.updateActive(dt);
|
|
11643
11986
|
this.updateLeaks(dt);
|
|
11644
11987
|
this.pieceSys.update(dt);
|
|
@@ -11657,6 +12000,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11657
12000
|
}
|
|
11658
12001
|
}
|
|
11659
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
|
+
}
|
|
11660
12024
|
syncTransforms() {
|
|
11661
12025
|
for (const b of this.bounds.values()) this.syncOne(b);
|
|
11662
12026
|
}
|
|
@@ -11711,6 +12075,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11711
12075
|
case "detach":
|
|
11712
12076
|
this.onDetach(ev);
|
|
11713
12077
|
break;
|
|
12078
|
+
case "settle":
|
|
12079
|
+
this.onSettle(ev.object);
|
|
12080
|
+
break;
|
|
11714
12081
|
case "collapse":
|
|
11715
12082
|
this.splitLoose(ev.object);
|
|
11716
12083
|
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
@@ -11802,7 +12169,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11802
12169
|
same.direction = ev.direction;
|
|
11803
12170
|
} else {
|
|
11804
12171
|
if (this.leaks.length >= 24) this.leaks.shift();
|
|
11805
|
-
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 });
|
|
11806
12173
|
}
|
|
11807
12174
|
void key;
|
|
11808
12175
|
break;
|
|
@@ -11879,10 +12246,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11879
12246
|
const def = this.world.materials.get(pv.material);
|
|
11880
12247
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
11881
12248
|
const wn = this.worldDir(b, ev.dent.normal);
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
}
|
|
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);
|
|
11886
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);
|
|
11887
12253
|
this.sound(impactKey(def), ev.worldPoint, clamp6(0.3 + ev.dent.depth * 12, 0.3, 1));
|
|
11888
12254
|
}
|
|
@@ -12078,6 +12444,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12078
12444
|
}
|
|
12079
12445
|
}
|
|
12080
12446
|
this.bounds.set(newId, nb);
|
|
12447
|
+
for (const pv of nb.parts.values()) this.tagPart(pv, newId);
|
|
12081
12448
|
const meshes = this.meshesOf(nb);
|
|
12082
12449
|
if (meshes.length === 0) return;
|
|
12083
12450
|
const { half, center } = this.measure(group, meshes);
|
|
@@ -12105,24 +12472,40 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12105
12472
|
const report = options.report;
|
|
12106
12473
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
12107
12474
|
const energy = ev.energy * confinement;
|
|
12108
|
-
|
|
12475
|
+
const reachOf = (objectId) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? 1;
|
|
12476
|
+
const shove = (pc) => {
|
|
12109
12477
|
this.pieceSys.centerOf(pc, c);
|
|
12110
|
-
const
|
|
12111
|
-
|
|
12112
|
-
const dz = c.z - p[2];
|
|
12113
|
-
const d = Math.hypot(dx, dy, dz);
|
|
12114
|
-
if (d > ev.radius) continue;
|
|
12115
|
-
const fall = (1 - d / ev.radius) ** 2;
|
|
12116
|
-
const reach = report?.find((r) => r.object === pc.objectId)?.blast?.shelter ?? 1;
|
|
12117
|
-
const speed = Math.min(30, Math.sqrt(2 * energy * reach * 0.02 / pc.mass)) * fall;
|
|
12118
|
-
const inv = d > 1e-4 ? 1 / d : 0;
|
|
12119
|
-
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;
|
|
12120
12480
|
const spread = pc.halfExtents[0];
|
|
12121
|
-
|
|
12122
|
-
|
|
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);
|
|
12123
12486
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
12124
12487
|
this.sound("explode", p, clamp6(0.5 + ev.radius / 10, 0.5, 1));
|
|
12125
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
|
+
}
|
|
12126
12509
|
playExplosion(ev, options) {
|
|
12127
12510
|
const named = options.effect ?? ev.effect;
|
|
12128
12511
|
const direction = options.direction;
|
|
@@ -12223,7 +12606,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12223
12606
|
for (let i = this.leaks.length - 1; i >= 0; i--) {
|
|
12224
12607
|
const l = this.leaks[i];
|
|
12225
12608
|
const ps = this.world.part(l.object, l.part);
|
|
12226
|
-
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)) {
|
|
12227
12610
|
this.leaks.splice(i, 1);
|
|
12228
12611
|
continue;
|
|
12229
12612
|
}
|
|
@@ -12274,6 +12657,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12274
12657
|
for (const [pid, pv] of [...b.parts]) {
|
|
12275
12658
|
if (pv.home !== home) continue;
|
|
12276
12659
|
this.visuals.restorePart(pv);
|
|
12660
|
+
this.tagPart(pv, home);
|
|
12277
12661
|
if (b !== homeBound && homeBound) {
|
|
12278
12662
|
b.parts.delete(pid);
|
|
12279
12663
|
homeBound.parts.set(pid, pv);
|
|
@@ -12315,10 +12699,31 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12315
12699
|
const { puffs, sparks, glows, chunks } = this.vfx.stats();
|
|
12316
12700
|
return puffs + sparks + glows + chunks;
|
|
12317
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
|
+
}
|
|
12318
12722
|
rebarCount() {
|
|
12319
12723
|
return this.cages.count;
|
|
12320
12724
|
}
|
|
12321
12725
|
dispose() {
|
|
12726
|
+
this.releaseWarmProbes();
|
|
12322
12727
|
if (this.disposed) return;
|
|
12323
12728
|
this.unsubscribe();
|
|
12324
12729
|
this.clear();
|