@adatechnology/game-floorplan-three 0.4.2 → 0.5.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 +11 -1
- package/dist/index.global.js +285 -68
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +98 -24
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -599,7 +599,8 @@ interface FurnitureSpecResult {
|
|
|
599
599
|
* Materials: `wood` is `wood_hard`, and `wood_soft` for thin boards and panels (under 3.5 cm); `metal` is `steel`; `fabric`, `glass` and `plant` keep
|
|
600
600
|
* their name; `plain` is `plastic`, or `paper` for shelves and racks (their books and boxes). Parts that touch the floor are anchored. Joints come from
|
|
601
601
|
* `inferJoints` (screws between wood, welds or bolts between metal...), except that paper rests on what it lies on with a `resting` joint (weak: it falls when
|
|
602
|
-
* the board breaks) and is not joined to its neighbours.
|
|
602
|
+
* the board breaks) and is not joined to its neighbours. Shelves and racks stand as a carcass: the sides carry, each shelf needs both of its ends
|
|
603
|
+
* held, and the back panel carries nothing, so a broken side drops the shelves it held, with their books.
|
|
603
604
|
*
|
|
604
605
|
* @example
|
|
605
606
|
* const { spec, build, top } = furnitureSpec(THREE, 'bookcase') // 1.2 x 0.35 x 2 m, boards and books
|
|
@@ -648,6 +649,15 @@ interface WallPiece {
|
|
|
648
649
|
readonly spec: any;
|
|
649
650
|
/** Draws the wall with the house renderer: meshes carry `userData.destructionPart` of their part, in the space of the spec. */
|
|
650
651
|
build(THREE: any): any;
|
|
652
|
+
/**
|
|
653
|
+
* Once a drawing from `build` is in a destruction world as `objectId` (with this `spec`), the house renderer follows its events and draws on it what
|
|
654
|
+
* a generic destruction renderer cannot: the bricks that shots and blows knock out (`chip`) and the opening of a breach, as gaps in the wall's
|
|
655
|
+
* courses. `restore` puts the wall back (after `world.reset`), `dispose` stops following.
|
|
656
|
+
*/
|
|
657
|
+
follow(root: any, world: any, objectId: string): {
|
|
658
|
+
restore(): void;
|
|
659
|
+
dispose(): void;
|
|
660
|
+
} | null;
|
|
651
661
|
}
|
|
652
662
|
/**
|
|
653
663
|
* The plan of a free-standing wall along x, centred on the origin: a one-storey plan whose only wall is exterior on both sides, with the masonry construction of
|
package/dist/index.global.js
CHANGED
|
@@ -3551,8 +3551,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
3551
3551
|
var OPEN_ROOM_TYPES = /* @__PURE__ */ new Set(["balcony", "terrace", "veranda", "balcony_gourmet"]);
|
|
3552
3552
|
var rectsOf = (c) => c.parts && c.parts.length ? c.parts : [[c.u0, c.v0, c.u1, c.v1]];
|
|
3553
3553
|
var isOpenArea = (c) => OPEN_ROOM_TYPES.has(c.type) || c.outdoor === true;
|
|
3554
|
-
function planLod(
|
|
3555
|
-
const l =
|
|
3554
|
+
function planLod(houses2, focus, budget) {
|
|
3555
|
+
const l = houses2.map((c) => ({ c, d: c.distance(focus), n0: c.stats.drawCalls, n1: c.stats.drawCallsLOD1, level: 0 })).sort((a, b) => a.d - b.d);
|
|
3556
3556
|
let total = l.reduce((s, x) => s + x.n0, 0);
|
|
3557
3557
|
for (let i = l.length - 1; i >= 0 && total > budget; i--) {
|
|
3558
3558
|
total -= l[i].n0 - l[i].n1;
|
|
@@ -10695,12 +10695,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
10695
10695
|
A.invalidate = () => {
|
|
10696
10696
|
for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
|
|
10697
10697
|
};
|
|
10698
|
-
A.update = function(
|
|
10698
|
+
A.update = function(houses2, focus, op) {
|
|
10699
10699
|
op = op || {};
|
|
10700
|
-
|
|
10700
|
+
houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
|
|
10701
10701
|
_f.set(focus.x, focus.y, focus.z);
|
|
10702
10702
|
const rooms = [], exterior = [];
|
|
10703
|
-
for (const house of
|
|
10703
|
+
for (const house of houses2) {
|
|
10704
10704
|
if (house.group && !house.group.visible) continue;
|
|
10705
10705
|
house.house.updateMatrixWorld(true);
|
|
10706
10706
|
const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
|
|
@@ -11232,6 +11232,45 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11232
11232
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
11233
11233
|
};
|
|
11234
11234
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
11235
|
+
var splinterBox = (inp, box3, limit = inp.maxFragments) => {
|
|
11236
|
+
const { rnd } = inp;
|
|
11237
|
+
const dims = box3.s;
|
|
11238
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
11239
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
11240
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
11241
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
11242
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
11243
|
+
const maxLen = fmax * 4;
|
|
11244
|
+
const cells = bsp(box3, c, [a, b], rnd);
|
|
11245
|
+
const pieces = [];
|
|
11246
|
+
for (const cell of cells) {
|
|
11247
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
11248
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
11249
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
11250
|
+
}
|
|
11251
|
+
return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
|
|
11252
|
+
};
|
|
11253
|
+
var severAt = (inp, root, at, gap) => {
|
|
11254
|
+
const dims = root.s;
|
|
11255
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
11256
|
+
const half = dims[L] / 2;
|
|
11257
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
11258
|
+
const out = [];
|
|
11259
|
+
const stub = (from, to) => {
|
|
11260
|
+
if (to - from < 0.02) return;
|
|
11261
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
11262
|
+
c[L] = (from + to) / 2;
|
|
11263
|
+
sz[L] = to - from;
|
|
11264
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
11265
|
+
};
|
|
11266
|
+
stub(-half, lo);
|
|
11267
|
+
stub(hi, half);
|
|
11268
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
11269
|
+
band.c[L] = (lo + hi) / 2;
|
|
11270
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
11271
|
+
for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
11272
|
+
return out;
|
|
11273
|
+
};
|
|
11235
11274
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
11236
11275
|
const { dims, rnd, maxFragments } = inp;
|
|
11237
11276
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -11270,23 +11309,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
11270
11309
|
return out;
|
|
11271
11310
|
}
|
|
11272
11311
|
if (style === "splinter") {
|
|
11273
|
-
|
|
11274
|
-
const
|
|
11275
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
11276
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
11277
|
-
const maxLen = fmax * 4;
|
|
11278
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
11279
|
-
const pieces = [];
|
|
11280
|
-
for (const cell of cells) {
|
|
11281
|
-
let list2 = [cell];
|
|
11282
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
11283
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
11284
|
-
list2 = bsp(cell, k, [g], rnd);
|
|
11285
|
-
for (const p of list2) pieces.push(p);
|
|
11286
|
-
}
|
|
11287
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
11288
|
-
out.push({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] });
|
|
11289
|
-
}
|
|
11312
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
11313
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
11290
11314
|
return out;
|
|
11291
11315
|
}
|
|
11292
11316
|
if (style === "tear") {
|
|
@@ -13274,9 +13298,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13274
13298
|
var BREACH_AREA = 0.25;
|
|
13275
13299
|
var BREACH_BLAST_REACH = 0.12;
|
|
13276
13300
|
var RESIDUAL_CAPACITY = 0.15;
|
|
13301
|
+
var SEVER_BAND = 0.1;
|
|
13302
|
+
var SEVER_ASPECT = 3;
|
|
13303
|
+
var SEVER_BALLISTIC = 2;
|
|
13277
13304
|
var SUPPORT_TOLERANCE = 0.05;
|
|
13278
13305
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
13279
13306
|
var BREACH_GRID_CAP = 4096;
|
|
13307
|
+
var CHIP_ENERGY = 200;
|
|
13308
|
+
var CHIP_SPLASH = 0.5;
|
|
13280
13309
|
var CUT_SLICE = 1;
|
|
13281
13310
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
13282
13311
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -13320,6 +13349,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13320
13349
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
13321
13350
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
13322
13351
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
13352
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
13323
13353
|
const rng2 = createRng(options.seed ?? 1);
|
|
13324
13354
|
const rnd = () => rng2.next();
|
|
13325
13355
|
let time = 0;
|
|
@@ -13693,41 +13723,68 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13693
13723
|
markDamaged(p);
|
|
13694
13724
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
13695
13725
|
};
|
|
13726
|
+
const freeSpan = (p, supports) => {
|
|
13727
|
+
if (supports.length === 0) return Infinity;
|
|
13728
|
+
const d = p.dims;
|
|
13729
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
13730
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
13731
|
+
let far = 0;
|
|
13732
|
+
for (let i = 0; i <= 16; i++) {
|
|
13733
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
13734
|
+
let near = Infinity;
|
|
13735
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
13736
|
+
far = Math.max(far, near);
|
|
13737
|
+
}
|
|
13738
|
+
return 2 * far;
|
|
13739
|
+
};
|
|
13696
13740
|
const solveStructure = (o) => {
|
|
13697
13741
|
o.maxStress = 0;
|
|
13698
13742
|
if (!o.hasStructure) return;
|
|
13699
13743
|
const parts = o.parts;
|
|
13700
13744
|
const n = parts.length;
|
|
13701
13745
|
const dist = new Array(n).fill(-1);
|
|
13702
|
-
const queue = [];
|
|
13703
13746
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
13704
13747
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
if (
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13748
|
+
const out = /* @__PURE__ */ new Set();
|
|
13749
|
+
let queue = [];
|
|
13750
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
13751
|
+
for (; ; ) {
|
|
13752
|
+
dist.fill(-1);
|
|
13753
|
+
queue = [];
|
|
13754
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
13755
|
+
dist[i] = 0;
|
|
13756
|
+
queue.push(i);
|
|
13757
|
+
}
|
|
13758
|
+
for (let h = 0; h < queue.length; h++) {
|
|
13759
|
+
const s = queue[h];
|
|
13760
|
+
if (isPartition(s)) continue;
|
|
13761
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
13762
|
+
if (j.st.open || !j.bears) continue;
|
|
13763
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
13764
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
13765
|
+
dist[r] = dist[s] + 1;
|
|
13766
|
+
queue.push(r);
|
|
13767
|
+
}
|
|
13718
13768
|
}
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13769
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
13770
|
+
let dropped = false;
|
|
13771
|
+
for (const i of queue) {
|
|
13772
|
+
const lower = [];
|
|
13773
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
13774
|
+
if (j.st.open || !j.bears) continue;
|
|
13775
|
+
const k = restsOn(o, j, i);
|
|
13776
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
13777
|
+
}
|
|
13778
|
+
lowerOf.set(i, lower);
|
|
13779
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
13780
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
13781
|
+
out.add(i);
|
|
13782
|
+
dropped = true;
|
|
13783
|
+
}
|
|
13728
13784
|
}
|
|
13729
|
-
|
|
13785
|
+
if (!dropped) break;
|
|
13730
13786
|
}
|
|
13787
|
+
const incoming = new Array(n).fill(0);
|
|
13731
13788
|
const bending = new Array(n).fill(0);
|
|
13732
13789
|
const through = parts.map(() => []);
|
|
13733
13790
|
const pass = (k, at, mass) => {
|
|
@@ -14239,6 +14296,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14239
14296
|
const r = rayPart(o, p, origin, d);
|
|
14240
14297
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
14241
14298
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
14299
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
14242
14300
|
if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
|
|
14243
14301
|
}
|
|
14244
14302
|
}
|
|
@@ -14502,6 +14560,62 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14502
14560
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
14503
14561
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
14504
14562
|
};
|
|
14563
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
14564
|
+
const gridOf = (p) => {
|
|
14565
|
+
let grid = unitGrids.get(p);
|
|
14566
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
|
|
14567
|
+
return grid;
|
|
14568
|
+
};
|
|
14569
|
+
const insideChip = (p, objectPoint) => {
|
|
14570
|
+
const cells = p.st.chipped;
|
|
14571
|
+
if (!cells?.length) return false;
|
|
14572
|
+
const [u, v] = planeAxes(p.thin);
|
|
14573
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
14574
|
+
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);
|
|
14575
|
+
};
|
|
14576
|
+
const chipUnits = (o, p, wp, energy, dir, radius, src) => {
|
|
14577
|
+
var _a, _b;
|
|
14578
|
+
if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
14579
|
+
const [u, v] = planeAxes(p.thin);
|
|
14580
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
14581
|
+
const grid = gridOf(p);
|
|
14582
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
14583
|
+
const out = [];
|
|
14584
|
+
for (let i = 0; i < grid.length; i++) {
|
|
14585
|
+
const cell = grid[i];
|
|
14586
|
+
if (worn[i] === -1) continue;
|
|
14587
|
+
const gap = Math.hypot(Math.max(0, Math.abs(lp[u] - cell.center[u]) - cell.size[u] / 2), Math.max(0, Math.abs(lp[v] - cell.center[v]) - cell.size[v] / 2));
|
|
14588
|
+
if (gap > radius) continue;
|
|
14589
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
14590
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
14591
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
14592
|
+
if (total < chipEnergy * mass) {
|
|
14593
|
+
worn[i] = total;
|
|
14594
|
+
continue;
|
|
14595
|
+
}
|
|
14596
|
+
worn[i] = -1;
|
|
14597
|
+
out.push(cell);
|
|
14598
|
+
}
|
|
14599
|
+
if (out.length === 0) return;
|
|
14600
|
+
const { c, q } = partXf(o, p);
|
|
14601
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
14602
|
+
const holes = localHolesOf(p);
|
|
14603
|
+
for (const cell of out) {
|
|
14604
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
14605
|
+
const center = toWorld(c, q, cell.center);
|
|
14606
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
14607
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
14608
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
14609
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
14610
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
14611
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
14612
|
+
markDamaged(p);
|
|
14613
|
+
setStage(o, p, 1, {}, src);
|
|
14614
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
14615
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
14616
|
+
}
|
|
14617
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
14618
|
+
};
|
|
14505
14619
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
14506
14620
|
const [u, v] = planeAxes(p.thin);
|
|
14507
14621
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -14513,7 +14627,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14513
14627
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
14514
14628
|
return;
|
|
14515
14629
|
}
|
|
14516
|
-
const
|
|
14630
|
+
const worn = p.rt.chips;
|
|
14631
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
14517
14632
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
14518
14633
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
14519
14634
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -14597,6 +14712,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14597
14712
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
14598
14713
|
const hitLocal = toLocal(c, q, hitW);
|
|
14599
14714
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
14715
|
+
const axis = style === "splinter" ? severAxis(p) : -1;
|
|
14716
|
+
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
14600
14717
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
14601
14718
|
style,
|
|
14602
14719
|
dims: p.dims,
|
|
@@ -14606,7 +14723,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14606
14723
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
14607
14724
|
maxFragments,
|
|
14608
14725
|
rnd,
|
|
14609
|
-
sheet: p.mat.sheet
|
|
14726
|
+
sheet: p.mat.sheet,
|
|
14727
|
+
...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
|
|
14610
14728
|
});
|
|
14611
14729
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
14612
14730
|
const mass = p.st.mass;
|
|
@@ -14665,10 +14783,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14665
14783
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
14666
14784
|
};
|
|
14667
14785
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
14786
|
+
const severAxis = (p) => {
|
|
14787
|
+
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
14788
|
+
const d = p.dims;
|
|
14789
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
14790
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
14791
|
+
};
|
|
14792
|
+
const bandOf = (o, p, axis, origin) => {
|
|
14793
|
+
const { c, q } = partXf(o, p);
|
|
14794
|
+
const along = toLocal(c, q, origin)[axis];
|
|
14795
|
+
const L = p.dims[axis];
|
|
14796
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
14797
|
+
};
|
|
14668
14798
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
14799
|
+
var _a;
|
|
14669
14800
|
if (isLost(p) || amount <= 0) return 0;
|
|
14670
14801
|
const prev = p.st.hp;
|
|
14671
14802
|
p.st.hp -= amount;
|
|
14803
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
14804
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
14805
|
+
const band = bandOf(o, p, axis, origin);
|
|
14806
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
14807
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
14808
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
14809
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
14810
|
+
p.st.hp = 0;
|
|
14811
|
+
}
|
|
14812
|
+
}
|
|
14672
14813
|
markDamaged(p);
|
|
14673
14814
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
14674
14815
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -14707,7 +14848,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14707
14848
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
14708
14849
|
const mat = p.mat;
|
|
14709
14850
|
const piercing = round3.piercing ?? 1;
|
|
14710
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
|
|
14851
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
|
|
14711
14852
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
14712
14853
|
}
|
|
14713
14854
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -14766,6 +14907,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14766
14907
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
14767
14908
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
14768
14909
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
|
|
14910
|
+
chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
|
|
14769
14911
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
14770
14912
|
};
|
|
14771
14913
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -14793,6 +14935,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14793
14935
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
14794
14936
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
14795
14937
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
14938
|
+
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
14796
14939
|
return done;
|
|
14797
14940
|
};
|
|
14798
14941
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -19106,7 +19249,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19106
19249
|
}
|
|
19107
19250
|
return null;
|
|
19108
19251
|
};
|
|
19109
|
-
|
|
19252
|
+
const local = (p) => {
|
|
19253
|
+
house.updateMatrixWorld(true);
|
|
19254
|
+
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
19255
|
+
return [v.x, v.y, v.z];
|
|
19256
|
+
};
|
|
19257
|
+
function courses(part, units, opening, chips, paint) {
|
|
19110
19258
|
const [u0, v0, u1, v1] = part.aabb;
|
|
19111
19259
|
const alongU = u1 - u0 >= v1 - v0;
|
|
19112
19260
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
@@ -19130,23 +19278,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19130
19278
|
const x = alongU ? u0 + mid : (u0 + u1) / 2;
|
|
19131
19279
|
const z = alongU ? (v0 + v1) / 2 : v0 + mid;
|
|
19132
19280
|
const y = part.y0 + (r + 0.5) * rowH;
|
|
19133
|
-
const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
|
|
19134
|
-
if (d < opening.radius) continue;
|
|
19135
|
-
|
|
19281
|
+
const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
|
|
19282
|
+
if (opening && d < opening.radius) continue;
|
|
19283
|
+
const gone = (c) => Math.abs(c[1] - y) <= rowH / 2 && (alongU ? c[0] >= u0 + a && c[0] <= u0 + b : c[2] >= v0 + a && c[2] <= v0 + b);
|
|
19284
|
+
if (chips.some(gone)) continue;
|
|
19285
|
+
const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
|
|
19286
|
+
list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
|
|
19136
19287
|
}
|
|
19137
19288
|
}
|
|
19138
19289
|
return list2;
|
|
19139
19290
|
}
|
|
19140
|
-
function
|
|
19141
|
-
if (lod !== 0
|
|
19142
|
-
const part = structure.byId.get(
|
|
19143
|
-
const spec = opts.spec()?.parts?.find((q) => q.id ===
|
|
19291
|
+
function draw2(partId, change) {
|
|
19292
|
+
if (lod !== 0) return false;
|
|
19293
|
+
const part = structure.byId.get(partId);
|
|
19294
|
+
const spec = opts.spec()?.parts?.find((q) => q.id === partId);
|
|
19144
19295
|
if (!part || !spec || !spec.units) return false;
|
|
19145
|
-
|
|
19146
|
-
if (!
|
|
19147
|
-
|
|
19296
|
+
let view = views.get(partId);
|
|
19297
|
+
if (!view) {
|
|
19298
|
+
const items = structure.meshesOf(partId).filter((it) => it.mesh);
|
|
19299
|
+
if (!items.length) return false;
|
|
19300
|
+
view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: [] };
|
|
19301
|
+
}
|
|
19302
|
+
if (change.opening) {
|
|
19303
|
+
if (view.opening) return false;
|
|
19304
|
+
view.opening = { center: local(change.opening.center), radius: change.opening.radius };
|
|
19305
|
+
}
|
|
19306
|
+
if (change.chip) view.chips.push(local(change.chip));
|
|
19307
|
+
const list2 = courses(part, spec.units, view.opening, view.chips, view.paint);
|
|
19308
|
+
if (view.mesh) {
|
|
19309
|
+
view.mesh.removeFromParent();
|
|
19310
|
+
view.mesh.dispose();
|
|
19311
|
+
}
|
|
19148
19312
|
const mesh2 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
19149
|
-
mesh2.name = "breach:" +
|
|
19313
|
+
mesh2.name = "breach:" + partId;
|
|
19150
19314
|
list2.forEach((u, i) => {
|
|
19151
19315
|
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
19152
19316
|
mesh2.setMatrixAt(i, tmp.m);
|
|
@@ -19158,8 +19322,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19158
19322
|
mesh2.castShadow = true;
|
|
19159
19323
|
mesh2.receiveShadow = true;
|
|
19160
19324
|
house.add(mesh2);
|
|
19161
|
-
|
|
19162
|
-
|
|
19325
|
+
if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
19326
|
+
view.mesh = mesh2;
|
|
19327
|
+
view.units = list2.length;
|
|
19328
|
+
views.set(partId, view);
|
|
19163
19329
|
invalidate();
|
|
19164
19330
|
invalidateShadow();
|
|
19165
19331
|
return true;
|
|
@@ -19173,7 +19339,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19173
19339
|
return {
|
|
19174
19340
|
/** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
|
|
19175
19341
|
stage(event) {
|
|
19176
|
-
return event.stage === 2 ?
|
|
19342
|
+
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
19343
|
+
},
|
|
19344
|
+
/** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
|
|
19345
|
+
chip(event) {
|
|
19346
|
+
return draw2(event.part, { chip: event.center });
|
|
19177
19347
|
},
|
|
19178
19348
|
/** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
|
|
19179
19349
|
drop(partId) {
|
|
@@ -19405,6 +19575,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19405
19575
|
case "stage":
|
|
19406
19576
|
breach.stage(e);
|
|
19407
19577
|
break;
|
|
19578
|
+
case "chip":
|
|
19579
|
+
breach.chip(e);
|
|
19580
|
+
break;
|
|
19408
19581
|
case "collapse":
|
|
19409
19582
|
for (const id of e.parts) {
|
|
19410
19583
|
breach.drop(id);
|
|
@@ -19459,6 +19632,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
19459
19632
|
if (D.active && D.world.has(D.id)) D.world.remove(D.id);
|
|
19460
19633
|
D.active = false;
|
|
19461
19634
|
};
|
|
19635
|
+
D.follow = (world, id, spec) => {
|
|
19636
|
+
D.spec = spec;
|
|
19637
|
+
const offFollow = world.on("*", (e) => {
|
|
19638
|
+
if (e.object !== id) return;
|
|
19639
|
+
switch (e.type) {
|
|
19640
|
+
case "stage":
|
|
19641
|
+
breach.stage(e);
|
|
19642
|
+
break;
|
|
19643
|
+
case "chip":
|
|
19644
|
+
breach.chip(e);
|
|
19645
|
+
break;
|
|
19646
|
+
case "fracture":
|
|
19647
|
+
breach.drop(e.part);
|
|
19648
|
+
break;
|
|
19649
|
+
case "collapse":
|
|
19650
|
+
for (const p of e.parts) breach.drop(p);
|
|
19651
|
+
break;
|
|
19652
|
+
default:
|
|
19653
|
+
break;
|
|
19654
|
+
}
|
|
19655
|
+
});
|
|
19656
|
+
return { restore: () => breach.restore(), dispose: () => {
|
|
19657
|
+
offFollow();
|
|
19658
|
+
breach.restore();
|
|
19659
|
+
} };
|
|
19660
|
+
};
|
|
19462
19661
|
D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
|
|
19463
19662
|
D.state = () => D.active ? D.world.state(D.id) : null;
|
|
19464
19663
|
D.reset = () => {
|
|
@@ -22580,6 +22779,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22580
22779
|
|
|
22581
22780
|
// src/furniture/spec.ts
|
|
22582
22781
|
var WOOD_THIN = 0.035;
|
|
22782
|
+
var SHELF_SPAN = 1.5;
|
|
22783
|
+
var SHELVING = /shelf|bookcase|rack/;
|
|
22784
|
+
function carcass(parts, drawn) {
|
|
22785
|
+
const all = [...drawn.values()];
|
|
22786
|
+
const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
|
|
22787
|
+
for (const part of parts) {
|
|
22788
|
+
if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
|
|
22789
|
+
const [x, y, z] = part.shape.size;
|
|
22790
|
+
if (y >= x && y >= z) {
|
|
22791
|
+
if (z < x && x >= 0.6 * width) continue;
|
|
22792
|
+
part.structure = { role: "column" };
|
|
22793
|
+
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
22794
|
+
}
|
|
22795
|
+
}
|
|
22583
22796
|
var FLOOR_TOUCH = 5e-3;
|
|
22584
22797
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
22585
22798
|
var BASE_MATERIAL = {
|
|
@@ -22695,6 +22908,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22695
22908
|
if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
22696
22909
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
22697
22910
|
}
|
|
22911
|
+
if (opts.carcass) carcass(parts, byId);
|
|
22698
22912
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
22699
22913
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|
|
22700
22914
|
const build = (T3) => {
|
|
@@ -22738,7 +22952,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22738
22952
|
if (w === void 0 || d === void 0 || h === void 0) throw new Error(`game-floorplan-three: furniture type "${type}" is not in the catalogue, so give w, d and h`);
|
|
22739
22953
|
const o = opts.options ?? {};
|
|
22740
22954
|
const seed = Math.round(opts.seed ?? 1);
|
|
22741
|
-
const plain2 =
|
|
22955
|
+
const plain2 = SHELVING.test(type) ? "paper" : "plastic";
|
|
22742
22956
|
const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
|
|
22743
22957
|
const { K, shared } = renderer.internals;
|
|
22744
22958
|
const before = K.PARTS.length;
|
|
@@ -22757,7 +22971,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22757
22971
|
shared.ctx = saved.ctx;
|
|
22758
22972
|
}
|
|
22759
22973
|
},
|
|
22760
|
-
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs }
|
|
22974
|
+
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
|
|
22761
22975
|
);
|
|
22762
22976
|
const spouts = K.PARTS.slice(before).filter((p) => p.type === "water_tap").map((p) => [p.pos[0] - root.x, p.pos[1], p.pos[2] - root.z]);
|
|
22763
22977
|
K.PARTS.length = before;
|
|
@@ -22824,7 +23038,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22824
23038
|
function draw(THREE, plan, options) {
|
|
22825
23039
|
return create3(THREE, { ...options, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true, pieces: "meshes" });
|
|
22826
23040
|
}
|
|
23041
|
+
var houses = /* @__PURE__ */ new WeakMap();
|
|
22827
23042
|
function label(house) {
|
|
23043
|
+
houses.set(house.group, house);
|
|
22828
23044
|
for (const [id, items] of Object.entries(house.destruction.ranges())) {
|
|
22829
23045
|
for (const item of items) {
|
|
22830
23046
|
const object2 = item.isObject3D ? item : item.mesh;
|
|
@@ -22837,12 +23053,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
22837
23053
|
const plan = wallPlan(options);
|
|
22838
23054
|
const houseOptions = options.house ?? {};
|
|
22839
23055
|
const probe = draw(THREE, plan, { ...houseOptions, destruction: void 0, destructionRenderer: void 0 });
|
|
22840
|
-
const spec = probe.structure.spec(
|
|
23056
|
+
const spec = probe.structure.spec();
|
|
22841
23057
|
probe.dispose();
|
|
22842
23058
|
return {
|
|
22843
23059
|
plan,
|
|
22844
23060
|
spec,
|
|
22845
|
-
build: (T2) => label(draw(T2, plan, houseOptions))
|
|
23061
|
+
build: (T2) => label(draw(T2, plan, houseOptions)),
|
|
23062
|
+
follow: (root, world, objectId) => houses.get(root)?.destruction?.follow?.(world, objectId, spec) ?? null
|
|
22846
23063
|
};
|
|
22847
23064
|
}
|
|
22848
23065
|
return __toCommonJS(index_exports);
|