@adatechnology/game-destruction-three 0.10.0 → 0.11.1
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 +613 -37
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +405 -27
- 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
|
|
|
@@ -6387,6 +6390,24 @@ var GameDestructionThree = (() => {
|
|
|
6387
6390
|
for (const decal2 of this.decals) decal2.on = false;
|
|
6388
6391
|
this.count = 0;
|
|
6389
6392
|
}
|
|
6393
|
+
/**
|
|
6394
|
+
* Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
|
|
6395
|
+
* world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
|
|
6396
|
+
*/
|
|
6397
|
+
clearInside(center, half, rotation = [0, 0, 0, 1]) {
|
|
6398
|
+
const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
|
|
6399
|
+
let gone = 0;
|
|
6400
|
+
for (const decal2 of this.decals) {
|
|
6401
|
+
if (!decal2.on) continue;
|
|
6402
|
+
const vx2 = decal2.x - center[0], vy2 = decal2.y - center[1], vz2 = decal2.z - center[2];
|
|
6403
|
+
const tx = 2 * (qy * vz2 - qz * vy2), ty = 2 * (qz * vx2 - qx * vz2), tz = 2 * (qx * vy2 - qy * vx2);
|
|
6404
|
+
const lx = vx2 + qw * tx + (qy * tz - qz * ty), ly = vy2 + qw * ty + (qz * tx - qx * tz), lz = vz2 + qw * tz + (qx * ty - qy * tx);
|
|
6405
|
+
if (Math.abs(lx) > half[0] || Math.abs(ly) > half[1] || Math.abs(lz) > half[2]) continue;
|
|
6406
|
+
decal2.on = false;
|
|
6407
|
+
gone++;
|
|
6408
|
+
}
|
|
6409
|
+
return gone;
|
|
6410
|
+
}
|
|
6390
6411
|
update(dt) {
|
|
6391
6412
|
this.count = 0;
|
|
6392
6413
|
for (const decal2 of this.decals) {
|
|
@@ -8589,6 +8610,44 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8589
8610
|
}
|
|
8590
8611
|
var LIGHT_BUDGET = EVENT_BUDGET.blastLights + EVENT_BUDGET.fireLights;
|
|
8591
8612
|
|
|
8613
|
+
// src/fitted-boxes.ts
|
|
8614
|
+
var SLICE_LENGTH = 0.12;
|
|
8615
|
+
var MAX_SLICES = 12;
|
|
8616
|
+
var SNUG_RATIO = 1.1;
|
|
8617
|
+
function pushFittedBoxes(local2, matrix, out) {
|
|
8618
|
+
const e = matrix.elements;
|
|
8619
|
+
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);
|
|
8620
|
+
const lengths = [0, 1, 2].map((k) => sizes[k] * Math.hypot(e[k * 4], e[k * 4 + 1], e[k * 4 + 2]));
|
|
8621
|
+
const longest = lengths.indexOf(Math.max(...lengths));
|
|
8622
|
+
const whole = worldBoxOf(local2, matrix, longest, 0, 1);
|
|
8623
|
+
const span = (whole[3] - whole[0]) * (whole[4] - whole[1]) * (whole[5] - whole[2]);
|
|
8624
|
+
const volume = lengths[0] * lengths[1] * lengths[2];
|
|
8625
|
+
const slices = span <= volume * SNUG_RATIO ? 1 : Math.min(MAX_SLICES, Math.ceil(lengths[longest] / SLICE_LENGTH));
|
|
8626
|
+
if (slices <= 1) {
|
|
8627
|
+
out.push(...whole);
|
|
8628
|
+
return;
|
|
8629
|
+
}
|
|
8630
|
+
for (let i = 0; i < slices; i++) out.push(...worldBoxOf(local2, matrix, longest, i / slices, (i + 1) / slices));
|
|
8631
|
+
}
|
|
8632
|
+
function worldBoxOf(local2, matrix, axis2, from, to) {
|
|
8633
|
+
const e = matrix.elements;
|
|
8634
|
+
const low = [local2.min.x, local2.min.y, local2.min.z];
|
|
8635
|
+
const high = [local2.max.x, local2.max.y, local2.max.z];
|
|
8636
|
+
const start = low[axis2];
|
|
8637
|
+
const size = high[axis2] - start;
|
|
8638
|
+
low[axis2] = start + size * from;
|
|
8639
|
+
high[axis2] = start + size * to;
|
|
8640
|
+
const box = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
8641
|
+
for (const x of [low[0], high[0]]) for (const y of [low[1], high[1]]) for (const z of [low[2], high[2]]) {
|
|
8642
|
+
for (let k = 0; k < 3; k++) {
|
|
8643
|
+
const v = e[k] * x + e[k + 4] * y + e[k + 8] * z + e[k + 12];
|
|
8644
|
+
box[k] = Math.min(box[k], v);
|
|
8645
|
+
box[k + 3] = Math.max(box[k + 3], v);
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8648
|
+
return box;
|
|
8649
|
+
}
|
|
8650
|
+
|
|
8592
8651
|
// src/math.ts
|
|
8593
8652
|
function createRng(seed) {
|
|
8594
8653
|
let a = seed >>> 0;
|
|
@@ -8630,12 +8689,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8630
8689
|
var SHEET_MAX = 4e-3;
|
|
8631
8690
|
var SHEET_AIR = 2.5;
|
|
8632
8691
|
var SHEET_SWAY = 3;
|
|
8692
|
+
var BIG_LENGTH = 0.2;
|
|
8693
|
+
var BIG_WIDTH = 0.03;
|
|
8694
|
+
var isBig = (size) => {
|
|
8695
|
+
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
8696
|
+
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
8697
|
+
};
|
|
8633
8698
|
var FragmentSystem = class {
|
|
8634
8699
|
constructor(THREE, o) {
|
|
8635
8700
|
__publicField(this, "THREE", THREE);
|
|
8636
8701
|
__publicField(this, "o", o);
|
|
8637
8702
|
__publicField(this, "list", []);
|
|
8638
8703
|
__publicField(this, "box");
|
|
8704
|
+
/** The mesh box of a fragment before its scale. */
|
|
8705
|
+
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
8639
8706
|
__publicField(this, "shardGeos", []);
|
|
8640
8707
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
8641
8708
|
__publicField(this, "q");
|
|
@@ -8644,6 +8711,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8644
8711
|
__publicField(this, "v");
|
|
8645
8712
|
__publicField(this, "w");
|
|
8646
8713
|
__publicField(this, "rng", createRng(1374496523));
|
|
8714
|
+
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
8715
|
+
__publicField(this, "tick", 0);
|
|
8647
8716
|
__publicField(this, "markGeometry", null);
|
|
8648
8717
|
__publicField(this, "markMaterial", null);
|
|
8649
8718
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -8711,8 +8780,28 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8711
8780
|
mesh2.add(disc);
|
|
8712
8781
|
}
|
|
8713
8782
|
}
|
|
8714
|
-
/**
|
|
8783
|
+
/**
|
|
8784
|
+
* 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
|
|
8785
|
+
* 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
|
|
8786
|
+
* out. Returns null when the fragment was not made.
|
|
8787
|
+
*/
|
|
8715
8788
|
spawn(f, material, home, maxAllowed) {
|
|
8789
|
+
if (this.list.length >= maxAllowed) {
|
|
8790
|
+
let alive = 0;
|
|
8791
|
+
let oldest;
|
|
8792
|
+
let oldestSmall;
|
|
8793
|
+
for (const l of this.list) {
|
|
8794
|
+
if (l.dying) continue;
|
|
8795
|
+
alive++;
|
|
8796
|
+
oldest ?? (oldest = l);
|
|
8797
|
+
if (!l.body) oldestSmall ?? (oldestSmall = l);
|
|
8798
|
+
}
|
|
8799
|
+
oldest = oldestSmall ?? oldest;
|
|
8800
|
+
if (alive >= maxAllowed) {
|
|
8801
|
+
if (!oldest || oldest.born === this.tick) return null;
|
|
8802
|
+
oldest.dying = true;
|
|
8803
|
+
}
|
|
8804
|
+
}
|
|
8716
8805
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
8717
8806
|
const mesh2 = new this.THREE.Mesh(geo, material);
|
|
8718
8807
|
mesh2.position.set(f.center[0], f.center[1], f.center[2]);
|
|
@@ -8736,19 +8825,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8736
8825
|
rest: 0,
|
|
8737
8826
|
fade: 1,
|
|
8738
8827
|
dying: false,
|
|
8739
|
-
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0
|
|
8828
|
+
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
8829
|
+
body: null,
|
|
8830
|
+
born: this.tick
|
|
8740
8831
|
};
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8832
|
+
if (f.shape !== "shard" && isBig(f.size)) {
|
|
8833
|
+
live.body = {
|
|
8834
|
+
mass: f.mass,
|
|
8835
|
+
push: (x, z) => {
|
|
8836
|
+
live.vel.x = x;
|
|
8837
|
+
live.vel.z = z;
|
|
8838
|
+
live.vel.y = Math.max(live.vel.y, 0.3);
|
|
8839
|
+
live.asleep = false;
|
|
8840
|
+
live.rest = 0;
|
|
8841
|
+
}
|
|
8842
|
+
};
|
|
8748
8843
|
}
|
|
8844
|
+
this.list.push(live);
|
|
8749
8845
|
this.addMarks(mesh2, f);
|
|
8750
8846
|
return mesh2;
|
|
8751
8847
|
}
|
|
8848
|
+
/** The boxes of the big fragments around (x, z). */
|
|
8849
|
+
solidsNear(x, z, radius, found) {
|
|
8850
|
+
const boxes = [];
|
|
8851
|
+
for (const f of this.list) {
|
|
8852
|
+
if (!f.body || f.dying) continue;
|
|
8853
|
+
const reach = f.half.length() + radius;
|
|
8854
|
+
if (Math.abs(f.mesh.position.x - x) > reach || Math.abs(f.mesh.position.z - z) > reach) continue;
|
|
8855
|
+
f.mesh.updateMatrixWorld();
|
|
8856
|
+
boxes.length = 0;
|
|
8857
|
+
pushFittedBoxes(this.unitBox, f.mesh.matrixWorld, boxes);
|
|
8858
|
+
for (let i = 0; i < boxes.length; i += 6) {
|
|
8859
|
+
if (x < boxes[i] - radius || x > boxes[i + 3] + radius || z < boxes[i + 2] - radius || z > boxes[i + 5] + radius) continue;
|
|
8860
|
+
found(f.home, [boxes[i], boxes[i + 1], boxes[i + 2]], [boxes[i + 3], boxes[i + 4], boxes[i + 5]], f.body);
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8752
8864
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
8753
8865
|
blast(point, radius, speed) {
|
|
8754
8866
|
for (const f of this.list) {
|
|
@@ -8764,7 +8876,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8764
8876
|
f.rest = 0;
|
|
8765
8877
|
}
|
|
8766
8878
|
}
|
|
8879
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
8880
|
+
wakeRegion(x, z, radius) {
|
|
8881
|
+
for (const f of this.list) {
|
|
8882
|
+
if (!f.asleep || Math.hypot(f.mesh.position.x - x, f.mesh.position.z - z) > radius) continue;
|
|
8883
|
+
f.asleep = false;
|
|
8884
|
+
f.rest = 0;
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8767
8887
|
update(dt) {
|
|
8888
|
+
this.tick++;
|
|
8768
8889
|
const g = this.o.gravity;
|
|
8769
8890
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
8770
8891
|
const f = this.list[i];
|
|
@@ -8779,7 +8900,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8779
8900
|
}
|
|
8780
8901
|
if (f.asleep) {
|
|
8781
8902
|
f.rest += dt;
|
|
8782
|
-
if (this.o.life > 0 && f.rest > this.o.life) f.dying = true;
|
|
8903
|
+
if (this.o.life > 0 && f.rest > this.o.life && !f.body) f.dying = true;
|
|
8783
8904
|
continue;
|
|
8784
8905
|
}
|
|
8785
8906
|
this.step(f, dt, g);
|
|
@@ -8853,7 +8974,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8853
8974
|
let small = 0;
|
|
8854
8975
|
for (let i = 1; i < 3; i++) if (half[i] < half[small]) small = i;
|
|
8855
8976
|
let axis2 = small;
|
|
8856
|
-
if (Math.abs(up[small]) < 0.35) {
|
|
8977
|
+
if (Math.abs(up[small]) < 0.35 && !f.body) {
|
|
8857
8978
|
axis2 = 0;
|
|
8858
8979
|
for (let i = 1; i < 3; i++) if (Math.abs(up[i]) > Math.abs(up[axis2])) axis2 = i;
|
|
8859
8980
|
}
|
|
@@ -8979,6 +9100,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8979
9100
|
var SLOP = 2e-3;
|
|
8980
9101
|
var NARROW = 0.3;
|
|
8981
9102
|
var TOPPLE = 3;
|
|
9103
|
+
var TIP_ACCELERATION = 30;
|
|
9104
|
+
var TIP_LYING = 0.25;
|
|
9105
|
+
var TIP_SECONDS = 3;
|
|
9106
|
+
var STEP = 0.03;
|
|
9107
|
+
var boxTopAt = (s, x, z) => {
|
|
9108
|
+
const e = s.e;
|
|
9109
|
+
const dx = x - s.c[0], dz = z - s.c[2];
|
|
9110
|
+
let t0 = -Infinity, t1 = Infinity;
|
|
9111
|
+
for (let k = 0; k < 3; k++) {
|
|
9112
|
+
const p = e[k * 4] * dx + e[k * 4 + 2] * dz;
|
|
9113
|
+
const d = e[k * 4 + 1];
|
|
9114
|
+
const h = s.half[k];
|
|
9115
|
+
if (Math.abs(d) < 1e-9) {
|
|
9116
|
+
if (Math.abs(p) > h) return -Infinity;
|
|
9117
|
+
continue;
|
|
9118
|
+
}
|
|
9119
|
+
let a = (-h - p) / d, b = (h - p) / d;
|
|
9120
|
+
if (a > b) [a, b] = [b, a];
|
|
9121
|
+
t0 = Math.max(t0, a);
|
|
9122
|
+
t1 = Math.min(t1, b);
|
|
9123
|
+
if (t0 > t1) return -Infinity;
|
|
9124
|
+
}
|
|
9125
|
+
return s.c[1] + t1;
|
|
9126
|
+
};
|
|
8982
9127
|
var PieceSystem = class {
|
|
8983
9128
|
constructor(THREE, o) {
|
|
8984
9129
|
__publicField(this, "THREE", THREE);
|
|
@@ -8990,6 +9135,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8990
9135
|
__publicField(this, "dq");
|
|
8991
9136
|
__publicField(this, "m");
|
|
8992
9137
|
__publicField(this, "ax");
|
|
9138
|
+
__publicField(this, "solids", []);
|
|
8993
9139
|
this.p = new THREE.Vector3();
|
|
8994
9140
|
this.q = new THREE.Quaternion();
|
|
8995
9141
|
this.dq = new THREE.Quaternion();
|
|
@@ -8999,6 +9145,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8999
9145
|
get(objectId) {
|
|
9000
9146
|
return this.list.find((b) => b.objectId === objectId) ?? null;
|
|
9001
9147
|
}
|
|
9148
|
+
/** 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. */
|
|
9149
|
+
tip(body, x, z) {
|
|
9150
|
+
body.tipping = { x, z, seconds: TIP_SECONDS };
|
|
9151
|
+
body.sleeping = false;
|
|
9152
|
+
}
|
|
9002
9153
|
create(objectId, root, halfExtents, localCenter, mass, velocity, angular) {
|
|
9003
9154
|
const existing = this.get(objectId);
|
|
9004
9155
|
if (existing) {
|
|
@@ -9012,6 +9163,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9012
9163
|
}
|
|
9013
9164
|
const body = {
|
|
9014
9165
|
objectId,
|
|
9166
|
+
tipping: null,
|
|
9015
9167
|
root,
|
|
9016
9168
|
velocity: velocity ? [...velocity] : [0, 0, 0],
|
|
9017
9169
|
angularVelocity: angular ? [...angular] : [0, 0, 0],
|
|
@@ -9081,8 +9233,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9081
9233
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
9082
9234
|
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
9235
|
}
|
|
9236
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
9237
|
+
wakeRegion(x, z, radius) {
|
|
9238
|
+
for (const b of this.list) {
|
|
9239
|
+
if (!b.sleeping || b.held || Math.hypot(b.root.position.x - x, b.root.position.z - z) > radius) continue;
|
|
9240
|
+
b.sleeping = false;
|
|
9241
|
+
b.still = 0;
|
|
9242
|
+
}
|
|
9243
|
+
}
|
|
9084
9244
|
update(dt) {
|
|
9085
9245
|
this.time += dt;
|
|
9246
|
+
this.solids = this.list.map((body) => {
|
|
9247
|
+
const c = this.centerOf(body, new this.THREE.Vector3());
|
|
9248
|
+
const h = body.halfExtents;
|
|
9249
|
+
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]) };
|
|
9250
|
+
});
|
|
9086
9251
|
for (const b of this.list) {
|
|
9087
9252
|
if (b.held) {
|
|
9088
9253
|
b.velocity = [0, 0, 0];
|
|
@@ -9096,6 +9261,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9096
9261
|
}
|
|
9097
9262
|
this.separate(dt);
|
|
9098
9263
|
}
|
|
9264
|
+
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
9265
|
+
supportAt(self, x, y, z, step) {
|
|
9266
|
+
let top = this.o.groundAt(x, z);
|
|
9267
|
+
for (const s of this.solids) {
|
|
9268
|
+
if (s.body === self || s.body.held || Math.abs(x - s.c[0]) > s.r || Math.abs(z - s.c[2]) > s.r) continue;
|
|
9269
|
+
const t = boxTopAt(s, x, z);
|
|
9270
|
+
if (t > top && t <= y + step) top = t;
|
|
9271
|
+
}
|
|
9272
|
+
const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
|
|
9273
|
+
return Math.max(top, fixed);
|
|
9274
|
+
}
|
|
9099
9275
|
integrate(b, dt) {
|
|
9100
9276
|
const c = this.centerOf(b, this.p);
|
|
9101
9277
|
const pos = [c.x, c.y, c.z];
|
|
@@ -9134,8 +9310,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9134
9310
|
const sy = i & 2 ? hy : -hy;
|
|
9135
9311
|
const sz = i & 4 ? hz : -hz;
|
|
9136
9312
|
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
|
|
9313
|
+
const cy = pos[1] + r[1];
|
|
9314
|
+
const gy = this.supportAt(b, pos[0] + r[0], cy, pos[2] + r[2], STEP + Math.max(0, -v[1]) * h * 2);
|
|
9315
|
+
const pen = gy - cy;
|
|
9139
9316
|
if (pen > -SLOP) {
|
|
9140
9317
|
rs.push(r);
|
|
9141
9318
|
if (pen > maxPen) maxPen = pen;
|
|
@@ -9158,7 +9335,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9158
9335
|
}
|
|
9159
9336
|
const lx = pos[0] + sx / rs.length;
|
|
9160
9337
|
const lz = pos[2] + sz / rs.length;
|
|
9161
|
-
landPoint = [lx,
|
|
9338
|
+
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
9162
9339
|
}
|
|
9163
9340
|
for (let it = 0; it < 4; it++) {
|
|
9164
9341
|
for (const r of rs) {
|
|
@@ -9211,6 +9388,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9211
9388
|
const wx = x1 - x0;
|
|
9212
9389
|
const wz = z1 - z0;
|
|
9213
9390
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
9391
|
+
if (b.tipping) {
|
|
9392
|
+
b.tipping.seconds -= h;
|
|
9393
|
+
if (height < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
9394
|
+
else {
|
|
9395
|
+
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
9396
|
+
w[2] += b.tipping.z * TIP_ACCELERATION * h;
|
|
9397
|
+
unstable = true;
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9214
9400
|
if (unstable) {
|
|
9215
9401
|
const dirX = x0 + x1 > 0 ? -1 : 1;
|
|
9216
9402
|
const dirZ = z0 + z1 > 0 ? -1 : 1;
|
|
@@ -9247,6 +9433,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9247
9433
|
b.still = 0;
|
|
9248
9434
|
}
|
|
9249
9435
|
}
|
|
9436
|
+
/** Half the height of the box of a piece as it is turned now. */
|
|
9437
|
+
halfHeight(b) {
|
|
9438
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
9439
|
+
return Math.abs(e[1]) * b.halfExtents[0] + Math.abs(e[5]) * b.halfExtents[1] + Math.abs(e[9]) * b.halfExtents[2];
|
|
9440
|
+
}
|
|
9250
9441
|
/** Push overlapping pieces apart (horizontal bounding circles), gently, so they do not interpenetrate forever. */
|
|
9251
9442
|
separate(dt) {
|
|
9252
9443
|
const n = this.list.length;
|
|
@@ -9265,6 +9456,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9265
9456
|
this.centerOf(a, ca);
|
|
9266
9457
|
this.centerOf(b, cb);
|
|
9267
9458
|
if (Math.abs(ca.y - cb.y) > ra + rb) continue;
|
|
9459
|
+
if (Math.abs(ca.y - cb.y) >= (this.halfHeight(a) + this.halfHeight(b)) * 0.9) continue;
|
|
9268
9460
|
const rh = (Math.max(a.halfExtents[0], a.halfExtents[2]) + Math.max(b.halfExtents[0], b.halfExtents[2])) * 0.8;
|
|
9269
9461
|
let dx = cb.x - ca.x;
|
|
9270
9462
|
let dz = cb.z - ca.z;
|
|
@@ -9660,14 +9852,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9660
9852
|
smoke: "grey"
|
|
9661
9853
|
},
|
|
9662
9854
|
visual: {
|
|
9663
|
-
charColor: 1709072
|
|
9855
|
+
charColor: 1709072,
|
|
9856
|
+
innerColor: 14730131
|
|
9664
9857
|
},
|
|
9665
9858
|
tags: [
|
|
9666
9859
|
"wood",
|
|
9667
9860
|
"organic",
|
|
9668
9861
|
"flammable"
|
|
9669
9862
|
],
|
|
9670
|
-
compressive: 1e5
|
|
9863
|
+
compressive: 1e5,
|
|
9864
|
+
hits: {
|
|
9865
|
+
blunt: {
|
|
9866
|
+
tear: "splinters",
|
|
9867
|
+
tearJoules: 60
|
|
9868
|
+
}
|
|
9869
|
+
}
|
|
9671
9870
|
},
|
|
9672
9871
|
{
|
|
9673
9872
|
id: "wood_hard",
|
|
@@ -9697,14 +9896,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9697
9896
|
smoke: "grey"
|
|
9698
9897
|
},
|
|
9699
9898
|
visual: {
|
|
9700
|
-
charColor: 1709072
|
|
9899
|
+
charColor: 1709072,
|
|
9900
|
+
innerColor: 13213802
|
|
9701
9901
|
},
|
|
9702
9902
|
tags: [
|
|
9703
9903
|
"wood",
|
|
9704
9904
|
"organic",
|
|
9705
9905
|
"flammable"
|
|
9706
9906
|
],
|
|
9707
|
-
compressive: 15e4
|
|
9907
|
+
compressive: 15e4,
|
|
9908
|
+
hits: {
|
|
9909
|
+
blunt: {
|
|
9910
|
+
tear: "splinters",
|
|
9911
|
+
tearJoules: 150
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9708
9914
|
},
|
|
9709
9915
|
{
|
|
9710
9916
|
id: "plywood",
|
|
@@ -9734,14 +9940,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9734
9940
|
smoke: "grey"
|
|
9735
9941
|
},
|
|
9736
9942
|
visual: {
|
|
9737
|
-
charColor: 1709072
|
|
9943
|
+
charColor: 1709072,
|
|
9944
|
+
innerColor: 14862234
|
|
9738
9945
|
},
|
|
9739
9946
|
tags: [
|
|
9740
9947
|
"wood",
|
|
9741
9948
|
"organic",
|
|
9742
9949
|
"flammable"
|
|
9743
9950
|
],
|
|
9744
|
-
compressive: 6e4
|
|
9951
|
+
compressive: 6e4,
|
|
9952
|
+
hits: {
|
|
9953
|
+
ballistic: {
|
|
9954
|
+
tear: "splinters"
|
|
9955
|
+
},
|
|
9956
|
+
blunt: {
|
|
9957
|
+
tear: "splinters",
|
|
9958
|
+
tearJoules: 80
|
|
9959
|
+
}
|
|
9960
|
+
}
|
|
9745
9961
|
},
|
|
9746
9962
|
{
|
|
9747
9963
|
id: "mdf",
|
|
@@ -9768,14 +9984,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9768
9984
|
smoke: "grey"
|
|
9769
9985
|
},
|
|
9770
9986
|
visual: {
|
|
9771
|
-
charColor: 1709072
|
|
9987
|
+
charColor: 1709072,
|
|
9988
|
+
innerColor: 12096362
|
|
9772
9989
|
},
|
|
9773
9990
|
tags: [
|
|
9774
9991
|
"wood",
|
|
9775
9992
|
"organic",
|
|
9776
9993
|
"flammable"
|
|
9777
9994
|
],
|
|
9778
|
-
compressive: 3e4
|
|
9995
|
+
compressive: 3e4,
|
|
9996
|
+
hits: {
|
|
9997
|
+
ballistic: {
|
|
9998
|
+
tear: "lumps",
|
|
9999
|
+
tearJoules: 120
|
|
10000
|
+
},
|
|
10001
|
+
blunt: {
|
|
10002
|
+
tear: "lumps",
|
|
10003
|
+
tearJoules: 20
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
9779
10006
|
},
|
|
9780
10007
|
{
|
|
9781
10008
|
id: "glass",
|
|
@@ -9889,8 +10116,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9889
10116
|
damageScale: {
|
|
9890
10117
|
explosive: 1.2
|
|
9891
10118
|
},
|
|
10119
|
+
hits: {
|
|
10120
|
+
ballistic: {
|
|
10121
|
+
unitJoules: 500,
|
|
10122
|
+
floor: 900,
|
|
10123
|
+
crater: true
|
|
10124
|
+
},
|
|
10125
|
+
blunt: {
|
|
10126
|
+
unitJoules: 1500,
|
|
10127
|
+
floor: 60
|
|
10128
|
+
}
|
|
10129
|
+
},
|
|
9892
10130
|
hitDebris: "chips",
|
|
9893
10131
|
breakDebris: "dust",
|
|
10132
|
+
visual: { innerColor: 10197652 },
|
|
9894
10133
|
thermal: {
|
|
9895
10134
|
conductivity: 0.15,
|
|
9896
10135
|
heatCapacity: 2.5
|
|
@@ -9921,6 +10160,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9921
10160
|
],
|
|
9922
10161
|
hitDebris: "chips",
|
|
9923
10162
|
breakDebris: "dust",
|
|
10163
|
+
visual: { innerColor: 12085567 },
|
|
9924
10164
|
thermal: {
|
|
9925
10165
|
conductivity: 0.15,
|
|
9926
10166
|
heatCapacity: 2.5
|
|
@@ -9951,6 +10191,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9951
10191
|
],
|
|
9952
10192
|
hitDebris: "dust",
|
|
9953
10193
|
breakDebris: "dust",
|
|
10194
|
+
visual: { innerColor: 14999507 },
|
|
9954
10195
|
thermal: {
|
|
9955
10196
|
conductivity: 0.1,
|
|
9956
10197
|
heatCapacity: 1.5
|
|
@@ -9976,6 +10217,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9976
10217
|
],
|
|
9977
10218
|
hitDebris: "chips",
|
|
9978
10219
|
breakDebris: "dust",
|
|
10220
|
+
visual: { innerColor: 9407364 },
|
|
9979
10221
|
thermal: {
|
|
9980
10222
|
conductivity: 0.2,
|
|
9981
10223
|
heatCapacity: 2.5
|
|
@@ -10257,7 +10499,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10257
10499
|
smoke: "white"
|
|
10258
10500
|
},
|
|
10259
10501
|
visual: {
|
|
10260
|
-
charColor: 1709072
|
|
10502
|
+
charColor: 1709072,
|
|
10503
|
+
innerColor: 14207126
|
|
10261
10504
|
},
|
|
10262
10505
|
tags: [
|
|
10263
10506
|
"plant",
|
|
@@ -10265,7 +10508,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10265
10508
|
"organic",
|
|
10266
10509
|
"flammable"
|
|
10267
10510
|
],
|
|
10268
|
-
compressive: 6e4
|
|
10511
|
+
compressive: 6e4,
|
|
10512
|
+
hits: {
|
|
10513
|
+
blunt: {
|
|
10514
|
+
tear: "none"
|
|
10515
|
+
}
|
|
10516
|
+
}
|
|
10269
10517
|
},
|
|
10270
10518
|
{
|
|
10271
10519
|
id: "cactus",
|
|
@@ -10380,6 +10628,130 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10380
10628
|
"masonry"
|
|
10381
10629
|
],
|
|
10382
10630
|
compressive: 2e4
|
|
10631
|
+
},
|
|
10632
|
+
{
|
|
10633
|
+
id: "asphalt",
|
|
10634
|
+
density: 2350,
|
|
10635
|
+
hpPerKg: 10,
|
|
10636
|
+
penetration: 14,
|
|
10637
|
+
penetrationClass: "concrete",
|
|
10638
|
+
penetrationScale: 0.8,
|
|
10639
|
+
ductility: 0.1,
|
|
10640
|
+
fracture: "crumble",
|
|
10641
|
+
fragmentSize: [
|
|
10642
|
+
0.03,
|
|
10643
|
+
0.15
|
|
10644
|
+
],
|
|
10645
|
+
hitDebris: "chips",
|
|
10646
|
+
breakDebris: "dust",
|
|
10647
|
+
thermal: {
|
|
10648
|
+
conductivity: 0.1,
|
|
10649
|
+
heatCapacity: 2,
|
|
10650
|
+
ignitionC: 400,
|
|
10651
|
+
burnRate: 2e-3,
|
|
10652
|
+
smoke: "black"
|
|
10653
|
+
},
|
|
10654
|
+
tags: [
|
|
10655
|
+
"masonry",
|
|
10656
|
+
"brittle"
|
|
10657
|
+
],
|
|
10658
|
+
compressive: 3e4
|
|
10659
|
+
},
|
|
10660
|
+
{
|
|
10661
|
+
id: "sand",
|
|
10662
|
+
density: 1600,
|
|
10663
|
+
hpPerKg: 2,
|
|
10664
|
+
penetration: 6,
|
|
10665
|
+
ductility: 0.4,
|
|
10666
|
+
fracture: "crumble",
|
|
10667
|
+
fragmentSize: [
|
|
10668
|
+
0.01,
|
|
10669
|
+
0.04
|
|
10670
|
+
],
|
|
10671
|
+
hitDebris: "dust",
|
|
10672
|
+
breakDebris: "dust",
|
|
10673
|
+
thermal: {
|
|
10674
|
+
conductivity: 0.1,
|
|
10675
|
+
heatCapacity: 2
|
|
10676
|
+
},
|
|
10677
|
+
tags: [
|
|
10678
|
+
"soil"
|
|
10679
|
+
],
|
|
10680
|
+
compressive: 1e4
|
|
10681
|
+
},
|
|
10682
|
+
{
|
|
10683
|
+
id: "gravel",
|
|
10684
|
+
density: 1800,
|
|
10685
|
+
hpPerKg: 3,
|
|
10686
|
+
penetration: 8,
|
|
10687
|
+
ductility: 0.3,
|
|
10688
|
+
fracture: "crumble",
|
|
10689
|
+
fragmentSize: [
|
|
10690
|
+
0.02,
|
|
10691
|
+
0.06
|
|
10692
|
+
],
|
|
10693
|
+
hitDebris: "chips",
|
|
10694
|
+
breakDebris: "dust",
|
|
10695
|
+
thermal: {
|
|
10696
|
+
conductivity: 0.1,
|
|
10697
|
+
heatCapacity: 2
|
|
10698
|
+
},
|
|
10699
|
+
tags: [
|
|
10700
|
+
"soil",
|
|
10701
|
+
"masonry"
|
|
10702
|
+
],
|
|
10703
|
+
compressive: 2e4
|
|
10704
|
+
},
|
|
10705
|
+
{
|
|
10706
|
+
id: "turf",
|
|
10707
|
+
density: 900,
|
|
10708
|
+
hpPerKg: 3,
|
|
10709
|
+
penetration: 4,
|
|
10710
|
+
ductility: 0.5,
|
|
10711
|
+
fracture: "crumble",
|
|
10712
|
+
fragmentSize: [
|
|
10713
|
+
0.03,
|
|
10714
|
+
0.1
|
|
10715
|
+
],
|
|
10716
|
+
hitDebris: "dust",
|
|
10717
|
+
breakDebris: "dust",
|
|
10718
|
+
thermal: {
|
|
10719
|
+
conductivity: 0.1,
|
|
10720
|
+
heatCapacity: 1.5,
|
|
10721
|
+
ignitionC: 250,
|
|
10722
|
+
burnRate: 0.03,
|
|
10723
|
+
smoke: "white"
|
|
10724
|
+
},
|
|
10725
|
+
tags: [
|
|
10726
|
+
"soil",
|
|
10727
|
+
"organic"
|
|
10728
|
+
],
|
|
10729
|
+
compressive: 15e3
|
|
10730
|
+
},
|
|
10731
|
+
{
|
|
10732
|
+
id: "mortar",
|
|
10733
|
+
density: 1900,
|
|
10734
|
+
hpPerKg: 10,
|
|
10735
|
+
penetration: 12,
|
|
10736
|
+
penetrationClass: "concrete",
|
|
10737
|
+
penetrationScale: 1.2,
|
|
10738
|
+
ductility: 0.03,
|
|
10739
|
+
fracture: "crumble",
|
|
10740
|
+
fragmentSize: [
|
|
10741
|
+
0.02,
|
|
10742
|
+
0.08
|
|
10743
|
+
],
|
|
10744
|
+
hitDebris: "chips",
|
|
10745
|
+
breakDebris: "dust",
|
|
10746
|
+
thermal: {
|
|
10747
|
+
conductivity: 0.15,
|
|
10748
|
+
heatCapacity: 2.5
|
|
10749
|
+
},
|
|
10750
|
+
tags: [
|
|
10751
|
+
"masonry",
|
|
10752
|
+
"brittle"
|
|
10753
|
+
],
|
|
10754
|
+
compressive: 1e5
|
|
10383
10755
|
}
|
|
10384
10756
|
];
|
|
10385
10757
|
var material_hints_default = {
|
|
@@ -10575,6 +10947,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10575
10947
|
const fill = options.fillOf?.(o, material);
|
|
10576
10948
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10577
10949
|
const contains = containsOf(ud.destructionContains);
|
|
10950
|
+
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
10578
10951
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10579
10952
|
if (existing) {
|
|
10580
10953
|
const before = sizeOf(existing.box);
|
|
@@ -10587,8 +10960,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10587
10960
|
existing.skin = existing.skin && skin;
|
|
10588
10961
|
if (fill !== void 0) existing.fill = fill;
|
|
10589
10962
|
if (contains) existing.contains = contains;
|
|
10963
|
+
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
10590
10964
|
} 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 };
|
|
10965
|
+
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
10966
|
entries.push(e);
|
|
10593
10967
|
if (explicit) byKey.set(explicit, e);
|
|
10594
10968
|
}
|
|
@@ -10656,7 +11030,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10656
11030
|
const minSide = Math.min(...size);
|
|
10657
11031
|
if (e.fill !== void 0) part.fill = e.fill;
|
|
10658
11032
|
else if (e.hollow) part.fill = 0.05;
|
|
10659
|
-
if (
|
|
11033
|
+
if (e.thickness !== void 0) part.thickness = e.thickness;
|
|
11034
|
+
else if (minSide < 0.01 && METAL.test(e.material)) {
|
|
10660
11035
|
part.fill = e.fill ?? 1;
|
|
10661
11036
|
part.thickness = Math.max(minSide, 5e-4);
|
|
10662
11037
|
}
|
|
@@ -10747,9 +11122,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10747
11122
|
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
10748
11123
|
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
10749
11124
|
triangle.set(corners[0], corners[1], corners[2]);
|
|
11125
|
+
if (triangle.getArea() <= 1e-14) continue;
|
|
10750
11126
|
triangle.closestPointToPoint(local2, closest);
|
|
10751
11127
|
const distance = closest.distanceToSquared(local2);
|
|
10752
|
-
if (distance
|
|
11128
|
+
if (!(distance < nearest)) continue;
|
|
10753
11129
|
nearest = distance;
|
|
10754
11130
|
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
10755
11131
|
triangle.getBarycoord(closest, weights);
|
|
@@ -10928,6 +11304,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10928
11304
|
return out;
|
|
10929
11305
|
}
|
|
10930
11306
|
/** Box of the part in world space. */
|
|
11307
|
+
/** Appends the boxes that follow the mesh in the world (`pushFittedBoxes`) to `out`. */
|
|
11308
|
+
fittedBoxes(mv, out) {
|
|
11309
|
+
const box = new this.THREE.Box3();
|
|
11310
|
+
if (mv.host) this.localBox(mv, box);
|
|
11311
|
+
else {
|
|
11312
|
+
const geometry = mv.mesh.geometry;
|
|
11313
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
11314
|
+
if (geometry.boundingBox) box.copy(geometry.boundingBox);
|
|
11315
|
+
else box.makeEmpty();
|
|
11316
|
+
}
|
|
11317
|
+
if (box.isEmpty()) return;
|
|
11318
|
+
mv.mesh.updateWorldMatrix(true, false);
|
|
11319
|
+
pushFittedBoxes(box, mv.mesh.matrixWorld, out);
|
|
11320
|
+
}
|
|
10931
11321
|
worldBox(mv, out) {
|
|
10932
11322
|
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
10933
11323
|
this.localBox(mv, out);
|
|
@@ -11191,6 +11581,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11191
11581
|
d.geometry?.dispose();
|
|
11192
11582
|
this.decalCount = Math.max(0, this.decalCount - 1);
|
|
11193
11583
|
}
|
|
11584
|
+
/**
|
|
11585
|
+
* Takes away the decals of a mesh that sit inside a box (world `center`, `half` extents along the axes of the world `rotation`): the surface they
|
|
11586
|
+
* marked is gone (a unit came out of the wall with its bullet holes and spalls).
|
|
11587
|
+
*/
|
|
11588
|
+
clearDecals(mv, center, half, rotation) {
|
|
11589
|
+
const inv = rotation.clone().invert();
|
|
11590
|
+
const p = new this.THREE.Vector3();
|
|
11591
|
+
for (let i = mv.decals.length - 1; i >= 0; i--) {
|
|
11592
|
+
const d = mv.decals[i];
|
|
11593
|
+
d.obj.getWorldPosition(p).sub(center).applyQuaternion(inv);
|
|
11594
|
+
if (Math.abs(p.x) > half.x || Math.abs(p.y) > half.y || Math.abs(p.z) > half.z) continue;
|
|
11595
|
+
this.disposeDecal(d);
|
|
11596
|
+
mv.decals.splice(i, 1);
|
|
11597
|
+
}
|
|
11598
|
+
}
|
|
11194
11599
|
addHole(mv, worldPoint, worldNormal, diameter) {
|
|
11195
11600
|
const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 25e-4);
|
|
11196
11601
|
const work = mv.host ? mv.host.work : mv.work;
|
|
@@ -11673,9 +12078,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11673
12078
|
// src/renderer.ts
|
|
11674
12079
|
var rendererCount = 0;
|
|
11675
12080
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
12081
|
+
var CHIP_DECAL_EDGE = 0.05;
|
|
11676
12082
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
11677
12083
|
var GLOW_FROM_C = 430;
|
|
11678
12084
|
var QUEUE_CAP = 4e3;
|
|
12085
|
+
var STATIC_REFRESH = 0.5;
|
|
12086
|
+
var STATIC_SPAN = 3;
|
|
12087
|
+
var FEET_BAND = 0.03;
|
|
12088
|
+
var TIP_MIN_HEIGHT = 0.3;
|
|
12089
|
+
var TIP_NARROW = 0.3;
|
|
11679
12090
|
var FIRE_CEILING_SECONDS = 3600;
|
|
11680
12091
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
11681
12092
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
@@ -11720,6 +12131,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11720
12131
|
if (def.fracture === "tear") return "impact_soft";
|
|
11721
12132
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
11722
12133
|
}
|
|
12134
|
+
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
11723
12135
|
function fractureKey(def, style) {
|
|
11724
12136
|
switch (style) {
|
|
11725
12137
|
case "shatter":
|
|
@@ -11761,6 +12173,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11761
12173
|
__publicField(this, "autoSplit");
|
|
11762
12174
|
__publicField(this, "maxFragments");
|
|
11763
12175
|
__publicField(this, "bounds", /* @__PURE__ */ new Map());
|
|
12176
|
+
/** World boxes (min xyz, max xyz) of the drawn meshes of the bound objects that are not pieces, for the pieces to land on. */
|
|
12177
|
+
__publicField(this, "staticBoxes", /* @__PURE__ */ new Map());
|
|
12178
|
+
/** Objects to measure again; `staticAll` measures every one. */
|
|
12179
|
+
__publicField(this, "staticDirty", /* @__PURE__ */ new Set());
|
|
12180
|
+
__publicField(this, "staticAll", true);
|
|
12181
|
+
/** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
|
|
12182
|
+
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
12183
|
+
__publicField(this, "staticAt", 0);
|
|
11764
12184
|
__publicField(this, "queue", []);
|
|
11765
12185
|
__publicField(this, "unsubscribe");
|
|
11766
12186
|
__publicField(this, "visuals");
|
|
@@ -11822,6 +12242,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11822
12242
|
this.cages = new RebarCages(THREE, o.scene);
|
|
11823
12243
|
this.pieceSys = new PieceSystem(THREE, {
|
|
11824
12244
|
groundAt: this.groundAt,
|
|
12245
|
+
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
11825
12246
|
gravity: this.gravity,
|
|
11826
12247
|
scene: o.scene,
|
|
11827
12248
|
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
@@ -11834,6 +12255,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11834
12255
|
// Binding
|
|
11835
12256
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
11836
12257
|
bind(objectId, root, options = {}) {
|
|
12258
|
+
this.staticDirty.add(objectId);
|
|
11837
12259
|
if (this.bounds.has(objectId)) this.unbind(objectId);
|
|
11838
12260
|
const state = this.world.state(objectId);
|
|
11839
12261
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
@@ -11953,6 +12375,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11953
12375
|
}
|
|
11954
12376
|
}
|
|
11955
12377
|
unbind(objectId) {
|
|
12378
|
+
this.staticDirty.add(objectId);
|
|
11956
12379
|
const b = this.bounds.get(objectId);
|
|
11957
12380
|
if (!b) return;
|
|
11958
12381
|
this.restore(objectId);
|
|
@@ -11992,6 +12415,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11992
12415
|
return m > 0 ? m : 5;
|
|
11993
12416
|
}
|
|
11994
12417
|
makePiece(objectId, options = {}) {
|
|
12418
|
+
this.staticDirty.add(objectId);
|
|
11995
12419
|
const b = this.bounds.get(objectId);
|
|
11996
12420
|
if (!b) throw new Error(`game-destruction-three: object "${objectId}" is not bound`);
|
|
11997
12421
|
if (b.root.parent && b.root.parent !== this.scene) this.scene.attach(b.root);
|
|
@@ -12104,7 +12528,100 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12104
12528
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
12105
12529
|
// Events
|
|
12106
12530
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
12531
|
+
/**
|
|
12532
|
+
* 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
|
|
12533
|
+
* 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`
|
|
12534
|
+
* are left out (walls, floors, roofs: the terrain under them is the host's `groundAt`).
|
|
12535
|
+
*/
|
|
12536
|
+
staticTopAt(x, z, y, step) {
|
|
12537
|
+
let top = -Infinity;
|
|
12538
|
+
for (const a of this.staticNow().values()) {
|
|
12539
|
+
if (a.length === 0 || x < a[0] || x > a[3] || z < a[2] || z > a[5]) continue;
|
|
12540
|
+
for (let i = 6; i < a.length; i += 6) {
|
|
12541
|
+
if (x < a[i] || x > a[i + 3] || z < a[i + 2] || z > a[i + 5]) continue;
|
|
12542
|
+
const t = a[i + 4];
|
|
12543
|
+
if (t > top && t <= y + step) top = t;
|
|
12544
|
+
}
|
|
12545
|
+
}
|
|
12546
|
+
return top;
|
|
12547
|
+
}
|
|
12548
|
+
/** 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). */
|
|
12549
|
+
staticNow() {
|
|
12550
|
+
if (this.time - this.staticAt > STATIC_REFRESH) {
|
|
12551
|
+
this.staticAt = this.time;
|
|
12552
|
+
this.staticAll = true;
|
|
12553
|
+
}
|
|
12554
|
+
if (!this.staticAll && this.staticDirty.size === 0) return this.staticBoxes;
|
|
12555
|
+
const box = new this.THREE.Box3();
|
|
12556
|
+
const measure = (b) => {
|
|
12557
|
+
if (b.piece || this.pieceSys.get(b.id)) {
|
|
12558
|
+
this.staticBoxes.delete(b.id);
|
|
12559
|
+
return;
|
|
12560
|
+
}
|
|
12561
|
+
const out = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
12562
|
+
for (const mv of this.meshesOf(b)) {
|
|
12563
|
+
if (mv.host || mv.loose || !this.visuals.shown(mv)) continue;
|
|
12564
|
+
this.visuals.worldBox(mv, box);
|
|
12565
|
+
if (box.isEmpty() || box.max.x - box.min.x > STATIC_SPAN || box.max.z - box.min.z > STATIC_SPAN) continue;
|
|
12566
|
+
this.visuals.fittedBoxes(mv, out);
|
|
12567
|
+
for (let k = 0; k < 3; k++) {
|
|
12568
|
+
out[k] = Math.min(out[k], box.min.getComponent(k));
|
|
12569
|
+
out[k + 3] = Math.max(out[k + 3], box.max.getComponent(k));
|
|
12570
|
+
}
|
|
12571
|
+
}
|
|
12572
|
+
this.staticBoxes.set(b.id, out.length > 6 ? out : []);
|
|
12573
|
+
};
|
|
12574
|
+
if (this.staticAll) {
|
|
12575
|
+
this.staticBoxes.clear();
|
|
12576
|
+
for (const b of this.bounds.values()) measure(b);
|
|
12577
|
+
} else {
|
|
12578
|
+
for (const id of this.staticDirty) {
|
|
12579
|
+
const b = this.bounds.get(id);
|
|
12580
|
+
if (b) measure(b);
|
|
12581
|
+
else this.staticBoxes.delete(id);
|
|
12582
|
+
}
|
|
12583
|
+
}
|
|
12584
|
+
this.staticAll = false;
|
|
12585
|
+
this.staticDirty.clear();
|
|
12586
|
+
return this.staticBoxes;
|
|
12587
|
+
}
|
|
12588
|
+
/** The boxes of a piece's drawn meshes, measured once per frame. */
|
|
12589
|
+
pieceBoxesOf(pc) {
|
|
12590
|
+
const cached = this.pieceBoxes.get(pc);
|
|
12591
|
+
if (cached && cached.at === this.time) return cached.boxes;
|
|
12592
|
+
const boxes = [];
|
|
12593
|
+
const b = this.bounds.get(pc.objectId);
|
|
12594
|
+
if (b) {
|
|
12595
|
+
for (const mv of this.meshesOf(b)) {
|
|
12596
|
+
if (this.visuals.shown(mv)) this.visuals.fittedBoxes(mv, boxes);
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12599
|
+
this.pieceBoxes.set(pc, { at: this.time, boxes });
|
|
12600
|
+
return boxes;
|
|
12601
|
+
}
|
|
12602
|
+
solidsNear(x, z, radius) {
|
|
12603
|
+
const found = [];
|
|
12604
|
+
const scan = (a, id, piece, from) => {
|
|
12605
|
+
if (from > 0 && (a.length === 0 || x < a[0] - radius || x > a[3] + radius || z < a[2] - radius || z > a[5] + radius)) return;
|
|
12606
|
+
for (let i = from; i < a.length; i += 6) {
|
|
12607
|
+
if (x < a[i] - radius || x > a[i + 3] + radius || z < a[i + 2] - radius || z > a[i + 5] + radius) continue;
|
|
12608
|
+
found.push({ objectId: id, min: [a[i], a[i + 1], a[i + 2]], max: [a[i + 3], a[i + 4], a[i + 5]], piece });
|
|
12609
|
+
}
|
|
12610
|
+
};
|
|
12611
|
+
for (const [id, a] of this.staticNow()) scan(a, id, null, 6);
|
|
12612
|
+
const c = this.v1;
|
|
12613
|
+
for (const pc of this.pieceSys.list) {
|
|
12614
|
+
if (pc.held) continue;
|
|
12615
|
+
this.pieceSys.centerOf(pc, c);
|
|
12616
|
+
const reach = Math.hypot(pc.halfExtents[0], pc.halfExtents[1], pc.halfExtents[2]) + radius;
|
|
12617
|
+
if (Math.abs(c.x - x) > reach || Math.abs(c.z - z) > reach) continue;
|
|
12618
|
+
scan(this.pieceBoxesOf(pc), pc.objectId, pc, 0);
|
|
12619
|
+
}
|
|
12620
|
+
this.fragments.solidsNear(x, z, radius, (home, min2, max2, loose) => found.push({ objectId: home, min: min2, max: max2, piece: null, loose }));
|
|
12621
|
+
return found;
|
|
12622
|
+
}
|
|
12107
12623
|
apply(ev) {
|
|
12624
|
+
this.staticDirty.add(ev.object);
|
|
12108
12625
|
switch (ev.type) {
|
|
12109
12626
|
case "damage":
|
|
12110
12627
|
if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
|
|
@@ -12125,7 +12642,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12125
12642
|
break;
|
|
12126
12643
|
}
|
|
12127
12644
|
case "debris":
|
|
12128
|
-
this.vfx.burst(ev.kind, ev.point, ev.normal, ev.count, ev.speed > 0 ? ev.speed : DEFAULT_SPEED[ev.kind] ?? 1, this.debrisOptions(ev.kind, ev.object, ev.part));
|
|
12645
|
+
this.vfx.burst(ev.kind, ev.point, ev.normal, ev.count, ev.speed > 0 ? ev.speed : DEFAULT_SPEED[ev.kind] ?? 1, this.debrisOptions(ev.kind, ev.object, ev.part, ev.material));
|
|
12129
12646
|
break;
|
|
12130
12647
|
case "fracture":
|
|
12131
12648
|
this.onFracture(ev);
|
|
@@ -12298,10 +12815,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12298
12815
|
}
|
|
12299
12816
|
return def ? fallbackColor(def.id, def) : null;
|
|
12300
12817
|
}
|
|
12301
|
-
debrisOptions(kind, objectId, partId) {
|
|
12818
|
+
debrisOptions(kind, objectId, partId, material) {
|
|
12302
12819
|
const pv = this.partVis(objectId, partId);
|
|
12303
|
-
const def =
|
|
12820
|
+
const def = this.world.materials.get(material ?? pv?.material ?? "");
|
|
12304
12821
|
if (kind === "smoke") return { color: SMOKE_TONES[def?.thermal.smoke ?? "grey"] };
|
|
12822
|
+
if ((kind === "dust" || kind === "chips") && def?.visual?.innerColor !== void 0) return { color: def.visual.innerColor };
|
|
12305
12823
|
if (kind === "fibers" || kind === "leaves") {
|
|
12306
12824
|
if (def?.visual?.innerColor !== void 0) return { color: def.visual.innerColor };
|
|
12307
12825
|
const c = pv ? this.visuals.baseColorHex(pv.meshes[0] ?? {}) : -1;
|
|
@@ -12409,8 +12927,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12409
12927
|
const mv = this.nearestMesh(pv, wp);
|
|
12410
12928
|
if (!mv) return;
|
|
12411
12929
|
const def = this.world.materials.get(pv.material);
|
|
12412
|
-
|
|
12413
|
-
|
|
12930
|
+
const normal = this.faceNormal(mv, wp);
|
|
12931
|
+
const core = def?.visual?.innerColor;
|
|
12932
|
+
if (core !== void 0) this.vfx.burst("chips", ev.point, [normal.x, normal.y, normal.z], 5, 2.5, { color: core });
|
|
12933
|
+
else this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.point);
|
|
12934
|
+
this.visuals.addSpall(mv, wp, normal, Math.sqrt(ev.area / Math.PI), core ?? fallbackColor(pv.material, def));
|
|
12414
12935
|
}
|
|
12415
12936
|
/**
|
|
12416
12937
|
* Stage 2 of a staged part: the unit-sized fragments come out and the part stays drawn (hiding it is what `fracture` does). The opening itself
|
|
@@ -12433,16 +12954,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12433
12954
|
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
12434
12955
|
}
|
|
12435
12956
|
/**
|
|
12436
|
-
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust
|
|
12437
|
-
* the
|
|
12957
|
+
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust, and the marks drawn on it (bullet holes, spalls, the impact
|
|
12958
|
+
* marks of the effects) go with it. The gap itself is the drawing's (the house renderer cuts the wall where the unit was); a part drawn as one box
|
|
12959
|
+
* keeps its box.
|
|
12438
12960
|
*/
|
|
12439
12961
|
onChip(ev) {
|
|
12440
12962
|
const pv = this.partVis(ev.object, ev.part);
|
|
12441
12963
|
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
12442
12964
|
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
12443
12965
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
12444
|
-
for (const fr of ev.fragments)
|
|
12445
|
-
|
|
12966
|
+
for (const fr of ev.fragments) {
|
|
12967
|
+
const fdef = this.world.materials.get(fr.material);
|
|
12968
|
+
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
12969
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone, false), ev.object, maxAlive);
|
|
12970
|
+
}
|
|
12971
|
+
const T = this.THREE;
|
|
12972
|
+
const center = new T.Vector3(ev.center[0], ev.center[1], ev.center[2]);
|
|
12973
|
+
const half = new T.Vector3(ev.size[0] / 2 + CHIP_DECAL_EDGE, ev.size[1] / 2 + CHIP_DECAL_EDGE, ev.size[2] / 2 + CHIP_DECAL_EDGE);
|
|
12974
|
+
const rotation = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
|
|
12975
|
+
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
|
|
12976
|
+
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
12977
|
+
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
12446
12978
|
this.sound(impactKey(def), ev.center, 0.7);
|
|
12447
12979
|
}
|
|
12448
12980
|
onFracture(ev) {
|
|
@@ -12522,6 +13054,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12522
13054
|
this.pieceSys.setCenter(old, center);
|
|
12523
13055
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
12524
13056
|
old.sleeping = false;
|
|
13057
|
+
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
13058
|
+
this.makePiece(objectId);
|
|
13059
|
+
this.tipIfTopHeavy(b);
|
|
12525
13060
|
}
|
|
12526
13061
|
}
|
|
12527
13062
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -12568,8 +13103,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12568
13103
|
}
|
|
12569
13104
|
const piece = this.pieceSys.create(newId, group, half, center, this.massOf(newId), vel, ang);
|
|
12570
13105
|
nb.piece = piece;
|
|
13106
|
+
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
12571
13107
|
this.o.onPiece?.(piece);
|
|
12572
13108
|
}
|
|
13109
|
+
/**
|
|
13110
|
+
* 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
|
|
13111
|
+
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
13112
|
+
*/
|
|
13113
|
+
tipIfTopHeavy(b) {
|
|
13114
|
+
const piece = b.piece;
|
|
13115
|
+
if (!piece) return;
|
|
13116
|
+
const box = new this.THREE.Box3();
|
|
13117
|
+
const boxes = this.meshesOf(b).filter((mv) => this.visuals.shown(mv)).map((mv) => this.visuals.worldBox(mv, new this.THREE.Box3()));
|
|
13118
|
+
if (boxes.length === 0) return;
|
|
13119
|
+
for (const one of boxes) box.union(one);
|
|
13120
|
+
const height = box.max.y - box.min.y;
|
|
13121
|
+
const feet = new this.THREE.Box3();
|
|
13122
|
+
for (const one of boxes) if (one.min.y < box.min.y + FEET_BAND) feet.union(one);
|
|
13123
|
+
const spanX = feet.max.x - feet.min.x;
|
|
13124
|
+
const spanZ = feet.max.z - feet.min.z;
|
|
13125
|
+
if (height < TIP_MIN_HEIGHT || Math.min(spanX, spanZ) >= TIP_NARROW * height) return;
|
|
13126
|
+
const across = spanX <= spanZ ? 0 : 2;
|
|
13127
|
+
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;
|
|
13128
|
+
const away = lean < 0 ? -1 : 1;
|
|
13129
|
+
if (across === 0) this.pieceSys.tip(piece, 0, -away);
|
|
13130
|
+
else this.pieceSys.tip(piece, away, 0);
|
|
13131
|
+
}
|
|
12573
13132
|
onExplode(ev, options = {}) {
|
|
12574
13133
|
const p = ev.point;
|
|
12575
13134
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
@@ -12820,6 +13379,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12820
13379
|
this.vfx.sprites().clear();
|
|
12821
13380
|
this.ownVfx?.world.clear();
|
|
12822
13381
|
}
|
|
13382
|
+
wakeRegion(x, z, radius) {
|
|
13383
|
+
this.fragments.wakeRegion(x, z, radius);
|
|
13384
|
+
this.pieceSys.wakeRegion(x, z, radius);
|
|
13385
|
+
}
|
|
12823
13386
|
stats() {
|
|
12824
13387
|
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
13388
|
}
|
|
@@ -13112,7 +13675,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13112
13675
|
volume: 0,
|
|
13113
13676
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
13114
13677
|
};
|
|
13115
|
-
var
|
|
13678
|
+
var drag = (id, layers) => ({ ...sceneRecipe(id, layers, ""), sources: ["sdk: a base dragged on a floor, one grain per stretch"] });
|
|
13679
|
+
var DRAG_SOUNDS = [
|
|
13680
|
+
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 })]),
|
|
13681
|
+
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 })]),
|
|
13682
|
+
drag("drag_glass", [sceneNoise({ type: "bandpass", f: 2600, q: 5, d: 0.2, vol: 0.22 })]),
|
|
13683
|
+
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] }))]),
|
|
13684
|
+
drag("drag_soft", [sceneNoise({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
13685
|
+
];
|
|
13686
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
13116
13687
|
|
|
13117
13688
|
// src/audio/sound.ts
|
|
13118
13689
|
var DESTRUCTION_SOUND_KEYS = {
|
|
@@ -13122,6 +13693,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13122
13693
|
impact_glass: { sound: "ping" },
|
|
13123
13694
|
impact_masonry: { sound: "chip" },
|
|
13124
13695
|
impact_soft: { sound: "thud" },
|
|
13696
|
+
drag_wood: { sound: "drag_wood" },
|
|
13697
|
+
drag_metal: { sound: "drag_metal" },
|
|
13698
|
+
drag_glass: { sound: "drag_glass" },
|
|
13699
|
+
drag_masonry: { sound: "drag_masonry" },
|
|
13700
|
+
drag_soft: { sound: "drag_soft" },
|
|
13125
13701
|
shatter_glass: { sound: "glass_shatter" },
|
|
13126
13702
|
crumble: { sound: "crumble" },
|
|
13127
13703
|
splinter: { sound: "crack" },
|