@adatechnology/game-destruction-three 0.10.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 +556 -29
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +370 -19
- 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
|
|
|
@@ -8589,6 +8592,44 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8589
8592
|
}
|
|
8590
8593
|
var LIGHT_BUDGET = EVENT_BUDGET.blastLights + EVENT_BUDGET.fireLights;
|
|
8591
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
|
+
|
|
8592
8633
|
// src/math.ts
|
|
8593
8634
|
function createRng(seed) {
|
|
8594
8635
|
let a = seed >>> 0;
|
|
@@ -8630,12 +8671,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8630
8671
|
var SHEET_MAX = 4e-3;
|
|
8631
8672
|
var SHEET_AIR = 2.5;
|
|
8632
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
|
+
};
|
|
8633
8680
|
var FragmentSystem = class {
|
|
8634
8681
|
constructor(THREE, o) {
|
|
8635
8682
|
__publicField(this, "THREE", THREE);
|
|
8636
8683
|
__publicField(this, "o", o);
|
|
8637
8684
|
__publicField(this, "list", []);
|
|
8638
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 } });
|
|
8639
8688
|
__publicField(this, "shardGeos", []);
|
|
8640
8689
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
8641
8690
|
__publicField(this, "q");
|
|
@@ -8644,6 +8693,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8644
8693
|
__publicField(this, "v");
|
|
8645
8694
|
__publicField(this, "w");
|
|
8646
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);
|
|
8647
8698
|
__publicField(this, "markGeometry", null);
|
|
8648
8699
|
__publicField(this, "markMaterial", null);
|
|
8649
8700
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -8711,8 +8762,28 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8711
8762
|
mesh2.add(disc);
|
|
8712
8763
|
}
|
|
8713
8764
|
}
|
|
8714
|
-
/**
|
|
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
|
+
*/
|
|
8715
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
|
+
}
|
|
8716
8787
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
8717
8788
|
const mesh2 = new this.THREE.Mesh(geo, material);
|
|
8718
8789
|
mesh2.position.set(f.center[0], f.center[1], f.center[2]);
|
|
@@ -8736,19 +8807,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8736
8807
|
rest: 0,
|
|
8737
8808
|
fade: 1,
|
|
8738
8809
|
dying: false,
|
|
8739
|
-
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
|
|
8740
8813
|
};
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
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
|
+
};
|
|
8748
8825
|
}
|
|
8826
|
+
this.list.push(live);
|
|
8749
8827
|
this.addMarks(mesh2, f);
|
|
8750
8828
|
return mesh2;
|
|
8751
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
|
+
}
|
|
8752
8846
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
8753
8847
|
blast(point, radius, speed) {
|
|
8754
8848
|
for (const f of this.list) {
|
|
@@ -8764,7 +8858,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8764
8858
|
f.rest = 0;
|
|
8765
8859
|
}
|
|
8766
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
|
+
}
|
|
8767
8869
|
update(dt) {
|
|
8870
|
+
this.tick++;
|
|
8768
8871
|
const g = this.o.gravity;
|
|
8769
8872
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
8770
8873
|
const f = this.list[i];
|
|
@@ -8779,7 +8882,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8779
8882
|
}
|
|
8780
8883
|
if (f.asleep) {
|
|
8781
8884
|
f.rest += dt;
|
|
8782
|
-
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;
|
|
8783
8886
|
continue;
|
|
8784
8887
|
}
|
|
8785
8888
|
this.step(f, dt, g);
|
|
@@ -8853,7 +8956,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8853
8956
|
let small = 0;
|
|
8854
8957
|
for (let i = 1; i < 3; i++) if (half[i] < half[small]) small = i;
|
|
8855
8958
|
let axis2 = small;
|
|
8856
|
-
if (Math.abs(up[small]) < 0.35) {
|
|
8959
|
+
if (Math.abs(up[small]) < 0.35 && !f.body) {
|
|
8857
8960
|
axis2 = 0;
|
|
8858
8961
|
for (let i = 1; i < 3; i++) if (Math.abs(up[i]) > Math.abs(up[axis2])) axis2 = i;
|
|
8859
8962
|
}
|
|
@@ -8979,6 +9082,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8979
9082
|
var SLOP = 2e-3;
|
|
8980
9083
|
var NARROW = 0.3;
|
|
8981
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
|
+
};
|
|
8982
9109
|
var PieceSystem = class {
|
|
8983
9110
|
constructor(THREE, o) {
|
|
8984
9111
|
__publicField(this, "THREE", THREE);
|
|
@@ -8990,6 +9117,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8990
9117
|
__publicField(this, "dq");
|
|
8991
9118
|
__publicField(this, "m");
|
|
8992
9119
|
__publicField(this, "ax");
|
|
9120
|
+
__publicField(this, "solids", []);
|
|
8993
9121
|
this.p = new THREE.Vector3();
|
|
8994
9122
|
this.q = new THREE.Quaternion();
|
|
8995
9123
|
this.dq = new THREE.Quaternion();
|
|
@@ -8999,6 +9127,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8999
9127
|
get(objectId) {
|
|
9000
9128
|
return this.list.find((b) => b.objectId === objectId) ?? null;
|
|
9001
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
|
+
}
|
|
9002
9135
|
create(objectId, root, halfExtents, localCenter, mass, velocity, angular) {
|
|
9003
9136
|
const existing = this.get(objectId);
|
|
9004
9137
|
if (existing) {
|
|
@@ -9012,6 +9145,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9012
9145
|
}
|
|
9013
9146
|
const body = {
|
|
9014
9147
|
objectId,
|
|
9148
|
+
tipping: null,
|
|
9015
9149
|
root,
|
|
9016
9150
|
velocity: velocity ? [...velocity] : [0, 0, 0],
|
|
9017
9151
|
angularVelocity: angular ? [...angular] : [0, 0, 0],
|
|
@@ -9081,8 +9215,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9081
9215
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
9082
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];
|
|
9083
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
|
+
}
|
|
9084
9226
|
update(dt) {
|
|
9085
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
|
+
});
|
|
9086
9233
|
for (const b of this.list) {
|
|
9087
9234
|
if (b.held) {
|
|
9088
9235
|
b.velocity = [0, 0, 0];
|
|
@@ -9096,6 +9243,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9096
9243
|
}
|
|
9097
9244
|
this.separate(dt);
|
|
9098
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
|
+
}
|
|
9099
9257
|
integrate(b, dt) {
|
|
9100
9258
|
const c = this.centerOf(b, this.p);
|
|
9101
9259
|
const pos = [c.x, c.y, c.z];
|
|
@@ -9134,8 +9292,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9134
9292
|
const sy = i & 2 ? hy : -hy;
|
|
9135
9293
|
const sz = i & 4 ? hz : -hz;
|
|
9136
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];
|
|
9137
|
-
const
|
|
9138
|
-
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;
|
|
9139
9298
|
if (pen > -SLOP) {
|
|
9140
9299
|
rs.push(r);
|
|
9141
9300
|
if (pen > maxPen) maxPen = pen;
|
|
@@ -9158,7 +9317,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9158
9317
|
}
|
|
9159
9318
|
const lx = pos[0] + sx / rs.length;
|
|
9160
9319
|
const lz = pos[2] + sz / rs.length;
|
|
9161
|
-
landPoint = [lx,
|
|
9320
|
+
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
9162
9321
|
}
|
|
9163
9322
|
for (let it = 0; it < 4; it++) {
|
|
9164
9323
|
for (const r of rs) {
|
|
@@ -9211,6 +9370,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9211
9370
|
const wx = x1 - x0;
|
|
9212
9371
|
const wz = z1 - z0;
|
|
9213
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
|
+
}
|
|
9214
9382
|
if (unstable) {
|
|
9215
9383
|
const dirX = x0 + x1 > 0 ? -1 : 1;
|
|
9216
9384
|
const dirZ = z0 + z1 > 0 ? -1 : 1;
|
|
@@ -9247,6 +9415,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9247
9415
|
b.still = 0;
|
|
9248
9416
|
}
|
|
9249
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
|
+
}
|
|
9250
9423
|
/** Push overlapping pieces apart (horizontal bounding circles), gently, so they do not interpenetrate forever. */
|
|
9251
9424
|
separate(dt) {
|
|
9252
9425
|
const n = this.list.length;
|
|
@@ -9265,6 +9438,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9265
9438
|
this.centerOf(a, ca);
|
|
9266
9439
|
this.centerOf(b, cb);
|
|
9267
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;
|
|
9268
9442
|
const rh = (Math.max(a.halfExtents[0], a.halfExtents[2]) + Math.max(b.halfExtents[0], b.halfExtents[2])) * 0.8;
|
|
9269
9443
|
let dx = cb.x - ca.x;
|
|
9270
9444
|
let dz = cb.z - ca.z;
|
|
@@ -9660,14 +9834,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9660
9834
|
smoke: "grey"
|
|
9661
9835
|
},
|
|
9662
9836
|
visual: {
|
|
9663
|
-
charColor: 1709072
|
|
9837
|
+
charColor: 1709072,
|
|
9838
|
+
innerColor: 14730131
|
|
9664
9839
|
},
|
|
9665
9840
|
tags: [
|
|
9666
9841
|
"wood",
|
|
9667
9842
|
"organic",
|
|
9668
9843
|
"flammable"
|
|
9669
9844
|
],
|
|
9670
|
-
compressive: 1e5
|
|
9845
|
+
compressive: 1e5,
|
|
9846
|
+
hits: {
|
|
9847
|
+
blunt: {
|
|
9848
|
+
tear: "splinters",
|
|
9849
|
+
tearJoules: 60
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9671
9852
|
},
|
|
9672
9853
|
{
|
|
9673
9854
|
id: "wood_hard",
|
|
@@ -9697,14 +9878,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9697
9878
|
smoke: "grey"
|
|
9698
9879
|
},
|
|
9699
9880
|
visual: {
|
|
9700
|
-
charColor: 1709072
|
|
9881
|
+
charColor: 1709072,
|
|
9882
|
+
innerColor: 13213802
|
|
9701
9883
|
},
|
|
9702
9884
|
tags: [
|
|
9703
9885
|
"wood",
|
|
9704
9886
|
"organic",
|
|
9705
9887
|
"flammable"
|
|
9706
9888
|
],
|
|
9707
|
-
compressive: 15e4
|
|
9889
|
+
compressive: 15e4,
|
|
9890
|
+
hits: {
|
|
9891
|
+
blunt: {
|
|
9892
|
+
tear: "splinters",
|
|
9893
|
+
tearJoules: 150
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9708
9896
|
},
|
|
9709
9897
|
{
|
|
9710
9898
|
id: "plywood",
|
|
@@ -9734,14 +9922,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9734
9922
|
smoke: "grey"
|
|
9735
9923
|
},
|
|
9736
9924
|
visual: {
|
|
9737
|
-
charColor: 1709072
|
|
9925
|
+
charColor: 1709072,
|
|
9926
|
+
innerColor: 14862234
|
|
9738
9927
|
},
|
|
9739
9928
|
tags: [
|
|
9740
9929
|
"wood",
|
|
9741
9930
|
"organic",
|
|
9742
9931
|
"flammable"
|
|
9743
9932
|
],
|
|
9744
|
-
compressive: 6e4
|
|
9933
|
+
compressive: 6e4,
|
|
9934
|
+
hits: {
|
|
9935
|
+
ballistic: {
|
|
9936
|
+
tear: "splinters"
|
|
9937
|
+
},
|
|
9938
|
+
blunt: {
|
|
9939
|
+
tear: "splinters",
|
|
9940
|
+
tearJoules: 80
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9745
9943
|
},
|
|
9746
9944
|
{
|
|
9747
9945
|
id: "mdf",
|
|
@@ -9768,14 +9966,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9768
9966
|
smoke: "grey"
|
|
9769
9967
|
},
|
|
9770
9968
|
visual: {
|
|
9771
|
-
charColor: 1709072
|
|
9969
|
+
charColor: 1709072,
|
|
9970
|
+
innerColor: 12096362
|
|
9772
9971
|
},
|
|
9773
9972
|
tags: [
|
|
9774
9973
|
"wood",
|
|
9775
9974
|
"organic",
|
|
9776
9975
|
"flammable"
|
|
9777
9976
|
],
|
|
9778
|
-
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
|
+
}
|
|
9779
9988
|
},
|
|
9780
9989
|
{
|
|
9781
9990
|
id: "glass",
|
|
@@ -9889,6 +10098,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9889
10098
|
damageScale: {
|
|
9890
10099
|
explosive: 1.2
|
|
9891
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
|
+
},
|
|
9892
10112
|
hitDebris: "chips",
|
|
9893
10113
|
breakDebris: "dust",
|
|
9894
10114
|
thermal: {
|
|
@@ -10257,7 +10477,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10257
10477
|
smoke: "white"
|
|
10258
10478
|
},
|
|
10259
10479
|
visual: {
|
|
10260
|
-
charColor: 1709072
|
|
10480
|
+
charColor: 1709072,
|
|
10481
|
+
innerColor: 14207126
|
|
10261
10482
|
},
|
|
10262
10483
|
tags: [
|
|
10263
10484
|
"plant",
|
|
@@ -10265,7 +10486,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10265
10486
|
"organic",
|
|
10266
10487
|
"flammable"
|
|
10267
10488
|
],
|
|
10268
|
-
compressive: 6e4
|
|
10489
|
+
compressive: 6e4,
|
|
10490
|
+
hits: {
|
|
10491
|
+
blunt: {
|
|
10492
|
+
tear: "none"
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10269
10495
|
},
|
|
10270
10496
|
{
|
|
10271
10497
|
id: "cactus",
|
|
@@ -10380,6 +10606,130 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10380
10606
|
"masonry"
|
|
10381
10607
|
],
|
|
10382
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
|
|
10383
10733
|
}
|
|
10384
10734
|
];
|
|
10385
10735
|
var material_hints_default = {
|
|
@@ -10575,6 +10925,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10575
10925
|
const fill = options.fillOf?.(o, material);
|
|
10576
10926
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10577
10927
|
const contains = containsOf(ud.destructionContains);
|
|
10928
|
+
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
10578
10929
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10579
10930
|
if (existing) {
|
|
10580
10931
|
const before = sizeOf(existing.box);
|
|
@@ -10587,8 +10938,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10587
10938
|
existing.skin = existing.skin && skin;
|
|
10588
10939
|
if (fill !== void 0) existing.fill = fill;
|
|
10589
10940
|
if (contains) existing.contains = contains;
|
|
10941
|
+
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
10590
10942
|
} else {
|
|
10591
|
-
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 };
|
|
10592
10944
|
entries.push(e);
|
|
10593
10945
|
if (explicit) byKey.set(explicit, e);
|
|
10594
10946
|
}
|
|
@@ -10656,7 +11008,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10656
11008
|
const minSide = Math.min(...size);
|
|
10657
11009
|
if (e.fill !== void 0) part.fill = e.fill;
|
|
10658
11010
|
else if (e.hollow) part.fill = 0.05;
|
|
10659
|
-
if (
|
|
11011
|
+
if (e.thickness !== void 0) part.thickness = e.thickness;
|
|
11012
|
+
else if (minSide < 0.01 && METAL.test(e.material)) {
|
|
10660
11013
|
part.fill = e.fill ?? 1;
|
|
10661
11014
|
part.thickness = Math.max(minSide, 5e-4);
|
|
10662
11015
|
}
|
|
@@ -10747,9 +11100,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10747
11100
|
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
10748
11101
|
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
10749
11102
|
triangle.set(corners[0], corners[1], corners[2]);
|
|
11103
|
+
if (triangle.getArea() <= 1e-14) continue;
|
|
10750
11104
|
triangle.closestPointToPoint(local2, closest);
|
|
10751
11105
|
const distance = closest.distanceToSquared(local2);
|
|
10752
|
-
if (distance
|
|
11106
|
+
if (!(distance < nearest)) continue;
|
|
10753
11107
|
nearest = distance;
|
|
10754
11108
|
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10755
11109
|
triangle.getBarycoord(closest, weights);
|
|
@@ -10928,6 +11282,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10928
11282
|
return out;
|
|
10929
11283
|
}
|
|
10930
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
|
+
}
|
|
10931
11299
|
worldBox(mv, out) {
|
|
10932
11300
|
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
10933
11301
|
this.localBox(mv, out);
|
|
@@ -11676,6 +12044,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11676
12044
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
11677
12045
|
var GLOW_FROM_C = 430;
|
|
11678
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;
|
|
11679
12052
|
var FIRE_CEILING_SECONDS = 3600;
|
|
11680
12053
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
11681
12054
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
@@ -11720,6 +12093,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11720
12093
|
if (def.fracture === "tear") return "impact_soft";
|
|
11721
12094
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
11722
12095
|
}
|
|
12096
|
+
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
11723
12097
|
function fractureKey(def, style) {
|
|
11724
12098
|
switch (style) {
|
|
11725
12099
|
case "shatter":
|
|
@@ -11761,6 +12135,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11761
12135
|
__publicField(this, "autoSplit");
|
|
11762
12136
|
__publicField(this, "maxFragments");
|
|
11763
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);
|
|
11764
12146
|
__publicField(this, "queue", []);
|
|
11765
12147
|
__publicField(this, "unsubscribe");
|
|
11766
12148
|
__publicField(this, "visuals");
|
|
@@ -11822,6 +12204,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11822
12204
|
this.cages = new RebarCages(THREE, o.scene);
|
|
11823
12205
|
this.pieceSys = new PieceSystem(THREE, {
|
|
11824
12206
|
groundAt: this.groundAt,
|
|
12207
|
+
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
11825
12208
|
gravity: this.gravity,
|
|
11826
12209
|
scene: o.scene,
|
|
11827
12210
|
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
@@ -11834,6 +12217,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11834
12217
|
// Binding
|
|
11835
12218
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11836
12219
|
bind(objectId, root, options = {}) {
|
|
12220
|
+
this.staticDirty.add(objectId);
|
|
11837
12221
|
if (this.bounds.has(objectId)) this.unbind(objectId);
|
|
11838
12222
|
const state = this.world.state(objectId);
|
|
11839
12223
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
@@ -11953,6 +12337,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11953
12337
|
}
|
|
11954
12338
|
}
|
|
11955
12339
|
unbind(objectId) {
|
|
12340
|
+
this.staticDirty.add(objectId);
|
|
11956
12341
|
const b = this.bounds.get(objectId);
|
|
11957
12342
|
if (!b) return;
|
|
11958
12343
|
this.restore(objectId);
|
|
@@ -11992,6 +12377,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11992
12377
|
return m > 0 ? m : 5;
|
|
11993
12378
|
}
|
|
11994
12379
|
makePiece(objectId, options = {}) {
|
|
12380
|
+
this.staticDirty.add(objectId);
|
|
11995
12381
|
const b = this.bounds.get(objectId);
|
|
11996
12382
|
if (!b) throw new Error(`game-destruction-three: object "${objectId}" is not bound`);
|
|
11997
12383
|
if (b.root.parent && b.root.parent !== this.scene) this.scene.attach(b.root);
|
|
@@ -12104,7 +12490,100 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12104
12490
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
12105
12491
|
// Events
|
|
12106
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
|
+
}
|
|
12107
12585
|
apply(ev) {
|
|
12586
|
+
this.staticDirty.add(ev.object);
|
|
12108
12587
|
switch (ev.type) {
|
|
12109
12588
|
case "damage":
|
|
12110
12589
|
if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
|
|
@@ -12441,7 +12920,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12441
12920
|
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
12442
12921
|
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
12443
12922
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
12444
|
-
for (const fr of ev.fragments)
|
|
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
|
+
}
|
|
12445
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 });
|
|
12446
12929
|
this.sound(impactKey(def), ev.center, 0.7);
|
|
12447
12930
|
}
|
|
@@ -12522,6 +13005,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12522
13005
|
this.pieceSys.setCenter(old, center);
|
|
12523
13006
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
12524
13007
|
old.sleeping = false;
|
|
13008
|
+
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
13009
|
+
this.makePiece(objectId);
|
|
13010
|
+
this.tipIfTopHeavy(b);
|
|
12525
13011
|
}
|
|
12526
13012
|
}
|
|
12527
13013
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -12568,8 +13054,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12568
13054
|
}
|
|
12569
13055
|
const piece = this.pieceSys.create(newId, group, half, center, this.massOf(newId), vel, ang);
|
|
12570
13056
|
nb.piece = piece;
|
|
13057
|
+
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
12571
13058
|
this.o.onPiece?.(piece);
|
|
12572
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
|
+
}
|
|
12573
13083
|
onExplode(ev, options = {}) {
|
|
12574
13084
|
const p = ev.point;
|
|
12575
13085
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
@@ -12820,6 +13330,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12820
13330
|
this.vfx.sprites().clear();
|
|
12821
13331
|
this.ownVfx?.world.clear();
|
|
12822
13332
|
}
|
|
13333
|
+
wakeRegion(x, z, radius) {
|
|
13334
|
+
this.fragments.wakeRegion(x, z, radius);
|
|
13335
|
+
this.pieceSys.wakeRegion(x, z, radius);
|
|
13336
|
+
}
|
|
12823
13337
|
stats() {
|
|
12824
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 };
|
|
12825
13339
|
}
|
|
@@ -13112,7 +13626,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13112
13626
|
volume: 0,
|
|
13113
13627
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
13114
13628
|
};
|
|
13115
|
-
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] };
|
|
13116
13638
|
|
|
13117
13639
|
// src/audio/sound.ts
|
|
13118
13640
|
var DESTRUCTION_SOUND_KEYS = {
|
|
@@ -13122,6 +13644,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13122
13644
|
impact_glass: { sound: "ping" },
|
|
13123
13645
|
impact_masonry: { sound: "chip" },
|
|
13124
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" },
|
|
13125
13652
|
shatter_glass: { sound: "glass_shatter" },
|
|
13126
13653
|
crumble: { sound: "crumble" },
|
|
13127
13654
|
splinter: { sound: "crack" },
|