@adatechnology/game-destruction-three 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.global.js +630 -174
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +445 -86
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.global.js
CHANGED
|
@@ -7727,6 +7727,11 @@ void main(){
|
|
|
7727
7727
|
var MAX_CONFORM_STEPS = 48;
|
|
7728
7728
|
var MIN_CONFORM_STEPS = 16;
|
|
7729
7729
|
var BLOOD_GROW_RATE = 1.2;
|
|
7730
|
+
var BLOOD_START_GROWTH = 0.05;
|
|
7731
|
+
var DEFAULT_MAX_GROUND_MARKS = 64;
|
|
7732
|
+
var DEFAULT_MAX_ROCKS = 512;
|
|
7733
|
+
var DEFAULT_MAX_BLOOD = 256;
|
|
7734
|
+
var MAX_SPARE_GROUND_GEOMETRIES = 16;
|
|
7730
7735
|
function createMarkLayer(THREE, options) {
|
|
7731
7736
|
const group = new THREE.Group();
|
|
7732
7737
|
const textures = createMarkTextures(THREE, options.createCanvas);
|
|
@@ -7750,110 +7755,168 @@ void main(){
|
|
|
7750
7755
|
const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
7751
7756
|
const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
|
|
7752
7757
|
owned.push(bloodMaterial, bloodGeometry);
|
|
7753
|
-
const
|
|
7754
|
-
const
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
|
|
7760
|
-
rockMaterials.set(color, material);
|
|
7761
|
-
owned.push(material);
|
|
7758
|
+
const maxGroundMarks = options.maxGroundMarks ?? DEFAULT_MAX_GROUND_MARKS;
|
|
7759
|
+
const instanced = {};
|
|
7760
|
+
function rockMaterial() {
|
|
7761
|
+
if (!instanced.rockMaterial) {
|
|
7762
|
+
instanced.rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.9, metalness: 0, flatShading: true });
|
|
7763
|
+
owned.push(instanced.rockMaterial);
|
|
7762
7764
|
}
|
|
7763
|
-
return
|
|
7765
|
+
return instanced.rockMaterial;
|
|
7766
|
+
}
|
|
7767
|
+
function bloodMesh() {
|
|
7768
|
+
return instanced.blood ?? (instanced.blood = attach(createInstanced(bloodGeometry, bloodMaterial, options.maxBlood ?? DEFAULT_MAX_BLOOD)));
|
|
7769
|
+
}
|
|
7770
|
+
function rockMesh() {
|
|
7771
|
+
return instanced.rock ?? (instanced.rock = attach(createInstanced(options.rockGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
7772
|
+
}
|
|
7773
|
+
function plateMesh() {
|
|
7774
|
+
return instanced.plate ?? (instanced.plate = attach(createInstanced(options.plateGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
7775
|
+
}
|
|
7776
|
+
function attach(mesh2, castShadow = false) {
|
|
7777
|
+
mesh2.castShadow = castShadow;
|
|
7778
|
+
group.add(mesh2);
|
|
7779
|
+
return mesh2;
|
|
7780
|
+
}
|
|
7781
|
+
const groundMarks = [];
|
|
7782
|
+
const spareGeometries = [];
|
|
7783
|
+
const blood = [];
|
|
7784
|
+
const placed = { blood: 0, rock: 0, plate: 0 };
|
|
7785
|
+
const matrix = new THREE.Matrix4();
|
|
7786
|
+
const position = new THREE.Vector3();
|
|
7787
|
+
const quaternion = new THREE.Quaternion();
|
|
7788
|
+
const euler = new THREE.Euler();
|
|
7789
|
+
const scale = new THREE.Vector3();
|
|
7790
|
+
const color = new THREE.Color();
|
|
7791
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
7792
|
+
function createInstanced(geometry, material, capacity) {
|
|
7793
|
+
const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
|
|
7794
|
+
mesh2.count = 0;
|
|
7795
|
+
mesh2.frustumCulled = false;
|
|
7796
|
+
mesh2.receiveShadow = true;
|
|
7797
|
+
return mesh2;
|
|
7798
|
+
}
|
|
7799
|
+
function takeSpare(vertexCount) {
|
|
7800
|
+
const found = spareGeometries.findIndex((geometry) => geometry.getAttribute("position").count === vertexCount);
|
|
7801
|
+
return found < 0 ? void 0 : spareGeometries.splice(found, 1)[0];
|
|
7802
|
+
}
|
|
7803
|
+
function evictOldestGroundMark() {
|
|
7804
|
+
const oldest = groundMarks.shift();
|
|
7805
|
+
group.remove(oldest.mesh);
|
|
7806
|
+
spareGeometries.push(oldest.mesh.geometry);
|
|
7807
|
+
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
7764
7808
|
}
|
|
7765
7809
|
function conform(mark, material) {
|
|
7766
7810
|
const radius = mark.radius;
|
|
7767
7811
|
const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
|
|
7768
|
-
|
|
7769
|
-
const
|
|
7812
|
+
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
7813
|
+
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
7814
|
+
const grid = geometry.attributes.position;
|
|
7815
|
+
const cell = 2 / steps;
|
|
7770
7816
|
const cos2 = Math.cos(mark.rotation);
|
|
7771
7817
|
const sin2 = Math.sin(mark.rotation);
|
|
7772
|
-
for (let index = 0; index <
|
|
7773
|
-
const localX =
|
|
7774
|
-
const localZ =
|
|
7818
|
+
for (let index = 0; index < grid.count; index++) {
|
|
7819
|
+
const localX = Math.fround(index % (steps + 1) * cell - 1) * radius;
|
|
7820
|
+
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
7775
7821
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
7776
7822
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
7777
|
-
|
|
7823
|
+
grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
|
|
7778
7824
|
}
|
|
7825
|
+
grid.needsUpdate = true;
|
|
7779
7826
|
geometry.computeVertexNormals();
|
|
7827
|
+
geometry.attributes.normal.needsUpdate = true;
|
|
7828
|
+
geometry.boundingSphere = null;
|
|
7829
|
+
geometry.boundingBox = null;
|
|
7780
7830
|
const mesh2 = new THREE.Mesh(geometry, material);
|
|
7781
7831
|
mesh2.receiveShadow = true;
|
|
7782
|
-
|
|
7832
|
+
group.add(mesh2);
|
|
7833
|
+
groundMarks.push({ mesh: mesh2, steps });
|
|
7783
7834
|
}
|
|
7784
7835
|
function addRock(mark) {
|
|
7785
|
-
const mesh2 =
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
mesh2.
|
|
7790
|
-
mesh2.
|
|
7791
|
-
|
|
7836
|
+
const mesh2 = mark.plate ? plateMesh() : rockMesh();
|
|
7837
|
+
const slot = mark.plate ? "plate" : "rock";
|
|
7838
|
+
const index = placed[slot]++ % mesh2.instanceMatrix.count;
|
|
7839
|
+
quaternion.setFromEuler(euler.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]));
|
|
7840
|
+
mesh2.setMatrixAt(index, matrix.compose(position.set(mark.x, mark.y, mark.z), quaternion, scale.set(mark.scale[0], mark.scale[1], mark.scale[2])));
|
|
7841
|
+
mesh2.setColorAt(index, color.set(mark.color));
|
|
7842
|
+
mesh2.count = Math.min(placed[slot], mesh2.instanceMatrix.count);
|
|
7843
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
7844
|
+
mesh2.instanceColor.needsUpdate = true;
|
|
7845
|
+
}
|
|
7846
|
+
function writeBlood(index, drawn, growth) {
|
|
7847
|
+
quaternion.setFromAxisAngle(up, drawn.rotation);
|
|
7848
|
+
const mesh2 = bloodMesh();
|
|
7849
|
+
mesh2.setMatrixAt(index, matrix.compose(position.set(drawn.position[0], drawn.position[1], drawn.position[2]), quaternion, scale.set(drawn.scaleX * growth, 1, drawn.scaleZ * growth)));
|
|
7850
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
7792
7851
|
}
|
|
7793
7852
|
function addBlood(mark) {
|
|
7794
|
-
const
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
mesh2.scale.multiplyScalar(0.05);
|
|
7800
|
-
blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
|
|
7801
|
-
return mesh2;
|
|
7853
|
+
const index = placed.blood++ % bloodMesh().instanceMatrix.count;
|
|
7854
|
+
const drawn = { position: [mark.x, mark.y, mark.z], rotation: mark.rotation, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 };
|
|
7855
|
+
blood[index] = drawn;
|
|
7856
|
+
writeBlood(index, drawn, BLOOD_START_GROWTH);
|
|
7857
|
+
bloodMesh().count = Math.min(placed.blood, bloodMesh().instanceMatrix.count);
|
|
7802
7858
|
}
|
|
7803
7859
|
function build(mark) {
|
|
7804
|
-
if (mark.kind === "scorch")
|
|
7805
|
-
if (mark.kind === "dirt")
|
|
7806
|
-
if (mark.kind === "rock")
|
|
7807
|
-
|
|
7808
|
-
}
|
|
7809
|
-
function
|
|
7810
|
-
for (const
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7860
|
+
if (mark.kind === "scorch") conform(mark, scorchMaterials[mark.variant]);
|
|
7861
|
+
else if (mark.kind === "dirt") conform(mark, dirtMaterial);
|
|
7862
|
+
else if (mark.kind === "rock") addRock(mark);
|
|
7863
|
+
else addBlood(mark);
|
|
7864
|
+
}
|
|
7865
|
+
function clearMarks() {
|
|
7866
|
+
for (const drawn of groundMarks) group.remove(drawn.mesh);
|
|
7867
|
+
for (const drawn of groundMarks) drawn.mesh.geometry.dispose();
|
|
7868
|
+
groundMarks.length = 0;
|
|
7869
|
+
for (const geometry of spareGeometries) geometry.dispose();
|
|
7870
|
+
spareGeometries.length = 0;
|
|
7815
7871
|
blood.length = 0;
|
|
7816
|
-
|
|
7872
|
+
placed.blood = placed.rock = placed.plate = 0;
|
|
7873
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) if (mesh2) mesh2.count = 0;
|
|
7817
7874
|
}
|
|
7818
7875
|
return {
|
|
7819
7876
|
group,
|
|
7820
7877
|
handle(event) {
|
|
7821
|
-
if (event.type === "clear")
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7878
|
+
if (event.type === "clear") clearMarks();
|
|
7879
|
+
else build(event.mark);
|
|
7880
|
+
},
|
|
7881
|
+
prepare() {
|
|
7882
|
+
bloodMesh();
|
|
7883
|
+
rockMesh();
|
|
7884
|
+
plateMesh();
|
|
7827
7885
|
},
|
|
7828
7886
|
update(dt) {
|
|
7829
|
-
for (
|
|
7830
|
-
|
|
7887
|
+
for (let index = 0; index < blood.length; index++) {
|
|
7888
|
+
const drawn = blood[index];
|
|
7889
|
+
if (!drawn || drawn.age > 1) continue;
|
|
7831
7890
|
drawn.age += dt * BLOOD_GROW_RATE;
|
|
7832
|
-
|
|
7833
|
-
drawn.mesh.scale.set(drawn.scaleX * Math.max(0.05, ease3), 1, drawn.scaleZ * Math.max(0.05, ease3));
|
|
7891
|
+
writeBlood(index, drawn, Math.max(BLOOD_START_GROWTH, 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5)));
|
|
7834
7892
|
}
|
|
7835
7893
|
},
|
|
7836
|
-
count: () =>
|
|
7894
|
+
count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
|
|
7837
7895
|
dispose() {
|
|
7838
|
-
|
|
7896
|
+
clearMarks();
|
|
7897
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
|
|
7839
7898
|
for (const item of owned) item.dispose();
|
|
7840
7899
|
}
|
|
7841
7900
|
};
|
|
7842
7901
|
}
|
|
7843
7902
|
var SPHERE_DEFAULT_WIDTH = 8;
|
|
7844
7903
|
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
7904
|
+
var MAX_PARKED_MESHES_PER_KIND = 256;
|
|
7845
7905
|
function geometryOf(THREE, particle) {
|
|
7846
7906
|
switch (particle.shape) {
|
|
7847
7907
|
case "cone":
|
|
7848
|
-
return new THREE.ConeGeometry(
|
|
7908
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, false);
|
|
7849
7909
|
case "cone_open":
|
|
7850
|
-
return new THREE.ConeGeometry(
|
|
7910
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, true);
|
|
7851
7911
|
case "sphere":
|
|
7852
|
-
return new THREE.SphereGeometry(
|
|
7912
|
+
return new THREE.SphereGeometry(1, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
7853
7913
|
case "icosahedron":
|
|
7854
|
-
return new THREE.IcosahedronGeometry(
|
|
7914
|
+
return new THREE.IcosahedronGeometry(1, particle.segments);
|
|
7855
7915
|
}
|
|
7856
7916
|
}
|
|
7917
|
+
function isCone(particle) {
|
|
7918
|
+
return particle.shape === "cone" || particle.shape === "cone_open";
|
|
7919
|
+
}
|
|
7857
7920
|
function materialOf(THREE, particle) {
|
|
7858
7921
|
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
7859
7922
|
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
@@ -7864,51 +7927,82 @@ void main(){
|
|
|
7864
7927
|
(kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
|
|
7865
7928
|
);
|
|
7866
7929
|
}
|
|
7930
|
+
function geometryKeyOf(particle) {
|
|
7931
|
+
return `${particle.shape}:${particle.segments}:${particle.segments2}`;
|
|
7932
|
+
}
|
|
7867
7933
|
function createMeshLayer(THREE, pool) {
|
|
7868
7934
|
const group = new THREE.Group();
|
|
7869
7935
|
group.name = "workshop-meshes";
|
|
7870
7936
|
const drawn = /* @__PURE__ */ new Map();
|
|
7937
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
7938
|
+
const parked = /* @__PURE__ */ new Map();
|
|
7871
7939
|
const frame = new THREE.Quaternion();
|
|
7872
7940
|
const euler = new THREE.Euler();
|
|
7873
7941
|
const turn = new THREE.Quaternion();
|
|
7874
7942
|
const offset = new THREE.Vector3();
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7943
|
+
let stamp = 0;
|
|
7944
|
+
function geometryFor(particle) {
|
|
7945
|
+
const key = geometryKeyOf(particle);
|
|
7946
|
+
let geometry = geometries.get(key);
|
|
7947
|
+
if (!geometry) {
|
|
7948
|
+
geometry = geometryOf(THREE, particle);
|
|
7949
|
+
geometries.set(key, geometry);
|
|
7950
|
+
}
|
|
7951
|
+
return geometry;
|
|
7952
|
+
}
|
|
7953
|
+
function acquire(particle) {
|
|
7954
|
+
const parkingKey = `${geometryKeyOf(particle)}|${particle.lit}|${particle.additive}`;
|
|
7955
|
+
const mesh2 = parked.get(parkingKey)?.pop() ?? new THREE.Mesh(geometryFor(particle), materialOf(THREE, particle));
|
|
7956
|
+
mesh2.material.color.set(particle.color);
|
|
7957
|
+
mesh2.frustumCulled = false;
|
|
7958
|
+
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7959
|
+
group.add(mesh2);
|
|
7960
|
+
return { mesh: mesh2, parkingKey, stamp };
|
|
7961
|
+
}
|
|
7962
|
+
function park(entry) {
|
|
7963
|
+
entry.mesh.removeFromParent();
|
|
7964
|
+
let free = parked.get(entry.parkingKey);
|
|
7965
|
+
if (!free) {
|
|
7966
|
+
free = [];
|
|
7967
|
+
parked.set(entry.parkingKey, free);
|
|
7968
|
+
}
|
|
7969
|
+
if (free.length < MAX_PARKED_MESHES_PER_KIND) free.push(entry.mesh);
|
|
7970
|
+
else entry.mesh.material.dispose();
|
|
7879
7971
|
}
|
|
7880
7972
|
return {
|
|
7881
7973
|
group,
|
|
7882
7974
|
sync() {
|
|
7883
|
-
|
|
7975
|
+
stamp++;
|
|
7884
7976
|
for (const particle of pool.particles) {
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
mesh2.frustumCulled = false;
|
|
7890
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7891
|
-
drawn.set(particle.id, mesh2);
|
|
7892
|
-
group.add(mesh2);
|
|
7977
|
+
let entry = drawn.get(particle.id);
|
|
7978
|
+
if (!entry) {
|
|
7979
|
+
entry = acquire(particle);
|
|
7980
|
+
drawn.set(particle.id, entry);
|
|
7893
7981
|
}
|
|
7982
|
+
entry.stamp = stamp;
|
|
7983
|
+
const mesh2 = entry.mesh;
|
|
7894
7984
|
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
7895
7985
|
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
7896
7986
|
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
7897
7987
|
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
7898
7988
|
mesh2.quaternion.copy(frame).multiply(turn);
|
|
7899
|
-
mesh2.scale.
|
|
7989
|
+
mesh2.scale.set(particle.radius * particle.scale, (isCone(particle) ? particle.height : particle.radius) * particle.scale, particle.radius * particle.scale);
|
|
7900
7990
|
mesh2.material.opacity = particle.opacity;
|
|
7901
7991
|
}
|
|
7902
|
-
for (const [id,
|
|
7903
|
-
if (
|
|
7904
|
-
|
|
7992
|
+
for (const [id, entry] of drawn) {
|
|
7993
|
+
if (entry.stamp === stamp) continue;
|
|
7994
|
+
park(entry);
|
|
7905
7995
|
drawn.delete(id);
|
|
7906
7996
|
}
|
|
7907
7997
|
return pool.count;
|
|
7908
7998
|
},
|
|
7909
7999
|
dispose() {
|
|
7910
|
-
for (const
|
|
8000
|
+
for (const entry of drawn.values()) park(entry);
|
|
7911
8001
|
drawn.clear();
|
|
8002
|
+
for (const free of parked.values()) for (const mesh2 of free) mesh2.material.dispose();
|
|
8003
|
+
parked.clear();
|
|
8004
|
+
for (const geometry of geometries.values()) geometry.dispose();
|
|
8005
|
+
geometries.clear();
|
|
7912
8006
|
group.removeFromParent();
|
|
7913
8007
|
}
|
|
7914
8008
|
};
|
|
@@ -8476,7 +8570,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8476
8570
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
8477
8571
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
8478
8572
|
const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
|
|
8479
|
-
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry });
|
|
8573
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
8480
8574
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
8481
8575
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
8482
8576
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -8502,6 +8596,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8502
8596
|
const position = options.camera.position;
|
|
8503
8597
|
world.setCamera([position.x, position.y, position.z]);
|
|
8504
8598
|
}
|
|
8599
|
+
function attachWorkshopLayers() {
|
|
8600
|
+
const engine = world.peekWorkshop();
|
|
8601
|
+
if (!engine || engine === workshopEngine) return;
|
|
8602
|
+
workshopLayers?.dispose();
|
|
8603
|
+
if (workshopLayers) root.remove(workshopLayers.group);
|
|
8604
|
+
workshopEngine = engine;
|
|
8605
|
+
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
8606
|
+
root.add(workshopLayers.group);
|
|
8607
|
+
applyLayers();
|
|
8608
|
+
}
|
|
8505
8609
|
function syncAll() {
|
|
8506
8610
|
lastDrawn.puffs = puffs.sync();
|
|
8507
8611
|
lastDrawn.sparks = sparks.sync();
|
|
@@ -8510,15 +8614,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8510
8614
|
lastDrawn.shocks = shocks.sync();
|
|
8511
8615
|
lastDrawn.sprites = sprites.sync();
|
|
8512
8616
|
lights.sync();
|
|
8513
|
-
|
|
8514
|
-
if (engine && engine !== workshopEngine) {
|
|
8515
|
-
workshopLayers?.dispose();
|
|
8516
|
-
if (workshopLayers) root.remove(workshopLayers.group);
|
|
8517
|
-
workshopEngine = engine;
|
|
8518
|
-
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
8519
|
-
root.add(workshopLayers.group);
|
|
8520
|
-
applyLayers();
|
|
8521
|
-
}
|
|
8617
|
+
attachWorkshopLayers();
|
|
8522
8618
|
const workshopCalls = workshopLayers?.sync() ?? 0;
|
|
8523
8619
|
const glow = pools.lights.glow;
|
|
8524
8620
|
uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
|
|
@@ -8571,6 +8667,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8571
8667
|
},
|
|
8572
8668
|
warmUpProbes() {
|
|
8573
8669
|
releaseAnchors();
|
|
8670
|
+
marks.prepare();
|
|
8671
|
+
world.workshop();
|
|
8672
|
+
attachWorkshopLayers();
|
|
8574
8673
|
const meshes = createMeshLayerProbes(THREE);
|
|
8575
8674
|
for (const mesh2 of meshes) mesh2.frustumCulled = false;
|
|
8576
8675
|
const group = new THREE.Group();
|
|
@@ -8691,6 +8790,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8691
8790
|
var SHEET_SWAY = 3;
|
|
8692
8791
|
var BIG_LENGTH = 0.2;
|
|
8693
8792
|
var BIG_WIDTH = 0.03;
|
|
8793
|
+
var FRAGMENT_SEED = 1374496523;
|
|
8694
8794
|
var isBig = (size) => {
|
|
8695
8795
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
8696
8796
|
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
@@ -8710,7 +8810,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8710
8810
|
__publicField(this, "m");
|
|
8711
8811
|
__publicField(this, "v");
|
|
8712
8812
|
__publicField(this, "w");
|
|
8713
|
-
__publicField(this, "rng", createRng(
|
|
8813
|
+
__publicField(this, "rng", createRng(FRAGMENT_SEED));
|
|
8714
8814
|
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
8715
8815
|
__publicField(this, "tick", 0);
|
|
8716
8816
|
__publicField(this, "markGeometry", null);
|
|
@@ -8811,6 +8911,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8811
8911
|
mesh2.scale.copy(sc);
|
|
8812
8912
|
mesh2.castShadow = true;
|
|
8813
8913
|
mesh2.userData.destructionDebris = true;
|
|
8914
|
+
mesh2.name = "fragment";
|
|
8915
|
+
markKitOrigin(mesh2, { owner: "game-destruction-three", kind: "fragment", id: f.material });
|
|
8814
8916
|
this.o.scene.add(mesh2);
|
|
8815
8917
|
const live = {
|
|
8816
8918
|
mesh: mesh2,
|
|
@@ -8827,7 +8929,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8827
8929
|
dying: false,
|
|
8828
8930
|
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
8829
8931
|
body: null,
|
|
8830
|
-
born: this.tick
|
|
8932
|
+
born: this.tick,
|
|
8933
|
+
life: Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
8831
8934
|
};
|
|
8832
8935
|
if (f.shape !== "shard" && isBig(f.size)) {
|
|
8833
8936
|
live.body = {
|
|
@@ -8836,8 +8939,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8836
8939
|
live.vel.x = x;
|
|
8837
8940
|
live.vel.z = z;
|
|
8838
8941
|
live.vel.y = Math.max(live.vel.y, 0.3);
|
|
8839
|
-
live
|
|
8840
|
-
live.rest = 0;
|
|
8942
|
+
this.rouse(live);
|
|
8841
8943
|
}
|
|
8842
8944
|
};
|
|
8843
8945
|
}
|
|
@@ -8872,16 +8974,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8872
8974
|
d.y += k * 0.4;
|
|
8873
8975
|
f.vel.add(d);
|
|
8874
8976
|
f.ang.add(this.w.set(this.rng() - 0.5, this.rng() - 0.5, this.rng() - 0.5).multiplyScalar(k));
|
|
8875
|
-
f
|
|
8876
|
-
f.rest = 0;
|
|
8977
|
+
this.rouse(f);
|
|
8877
8978
|
}
|
|
8878
8979
|
}
|
|
8980
|
+
/** A fragment that came to rest stops having its matrix recomputed on every frame; whatever moves it again calls this first. */
|
|
8981
|
+
rouse(f) {
|
|
8982
|
+
f.asleep = false;
|
|
8983
|
+
f.rest = 0;
|
|
8984
|
+
f.mesh.matrixAutoUpdate = true;
|
|
8985
|
+
}
|
|
8879
8986
|
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
8880
8987
|
wakeRegion(x, z, radius) {
|
|
8881
8988
|
for (const f of this.list) {
|
|
8882
8989
|
if (!f.asleep || Math.hypot(f.mesh.position.x - x, f.mesh.position.z - z) > radius) continue;
|
|
8883
|
-
f
|
|
8884
|
-
f.rest = 0;
|
|
8990
|
+
this.rouse(f);
|
|
8885
8991
|
}
|
|
8886
8992
|
}
|
|
8887
8993
|
update(dt) {
|
|
@@ -8890,6 +8996,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8890
8996
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
8891
8997
|
const f = this.list[i];
|
|
8892
8998
|
if (f.dying) {
|
|
8999
|
+
f.mesh.matrixAutoUpdate = true;
|
|
8893
9000
|
f.fade -= dt / 0.6;
|
|
8894
9001
|
if (f.fade <= 0) {
|
|
8895
9002
|
this.o.scene.remove(f.mesh);
|
|
@@ -8900,7 +9007,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8900
9007
|
}
|
|
8901
9008
|
if (f.asleep) {
|
|
8902
9009
|
f.rest += dt;
|
|
8903
|
-
if (
|
|
9010
|
+
if (f.life > 0 && f.rest > f.life && !f.body) {
|
|
9011
|
+
f.dying = true;
|
|
9012
|
+
f.mesh.matrixAutoUpdate = true;
|
|
9013
|
+
}
|
|
8904
9014
|
continue;
|
|
8905
9015
|
}
|
|
8906
9016
|
this.step(f, dt, g);
|
|
@@ -8949,6 +9059,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8949
9059
|
f.still += dt;
|
|
8950
9060
|
if (f.still > 0.4) {
|
|
8951
9061
|
f.asleep = true;
|
|
9062
|
+
f.mesh.updateMatrix();
|
|
9063
|
+
f.mesh.matrixAutoUpdate = false;
|
|
8952
9064
|
f.vel.set(0, 0, 0);
|
|
8953
9065
|
f.ang.set(0, 0, 0);
|
|
8954
9066
|
}
|
|
@@ -8998,6 +9110,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8998
9110
|
for (const f of this.list) this.o.scene.remove(f.mesh);
|
|
8999
9111
|
this.list.length = 0;
|
|
9000
9112
|
}
|
|
9113
|
+
/** `clear`, the random stream back to its seed and the tick to 0: what comes next flies as on a new system. */
|
|
9114
|
+
reset() {
|
|
9115
|
+
this.clear();
|
|
9116
|
+
this.rng = createRng(FRAGMENT_SEED);
|
|
9117
|
+
this.tick = 0;
|
|
9118
|
+
}
|
|
9001
9119
|
dispose() {
|
|
9002
9120
|
this.clear();
|
|
9003
9121
|
this.box.dispose();
|
|
@@ -9096,6 +9214,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9096
9214
|
var SUB_DT = 1 / 120;
|
|
9097
9215
|
var MU = 0.7;
|
|
9098
9216
|
var SLEEP_AFTER = 0.5;
|
|
9217
|
+
var CALM_DRIFT = 0.01;
|
|
9218
|
+
var CALM_TURN = 0.05;
|
|
9219
|
+
var CALM_SLEEP_AFTER = 1.5;
|
|
9099
9220
|
var HARD_LANDING = 3;
|
|
9100
9221
|
var SLOP = 2e-3;
|
|
9101
9222
|
var NARROW = 0.3;
|
|
@@ -9104,6 +9225,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9104
9225
|
var TIP_LYING = 0.25;
|
|
9105
9226
|
var TIP_SECONDS = 3;
|
|
9106
9227
|
var STEP = 0.03;
|
|
9228
|
+
var WARM_SLOTS = 8 + 12 * 7;
|
|
9229
|
+
var SOLVER_PASSES = 10;
|
|
9230
|
+
var SIDE_BOUNCE = 0.1;
|
|
9231
|
+
var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
|
|
9107
9232
|
var boxTopAt = (s, x, z) => {
|
|
9108
9233
|
const e = s.e;
|
|
9109
9234
|
const dx = x - s.c[0], dz = z - s.c[2];
|
|
@@ -9174,6 +9299,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9174
9299
|
localCenter: [localCenter.x, localCenter.y, localCenter.z],
|
|
9175
9300
|
centerV: localCenter.clone(),
|
|
9176
9301
|
still: 0,
|
|
9302
|
+
calmAt: null,
|
|
9303
|
+
calm: 0,
|
|
9177
9304
|
lastHit: -1,
|
|
9178
9305
|
onGround: false
|
|
9179
9306
|
};
|
|
@@ -9192,6 +9319,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9192
9319
|
clear() {
|
|
9193
9320
|
this.list.length = 0;
|
|
9194
9321
|
}
|
|
9322
|
+
/** `clear` and the clock back to 0. */
|
|
9323
|
+
reset() {
|
|
9324
|
+
this.clear();
|
|
9325
|
+
this.time = 0;
|
|
9326
|
+
}
|
|
9195
9327
|
/** World position of the centre of mass. */
|
|
9196
9328
|
centerOf(b, out) {
|
|
9197
9329
|
return out.copy(b.centerV).applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
@@ -9216,6 +9348,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9216
9348
|
}
|
|
9217
9349
|
b.sleeping = false;
|
|
9218
9350
|
b.still = 0;
|
|
9351
|
+
b.calmAt = null;
|
|
9219
9352
|
}
|
|
9220
9353
|
/** I^-1 * (x, y, z) in world space for a box with the body's half extents and mass (needs `this.m` to hold the body's rotation). */
|
|
9221
9354
|
mulInvInertia(b, x, y, z) {
|
|
@@ -9233,12 +9366,51 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9233
9366
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
9234
9367
|
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];
|
|
9235
9368
|
}
|
|
9369
|
+
/** The points of the box that touch the ground, in its own frame: the 8 corners, then (with `edges`) `EDGE_POINTS` along each of its 12 edges. */
|
|
9370
|
+
samples(b, edges) {
|
|
9371
|
+
const [hx, hy, hz] = b.halfExtents;
|
|
9372
|
+
const out = [];
|
|
9373
|
+
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ? hz : -hz]);
|
|
9374
|
+
if (!edges) return out;
|
|
9375
|
+
const h = [hx, hy, hz];
|
|
9376
|
+
for (let axis2 = 0; axis2 < 3; axis2++) {
|
|
9377
|
+
const a1 = (axis2 + 1) % 3;
|
|
9378
|
+
const a2 = (axis2 + 2) % 3;
|
|
9379
|
+
for (let k = 0; k < 4; k++) {
|
|
9380
|
+
for (const f of EDGE_POINTS) {
|
|
9381
|
+
const p = [0, 0, 0];
|
|
9382
|
+
p[axis2] = f * h[axis2];
|
|
9383
|
+
p[a1] = k & 1 ? h[a1] : -h[a1];
|
|
9384
|
+
p[a2] = k & 2 ? h[a2] : -h[a2];
|
|
9385
|
+
out.push(p);
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
return out;
|
|
9390
|
+
}
|
|
9391
|
+
/**
|
|
9392
|
+
* An impulse along the unit normal `n` at `r` (from the centre of mass, world) that stops the point closing in along `n` (with `bounce`); `e` holds the body's rotation.
|
|
9393
|
+
*/
|
|
9394
|
+
contactImpulse(b, e, r, n, bounce2) {
|
|
9395
|
+
const v = b.velocity;
|
|
9396
|
+
const w = b.angularVelocity;
|
|
9397
|
+
const vn = (v[0] + w[1] * r[2] - w[2] * r[1]) * n[0] + (v[1] + w[2] * r[0] - w[0] * r[2]) * n[1] + (v[2] + w[0] * r[1] - w[1] * r[0]) * n[2];
|
|
9398
|
+
if (vn >= 0) return;
|
|
9399
|
+
const a = this.mulInvInertiaWith(b, e, r[1] * n[2] - r[2] * n[1], r[2] * n[0] - r[0] * n[2], r[0] * n[1] - r[1] * n[0]);
|
|
9400
|
+
const k = (a[1] * r[2] - a[2] * r[1]) * n[0] + (a[2] * r[0] - a[0] * r[2]) * n[1] + (a[0] * r[1] - a[1] * r[0]) * n[2];
|
|
9401
|
+
const j = -(1 + bounce2) * vn / (1 / b.mass + k);
|
|
9402
|
+
for (let i = 0; i < 3; i++) {
|
|
9403
|
+
v[i] += j * n[i] / b.mass;
|
|
9404
|
+
w[i] += a[i] * j;
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9236
9407
|
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
9237
9408
|
wakeRegion(x, z, radius) {
|
|
9238
9409
|
for (const b of this.list) {
|
|
9239
9410
|
if (!b.sleeping || b.held || Math.hypot(b.root.position.x - x, b.root.position.z - z) > radius) continue;
|
|
9240
9411
|
b.sleeping = false;
|
|
9241
9412
|
b.still = 0;
|
|
9413
|
+
b.calmAt = null;
|
|
9242
9414
|
}
|
|
9243
9415
|
}
|
|
9244
9416
|
update(dt) {
|
|
@@ -9254,6 +9426,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9254
9426
|
b.angularVelocity = [0, 0, 0];
|
|
9255
9427
|
b.sleeping = false;
|
|
9256
9428
|
b.still = 0;
|
|
9429
|
+
b.calmAt = null;
|
|
9257
9430
|
continue;
|
|
9258
9431
|
}
|
|
9259
9432
|
if (b.sleeping) continue;
|
|
@@ -9278,7 +9451,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9278
9451
|
const q = this.q.copy(b.root.quaternion);
|
|
9279
9452
|
const v = b.velocity;
|
|
9280
9453
|
const w = b.angularVelocity;
|
|
9281
|
-
const [hx, hy, hz] = b.halfExtents;
|
|
9282
9454
|
const invM = 1 / b.mass;
|
|
9283
9455
|
const steps = clamp6(Math.ceil(dt / SUB_DT), 1, 8);
|
|
9284
9456
|
const h = dt / steps;
|
|
@@ -9287,7 +9459,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9287
9459
|
let contactedLast = false;
|
|
9288
9460
|
let touched = false;
|
|
9289
9461
|
let unstable = false;
|
|
9462
|
+
const corners = this.samples(b, false);
|
|
9463
|
+
this.m.makeRotationFromQuaternion(q);
|
|
9464
|
+
let gLow = Infinity;
|
|
9465
|
+
let gHigh = -Infinity;
|
|
9466
|
+
for (const [sx, sy, sz] of corners) {
|
|
9467
|
+
const e0 = this.m.elements;
|
|
9468
|
+
const y = pos[1] + e0[1] * sx + e0[5] * sy + e0[9] * sz;
|
|
9469
|
+
const x = pos[0] + e0[0] * sx + e0[4] * sy + e0[8] * sz;
|
|
9470
|
+
const z = pos[2] + e0[2] * sx + e0[6] * sy + e0[10] * sz;
|
|
9471
|
+
const g = Math.max(this.o.groundAt(x, z), this.o.staticTopAt?.(x, z, y, STEP) ?? -Infinity);
|
|
9472
|
+
gLow = Math.min(gLow, g);
|
|
9473
|
+
gHigh = Math.max(gHigh, g);
|
|
9474
|
+
}
|
|
9475
|
+
const local2 = gHigh - gLow > STEP ? this.samples(b, true) : corners;
|
|
9476
|
+
const prev = local2.map(() => [0, 0, 0]);
|
|
9290
9477
|
for (let s = 0; s < steps; s++) {
|
|
9478
|
+
this.m.makeRotationFromQuaternion(q);
|
|
9479
|
+
const e0 = this.m.elements;
|
|
9480
|
+
for (let i = 0; i < local2.length; i++) {
|
|
9481
|
+
const [sx, sy, sz] = local2[i];
|
|
9482
|
+
prev[i][0] = pos[0] + e0[0] * sx + e0[4] * sy + e0[8] * sz;
|
|
9483
|
+
prev[i][1] = pos[1] + e0[1] * sx + e0[5] * sy + e0[9] * sz;
|
|
9484
|
+
prev[i][2] = pos[2] + e0[2] * sx + e0[6] * sy + e0[10] * sz;
|
|
9485
|
+
}
|
|
9291
9486
|
v[1] -= this.o.gravity * h;
|
|
9292
9487
|
pos[0] += v[0] * h;
|
|
9293
9488
|
pos[1] += v[1] * h;
|
|
@@ -9304,20 +9499,60 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9304
9499
|
this.m.makeRotationFromQuaternion(q);
|
|
9305
9500
|
const e = this.m.elements;
|
|
9306
9501
|
const rs = [];
|
|
9502
|
+
const ids = [];
|
|
9307
9503
|
let maxPen = 0;
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9504
|
+
const rise = STEP + Math.max(0, -v[1]) * h * 2;
|
|
9505
|
+
let side = null;
|
|
9506
|
+
let lowest = Infinity;
|
|
9507
|
+
for (let i = 0; i < local2.length; i++) {
|
|
9508
|
+
const [sx, sy, sz] = local2[i];
|
|
9312
9509
|
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];
|
|
9510
|
+
const px = pos[0] + r[0];
|
|
9511
|
+
const pz = pos[2] + r[2];
|
|
9313
9512
|
const cy = pos[1] + r[1];
|
|
9314
|
-
const
|
|
9513
|
+
const was = prev[i];
|
|
9514
|
+
const raw = this.o.groundAt(px, pz);
|
|
9515
|
+
if (raw > cy - SLOP && was[1] < raw - STEP) {
|
|
9516
|
+
if (this.o.groundAt(was[0], was[2]) >= raw - STEP) {
|
|
9517
|
+
const floor2 = b.floor;
|
|
9518
|
+
if (floor2 !== void 0 && floor2 - cy > -SLOP && floor2 < raw - STEP) {
|
|
9519
|
+
rs.push(r);
|
|
9520
|
+
ids.push(i);
|
|
9521
|
+
maxPen = Math.max(maxPen, floor2 - cy);
|
|
9522
|
+
}
|
|
9523
|
+
continue;
|
|
9524
|
+
}
|
|
9525
|
+
const dx = px - was[0];
|
|
9526
|
+
const dz = pz - was[2];
|
|
9527
|
+
const len = Math.hypot(dx, dz);
|
|
9528
|
+
if (len > 1e-6) {
|
|
9529
|
+
let lo = 0;
|
|
9530
|
+
let hi = 1;
|
|
9531
|
+
for (let it = 0; it < 6; it++) {
|
|
9532
|
+
const mid = (lo + hi) / 2;
|
|
9533
|
+
if (this.o.groundAt(was[0] + dx * mid, was[2] + dz * mid) >= raw - STEP) hi = mid;
|
|
9534
|
+
else lo = mid;
|
|
9535
|
+
}
|
|
9536
|
+
const back = (1 - lo) * len + 1e-3;
|
|
9537
|
+
if (!side || back > side.back) side = { back, dir: [dx / len, dz / len], r };
|
|
9538
|
+
}
|
|
9539
|
+
continue;
|
|
9540
|
+
}
|
|
9541
|
+
const gy = i < 8 ? this.supportAt(b, px, cy, pz, rise) : Math.max(raw, this.o.staticTopAt?.(px, pz, cy, rise) ?? -Infinity);
|
|
9315
9542
|
const pen = gy - cy;
|
|
9316
9543
|
if (pen > -SLOP) {
|
|
9317
9544
|
rs.push(r);
|
|
9545
|
+
ids.push(i);
|
|
9318
9546
|
if (pen > maxPen) maxPen = pen;
|
|
9547
|
+
lowest = Math.min(lowest, gy);
|
|
9319
9548
|
}
|
|
9320
9549
|
}
|
|
9550
|
+
if (lowest < Infinity) b.floor = lowest;
|
|
9551
|
+
if (side) {
|
|
9552
|
+
pos[0] -= side.dir[0] * side.back;
|
|
9553
|
+
pos[2] -= side.dir[1] * side.back;
|
|
9554
|
+
this.contactImpulse(b, e, side.r, [-side.dir[0], 0, -side.dir[1]], SIDE_BOUNCE);
|
|
9555
|
+
}
|
|
9321
9556
|
contactedLast = rs.length > 0;
|
|
9322
9557
|
if (contactedLast) touched = true;
|
|
9323
9558
|
if (!contactedLast) continue;
|
|
@@ -9337,45 +9572,88 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9337
9572
|
const lz = pos[2] + sz / rs.length;
|
|
9338
9573
|
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
9339
9574
|
}
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9575
|
+
const goal = rs.map((r) => {
|
|
9576
|
+
const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
|
|
9577
|
+
return vy2 < 0 ? -restitution * vy2 : 0;
|
|
9578
|
+
});
|
|
9579
|
+
const vx0 = v[0];
|
|
9580
|
+
const vz0 = v[2];
|
|
9581
|
+
if (!b.warm || b.warm.length !== WARM_SLOTS * 3) b.warm = new Float64Array(WARM_SLOTS * 3);
|
|
9582
|
+
const warm = b.warm;
|
|
9583
|
+
const accN = ids.map((id) => id < WARM_SLOTS ? warm[id * 3] : 0);
|
|
9584
|
+
const accT = ids.map((id) => id < WARM_SLOTS ? [warm[id * 3 + 1], warm[id * 3 + 2]] : [0, 0]);
|
|
9585
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9586
|
+
const r = rs[k];
|
|
9587
|
+
const jn = accN[k];
|
|
9588
|
+
const [fx, fz] = accT[k];
|
|
9589
|
+
if (jn === 0 && fx === 0 && fz === 0) continue;
|
|
9590
|
+
v[0] += fx * invM;
|
|
9591
|
+
v[1] += jn * invM;
|
|
9592
|
+
v[2] += fz * invM;
|
|
9593
|
+
const dw = this.mulInvInertiaWith(b, e, r[1] * fz - r[2] * jn, r[2] * fx - r[0] * fz, r[0] * jn - r[1] * fx);
|
|
9594
|
+
w[0] += dw[0];
|
|
9595
|
+
w[1] += dw[1];
|
|
9596
|
+
w[2] += dw[2];
|
|
9597
|
+
}
|
|
9598
|
+
for (let it = 0; it < SOLVER_PASSES; it++) {
|
|
9599
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9600
|
+
const r = rs[k];
|
|
9601
|
+
const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
|
|
9347
9602
|
const a = this.mulInvInertiaWith(b, e, -r[2], 0, r[0]);
|
|
9348
9603
|
const denom = invM + (a[2] * r[0] - a[0] * r[2]);
|
|
9349
|
-
const
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9604
|
+
const total = Math.max(0, accN[k] + (goal[k] - vy2) / denom);
|
|
9605
|
+
const j = total - accN[k];
|
|
9606
|
+
accN[k] = total;
|
|
9607
|
+
if (j !== 0) {
|
|
9608
|
+
v[1] += j * invM;
|
|
9609
|
+
w[0] += a[0] * j;
|
|
9610
|
+
w[1] += a[1] * j;
|
|
9611
|
+
w[2] += a[2] * j;
|
|
9612
|
+
}
|
|
9613
|
+
const vx2 = v[0] + (w[1] * r[2] - w[2] * r[1]);
|
|
9614
|
+
const vz2 = v[2] + (w[0] * r[1] - w[1] * r[0]);
|
|
9358
9615
|
const vt = Math.hypot(vx2, vz2);
|
|
9359
|
-
if (vt
|
|
9616
|
+
if (vt < 1e-6 && accT[k][0] === 0 && accT[k][1] === 0) continue;
|
|
9617
|
+
let fx = accT[k][0];
|
|
9618
|
+
let fz = accT[k][1];
|
|
9619
|
+
if (vt > 1e-6) {
|
|
9360
9620
|
const tx = vx2 / vt;
|
|
9361
9621
|
const tz = vz2 / vt;
|
|
9362
|
-
const
|
|
9363
|
-
const ry = r[2] * tx - r[0] * tz;
|
|
9364
|
-
const rz = -r[1] * tx;
|
|
9365
|
-
const at3 = this.mulInvInertiaWith(b, e, rx, ry, rz);
|
|
9622
|
+
const at3 = this.mulInvInertiaWith(b, e, r[1] * tz, r[2] * tx - r[0] * tz, -r[1] * tx);
|
|
9366
9623
|
const cx = at3[1] * r[2] - at3[2] * r[1];
|
|
9367
9624
|
const cz = at3[0] * r[1] - at3[1] * r[0];
|
|
9368
|
-
const
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
v[2] -= tz * jt * invM;
|
|
9372
|
-
const dwt = this.mulInvInertiaWith(b, e, rx * jt, ry * jt, rz * jt);
|
|
9373
|
-
w[0] -= dwt[0];
|
|
9374
|
-
w[1] -= dwt[1];
|
|
9375
|
-
w[2] -= dwt[2];
|
|
9625
|
+
const jt = vt / (invM + cx * tx + cz * tz);
|
|
9626
|
+
fx -= tx * jt;
|
|
9627
|
+
fz -= tz * jt;
|
|
9376
9628
|
}
|
|
9629
|
+
const most = MU * accN[k];
|
|
9630
|
+
const f = Math.hypot(fx, fz);
|
|
9631
|
+
if (f > most) {
|
|
9632
|
+
fx *= most / f;
|
|
9633
|
+
fz *= most / f;
|
|
9634
|
+
}
|
|
9635
|
+
const dx = fx - accT[k][0];
|
|
9636
|
+
const dz = fz - accT[k][1];
|
|
9637
|
+
accT[k] = [fx, fz];
|
|
9638
|
+
if (dx === 0 && dz === 0) continue;
|
|
9639
|
+
v[0] += dx * invM;
|
|
9640
|
+
v[2] += dz * invM;
|
|
9641
|
+
const dw = this.mulInvInertiaWith(b, e, r[1] * dz, r[2] * dx - r[0] * dz, -r[1] * dx);
|
|
9642
|
+
w[0] += dw[0];
|
|
9643
|
+
w[1] += dw[1];
|
|
9644
|
+
w[2] += dw[2];
|
|
9377
9645
|
}
|
|
9378
9646
|
}
|
|
9647
|
+
pos[0] += (v[0] - vx0) * h;
|
|
9648
|
+
pos[2] += (v[2] - vz0) * h;
|
|
9649
|
+
warm.fill(0);
|
|
9650
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9651
|
+
const id = ids[k];
|
|
9652
|
+
if (id >= WARM_SLOTS) continue;
|
|
9653
|
+
warm[id * 3] = accN[k];
|
|
9654
|
+
warm[id * 3 + 1] = accT[k][0];
|
|
9655
|
+
warm[id * 3 + 2] = accT[k][1];
|
|
9656
|
+
}
|
|
9379
9657
|
let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity, low = 0;
|
|
9380
9658
|
for (const r of rs) {
|
|
9381
9659
|
x0 = Math.min(x0, r[0]);
|
|
@@ -9390,7 +9668,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9390
9668
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
9391
9669
|
if (b.tipping) {
|
|
9392
9670
|
b.tipping.seconds -= h;
|
|
9393
|
-
|
|
9671
|
+
const [hx, hy, hz] = b.halfExtents;
|
|
9672
|
+
const upHalf = Math.abs(e[1]) * hx + Math.abs(e[5]) * hy + Math.abs(e[9]) * hz;
|
|
9673
|
+
if (upHalf < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
9394
9674
|
else {
|
|
9395
9675
|
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
9396
9676
|
w[2] += b.tipping.z * TIP_ACCELERATION * h;
|
|
@@ -9422,16 +9702,26 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9422
9702
|
this.o.onHardLanding(b, landPoint, landing);
|
|
9423
9703
|
}
|
|
9424
9704
|
const still = touched && !unstable && Math.hypot(v[0], v[1], v[2]) < 0.12 && Math.hypot(w[0], w[1], w[2]) < 0.45;
|
|
9425
|
-
if (still)
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
}
|
|
9433
|
-
|
|
9434
|
-
|
|
9705
|
+
if (still) b.still += dt;
|
|
9706
|
+
else b.still = 0;
|
|
9707
|
+
if (b.still >= SLEEP_AFTER || this.staysInPlace(b, touched && !b.tipping, pos, q, dt)) {
|
|
9708
|
+
b.sleeping = true;
|
|
9709
|
+
b.calmAt = null;
|
|
9710
|
+
v[0] = v[1] = v[2] = 0;
|
|
9711
|
+
w[0] = w[1] = w[2] = 0;
|
|
9712
|
+
}
|
|
9713
|
+
}
|
|
9714
|
+
/** Whether the piece has kept to the same spot on the ground for `CALM_SLEEP_AFTER` (see `CALM_DRIFT`). */
|
|
9715
|
+
staysInPlace(b, touched, center, q, dt) {
|
|
9716
|
+
const at3 = b.calmAt;
|
|
9717
|
+
const turned = at3 ? 2 * Math.acos(Math.min(1, Math.abs(at3.quaternion[0] * q.x + at3.quaternion[1] * q.y + at3.quaternion[2] * q.z + at3.quaternion[3] * q.w))) : Infinity;
|
|
9718
|
+
if (!touched || !at3 || Math.hypot(center[0] - at3.center[0], center[1] - at3.center[1], center[2] - at3.center[2]) > CALM_DRIFT || turned > CALM_TURN) {
|
|
9719
|
+
b.calmAt = touched ? { center: [center[0], center[1], center[2]], quaternion: [q.x, q.y, q.z, q.w] } : null;
|
|
9720
|
+
b.calm = 0;
|
|
9721
|
+
return false;
|
|
9722
|
+
}
|
|
9723
|
+
b.calm += dt;
|
|
9724
|
+
return b.calm >= CALM_SLEEP_AFTER;
|
|
9435
9725
|
}
|
|
9436
9726
|
/** Half the height of the box of a piece as it is turned now. */
|
|
9437
9727
|
halfHeight(b) {
|
|
@@ -9449,8 +9739,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9449
9739
|
const a = this.list[i];
|
|
9450
9740
|
for (let j = i + 1; j < n; j++) {
|
|
9451
9741
|
const b = this.list[j];
|
|
9452
|
-
|
|
9453
|
-
|
|
9742
|
+
const fixedA = a.held || a.sleeping;
|
|
9743
|
+
const fixedB = b.held || b.sleeping;
|
|
9744
|
+
if (fixedA && fixedB) continue;
|
|
9454
9745
|
const ra = Math.hypot(a.halfExtents[0], a.halfExtents[1], a.halfExtents[2]);
|
|
9455
9746
|
const rb = Math.hypot(b.halfExtents[0], b.halfExtents[1], b.halfExtents[2]);
|
|
9456
9747
|
this.centerOf(a, ca);
|
|
@@ -9468,19 +9759,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9468
9759
|
d = 1;
|
|
9469
9760
|
}
|
|
9470
9761
|
const push = Math.min(maxMove, (rh - d) * 0.5);
|
|
9471
|
-
const wa =
|
|
9472
|
-
const wb =
|
|
9762
|
+
const wa = fixedA ? 0 : fixedB ? 1 : b.mass / (a.mass + b.mass);
|
|
9763
|
+
const wb = fixedB ? 0 : fixedA ? 1 : a.mass / (a.mass + b.mass);
|
|
9473
9764
|
const ux = dx / (d || 1);
|
|
9474
9765
|
const uz = dz / (d || 1);
|
|
9475
|
-
if (!
|
|
9766
|
+
if (!fixedA) {
|
|
9476
9767
|
a.root.position.x -= ux * push * 2 * wa;
|
|
9477
9768
|
a.root.position.z -= uz * push * 2 * wa;
|
|
9478
|
-
a.sleeping = false;
|
|
9479
9769
|
}
|
|
9480
|
-
if (!
|
|
9770
|
+
if (!fixedB) {
|
|
9481
9771
|
b.root.position.x += ux * push * 2 * wb;
|
|
9482
9772
|
b.root.position.z += uz * push * 2 * wb;
|
|
9483
|
-
b.sleeping = false;
|
|
9484
9773
|
}
|
|
9485
9774
|
}
|
|
9486
9775
|
}
|
|
@@ -10889,6 +11178,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10889
11178
|
}
|
|
10890
11179
|
return out;
|
|
10891
11180
|
};
|
|
11181
|
+
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
10892
11182
|
|
|
10893
11183
|
// src/spec.ts
|
|
10894
11184
|
function containsOf(value) {
|
|
@@ -10947,6 +11237,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10947
11237
|
const fill = options.fillOf?.(o, material);
|
|
10948
11238
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10949
11239
|
const contains = containsOf(ud.destructionContains);
|
|
11240
|
+
const powder = typeof ud.destructionPowder?.color === "number" ? { color: ud.destructionPowder.color } : void 0;
|
|
10950
11241
|
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
10951
11242
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10952
11243
|
if (existing) {
|
|
@@ -10960,9 +11251,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10960
11251
|
existing.skin = existing.skin && skin;
|
|
10961
11252
|
if (fill !== void 0) existing.fill = fill;
|
|
10962
11253
|
if (contains) existing.contains = contains;
|
|
11254
|
+
if (powder) existing.powder = powder;
|
|
10963
11255
|
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
10964
11256
|
} else {
|
|
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 };
|
|
11257
|
+
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, ...powder ? { powder } : {}, ...thickness !== void 0 ? { thickness } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
10966
11258
|
entries.push(e);
|
|
10967
11259
|
if (explicit) byKey.set(explicit, e);
|
|
10968
11260
|
}
|
|
@@ -11037,6 +11329,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11037
11329
|
}
|
|
11038
11330
|
if (e.tags) part.tags = [...e.tags];
|
|
11039
11331
|
if (e.contains) part.contains = { ...e.contains };
|
|
11332
|
+
if (e.powder) part.powder = { ...e.powder };
|
|
11040
11333
|
const ao = options.anchorBelow ?? -1;
|
|
11041
11334
|
if (ao >= 0 && e.box.min.y <= ao) part.anchored = true;
|
|
11042
11335
|
parts.push(part);
|
|
@@ -11214,6 +11507,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11214
11507
|
var SKIN_SLACK = 5e-4;
|
|
11215
11508
|
var SKIN_LAYER_SLACK = 4;
|
|
11216
11509
|
var NEAREST_MARGIN = 1.5;
|
|
11510
|
+
var basePositions = (host) => {
|
|
11511
|
+
const p0 = host.mesh.userData._p0;
|
|
11512
|
+
if (p0) return p0;
|
|
11513
|
+
const op = positionAttr(host.origGeometry);
|
|
11514
|
+
return op ? op.array : null;
|
|
11515
|
+
};
|
|
11516
|
+
var collapseForeign = (mesh2, arr) => {
|
|
11517
|
+
for (const [v0, v1] of mesh2.userData.collapsedRanges ?? []) {
|
|
11518
|
+
for (let k = v0 + 1; k < v1; k++) {
|
|
11519
|
+
arr[k * 3] = arr[v0 * 3];
|
|
11520
|
+
arr[k * 3 + 1] = arr[v0 * 3 + 1];
|
|
11521
|
+
arr[k * 3 + 2] = arr[v0 * 3 + 2];
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11524
|
+
};
|
|
11525
|
+
var DECAL_SNAP = 0.03;
|
|
11217
11526
|
var Visuals = class {
|
|
11218
11527
|
constructor(THREE, lowQuality, decalCap) {
|
|
11219
11528
|
__publicField(this, "THREE", THREE);
|
|
@@ -11223,6 +11532,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11223
11532
|
__publicField(this, "a");
|
|
11224
11533
|
__publicField(this, "b");
|
|
11225
11534
|
__publicField(this, "m4");
|
|
11535
|
+
__publicField(this, "ray");
|
|
11226
11536
|
__publicField(this, "box");
|
|
11227
11537
|
__publicField(this, "t");
|
|
11228
11538
|
__publicField(this, "lbox");
|
|
@@ -11233,6 +11543,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11233
11543
|
this.a = tmpV(THREE);
|
|
11234
11544
|
this.b = tmpV(THREE);
|
|
11235
11545
|
this.m4 = new THREE.Matrix4();
|
|
11546
|
+
this.ray = new THREE.Raycaster();
|
|
11236
11547
|
this.box = new THREE.Box3();
|
|
11237
11548
|
this.t = tmpV(THREE);
|
|
11238
11549
|
this.lbox = new THREE.Box3();
|
|
@@ -11386,7 +11697,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11386
11697
|
host.ownGeo = true;
|
|
11387
11698
|
const g = host.mesh.geometry.clone();
|
|
11388
11699
|
host.mesh.geometry = g;
|
|
11389
|
-
host.work = new Float32Array(positionAttr(g).array);
|
|
11700
|
+
host.work = new Float32Array(basePositions(host) ?? positionAttr(g).array);
|
|
11390
11701
|
return true;
|
|
11391
11702
|
}
|
|
11392
11703
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -11556,12 +11867,38 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11556
11867
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
11557
11868
|
// Decals
|
|
11558
11869
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
11870
|
+
/**
|
|
11871
|
+
* The point on the drawn face of `mesh` under a world point, within `DECAL_SNAP` metres along the normal: the world's box of a part and the face
|
|
11872
|
+
* the renderer drew (a coat of plaster, a merged house mesh) can be a few millimetres apart, and a mark behind the drawn face does not show.
|
|
11873
|
+
*/
|
|
11874
|
+
onFace(mesh2, worldPoint, worldNormal) {
|
|
11875
|
+
if (!mesh2.isMesh || !mesh2.geometry?.attributes?.position) return worldPoint;
|
|
11876
|
+
const n = worldNormal.clone().normalize();
|
|
11877
|
+
this.ray.set(worldPoint.clone().addScaledVector(n, DECAL_SNAP), n.clone().negate());
|
|
11878
|
+
this.ray.near = 0;
|
|
11879
|
+
this.ray.far = 2 * DECAL_SNAP;
|
|
11880
|
+
const hits = [];
|
|
11881
|
+
mesh2.raycast(this.ray, hits);
|
|
11882
|
+
for (const c of mesh2.parent?.children ?? []) {
|
|
11883
|
+
if (c === mesh2 || c.name !== "breach-shell") continue;
|
|
11884
|
+
c.raycast(this.ray, hits);
|
|
11885
|
+
const behind = c.userData.behind?.();
|
|
11886
|
+
if (behind) behind.raycast(this.ray, hits);
|
|
11887
|
+
}
|
|
11888
|
+
let best = null;
|
|
11889
|
+
for (const h of hits) {
|
|
11890
|
+
if (h.object.userData.cutAt?.(h.point)) continue;
|
|
11891
|
+
if (h.face && h.face.normal.clone().transformDirection(h.object.matrixWorld).dot(n) <= 0.3) continue;
|
|
11892
|
+
if (!best || Math.abs(h.distance - DECAL_SNAP) < Math.abs(best.distance - DECAL_SNAP)) best = h;
|
|
11893
|
+
}
|
|
11894
|
+
return best ? best.point : worldPoint;
|
|
11895
|
+
}
|
|
11559
11896
|
/** Decal pose in mesh space: position and a rotation taking +Z to the surface normal. */
|
|
11560
11897
|
decalPose(mesh2, worldPoint, worldNormal, lift) {
|
|
11561
11898
|
mesh2.updateWorldMatrix(true, false);
|
|
11562
11899
|
const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
11563
11900
|
const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
|
|
11564
|
-
const pos = mesh2.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
|
|
11901
|
+
const pos = mesh2.worldToLocal(this.onFace(mesh2, worldPoint, worldNormal).clone()).addScaledVector(ln, lift / s);
|
|
11565
11902
|
const quat2 = new this.THREE.Quaternion().setFromUnitVectors(new this.THREE.Vector3(0, 0, 1), ln);
|
|
11566
11903
|
return { s, pos, quat: quat2 };
|
|
11567
11904
|
}
|
|
@@ -11800,6 +12137,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11800
12137
|
if (!pos || !work) return;
|
|
11801
12138
|
const arr = pos.array;
|
|
11802
12139
|
arr.set(work);
|
|
12140
|
+
collapseForeign(host.mesh, arr);
|
|
11803
12141
|
for (const s of host.sharers) {
|
|
11804
12142
|
if (s.merged && s.merged.host === host) {
|
|
11805
12143
|
if (s.collapsed) this.collapseRange(arr, s.merged.range);
|
|
@@ -12004,9 +12342,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12004
12342
|
mv.touched = false;
|
|
12005
12343
|
if (!host.ownGeo) return;
|
|
12006
12344
|
const work = host.work;
|
|
12007
|
-
const op =
|
|
12345
|
+
const op = basePositions(host);
|
|
12008
12346
|
const [v0, v1] = merged.range;
|
|
12009
|
-
if (op) work.set(op.
|
|
12347
|
+
if (op) work.set(op.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
12010
12348
|
const attr = host.mesh.geometry.getAttribute("color");
|
|
12011
12349
|
if (attr) {
|
|
12012
12350
|
const orig = host.origGeometry.getAttribute("color");
|
|
@@ -12020,6 +12358,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12020
12358
|
if (host.sharers.every((s) => !s.touched)) {
|
|
12021
12359
|
host.mesh.geometry.dispose();
|
|
12022
12360
|
host.mesh.geometry = host.origGeometry;
|
|
12361
|
+
const base = basePositions(host), back = positionAttr(host.origGeometry);
|
|
12362
|
+
if (base && back) {
|
|
12363
|
+
const arr = back.array;
|
|
12364
|
+
arr.set(base);
|
|
12365
|
+
collapseForeign(host.mesh, arr);
|
|
12366
|
+
back.needsUpdate = true;
|
|
12367
|
+
}
|
|
12023
12368
|
host.work = null;
|
|
12024
12369
|
host.ownGeo = false;
|
|
12025
12370
|
if (host.ownMat) {
|
|
@@ -12079,9 +12424,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12079
12424
|
var rendererCount = 0;
|
|
12080
12425
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
12081
12426
|
var CHIP_DECAL_EDGE = 0.05;
|
|
12427
|
+
var OPAQUE_CRACK = 0.1;
|
|
12428
|
+
var WOOD_RIM = 1.5;
|
|
12082
12429
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
12083
12430
|
var GLOW_FROM_C = 430;
|
|
12084
12431
|
var QUEUE_CAP = 4e3;
|
|
12432
|
+
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
12085
12433
|
var STATIC_REFRESH = 0.5;
|
|
12086
12434
|
var STATIC_SPAN = 3;
|
|
12087
12435
|
var FEET_BAND = 0.03;
|
|
@@ -12157,6 +12505,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12157
12505
|
const r = v;
|
|
12158
12506
|
return !!r.mesh && Array.isArray(r.range);
|
|
12159
12507
|
}
|
|
12508
|
+
var RNG_SEED = 12648430;
|
|
12160
12509
|
function createDestructionRenderer(THREE, options) {
|
|
12161
12510
|
return new Renderer(THREE, options);
|
|
12162
12511
|
}
|
|
@@ -12172,6 +12521,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12172
12521
|
__publicField(this, "gravity");
|
|
12173
12522
|
__publicField(this, "autoSplit");
|
|
12174
12523
|
__publicField(this, "maxFragments");
|
|
12524
|
+
__publicField(this, "eventBudgetMs");
|
|
12175
12525
|
__publicField(this, "bounds", /* @__PURE__ */ new Map());
|
|
12176
12526
|
/** 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
12527
|
__publicField(this, "staticBoxes", /* @__PURE__ */ new Map());
|
|
@@ -12182,6 +12532,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12182
12532
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
12183
12533
|
__publicField(this, "staticAt", 0);
|
|
12184
12534
|
__publicField(this, "queue", []);
|
|
12535
|
+
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
12536
|
+
__publicField(this, "slices", []);
|
|
12537
|
+
/** When the work of this frame must stop; `Infinity` outside `drainQueue`, where a call finishes what it started. */
|
|
12538
|
+
__publicField(this, "sliceDeadline", Infinity);
|
|
12185
12539
|
__publicField(this, "unsubscribe");
|
|
12186
12540
|
__publicField(this, "visuals");
|
|
12187
12541
|
__publicField(this, "fragments");
|
|
@@ -12195,7 +12549,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12195
12549
|
__publicField(this, "leaks", []);
|
|
12196
12550
|
__publicField(this, "homeTags", []);
|
|
12197
12551
|
__publicField(this, "throttles", /* @__PURE__ */ new Map());
|
|
12198
|
-
__publicField(this, "rng", createRng(
|
|
12552
|
+
__publicField(this, "rng", createRng(RNG_SEED));
|
|
12199
12553
|
__publicField(this, "vfx");
|
|
12200
12554
|
/** Names this renderer to the world's drips, so a shared world tells each renderer where its own landed. */
|
|
12201
12555
|
__publicField(this, "owner", `destruction-${++rendererCount}`);
|
|
@@ -12219,6 +12573,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12219
12573
|
this.gravity = o.gravity ?? 9.81;
|
|
12220
12574
|
this.autoSplit = o.autoSplit !== false;
|
|
12221
12575
|
this.maxFragments = o.maxFragments ?? 160;
|
|
12576
|
+
this.eventBudgetMs = o.eventBudgetMs ?? Infinity;
|
|
12222
12577
|
this.box = new THREE.Box3();
|
|
12223
12578
|
this.tmpQ = new THREE.Quaternion();
|
|
12224
12579
|
this.v1 = new THREE.Vector3();
|
|
@@ -12304,6 +12659,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12304
12659
|
if (leftovers.length) this.matchByBox(objectId, root, leftovers, add2);
|
|
12305
12660
|
}
|
|
12306
12661
|
const bound = { id: objectId, root, sync: options.syncTransform !== false, parts: /* @__PURE__ */ new Map(), lastPos: null, lastQuat: null, piece: null, isSplit: false };
|
|
12662
|
+
if (options.onFrame) bound.onFrame = options.onFrame;
|
|
12307
12663
|
const hosts = /* @__PURE__ */ new Map();
|
|
12308
12664
|
for (const pid of partIds) bound.parts.set(pid, this.makePartVis(objectId, pid, state?.parts.find((p) => p.id === pid)?.material ?? "", byPart.get(pid) ?? [], hosts));
|
|
12309
12665
|
for (const [pid, meshes] of byPart) {
|
|
@@ -12463,6 +12819,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12463
12819
|
dt = clamp6(dt, 0, 0.1);
|
|
12464
12820
|
this.time += dt;
|
|
12465
12821
|
this.drainQueue();
|
|
12822
|
+
for (const b of this.bounds.values()) b.onFrame?.(dt);
|
|
12466
12823
|
for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
|
|
12467
12824
|
this.updateActive(dt);
|
|
12468
12825
|
this.updateLeaks(dt);
|
|
@@ -12474,11 +12831,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12474
12831
|
}
|
|
12475
12832
|
drainQueue() {
|
|
12476
12833
|
const batch = this.queue.splice(0, this.queue.length);
|
|
12477
|
-
|
|
12834
|
+
const deadline = performance.now() + this.eventBudgetMs;
|
|
12835
|
+
this.sliceDeadline = deadline;
|
|
12836
|
+
try {
|
|
12837
|
+
this.runSlices(deadline);
|
|
12838
|
+
for (let i = 0; i < batch.length; i++) {
|
|
12839
|
+
if (i > 0 && performance.now() >= deadline) {
|
|
12840
|
+
this.queue.unshift(...batch.slice(i));
|
|
12841
|
+
return;
|
|
12842
|
+
}
|
|
12843
|
+
try {
|
|
12844
|
+
this.apply(batch[i]);
|
|
12845
|
+
} catch (err) {
|
|
12846
|
+
if (typeof console !== "undefined") console.error("game-destruction-three: failed to apply", batch[i].type, err);
|
|
12847
|
+
}
|
|
12848
|
+
}
|
|
12849
|
+
} finally {
|
|
12850
|
+
this.sliceDeadline = Infinity;
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
/** Runs the work left by earlier frames until the deadline; at least one piece per frame, so it always finishes. */
|
|
12854
|
+
runSlices(deadline) {
|
|
12855
|
+
for (let ran = 0; this.slices.length > 0 && (ran === 0 || performance.now() < deadline); ran++) {
|
|
12478
12856
|
try {
|
|
12479
|
-
this.
|
|
12857
|
+
this.slices.shift()();
|
|
12480
12858
|
} catch (err) {
|
|
12481
|
-
if (typeof console !== "undefined") console.error("game-destruction-three: failed to
|
|
12859
|
+
if (typeof console !== "undefined") console.error("game-destruction-three: failed to run deferred work", err);
|
|
12482
12860
|
}
|
|
12483
12861
|
}
|
|
12484
12862
|
}
|
|
@@ -12876,12 +13254,19 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12876
13254
|
const seed = hashNumbers(ev.time, point[0], point[1], point[2]);
|
|
12877
13255
|
this.visuals.addCrack(mv, wp, normal, 7, Math.max(0.05, ev.hole.diameter * 5), 15267071, seed);
|
|
12878
13256
|
} else {
|
|
13257
|
+
const fresh = def?.penetrationClass === "wood" ? def.visual?.innerColor : void 0;
|
|
13258
|
+
if (fresh !== void 0) this.visuals.addSpall(mv, wp, normal, Math.max(6e-3, ev.hole.diameter * WOOD_RIM), fresh);
|
|
12879
13259
|
this.visuals.addHole(mv, wp, normal, ev.hole.diameter);
|
|
12880
13260
|
}
|
|
12881
13261
|
};
|
|
12882
13262
|
draw(ev.worldPoint, n);
|
|
12883
13263
|
if (ev.hole.through && ev.exitPoint) draw(ev.exitPoint, n.clone().negate());
|
|
12884
13264
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [n.x, n.y, n.z], 6, 3.5);
|
|
13265
|
+
if (ev.powder) {
|
|
13266
|
+
const puff3 = { color: ev.powder.color, size: 0.6, life: 1.6, alpha: 1.3 };
|
|
13267
|
+
this.vfx.burst("dust", ev.worldPoint, [n.x, n.y, n.z], this.low ? 5 : 10, 0.7, puff3);
|
|
13268
|
+
if (ev.hole.through && ev.exitPoint) this.vfx.burst("dust", ev.exitPoint, [-n.x, -n.y, -n.z], this.low ? 10 : 20, 1.3, puff3);
|
|
13269
|
+
}
|
|
12885
13270
|
this.sound(impactKey(def), ev.worldPoint, 0.6);
|
|
12886
13271
|
}
|
|
12887
13272
|
onCrack(ev) {
|
|
@@ -12892,8 +13277,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12892
13277
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
12893
13278
|
const mv = this.nearestMesh(pv, wp);
|
|
12894
13279
|
if (mv) {
|
|
12895
|
-
const
|
|
12896
|
-
|
|
13280
|
+
const glass = !!def?.visual?.transparent;
|
|
13281
|
+
const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
|
|
13282
|
+
const color = glass ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
|
|
13283
|
+
const length = glass ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
|
|
13284
|
+
this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
|
|
12897
13285
|
}
|
|
12898
13286
|
this.sound(impactKey(def), ev.worldPoint, 0.5);
|
|
12899
13287
|
}
|
|
@@ -12931,6 +13319,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12931
13319
|
const core = def?.visual?.innerColor;
|
|
12932
13320
|
if (core !== void 0) this.vfx.burst("chips", ev.point, [normal.x, normal.y, normal.z], 5, 2.5, { color: core });
|
|
12933
13321
|
else this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.point);
|
|
13322
|
+
if (mv.mesh.userData.drawnSpalls?.includes(ev.part)) return;
|
|
12934
13323
|
this.visuals.addSpall(mv, wp, normal, Math.sqrt(ev.area / Math.PI), core ?? fallbackColor(pv.material, def));
|
|
12935
13324
|
}
|
|
12936
13325
|
/**
|
|
@@ -12968,6 +13357,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12968
13357
|
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
12969
13358
|
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone, false), ev.object, maxAlive);
|
|
12970
13359
|
}
|
|
13360
|
+
if (ev.torn) {
|
|
13361
|
+
this.sound(impactKey(def), ev.center, 0.5);
|
|
13362
|
+
return;
|
|
13363
|
+
}
|
|
12971
13364
|
const T = this.THREE;
|
|
12972
13365
|
const center = new T.Vector3(ev.center[0], ev.center[1], ev.center[2]);
|
|
12973
13366
|
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);
|
|
@@ -12992,15 +13385,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12992
13385
|
if (m && m.transparent) opacity = clamp6(m.opacity, 0.2, 0.8);
|
|
12993
13386
|
this.visuals.hide(pv, "broken");
|
|
12994
13387
|
}
|
|
12995
|
-
|
|
12996
|
-
const step = this.low ? 2 : 1;
|
|
12997
|
-
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
12998
|
-
const fr = ev.fragments[i];
|
|
12999
|
-
const fdef = this.world.materials.get(fr.material);
|
|
13000
|
-
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def || !fdef ? color : fallbackColor(fr.material, fdef);
|
|
13001
|
-
const mat = this.fragments.materialFor(fr.material, tone, transparent || fdef?.visual?.transparent === true, opacity);
|
|
13002
|
-
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
13003
|
-
}
|
|
13388
|
+
this.spawnFragments(ev, 0, { def, color, opacity, transparent });
|
|
13004
13389
|
const up = [0, 1, 0];
|
|
13005
13390
|
const sizeK = clamp6(ev.fragments.length / 10, 0.5, 2);
|
|
13006
13391
|
switch (ev.style) {
|
|
@@ -13026,6 +13411,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13026
13411
|
}
|
|
13027
13412
|
this.sound(fractureKey(def, ev.style), ev.point, clamp6(0.4 + ev.fragments.length * 0.03, 0.4, 1));
|
|
13028
13413
|
}
|
|
13414
|
+
/** Spawns the fragments of a fracture from index `from`; what does not fit in this frame's budget is spawned by the next frames. */
|
|
13415
|
+
spawnFragments(ev, from, look) {
|
|
13416
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
13417
|
+
const step = this.low ? 2 : 1;
|
|
13418
|
+
for (let i = from; i < ev.fragments.length; i += step) {
|
|
13419
|
+
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && performance.now() >= this.sliceDeadline) {
|
|
13420
|
+
this.slices.push(() => this.spawnFragments(ev, i, look));
|
|
13421
|
+
return;
|
|
13422
|
+
}
|
|
13423
|
+
const fr = ev.fragments[i];
|
|
13424
|
+
const fdef = this.world.materials.get(fr.material);
|
|
13425
|
+
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === look.def || !fdef ? look.color : fallbackColor(fr.material, fdef);
|
|
13426
|
+
const mat = this.fragments.materialFor(fr.material, tone, look.transparent || fdef?.visual?.transparent === true, look.opacity);
|
|
13427
|
+
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
13428
|
+
}
|
|
13429
|
+
}
|
|
13029
13430
|
onDetach(ev) {
|
|
13030
13431
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
13031
13432
|
this.splitLoose(ev.object);
|
|
@@ -13039,6 +13440,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13039
13440
|
if (!st || st.groups.length < 2) return;
|
|
13040
13441
|
const loose = st.groups.length - 1;
|
|
13041
13442
|
for (let i = 0; i < loose; i++) {
|
|
13443
|
+
if (i > 0 && performance.now() >= this.sliceDeadline) {
|
|
13444
|
+
this.slices.push(() => {
|
|
13445
|
+
this.staticDirty.add(objectId);
|
|
13446
|
+
this.splitLoose(objectId);
|
|
13447
|
+
});
|
|
13448
|
+
return;
|
|
13449
|
+
}
|
|
13042
13450
|
let newId;
|
|
13043
13451
|
try {
|
|
13044
13452
|
newId = this.world.splitGroup(objectId, 1);
|
|
@@ -13055,8 +13463,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13055
13463
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
13056
13464
|
old.sleeping = false;
|
|
13057
13465
|
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
13058
|
-
this.makePiece(objectId);
|
|
13466
|
+
const piece = this.makePiece(objectId);
|
|
13059
13467
|
this.tipIfTopHeavy(b);
|
|
13468
|
+
this.o.onLoosened?.(objectId, piece, "apart");
|
|
13060
13469
|
}
|
|
13061
13470
|
}
|
|
13062
13471
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -13165,7 +13574,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13165
13574
|
const piece = this.makePiece(b.id, { mass });
|
|
13166
13575
|
const body = this.pieceSys.get(b.id);
|
|
13167
13576
|
if (!body) continue;
|
|
13168
|
-
this.o.onLoosened?.(b.id, piece);
|
|
13577
|
+
this.o.onLoosened?.(b.id, piece, "blast");
|
|
13169
13578
|
const spread = half[0];
|
|
13170
13579
|
const momentum = hit.speed * mass;
|
|
13171
13580
|
this.pieceSys.impulse(body, [hit.direction[0] * momentum, hit.direction[1] * momentum, hit.direction[2] * momentum], [world.x + (this.rng() - 0.5) * spread, world.y + spread * 0.5, world.z + (this.rng() - 0.5) * spread]);
|
|
@@ -13360,6 +13769,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13360
13769
|
}
|
|
13361
13770
|
clear() {
|
|
13362
13771
|
this.queue.length = 0;
|
|
13772
|
+
this.slices.length = 0;
|
|
13363
13773
|
this.active.clear();
|
|
13364
13774
|
for (const key of [...this.fires.keys()]) this.stopFire(key);
|
|
13365
13775
|
this.leaks.length = 0;
|
|
@@ -13379,6 +13789,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13379
13789
|
this.vfx.sprites().clear();
|
|
13380
13790
|
this.ownVfx?.world.clear();
|
|
13381
13791
|
}
|
|
13792
|
+
reset() {
|
|
13793
|
+
this.clear();
|
|
13794
|
+
this.rng = createRng(RNG_SEED);
|
|
13795
|
+
this.time = 0;
|
|
13796
|
+
this.staticAt = 0;
|
|
13797
|
+
this.throttles.clear();
|
|
13798
|
+
this.fragments.reset();
|
|
13799
|
+
this.pieceSys.reset();
|
|
13800
|
+
}
|
|
13382
13801
|
wakeRegion(x, z, radius) {
|
|
13383
13802
|
this.fragments.wakeRegion(x, z, radius);
|
|
13384
13803
|
this.pieceSys.wakeRegion(x, z, radius);
|
|
@@ -13683,7 +14102,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13683
14102
|
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
14103
|
drag("drag_soft", [sceneNoise({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
13685
14104
|
];
|
|
13686
|
-
var
|
|
14105
|
+
var leaf = (id, layers) => ({ ...sceneRecipe(id, layers, ""), sources: ["sdk: a door, lid or drawer worked by hand"] });
|
|
14106
|
+
var LEAF_SOUNDS = [
|
|
14107
|
+
leaf("latch", [sceneNoise({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.35 }), sceneTone({ f: 1900, f2: 1500, d: 0.025, vol: 0.06, delay: 0.035 })]),
|
|
14108
|
+
leaf("creak_wood", [sceneTone({ f: [190, 240], f2: 310, d: 0.5, vol: 0.05 }), sceneNoise({ type: "bandpass", f: 520, q: 6, a: 0.05, d: 0.45, vol: 0.12 })]),
|
|
14109
|
+
leaf("creak_metal", [sceneTone({ f: [620, 760], f2: 900, d: 0.35, vol: 0.035 }), sceneNoise({ type: "bandpass", f: 1400, q: 8, a: 0.04, d: 0.3, vol: 0.08 })]),
|
|
14110
|
+
leaf("shut_wood", [sceneTone({ f: 120, f2: 60, d: 0.14, vol: 0.5 }), sceneNoise({ type: "bandpass", f: 600, q: 1.2, d: 0.1, vol: 0.55, wet: 0.25 }), sceneNoise({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.25, delay: 0.02 })]),
|
|
14111
|
+
leaf("shut_metal", [sceneTone({ f: 160, f2: 85, d: 0.18, vol: 0.55 }), sceneNoise({ type: "bandpass", f: 900, q: 1.6, d: 0.12, vol: 0.45, wet: 0.3 }), sceneTone({ f: 1250, f2: 1150, d: 0.12, vol: 0.06 })]),
|
|
14112
|
+
leaf("shut_soft", [sceneTone({ f: 95, f2: 55, d: 0.12, vol: 0.35 }), sceneNoise({ type: "lowpass", f: 500, d: 0.1, vol: 0.3 })]),
|
|
14113
|
+
leaf("rattle", sceneMany(5, () => sceneNoise({ type: "bandpass", f: [1600, 2600], q: 3, d: 0.035, vol: 0.32, delay: [0, 0.45] }))),
|
|
14114
|
+
leaf("slide", [sceneNoise({ type: "bandpass", f: 700, q: 2, a: 0.04, d: 0.35, vol: 0.22 }), sceneNoise({ type: "bandpass", f: 2200, q: 5, d: 0.3, vol: 0.05 })])
|
|
14115
|
+
];
|
|
14116
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
13687
14117
|
|
|
13688
14118
|
// src/audio/sound.ts
|
|
13689
14119
|
var DESTRUCTION_SOUND_KEYS = {
|
|
@@ -13698,6 +14128,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13698
14128
|
drag_glass: { sound: "drag_glass" },
|
|
13699
14129
|
drag_masonry: { sound: "drag_masonry" },
|
|
13700
14130
|
drag_soft: { sound: "drag_soft" },
|
|
14131
|
+
// a leaf worked by hand (doors, lids, drawers): the latch, the hinge turning, the leaf meeting the frame, a locked one shaken
|
|
14132
|
+
leaf_latch: { sound: "latch" },
|
|
14133
|
+
leaf_creak_wood: { sound: "creak_wood" },
|
|
14134
|
+
leaf_creak_metal: { sound: "creak_metal" },
|
|
14135
|
+
leaf_shut_wood: { sound: "shut_wood" },
|
|
14136
|
+
leaf_shut_metal: { sound: "shut_metal" },
|
|
14137
|
+
leaf_shut_glass: { sound: "shut_metal", volumeScale: 0.6 },
|
|
14138
|
+
leaf_shut_masonry: { sound: "shut_wood" },
|
|
14139
|
+
leaf_shut_soft: { sound: "shut_soft" },
|
|
14140
|
+
leaf_rattle: { sound: "rattle" },
|
|
14141
|
+
leaf_slide: { sound: "slide" },
|
|
14142
|
+
// the keys of the item clips (furniture and vehicle doors, hoods, trunks, drawers)
|
|
14143
|
+
door_latch: { sound: "latch" },
|
|
14144
|
+
hood_latch: { sound: "latch" },
|
|
14145
|
+
trunk_latch: { sound: "latch" },
|
|
14146
|
+
door_open: { sound: "latch" },
|
|
14147
|
+
door_shut: { sound: "shut_metal" },
|
|
14148
|
+
hood_shut: { sound: "shut_metal" },
|
|
14149
|
+
trunk_shut: { sound: "shut_metal" },
|
|
14150
|
+
door_close: { sound: "shut_wood", volumeScale: 0.7 },
|
|
14151
|
+
drawer_open: { sound: "slide" },
|
|
14152
|
+
drawer_close: { sound: "slide" },
|
|
14153
|
+
lid_open: { sound: "latch" },
|
|
14154
|
+
lid_close: { sound: "shut_soft" },
|
|
14155
|
+
flap_open: { sound: "latch" },
|
|
14156
|
+
flap_close: { sound: "shut_soft" },
|
|
13701
14157
|
shatter_glass: { sound: "glass_shatter" },
|
|
13702
14158
|
crumble: { sound: "crumble" },
|
|
13703
14159
|
splinter: { sound: "crack" },
|