@adatechnology/game-floorplan-three 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -1
- package/dist/index.global.js +373 -49
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +230 -31
- package/dist/index.js.map +1 -1
- package/package.json +20 -21
package/dist/index.global.js
CHANGED
|
@@ -13523,6 +13523,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13523
13523
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
13524
13524
|
var VENT_DIAMETER = 8e-3;
|
|
13525
13525
|
var FLAME_LIGHTS_LEAK_C = 470;
|
|
13526
|
+
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
13526
13527
|
var BREACH_RADIUS = 0.55;
|
|
13527
13528
|
var BREACH_BLAST_FACTOR = 6;
|
|
13528
13529
|
var BREACH_AREA = 0.25;
|
|
@@ -13538,6 +13539,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13538
13539
|
var BREACH_GRID_CAP = 4096;
|
|
13539
13540
|
var CHIP_ENERGY = 200;
|
|
13540
13541
|
var CHIP_SPLASH = 0.5;
|
|
13542
|
+
var CRATER_SPENT = 0.6;
|
|
13541
13543
|
var BAR_BOW = 0.08;
|
|
13542
13544
|
var BULLET_JOINT_SPAN = 0.3;
|
|
13543
13545
|
var CUT_SLICE = 1;
|
|
@@ -13558,12 +13560,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13558
13560
|
const cells = p.st.chipped;
|
|
13559
13561
|
if (!cells?.length) return 0;
|
|
13560
13562
|
const [u, v] = planeAxes(p.thin);
|
|
13561
|
-
const
|
|
13563
|
+
const role = p.spec.structure?.role;
|
|
13564
|
+
const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
|
|
13565
|
+
const upright = Math.abs(up[1]) > 0.7 ? v : Math.abs(rotateVec(p.q, [0, 0, 0].map((_, k) => k === u ? 1 : 0))[1]) > 0.7 ? u : -1;
|
|
13566
|
+
const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
|
|
13562
13567
|
const across = along === u ? v : u;
|
|
13563
13568
|
let worst = 0;
|
|
13564
13569
|
for (const c of cells) {
|
|
13565
|
-
|
|
13566
|
-
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6)
|
|
13570
|
+
const spans = [];
|
|
13571
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) spans.push([d.center[across] - d.size[across] / 2, d.center[across] + d.size[across] / 2]);
|
|
13572
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
13573
|
+
let gone = 0, end = -Infinity;
|
|
13574
|
+
for (const [a, b] of spans) {
|
|
13575
|
+
if (b <= end) continue;
|
|
13576
|
+
gone += b - Math.max(a, end);
|
|
13577
|
+
end = b;
|
|
13578
|
+
}
|
|
13567
13579
|
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
13568
13580
|
}
|
|
13569
13581
|
return Math.min(1, worst);
|
|
@@ -13859,7 +13871,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13859
13871
|
if (k < 0 || j.spec.kind !== "tie") return k;
|
|
13860
13872
|
return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
|
|
13861
13873
|
};
|
|
13874
|
+
let poseVersion = 0;
|
|
13875
|
+
const contactMemo = /* @__PURE__ */ new WeakMap();
|
|
13862
13876
|
const contactBox = (p, support) => {
|
|
13877
|
+
let byTouching = contactMemo.get(p);
|
|
13878
|
+
if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
|
|
13879
|
+
const known = byTouching.get(support);
|
|
13880
|
+
if (known && known.version === poseVersion) return known.box;
|
|
13881
|
+
const box3 = measureContact(p, support);
|
|
13882
|
+
byTouching.set(support, { version: poseVersion, box: box3 });
|
|
13883
|
+
return box3;
|
|
13884
|
+
};
|
|
13885
|
+
const measureContact = (p, support) => {
|
|
13863
13886
|
const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
|
|
13864
13887
|
const lo = [0, 0, 0], hi = [0, 0, 0];
|
|
13865
13888
|
for (let k = 0; k < 3; k++) {
|
|
@@ -14654,7 +14677,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14654
14677
|
p.st.holes.push(hole);
|
|
14655
14678
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
14656
14679
|
markDamaged(p);
|
|
14657
|
-
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
14680
|
+
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
|
|
14658
14681
|
setStage(o, p, 1, {}, src);
|
|
14659
14682
|
return hole;
|
|
14660
14683
|
};
|
|
@@ -14671,6 +14694,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14671
14694
|
const detachEvents = (o, opened, by, src) => {
|
|
14672
14695
|
for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
|
|
14673
14696
|
};
|
|
14697
|
+
const slideOffResting = (o, rotation) => {
|
|
14698
|
+
const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
|
|
14699
|
+
if (upY >= RESTING_HOLD_COS) return;
|
|
14700
|
+
const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
|
|
14701
|
+
if (opened.length === 0) return;
|
|
14702
|
+
run(() => {
|
|
14703
|
+
for (const j of opened) {
|
|
14704
|
+
j.st.open = true;
|
|
14705
|
+
j.st.openedBy = "break";
|
|
14706
|
+
}
|
|
14707
|
+
solveStructure(o);
|
|
14708
|
+
recomputeGroups(o);
|
|
14709
|
+
detachEvents(o, opened, "break");
|
|
14710
|
+
collapseCheck(o);
|
|
14711
|
+
});
|
|
14712
|
+
};
|
|
14674
14713
|
const checkDestroyed = (o, src) => {
|
|
14675
14714
|
if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
|
|
14676
14715
|
o.st.destroyed = true;
|
|
@@ -14771,7 +14810,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14771
14810
|
const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
|
|
14772
14811
|
const unitsOf2 = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
14773
14812
|
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
14774
|
-
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
14813
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner = false) => {
|
|
14775
14814
|
const { c, q } = partXf(o, p);
|
|
14776
14815
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
14777
14816
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -14795,7 +14834,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14795
14834
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
14796
14835
|
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
14797
14836
|
mass: mass * faceArea(cell) / total,
|
|
14798
|
-
...marks.length > 0 ? { marks } : {}
|
|
14837
|
+
...marks.length > 0 ? { marks } : {},
|
|
14838
|
+
...inner ? { inner } : {}
|
|
14799
14839
|
};
|
|
14800
14840
|
});
|
|
14801
14841
|
};
|
|
@@ -14836,7 +14876,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14836
14876
|
return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
|
|
14837
14877
|
};
|
|
14838
14878
|
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
14839
|
-
var _a, _b;
|
|
14879
|
+
var _a, _b, _c;
|
|
14840
14880
|
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
14841
14881
|
const rule = p.mat.hits?.[kind];
|
|
14842
14882
|
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
@@ -14846,7 +14886,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14846
14886
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
14847
14887
|
const grid = gridOf(p);
|
|
14848
14888
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
14889
|
+
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
14849
14890
|
const out = [];
|
|
14891
|
+
const shot = kind === "ballistic";
|
|
14850
14892
|
for (let i = 0; i < grid.length; i++) {
|
|
14851
14893
|
const cell = grid[i];
|
|
14852
14894
|
if (worn[i] === -1) continue;
|
|
@@ -14859,18 +14901,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14859
14901
|
worn[i] = total;
|
|
14860
14902
|
continue;
|
|
14861
14903
|
}
|
|
14862
|
-
|
|
14863
|
-
|
|
14904
|
+
if (!shot) {
|
|
14905
|
+
worn[i] = -1;
|
|
14906
|
+
out.push(cell);
|
|
14907
|
+
continue;
|
|
14908
|
+
}
|
|
14909
|
+
const size = [cell.size[0], cell.size[1], cell.size[2]];
|
|
14910
|
+
const center = [cell.center[0], cell.center[1], cell.center[2]];
|
|
14911
|
+
for (const a of [u, v]) {
|
|
14912
|
+
const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
|
|
14913
|
+
const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
|
|
14914
|
+
size[a] = w;
|
|
14915
|
+
center[a] = clamp2(lp[a], lo, hi);
|
|
14916
|
+
}
|
|
14917
|
+
const taken2 = (cut[i] ?? 0) + size[u] * size[v];
|
|
14918
|
+
if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
|
|
14919
|
+
worn[i] = -1;
|
|
14920
|
+
out.push({ ...cell, crater: true });
|
|
14921
|
+
continue;
|
|
14922
|
+
}
|
|
14923
|
+
worn[i] = 0;
|
|
14924
|
+
cut[i] = taken2;
|
|
14925
|
+
out.push({ center, size, crater: true });
|
|
14864
14926
|
}
|
|
14865
14927
|
if (out.length === 0) return;
|
|
14866
14928
|
const { c, q } = partXf(o, p);
|
|
14867
|
-
const chipped = (
|
|
14929
|
+
const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
|
|
14868
14930
|
const holes = localHolesOf(p);
|
|
14869
14931
|
for (const cell of out) {
|
|
14870
14932
|
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
14871
14933
|
const center = toWorld(c, q, cell.center);
|
|
14872
14934
|
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
14873
|
-
const
|
|
14935
|
+
const tag = `c${chipped.length}_`;
|
|
14936
|
+
const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
|
|
14874
14937
|
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
14875
14938
|
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
14876
14939
|
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
@@ -14887,6 +14950,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14887
14950
|
collapseCheck(o, src);
|
|
14888
14951
|
}
|
|
14889
14952
|
};
|
|
14953
|
+
const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
|
|
14954
|
+
const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
|
|
14955
|
+
const { c, q } = partXf(o, p);
|
|
14956
|
+
const [u, v] = planeAxes(p.thin);
|
|
14957
|
+
const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
|
|
14958
|
+
const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
|
|
14959
|
+
const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
|
|
14960
|
+
const n = clamp2(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
|
|
14961
|
+
const out = [];
|
|
14962
|
+
for (let i = 0; i < n; i++) {
|
|
14963
|
+
const back = i % 3 === 2;
|
|
14964
|
+
const lc = [cell.center[0], cell.center[1], cell.center[2]];
|
|
14965
|
+
lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
|
|
14966
|
+
lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
|
|
14967
|
+
lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
|
|
14968
|
+
const size = [0, 0, 0];
|
|
14969
|
+
size[u] = bit * (0.6 + 0.8 * rnd());
|
|
14970
|
+
size[v] = bit * (0.5 + 0.7 * rnd());
|
|
14971
|
+
size[p.thin] = bit * (0.4 + 0.6 * rnd());
|
|
14972
|
+
const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
|
|
14973
|
+
const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
|
|
14974
|
+
const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
|
|
14975
|
+
out.push({
|
|
14976
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
14977
|
+
part: p.spec.id,
|
|
14978
|
+
material: p.spec.material,
|
|
14979
|
+
shape: "chunk",
|
|
14980
|
+
center: toWorld(c, q, lc),
|
|
14981
|
+
size,
|
|
14982
|
+
rotation: q,
|
|
14983
|
+
velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
|
|
14984
|
+
angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
|
|
14985
|
+
mass: mass / n,
|
|
14986
|
+
inner: true
|
|
14987
|
+
});
|
|
14988
|
+
}
|
|
14989
|
+
return out;
|
|
14990
|
+
};
|
|
14890
14991
|
const exposeRebar = (o, p, src) => {
|
|
14891
14992
|
const r = p.spec.reinforcement;
|
|
14892
14993
|
if (!r) return;
|
|
@@ -15174,7 +15275,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15174
15275
|
});
|
|
15175
15276
|
}
|
|
15176
15277
|
const patch = [scale * 4, scale * 4, scale * 4];
|
|
15177
|
-
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
|
|
15278
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments, torn: true });
|
|
15178
15279
|
};
|
|
15179
15280
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
15180
15281
|
const mat = p.mat;
|
|
@@ -15233,12 +15334,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15233
15334
|
} else if (mat.ductility >= 0.5) {
|
|
15234
15335
|
addDent(o, p, entry2, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
|
|
15235
15336
|
} else if (brittle) {
|
|
15236
|
-
addCrack(o, p, entry2, n, crackLen, src);
|
|
15337
|
+
if (!p.spec.units) addCrack(o, p, entry2, n, crackLen, src);
|
|
15237
15338
|
if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
15238
15339
|
} else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
15239
15340
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
15240
15341
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
|
|
15241
|
-
const
|
|
15342
|
+
const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
|
|
15343
|
+
const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
|
|
15242
15344
|
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
|
|
15243
15345
|
if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
15244
15346
|
chipUnits(o, p, "ballistic", entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
@@ -15323,10 +15425,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15323
15425
|
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
15324
15426
|
return reach;
|
|
15325
15427
|
};
|
|
15428
|
+
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
15429
|
+
const blastCandidates = (o, point, radius) => {
|
|
15430
|
+
const local = toLocal(o.st.position, o.st.rotation, point);
|
|
15431
|
+
const near = [];
|
|
15432
|
+
for (const p of o.parts) {
|
|
15433
|
+
const reach = radius + p.bound;
|
|
15434
|
+
const dx = local[0] - p.pos[0];
|
|
15435
|
+
const dy = local[1] - p.pos[1];
|
|
15436
|
+
const dz = local[2] - p.pos[2];
|
|
15437
|
+
if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
|
|
15438
|
+
}
|
|
15439
|
+
blastProbe.partsTested += o.parts.length;
|
|
15440
|
+
blastProbe.partsNear += near.length;
|
|
15441
|
+
return near;
|
|
15442
|
+
};
|
|
15326
15443
|
const blastObject = (o, point, energy, radius, src) => {
|
|
15327
15444
|
const start = cur.length;
|
|
15328
15445
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
15329
|
-
for (const p of o
|
|
15446
|
+
for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
|
|
15330
15447
|
res.events = cur.slice(start);
|
|
15331
15448
|
return res;
|
|
15332
15449
|
};
|
|
@@ -15450,7 +15567,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15450
15567
|
let order = 0;
|
|
15451
15568
|
for (const o of inRange) {
|
|
15452
15569
|
if (only && only !== o) continue;
|
|
15453
|
-
for (const p of o
|
|
15570
|
+
for (const p of blastCandidates(o, point, radius)) {
|
|
15454
15571
|
if (isLost(p)) continue;
|
|
15455
15572
|
const dist = distToPart(o, p, point);
|
|
15456
15573
|
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
@@ -15997,6 +16114,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15997
16114
|
const p = getPart(o, partId);
|
|
15998
16115
|
p.pos = [position[0], position[1], position[2]];
|
|
15999
16116
|
p.q = q;
|
|
16117
|
+
poseVersion++;
|
|
16000
16118
|
updateBound(o);
|
|
16001
16119
|
},
|
|
16002
16120
|
remove(id) {
|
|
@@ -16015,6 +16133,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16015
16133
|
const o = getObj(id);
|
|
16016
16134
|
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
16017
16135
|
applySoilPose(o);
|
|
16136
|
+
slideOffResting(o, rotation);
|
|
16018
16137
|
},
|
|
16019
16138
|
splitGroup(id, group, newId) {
|
|
16020
16139
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -16038,7 +16157,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16038
16157
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
16039
16158
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
16040
16159
|
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
16041
|
-
return
|
|
16160
|
+
return s;
|
|
16042
16161
|
};
|
|
16043
16162
|
const inNew = (pid) => moved.has(pid);
|
|
16044
16163
|
const inOld = (pid) => !moved.has(pid);
|
|
@@ -16065,7 +16184,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16065
16184
|
const p = getPart(o, partId);
|
|
16066
16185
|
return whole(() => doHit(o, h, p));
|
|
16067
16186
|
},
|
|
16187
|
+
blastProbe() {
|
|
16188
|
+
return { ...blastProbe };
|
|
16189
|
+
},
|
|
16068
16190
|
explode(point, energy, radius, source, blast) {
|
|
16191
|
+
blastProbe.partsTested = 0;
|
|
16192
|
+
blastProbe.partsNear = 0;
|
|
16069
16193
|
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
16070
16194
|
},
|
|
16071
16195
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
@@ -16075,7 +16199,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16075
16199
|
sweepBox(box3, direction, distance2, options2 = {}) {
|
|
16076
16200
|
const d = vec.normalize(direction);
|
|
16077
16201
|
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
16078
|
-
const
|
|
16202
|
+
const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
|
|
16203
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : hosted ? new Set(hosted.impl.describe().parts.map((p) => `${hosted.impl.id}\0${p.id}`)) : void 0;
|
|
16079
16204
|
let best = null;
|
|
16080
16205
|
for (const origin of sweepOrigins(box3, d, options2.spacing ?? 0.2)) {
|
|
16081
16206
|
const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
|
|
@@ -19589,6 +19714,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19589
19714
|
function collapseRange(mesh2, v0, v1) {
|
|
19590
19715
|
const pos = mesh2.geometry.attributes.position;
|
|
19591
19716
|
const a = pos.array;
|
|
19717
|
+
const u = mesh2.userData;
|
|
19718
|
+
if (!u._p0) u._p0 = a.slice();
|
|
19719
|
+
const list2 = u.collapsedRanges ?? (u.collapsedRanges = []);
|
|
19720
|
+
if (!list2.some((r) => r[0] === v0 && r[1] === v1)) list2.push([v0, v1]);
|
|
19592
19721
|
const saved = a.slice(v0 * 3, v1 * 3);
|
|
19593
19722
|
const x = a[v0 * 3], y = a[v0 * 3 + 1], z = a[v0 * 3 + 2];
|
|
19594
19723
|
for (let k = v0; k < v1; k++) {
|
|
@@ -19603,6 +19732,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19603
19732
|
const pos = mesh2.geometry.attributes.position;
|
|
19604
19733
|
pos.array.set(saved, v0 * 3);
|
|
19605
19734
|
pos.needsUpdate = true;
|
|
19735
|
+
const list2 = mesh2.userData.collapsedRanges;
|
|
19736
|
+
if (list2) mesh2.userData.collapsedRanges = list2.filter((r) => r[0] !== v0);
|
|
19606
19737
|
}
|
|
19607
19738
|
function noise2(x, y, cell) {
|
|
19608
19739
|
const h = (i2, j2) => {
|
|
@@ -19617,13 +19748,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19617
19748
|
var SPALL_MIN = 0.025;
|
|
19618
19749
|
var SPALL_RAGGED = 0.045;
|
|
19619
19750
|
var COVER2 = 0.015;
|
|
19751
|
+
var MAX_FLAKES = 64;
|
|
19620
19752
|
function installDestructionBreach(K, _shared) {
|
|
19621
19753
|
const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
|
|
19622
19754
|
function createBreachView(opts) {
|
|
19623
19755
|
const { house, structure } = opts;
|
|
19624
19756
|
const views = /* @__PURE__ */ new Map();
|
|
19757
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
19758
|
+
const paintBudgetMs = opts.paintBudgetMs ?? Infinity;
|
|
19759
|
+
const dropped = [];
|
|
19625
19760
|
let lod = 0;
|
|
19626
|
-
const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
|
|
19627
19761
|
const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
19628
19762
|
const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
|
|
19629
19763
|
const local = (p) => {
|
|
@@ -19692,6 +19826,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19692
19826
|
const mesh2 = new THREE.Mesh(g, material);
|
|
19693
19827
|
mesh2.name = "breach-shell";
|
|
19694
19828
|
mesh2.userData.breachMask = mask;
|
|
19829
|
+
mesh2.userData.cutAt = (p) => {
|
|
19830
|
+
const v = local([p.x, p.y, p.z]);
|
|
19831
|
+
const i = Math.floor(((mask.alongU ? v[0] : v[2]) - mask.start) / mask.len * mask.w), j = Math.floor((v[1] - mask.y0) / mask.height * mask.h);
|
|
19832
|
+
return i >= 0 && i < mask.w && j >= 0 && j < mask.h && mask.data[j * mask.w + i] > 127;
|
|
19833
|
+
};
|
|
19695
19834
|
mesh2.position.copy(it.mesh.position);
|
|
19696
19835
|
mesh2.quaternion.copy(it.mesh.quaternion);
|
|
19697
19836
|
mesh2.scale.copy(it.mesh.scale);
|
|
@@ -19707,6 +19846,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19707
19846
|
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh2.matrixWorld);
|
|
19708
19847
|
return mesh2;
|
|
19709
19848
|
}
|
|
19849
|
+
function unitsMaterial(core) {
|
|
19850
|
+
const material = new THREE.MeshLambertMaterial({ color: 16777215, side: THREE.DoubleSide });
|
|
19851
|
+
material.onBeforeCompile = (sh) => {
|
|
19852
|
+
sh.uniforms.uCore = { value: core.texture };
|
|
19853
|
+
sh.uniforms.uCoreRect = { value: core.rect };
|
|
19854
|
+
sh.uniforms.uCoreAlongX = { value: core.alongU ? 1 : 0 };
|
|
19855
|
+
sh.vertexShader = sh.vertexShader.replace("void main() {", "varying vec3 vHousePos;\nvoid main() {").replace("#include <begin_vertex>", "#include <begin_vertex>\n#ifdef USE_INSTANCING\nvHousePos = (instanceMatrix * vec4(transformed, 1.0)).xyz;\n#else\nvHousePos = transformed;\n#endif");
|
|
19856
|
+
sh.fragmentShader = sh.fragmentShader.replace("void main() {", "uniform sampler2D uCore;\nuniform vec4 uCoreRect;\nuniform float uCoreAlongX;\nvarying vec3 vHousePos;\nvoid main() {\n vec2 cUv = vec2(((uCoreAlongX > 0.5 ? vHousePos.x : vHousePos.z) - uCoreRect.x) / uCoreRect.z, (vHousePos.y - uCoreRect.y) / uCoreRect.w);\n if (cUv.x >= 0.0 && cUv.x <= 1.0 && cUv.y >= 0.0 && cUv.y <= 1.0 && texture2D(uCore, cUv).r > 0.5) discard;").replace("#include <color_fragment>", "#include <color_fragment>\nif (!gl_FrontFacing) diffuseColor.rgb *= 0.55;");
|
|
19857
|
+
};
|
|
19858
|
+
material.customProgramCacheKey = () => "breachCore";
|
|
19859
|
+
material.userData.breachCore = core;
|
|
19860
|
+
return material;
|
|
19861
|
+
}
|
|
19710
19862
|
function maskOf(hostId, parts) {
|
|
19711
19863
|
const host = structure.byId.get(hostId);
|
|
19712
19864
|
const [u0, v0, u1, v1] = host.aabb;
|
|
@@ -19722,41 +19874,73 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19722
19874
|
return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
|
|
19723
19875
|
}
|
|
19724
19876
|
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
19877
|
+
const ellipseDistance = (s, y, cs, cy, rs, ry) => (Math.hypot((s - cs) / Math.max(rs, 1e-4), (y - cy) / Math.max(ry, 1e-4)) - 1) * Math.min(rs, ry);
|
|
19878
|
+
function covered(c, boxes) {
|
|
19879
|
+
const n = 8;
|
|
19880
|
+
let hit = 0;
|
|
19881
|
+
for (let j = 0; j < n; j++) {
|
|
19882
|
+
const y = c.y0 + (j + 0.5) / n * (c.y1 - c.y0);
|
|
19883
|
+
for (let i = 0; i < n; i++) {
|
|
19884
|
+
const s = c.a + (i + 0.5) / n * (c.b - c.a);
|
|
19885
|
+
if (boxes.some((k) => s >= k.s0 && s <= k.s1 && y >= k.y0 && y <= k.y1)) hit++;
|
|
19886
|
+
}
|
|
19887
|
+
}
|
|
19888
|
+
return hit / (n * n);
|
|
19889
|
+
}
|
|
19725
19890
|
function paint(view) {
|
|
19726
|
-
const { mask } = view;
|
|
19891
|
+
const { mask, core } = view;
|
|
19727
19892
|
const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
|
|
19728
19893
|
const holes = [];
|
|
19894
|
+
const pits = [];
|
|
19729
19895
|
const discs = [];
|
|
19896
|
+
const flakes = [];
|
|
19730
19897
|
const shown = [];
|
|
19731
19898
|
for (const id of view.parts) {
|
|
19732
|
-
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id);
|
|
19899
|
+
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id), spalls = view.spalls.get(id);
|
|
19733
19900
|
const all = cells(part, units2);
|
|
19734
19901
|
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
19735
19902
|
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
19736
19903
|
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
19737
|
-
|
|
19904
|
+
for (const k of spalls) flakes.push({ s: along(k.center), y: k.center[1], r: k.radius });
|
|
19905
|
+
const boxes = chips.map((k) => ({ s0: along(k.center) - k.along, s1: along(k.center) + k.along, y0: k.center[1] - k.up, y1: k.center[1] + k.up }));
|
|
19906
|
+
const gone = (c) => covered(c, boxes.filter((k) => k.s1 > c.a && k.s0 < c.b && k.y1 > c.y0 && k.y0 < c.y1)) >= 0.9 || opening && Math.hypot((c.a + c.b) / 2 - along(opening.center), (c.y0 + c.y1) / 2 - opening.center[1]) < opening.radius;
|
|
19738
19907
|
const kept = [];
|
|
19739
19908
|
for (const c of all) (gone(c) ? holes : kept).push(c);
|
|
19740
19909
|
for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
|
|
19910
|
+
for (const k of boxes) {
|
|
19911
|
+
if (holes.some((h) => k.s0 >= h.a - 1e-4 && k.s1 <= h.b + 1e-4 && k.y0 >= h.y0 - 1e-4 && k.y1 <= h.y1 + 1e-4)) continue;
|
|
19912
|
+
pits.push({ s: (k.s0 + k.s1) / 2, y: (k.y0 + k.y1) / 2, rs: (k.s1 - k.s0) / 2, ry: (k.y1 - k.y0) / 2 });
|
|
19913
|
+
}
|
|
19741
19914
|
}
|
|
19742
19915
|
const reach = SPALL_MIN + SPALL_RAGGED;
|
|
19916
|
+
const ragged = (s, y) => 0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011);
|
|
19743
19917
|
mask.data.fill(0);
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
const
|
|
19918
|
+
core.data.fill(0);
|
|
19919
|
+
const stamp = (m, s0, s1, y0, y1, inside2) => {
|
|
19920
|
+
const i0 = Math.max(0, Math.floor((s0 - reach) / m.len * m.w)), i1 = Math.min(m.w - 1, Math.ceil((s1 + reach) / m.len * m.w));
|
|
19921
|
+
const j0 = Math.max(0, Math.floor((y0 - reach - m.y0) / m.height * m.h)), j1 = Math.min(m.h - 1, Math.ceil((y1 + reach - m.y0) / m.height * m.h));
|
|
19747
19922
|
for (let j = j0; j <= j1; j++) {
|
|
19748
|
-
const y =
|
|
19923
|
+
const y = m.y0 + (j + 0.5) / m.h * m.height;
|
|
19749
19924
|
for (let i = i0; i <= i1; i++) {
|
|
19750
|
-
const s = (i + 0.5) /
|
|
19751
|
-
|
|
19752
|
-
if (inside2(s, y) <= edge) mask.data[j * mask.w + i] = 255;
|
|
19925
|
+
const s = (i + 0.5) / m.w * m.len;
|
|
19926
|
+
if (inside2(s, y)) m.data[j * m.w + i] = 255;
|
|
19753
19927
|
}
|
|
19754
19928
|
}
|
|
19755
19929
|
};
|
|
19756
|
-
|
|
19757
|
-
for (const
|
|
19930
|
+
const plaster = (d, s, y) => d <= SPALL_MIN + SPALL_RAGGED * ragged(s, y);
|
|
19931
|
+
for (const c of holes) stamp(mask, c.a, c.b, c.y0, c.y1, (s, y) => plaster(boxDistance(s, y, c.a, c.b, c.y0, c.y1), s, y));
|
|
19932
|
+
for (const d of discs) stamp(mask, d.s - d.r, d.s + d.r, d.y - d.r, d.y + d.r, (s, y) => plaster(Math.max(0, Math.hypot(s - d.s, y - d.y) - d.r), s, y));
|
|
19933
|
+
for (const f of flakes) {
|
|
19934
|
+
const rough = (s, y) => Math.hypot(s - f.s, y - f.y) - f.r * (0.65 + 0.7 * ragged(s * 2.3, y * 2.3));
|
|
19935
|
+
stamp(mask, f.s - 2 * f.r, f.s + 2 * f.r, f.y - 2 * f.r, f.y + 2 * f.r, (s, y) => rough(s, y) <= 0);
|
|
19936
|
+
}
|
|
19937
|
+
for (const p of pits) {
|
|
19938
|
+
stamp(mask, p.s - p.rs, p.s + p.rs, p.y - p.ry, p.y + p.ry, (s, y) => plaster(Math.max(0, ellipseDistance(s, y, p.s, p.y, p.rs, p.ry)), s, y));
|
|
19939
|
+
stamp(core, p.s - p.rs, p.s + p.rs, p.y - p.ry, p.y + p.ry, (s, y) => ellipseDistance(s, y, p.s, p.y, p.rs, p.ry) <= -0.35 * Math.min(p.rs, p.ry) * noise2(s * 1.7, y * 1.3, 0.012));
|
|
19940
|
+
}
|
|
19758
19941
|
mask.texture.needsUpdate = true;
|
|
19759
|
-
|
|
19942
|
+
core.texture.needsUpdate = true;
|
|
19943
|
+
const near = (c) => holes.some((h) => boxDistance((c.a + c.b) / 2, (c.y0 + c.y1) / 2, h.a - (c.b - c.a) / 2, h.b + (c.b - c.a) / 2, h.y0 - (c.y1 - c.y0) / 2, h.y1 + (c.y1 - c.y0) / 2) <= reach) || discs.some((d) => boxDistance(d.s, d.y, c.a, c.b, c.y0, c.y1) <= d.r + reach) || pits.some((p) => boxDistance(p.s, p.y, c.a, c.b, c.y0, c.y1) <= Math.max(p.rs, p.ry) + reach) || flakes.some((f) => boxDistance(f.s, f.y, c.a, c.b, c.y0, c.y1) <= 2 * f.r);
|
|
19760
19944
|
const list2 = [];
|
|
19761
19945
|
const tone = (hex2, k) => tmp.c.setHex(hex2).multiplyScalar(k).getHex();
|
|
19762
19946
|
for (const { c, part, units: units2, thick, mid } of shown) {
|
|
@@ -19766,7 +19950,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19766
19950
|
const base = fragmentMaterial(part.material).color.getHex();
|
|
19767
19951
|
const n = noise2(centre * 3.1, y * 2.7, 0.07);
|
|
19768
19952
|
if (units2.mortar === "concrete") {
|
|
19769
|
-
at(len, h, thick - 2 * COVER2
|
|
19953
|
+
at(len, h, thick - 2 * COVER2, tone(base, 0.9 + 0.04 * n));
|
|
19770
19954
|
} else {
|
|
19771
19955
|
at(len, h, thick - 2 * COVER2 - 0.012, 10327948);
|
|
19772
19956
|
at(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
|
|
@@ -19776,7 +19960,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19776
19960
|
view.units.removeFromParent();
|
|
19777
19961
|
view.units.dispose();
|
|
19778
19962
|
}
|
|
19779
|
-
const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
19963
|
+
const units = new THREE.InstancedMesh(unitGeometry, view.unitMaterial, Math.max(1, list2.length));
|
|
19780
19964
|
units.name = "breach:" + view.host;
|
|
19781
19965
|
list2.forEach((u, i) => {
|
|
19782
19966
|
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
@@ -19792,6 +19976,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19792
19976
|
house.add(units);
|
|
19793
19977
|
view.units = units;
|
|
19794
19978
|
view.gone = holes.length;
|
|
19979
|
+
view.pits = pits.length;
|
|
19980
|
+
view.flakes = flakes.length;
|
|
19981
|
+
}
|
|
19982
|
+
function chipBox(chip, alongU) {
|
|
19983
|
+
house.updateMatrixWorld(true);
|
|
19984
|
+
const q = tmp.q.set(chip.rotation[0], chip.rotation[1], chip.rotation[2], chip.rotation[3]).premultiply(house.getWorldQuaternion(new THREE.Quaternion()).invert());
|
|
19985
|
+
let a = 0, u = 0;
|
|
19986
|
+
for (let k = 0; k < 3; k++) {
|
|
19987
|
+
const axis = tmp.p.set(k === 0 ? 1 : 0, k === 1 ? 1 : 0, k === 2 ? 1 : 0).applyQuaternion(q);
|
|
19988
|
+
a += Math.abs(alongU ? axis.x : axis.z) * chip.size[k] / 2;
|
|
19989
|
+
u += Math.abs(axis.y) * chip.size[k] / 2;
|
|
19990
|
+
}
|
|
19991
|
+
return { center: local(chip.center), along: a, up: u };
|
|
19795
19992
|
}
|
|
19796
19993
|
function draw2(partId, change) {
|
|
19797
19994
|
if (lod !== 0) return false;
|
|
@@ -19803,23 +20000,49 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19803
20000
|
if (!items.length) return false;
|
|
19804
20001
|
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
19805
20002
|
if (!parts.includes(partId)) return false;
|
|
19806
|
-
const mask = maskOf(hostId, parts);
|
|
19807
|
-
view = {
|
|
20003
|
+
const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
|
|
20004
|
+
view = {
|
|
20005
|
+
host: hostId,
|
|
20006
|
+
parts,
|
|
20007
|
+
items,
|
|
20008
|
+
mask,
|
|
20009
|
+
core,
|
|
20010
|
+
unitMaterial: unitsMaterial(core),
|
|
20011
|
+
shells: [],
|
|
20012
|
+
saved: null,
|
|
20013
|
+
units: null,
|
|
20014
|
+
gone: 0,
|
|
20015
|
+
pits: 0,
|
|
20016
|
+
chips: new Map(parts.map((id) => [id, []])),
|
|
20017
|
+
spalls: new Map(parts.map((id) => [id, []])),
|
|
20018
|
+
openings: /* @__PURE__ */ new Map(),
|
|
20019
|
+
flakes: 0
|
|
20020
|
+
};
|
|
19808
20021
|
}
|
|
19809
20022
|
if (!view.chips.has(partId)) return false;
|
|
19810
20023
|
if (change.opening) {
|
|
19811
20024
|
if (view.openings.has(partId)) return false;
|
|
19812
20025
|
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
19813
20026
|
}
|
|
19814
|
-
if (change.chip) view.chips.get(partId).push(
|
|
20027
|
+
if (change.chip) view.chips.get(partId).push(chipBox(change.chip, view.mask.alongU));
|
|
20028
|
+
if (change.spall) {
|
|
20029
|
+
const list2 = view.spalls.get(partId);
|
|
20030
|
+
list2.push({ center: local(change.spall.center), radius: change.spall.radius });
|
|
20031
|
+
if (list2.length > MAX_FLAKES) list2.shift();
|
|
20032
|
+
}
|
|
19815
20033
|
if (!view.saved) {
|
|
19816
20034
|
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
20035
|
+
for (const m of view.shells) m.userData.behind = () => view.units;
|
|
19817
20036
|
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
19818
20037
|
}
|
|
19819
|
-
paint(view);
|
|
19820
20038
|
views.set(hostId, view);
|
|
19821
|
-
|
|
19822
|
-
|
|
20039
|
+
if (opts.deferPaint && change.spall) dirty.add(view);
|
|
20040
|
+
else {
|
|
20041
|
+
dirty.delete(view);
|
|
20042
|
+
paint(view);
|
|
20043
|
+
invalidate();
|
|
20044
|
+
invalidateShadow();
|
|
20045
|
+
}
|
|
19823
20046
|
return true;
|
|
19824
20047
|
}
|
|
19825
20048
|
function plain2(view, shown) {
|
|
@@ -19828,24 +20051,70 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19828
20051
|
else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
|
|
19829
20052
|
}
|
|
19830
20053
|
}
|
|
20054
|
+
function flush(budgetMs = paintBudgetMs) {
|
|
20055
|
+
if (dirty.size === 0) return 0;
|
|
20056
|
+
const deadline = performance.now() + budgetMs;
|
|
20057
|
+
for (const view of dirty) {
|
|
20058
|
+
dirty.delete(view);
|
|
20059
|
+
paint(view);
|
|
20060
|
+
if (performance.now() >= deadline) break;
|
|
20061
|
+
}
|
|
20062
|
+
invalidate();
|
|
20063
|
+
invalidateShadow();
|
|
20064
|
+
return dirty.size;
|
|
20065
|
+
}
|
|
19831
20066
|
function dispose(view) {
|
|
19832
|
-
|
|
19833
|
-
view.units
|
|
20067
|
+
dirty.delete(view);
|
|
20068
|
+
if (view.units) {
|
|
20069
|
+
view.units.removeFromParent();
|
|
20070
|
+
view.units.dispose();
|
|
20071
|
+
}
|
|
20072
|
+
view.unitMaterial.dispose();
|
|
19834
20073
|
for (const m of view.shells) {
|
|
19835
20074
|
m.removeFromParent();
|
|
19836
20075
|
m.geometry.dispose();
|
|
19837
20076
|
m.material.dispose();
|
|
19838
20077
|
}
|
|
19839
20078
|
view.mask.texture.dispose();
|
|
20079
|
+
view.core.texture.dispose();
|
|
19840
20080
|
}
|
|
19841
20081
|
return {
|
|
19842
20082
|
/** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
|
|
19843
20083
|
stage(event) {
|
|
19844
20084
|
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
19845
20085
|
},
|
|
19846
|
-
/**
|
|
20086
|
+
/**
|
|
20087
|
+
* Handles a `spall` of a wall of units: the coat comes off in a ragged patch and the units show under it, drawn by the house (the destruction
|
|
20088
|
+
* renderer leaves its flat patch out on the meshes `claim` marked). Returns true when the wall was cut.
|
|
20089
|
+
*/
|
|
20090
|
+
spall(event) {
|
|
20091
|
+
const r = Math.sqrt(Math.max(0, event.area) / Math.PI);
|
|
20092
|
+
return r > 0 ? draw2(event.part, { spall: { center: event.point, radius: r } }) : false;
|
|
20093
|
+
},
|
|
20094
|
+
/**
|
|
20095
|
+
* Marks the meshes that draw a wall of units as drawing its spalls itself (`userData.drawnSpalls`, the part ids): the destruction renderer then
|
|
20096
|
+
* throws the bits and leaves its patch out. Call it once the spec is known.
|
|
20097
|
+
*/
|
|
20098
|
+
claim() {
|
|
20099
|
+
var _a;
|
|
20100
|
+
for (const q of opts.spec()?.parts ?? []) {
|
|
20101
|
+
if (!q.units) continue;
|
|
20102
|
+
const host = hostOf(q.id);
|
|
20103
|
+
if (!host) continue;
|
|
20104
|
+
for (const it of structure.meshesOf(host)) {
|
|
20105
|
+
if (!it.mesh) continue;
|
|
20106
|
+
const list2 = (_a = it.mesh.userData).drawnSpalls ?? (_a.drawnSpalls = []);
|
|
20107
|
+
if (!list2.includes(q.id)) list2.push(q.id);
|
|
20108
|
+
}
|
|
20109
|
+
}
|
|
20110
|
+
},
|
|
20111
|
+
/**
|
|
20112
|
+
* Handles a `chip` event: what came out (a unit, or a crater a shot broke through one) leaves a hole in the wall where it was; a `torn` chip (what a blow
|
|
20113
|
+
* tore off a face) leaves the marks the renderer drew. Returns true when the wall was cut.
|
|
20114
|
+
*/
|
|
19847
20115
|
chip(event) {
|
|
19848
|
-
|
|
20116
|
+
if (event.torn) return false;
|
|
20117
|
+
return draw2(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation: event.rotation ?? [0, 0, 0, 1] } });
|
|
19849
20118
|
},
|
|
19850
20119
|
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
19851
20120
|
drop(partId) {
|
|
@@ -19853,9 +20122,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19853
20122
|
if (!v) return;
|
|
19854
20123
|
dispose(v);
|
|
19855
20124
|
views.delete(partId);
|
|
20125
|
+
if (v.saved) dropped.push(v);
|
|
19856
20126
|
},
|
|
19857
20127
|
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
19858
20128
|
setLod(level) {
|
|
20129
|
+
flush(Infinity);
|
|
19859
20130
|
const was = lod;
|
|
19860
20131
|
lod = level;
|
|
19861
20132
|
if (was === 0 === (level === 0)) return;
|
|
@@ -19874,15 +20145,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19874
20145
|
if (lod === 0) plain2(v, true);
|
|
19875
20146
|
}
|
|
19876
20147
|
views.clear();
|
|
20148
|
+
for (const v of dropped) plain2(v, true);
|
|
20149
|
+
dropped.length = 0;
|
|
19877
20150
|
},
|
|
20151
|
+
flush,
|
|
19878
20152
|
/** The wall has damage drawn. */
|
|
19879
20153
|
has(partId) {
|
|
19880
20154
|
return views.has(partId);
|
|
19881
20155
|
},
|
|
19882
|
-
/** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
|
|
19883
|
-
statistics: () => ({
|
|
20156
|
+
/** Walls with damage drawn, units out of them, craters shots broke through units that stay, and units laid bare at the edge of the cuts. */
|
|
20157
|
+
statistics: () => (flush(Infinity), {
|
|
19884
20158
|
walls: views.size,
|
|
19885
20159
|
gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
|
|
20160
|
+
craters: [...views.values()].reduce((n, v) => n + v.pits, 0),
|
|
20161
|
+
flakes: [...views.values()].reduce((n, v) => n + v.flakes, 0),
|
|
19886
20162
|
units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
|
|
19887
20163
|
})
|
|
19888
20164
|
};
|
|
@@ -19891,6 +20167,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19891
20167
|
}
|
|
19892
20168
|
|
|
19893
20169
|
// src/destruction/world.ts
|
|
20170
|
+
var FOLLOW_FLUSH_FALLBACK_MS = 100;
|
|
19894
20171
|
function installDestructionWorld(K, shared) {
|
|
19895
20172
|
const { THREE, cfg, invalidateShadow, invalidate, rngString, FRAGMENT_GEO, fragmentMaterial, createBreachView } = K;
|
|
19896
20173
|
function createHouseDestruction(root, house, structure, parts, colliders, water) {
|
|
@@ -19899,7 +20176,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19899
20176
|
const R = cfg.destructionRenderer || null, listeners = /* @__PURE__ */ new Set();
|
|
19900
20177
|
D.renderer = R;
|
|
19901
20178
|
D.spec = null;
|
|
19902
|
-
const breach = createBreachView({ house, structure, spec: () => D.spec });
|
|
20179
|
+
const breach = createBreachView({ house, structure, spec: () => D.spec, deferPaint: true, ...cfg.breachPaintBudgetMs !== void 0 ? { paintBudgetMs: cfg.breachPaintBudgetMs } : {} });
|
|
19903
20180
|
D.breach = breach;
|
|
19904
20181
|
D.onEvent = (fn) => {
|
|
19905
20182
|
listeners.add(fn);
|
|
@@ -20084,6 +20361,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20084
20361
|
case "chip":
|
|
20085
20362
|
breach.chip(e);
|
|
20086
20363
|
break;
|
|
20364
|
+
case "spall":
|
|
20365
|
+
breach.spall(e);
|
|
20366
|
+
break;
|
|
20087
20367
|
case "collapse":
|
|
20088
20368
|
for (const id of e.parts) {
|
|
20089
20369
|
breach.drop(id);
|
|
@@ -20125,7 +20405,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20125
20405
|
D.specOptions = specOptions || null;
|
|
20126
20406
|
D.spec = structure.spec(specOptions);
|
|
20127
20407
|
D.id = D.world.add(D.spec, { id: "house3d:" + ++shared.destructionSeq, position: [p.x, p.y, p.z], rotation: [q.x, q.y, q.z, q.w] });
|
|
20128
|
-
|
|
20408
|
+
breach.claim();
|
|
20409
|
+
if (R) R.bind(D.id, house, { parts: rangesOf(), onFrame: () => breach.flush() });
|
|
20129
20410
|
off = D.world.on("*", handleEvent);
|
|
20130
20411
|
D.active = true;
|
|
20131
20412
|
return D;
|
|
@@ -20140,6 +20421,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20140
20421
|
};
|
|
20141
20422
|
D.follow = (world, id, spec) => {
|
|
20142
20423
|
D.spec = spec;
|
|
20424
|
+
breach.claim();
|
|
20425
|
+
let fallback = null;
|
|
20143
20426
|
const offFollow = world.on("*", (e) => {
|
|
20144
20427
|
if (e.object !== id) return;
|
|
20145
20428
|
switch (e.type) {
|
|
@@ -20149,6 +20432,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20149
20432
|
case "chip":
|
|
20150
20433
|
breach.chip(e);
|
|
20151
20434
|
break;
|
|
20435
|
+
case "spall":
|
|
20436
|
+
breach.spall(e);
|
|
20437
|
+
break;
|
|
20152
20438
|
case "fracture":
|
|
20153
20439
|
breach.drop(e.part);
|
|
20154
20440
|
break;
|
|
@@ -20158,10 +20444,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20158
20444
|
default:
|
|
20159
20445
|
break;
|
|
20160
20446
|
}
|
|
20447
|
+
if (!fallback && typeof setTimeout === "function") fallback = setTimeout(() => {
|
|
20448
|
+
fallback = null;
|
|
20449
|
+
breach.flush(Infinity);
|
|
20450
|
+
}, FOLLOW_FLUSH_FALLBACK_MS);
|
|
20161
20451
|
});
|
|
20162
20452
|
return { restore: () => breach.restore(), dispose: () => {
|
|
20163
20453
|
offFollow();
|
|
20164
20454
|
breach.restore();
|
|
20455
|
+
}, update: () => {
|
|
20456
|
+
breach.flush();
|
|
20165
20457
|
} };
|
|
20166
20458
|
};
|
|
20167
20459
|
D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
|
|
@@ -20194,6 +20486,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
20194
20486
|
return r;
|
|
20195
20487
|
};
|
|
20196
20488
|
D.animate = (dt) => {
|
|
20489
|
+
breach.flush();
|
|
20197
20490
|
if (R) return;
|
|
20198
20491
|
let moved = false;
|
|
20199
20492
|
for (const [id, list2] of bodies) {
|
|
@@ -21658,6 +21951,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
21658
21951
|
houseRenderer = create3(THREE, {
|
|
21659
21952
|
locale,
|
|
21660
21953
|
lod: { interior: 10, hide: 40 },
|
|
21954
|
+
breachPaintBudgetMs: 4,
|
|
21661
21955
|
...options.house,
|
|
21662
21956
|
quality: settings.quality,
|
|
21663
21957
|
...options.world ? { destruction: options.world } : {},
|
|
@@ -21813,9 +22107,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
21813
22107
|
host?.log(text.steps.no_door);
|
|
21814
22108
|
return;
|
|
21815
22109
|
}
|
|
21816
|
-
|
|
22110
|
+
const fo = target.target;
|
|
22111
|
+
const reach = fo && host?.reachFor && fo.type === "swing" && fo.gs?.[0] && fo.handles?.[0] ? doorReach(p, fo) : null;
|
|
22112
|
+
if (reach && host?.reachFor) {
|
|
22113
|
+
host.reachFor({ ...reach, onGrip: () => void p.house.toggleDoor(fo) });
|
|
22114
|
+
} else p.house.interact(eyeOf(p));
|
|
21817
22115
|
host?.log(fill(text.steps.door, { text: String(target.text) }));
|
|
21818
22116
|
};
|
|
22117
|
+
const doorReach = (p, fo) => {
|
|
22118
|
+
const g = fo.gs[0];
|
|
22119
|
+
const pivot = fo.handles[0].pivot;
|
|
22120
|
+
const at = (angle) => {
|
|
22121
|
+
const c = Math.cos(angle);
|
|
22122
|
+
const s = Math.sin(angle);
|
|
22123
|
+
const v = new THREE.Vector3(pivot[0] * c + pivot[2] * s, pivot[1], -pivot[0] * s + pivot[2] * c).add(g.position);
|
|
22124
|
+
return g.parent ? g.parent.localToWorld(v) : v;
|
|
22125
|
+
};
|
|
22126
|
+
const camera = host?.camera;
|
|
22127
|
+
const eye = new THREE.Vector3(camera.position.x, 0, camera.position.z);
|
|
22128
|
+
const flat = (v) => Math.hypot(v.x - eye.x, v.z - eye.z);
|
|
22129
|
+
const closed = at(0);
|
|
22130
|
+
const open = at(fo.rot);
|
|
22131
|
+
const opening = !fo.open;
|
|
22132
|
+
return {
|
|
22133
|
+
handle: () => {
|
|
22134
|
+
g.updateMatrixWorld(true);
|
|
22135
|
+
return g.localToWorld(new THREE.Vector3(pivot[0], pivot[1], pivot[2])).toArray();
|
|
22136
|
+
},
|
|
22137
|
+
hinge: () => g.getWorldPosition(new THREE.Vector3()).toArray(),
|
|
22138
|
+
seconds: fo.locked || fo.blockedFlag ? 0.6 : 0.55,
|
|
22139
|
+
pull: opening ? flat(open) <= flat(closed) : flat(closed) <= flat(open),
|
|
22140
|
+
locked: !!(fo.locked || fo.blockedFlag)
|
|
22141
|
+
};
|
|
22142
|
+
};
|
|
21819
22143
|
const strike = () => {
|
|
21820
22144
|
const world = options.world;
|
|
21821
22145
|
if (!world || !host) return;
|