@adatechnology/game-items-three 0.3.1 → 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/dist/index.d.ts +13 -1
- package/dist/index.global.js +790 -99
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +64 -11
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.global.js
CHANGED
|
@@ -2761,14 +2761,19 @@ var GameItemsThree = (() => {
|
|
|
2761
2761
|
},
|
|
2762
2762
|
tessellate: {
|
|
2763
2763
|
cylinder: {
|
|
2764
|
-
heightSegments:
|
|
2765
|
-
radialSegments:
|
|
2764
|
+
heightSegments: 24,
|
|
2765
|
+
radialSegments: 40,
|
|
2766
|
+
capRings: 6
|
|
2766
2767
|
},
|
|
2767
2768
|
box: {
|
|
2768
2769
|
widthSegments: 6,
|
|
2769
2770
|
heightSegments: 1,
|
|
2770
2771
|
depthSegments: 4
|
|
2771
2772
|
}
|
|
2773
|
+
},
|
|
2774
|
+
wall: {
|
|
2775
|
+
body: 1e-4,
|
|
2776
|
+
ends: 25e-5
|
|
2772
2777
|
}
|
|
2773
2778
|
}
|
|
2774
2779
|
},
|
|
@@ -2792,14 +2797,19 @@ var GameItemsThree = (() => {
|
|
|
2792
2797
|
},
|
|
2793
2798
|
tessellate: {
|
|
2794
2799
|
cylinder: {
|
|
2795
|
-
heightSegments:
|
|
2796
|
-
radialSegments:
|
|
2800
|
+
heightSegments: 24,
|
|
2801
|
+
radialSegments: 40,
|
|
2802
|
+
capRings: 6
|
|
2797
2803
|
},
|
|
2798
2804
|
box: {
|
|
2799
2805
|
widthSegments: 6,
|
|
2800
2806
|
heightSegments: 1,
|
|
2801
2807
|
depthSegments: 4
|
|
2802
2808
|
}
|
|
2809
|
+
},
|
|
2810
|
+
wall: {
|
|
2811
|
+
body: 18e-5,
|
|
2812
|
+
ends: 22e-5
|
|
2803
2813
|
}
|
|
2804
2814
|
}
|
|
2805
2815
|
}
|
|
@@ -2893,12 +2903,53 @@ var GameItemsThree = (() => {
|
|
|
2893
2903
|
if (!box4) return 0;
|
|
2894
2904
|
return (box4.max.x - box4.min.x) * (box4.max.y - box4.min.y) * (box4.max.z - box4.min.z);
|
|
2895
2905
|
}
|
|
2896
|
-
function
|
|
2906
|
+
function ringedCylinder(T2, p, radial, maxRows, rings) {
|
|
2907
|
+
const half = p.height / 2;
|
|
2908
|
+
const column = 2 * Math.PI * Math.max(p.radiusTop, p.radiusBottom, 1e-6) / radial;
|
|
2909
|
+
const rows = Math.max(1, Math.min(maxRows, Math.round(p.height / column)));
|
|
2910
|
+
const points = [];
|
|
2911
|
+
if (!p.openEnded) for (let i = 0; i <= rings; i++) points.push(new T2.Vector2(p.radiusBottom * i / rings, -half));
|
|
2912
|
+
for (let j = 0; j <= rows; j++) points.push(new T2.Vector2(p.radiusBottom + (p.radiusTop - p.radiusBottom) * j / rows, -half + p.height * j / rows));
|
|
2913
|
+
if (!p.openEnded) for (let i = rings; i >= 0; i--) points.push(new T2.Vector2(p.radiusTop * i / rings, half));
|
|
2914
|
+
return lathed(T2, points, radial, p.thetaStart, p.thetaLength);
|
|
2915
|
+
}
|
|
2916
|
+
function lathed(T2, points, radial, phiStart, phiLength) {
|
|
2917
|
+
const geometry = new T2.LatheGeometry([...points], radial, phiStart, phiLength);
|
|
2918
|
+
geometry.computeVertexNormals();
|
|
2919
|
+
const position = geometry.getAttribute("position");
|
|
2920
|
+
const normal = geometry.getAttribute("normal");
|
|
2921
|
+
const middle = (Math.min(...points.map((point) => point.y)) + Math.max(...points.map((point) => point.y))) / 2;
|
|
2922
|
+
for (let i = 0; i < normal.count; i++) {
|
|
2923
|
+
if (Math.hypot(normal.getX(i), normal.getY(i), normal.getZ(i)) > 0.5) continue;
|
|
2924
|
+
normal.setXYZ(i, 0, position.getY(i) >= middle ? 1 : -1, 0);
|
|
2925
|
+
}
|
|
2926
|
+
return geometry;
|
|
2927
|
+
}
|
|
2928
|
+
function finerProfile(T2, points, step) {
|
|
2929
|
+
const out = [];
|
|
2930
|
+
points.forEach((point, index) => {
|
|
2931
|
+
const next = points[index + 1];
|
|
2932
|
+
out.push(point.clone());
|
|
2933
|
+
if (!next) return;
|
|
2934
|
+
const pieces = Math.max(1, Math.round(point.distanceTo(next) / step));
|
|
2935
|
+
for (let k = 1; k < pieces; k++) out.push(point.clone().lerp(next, k / pieces));
|
|
2936
|
+
});
|
|
2937
|
+
return out;
|
|
2938
|
+
}
|
|
2939
|
+
function tessellated(geometry, rule, T2) {
|
|
2897
2940
|
const Shape = geometry.constructor;
|
|
2898
2941
|
if (geometry.type === "CylinderGeometry" && rule.cylinder) {
|
|
2899
|
-
const
|
|
2942
|
+
const parameters = geometry.parameters;
|
|
2943
|
+
const { radiusTop, radiusBottom, height, openEnded, thetaStart, thetaLength } = parameters;
|
|
2944
|
+
if (T2 && rule.cylinder.capRings !== void 0) return ringedCylinder(T2, parameters, rule.cylinder.radialSegments, rule.cylinder.heightSegments, rule.cylinder.capRings);
|
|
2900
2945
|
return new Shape(radiusTop, radiusBottom, height, rule.cylinder.radialSegments, rule.cylinder.heightSegments, openEnded, thetaStart, thetaLength);
|
|
2901
2946
|
}
|
|
2947
|
+
if (geometry.type === "LatheGeometry" && rule.cylinder && T2 && rule.cylinder.capRings !== void 0) {
|
|
2948
|
+
const { points, segments, phiStart, phiLength } = geometry.parameters;
|
|
2949
|
+
if (segments >= rule.cylinder.radialSegments) return void 0;
|
|
2950
|
+
const widest = Math.max(...points.map((point) => point.x), 1e-6);
|
|
2951
|
+
return lathed(T2, finerProfile(T2, points, 2 * Math.PI * widest / rule.cylinder.radialSegments), rule.cylinder.radialSegments, phiStart, phiLength);
|
|
2952
|
+
}
|
|
2902
2953
|
if (geometry.type === "BoxGeometry" && rule.box) {
|
|
2903
2954
|
const { width, height, depth } = geometry.parameters;
|
|
2904
2955
|
return new Shape(width, height, depth, rule.box.widthSegments, rule.box.heightSegments, rule.box.depthSegments);
|
|
@@ -2906,14 +2957,14 @@ var GameItemsThree = (() => {
|
|
|
2906
2957
|
return void 0;
|
|
2907
2958
|
}
|
|
2908
2959
|
var triangleCount = (geometry) => (geometry.index ? geometry.index.count : geometry.getAttribute("position").count) / 3;
|
|
2909
|
-
function tessellateShapes(root, rule) {
|
|
2960
|
+
function tessellateShapes(root, rule, T2) {
|
|
2910
2961
|
const rebuilt = /* @__PURE__ */ new Map();
|
|
2911
2962
|
root.traverse((object2) => {
|
|
2912
2963
|
const mesh2 = object2;
|
|
2913
2964
|
if (!mesh2.isMesh) return;
|
|
2914
2965
|
let fresh = rebuilt.get(mesh2.geometry);
|
|
2915
2966
|
if (!fresh) {
|
|
2916
|
-
fresh = tessellated(mesh2.geometry, rule);
|
|
2967
|
+
fresh = tessellated(mesh2.geometry, rule, T2);
|
|
2917
2968
|
if (!fresh) return;
|
|
2918
2969
|
rebuilt.set(mesh2.geometry, fresh);
|
|
2919
2970
|
}
|
|
@@ -2927,10 +2978,10 @@ var GameItemsThree = (() => {
|
|
|
2927
2978
|
root.userData.tessellationTriangles = added;
|
|
2928
2979
|
}
|
|
2929
2980
|
function tagForDestruction(root, options = {}) {
|
|
2930
|
-
const { modelId, weight, size } = options;
|
|
2981
|
+
const { modelId, weight, size, three } = options;
|
|
2931
2982
|
if (weight !== void 0 && weight > 0) root.userData.destructionMass = weight;
|
|
2932
2983
|
const rule = modelId === void 0 ? void 0 : itemDestruction(modelId);
|
|
2933
|
-
if (rule?.tessellate) tessellateShapes(root, rule.tessellate);
|
|
2984
|
+
if (rule?.tessellate) tessellateShapes(root, rule.tessellate, three);
|
|
2934
2985
|
const solid = [];
|
|
2935
2986
|
root.traverse((object2) => {
|
|
2936
2987
|
const mesh2 = object2;
|
|
@@ -2950,6 +3001,7 @@ var GameItemsThree = (() => {
|
|
|
2950
3001
|
const anchor = solid.reduce((largest, mesh2) => volumeOf(mesh2) > volumeOf(largest) ? mesh2 : largest);
|
|
2951
3002
|
solid.forEach((mesh2, index) => {
|
|
2952
3003
|
mesh2.userData.destructionPart = mesh2 === anchor ? { id: SHELL_PART_ID } : { id: `shell_${index}`, parent: SHELL_PART_ID, kind: "weld" };
|
|
3004
|
+
if (rule.wall) mesh2.userData.destructionThickness = mesh2 === anchor ? rule.wall.body : rule.wall.ends;
|
|
2953
3005
|
});
|
|
2954
3006
|
const contents = size === void 0 ? void 0 : itemContents({ ...modelId === void 0 ? {} : { model: modelId }, size, ...weight === void 0 ? {} : { weight } });
|
|
2955
3007
|
if (contents) anchor.userData.destructionContains = { fluid: contents.liquid, liters: contents.liters };
|
|
@@ -9070,7 +9122,7 @@ var GameItemsThree = (() => {
|
|
|
9070
9122
|
const materials = kit.materials.slice(first);
|
|
9071
9123
|
if (options.wear) setWear(materials, options.wear);
|
|
9072
9124
|
root.userData.wear = options.wear ?? 0;
|
|
9073
|
-
if (options.destructible) tagForDestruction(root, { ...kind === "model" && item.model !== void 0 ? { modelId: item.model } : {}, ...item.weight === void 0 ? {} : { weight: item.weight }, size: item.size });
|
|
9125
|
+
if (options.destructible) tagForDestruction(root, { ...kind === "model" && item.model !== void 0 ? { modelId: item.model } : {}, ...item.weight === void 0 ? {} : { weight: item.weight }, size: item.size, three: T2 });
|
|
9074
9126
|
markKitOrigin(root, { owner: "game-items-three", kind: "item", id: item.id, ...kind === "shape" ? { standIn: true } : {} });
|
|
9075
9127
|
return { root, kind, ...shape ? { shape } : {}, size: item.size, materials };
|
|
9076
9128
|
}
|
|
@@ -12694,6 +12746,47 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12694
12746
|
return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
|
|
12695
12747
|
};
|
|
12696
12748
|
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
12749
|
+
var splinterBox = (inp, box4, limit = inp.maxFragments) => {
|
|
12750
|
+
const { rnd } = inp;
|
|
12751
|
+
const dims = box4.s;
|
|
12752
|
+
const [fmin, fmax] = inp.fragmentSize;
|
|
12753
|
+
const g = inp.grain ?? [0, 1, 2].sort((a2, b2) => dims[b2] - dims[a2])[0];
|
|
12754
|
+
const [a, b] = [0, 1, 2].filter((i) => i !== g);
|
|
12755
|
+
const w = Math.max(fmin * 1.5, 0.01);
|
|
12756
|
+
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
|
|
12757
|
+
const maxLen = fmax * 4;
|
|
12758
|
+
const cells = bsp(box4, c, [a, b], rnd);
|
|
12759
|
+
const pieces = [];
|
|
12760
|
+
for (const cell of cells) {
|
|
12761
|
+
const extra = rnd() < 0.4 ? 1 : 0;
|
|
12762
|
+
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
12763
|
+
for (const p of bsp(cell, k, [g], rnd)) pieces.push(p);
|
|
12764
|
+
}
|
|
12765
|
+
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] }));
|
|
12766
|
+
};
|
|
12767
|
+
var severAt = (inp, root, at, gap) => {
|
|
12768
|
+
const dims = root.s;
|
|
12769
|
+
const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
|
|
12770
|
+
const half = dims[L] / 2;
|
|
12771
|
+
const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
|
|
12772
|
+
const out = [];
|
|
12773
|
+
const stub = (from, to) => {
|
|
12774
|
+
if (to - from < 0.02) return;
|
|
12775
|
+
const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
|
|
12776
|
+
c[L] = (from + to) / 2;
|
|
12777
|
+
sz[L] = to - from;
|
|
12778
|
+
out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
|
|
12779
|
+
};
|
|
12780
|
+
stub(-half, lo);
|
|
12781
|
+
stub(hi, half);
|
|
12782
|
+
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
12783
|
+
band.c[L] = (lo + hi) / 2;
|
|
12784
|
+
band.s[L] = Math.max(hi - lo, 0.01);
|
|
12785
|
+
if (inp.style === "snap") {
|
|
12786
|
+
for (const b of bsp(band, Math.max(3, Math.min(6, inp.maxFragments - out.length)), [0, 1, 2], inp.rnd)) out.push({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
12787
|
+
} else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
|
|
12788
|
+
return out;
|
|
12789
|
+
};
|
|
12697
12790
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
12698
12791
|
const { dims, rnd, maxFragments } = inp;
|
|
12699
12792
|
const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
|
|
@@ -12722,6 +12815,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12722
12815
|
const t = sorted[0];
|
|
12723
12816
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
12724
12817
|
if (style === "snap") {
|
|
12818
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
12725
12819
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
12726
12820
|
for (const b of bsp(root, n2, [0, 1, 2], rnd)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
|
|
12727
12821
|
return out;
|
|
@@ -12732,23 +12826,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12732
12826
|
return out;
|
|
12733
12827
|
}
|
|
12734
12828
|
if (style === "splinter") {
|
|
12735
|
-
|
|
12736
|
-
const
|
|
12737
|
-
const w = Math.max(fmin * 1.5, 0.01);
|
|
12738
|
-
const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(maxFragments / 2)));
|
|
12739
|
-
const maxLen = fmax * 4;
|
|
12740
|
-
const cells = bsp(root, c, [a, b], rnd);
|
|
12741
|
-
const pieces = [];
|
|
12742
|
-
for (const cell of cells) {
|
|
12743
|
-
let list2 = [cell];
|
|
12744
|
-
const extra = rnd() < 0.4 ? 1 : 0;
|
|
12745
|
-
const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
|
|
12746
|
-
list2 = bsp(cell, k, [g], rnd);
|
|
12747
|
-
for (const p of list2) pieces.push(p);
|
|
12748
|
-
}
|
|
12749
|
-
for (const p of pieces.slice(0, maxFragments)) {
|
|
12750
|
-
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] });
|
|
12751
|
-
}
|
|
12829
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
12830
|
+
for (const p of splinterBox(inp, root)) out.push(p);
|
|
12752
12831
|
return out;
|
|
12753
12832
|
}
|
|
12754
12833
|
if (style === "tear") {
|
|
@@ -12910,6 +12989,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12910
12989
|
for (const x of spread(dims[u], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(v, dims[v] / 2 - 0.02, [[u, x], [thin, 0]]));
|
|
12911
12990
|
return out;
|
|
12912
12991
|
};
|
|
12992
|
+
var clipBarsToBox = (bars, u, v, center, size) => {
|
|
12993
|
+
const out = [];
|
|
12994
|
+
for (const [a, b] of bars) {
|
|
12995
|
+
const axis = a[u] !== b[u] ? u : v;
|
|
12996
|
+
const other = axis === u ? v : u;
|
|
12997
|
+
if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
|
|
12998
|
+
const lo = Math.max(Math.min(a[axis], b[axis]), center[axis] - size[axis] / 2), hi = Math.min(Math.max(a[axis], b[axis]), center[axis] + size[axis] / 2);
|
|
12999
|
+
if (hi - lo < 1e-6) continue;
|
|
13000
|
+
const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
|
|
13001
|
+
p[axis] = lo;
|
|
13002
|
+
q[axis] = hi;
|
|
13003
|
+
out.push([p, q]);
|
|
13004
|
+
}
|
|
13005
|
+
return out;
|
|
13006
|
+
};
|
|
13007
|
+
var bendBar = ([a, b], thin, bow, i) => {
|
|
13008
|
+
const length = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
13009
|
+
const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
13010
|
+
m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length;
|
|
13011
|
+
return [[a, m], [m, b]];
|
|
13012
|
+
};
|
|
12913
13013
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
12914
13014
|
const out = [];
|
|
12915
13015
|
for (const [a, b] of bars) {
|
|
@@ -13830,14 +13930,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13830
13930
|
smoke: "grey"
|
|
13831
13931
|
},
|
|
13832
13932
|
visual: {
|
|
13833
|
-
charColor: 1709072
|
|
13933
|
+
charColor: 1709072,
|
|
13934
|
+
innerColor: 14730131
|
|
13834
13935
|
},
|
|
13835
13936
|
tags: [
|
|
13836
13937
|
"wood",
|
|
13837
13938
|
"organic",
|
|
13838
13939
|
"flammable"
|
|
13839
13940
|
],
|
|
13840
|
-
compressive: 1e5
|
|
13941
|
+
compressive: 1e5,
|
|
13942
|
+
hits: {
|
|
13943
|
+
blunt: {
|
|
13944
|
+
tear: "splinters",
|
|
13945
|
+
tearJoules: 60
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13841
13948
|
},
|
|
13842
13949
|
{
|
|
13843
13950
|
id: "wood_hard",
|
|
@@ -13867,14 +13974,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13867
13974
|
smoke: "grey"
|
|
13868
13975
|
},
|
|
13869
13976
|
visual: {
|
|
13870
|
-
charColor: 1709072
|
|
13977
|
+
charColor: 1709072,
|
|
13978
|
+
innerColor: 13213802
|
|
13871
13979
|
},
|
|
13872
13980
|
tags: [
|
|
13873
13981
|
"wood",
|
|
13874
13982
|
"organic",
|
|
13875
13983
|
"flammable"
|
|
13876
13984
|
],
|
|
13877
|
-
compressive: 15e4
|
|
13985
|
+
compressive: 15e4,
|
|
13986
|
+
hits: {
|
|
13987
|
+
blunt: {
|
|
13988
|
+
tear: "splinters",
|
|
13989
|
+
tearJoules: 150
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13878
13992
|
},
|
|
13879
13993
|
{
|
|
13880
13994
|
id: "plywood",
|
|
@@ -13904,14 +14018,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13904
14018
|
smoke: "grey"
|
|
13905
14019
|
},
|
|
13906
14020
|
visual: {
|
|
13907
|
-
charColor: 1709072
|
|
14021
|
+
charColor: 1709072,
|
|
14022
|
+
innerColor: 14862234
|
|
13908
14023
|
},
|
|
13909
14024
|
tags: [
|
|
13910
14025
|
"wood",
|
|
13911
14026
|
"organic",
|
|
13912
14027
|
"flammable"
|
|
13913
14028
|
],
|
|
13914
|
-
compressive: 6e4
|
|
14029
|
+
compressive: 6e4,
|
|
14030
|
+
hits: {
|
|
14031
|
+
ballistic: {
|
|
14032
|
+
tear: "splinters"
|
|
14033
|
+
},
|
|
14034
|
+
blunt: {
|
|
14035
|
+
tear: "splinters",
|
|
14036
|
+
tearJoules: 80
|
|
14037
|
+
}
|
|
14038
|
+
}
|
|
13915
14039
|
},
|
|
13916
14040
|
{
|
|
13917
14041
|
id: "mdf",
|
|
@@ -13938,14 +14062,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13938
14062
|
smoke: "grey"
|
|
13939
14063
|
},
|
|
13940
14064
|
visual: {
|
|
13941
|
-
charColor: 1709072
|
|
14065
|
+
charColor: 1709072,
|
|
14066
|
+
innerColor: 12096362
|
|
13942
14067
|
},
|
|
13943
14068
|
tags: [
|
|
13944
14069
|
"wood",
|
|
13945
14070
|
"organic",
|
|
13946
14071
|
"flammable"
|
|
13947
14072
|
],
|
|
13948
|
-
compressive: 3e4
|
|
14073
|
+
compressive: 3e4,
|
|
14074
|
+
hits: {
|
|
14075
|
+
ballistic: {
|
|
14076
|
+
tear: "lumps",
|
|
14077
|
+
tearJoules: 120
|
|
14078
|
+
},
|
|
14079
|
+
blunt: {
|
|
14080
|
+
tear: "lumps",
|
|
14081
|
+
tearJoules: 20
|
|
14082
|
+
}
|
|
14083
|
+
}
|
|
13949
14084
|
},
|
|
13950
14085
|
{
|
|
13951
14086
|
id: "glass",
|
|
@@ -14059,6 +14194,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14059
14194
|
damageScale: {
|
|
14060
14195
|
explosive: 1.2
|
|
14061
14196
|
},
|
|
14197
|
+
hits: {
|
|
14198
|
+
ballistic: {
|
|
14199
|
+
unitJoules: 500,
|
|
14200
|
+
floor: 900,
|
|
14201
|
+
crater: true
|
|
14202
|
+
},
|
|
14203
|
+
blunt: {
|
|
14204
|
+
unitJoules: 1500,
|
|
14205
|
+
floor: 60
|
|
14206
|
+
}
|
|
14207
|
+
},
|
|
14062
14208
|
hitDebris: "chips",
|
|
14063
14209
|
breakDebris: "dust",
|
|
14064
14210
|
thermal: {
|
|
@@ -14427,7 +14573,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14427
14573
|
smoke: "white"
|
|
14428
14574
|
},
|
|
14429
14575
|
visual: {
|
|
14430
|
-
charColor: 1709072
|
|
14576
|
+
charColor: 1709072,
|
|
14577
|
+
innerColor: 14207126
|
|
14431
14578
|
},
|
|
14432
14579
|
tags: [
|
|
14433
14580
|
"plant",
|
|
@@ -14435,7 +14582,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14435
14582
|
"organic",
|
|
14436
14583
|
"flammable"
|
|
14437
14584
|
],
|
|
14438
|
-
compressive: 6e4
|
|
14585
|
+
compressive: 6e4,
|
|
14586
|
+
hits: {
|
|
14587
|
+
blunt: {
|
|
14588
|
+
tear: "none"
|
|
14589
|
+
}
|
|
14590
|
+
}
|
|
14439
14591
|
},
|
|
14440
14592
|
{
|
|
14441
14593
|
id: "cactus",
|
|
@@ -14550,6 +14702,130 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14550
14702
|
"masonry"
|
|
14551
14703
|
],
|
|
14552
14704
|
compressive: 2e4
|
|
14705
|
+
},
|
|
14706
|
+
{
|
|
14707
|
+
id: "asphalt",
|
|
14708
|
+
density: 2350,
|
|
14709
|
+
hpPerKg: 10,
|
|
14710
|
+
penetration: 14,
|
|
14711
|
+
penetrationClass: "concrete",
|
|
14712
|
+
penetrationScale: 0.8,
|
|
14713
|
+
ductility: 0.1,
|
|
14714
|
+
fracture: "crumble",
|
|
14715
|
+
fragmentSize: [
|
|
14716
|
+
0.03,
|
|
14717
|
+
0.15
|
|
14718
|
+
],
|
|
14719
|
+
hitDebris: "chips",
|
|
14720
|
+
breakDebris: "dust",
|
|
14721
|
+
thermal: {
|
|
14722
|
+
conductivity: 0.1,
|
|
14723
|
+
heatCapacity: 2,
|
|
14724
|
+
ignitionC: 400,
|
|
14725
|
+
burnRate: 2e-3,
|
|
14726
|
+
smoke: "black"
|
|
14727
|
+
},
|
|
14728
|
+
tags: [
|
|
14729
|
+
"masonry",
|
|
14730
|
+
"brittle"
|
|
14731
|
+
],
|
|
14732
|
+
compressive: 3e4
|
|
14733
|
+
},
|
|
14734
|
+
{
|
|
14735
|
+
id: "sand",
|
|
14736
|
+
density: 1600,
|
|
14737
|
+
hpPerKg: 2,
|
|
14738
|
+
penetration: 6,
|
|
14739
|
+
ductility: 0.4,
|
|
14740
|
+
fracture: "crumble",
|
|
14741
|
+
fragmentSize: [
|
|
14742
|
+
0.01,
|
|
14743
|
+
0.04
|
|
14744
|
+
],
|
|
14745
|
+
hitDebris: "dust",
|
|
14746
|
+
breakDebris: "dust",
|
|
14747
|
+
thermal: {
|
|
14748
|
+
conductivity: 0.1,
|
|
14749
|
+
heatCapacity: 2
|
|
14750
|
+
},
|
|
14751
|
+
tags: [
|
|
14752
|
+
"soil"
|
|
14753
|
+
],
|
|
14754
|
+
compressive: 1e4
|
|
14755
|
+
},
|
|
14756
|
+
{
|
|
14757
|
+
id: "gravel",
|
|
14758
|
+
density: 1800,
|
|
14759
|
+
hpPerKg: 3,
|
|
14760
|
+
penetration: 8,
|
|
14761
|
+
ductility: 0.3,
|
|
14762
|
+
fracture: "crumble",
|
|
14763
|
+
fragmentSize: [
|
|
14764
|
+
0.02,
|
|
14765
|
+
0.06
|
|
14766
|
+
],
|
|
14767
|
+
hitDebris: "chips",
|
|
14768
|
+
breakDebris: "dust",
|
|
14769
|
+
thermal: {
|
|
14770
|
+
conductivity: 0.1,
|
|
14771
|
+
heatCapacity: 2
|
|
14772
|
+
},
|
|
14773
|
+
tags: [
|
|
14774
|
+
"soil",
|
|
14775
|
+
"masonry"
|
|
14776
|
+
],
|
|
14777
|
+
compressive: 2e4
|
|
14778
|
+
},
|
|
14779
|
+
{
|
|
14780
|
+
id: "turf",
|
|
14781
|
+
density: 900,
|
|
14782
|
+
hpPerKg: 3,
|
|
14783
|
+
penetration: 4,
|
|
14784
|
+
ductility: 0.5,
|
|
14785
|
+
fracture: "crumble",
|
|
14786
|
+
fragmentSize: [
|
|
14787
|
+
0.03,
|
|
14788
|
+
0.1
|
|
14789
|
+
],
|
|
14790
|
+
hitDebris: "dust",
|
|
14791
|
+
breakDebris: "dust",
|
|
14792
|
+
thermal: {
|
|
14793
|
+
conductivity: 0.1,
|
|
14794
|
+
heatCapacity: 1.5,
|
|
14795
|
+
ignitionC: 250,
|
|
14796
|
+
burnRate: 0.03,
|
|
14797
|
+
smoke: "white"
|
|
14798
|
+
},
|
|
14799
|
+
tags: [
|
|
14800
|
+
"soil",
|
|
14801
|
+
"organic"
|
|
14802
|
+
],
|
|
14803
|
+
compressive: 15e3
|
|
14804
|
+
},
|
|
14805
|
+
{
|
|
14806
|
+
id: "mortar",
|
|
14807
|
+
density: 1900,
|
|
14808
|
+
hpPerKg: 10,
|
|
14809
|
+
penetration: 12,
|
|
14810
|
+
penetrationClass: "concrete",
|
|
14811
|
+
penetrationScale: 1.2,
|
|
14812
|
+
ductility: 0.03,
|
|
14813
|
+
fracture: "crumble",
|
|
14814
|
+
fragmentSize: [
|
|
14815
|
+
0.02,
|
|
14816
|
+
0.08
|
|
14817
|
+
],
|
|
14818
|
+
hitDebris: "chips",
|
|
14819
|
+
breakDebris: "dust",
|
|
14820
|
+
thermal: {
|
|
14821
|
+
conductivity: 0.15,
|
|
14822
|
+
heatCapacity: 2.5
|
|
14823
|
+
},
|
|
14824
|
+
tags: [
|
|
14825
|
+
"masonry",
|
|
14826
|
+
"brittle"
|
|
14827
|
+
],
|
|
14828
|
+
compressive: 1e5
|
|
14553
14829
|
}
|
|
14554
14830
|
];
|
|
14555
14831
|
var material_hints_default = {
|
|
@@ -14723,6 +14999,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14723
14999
|
return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
|
|
14724
15000
|
};
|
|
14725
15001
|
var THIN_SHEET_DISHING = 5e-3;
|
|
15002
|
+
var SWEEP_BACK = 0.01;
|
|
15003
|
+
function sweepOrigins(box4, d, spacing) {
|
|
15004
|
+
const origins = [];
|
|
15005
|
+
const line = (from, to) => {
|
|
15006
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
15007
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
15008
|
+
};
|
|
15009
|
+
for (const axis of [0, 1, 2]) {
|
|
15010
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
15011
|
+
const lead = d[axis] > 0 ? box4.max[axis] - SWEEP_BACK : box4.min[axis] + SWEEP_BACK;
|
|
15012
|
+
const u = (axis + 1) % 3;
|
|
15013
|
+
const v = (axis + 2) % 3;
|
|
15014
|
+
for (const a of line(box4.min[u], box4.max[u])) {
|
|
15015
|
+
for (const b of line(box4.min[v], box4.max[v])) {
|
|
15016
|
+
const point = [0, 0, 0];
|
|
15017
|
+
point[axis] = lead;
|
|
15018
|
+
point[u] = a;
|
|
15019
|
+
point[v] = b;
|
|
15020
|
+
origins.push(point);
|
|
15021
|
+
}
|
|
15022
|
+
}
|
|
15023
|
+
}
|
|
15024
|
+
return origins;
|
|
15025
|
+
}
|
|
15026
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
15027
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
14726
15028
|
var DMG_PER_J = 0.02;
|
|
14727
15029
|
var BLAST_COUPLING = 0.1;
|
|
14728
15030
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -14736,9 +15038,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14736
15038
|
var BREACH_AREA = 0.25;
|
|
14737
15039
|
var BREACH_BLAST_REACH = 0.12;
|
|
14738
15040
|
var RESIDUAL_CAPACITY = 0.15;
|
|
15041
|
+
var SEVER_BAND = 0.1;
|
|
15042
|
+
var SEVER_ASPECT = 3;
|
|
15043
|
+
var SEVER_BALLISTIC = 2;
|
|
15044
|
+
var TEAR_JOULES = 150;
|
|
15045
|
+
var tears = 0;
|
|
14739
15046
|
var SUPPORT_TOLERANCE = 0.05;
|
|
14740
15047
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
14741
15048
|
var BREACH_GRID_CAP = 4096;
|
|
15049
|
+
var CHIP_ENERGY = 200;
|
|
15050
|
+
var CHIP_SPLASH = 0.5;
|
|
15051
|
+
var BAR_BOW = 0.08;
|
|
14742
15052
|
var CUT_SLICE = 1;
|
|
14743
15053
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
14744
15054
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -14748,8 +15058,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14748
15058
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
14749
15059
|
};
|
|
14750
15060
|
var capacityScale = (p) => {
|
|
14751
|
-
|
|
14752
|
-
|
|
15061
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
15062
|
+
if (p.st.stripped) return residual;
|
|
15063
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
15064
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
15065
|
+
};
|
|
15066
|
+
var sectionLoss = (p) => {
|
|
15067
|
+
const cells = p.st.chipped;
|
|
15068
|
+
if (!cells?.length) return 0;
|
|
15069
|
+
const [u, v] = planeAxes(p.thin);
|
|
15070
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
15071
|
+
const across2 = along === u ? v : u;
|
|
15072
|
+
let worst = 0;
|
|
15073
|
+
for (const c of cells) {
|
|
15074
|
+
let gone = 0;
|
|
15075
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across2];
|
|
15076
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across2], 1e-6));
|
|
15077
|
+
}
|
|
15078
|
+
return Math.min(1, worst);
|
|
14753
15079
|
};
|
|
14754
15080
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
14755
15081
|
var clone = (v) => structuredClone(v);
|
|
@@ -14782,6 +15108,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14782
15108
|
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
14783
15109
|
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
14784
15110
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
15111
|
+
const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
|
|
14785
15112
|
const rng3 = createRng(options.seed ?? 1);
|
|
14786
15113
|
const rnd = () => rng3.next();
|
|
14787
15114
|
let time = 0;
|
|
@@ -15155,41 +15482,68 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15155
15482
|
markDamaged(p);
|
|
15156
15483
|
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
15157
15484
|
};
|
|
15485
|
+
const freeSpan = (p, supports) => {
|
|
15486
|
+
if (supports.length === 0) return Infinity;
|
|
15487
|
+
const d = p.dims;
|
|
15488
|
+
const L = [0, 1, 2].sort((a, b) => d[b] - d[a])[0];
|
|
15489
|
+
const spans = supports.map((k) => contactBox(p, k)).map(([lo, hi]) => [lo[L], hi[L]]);
|
|
15490
|
+
let far = 0;
|
|
15491
|
+
for (let i = 0; i <= 16; i++) {
|
|
15492
|
+
const x = (i / 16 - 0.5) * d[L];
|
|
15493
|
+
let near = Infinity;
|
|
15494
|
+
for (const [lo, hi] of spans) near = Math.min(near, Math.max(lo - x, x - hi, 0));
|
|
15495
|
+
far = Math.max(far, near);
|
|
15496
|
+
}
|
|
15497
|
+
return 2 * far;
|
|
15498
|
+
};
|
|
15158
15499
|
const solveStructure = (o) => {
|
|
15159
15500
|
o.maxStress = 0;
|
|
15160
15501
|
if (!o.hasStructure) return;
|
|
15161
15502
|
const parts = o.parts;
|
|
15162
15503
|
const n = parts.length;
|
|
15163
15504
|
const dist = new Array(n).fill(-1);
|
|
15164
|
-
const queue = [];
|
|
15165
15505
|
const ok = (i) => parts[i].structural && !isLost(parts[i]);
|
|
15166
15506
|
const isPartition = (i) => parts[i].spec.structure?.role === "partition";
|
|
15167
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15173
|
-
if (
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15507
|
+
const out = /* @__PURE__ */ new Set();
|
|
15508
|
+
let queue = [];
|
|
15509
|
+
let lowerOf = /* @__PURE__ */ new Map();
|
|
15510
|
+
for (; ; ) {
|
|
15511
|
+
dist.fill(-1);
|
|
15512
|
+
queue = [];
|
|
15513
|
+
for (let i = 0; i < n; i++) if (ok(i) && !out.has(i) && parts[i].anchored) {
|
|
15514
|
+
dist[i] = 0;
|
|
15515
|
+
queue.push(i);
|
|
15516
|
+
}
|
|
15517
|
+
for (let h = 0; h < queue.length; h++) {
|
|
15518
|
+
const s = queue[h];
|
|
15519
|
+
if (isPartition(s)) continue;
|
|
15520
|
+
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
15521
|
+
if (j.st.open || !j.bears) continue;
|
|
15522
|
+
const r = j.ia === s ? j.ib : j.ia;
|
|
15523
|
+
if (dist[r] >= 0 || !ok(r) || out.has(r) || restsOn(o, j, r) !== s) continue;
|
|
15524
|
+
dist[r] = dist[s] + 1;
|
|
15525
|
+
queue.push(r);
|
|
15526
|
+
}
|
|
15180
15527
|
}
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15528
|
+
lowerOf = /* @__PURE__ */ new Map();
|
|
15529
|
+
let dropped = false;
|
|
15530
|
+
for (const i of queue) {
|
|
15531
|
+
const lower = [];
|
|
15532
|
+
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
15533
|
+
if (j.st.open || !j.bears) continue;
|
|
15534
|
+
const k = restsOn(o, j, i);
|
|
15535
|
+
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
15536
|
+
}
|
|
15537
|
+
lowerOf.set(i, lower);
|
|
15538
|
+
const max = parts[i].spec.structure?.maxSpan;
|
|
15539
|
+
if (max !== void 0 && !parts[i].anchored && freeSpan(parts[i], lower.map((k) => parts[k])) > max) {
|
|
15540
|
+
out.add(i);
|
|
15541
|
+
dropped = true;
|
|
15542
|
+
}
|
|
15190
15543
|
}
|
|
15191
|
-
|
|
15544
|
+
if (!dropped) break;
|
|
15192
15545
|
}
|
|
15546
|
+
const incoming = new Array(n).fill(0);
|
|
15193
15547
|
const bending = new Array(n).fill(0);
|
|
15194
15548
|
const through = parts.map(() => []);
|
|
15195
15549
|
const pass = (k, at, mass) => {
|
|
@@ -15701,6 +16055,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15701
16055
|
const r = rayPart(o, p, origin, d);
|
|
15702
16056
|
if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
|
|
15703
16057
|
if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
|
|
16058
|
+
if (p.st.chipped && insideChip(p, objLocal(o, vec.madd(origin, d, r.t0)))) continue;
|
|
15704
16059
|
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 };
|
|
15705
16060
|
}
|
|
15706
16061
|
}
|
|
@@ -15901,6 +16256,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15901
16256
|
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
15902
16257
|
setStage(o, p, 1, {}, src);
|
|
15903
16258
|
};
|
|
16259
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
16260
|
+
markDamaged(p);
|
|
16261
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
16262
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
16263
|
+
setStage(o, p, 1, {}, src);
|
|
16264
|
+
};
|
|
15904
16265
|
const insideOpening = (o, p, objectPoint) => {
|
|
15905
16266
|
const op = p.st.opening;
|
|
15906
16267
|
if (!op) return false;
|
|
@@ -15964,6 +16325,91 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15964
16325
|
if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
|
|
15965
16326
|
openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
|
|
15966
16327
|
};
|
|
16328
|
+
const unitGrids = /* @__PURE__ */ new WeakMap();
|
|
16329
|
+
const gridOf = (p) => {
|
|
16330
|
+
let grid = unitGrids.get(p);
|
|
16331
|
+
if (!grid) unitGrids.set(p, grid = unitGrid(p.dims, p.thin, unitsOf2(p), BREACH_GRID_CAP));
|
|
16332
|
+
return grid;
|
|
16333
|
+
};
|
|
16334
|
+
const insideChip = (p, objectPoint) => {
|
|
16335
|
+
const cells = p.st.chipped;
|
|
16336
|
+
if (!cells?.length) return false;
|
|
16337
|
+
const [u, v] = planeAxes(p.thin);
|
|
16338
|
+
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
16339
|
+
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);
|
|
16340
|
+
};
|
|
16341
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
16342
|
+
var _a, _b;
|
|
16343
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
16344
|
+
const rule = p.mat.hits?.[kind];
|
|
16345
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
16346
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
16347
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
16348
|
+
const [u, v] = planeAxes(p.thin);
|
|
16349
|
+
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
16350
|
+
const grid = gridOf(p);
|
|
16351
|
+
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
16352
|
+
const out = [];
|
|
16353
|
+
for (let i = 0; i < grid.length; i++) {
|
|
16354
|
+
const cell = grid[i];
|
|
16355
|
+
if (worn[i] === -1) continue;
|
|
16356
|
+
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));
|
|
16357
|
+
if (gap > radius) continue;
|
|
16358
|
+
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
16359
|
+
const total = (worn[i] ?? 0) + energy * share;
|
|
16360
|
+
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
16361
|
+
if (total < perKg * mass) {
|
|
16362
|
+
worn[i] = total;
|
|
16363
|
+
continue;
|
|
16364
|
+
}
|
|
16365
|
+
worn[i] = -1;
|
|
16366
|
+
out.push(cell);
|
|
16367
|
+
}
|
|
16368
|
+
if (out.length === 0) return;
|
|
16369
|
+
const { c, q } = partXf(o, p);
|
|
16370
|
+
const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
|
|
16371
|
+
const holes = localHolesOf(p);
|
|
16372
|
+
for (const cell of out) {
|
|
16373
|
+
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
16374
|
+
const center = toWorld(c, q, cell.center);
|
|
16375
|
+
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
16376
|
+
const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
|
|
16377
|
+
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
16378
|
+
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
16379
|
+
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
16380
|
+
p.rt.removed = (p.rt.removed ?? 0) + mass;
|
|
16381
|
+
markDamaged(p);
|
|
16382
|
+
setStage(o, p, 1, {}, src);
|
|
16383
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
16384
|
+
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
16385
|
+
}
|
|
16386
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
16387
|
+
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
16388
|
+
if (p.structural && !isLost(p)) {
|
|
16389
|
+
solveStructure(o);
|
|
16390
|
+
collapseCheck(o, src);
|
|
16391
|
+
}
|
|
16392
|
+
};
|
|
16393
|
+
const exposeRebar = (o, p, src) => {
|
|
16394
|
+
const r = p.spec.reinforcement;
|
|
16395
|
+
if (!r) return;
|
|
16396
|
+
const [u, v] = planeAxes(p.thin);
|
|
16397
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
16398
|
+
let bars;
|
|
16399
|
+
if (p.st.stripped) bars = all;
|
|
16400
|
+
else {
|
|
16401
|
+
bars = [];
|
|
16402
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
16403
|
+
const op = p.st.opening;
|
|
16404
|
+
if (op) {
|
|
16405
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
16406
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
16407
|
+
}
|
|
16408
|
+
}
|
|
16409
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
16410
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
16411
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
16412
|
+
};
|
|
15967
16413
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
15968
16414
|
const [u, v] = planeAxes(p.thin);
|
|
15969
16415
|
const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
|
|
@@ -15975,7 +16421,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15975
16421
|
failPart(o, p, worldCenter, dir, energy, 12, src);
|
|
15976
16422
|
return;
|
|
15977
16423
|
}
|
|
15978
|
-
const
|
|
16424
|
+
const worn = p.rt.chips;
|
|
16425
|
+
const cells = cellsWithin(gridOf(p).filter((_, i) => worn?.[i] !== -1), p.thin, lc, radius);
|
|
15979
16426
|
const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
|
|
15980
16427
|
const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
|
|
15981
16428
|
const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
|
|
@@ -15989,10 +16436,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15989
16436
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
15990
16437
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
15991
16438
|
const reinforcement = p.spec.reinforcement;
|
|
15992
|
-
if (reinforcement)
|
|
15993
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
15994
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
15995
|
-
}
|
|
16439
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
15996
16440
|
if (p.structural) solveStructure(o);
|
|
15997
16441
|
};
|
|
15998
16442
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -16004,8 +16448,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16004
16448
|
p.st.burning = false;
|
|
16005
16449
|
p.rt.jet = false;
|
|
16006
16450
|
markDamaged(p);
|
|
16007
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
16008
16451
|
solveStructure(o);
|
|
16452
|
+
exposeRebar(o, p, src);
|
|
16009
16453
|
collapseCheck(o, src);
|
|
16010
16454
|
};
|
|
16011
16455
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -16059,6 +16503,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16059
16503
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
16060
16504
|
const hitLocal = toLocal(c, q, hitW);
|
|
16061
16505
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
16506
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
16507
|
+
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
16062
16508
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
16063
16509
|
style,
|
|
16064
16510
|
dims: p.dims,
|
|
@@ -16068,7 +16514,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16068
16514
|
fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
|
|
16069
16515
|
maxFragments,
|
|
16070
16516
|
rnd,
|
|
16071
|
-
sheet: p.mat.sheet
|
|
16517
|
+
sheet: p.mat.sheet,
|
|
16518
|
+
...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
|
|
16072
16519
|
});
|
|
16073
16520
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
16074
16521
|
const mass = p.st.mass;
|
|
@@ -16127,10 +16574,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16127
16574
|
emit({ ...base(o, src), type: "primed", part: p.spec.id, point: partXf(o, p).c, trigger, delay });
|
|
16128
16575
|
};
|
|
16129
16576
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
16577
|
+
const severAxis = (p) => {
|
|
16578
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
16579
|
+
const d = p.dims;
|
|
16580
|
+
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
16581
|
+
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
16582
|
+
};
|
|
16583
|
+
const bandOf = (o, p, axis, origin) => {
|
|
16584
|
+
const { c, q } = partXf(o, p);
|
|
16585
|
+
const along = toLocal(c, q, origin)[axis];
|
|
16586
|
+
const L = p.dims[axis];
|
|
16587
|
+
return clamp2(Math.floor((along + L / 2) / SEVER_BAND), 0, Math.ceil(L / SEVER_BAND) - 1);
|
|
16588
|
+
};
|
|
16130
16589
|
const applyDamage = (o, p, amount, type, origin, dir, fragEnergy, cap, src) => {
|
|
16590
|
+
var _a;
|
|
16131
16591
|
if (isLost(p) || amount <= 0) return 0;
|
|
16132
16592
|
const prev = p.st.hp;
|
|
16133
16593
|
p.st.hp -= amount;
|
|
16594
|
+
const axis = origin && type !== "fire" && type !== "heat" ? severAxis(p) : -1;
|
|
16595
|
+
if (axis >= 0 && p.st.hp > 0) {
|
|
16596
|
+
const band = bandOf(o, p, axis, origin);
|
|
16597
|
+
const cuts = (_a = p.rt).cuts ?? (_a.cuts = {});
|
|
16598
|
+
cuts[band] = (cuts[band] ?? 0) + amount * (type === "ballistic" ? SEVER_BALLISTIC : 1);
|
|
16599
|
+
if (cuts[band] >= p.st.maxHp * SEVER_BAND / p.dims[axis]) {
|
|
16600
|
+
p.rt.cutAt = -p.dims[axis] / 2 + (band + 0.5) * SEVER_BAND;
|
|
16601
|
+
p.st.hp = 0;
|
|
16602
|
+
}
|
|
16603
|
+
}
|
|
16134
16604
|
markDamaged(p);
|
|
16135
16605
|
const trigger = CHAIN_TRIGGER_OF[type];
|
|
16136
16606
|
const chained = trigger !== void 0 && chainDelayOf(p.spec, trigger) !== void 0;
|
|
@@ -16166,10 +16636,53 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16166
16636
|
}
|
|
16167
16637
|
}
|
|
16168
16638
|
};
|
|
16639
|
+
const tearOf = (mat, kind) => {
|
|
16640
|
+
const h = mat.hits?.[kind];
|
|
16641
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
16642
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
16643
|
+
};
|
|
16644
|
+
const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
|
|
16645
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
16646
|
+
if (tear === "none") return;
|
|
16647
|
+
const shot = kind === "ballistic";
|
|
16648
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
16649
|
+
if (n === 0) return;
|
|
16650
|
+
const fibre = tear === "lumps";
|
|
16651
|
+
const { q } = partXf(o, p);
|
|
16652
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
16653
|
+
const fragments = [];
|
|
16654
|
+
for (let i = 0; i < n; i++) {
|
|
16655
|
+
const back = !exit || i % 3 === 2;
|
|
16656
|
+
const len = scale * (fibre ? 1.2 + 1.2 * rnd() : 3 + 4 * rnd()), w = scale * (0.8 + 0.8 * rnd()), t = scale * (fibre ? 0.7 + 0.6 * rnd() : 0.4 + 0.4 * rnd());
|
|
16657
|
+
const size = [w, w, w];
|
|
16658
|
+
size[along] = len;
|
|
16659
|
+
size[(along + 1) % 3] = w;
|
|
16660
|
+
size[(along + 2) % 3] = t;
|
|
16661
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
16662
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
16663
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
16664
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
16665
|
+
fragments.push({
|
|
16666
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
16667
|
+
part: p.spec.id,
|
|
16668
|
+
material: p.spec.material,
|
|
16669
|
+
shape: fibre ? "chunk" : "splinter",
|
|
16670
|
+
center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
16671
|
+
size,
|
|
16672
|
+
rotation: q,
|
|
16673
|
+
velocity: v,
|
|
16674
|
+
inner: true,
|
|
16675
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
16676
|
+
mass
|
|
16677
|
+
});
|
|
16678
|
+
}
|
|
16679
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
16680
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
|
|
16681
|
+
};
|
|
16169
16682
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
16170
16683
|
const mat = p.mat;
|
|
16171
16684
|
const piercing = round3.piercing ?? 1;
|
|
16172
|
-
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2))) {
|
|
16685
|
+
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry2)) || insideChip(p, objLocal(o, entry2))) {
|
|
16173
16686
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry2, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
16174
16687
|
}
|
|
16175
16688
|
const sin = Math.min(1, Math.abs(vec.dot(d, n)));
|
|
@@ -16194,6 +16707,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16194
16707
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
16195
16708
|
}
|
|
16196
16709
|
if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
16710
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
16197
16711
|
const brittle = mat.ductility < 0.3;
|
|
16198
16712
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
16199
16713
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -16227,7 +16741,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16227
16741
|
} else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
16228
16742
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
16229
16743
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
16230
|
-
const
|
|
16744
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
16745
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
|
|
16746
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
16747
|
+
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);
|
|
16231
16748
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
16232
16749
|
};
|
|
16233
16750
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -16235,14 +16752,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16235
16752
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
16236
16753
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
16237
16754
|
const Ec = E * fragile;
|
|
16755
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
16756
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
16757
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
16238
16758
|
if (E > 0) {
|
|
16239
16759
|
if (kind === "blunt") {
|
|
16240
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
16760
|
+
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(patch, 0.015), clamp2(E * 1e-4 * fold, 1e-3, sheet ? Math.min(0.06, Math.min(...p.dims) * 0.45) : 0.06), src);
|
|
16241
16761
|
else if (mat.ductility < 0.3) {
|
|
16242
16762
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
16243
16763
|
} else if (E >= thrMm * 0.2) addDent(o, p, point, n, Math.max(contactR, 0.015), clamp2(E * 6e-5, 1e-3, 0.04), src);
|
|
16244
16764
|
} else if (mat.fracture === "tear") {
|
|
16245
16765
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
16766
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
16767
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
16768
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
16246
16769
|
} else if (mat.ductility >= 0.5) {
|
|
16247
16770
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
16248
16771
|
}
|
|
@@ -16255,6 +16778,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16255
16778
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
16256
16779
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
16257
16780
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
16781
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
16782
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
16258
16783
|
return done;
|
|
16259
16784
|
};
|
|
16260
16785
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -16833,6 +17358,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16833
17358
|
if (p.rt.stressT <= 0) {
|
|
16834
17359
|
p.rt.stressT = 0.5;
|
|
16835
17360
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
17361
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
16836
17362
|
}
|
|
16837
17363
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
16838
17364
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -17049,6 +17575,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17049
17575
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
17050
17576
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
17051
17577
|
},
|
|
17578
|
+
sweepBox(box4, direction, distance2, options2 = {}) {
|
|
17579
|
+
const d = vec.normalize(direction);
|
|
17580
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
17581
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
17582
|
+
let best = null;
|
|
17583
|
+
for (const origin of sweepOrigins(box4, d, options2.spacing ?? 0.2)) {
|
|
17584
|
+
const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
|
|
17585
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
17586
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
17587
|
+
}
|
|
17588
|
+
return best;
|
|
17589
|
+
},
|
|
17052
17590
|
shoot(origin, direction, energy, caliber, source, round3) {
|
|
17053
17591
|
const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
|
|
17054
17592
|
return withCollapses(run(() => {
|
|
@@ -18620,7 +19158,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18620
19158
|
}
|
|
18621
19159
|
return spec;
|
|
18622
19160
|
};
|
|
18623
|
-
var
|
|
19161
|
+
var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
|
|
19162
|
+
var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
|
|
19163
|
+
var caged = (spec, part, rebar2) => {
|
|
19164
|
+
if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
|
|
19165
|
+
const size = spec.shape.kind === "box" ? spec.shape.size : null;
|
|
19166
|
+
if (!size) return spec;
|
|
19167
|
+
const thin = Math.min(...size);
|
|
19168
|
+
return {
|
|
19169
|
+
...spec,
|
|
19170
|
+
reinforcement: { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
|
|
19171
|
+
units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
|
|
19172
|
+
};
|
|
19173
|
+
};
|
|
19174
|
+
var partSpecOf = (part, capacity, detailed, rebar2) => {
|
|
19175
|
+
if (detailed && part.assembly) return assemblySpec(part, capacity);
|
|
19176
|
+
const spec = boxSpec(part, capacity);
|
|
19177
|
+
return detailed ? caged(spec, part, rebar2) : spec;
|
|
19178
|
+
};
|
|
18624
19179
|
function toDestructibleSpec(structure, options = {}) {
|
|
18625
19180
|
const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
|
|
18626
19181
|
const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
|
|
@@ -18637,7 +19192,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18637
19192
|
const sizing = { safety, redundancy, safetyOthers };
|
|
18638
19193
|
const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
|
|
18639
19194
|
const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
|
|
18640
|
-
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
|
|
19195
|
+
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
|
|
18641
19196
|
const margin = options.terrainMargin ?? TERRAIN_MARGIN;
|
|
18642
19197
|
const x0 = Math.min(...parts.map((part) => part.aabb[0]));
|
|
18643
19198
|
const z0 = Math.min(...parts.map((part) => part.aabb[1]));
|
|
@@ -26273,12 +26828,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26273
26828
|
A.invalidate = () => {
|
|
26274
26829
|
for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
|
|
26275
26830
|
};
|
|
26276
|
-
A.update = function(
|
|
26831
|
+
A.update = function(houses2, focus, op) {
|
|
26277
26832
|
op = op || {};
|
|
26278
|
-
|
|
26833
|
+
houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
|
|
26279
26834
|
_f.set(focus.x, focus.y, focus.z);
|
|
26280
26835
|
const rooms = [], exterior = [];
|
|
26281
|
-
for (const house of
|
|
26836
|
+
for (const house of houses2) {
|
|
26282
26837
|
if (house.group && !house.group.visible) continue;
|
|
26283
26838
|
house.house.updateMatrixWorld(true);
|
|
26284
26839
|
const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
|
|
@@ -26602,7 +27157,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26602
27157
|
if (!pc || pc.unit) return sp;
|
|
26603
27158
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
26604
27159
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
26605
|
-
|
|
27160
|
+
const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
|
|
27161
|
+
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
27162
|
+
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
27163
|
+
}
|
|
27164
|
+
return out;
|
|
26606
27165
|
}
|
|
26607
27166
|
if (!pc.assembly) return sp;
|
|
26608
27167
|
const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
|
|
@@ -26657,7 +27216,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26657
27216
|
}
|
|
26658
27217
|
return null;
|
|
26659
27218
|
};
|
|
26660
|
-
|
|
27219
|
+
const local = (p) => {
|
|
27220
|
+
house.updateMatrixWorld(true);
|
|
27221
|
+
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
27222
|
+
return [v.x, v.y, v.z];
|
|
27223
|
+
};
|
|
27224
|
+
function courses(part, units, opening, chips, paint) {
|
|
26661
27225
|
const [u0, v0, u1, v1] = part.aabb;
|
|
26662
27226
|
const alongU = u1 - u0 >= v1 - v0;
|
|
26663
27227
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
@@ -26681,23 +27245,54 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26681
27245
|
const x = alongU ? u0 + mid : (u0 + u1) / 2;
|
|
26682
27246
|
const z = alongU ? (v0 + v1) / 2 : v0 + mid;
|
|
26683
27247
|
const y = part.y0 + (r + 0.5) * rowH;
|
|
26684
|
-
const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
|
|
26685
|
-
if (d < opening.radius) continue;
|
|
26686
|
-
|
|
27248
|
+
const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
|
|
27249
|
+
if (opening && d < opening.radius) continue;
|
|
27250
|
+
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);
|
|
27251
|
+
if (chips.some(gone)) continue;
|
|
27252
|
+
const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
|
|
27253
|
+
list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
|
|
26687
27254
|
}
|
|
26688
27255
|
}
|
|
26689
27256
|
return list2;
|
|
26690
27257
|
}
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
26694
|
-
|
|
26695
|
-
|
|
26696
|
-
|
|
26697
|
-
|
|
26698
|
-
|
|
27258
|
+
const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
|
|
27259
|
+
const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
|
|
27260
|
+
function hostOf(partId) {
|
|
27261
|
+
if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
|
|
27262
|
+
const part = structure.byId.get(partId);
|
|
27263
|
+
if (!part) return null;
|
|
27264
|
+
for (const q of structure.parts) {
|
|
27265
|
+
if (q.id !== partId && same(q.aabb, part.aabb) && Math.abs(q.y1 - part.y0) < 0.01 && structure.meshesOf(q.id).some((it) => it.mesh)) return q.id;
|
|
27266
|
+
}
|
|
27267
|
+
return null;
|
|
27268
|
+
}
|
|
27269
|
+
const riders = (hostId) => structure.parts.filter((q) => q.id !== hostId && specOf(q.id)?.units && hostOf(q.id) === hostId && !structure.meshesOf(q.id).some((it) => it.mesh)).map((q) => q.id);
|
|
27270
|
+
function draw2(partId, change) {
|
|
27271
|
+
if (lod !== 0) return false;
|
|
27272
|
+
const hostId = hostOf(partId);
|
|
27273
|
+
if (!hostId || !specOf(partId)?.units) return false;
|
|
27274
|
+
let view = views.get(hostId);
|
|
27275
|
+
if (!view) {
|
|
27276
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
27277
|
+
if (!items.length) return false;
|
|
27278
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
27279
|
+
view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
|
|
27280
|
+
}
|
|
27281
|
+
if (!view.chips.has(partId)) return false;
|
|
27282
|
+
if (change.opening) {
|
|
27283
|
+
if (view.openings.has(partId)) return false;
|
|
27284
|
+
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
27285
|
+
if (partId === hostId) view.opening = view.openings.get(partId);
|
|
27286
|
+
}
|
|
27287
|
+
if (change.chip) view.chips.get(partId).push(local(change.chip));
|
|
27288
|
+
const list2 = [];
|
|
27289
|
+
for (const [id, chips] of view.chips) list2.push(...courses(structure.byId.get(id), specOf(id).units, view.openings.get(id) ?? null, chips, view.paint));
|
|
27290
|
+
if (view.mesh) {
|
|
27291
|
+
view.mesh.removeFromParent();
|
|
27292
|
+
view.mesh.dispose();
|
|
27293
|
+
}
|
|
26699
27294
|
const mesh2 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
26700
|
-
mesh2.name = "breach:" +
|
|
27295
|
+
mesh2.name = "breach:" + hostId;
|
|
26701
27296
|
list2.forEach((u, i) => {
|
|
26702
27297
|
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
26703
27298
|
mesh2.setMatrixAt(i, tmp.m);
|
|
@@ -26709,8 +27304,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26709
27304
|
mesh2.castShadow = true;
|
|
26710
27305
|
mesh2.receiveShadow = true;
|
|
26711
27306
|
house.add(mesh2);
|
|
26712
|
-
|
|
26713
|
-
|
|
27307
|
+
if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
27308
|
+
view.mesh = mesh2;
|
|
27309
|
+
view.units = list2.length;
|
|
27310
|
+
views.set(hostId, view);
|
|
26714
27311
|
invalidate();
|
|
26715
27312
|
invalidateShadow();
|
|
26716
27313
|
return true;
|
|
@@ -26724,7 +27321,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26724
27321
|
return {
|
|
26725
27322
|
/** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
|
|
26726
27323
|
stage(event) {
|
|
26727
|
-
return event.stage === 2 ?
|
|
27324
|
+
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
27325
|
+
},
|
|
27326
|
+
/** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
|
|
27327
|
+
chip(event) {
|
|
27328
|
+
return draw2(event.part, { chip: event.center });
|
|
26728
27329
|
},
|
|
26729
27330
|
/** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
|
|
26730
27331
|
drop(partId) {
|
|
@@ -26954,6 +27555,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26954
27555
|
case "stage":
|
|
26955
27556
|
breach.stage(e);
|
|
26956
27557
|
break;
|
|
27558
|
+
case "chip":
|
|
27559
|
+
breach.chip(e);
|
|
27560
|
+
break;
|
|
26957
27561
|
case "collapse":
|
|
26958
27562
|
for (const id of e.parts) {
|
|
26959
27563
|
breach.drop(id);
|
|
@@ -27008,6 +27612,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27008
27612
|
if (D.active && D.world.has(D.id)) D.world.remove(D.id);
|
|
27009
27613
|
D.active = false;
|
|
27010
27614
|
};
|
|
27615
|
+
D.follow = (world, id, spec) => {
|
|
27616
|
+
D.spec = spec;
|
|
27617
|
+
const offFollow = world.on("*", (e) => {
|
|
27618
|
+
if (e.object !== id) return;
|
|
27619
|
+
switch (e.type) {
|
|
27620
|
+
case "stage":
|
|
27621
|
+
breach.stage(e);
|
|
27622
|
+
break;
|
|
27623
|
+
case "chip":
|
|
27624
|
+
breach.chip(e);
|
|
27625
|
+
break;
|
|
27626
|
+
case "fracture":
|
|
27627
|
+
breach.drop(e.part);
|
|
27628
|
+
break;
|
|
27629
|
+
case "collapse":
|
|
27630
|
+
for (const p of e.parts) breach.drop(p);
|
|
27631
|
+
break;
|
|
27632
|
+
default:
|
|
27633
|
+
break;
|
|
27634
|
+
}
|
|
27635
|
+
});
|
|
27636
|
+
return { restore: () => breach.restore(), dispose: () => {
|
|
27637
|
+
offFollow();
|
|
27638
|
+
breach.restore();
|
|
27639
|
+
} };
|
|
27640
|
+
};
|
|
27011
27641
|
D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
|
|
27012
27642
|
D.state = () => D.active ? D.world.state(D.id) : null;
|
|
27013
27643
|
D.reset = () => {
|
|
@@ -27730,6 +28360,56 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27730
28360
|
return buildApi(K, shared);
|
|
27731
28361
|
}
|
|
27732
28362
|
var WOOD_THIN = 0.035;
|
|
28363
|
+
var BOARD_TOP_THICK = 0.07;
|
|
28364
|
+
var BOARD_TOP_WIDTH = 0.5;
|
|
28365
|
+
var BOARD_WIDTH = 0.16;
|
|
28366
|
+
var SHELF_SPAN = 1.5;
|
|
28367
|
+
var SHELVING = /shelf|bookcase|rack/;
|
|
28368
|
+
function carcass(parts, drawn) {
|
|
28369
|
+
const all = [...drawn.values()];
|
|
28370
|
+
const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
|
|
28371
|
+
for (const part of parts) {
|
|
28372
|
+
if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
|
|
28373
|
+
const [x, y, z] = part.shape.size;
|
|
28374
|
+
if (y >= x && y >= z) {
|
|
28375
|
+
if (z < x && x >= 0.6 * width) continue;
|
|
28376
|
+
part.structure = { role: "column" };
|
|
28377
|
+
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
28378
|
+
}
|
|
28379
|
+
}
|
|
28380
|
+
function splitTopsIntoBoards(drawn, materialOf, carcassed) {
|
|
28381
|
+
const tops = /* @__PURE__ */ new Map();
|
|
28382
|
+
if (carcassed) return tops;
|
|
28383
|
+
for (let i = drawn.length - 1; i >= 0; i--) {
|
|
28384
|
+
const p = drawn[i];
|
|
28385
|
+
if (p.key !== "wood" || materialOf.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
|
|
28386
|
+
const [sx, sy, sz] = p.shape.size;
|
|
28387
|
+
const across2 = sx < sz ? 0 : 2;
|
|
28388
|
+
const width = Math.min(sx, sz);
|
|
28389
|
+
if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
|
|
28390
|
+
const count = Math.round(width / BOARD_WIDTH);
|
|
28391
|
+
const boards = [];
|
|
28392
|
+
for (let b = 0; b < count; b++) {
|
|
28393
|
+
const size = [sx, sy, sz];
|
|
28394
|
+
size[across2] = width / count;
|
|
28395
|
+
const position = [...p.position];
|
|
28396
|
+
position[across2] += (b + 0.5) * size[across2] - width / 2;
|
|
28397
|
+
const min = [...p.min];
|
|
28398
|
+
const max = [...p.max];
|
|
28399
|
+
min[across2] = position[across2] - size[across2] / 2;
|
|
28400
|
+
max[across2] = position[across2] + size[across2] / 2;
|
|
28401
|
+
boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min, max, color: shadeBoard(p.color, b) });
|
|
28402
|
+
}
|
|
28403
|
+
drawn.splice(i, 1, ...boards);
|
|
28404
|
+
tops.set(p.id, boards);
|
|
28405
|
+
}
|
|
28406
|
+
return tops;
|
|
28407
|
+
}
|
|
28408
|
+
function shadeBoard(color2, board) {
|
|
28409
|
+
const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
|
|
28410
|
+
const channel = (shift) => Math.min(255, Math.round((color2 >> shift & 255) * k)) << shift;
|
|
28411
|
+
return channel(16) | channel(8) | channel(0);
|
|
28412
|
+
}
|
|
27733
28413
|
var FLOOR_TOUCH = 5e-3;
|
|
27734
28414
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
27735
28415
|
var BASE_MATERIAL = {
|
|
@@ -27807,10 +28487,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27807
28487
|
};
|
|
27808
28488
|
run(A);
|
|
27809
28489
|
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
28490
|
+
const boardsOf = splitTopsIntoBoards(drawn, materialOf, Boolean(opts.carcass));
|
|
27810
28491
|
const parts = drawn.map((p) => {
|
|
27811
28492
|
const thin = Math.min(...p.shape.kind === "box" ? p.shape.size : p.shape.kind === "cylinder" ? [p.shape.radius * 2, p.shape.height] : [p.shape.radius * 2]);
|
|
27812
28493
|
let material = materialOf[p.key] ?? "plastic";
|
|
27813
|
-
if (p.key === "wood" && material === "wood_hard" &&
|
|
28494
|
+
if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
|
|
28495
|
+
else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
27814
28496
|
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
27815
28497
|
if (p.rotation) part.rotation = p.rotation;
|
|
27816
28498
|
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
@@ -27845,6 +28527,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27845
28527
|
if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
27846
28528
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
27847
28529
|
}
|
|
28530
|
+
for (const boards of boardsOf.values()) {
|
|
28531
|
+
if (top && boards[0].max[1] <= top.y - 1e-6) continue;
|
|
28532
|
+
const mid = boards[boards.length >> 1];
|
|
28533
|
+
const min = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
|
|
28534
|
+
const max = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
|
|
28535
|
+
if ((max[0] - min[0]) * (max[1] - min[1]) >= 0.6 * footprint && mid.max[1] >= 0.2) top = { part: mid.id, y: mid.max[1], parts: boards.map((b) => b.id) };
|
|
28536
|
+
}
|
|
28537
|
+
if (opts.carcass) carcass(parts, byId);
|
|
27848
28538
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
27849
28539
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|
|
27850
28540
|
const build = (T3) => {
|
|
@@ -27888,7 +28578,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27888
28578
|
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`);
|
|
27889
28579
|
const o = opts.options ?? {};
|
|
27890
28580
|
const seed = Math.round(opts.seed ?? 1);
|
|
27891
|
-
const plain2 =
|
|
28581
|
+
const plain2 = SHELVING.test(type) ? "paper" : "plastic";
|
|
27892
28582
|
const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
|
|
27893
28583
|
const { K, shared } = renderer.internals;
|
|
27894
28584
|
const before = K.PARTS.length;
|
|
@@ -27907,7 +28597,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27907
28597
|
shared.ctx = saved.ctx;
|
|
27908
28598
|
}
|
|
27909
28599
|
},
|
|
27910
|
-
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs }
|
|
28600
|
+
{ materials: { plain: plain2, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
|
|
27911
28601
|
);
|
|
27912
28602
|
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]);
|
|
27913
28603
|
K.PARTS.length = before;
|
|
@@ -28833,6 +29523,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
28833
29523
|
const module = {
|
|
28834
29524
|
id: "items_area",
|
|
28835
29525
|
name: text.name,
|
|
29526
|
+
ground: { surface: "tile_floor" },
|
|
28836
29527
|
group: text.group,
|
|
28837
29528
|
state,
|
|
28838
29529
|
placed: () => entries2.map((entry2) => ({ id: entry2.id, itemId: entry2.itemId, group: entry2.group, kind: entry2.kind, size: entry2.size, root: entry2.root })),
|