@adatechnology/game-destruction-three 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +34 -3
- package/dist/index.global.js +585 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +395 -20
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.global.js
CHANGED
|
@@ -27,11 +27,14 @@ var GameDestructionThree = (() => {
|
|
|
27
27
|
DEFAULT_FIRE: () => DEFAULT_FIRE,
|
|
28
28
|
DESTRUCTION_SOUND_BANK: () => DESTRUCTION_SOUND_BANK,
|
|
29
29
|
DESTRUCTION_SOUND_KEYS: () => DESTRUCTION_SOUND_KEYS,
|
|
30
|
+
DRAG_SOUNDS: () => DRAG_SOUNDS,
|
|
30
31
|
EXPLOSION_BOOM: () => EXPLOSION_BOOM,
|
|
31
32
|
SCENE_SOUNDS: () => SCENE_SOUNDS,
|
|
32
33
|
createDestructionRenderer: () => createDestructionRenderer,
|
|
33
34
|
createDestructionSound: () => createDestructionSound,
|
|
35
|
+
dragSoundKey: () => dragSoundKey,
|
|
34
36
|
heatColor: () => heatColor,
|
|
37
|
+
impactKey: () => impactKey,
|
|
35
38
|
specFromObject3D: () => specFromObject3D
|
|
36
39
|
});
|
|
37
40
|
|
|
@@ -59,6 +62,13 @@ var GameDestructionThree = (() => {
|
|
|
59
62
|
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
60
63
|
return object;
|
|
61
64
|
}
|
|
65
|
+
function kitOriginOf(object) {
|
|
66
|
+
for (let node = object; node; node = node.parent) {
|
|
67
|
+
const origin = node.userData?.[KIT_ORIGIN_KEY];
|
|
68
|
+
if (origin && typeof origin.owner === "string") return origin;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
62
72
|
|
|
63
73
|
// ../game-vfx/dist/index.js
|
|
64
74
|
var __defProp2 = Object.defineProperty;
|
|
@@ -8582,6 +8592,44 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8582
8592
|
}
|
|
8583
8593
|
var LIGHT_BUDGET = EVENT_BUDGET.blastLights + EVENT_BUDGET.fireLights;
|
|
8584
8594
|
|
|
8595
|
+
// src/fitted-boxes.ts
|
|
8596
|
+
var SLICE_LENGTH = 0.12;
|
|
8597
|
+
var MAX_SLICES = 12;
|
|
8598
|
+
var SNUG_RATIO = 1.1;
|
|
8599
|
+
function pushFittedBoxes(local2, matrix, out) {
|
|
8600
|
+
const e = matrix.elements;
|
|
8601
|
+
const sizes = [0, 1, 2].map((k) => k === 0 ? local2.max.x - local2.min.x : k === 1 ? local2.max.y - local2.min.y : local2.max.z - local2.min.z);
|
|
8602
|
+
const lengths = [0, 1, 2].map((k) => sizes[k] * Math.hypot(e[k * 4], e[k * 4 + 1], e[k * 4 + 2]));
|
|
8603
|
+
const longest = lengths.indexOf(Math.max(...lengths));
|
|
8604
|
+
const whole = worldBoxOf(local2, matrix, longest, 0, 1);
|
|
8605
|
+
const span = (whole[3] - whole[0]) * (whole[4] - whole[1]) * (whole[5] - whole[2]);
|
|
8606
|
+
const volume = lengths[0] * lengths[1] * lengths[2];
|
|
8607
|
+
const slices = span <= volume * SNUG_RATIO ? 1 : Math.min(MAX_SLICES, Math.ceil(lengths[longest] / SLICE_LENGTH));
|
|
8608
|
+
if (slices <= 1) {
|
|
8609
|
+
out.push(...whole);
|
|
8610
|
+
return;
|
|
8611
|
+
}
|
|
8612
|
+
for (let i = 0; i < slices; i++) out.push(...worldBoxOf(local2, matrix, longest, i / slices, (i + 1) / slices));
|
|
8613
|
+
}
|
|
8614
|
+
function worldBoxOf(local2, matrix, axis2, from, to) {
|
|
8615
|
+
const e = matrix.elements;
|
|
8616
|
+
const low = [local2.min.x, local2.min.y, local2.min.z];
|
|
8617
|
+
const high = [local2.max.x, local2.max.y, local2.max.z];
|
|
8618
|
+
const start = low[axis2];
|
|
8619
|
+
const size = high[axis2] - start;
|
|
8620
|
+
low[axis2] = start + size * from;
|
|
8621
|
+
high[axis2] = start + size * to;
|
|
8622
|
+
const box = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
8623
|
+
for (const x of [low[0], high[0]]) for (const y of [low[1], high[1]]) for (const z of [low[2], high[2]]) {
|
|
8624
|
+
for (let k = 0; k < 3; k++) {
|
|
8625
|
+
const v = e[k] * x + e[k + 4] * y + e[k + 8] * z + e[k + 12];
|
|
8626
|
+
box[k] = Math.min(box[k], v);
|
|
8627
|
+
box[k + 3] = Math.max(box[k + 3], v);
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
return box;
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8585
8633
|
// src/math.ts
|
|
8586
8634
|
function createRng(seed) {
|
|
8587
8635
|
let a = seed >>> 0;
|
|
@@ -8623,12 +8671,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8623
8671
|
var SHEET_MAX = 4e-3;
|
|
8624
8672
|
var SHEET_AIR = 2.5;
|
|
8625
8673
|
var SHEET_SWAY = 3;
|
|
8674
|
+
var BIG_LENGTH = 0.2;
|
|
8675
|
+
var BIG_WIDTH = 0.03;
|
|
8676
|
+
var isBig = (size) => {
|
|
8677
|
+
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
8678
|
+
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
8679
|
+
};
|
|
8626
8680
|
var FragmentSystem = class {
|
|
8627
8681
|
constructor(THREE, o) {
|
|
8628
8682
|
__publicField(this, "THREE", THREE);
|
|
8629
8683
|
__publicField(this, "o", o);
|
|
8630
8684
|
__publicField(this, "list", []);
|
|
8631
8685
|
__publicField(this, "box");
|
|
8686
|
+
/** The mesh box of a fragment before its scale. */
|
|
8687
|
+
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
8632
8688
|
__publicField(this, "shardGeos", []);
|
|
8633
8689
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
8634
8690
|
__publicField(this, "q");
|
|
@@ -8637,6 +8693,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8637
8693
|
__publicField(this, "v");
|
|
8638
8694
|
__publicField(this, "w");
|
|
8639
8695
|
__publicField(this, "rng", createRng(1374496523));
|
|
8696
|
+
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
8697
|
+
__publicField(this, "tick", 0);
|
|
8640
8698
|
__publicField(this, "markGeometry", null);
|
|
8641
8699
|
__publicField(this, "markMaterial", null);
|
|
8642
8700
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -8704,8 +8762,28 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8704
8762
|
mesh2.add(disc);
|
|
8705
8763
|
}
|
|
8706
8764
|
}
|
|
8707
|
-
/**
|
|
8765
|
+
/**
|
|
8766
|
+
* Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget: over it the oldest fragment still showing fades out to make room, and a burst
|
|
8767
|
+
* bigger than the budget (every live fragment was made in this same frame) does not make the rest at all, so a blast never builds a thousand meshes only to fade them
|
|
8768
|
+
* out. Returns null when the fragment was not made.
|
|
8769
|
+
*/
|
|
8708
8770
|
spawn(f, material, home, maxAllowed) {
|
|
8771
|
+
if (this.list.length >= maxAllowed) {
|
|
8772
|
+
let alive = 0;
|
|
8773
|
+
let oldest;
|
|
8774
|
+
let oldestSmall;
|
|
8775
|
+
for (const l of this.list) {
|
|
8776
|
+
if (l.dying) continue;
|
|
8777
|
+
alive++;
|
|
8778
|
+
oldest ?? (oldest = l);
|
|
8779
|
+
if (!l.body) oldestSmall ?? (oldestSmall = l);
|
|
8780
|
+
}
|
|
8781
|
+
oldest = oldestSmall ?? oldest;
|
|
8782
|
+
if (alive >= maxAllowed) {
|
|
8783
|
+
if (!oldest || oldest.born === this.tick) return null;
|
|
8784
|
+
oldest.dying = true;
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
8709
8787
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
8710
8788
|
const mesh2 = new this.THREE.Mesh(geo, material);
|
|
8711
8789
|
mesh2.position.set(f.center[0], f.center[1], f.center[2]);
|
|
@@ -8729,19 +8807,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8729
8807
|
rest: 0,
|
|
8730
8808
|
fade: 1,
|
|
8731
8809
|
dying: false,
|
|
8732
|
-
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0
|
|
8810
|
+
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
8811
|
+
body: null,
|
|
8812
|
+
born: this.tick
|
|
8733
8813
|
};
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8814
|
+
if (f.shape !== "shard" && isBig(f.size)) {
|
|
8815
|
+
live.body = {
|
|
8816
|
+
mass: f.mass,
|
|
8817
|
+
push: (x, z) => {
|
|
8818
|
+
live.vel.x = x;
|
|
8819
|
+
live.vel.z = z;
|
|
8820
|
+
live.vel.y = Math.max(live.vel.y, 0.3);
|
|
8821
|
+
live.asleep = false;
|
|
8822
|
+
live.rest = 0;
|
|
8823
|
+
}
|
|
8824
|
+
};
|
|
8741
8825
|
}
|
|
8826
|
+
this.list.push(live);
|
|
8742
8827
|
this.addMarks(mesh2, f);
|
|
8743
8828
|
return mesh2;
|
|
8744
8829
|
}
|
|
8830
|
+
/** The boxes of the big fragments around (x, z). */
|
|
8831
|
+
solidsNear(x, z, radius, found) {
|
|
8832
|
+
const boxes = [];
|
|
8833
|
+
for (const f of this.list) {
|
|
8834
|
+
if (!f.body || f.dying) continue;
|
|
8835
|
+
const reach = f.half.length() + radius;
|
|
8836
|
+
if (Math.abs(f.mesh.position.x - x) > reach || Math.abs(f.mesh.position.z - z) > reach) continue;
|
|
8837
|
+
f.mesh.updateMatrixWorld();
|
|
8838
|
+
boxes.length = 0;
|
|
8839
|
+
pushFittedBoxes(this.unitBox, f.mesh.matrixWorld, boxes);
|
|
8840
|
+
for (let i = 0; i < boxes.length; i += 6) {
|
|
8841
|
+
if (x < boxes[i] - radius || x > boxes[i + 3] + radius || z < boxes[i + 2] - radius || z > boxes[i + 5] + radius) continue;
|
|
8842
|
+
found(f.home, [boxes[i], boxes[i + 1], boxes[i + 2]], [boxes[i + 3], boxes[i + 4], boxes[i + 5]], f.body);
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
}
|
|
8745
8846
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
8746
8847
|
blast(point, radius, speed) {
|
|
8747
8848
|
for (const f of this.list) {
|
|
@@ -8757,7 +8858,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8757
8858
|
f.rest = 0;
|
|
8758
8859
|
}
|
|
8759
8860
|
}
|
|
8861
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
8862
|
+
wakeRegion(x, z, radius) {
|
|
8863
|
+
for (const f of this.list) {
|
|
8864
|
+
if (!f.asleep || Math.hypot(f.mesh.position.x - x, f.mesh.position.z - z) > radius) continue;
|
|
8865
|
+
f.asleep = false;
|
|
8866
|
+
f.rest = 0;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8760
8869
|
update(dt) {
|
|
8870
|
+
this.tick++;
|
|
8761
8871
|
const g = this.o.gravity;
|
|
8762
8872
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
8763
8873
|
const f = this.list[i];
|
|
@@ -8772,7 +8882,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8772
8882
|
}
|
|
8773
8883
|
if (f.asleep) {
|
|
8774
8884
|
f.rest += dt;
|
|
8775
|
-
if (this.o.life > 0 && f.rest > this.o.life) f.dying = true;
|
|
8885
|
+
if (this.o.life > 0 && f.rest > this.o.life && !f.body) f.dying = true;
|
|
8776
8886
|
continue;
|
|
8777
8887
|
}
|
|
8778
8888
|
this.step(f, dt, g);
|
|
@@ -8846,7 +8956,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8846
8956
|
let small = 0;
|
|
8847
8957
|
for (let i = 1; i < 3; i++) if (half[i] < half[small]) small = i;
|
|
8848
8958
|
let axis2 = small;
|
|
8849
|
-
if (Math.abs(up[small]) < 0.35) {
|
|
8959
|
+
if (Math.abs(up[small]) < 0.35 && !f.body) {
|
|
8850
8960
|
axis2 = 0;
|
|
8851
8961
|
for (let i = 1; i < 3; i++) if (Math.abs(up[i]) > Math.abs(up[axis2])) axis2 = i;
|
|
8852
8962
|
}
|
|
@@ -8972,6 +9082,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8972
9082
|
var SLOP = 2e-3;
|
|
8973
9083
|
var NARROW = 0.3;
|
|
8974
9084
|
var TOPPLE = 3;
|
|
9085
|
+
var TIP_ACCELERATION = 30;
|
|
9086
|
+
var TIP_LYING = 0.25;
|
|
9087
|
+
var TIP_SECONDS = 3;
|
|
9088
|
+
var STEP = 0.03;
|
|
9089
|
+
var boxTopAt = (s, x, z) => {
|
|
9090
|
+
const e = s.e;
|
|
9091
|
+
const dx = x - s.c[0], dz = z - s.c[2];
|
|
9092
|
+
let t0 = -Infinity, t1 = Infinity;
|
|
9093
|
+
for (let k = 0; k < 3; k++) {
|
|
9094
|
+
const p = e[k * 4] * dx + e[k * 4 + 2] * dz;
|
|
9095
|
+
const d = e[k * 4 + 1];
|
|
9096
|
+
const h = s.half[k];
|
|
9097
|
+
if (Math.abs(d) < 1e-9) {
|
|
9098
|
+
if (Math.abs(p) > h) return -Infinity;
|
|
9099
|
+
continue;
|
|
9100
|
+
}
|
|
9101
|
+
let a = (-h - p) / d, b = (h - p) / d;
|
|
9102
|
+
if (a > b) [a, b] = [b, a];
|
|
9103
|
+
t0 = Math.max(t0, a);
|
|
9104
|
+
t1 = Math.min(t1, b);
|
|
9105
|
+
if (t0 > t1) return -Infinity;
|
|
9106
|
+
}
|
|
9107
|
+
return s.c[1] + t1;
|
|
9108
|
+
};
|
|
8975
9109
|
var PieceSystem = class {
|
|
8976
9110
|
constructor(THREE, o) {
|
|
8977
9111
|
__publicField(this, "THREE", THREE);
|
|
@@ -8983,6 +9117,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8983
9117
|
__publicField(this, "dq");
|
|
8984
9118
|
__publicField(this, "m");
|
|
8985
9119
|
__publicField(this, "ax");
|
|
9120
|
+
__publicField(this, "solids", []);
|
|
8986
9121
|
this.p = new THREE.Vector3();
|
|
8987
9122
|
this.q = new THREE.Quaternion();
|
|
8988
9123
|
this.dq = new THREE.Quaternion();
|
|
@@ -8992,6 +9127,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8992
9127
|
get(objectId) {
|
|
8993
9128
|
return this.list.find((b) => b.objectId === objectId) ?? null;
|
|
8994
9129
|
}
|
|
9130
|
+
/** Sets a piece falling over, about the world axes x and z (a unit axis): its feet are thinner than the box that stands for it, so the box alone would hold it up. */
|
|
9131
|
+
tip(body, x, z) {
|
|
9132
|
+
body.tipping = { x, z, seconds: TIP_SECONDS };
|
|
9133
|
+
body.sleeping = false;
|
|
9134
|
+
}
|
|
8995
9135
|
create(objectId, root, halfExtents, localCenter, mass, velocity, angular) {
|
|
8996
9136
|
const existing = this.get(objectId);
|
|
8997
9137
|
if (existing) {
|
|
@@ -9005,6 +9145,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9005
9145
|
}
|
|
9006
9146
|
const body = {
|
|
9007
9147
|
objectId,
|
|
9148
|
+
tipping: null,
|
|
9008
9149
|
root,
|
|
9009
9150
|
velocity: velocity ? [...velocity] : [0, 0, 0],
|
|
9010
9151
|
angularVelocity: angular ? [...angular] : [0, 0, 0],
|
|
@@ -9074,8 +9215,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9074
9215
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
9075
9216
|
return [e[0] * lx + e[4] * ly + e[8] * lz, e[1] * lx + e[5] * ly + e[9] * lz, e[2] * lx + e[6] * ly + e[10] * lz];
|
|
9076
9217
|
}
|
|
9218
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
9219
|
+
wakeRegion(x, z, radius) {
|
|
9220
|
+
for (const b of this.list) {
|
|
9221
|
+
if (!b.sleeping || b.held || Math.hypot(b.root.position.x - x, b.root.position.z - z) > radius) continue;
|
|
9222
|
+
b.sleeping = false;
|
|
9223
|
+
b.still = 0;
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
9077
9226
|
update(dt) {
|
|
9078
9227
|
this.time += dt;
|
|
9228
|
+
this.solids = this.list.map((body) => {
|
|
9229
|
+
const c = this.centerOf(body, new this.THREE.Vector3());
|
|
9230
|
+
const h = body.halfExtents;
|
|
9231
|
+
return { body, c: [c.x, c.y, c.z], e: [...new this.THREE.Matrix4().makeRotationFromQuaternion(body.root.quaternion).elements], half: h, r: Math.hypot(h[0], h[1], h[2]) };
|
|
9232
|
+
});
|
|
9079
9233
|
for (const b of this.list) {
|
|
9080
9234
|
if (b.held) {
|
|
9081
9235
|
b.velocity = [0, 0, 0];
|
|
@@ -9089,6 +9243,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9089
9243
|
}
|
|
9090
9244
|
this.separate(dt);
|
|
9091
9245
|
}
|
|
9246
|
+
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
9247
|
+
supportAt(self, x, y, z, step) {
|
|
9248
|
+
let top = this.o.groundAt(x, z);
|
|
9249
|
+
for (const s of this.solids) {
|
|
9250
|
+
if (s.body === self || s.body.held || Math.abs(x - s.c[0]) > s.r || Math.abs(z - s.c[2]) > s.r) continue;
|
|
9251
|
+
const t = boxTopAt(s, x, z);
|
|
9252
|
+
if (t > top && t <= y + step) top = t;
|
|
9253
|
+
}
|
|
9254
|
+
const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
|
|
9255
|
+
return Math.max(top, fixed);
|
|
9256
|
+
}
|
|
9092
9257
|
integrate(b, dt) {
|
|
9093
9258
|
const c = this.centerOf(b, this.p);
|
|
9094
9259
|
const pos = [c.x, c.y, c.z];
|
|
@@ -9127,8 +9292,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9127
9292
|
const sy = i & 2 ? hy : -hy;
|
|
9128
9293
|
const sz = i & 4 ? hz : -hz;
|
|
9129
9294
|
const r = [e[0] * sx + e[4] * sy + e[8] * sz, e[1] * sx + e[5] * sy + e[9] * sz, e[2] * sx + e[6] * sy + e[10] * sz];
|
|
9130
|
-
const
|
|
9131
|
-
const
|
|
9295
|
+
const cy = pos[1] + r[1];
|
|
9296
|
+
const gy = this.supportAt(b, pos[0] + r[0], cy, pos[2] + r[2], STEP + Math.max(0, -v[1]) * h * 2);
|
|
9297
|
+
const pen = gy - cy;
|
|
9132
9298
|
if (pen > -SLOP) {
|
|
9133
9299
|
rs.push(r);
|
|
9134
9300
|
if (pen > maxPen) maxPen = pen;
|
|
@@ -9151,7 +9317,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9151
9317
|
}
|
|
9152
9318
|
const lx = pos[0] + sx / rs.length;
|
|
9153
9319
|
const lz = pos[2] + sz / rs.length;
|
|
9154
|
-
landPoint = [lx,
|
|
9320
|
+
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
9155
9321
|
}
|
|
9156
9322
|
for (let it = 0; it < 4; it++) {
|
|
9157
9323
|
for (const r of rs) {
|
|
@@ -9204,6 +9370,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9204
9370
|
const wx = x1 - x0;
|
|
9205
9371
|
const wz = z1 - z0;
|
|
9206
9372
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
9373
|
+
if (b.tipping) {
|
|
9374
|
+
b.tipping.seconds -= h;
|
|
9375
|
+
if (height < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
9376
|
+
else {
|
|
9377
|
+
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
9378
|
+
w[2] += b.tipping.z * TIP_ACCELERATION * h;
|
|
9379
|
+
unstable = true;
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9207
9382
|
if (unstable) {
|
|
9208
9383
|
const dirX = x0 + x1 > 0 ? -1 : 1;
|
|
9209
9384
|
const dirZ = z0 + z1 > 0 ? -1 : 1;
|
|
@@ -9240,6 +9415,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9240
9415
|
b.still = 0;
|
|
9241
9416
|
}
|
|
9242
9417
|
}
|
|
9418
|
+
/** Half the height of the box of a piece as it is turned now. */
|
|
9419
|
+
halfHeight(b) {
|
|
9420
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
9421
|
+
return Math.abs(e[1]) * b.halfExtents[0] + Math.abs(e[5]) * b.halfExtents[1] + Math.abs(e[9]) * b.halfExtents[2];
|
|
9422
|
+
}
|
|
9243
9423
|
/** Push overlapping pieces apart (horizontal bounding circles), gently, so they do not interpenetrate forever. */
|
|
9244
9424
|
separate(dt) {
|
|
9245
9425
|
const n = this.list.length;
|
|
@@ -9258,6 +9438,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9258
9438
|
this.centerOf(a, ca);
|
|
9259
9439
|
this.centerOf(b, cb);
|
|
9260
9440
|
if (Math.abs(ca.y - cb.y) > ra + rb) continue;
|
|
9441
|
+
if (Math.abs(ca.y - cb.y) >= (this.halfHeight(a) + this.halfHeight(b)) * 0.9) continue;
|
|
9261
9442
|
const rh = (Math.max(a.halfExtents[0], a.halfExtents[2]) + Math.max(b.halfExtents[0], b.halfExtents[2])) * 0.8;
|
|
9262
9443
|
let dx = cb.x - ca.x;
|
|
9263
9444
|
let dz = cb.z - ca.z;
|
|
@@ -9653,14 +9834,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9653
9834
|
smoke: "grey"
|
|
9654
9835
|
},
|
|
9655
9836
|
visual: {
|
|
9656
|
-
charColor: 1709072
|
|
9837
|
+
charColor: 1709072,
|
|
9838
|
+
innerColor: 14730131
|
|
9657
9839
|
},
|
|
9658
9840
|
tags: [
|
|
9659
9841
|
"wood",
|
|
9660
9842
|
"organic",
|
|
9661
9843
|
"flammable"
|
|
9662
9844
|
],
|
|
9663
|
-
compressive: 1e5
|
|
9845
|
+
compressive: 1e5,
|
|
9846
|
+
hits: {
|
|
9847
|
+
blunt: {
|
|
9848
|
+
tear: "splinters",
|
|
9849
|
+
tearJoules: 60
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9664
9852
|
},
|
|
9665
9853
|
{
|
|
9666
9854
|
id: "wood_hard",
|
|
@@ -9690,14 +9878,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9690
9878
|
smoke: "grey"
|
|
9691
9879
|
},
|
|
9692
9880
|
visual: {
|
|
9693
|
-
charColor: 1709072
|
|
9881
|
+
charColor: 1709072,
|
|
9882
|
+
innerColor: 13213802
|
|
9694
9883
|
},
|
|
9695
9884
|
tags: [
|
|
9696
9885
|
"wood",
|
|
9697
9886
|
"organic",
|
|
9698
9887
|
"flammable"
|
|
9699
9888
|
],
|
|
9700
|
-
compressive: 15e4
|
|
9889
|
+
compressive: 15e4,
|
|
9890
|
+
hits: {
|
|
9891
|
+
blunt: {
|
|
9892
|
+
tear: "splinters",
|
|
9893
|
+
tearJoules: 150
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9701
9896
|
},
|
|
9702
9897
|
{
|
|
9703
9898
|
id: "plywood",
|
|
@@ -9727,14 +9922,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9727
9922
|
smoke: "grey"
|
|
9728
9923
|
},
|
|
9729
9924
|
visual: {
|
|
9730
|
-
charColor: 1709072
|
|
9925
|
+
charColor: 1709072,
|
|
9926
|
+
innerColor: 14862234
|
|
9731
9927
|
},
|
|
9732
9928
|
tags: [
|
|
9733
9929
|
"wood",
|
|
9734
9930
|
"organic",
|
|
9735
9931
|
"flammable"
|
|
9736
9932
|
],
|
|
9737
|
-
compressive: 6e4
|
|
9933
|
+
compressive: 6e4,
|
|
9934
|
+
hits: {
|
|
9935
|
+
ballistic: {
|
|
9936
|
+
tear: "splinters"
|
|
9937
|
+
},
|
|
9938
|
+
blunt: {
|
|
9939
|
+
tear: "splinters",
|
|
9940
|
+
tearJoules: 80
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9738
9943
|
},
|
|
9739
9944
|
{
|
|
9740
9945
|
id: "mdf",
|
|
@@ -9761,14 +9966,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9761
9966
|
smoke: "grey"
|
|
9762
9967
|
},
|
|
9763
9968
|
visual: {
|
|
9764
|
-
charColor: 1709072
|
|
9969
|
+
charColor: 1709072,
|
|
9970
|
+
innerColor: 12096362
|
|
9765
9971
|
},
|
|
9766
9972
|
tags: [
|
|
9767
9973
|
"wood",
|
|
9768
9974
|
"organic",
|
|
9769
9975
|
"flammable"
|
|
9770
9976
|
],
|
|
9771
|
-
compressive: 3e4
|
|
9977
|
+
compressive: 3e4,
|
|
9978
|
+
hits: {
|
|
9979
|
+
ballistic: {
|
|
9980
|
+
tear: "lumps",
|
|
9981
|
+
tearJoules: 120
|
|
9982
|
+
},
|
|
9983
|
+
blunt: {
|
|
9984
|
+
tear: "lumps",
|
|
9985
|
+
tearJoules: 20
|
|
9986
|
+
}
|
|
9987
|
+
}
|
|
9772
9988
|
},
|
|
9773
9989
|
{
|
|
9774
9990
|
id: "glass",
|
|
@@ -9882,6 +10098,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9882
10098
|
damageScale: {
|
|
9883
10099
|
explosive: 1.2
|
|
9884
10100
|
},
|
|
10101
|
+
hits: {
|
|
10102
|
+
ballistic: {
|
|
10103
|
+
unitJoules: 500,
|
|
10104
|
+
floor: 900,
|
|
10105
|
+
crater: true
|
|
10106
|
+
},
|
|
10107
|
+
blunt: {
|
|
10108
|
+
unitJoules: 1500,
|
|
10109
|
+
floor: 60
|
|
10110
|
+
}
|
|
10111
|
+
},
|
|
9885
10112
|
hitDebris: "chips",
|
|
9886
10113
|
breakDebris: "dust",
|
|
9887
10114
|
thermal: {
|
|
@@ -10250,7 +10477,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10250
10477
|
smoke: "white"
|
|
10251
10478
|
},
|
|
10252
10479
|
visual: {
|
|
10253
|
-
charColor: 1709072
|
|
10480
|
+
charColor: 1709072,
|
|
10481
|
+
innerColor: 14207126
|
|
10254
10482
|
},
|
|
10255
10483
|
tags: [
|
|
10256
10484
|
"plant",
|
|
@@ -10258,7 +10486,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10258
10486
|
"organic",
|
|
10259
10487
|
"flammable"
|
|
10260
10488
|
],
|
|
10261
|
-
compressive: 6e4
|
|
10489
|
+
compressive: 6e4,
|
|
10490
|
+
hits: {
|
|
10491
|
+
blunt: {
|
|
10492
|
+
tear: "none"
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10262
10495
|
},
|
|
10263
10496
|
{
|
|
10264
10497
|
id: "cactus",
|
|
@@ -10373,6 +10606,130 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10373
10606
|
"masonry"
|
|
10374
10607
|
],
|
|
10375
10608
|
compressive: 2e4
|
|
10609
|
+
},
|
|
10610
|
+
{
|
|
10611
|
+
id: "asphalt",
|
|
10612
|
+
density: 2350,
|
|
10613
|
+
hpPerKg: 10,
|
|
10614
|
+
penetration: 14,
|
|
10615
|
+
penetrationClass: "concrete",
|
|
10616
|
+
penetrationScale: 0.8,
|
|
10617
|
+
ductility: 0.1,
|
|
10618
|
+
fracture: "crumble",
|
|
10619
|
+
fragmentSize: [
|
|
10620
|
+
0.03,
|
|
10621
|
+
0.15
|
|
10622
|
+
],
|
|
10623
|
+
hitDebris: "chips",
|
|
10624
|
+
breakDebris: "dust",
|
|
10625
|
+
thermal: {
|
|
10626
|
+
conductivity: 0.1,
|
|
10627
|
+
heatCapacity: 2,
|
|
10628
|
+
ignitionC: 400,
|
|
10629
|
+
burnRate: 2e-3,
|
|
10630
|
+
smoke: "black"
|
|
10631
|
+
},
|
|
10632
|
+
tags: [
|
|
10633
|
+
"masonry",
|
|
10634
|
+
"brittle"
|
|
10635
|
+
],
|
|
10636
|
+
compressive: 3e4
|
|
10637
|
+
},
|
|
10638
|
+
{
|
|
10639
|
+
id: "sand",
|
|
10640
|
+
density: 1600,
|
|
10641
|
+
hpPerKg: 2,
|
|
10642
|
+
penetration: 6,
|
|
10643
|
+
ductility: 0.4,
|
|
10644
|
+
fracture: "crumble",
|
|
10645
|
+
fragmentSize: [
|
|
10646
|
+
0.01,
|
|
10647
|
+
0.04
|
|
10648
|
+
],
|
|
10649
|
+
hitDebris: "dust",
|
|
10650
|
+
breakDebris: "dust",
|
|
10651
|
+
thermal: {
|
|
10652
|
+
conductivity: 0.1,
|
|
10653
|
+
heatCapacity: 2
|
|
10654
|
+
},
|
|
10655
|
+
tags: [
|
|
10656
|
+
"soil"
|
|
10657
|
+
],
|
|
10658
|
+
compressive: 1e4
|
|
10659
|
+
},
|
|
10660
|
+
{
|
|
10661
|
+
id: "gravel",
|
|
10662
|
+
density: 1800,
|
|
10663
|
+
hpPerKg: 3,
|
|
10664
|
+
penetration: 8,
|
|
10665
|
+
ductility: 0.3,
|
|
10666
|
+
fracture: "crumble",
|
|
10667
|
+
fragmentSize: [
|
|
10668
|
+
0.02,
|
|
10669
|
+
0.06
|
|
10670
|
+
],
|
|
10671
|
+
hitDebris: "chips",
|
|
10672
|
+
breakDebris: "dust",
|
|
10673
|
+
thermal: {
|
|
10674
|
+
conductivity: 0.1,
|
|
10675
|
+
heatCapacity: 2
|
|
10676
|
+
},
|
|
10677
|
+
tags: [
|
|
10678
|
+
"soil",
|
|
10679
|
+
"masonry"
|
|
10680
|
+
],
|
|
10681
|
+
compressive: 2e4
|
|
10682
|
+
},
|
|
10683
|
+
{
|
|
10684
|
+
id: "turf",
|
|
10685
|
+
density: 900,
|
|
10686
|
+
hpPerKg: 3,
|
|
10687
|
+
penetration: 4,
|
|
10688
|
+
ductility: 0.5,
|
|
10689
|
+
fracture: "crumble",
|
|
10690
|
+
fragmentSize: [
|
|
10691
|
+
0.03,
|
|
10692
|
+
0.1
|
|
10693
|
+
],
|
|
10694
|
+
hitDebris: "dust",
|
|
10695
|
+
breakDebris: "dust",
|
|
10696
|
+
thermal: {
|
|
10697
|
+
conductivity: 0.1,
|
|
10698
|
+
heatCapacity: 1.5,
|
|
10699
|
+
ignitionC: 250,
|
|
10700
|
+
burnRate: 0.03,
|
|
10701
|
+
smoke: "white"
|
|
10702
|
+
},
|
|
10703
|
+
tags: [
|
|
10704
|
+
"soil",
|
|
10705
|
+
"organic"
|
|
10706
|
+
],
|
|
10707
|
+
compressive: 15e3
|
|
10708
|
+
},
|
|
10709
|
+
{
|
|
10710
|
+
id: "mortar",
|
|
10711
|
+
density: 1900,
|
|
10712
|
+
hpPerKg: 10,
|
|
10713
|
+
penetration: 12,
|
|
10714
|
+
penetrationClass: "concrete",
|
|
10715
|
+
penetrationScale: 1.2,
|
|
10716
|
+
ductility: 0.03,
|
|
10717
|
+
fracture: "crumble",
|
|
10718
|
+
fragmentSize: [
|
|
10719
|
+
0.02,
|
|
10720
|
+
0.08
|
|
10721
|
+
],
|
|
10722
|
+
hitDebris: "chips",
|
|
10723
|
+
breakDebris: "dust",
|
|
10724
|
+
thermal: {
|
|
10725
|
+
conductivity: 0.15,
|
|
10726
|
+
heatCapacity: 2.5
|
|
10727
|
+
},
|
|
10728
|
+
tags: [
|
|
10729
|
+
"masonry",
|
|
10730
|
+
"brittle"
|
|
10731
|
+
],
|
|
10732
|
+
compressive: 1e5
|
|
10376
10733
|
}
|
|
10377
10734
|
];
|
|
10378
10735
|
var material_hints_default = {
|
|
@@ -10568,6 +10925,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10568
10925
|
const fill = options.fillOf?.(o, material);
|
|
10569
10926
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10570
10927
|
const contains = containsOf(ud.destructionContains);
|
|
10928
|
+
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
10571
10929
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10572
10930
|
if (existing) {
|
|
10573
10931
|
const before = sizeOf(existing.box);
|
|
@@ -10580,8 +10938,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10580
10938
|
existing.skin = existing.skin && skin;
|
|
10581
10939
|
if (fill !== void 0) existing.fill = fill;
|
|
10582
10940
|
if (contains) existing.contains = contains;
|
|
10941
|
+
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
10583
10942
|
} else {
|
|
10584
|
-
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 };
|
|
10943
|
+
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, ...thickness !== void 0 ? { thickness } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
10585
10944
|
entries.push(e);
|
|
10586
10945
|
if (explicit) byKey.set(explicit, e);
|
|
10587
10946
|
}
|
|
@@ -10649,7 +11008,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10649
11008
|
const minSide = Math.min(...size);
|
|
10650
11009
|
if (e.fill !== void 0) part.fill = e.fill;
|
|
10651
11010
|
else if (e.hollow) part.fill = 0.05;
|
|
10652
|
-
if (
|
|
11011
|
+
if (e.thickness !== void 0) part.thickness = e.thickness;
|
|
11012
|
+
else if (minSide < 0.01 && METAL.test(e.material)) {
|
|
10653
11013
|
part.fill = e.fill ?? 1;
|
|
10654
11014
|
part.thickness = Math.max(minSide, 5e-4);
|
|
10655
11015
|
}
|
|
@@ -10740,9 +11100,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10740
11100
|
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
10741
11101
|
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
10742
11102
|
triangle.set(corners[0], corners[1], corners[2]);
|
|
11103
|
+
if (triangle.getArea() <= 1e-14) continue;
|
|
10743
11104
|
triangle.closestPointToPoint(local2, closest);
|
|
10744
11105
|
const distance = closest.distanceToSquared(local2);
|
|
10745
|
-
if (distance
|
|
11106
|
+
if (!(distance < nearest)) continue;
|
|
10746
11107
|
nearest = distance;
|
|
10747
11108
|
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10748
11109
|
triangle.getBarycoord(closest, weights);
|
|
@@ -10921,6 +11282,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10921
11282
|
return out;
|
|
10922
11283
|
}
|
|
10923
11284
|
/** Box of the part in world space. */
|
|
11285
|
+
/** Appends the boxes that follow the mesh in the world (`pushFittedBoxes`) to `out`. */
|
|
11286
|
+
fittedBoxes(mv, out) {
|
|
11287
|
+
const box = new this.THREE.Box3();
|
|
11288
|
+
if (mv.host) this.localBox(mv, box);
|
|
11289
|
+
else {
|
|
11290
|
+
const geometry = mv.mesh.geometry;
|
|
11291
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
11292
|
+
if (geometry.boundingBox) box.copy(geometry.boundingBox);
|
|
11293
|
+
else box.makeEmpty();
|
|
11294
|
+
}
|
|
11295
|
+
if (box.isEmpty()) return;
|
|
11296
|
+
mv.mesh.updateWorldMatrix(true, false);
|
|
11297
|
+
pushFittedBoxes(box, mv.mesh.matrixWorld, out);
|
|
11298
|
+
}
|
|
10924
11299
|
worldBox(mv, out) {
|
|
10925
11300
|
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
10926
11301
|
this.localBox(mv, out);
|
|
@@ -11665,9 +12040,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11665
12040
|
|
|
11666
12041
|
// src/renderer.ts
|
|
11667
12042
|
var rendererCount = 0;
|
|
12043
|
+
var COLLAPSE_CLOUD_KG = 1e3;
|
|
11668
12044
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
11669
12045
|
var GLOW_FROM_C = 430;
|
|
11670
12046
|
var QUEUE_CAP = 4e3;
|
|
12047
|
+
var STATIC_REFRESH = 0.5;
|
|
12048
|
+
var STATIC_SPAN = 3;
|
|
12049
|
+
var FEET_BAND = 0.03;
|
|
12050
|
+
var TIP_MIN_HEIGHT = 0.3;
|
|
12051
|
+
var TIP_NARROW = 0.3;
|
|
11671
12052
|
var FIRE_CEILING_SECONDS = 3600;
|
|
11672
12053
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
11673
12054
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
@@ -11712,6 +12093,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11712
12093
|
if (def.fracture === "tear") return "impact_soft";
|
|
11713
12094
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
11714
12095
|
}
|
|
12096
|
+
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
11715
12097
|
function fractureKey(def, style) {
|
|
11716
12098
|
switch (style) {
|
|
11717
12099
|
case "shatter":
|
|
@@ -11753,6 +12135,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11753
12135
|
__publicField(this, "autoSplit");
|
|
11754
12136
|
__publicField(this, "maxFragments");
|
|
11755
12137
|
__publicField(this, "bounds", /* @__PURE__ */ new Map());
|
|
12138
|
+
/** World boxes (min xyz, max xyz) of the drawn meshes of the bound objects that are not pieces, for the pieces to land on. */
|
|
12139
|
+
__publicField(this, "staticBoxes", /* @__PURE__ */ new Map());
|
|
12140
|
+
/** Objects to measure again; `staticAll` measures every one. */
|
|
12141
|
+
__publicField(this, "staticDirty", /* @__PURE__ */ new Set());
|
|
12142
|
+
__publicField(this, "staticAll", true);
|
|
12143
|
+
/** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
|
|
12144
|
+
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
12145
|
+
__publicField(this, "staticAt", 0);
|
|
11756
12146
|
__publicField(this, "queue", []);
|
|
11757
12147
|
__publicField(this, "unsubscribe");
|
|
11758
12148
|
__publicField(this, "visuals");
|
|
@@ -11814,6 +12204,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11814
12204
|
this.cages = new RebarCages(THREE, o.scene);
|
|
11815
12205
|
this.pieceSys = new PieceSystem(THREE, {
|
|
11816
12206
|
groundAt: this.groundAt,
|
|
12207
|
+
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
11817
12208
|
gravity: this.gravity,
|
|
11818
12209
|
scene: o.scene,
|
|
11819
12210
|
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
@@ -11826,6 +12217,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11826
12217
|
// Binding
|
|
11827
12218
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11828
12219
|
bind(objectId, root, options = {}) {
|
|
12220
|
+
this.staticDirty.add(objectId);
|
|
11829
12221
|
if (this.bounds.has(objectId)) this.unbind(objectId);
|
|
11830
12222
|
const state = this.world.state(objectId);
|
|
11831
12223
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
@@ -11945,6 +12337,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11945
12337
|
}
|
|
11946
12338
|
}
|
|
11947
12339
|
unbind(objectId) {
|
|
12340
|
+
this.staticDirty.add(objectId);
|
|
11948
12341
|
const b = this.bounds.get(objectId);
|
|
11949
12342
|
if (!b) return;
|
|
11950
12343
|
this.restore(objectId);
|
|
@@ -11984,6 +12377,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11984
12377
|
return m > 0 ? m : 5;
|
|
11985
12378
|
}
|
|
11986
12379
|
makePiece(objectId, options = {}) {
|
|
12380
|
+
this.staticDirty.add(objectId);
|
|
11987
12381
|
const b = this.bounds.get(objectId);
|
|
11988
12382
|
if (!b) throw new Error(`game-destruction-three: object "${objectId}" is not bound`);
|
|
11989
12383
|
if (b.root.parent && b.root.parent !== this.scene) this.scene.attach(b.root);
|
|
@@ -12096,7 +12490,100 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12096
12490
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
12097
12491
|
// Events
|
|
12098
12492
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
12493
|
+
/**
|
|
12494
|
+
* The highest top of a bound object that is not a piece under (x, z) a corner at height `y` can land on (at most `step` above it). The world boxes
|
|
12495
|
+
* of their drawn meshes are kept until an event or a moved root changes them; parts drawn inside a merged mesh and meshes wider than `STATIC_SPAN`
|
|
12496
|
+
* are left out (walls, floors, roofs: the terrain under them is the host's `groundAt`).
|
|
12497
|
+
*/
|
|
12498
|
+
staticTopAt(x, z, y, step) {
|
|
12499
|
+
let top = -Infinity;
|
|
12500
|
+
for (const a of this.staticNow().values()) {
|
|
12501
|
+
if (a.length === 0 || x < a[0] || x > a[3] || z < a[2] || z > a[5]) continue;
|
|
12502
|
+
for (let i = 6; i < a.length; i += 6) {
|
|
12503
|
+
if (x < a[i] || x > a[i + 3] || z < a[i + 2] || z > a[i + 5]) continue;
|
|
12504
|
+
const t = a[i + 4];
|
|
12505
|
+
if (t > top && t <= y + step) top = t;
|
|
12506
|
+
}
|
|
12507
|
+
}
|
|
12508
|
+
return top;
|
|
12509
|
+
}
|
|
12510
|
+
/** The boxes of the fixed objects (per object: the box around them all, then each one), each object measured again when an event touched it (all of them every `STATIC_REFRESH` s). */
|
|
12511
|
+
staticNow() {
|
|
12512
|
+
if (this.time - this.staticAt > STATIC_REFRESH) {
|
|
12513
|
+
this.staticAt = this.time;
|
|
12514
|
+
this.staticAll = true;
|
|
12515
|
+
}
|
|
12516
|
+
if (!this.staticAll && this.staticDirty.size === 0) return this.staticBoxes;
|
|
12517
|
+
const box = new this.THREE.Box3();
|
|
12518
|
+
const measure = (b) => {
|
|
12519
|
+
if (b.piece || this.pieceSys.get(b.id)) {
|
|
12520
|
+
this.staticBoxes.delete(b.id);
|
|
12521
|
+
return;
|
|
12522
|
+
}
|
|
12523
|
+
const out = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
12524
|
+
for (const mv of this.meshesOf(b)) {
|
|
12525
|
+
if (mv.host || mv.loose || !this.visuals.shown(mv)) continue;
|
|
12526
|
+
this.visuals.worldBox(mv, box);
|
|
12527
|
+
if (box.isEmpty() || box.max.x - box.min.x > STATIC_SPAN || box.max.z - box.min.z > STATIC_SPAN) continue;
|
|
12528
|
+
this.visuals.fittedBoxes(mv, out);
|
|
12529
|
+
for (let k = 0; k < 3; k++) {
|
|
12530
|
+
out[k] = Math.min(out[k], box.min.getComponent(k));
|
|
12531
|
+
out[k + 3] = Math.max(out[k + 3], box.max.getComponent(k));
|
|
12532
|
+
}
|
|
12533
|
+
}
|
|
12534
|
+
this.staticBoxes.set(b.id, out.length > 6 ? out : []);
|
|
12535
|
+
};
|
|
12536
|
+
if (this.staticAll) {
|
|
12537
|
+
this.staticBoxes.clear();
|
|
12538
|
+
for (const b of this.bounds.values()) measure(b);
|
|
12539
|
+
} else {
|
|
12540
|
+
for (const id of this.staticDirty) {
|
|
12541
|
+
const b = this.bounds.get(id);
|
|
12542
|
+
if (b) measure(b);
|
|
12543
|
+
else this.staticBoxes.delete(id);
|
|
12544
|
+
}
|
|
12545
|
+
}
|
|
12546
|
+
this.staticAll = false;
|
|
12547
|
+
this.staticDirty.clear();
|
|
12548
|
+
return this.staticBoxes;
|
|
12549
|
+
}
|
|
12550
|
+
/** The boxes of a piece's drawn meshes, measured once per frame. */
|
|
12551
|
+
pieceBoxesOf(pc) {
|
|
12552
|
+
const cached = this.pieceBoxes.get(pc);
|
|
12553
|
+
if (cached && cached.at === this.time) return cached.boxes;
|
|
12554
|
+
const boxes = [];
|
|
12555
|
+
const b = this.bounds.get(pc.objectId);
|
|
12556
|
+
if (b) {
|
|
12557
|
+
for (const mv of this.meshesOf(b)) {
|
|
12558
|
+
if (this.visuals.shown(mv)) this.visuals.fittedBoxes(mv, boxes);
|
|
12559
|
+
}
|
|
12560
|
+
}
|
|
12561
|
+
this.pieceBoxes.set(pc, { at: this.time, boxes });
|
|
12562
|
+
return boxes;
|
|
12563
|
+
}
|
|
12564
|
+
solidsNear(x, z, radius) {
|
|
12565
|
+
const found = [];
|
|
12566
|
+
const scan = (a, id, piece, from) => {
|
|
12567
|
+
if (from > 0 && (a.length === 0 || x < a[0] - radius || x > a[3] + radius || z < a[2] - radius || z > a[5] + radius)) return;
|
|
12568
|
+
for (let i = from; i < a.length; i += 6) {
|
|
12569
|
+
if (x < a[i] - radius || x > a[i + 3] + radius || z < a[i + 2] - radius || z > a[i + 5] + radius) continue;
|
|
12570
|
+
found.push({ objectId: id, min: [a[i], a[i + 1], a[i + 2]], max: [a[i + 3], a[i + 4], a[i + 5]], piece });
|
|
12571
|
+
}
|
|
12572
|
+
};
|
|
12573
|
+
for (const [id, a] of this.staticNow()) scan(a, id, null, 6);
|
|
12574
|
+
const c = this.v1;
|
|
12575
|
+
for (const pc of this.pieceSys.list) {
|
|
12576
|
+
if (pc.held) continue;
|
|
12577
|
+
this.pieceSys.centerOf(pc, c);
|
|
12578
|
+
const reach = Math.hypot(pc.halfExtents[0], pc.halfExtents[1], pc.halfExtents[2]) + radius;
|
|
12579
|
+
if (Math.abs(c.x - x) > reach || Math.abs(c.z - z) > reach) continue;
|
|
12580
|
+
scan(this.pieceBoxesOf(pc), pc.objectId, pc, 0);
|
|
12581
|
+
}
|
|
12582
|
+
this.fragments.solidsNear(x, z, radius, (home, min2, max2, loose) => found.push({ objectId: home, min: min2, max: max2, piece: null, loose }));
|
|
12583
|
+
return found;
|
|
12584
|
+
}
|
|
12099
12585
|
apply(ev) {
|
|
12586
|
+
this.staticDirty.add(ev.object);
|
|
12100
12587
|
switch (ev.type) {
|
|
12101
12588
|
case "damage":
|
|
12102
12589
|
if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
|
|
@@ -12128,10 +12615,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12128
12615
|
case "settle":
|
|
12129
12616
|
this.onSettle(ev.object);
|
|
12130
12617
|
break;
|
|
12131
|
-
case "collapse":
|
|
12618
|
+
case "collapse": {
|
|
12619
|
+
const root = this.bounds.get(ev.object)?.root;
|
|
12620
|
+
const furniture = !!root && kitOriginOf(root)?.kind === "furniture";
|
|
12132
12621
|
this.splitLoose(ev.object);
|
|
12133
|
-
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
12622
|
+
if (!furniture || ev.mass >= COLLAPSE_CLOUD_KG) this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
12623
|
+
else this.vfx.burst("dust", this.rootPoint(ev.object), [0, 1, 0], clamp6(Math.round(ev.mass / 4), 4, 24), 0.8);
|
|
12134
12624
|
break;
|
|
12625
|
+
}
|
|
12135
12626
|
case "disassemble_progress": {
|
|
12136
12627
|
const k = `unscrew:${ev.joint}`;
|
|
12137
12628
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -12161,6 +12652,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12161
12652
|
case "stage":
|
|
12162
12653
|
this.onStage(ev);
|
|
12163
12654
|
break;
|
|
12655
|
+
case "chip":
|
|
12656
|
+
this.onChip(ev);
|
|
12657
|
+
break;
|
|
12164
12658
|
case "expose":
|
|
12165
12659
|
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
12166
12660
|
if (ev.bars.length > 0) {
|
|
@@ -12417,6 +12911,23 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12417
12911
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
12418
12912
|
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
12419
12913
|
}
|
|
12914
|
+
/**
|
|
12915
|
+
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust. The gap itself is the drawing's (the house renderer redraws
|
|
12916
|
+
* the wall as its courses without that unit); a part drawn as one box keeps its box.
|
|
12917
|
+
*/
|
|
12918
|
+
onChip(ev) {
|
|
12919
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
12920
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
12921
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
12922
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
12923
|
+
for (const fr of ev.fragments) {
|
|
12924
|
+
const fdef = this.world.materials.get(fr.material);
|
|
12925
|
+
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
12926
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone, false), ev.object, maxAlive);
|
|
12927
|
+
}
|
|
12928
|
+
if (!this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.center)) this.vfx.burst("dust", ev.center, [0, 1, 0], 6, 0.8, { color });
|
|
12929
|
+
this.sound(impactKey(def), ev.center, 0.7);
|
|
12930
|
+
}
|
|
12420
12931
|
onFracture(ev) {
|
|
12421
12932
|
const b = this.bounds.get(ev.object);
|
|
12422
12933
|
const pv = b?.parts.get(ev.part);
|
|
@@ -12494,6 +13005,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12494
13005
|
this.pieceSys.setCenter(old, center);
|
|
12495
13006
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
12496
13007
|
old.sleeping = false;
|
|
13008
|
+
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
13009
|
+
this.makePiece(objectId);
|
|
13010
|
+
this.tipIfTopHeavy(b);
|
|
12497
13011
|
}
|
|
12498
13012
|
}
|
|
12499
13013
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -12540,8 +13054,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12540
13054
|
}
|
|
12541
13055
|
const piece = this.pieceSys.create(newId, group, half, center, this.massOf(newId), vel, ang);
|
|
12542
13056
|
nb.piece = piece;
|
|
13057
|
+
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
12543
13058
|
this.o.onPiece?.(piece);
|
|
12544
13059
|
}
|
|
13060
|
+
/**
|
|
13061
|
+
* A piece of furniture standing on feet too close for its height (a board on two legs in a line, a lone leg) does not stay up: the box of a piece hides that its weight is up
|
|
13062
|
+
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
13063
|
+
*/
|
|
13064
|
+
tipIfTopHeavy(b) {
|
|
13065
|
+
const piece = b.piece;
|
|
13066
|
+
if (!piece) return;
|
|
13067
|
+
const box = new this.THREE.Box3();
|
|
13068
|
+
const boxes = this.meshesOf(b).filter((mv) => this.visuals.shown(mv)).map((mv) => this.visuals.worldBox(mv, new this.THREE.Box3()));
|
|
13069
|
+
if (boxes.length === 0) return;
|
|
13070
|
+
for (const one of boxes) box.union(one);
|
|
13071
|
+
const height = box.max.y - box.min.y;
|
|
13072
|
+
const feet = new this.THREE.Box3();
|
|
13073
|
+
for (const one of boxes) if (one.min.y < box.min.y + FEET_BAND) feet.union(one);
|
|
13074
|
+
const spanX = feet.max.x - feet.min.x;
|
|
13075
|
+
const spanZ = feet.max.z - feet.min.z;
|
|
13076
|
+
if (height < TIP_MIN_HEIGHT || Math.min(spanX, spanZ) >= TIP_NARROW * height) return;
|
|
13077
|
+
const across = spanX <= spanZ ? 0 : 2;
|
|
13078
|
+
const lean = across === 0 ? box.min.x + box.max.x - feet.min.x - feet.max.x : box.min.z + box.max.z - feet.min.z - feet.max.z;
|
|
13079
|
+
const away = lean < 0 ? -1 : 1;
|
|
13080
|
+
if (across === 0) this.pieceSys.tip(piece, 0, -away);
|
|
13081
|
+
else this.pieceSys.tip(piece, away, 0);
|
|
13082
|
+
}
|
|
12545
13083
|
onExplode(ev, options = {}) {
|
|
12546
13084
|
const p = ev.point;
|
|
12547
13085
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
@@ -12792,6 +13330,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12792
13330
|
this.vfx.sprites().clear();
|
|
12793
13331
|
this.ownVfx?.world.clear();
|
|
12794
13332
|
}
|
|
13333
|
+
wakeRegion(x, z, radius) {
|
|
13334
|
+
this.fragments.wakeRegion(x, z, radius);
|
|
13335
|
+
this.pieceSys.wakeRegion(x, z, radius);
|
|
13336
|
+
}
|
|
12795
13337
|
stats() {
|
|
12796
13338
|
return { fragments: this.fragments.count, particles: this.vfx.sprites().live + this.ownedVfxParticles(), pieces: this.pieceSys.list.length, decals: this.visuals.decalCount + this.ground.count };
|
|
12797
13339
|
}
|
|
@@ -13084,7 +13626,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13084
13626
|
volume: 0,
|
|
13085
13627
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
13086
13628
|
};
|
|
13087
|
-
var
|
|
13629
|
+
var drag = (id, layers) => ({ ...sceneRecipe(id, layers, ""), sources: ["sdk: a base dragged on a floor, one grain per stretch"] });
|
|
13630
|
+
var DRAG_SOUNDS = [
|
|
13631
|
+
drag("drag_wood", [sceneNoise({ type: "bandpass", f: 380, q: 1.4, d: 0.28, vol: 0.5 }), sceneNoise({ type: "bandpass", f: [900, 1300], q: 3, d: 0.08, vol: 0.14 })]),
|
|
13632
|
+
drag("drag_metal", [sceneNoise({ type: "bandpass", f: 1500, q: 4, d: 0.26, vol: 0.32 }), sceneTone({ f: [880, 1240], f2: 760, d: 0.18, vol: 0.05 })]),
|
|
13633
|
+
drag("drag_glass", [sceneNoise({ type: "bandpass", f: 2600, q: 5, d: 0.2, vol: 0.22 })]),
|
|
13634
|
+
drag("drag_masonry", [sceneNoise({ type: "bandpass", f: 260, q: 0.9, d: 0.3, vol: 0.62 }), ...sceneMany(3, () => sceneNoise({ type: "bandpass", f: [600, 1200], q: 2, d: 0.04, vol: 0.14, delay: [0, 0.2] }))]),
|
|
13635
|
+
drag("drag_soft", [sceneNoise({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
13636
|
+
];
|
|
13637
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
13088
13638
|
|
|
13089
13639
|
// src/audio/sound.ts
|
|
13090
13640
|
var DESTRUCTION_SOUND_KEYS = {
|
|
@@ -13094,6 +13644,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13094
13644
|
impact_glass: { sound: "ping" },
|
|
13095
13645
|
impact_masonry: { sound: "chip" },
|
|
13096
13646
|
impact_soft: { sound: "thud" },
|
|
13647
|
+
drag_wood: { sound: "drag_wood" },
|
|
13648
|
+
drag_metal: { sound: "drag_metal" },
|
|
13649
|
+
drag_glass: { sound: "drag_glass" },
|
|
13650
|
+
drag_masonry: { sound: "drag_masonry" },
|
|
13651
|
+
drag_soft: { sound: "drag_soft" },
|
|
13097
13652
|
shatter_glass: { sound: "glass_shatter" },
|
|
13098
13653
|
crumble: { sound: "crumble" },
|
|
13099
13654
|
splinter: { sound: "crack" },
|