@adatechnology/game-destruction-three 0.3.2 → 0.4.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 +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.global.js +173 -0
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -57,6 +57,8 @@ var FragmentSystem = class {
|
|
|
57
57
|
__publicField(this, "v");
|
|
58
58
|
__publicField(this, "w");
|
|
59
59
|
__publicField(this, "rng", createRng(1374496523));
|
|
60
|
+
__publicField(this, "markGeometry", null);
|
|
61
|
+
__publicField(this, "markMaterial", null);
|
|
60
62
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
61
63
|
this.q = new THREE.Quaternion();
|
|
62
64
|
this.dq = new THREE.Quaternion();
|
|
@@ -103,6 +105,25 @@ var FragmentSystem = class {
|
|
|
103
105
|
}
|
|
104
106
|
return g;
|
|
105
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
|
|
110
|
+
* a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
|
|
111
|
+
*/
|
|
112
|
+
addMarks(mesh, f) {
|
|
113
|
+
if (!f.marks?.length) return;
|
|
114
|
+
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
115
|
+
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
116
|
+
const sc = mesh.scale;
|
|
117
|
+
for (const mark of f.marks) {
|
|
118
|
+
const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
|
|
119
|
+
const radius = Math.max(4e-3, mark.diameter) * 0.6;
|
|
120
|
+
const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
|
|
121
|
+
disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
|
|
122
|
+
disc.quaternion.setFromUnitVectors(this.w.set(0, 0, 1), n);
|
|
123
|
+
disc.scale.set(radius / sc.x, radius / sc.y, radius / sc.z);
|
|
124
|
+
mesh.add(disc);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
106
127
|
/** Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget. */
|
|
107
128
|
spawn(f, material, home, maxAllowed) {
|
|
108
129
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
@@ -136,6 +157,8 @@ var FragmentSystem = class {
|
|
|
136
157
|
alive--;
|
|
137
158
|
}
|
|
138
159
|
}
|
|
160
|
+
this.addMarks(mesh, f);
|
|
161
|
+
return mesh;
|
|
139
162
|
}
|
|
140
163
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
141
164
|
blast(point, radius, speed) {
|
|
@@ -259,6 +282,8 @@ var FragmentSystem = class {
|
|
|
259
282
|
dispose() {
|
|
260
283
|
this.clear();
|
|
261
284
|
this.box.dispose();
|
|
285
|
+
this.markGeometry?.dispose();
|
|
286
|
+
this.markMaterial?.dispose();
|
|
262
287
|
for (const g of this.shardGeos) g?.dispose();
|
|
263
288
|
this.shardGeos = [];
|
|
264
289
|
for (const m of this.materials.values()) m.dispose();
|
|
@@ -1010,6 +1035,78 @@ var PieceSystem = class {
|
|
|
1010
1035
|
}
|
|
1011
1036
|
};
|
|
1012
1037
|
|
|
1038
|
+
// src/rebar.ts
|
|
1039
|
+
var RebarCages = class {
|
|
1040
|
+
constructor(THREE, scene, radius = 4e-3) {
|
|
1041
|
+
__publicField(this, "THREE", THREE);
|
|
1042
|
+
__publicField(this, "scene", scene);
|
|
1043
|
+
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
1044
|
+
__publicField(this, "geometry");
|
|
1045
|
+
__publicField(this, "material");
|
|
1046
|
+
__publicField(this, "up");
|
|
1047
|
+
__publicField(this, "dir");
|
|
1048
|
+
__publicField(this, "mid");
|
|
1049
|
+
__publicField(this, "q");
|
|
1050
|
+
__publicField(this, "scale");
|
|
1051
|
+
__publicField(this, "m");
|
|
1052
|
+
this.geometry = new THREE.CylinderGeometry(radius, radius, 1, 6);
|
|
1053
|
+
this.material = new THREE.MeshStandardMaterial({ color: 7628380, roughness: 0.55, metalness: 0.6 });
|
|
1054
|
+
this.up = new THREE.Vector3(0, 1, 0);
|
|
1055
|
+
this.dir = new THREE.Vector3();
|
|
1056
|
+
this.mid = new THREE.Vector3();
|
|
1057
|
+
this.q = new THREE.Quaternion();
|
|
1058
|
+
this.scale = new THREE.Vector3();
|
|
1059
|
+
this.m = new THREE.Matrix4();
|
|
1060
|
+
}
|
|
1061
|
+
/** Bars drawn now, over every part. */
|
|
1062
|
+
get count() {
|
|
1063
|
+
let n = 0;
|
|
1064
|
+
for (const mesh of this.cages.values()) n += mesh.count;
|
|
1065
|
+
return n;
|
|
1066
|
+
}
|
|
1067
|
+
/** Meshes of the live cages (tests and picking). */
|
|
1068
|
+
meshes() {
|
|
1069
|
+
return [...this.cages.values()];
|
|
1070
|
+
}
|
|
1071
|
+
/** Draw `bars` (world space) as the cage of `key`; an empty list removes it. */
|
|
1072
|
+
set(key, bars) {
|
|
1073
|
+
this.remove(key);
|
|
1074
|
+
if (bars.length === 0) return;
|
|
1075
|
+
const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
1076
|
+
bars.forEach(([a, b], i) => {
|
|
1077
|
+
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
1078
|
+
const length = this.dir.length();
|
|
1079
|
+
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
1080
|
+
this.q.setFromUnitVectors(this.up, length > 1e-9 ? this.dir.divideScalar(length) : this.up);
|
|
1081
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1));
|
|
1082
|
+
mesh.setMatrixAt(i, this.m);
|
|
1083
|
+
});
|
|
1084
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
1085
|
+
mesh.castShadow = true;
|
|
1086
|
+
this.scene.add(mesh);
|
|
1087
|
+
this.cages.set(key, mesh);
|
|
1088
|
+
}
|
|
1089
|
+
remove(key) {
|
|
1090
|
+
const mesh = this.cages.get(key);
|
|
1091
|
+
if (!mesh) return;
|
|
1092
|
+
this.scene.remove(mesh);
|
|
1093
|
+
mesh.dispose();
|
|
1094
|
+
this.cages.delete(key);
|
|
1095
|
+
}
|
|
1096
|
+
/** Remove the cages of every part of `object` (keys are `object|part`). */
|
|
1097
|
+
removeObject(object) {
|
|
1098
|
+
for (const key of [...this.cages.keys()]) if (key.startsWith(`${object}|`)) this.remove(key);
|
|
1099
|
+
}
|
|
1100
|
+
clear() {
|
|
1101
|
+
for (const key of [...this.cages.keys()]) this.remove(key);
|
|
1102
|
+
}
|
|
1103
|
+
dispose() {
|
|
1104
|
+
this.clear();
|
|
1105
|
+
this.geometry.dispose();
|
|
1106
|
+
this.material.dispose();
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1013
1110
|
// src/spec.ts
|
|
1014
1111
|
import { guessMaterial, inferJoints } from "@adatechnology/game-destruction";
|
|
1015
1112
|
function partHintOf(value) {
|
|
@@ -1214,6 +1311,7 @@ var DecalKit = class {
|
|
|
1214
1311
|
__publicField(this, "disc", null);
|
|
1215
1312
|
__publicField(this, "discMat", null);
|
|
1216
1313
|
__publicField(this, "lineMats", /* @__PURE__ */ new Map());
|
|
1314
|
+
__publicField(this, "patchMats", /* @__PURE__ */ new Map());
|
|
1217
1315
|
}
|
|
1218
1316
|
discGeometry() {
|
|
1219
1317
|
return this.disc ?? (this.disc = new this.THREE.CircleGeometry(1, 14));
|
|
@@ -1229,6 +1327,15 @@ var DecalKit = class {
|
|
|
1229
1327
|
polygonOffsetUnits: -4
|
|
1230
1328
|
}));
|
|
1231
1329
|
}
|
|
1330
|
+
/** Opaque patch in the colour of what shows under a spalled coat. */
|
|
1331
|
+
patchMaterial(color) {
|
|
1332
|
+
let m = this.patchMats.get(color);
|
|
1333
|
+
if (!m) {
|
|
1334
|
+
m = new this.THREE.MeshBasicMaterial({ color, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
1335
|
+
this.patchMats.set(color, m);
|
|
1336
|
+
}
|
|
1337
|
+
return m;
|
|
1338
|
+
}
|
|
1232
1339
|
lineMaterial(color) {
|
|
1233
1340
|
let m = this.lineMats.get(color);
|
|
1234
1341
|
if (!m) {
|
|
@@ -1241,6 +1348,8 @@ var DecalKit = class {
|
|
|
1241
1348
|
this.disc?.dispose();
|
|
1242
1349
|
this.discMat?.dispose();
|
|
1243
1350
|
for (const m of this.lineMats.values()) m.dispose();
|
|
1351
|
+
for (const m of this.patchMats.values()) m.dispose();
|
|
1352
|
+
this.patchMats.clear();
|
|
1244
1353
|
this.disc = null;
|
|
1245
1354
|
this.discMat = null;
|
|
1246
1355
|
this.lineMats.clear();
|
|
@@ -1532,6 +1641,16 @@ var Visuals = class {
|
|
|
1532
1641
|
disc.renderOrder = 2;
|
|
1533
1642
|
this.addDecal(mv, { obj: disc });
|
|
1534
1643
|
}
|
|
1644
|
+
/** A patch where a surface coat came off, in the colour of the core under it; drawn under the bullet holes. */
|
|
1645
|
+
addSpall(mv, worldPoint, worldNormal, radius, color) {
|
|
1646
|
+
const { s, pos, quat } = this.decalPose(mv.mesh, worldPoint, worldNormal, 15e-4);
|
|
1647
|
+
const disc = new this.THREE.Mesh(this.kit.discGeometry(), this.kit.patchMaterial(color));
|
|
1648
|
+
disc.position.copy(pos);
|
|
1649
|
+
disc.quaternion.copy(quat);
|
|
1650
|
+
disc.scale.setScalar(Math.max(0.01, radius) / s);
|
|
1651
|
+
disc.renderOrder = 1;
|
|
1652
|
+
this.addDecal(mv, { obj: disc });
|
|
1653
|
+
}
|
|
1535
1654
|
/** A star of jagged lines: `rays` rays up to `length` metres, seeded so the same event always draws the same star. */
|
|
1536
1655
|
addCrack(mv, worldPoint, worldNormal, rays, length, color, seed) {
|
|
1537
1656
|
const { s, pos, quat } = this.decalPose(mv.mesh, worldPoint, worldNormal, 2e-3);
|
|
@@ -2057,6 +2176,7 @@ var Renderer = class {
|
|
|
2057
2176
|
__publicField(this, "fragments");
|
|
2058
2177
|
__publicField(this, "ground");
|
|
2059
2178
|
__publicField(this, "pieceSys");
|
|
2179
|
+
__publicField(this, "cages");
|
|
2060
2180
|
__publicField(this, "active", /* @__PURE__ */ new Map());
|
|
2061
2181
|
__publicField(this, "fires", /* @__PURE__ */ new Map());
|
|
2062
2182
|
__publicField(this, "leaks", []);
|
|
@@ -2108,6 +2228,7 @@ var Renderer = class {
|
|
|
2108
2228
|
max: this.maxFragments,
|
|
2109
2229
|
life: o.fragmentLife ?? 12
|
|
2110
2230
|
});
|
|
2231
|
+
this.cages = new RebarCages(THREE, o.scene);
|
|
2111
2232
|
this.pieceSys = new PieceSystem(THREE, {
|
|
2112
2233
|
groundAt: this.groundAt,
|
|
2113
2234
|
gravity: this.gravity,
|
|
@@ -2409,6 +2530,15 @@ var Renderer = class {
|
|
|
2409
2530
|
this.sound("ignite", ev.point, 0.7);
|
|
2410
2531
|
}
|
|
2411
2532
|
break;
|
|
2533
|
+
case "spall":
|
|
2534
|
+
this.onSpall(ev);
|
|
2535
|
+
break;
|
|
2536
|
+
case "stage":
|
|
2537
|
+
this.onStage(ev);
|
|
2538
|
+
break;
|
|
2539
|
+
case "expose":
|
|
2540
|
+
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
2541
|
+
break;
|
|
2412
2542
|
case "extinguish": {
|
|
2413
2543
|
const f = this.fires.get(`${ev.object}|${ev.part}`);
|
|
2414
2544
|
if (f) {
|
|
@@ -2574,6 +2704,43 @@ var Renderer = class {
|
|
|
2574
2704
|
}
|
|
2575
2705
|
this.sound(impactKey(def), ev.worldPoint, 0.5);
|
|
2576
2706
|
}
|
|
2707
|
+
/** Outward normal of the face of `mv` nearest to a world point: the thin axis of its world box (right for a wall; a sloped face is left to the vertex-range binding). */
|
|
2708
|
+
faceNormal(mv, point) {
|
|
2709
|
+
const box = this.visuals.worldBox(mv, this.box);
|
|
2710
|
+
const size = box.getSize(this.v1);
|
|
2711
|
+
const axis = size.x <= size.y && size.x <= size.z ? "x" : size.y <= size.z ? "y" : "z";
|
|
2712
|
+
const side = point[axis] >= (box.min[axis] + box.max[axis]) / 2 ? 1 : -1;
|
|
2713
|
+
return new this.THREE.Vector3(axis === "x" ? side : 0, axis === "y" ? side : 0, axis === "z" ? side : 0);
|
|
2714
|
+
}
|
|
2715
|
+
/** A coat came off: a patch in the colour of the core under it, below the bullet holes and cracks. */
|
|
2716
|
+
onSpall(ev) {
|
|
2717
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
2718
|
+
if (!pv) return;
|
|
2719
|
+
const wp = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
2720
|
+
const mv = this.nearestMesh(pv, wp);
|
|
2721
|
+
if (!mv) return;
|
|
2722
|
+
const def = this.world.materials.get(pv.material);
|
|
2723
|
+
this.visuals.addSpall(mv, wp, this.faceNormal(mv, wp), Math.sqrt(ev.area / Math.PI), fallbackColor(pv.material, def));
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* 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
|
|
2727
|
+
* (cutting the wall's faces) is not drawn yet: it needs the per-vertex binding of the house renderer.
|
|
2728
|
+
*/
|
|
2729
|
+
onStage(ev) {
|
|
2730
|
+
if (!ev.fragments || !ev.opening) return;
|
|
2731
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
2732
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
2733
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
2734
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
2735
|
+
const step = this.low ? 2 : 1;
|
|
2736
|
+
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
2737
|
+
const fr = ev.fragments[i];
|
|
2738
|
+
const fdef = this.world.materials.get(fr.material);
|
|
2739
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fdef === def || !fdef ? color : fallbackColor(fr.material, fdef), false), ev.object, maxAlive);
|
|
2740
|
+
}
|
|
2741
|
+
this.particles.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
2742
|
+
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
2743
|
+
}
|
|
2577
2744
|
onFracture(ev) {
|
|
2578
2745
|
const b = this.bounds.get(ev.object);
|
|
2579
2746
|
const pv = b?.parts.get(ev.part);
|
|
@@ -2884,6 +3051,7 @@ var Renderer = class {
|
|
|
2884
3051
|
for (const home of homes) {
|
|
2885
3052
|
this.fragments.removeHome(home);
|
|
2886
3053
|
this.ground.removeHome(home);
|
|
3054
|
+
this.cages.removeObject(home);
|
|
2887
3055
|
}
|
|
2888
3056
|
}
|
|
2889
3057
|
restoreHome(home) {
|
|
@@ -2920,6 +3088,7 @@ var Renderer = class {
|
|
|
2920
3088
|
}
|
|
2921
3089
|
this.fragments.clear();
|
|
2922
3090
|
this.ground.clear();
|
|
3091
|
+
this.cages.clear();
|
|
2923
3092
|
this.particles.clear();
|
|
2924
3093
|
if (this.light) {
|
|
2925
3094
|
this.light.intensity = 0;
|
|
@@ -2930,6 +3099,9 @@ var Renderer = class {
|
|
|
2930
3099
|
stats() {
|
|
2931
3100
|
return { fragments: this.fragments.count, particles: this.particles.live, pieces: this.pieceSys.list.length, decals: this.visuals.decalCount + this.ground.count };
|
|
2932
3101
|
}
|
|
3102
|
+
rebarCount() {
|
|
3103
|
+
return this.cages.count;
|
|
3104
|
+
}
|
|
2933
3105
|
dispose() {
|
|
2934
3106
|
if (this.disposed) return;
|
|
2935
3107
|
this.unsubscribe();
|
|
@@ -2937,6 +3109,7 @@ var Renderer = class {
|
|
|
2937
3109
|
for (const id of [...this.bounds.keys()]) this.unbind(id);
|
|
2938
3110
|
this.fragments.dispose();
|
|
2939
3111
|
this.ground.dispose();
|
|
3112
|
+
this.cages.dispose();
|
|
2940
3113
|
this.particles.dispose();
|
|
2941
3114
|
this.visuals.dispose();
|
|
2942
3115
|
this.light?.dispose?.();
|