@adatechnology/game-items-three 0.3.2 → 0.4.1
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 +726 -122
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +64 -11
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.global.js
CHANGED
|
@@ -2640,8 +2640,8 @@ var GameItemsThree = (() => {
|
|
|
2640
2640
|
bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
|
|
2641
2641
|
roughness = 0.55;
|
|
2642
2642
|
}
|
|
2643
|
-
const
|
|
2644
|
-
bed[index] = bedHeight + (
|
|
2643
|
+
const noise3 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
|
|
2644
|
+
bed[index] = bedHeight + (noise3 - 0.5) * roughness * 2;
|
|
2645
2645
|
vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
|
|
2646
2646
|
pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
|
|
2647
2647
|
}
|
|
@@ -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
|
}
|
|
@@ -12730,7 +12782,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12730
12782
|
const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
|
|
12731
12783
|
band.c[L] = (lo + hi) / 2;
|
|
12732
12784
|
band.s[L] = Math.max(hi - lo, 0.01);
|
|
12733
|
-
|
|
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);
|
|
12734
12788
|
return out;
|
|
12735
12789
|
};
|
|
12736
12790
|
var tearStack = (inp, root, t, u, v, n, sheet) => {
|
|
@@ -12761,6 +12815,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12761
12815
|
const t = sorted[0];
|
|
12762
12816
|
const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
|
|
12763
12817
|
if (style === "snap") {
|
|
12818
|
+
if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
|
|
12764
12819
|
const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
|
|
12765
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] });
|
|
12766
12821
|
return out;
|
|
@@ -12934,6 +12989,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
12934
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]]));
|
|
12935
12990
|
return out;
|
|
12936
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
|
+
};
|
|
12937
13013
|
var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
12938
13014
|
const out = [];
|
|
12939
13015
|
for (const [a, b] of bars) {
|
|
@@ -13854,14 +13930,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13854
13930
|
smoke: "grey"
|
|
13855
13931
|
},
|
|
13856
13932
|
visual: {
|
|
13857
|
-
charColor: 1709072
|
|
13933
|
+
charColor: 1709072,
|
|
13934
|
+
innerColor: 14730131
|
|
13858
13935
|
},
|
|
13859
13936
|
tags: [
|
|
13860
13937
|
"wood",
|
|
13861
13938
|
"organic",
|
|
13862
13939
|
"flammable"
|
|
13863
13940
|
],
|
|
13864
|
-
compressive: 1e5
|
|
13941
|
+
compressive: 1e5,
|
|
13942
|
+
hits: {
|
|
13943
|
+
blunt: {
|
|
13944
|
+
tear: "splinters",
|
|
13945
|
+
tearJoules: 60
|
|
13946
|
+
}
|
|
13947
|
+
}
|
|
13865
13948
|
},
|
|
13866
13949
|
{
|
|
13867
13950
|
id: "wood_hard",
|
|
@@ -13891,14 +13974,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13891
13974
|
smoke: "grey"
|
|
13892
13975
|
},
|
|
13893
13976
|
visual: {
|
|
13894
|
-
charColor: 1709072
|
|
13977
|
+
charColor: 1709072,
|
|
13978
|
+
innerColor: 13213802
|
|
13895
13979
|
},
|
|
13896
13980
|
tags: [
|
|
13897
13981
|
"wood",
|
|
13898
13982
|
"organic",
|
|
13899
13983
|
"flammable"
|
|
13900
13984
|
],
|
|
13901
|
-
compressive: 15e4
|
|
13985
|
+
compressive: 15e4,
|
|
13986
|
+
hits: {
|
|
13987
|
+
blunt: {
|
|
13988
|
+
tear: "splinters",
|
|
13989
|
+
tearJoules: 150
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13902
13992
|
},
|
|
13903
13993
|
{
|
|
13904
13994
|
id: "plywood",
|
|
@@ -13928,14 +14018,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13928
14018
|
smoke: "grey"
|
|
13929
14019
|
},
|
|
13930
14020
|
visual: {
|
|
13931
|
-
charColor: 1709072
|
|
14021
|
+
charColor: 1709072,
|
|
14022
|
+
innerColor: 14862234
|
|
13932
14023
|
},
|
|
13933
14024
|
tags: [
|
|
13934
14025
|
"wood",
|
|
13935
14026
|
"organic",
|
|
13936
14027
|
"flammable"
|
|
13937
14028
|
],
|
|
13938
|
-
compressive: 6e4
|
|
14029
|
+
compressive: 6e4,
|
|
14030
|
+
hits: {
|
|
14031
|
+
ballistic: {
|
|
14032
|
+
tear: "splinters"
|
|
14033
|
+
},
|
|
14034
|
+
blunt: {
|
|
14035
|
+
tear: "splinters",
|
|
14036
|
+
tearJoules: 80
|
|
14037
|
+
}
|
|
14038
|
+
}
|
|
13939
14039
|
},
|
|
13940
14040
|
{
|
|
13941
14041
|
id: "mdf",
|
|
@@ -13962,14 +14062,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
13962
14062
|
smoke: "grey"
|
|
13963
14063
|
},
|
|
13964
14064
|
visual: {
|
|
13965
|
-
charColor: 1709072
|
|
14065
|
+
charColor: 1709072,
|
|
14066
|
+
innerColor: 12096362
|
|
13966
14067
|
},
|
|
13967
14068
|
tags: [
|
|
13968
14069
|
"wood",
|
|
13969
14070
|
"organic",
|
|
13970
14071
|
"flammable"
|
|
13971
14072
|
],
|
|
13972
|
-
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
|
+
}
|
|
13973
14084
|
},
|
|
13974
14085
|
{
|
|
13975
14086
|
id: "glass",
|
|
@@ -14083,8 +14194,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14083
14194
|
damageScale: {
|
|
14084
14195
|
explosive: 1.2
|
|
14085
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
|
+
},
|
|
14086
14208
|
hitDebris: "chips",
|
|
14087
14209
|
breakDebris: "dust",
|
|
14210
|
+
visual: { innerColor: 10197652 },
|
|
14088
14211
|
thermal: {
|
|
14089
14212
|
conductivity: 0.15,
|
|
14090
14213
|
heatCapacity: 2.5
|
|
@@ -14115,6 +14238,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14115
14238
|
],
|
|
14116
14239
|
hitDebris: "chips",
|
|
14117
14240
|
breakDebris: "dust",
|
|
14241
|
+
visual: { innerColor: 12085567 },
|
|
14118
14242
|
thermal: {
|
|
14119
14243
|
conductivity: 0.15,
|
|
14120
14244
|
heatCapacity: 2.5
|
|
@@ -14145,6 +14269,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14145
14269
|
],
|
|
14146
14270
|
hitDebris: "dust",
|
|
14147
14271
|
breakDebris: "dust",
|
|
14272
|
+
visual: { innerColor: 14999507 },
|
|
14148
14273
|
thermal: {
|
|
14149
14274
|
conductivity: 0.1,
|
|
14150
14275
|
heatCapacity: 1.5
|
|
@@ -14170,6 +14295,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14170
14295
|
],
|
|
14171
14296
|
hitDebris: "chips",
|
|
14172
14297
|
breakDebris: "dust",
|
|
14298
|
+
visual: { innerColor: 9407364 },
|
|
14173
14299
|
thermal: {
|
|
14174
14300
|
conductivity: 0.2,
|
|
14175
14301
|
heatCapacity: 2.5
|
|
@@ -14451,7 +14577,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14451
14577
|
smoke: "white"
|
|
14452
14578
|
},
|
|
14453
14579
|
visual: {
|
|
14454
|
-
charColor: 1709072
|
|
14580
|
+
charColor: 1709072,
|
|
14581
|
+
innerColor: 14207126
|
|
14455
14582
|
},
|
|
14456
14583
|
tags: [
|
|
14457
14584
|
"plant",
|
|
@@ -14459,7 +14586,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14459
14586
|
"organic",
|
|
14460
14587
|
"flammable"
|
|
14461
14588
|
],
|
|
14462
|
-
compressive: 6e4
|
|
14589
|
+
compressive: 6e4,
|
|
14590
|
+
hits: {
|
|
14591
|
+
blunt: {
|
|
14592
|
+
tear: "none"
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14463
14595
|
},
|
|
14464
14596
|
{
|
|
14465
14597
|
id: "cactus",
|
|
@@ -14574,6 +14706,130 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14574
14706
|
"masonry"
|
|
14575
14707
|
],
|
|
14576
14708
|
compressive: 2e4
|
|
14709
|
+
},
|
|
14710
|
+
{
|
|
14711
|
+
id: "asphalt",
|
|
14712
|
+
density: 2350,
|
|
14713
|
+
hpPerKg: 10,
|
|
14714
|
+
penetration: 14,
|
|
14715
|
+
penetrationClass: "concrete",
|
|
14716
|
+
penetrationScale: 0.8,
|
|
14717
|
+
ductility: 0.1,
|
|
14718
|
+
fracture: "crumble",
|
|
14719
|
+
fragmentSize: [
|
|
14720
|
+
0.03,
|
|
14721
|
+
0.15
|
|
14722
|
+
],
|
|
14723
|
+
hitDebris: "chips",
|
|
14724
|
+
breakDebris: "dust",
|
|
14725
|
+
thermal: {
|
|
14726
|
+
conductivity: 0.1,
|
|
14727
|
+
heatCapacity: 2,
|
|
14728
|
+
ignitionC: 400,
|
|
14729
|
+
burnRate: 2e-3,
|
|
14730
|
+
smoke: "black"
|
|
14731
|
+
},
|
|
14732
|
+
tags: [
|
|
14733
|
+
"masonry",
|
|
14734
|
+
"brittle"
|
|
14735
|
+
],
|
|
14736
|
+
compressive: 3e4
|
|
14737
|
+
},
|
|
14738
|
+
{
|
|
14739
|
+
id: "sand",
|
|
14740
|
+
density: 1600,
|
|
14741
|
+
hpPerKg: 2,
|
|
14742
|
+
penetration: 6,
|
|
14743
|
+
ductility: 0.4,
|
|
14744
|
+
fracture: "crumble",
|
|
14745
|
+
fragmentSize: [
|
|
14746
|
+
0.01,
|
|
14747
|
+
0.04
|
|
14748
|
+
],
|
|
14749
|
+
hitDebris: "dust",
|
|
14750
|
+
breakDebris: "dust",
|
|
14751
|
+
thermal: {
|
|
14752
|
+
conductivity: 0.1,
|
|
14753
|
+
heatCapacity: 2
|
|
14754
|
+
},
|
|
14755
|
+
tags: [
|
|
14756
|
+
"soil"
|
|
14757
|
+
],
|
|
14758
|
+
compressive: 1e4
|
|
14759
|
+
},
|
|
14760
|
+
{
|
|
14761
|
+
id: "gravel",
|
|
14762
|
+
density: 1800,
|
|
14763
|
+
hpPerKg: 3,
|
|
14764
|
+
penetration: 8,
|
|
14765
|
+
ductility: 0.3,
|
|
14766
|
+
fracture: "crumble",
|
|
14767
|
+
fragmentSize: [
|
|
14768
|
+
0.02,
|
|
14769
|
+
0.06
|
|
14770
|
+
],
|
|
14771
|
+
hitDebris: "chips",
|
|
14772
|
+
breakDebris: "dust",
|
|
14773
|
+
thermal: {
|
|
14774
|
+
conductivity: 0.1,
|
|
14775
|
+
heatCapacity: 2
|
|
14776
|
+
},
|
|
14777
|
+
tags: [
|
|
14778
|
+
"soil",
|
|
14779
|
+
"masonry"
|
|
14780
|
+
],
|
|
14781
|
+
compressive: 2e4
|
|
14782
|
+
},
|
|
14783
|
+
{
|
|
14784
|
+
id: "turf",
|
|
14785
|
+
density: 900,
|
|
14786
|
+
hpPerKg: 3,
|
|
14787
|
+
penetration: 4,
|
|
14788
|
+
ductility: 0.5,
|
|
14789
|
+
fracture: "crumble",
|
|
14790
|
+
fragmentSize: [
|
|
14791
|
+
0.03,
|
|
14792
|
+
0.1
|
|
14793
|
+
],
|
|
14794
|
+
hitDebris: "dust",
|
|
14795
|
+
breakDebris: "dust",
|
|
14796
|
+
thermal: {
|
|
14797
|
+
conductivity: 0.1,
|
|
14798
|
+
heatCapacity: 1.5,
|
|
14799
|
+
ignitionC: 250,
|
|
14800
|
+
burnRate: 0.03,
|
|
14801
|
+
smoke: "white"
|
|
14802
|
+
},
|
|
14803
|
+
tags: [
|
|
14804
|
+
"soil",
|
|
14805
|
+
"organic"
|
|
14806
|
+
],
|
|
14807
|
+
compressive: 15e3
|
|
14808
|
+
},
|
|
14809
|
+
{
|
|
14810
|
+
id: "mortar",
|
|
14811
|
+
density: 1900,
|
|
14812
|
+
hpPerKg: 10,
|
|
14813
|
+
penetration: 12,
|
|
14814
|
+
penetrationClass: "concrete",
|
|
14815
|
+
penetrationScale: 1.2,
|
|
14816
|
+
ductility: 0.03,
|
|
14817
|
+
fracture: "crumble",
|
|
14818
|
+
fragmentSize: [
|
|
14819
|
+
0.02,
|
|
14820
|
+
0.08
|
|
14821
|
+
],
|
|
14822
|
+
hitDebris: "chips",
|
|
14823
|
+
breakDebris: "dust",
|
|
14824
|
+
thermal: {
|
|
14825
|
+
conductivity: 0.15,
|
|
14826
|
+
heatCapacity: 2.5
|
|
14827
|
+
},
|
|
14828
|
+
tags: [
|
|
14829
|
+
"masonry",
|
|
14830
|
+
"brittle"
|
|
14831
|
+
],
|
|
14832
|
+
compressive: 1e5
|
|
14577
14833
|
}
|
|
14578
14834
|
];
|
|
14579
14835
|
var material_hints_default = {
|
|
@@ -14747,6 +15003,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14747
15003
|
return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
|
|
14748
15004
|
};
|
|
14749
15005
|
var THIN_SHEET_DISHING = 5e-3;
|
|
15006
|
+
var SWEEP_BACK = 0.01;
|
|
15007
|
+
function sweepOrigins(box4, d, spacing) {
|
|
15008
|
+
const origins = [];
|
|
15009
|
+
const line = (from, to) => {
|
|
15010
|
+
const n = Math.max(1, Math.ceil((to - from) / spacing));
|
|
15011
|
+
return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
|
|
15012
|
+
};
|
|
15013
|
+
for (const axis of [0, 1, 2]) {
|
|
15014
|
+
if (Math.abs(d[axis]) < 1e-3) continue;
|
|
15015
|
+
const lead = d[axis] > 0 ? box4.max[axis] - SWEEP_BACK : box4.min[axis] + SWEEP_BACK;
|
|
15016
|
+
const u = (axis + 1) % 3;
|
|
15017
|
+
const v = (axis + 2) % 3;
|
|
15018
|
+
for (const a of line(box4.min[u], box4.max[u])) {
|
|
15019
|
+
for (const b of line(box4.min[v], box4.max[v])) {
|
|
15020
|
+
const point = [0, 0, 0];
|
|
15021
|
+
point[axis] = lead;
|
|
15022
|
+
point[u] = a;
|
|
15023
|
+
point[v] = b;
|
|
15024
|
+
origins.push(point);
|
|
15025
|
+
}
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
return origins;
|
|
15029
|
+
}
|
|
15030
|
+
var THIN_SHEET_CRUSH = 5e-4;
|
|
15031
|
+
var THIN_SHEET_CONTACT = 0.025;
|
|
14750
15032
|
var DMG_PER_J = 0.02;
|
|
14751
15033
|
var BLAST_COUPLING = 0.1;
|
|
14752
15034
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
@@ -14763,11 +15045,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14763
15045
|
var SEVER_BAND = 0.1;
|
|
14764
15046
|
var SEVER_ASPECT = 3;
|
|
14765
15047
|
var SEVER_BALLISTIC = 2;
|
|
15048
|
+
var TEAR_JOULES = 150;
|
|
15049
|
+
var tears = 0;
|
|
14766
15050
|
var SUPPORT_TOLERANCE = 0.05;
|
|
14767
15051
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
14768
15052
|
var BREACH_GRID_CAP = 4096;
|
|
14769
15053
|
var CHIP_ENERGY = 200;
|
|
14770
15054
|
var CHIP_SPLASH = 0.5;
|
|
15055
|
+
var BAR_BOW = 0.08;
|
|
15056
|
+
var BULLET_JOINT_SPAN = 0.3;
|
|
14771
15057
|
var CUT_SLICE = 1;
|
|
14772
15058
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
14773
15059
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -14777,8 +15063,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
14777
15063
|
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
14778
15064
|
};
|
|
14779
15065
|
var capacityScale = (p) => {
|
|
14780
|
-
|
|
14781
|
-
|
|
15066
|
+
const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
15067
|
+
if (p.st.stripped) return residual;
|
|
15068
|
+
const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
|
|
15069
|
+
return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
|
|
15070
|
+
};
|
|
15071
|
+
var sectionLoss = (p) => {
|
|
15072
|
+
const cells = p.st.chipped;
|
|
15073
|
+
if (!cells?.length) return 0;
|
|
15074
|
+
const [u, v] = planeAxes(p.thin);
|
|
15075
|
+
const along = p.dims[u] >= p.dims[v] ? u : v;
|
|
15076
|
+
const across2 = along === u ? v : u;
|
|
15077
|
+
let worst = 0;
|
|
15078
|
+
for (const c of cells) {
|
|
15079
|
+
let gone = 0;
|
|
15080
|
+
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across2];
|
|
15081
|
+
worst = Math.max(worst, gone / Math.max(p.dims[across2], 1e-6));
|
|
15082
|
+
}
|
|
15083
|
+
return Math.min(1, worst);
|
|
14782
15084
|
};
|
|
14783
15085
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
14784
15086
|
var clone = (v) => structuredClone(v);
|
|
@@ -15878,7 +16180,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15878
16180
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
15879
16181
|
setStage(o, p, 1, {}, src);
|
|
15880
16182
|
};
|
|
15881
|
-
const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
|
|
16183
|
+
const debris = (o, p, kind, point, normal, count, speed, src, material) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
|
|
15882
16184
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
15883
16185
|
const detachEvents = (o, opened, by, src) => {
|
|
15884
16186
|
for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
|
|
@@ -15919,7 +16221,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15919
16221
|
const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
|
|
15920
16222
|
return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
|
|
15921
16223
|
};
|
|
15922
|
-
const
|
|
16224
|
+
const jointLength = (o, j) => {
|
|
16225
|
+
const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
|
|
16226
|
+
let len = 0;
|
|
16227
|
+
for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
|
|
16228
|
+
return len;
|
|
16229
|
+
};
|
|
16230
|
+
const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
|
|
15923
16231
|
if (amount <= 0) return;
|
|
15924
16232
|
const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
|
|
15925
16233
|
if (split && mine.length) amount /= mine.length;
|
|
@@ -15927,7 +16235,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15927
16235
|
if (j.st.open) continue;
|
|
15928
16236
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
15929
16237
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
15930
|
-
j.st.hp -= amount;
|
|
16238
|
+
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
15931
16239
|
if (j.st.hp <= 0) {
|
|
15932
16240
|
j.st.hp = 0;
|
|
15933
16241
|
j.st.open = true;
|
|
@@ -15956,7 +16264,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
15956
16264
|
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
15957
16265
|
markDamaged(p);
|
|
15958
16266
|
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
15959
|
-
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
16267
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
|
|
16268
|
+
setStage(o, p, 1, {}, src);
|
|
16269
|
+
};
|
|
16270
|
+
const crater = (o, p, wp, normal, radius, src) => {
|
|
16271
|
+
markDamaged(p);
|
|
16272
|
+
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
|
|
16273
|
+
debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
|
|
15960
16274
|
setStage(o, p, 1, {}, src);
|
|
15961
16275
|
};
|
|
15962
16276
|
const insideOpening = (o, p, objectPoint) => {
|
|
@@ -16035,9 +16349,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16035
16349
|
const lp = toLocal(p.pos, p.q, objectPoint);
|
|
16036
16350
|
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);
|
|
16037
16351
|
};
|
|
16038
|
-
const chipUnits = (o, p, wp,
|
|
16352
|
+
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
16039
16353
|
var _a, _b;
|
|
16040
|
-
if (!p.spec.units ||
|
|
16354
|
+
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
16355
|
+
const rule = p.mat.hits?.[kind];
|
|
16356
|
+
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
16357
|
+
const energy = hitEnergy - (rule?.floor ?? 0);
|
|
16358
|
+
if (perKg <= 0 || energy <= 0) return;
|
|
16041
16359
|
const [u, v] = planeAxes(p.thin);
|
|
16042
16360
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
16043
16361
|
const grid = gridOf(p);
|
|
@@ -16051,7 +16369,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16051
16369
|
const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
|
|
16052
16370
|
const total = (worn[i] ?? 0) + energy * share;
|
|
16053
16371
|
const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
|
|
16054
|
-
if (total <
|
|
16372
|
+
if (total < perKg * mass) {
|
|
16055
16373
|
worn[i] = total;
|
|
16056
16374
|
continue;
|
|
16057
16375
|
}
|
|
@@ -16076,7 +16394,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16076
16394
|
emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
|
|
16077
16395
|
debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
|
|
16078
16396
|
}
|
|
16397
|
+
if (p.spec.reinforcement) exposeRebar(o, p, src);
|
|
16079
16398
|
for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
|
|
16399
|
+
if (p.structural && !isLost(p)) {
|
|
16400
|
+
solveStructure(o);
|
|
16401
|
+
collapseCheck(o, src);
|
|
16402
|
+
}
|
|
16403
|
+
};
|
|
16404
|
+
const exposeRebar = (o, p, src) => {
|
|
16405
|
+
const r = p.spec.reinforcement;
|
|
16406
|
+
if (!r) return;
|
|
16407
|
+
const [u, v] = planeAxes(p.thin);
|
|
16408
|
+
const all = rebarBars(p.dims, p.thin, r.spacing);
|
|
16409
|
+
let bars;
|
|
16410
|
+
if (p.st.stripped) bars = all;
|
|
16411
|
+
else {
|
|
16412
|
+
bars = [];
|
|
16413
|
+
for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
|
|
16414
|
+
const op = p.st.opening;
|
|
16415
|
+
if (op) {
|
|
16416
|
+
const lc = toLocal(p.pos, p.q, op.center);
|
|
16417
|
+
bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
|
|
16418
|
+
}
|
|
16419
|
+
}
|
|
16420
|
+
const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
|
|
16421
|
+
if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
|
|
16422
|
+
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
16080
16423
|
};
|
|
16081
16424
|
const openBreach = (o, p, center, total, energy, dir, src) => {
|
|
16082
16425
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -16104,10 +16447,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16104
16447
|
setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
|
|
16105
16448
|
debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
|
|
16106
16449
|
const reinforcement = p.spec.reinforcement;
|
|
16107
|
-
if (reinforcement)
|
|
16108
|
-
const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
|
|
16109
|
-
if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
|
|
16110
|
-
}
|
|
16450
|
+
if (reinforcement) exposeRebar(o, p, src);
|
|
16111
16451
|
if (p.structural) solveStructure(o);
|
|
16112
16452
|
};
|
|
16113
16453
|
const stripPart = (o, p, reinforcement, src) => {
|
|
@@ -16119,8 +16459,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16119
16459
|
p.st.burning = false;
|
|
16120
16460
|
p.rt.jet = false;
|
|
16121
16461
|
markDamaged(p);
|
|
16122
|
-
emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
|
|
16123
16462
|
solveStructure(o);
|
|
16463
|
+
exposeRebar(o, p, src);
|
|
16124
16464
|
collapseCheck(o, src);
|
|
16125
16465
|
};
|
|
16126
16466
|
const unitFragmentPlan = (p, cap) => {
|
|
@@ -16174,7 +16514,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16174
16514
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
16175
16515
|
const hitLocal = toLocal(c, q, hitW);
|
|
16176
16516
|
const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
|
|
16177
|
-
const axis = style === "splinter" ? severAxis(p) : -1;
|
|
16517
|
+
const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
|
|
16178
16518
|
const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
|
|
16179
16519
|
const plan = unitPlan.length > 0 ? unitPlan : planFragments({
|
|
16180
16520
|
style,
|
|
@@ -16246,7 +16586,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16246
16586
|
};
|
|
16247
16587
|
const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
|
|
16248
16588
|
const severAxis = (p) => {
|
|
16249
|
-
if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
|
|
16589
|
+
if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
|
|
16250
16590
|
const d = p.dims;
|
|
16251
16591
|
const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
|
|
16252
16592
|
return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
|
|
@@ -16307,6 +16647,49 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16307
16647
|
}
|
|
16308
16648
|
}
|
|
16309
16649
|
};
|
|
16650
|
+
const tearOf = (mat, kind) => {
|
|
16651
|
+
const h = mat.hits?.[kind];
|
|
16652
|
+
const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
|
|
16653
|
+
return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
|
|
16654
|
+
};
|
|
16655
|
+
const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
|
|
16656
|
+
const { tear, joules } = tearOf(p.mat, kind);
|
|
16657
|
+
if (tear === "none") return;
|
|
16658
|
+
const shot = kind === "ballistic";
|
|
16659
|
+
const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
|
|
16660
|
+
if (n === 0) return;
|
|
16661
|
+
const fibre = tear === "lumps";
|
|
16662
|
+
const { q } = partXf(o, p);
|
|
16663
|
+
const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
|
|
16664
|
+
const fragments = [];
|
|
16665
|
+
for (let i = 0; i < n; i++) {
|
|
16666
|
+
const back = !exit || i % 3 === 2;
|
|
16667
|
+
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());
|
|
16668
|
+
const size = [w, w, w];
|
|
16669
|
+
size[along] = len;
|
|
16670
|
+
size[(along + 1) % 3] = w;
|
|
16671
|
+
size[(along + 2) % 3] = t;
|
|
16672
|
+
const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
|
|
16673
|
+
const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
|
|
16674
|
+
const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
|
|
16675
|
+
const mass = size[0] * size[1] * size[2] * p.mat.density;
|
|
16676
|
+
fragments.push({
|
|
16677
|
+
id: `${p.spec.id}#t${tears++}-${i}`,
|
|
16678
|
+
part: p.spec.id,
|
|
16679
|
+
material: p.spec.material,
|
|
16680
|
+
shape: fibre ? "chunk" : "splinter",
|
|
16681
|
+
center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
|
|
16682
|
+
size,
|
|
16683
|
+
rotation: q,
|
|
16684
|
+
velocity: v,
|
|
16685
|
+
inner: true,
|
|
16686
|
+
angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
|
|
16687
|
+
mass
|
|
16688
|
+
});
|
|
16689
|
+
}
|
|
16690
|
+
const patch = [scale * 4, scale * 4, scale * 4];
|
|
16691
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
|
|
16692
|
+
};
|
|
16310
16693
|
const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
|
|
16311
16694
|
const mat = p.mat;
|
|
16312
16695
|
const piercing = round3.piercing ?? 1;
|
|
@@ -16335,6 +16718,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16335
16718
|
return { penetrated: false, exitEnergy: 0, damage: dmg2 };
|
|
16336
16719
|
}
|
|
16337
16720
|
if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
16721
|
+
else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
|
|
16338
16722
|
const brittle = mat.ductility < 0.3;
|
|
16339
16723
|
const ratio = Ep / Math.max(thr, 1e-6);
|
|
16340
16724
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
@@ -16367,9 +16751,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16367
16751
|
if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
16368
16752
|
} else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
|
|
16369
16753
|
debris(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
16370
|
-
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
16371
|
-
const
|
|
16372
|
-
|
|
16754
|
+
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
|
|
16755
|
+
const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
|
|
16756
|
+
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
|
|
16757
|
+
if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
16758
|
+
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);
|
|
16373
16759
|
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
16374
16760
|
};
|
|
16375
16761
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
@@ -16377,14 +16763,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16377
16763
|
const thrMm = mat.penetration * p.thickness * 1e3;
|
|
16378
16764
|
const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
|
|
16379
16765
|
const Ec = E * fragile;
|
|
16766
|
+
const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
|
|
16767
|
+
const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
|
|
16768
|
+
const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
|
|
16380
16769
|
if (E > 0) {
|
|
16381
16770
|
if (kind === "blunt") {
|
|
16382
|
-
if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(
|
|
16771
|
+
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);
|
|
16383
16772
|
else if (mat.ductility < 0.3) {
|
|
16384
16773
|
if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
|
|
16385
16774
|
} 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);
|
|
16386
16775
|
} else if (mat.fracture === "tear") {
|
|
16387
16776
|
addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
|
|
16777
|
+
} else if (mat.ductility >= 0.3 && E >= thrMm) {
|
|
16778
|
+
addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
|
|
16779
|
+
if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
|
|
16388
16780
|
} else if (mat.ductility >= 0.5) {
|
|
16389
16781
|
addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
|
|
16390
16782
|
}
|
|
@@ -16397,7 +16789,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16397
16789
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
16398
16790
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
16399
16791
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
16400
|
-
if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
|
|
16792
|
+
if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
|
|
16793
|
+
if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
|
|
16401
16794
|
return done;
|
|
16402
16795
|
};
|
|
16403
16796
|
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
@@ -16976,6 +17369,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
16976
17369
|
if (p.rt.stressT <= 0) {
|
|
16977
17370
|
p.rt.stressT = 0.5;
|
|
16978
17371
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
17372
|
+
if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
|
|
16979
17373
|
}
|
|
16980
17374
|
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
16981
17375
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
@@ -17192,6 +17586,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
17192
17586
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
17193
17587
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
17194
17588
|
},
|
|
17589
|
+
sweepBox(box4, direction, distance2, options2 = {}) {
|
|
17590
|
+
const d = vec.normalize(direction);
|
|
17591
|
+
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
17592
|
+
const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
|
|
17593
|
+
let best = null;
|
|
17594
|
+
for (const origin of sweepOrigins(box4, d, options2.spacing ?? 0.2)) {
|
|
17595
|
+
const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
|
|
17596
|
+
if (!r || best && r.distance >= best.distance) continue;
|
|
17597
|
+
best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
|
|
17598
|
+
}
|
|
17599
|
+
return best;
|
|
17600
|
+
},
|
|
17195
17601
|
shoot(origin, direction, energy, caliber, source, round3) {
|
|
17196
17602
|
const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
|
|
17197
17603
|
return withCollapses(run(() => {
|
|
@@ -18763,7 +19169,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18763
19169
|
}
|
|
18764
19170
|
return spec;
|
|
18765
19171
|
};
|
|
18766
|
-
var
|
|
19172
|
+
var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
|
|
19173
|
+
var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
|
|
19174
|
+
var caged = (spec, part, rebar2) => {
|
|
19175
|
+
if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
|
|
19176
|
+
const size = spec.shape.kind === "box" ? spec.shape.size : null;
|
|
19177
|
+
if (!size) return spec;
|
|
19178
|
+
const thin = Math.min(...size);
|
|
19179
|
+
return {
|
|
19180
|
+
...spec,
|
|
19181
|
+
reinforcement: { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
|
|
19182
|
+
units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
|
|
19183
|
+
};
|
|
19184
|
+
};
|
|
19185
|
+
var partSpecOf = (part, capacity, detailed, rebar2) => {
|
|
19186
|
+
if (detailed && part.assembly) return assemblySpec(part, capacity);
|
|
19187
|
+
const spec = boxSpec(part, capacity);
|
|
19188
|
+
return detailed ? caged(spec, part, rebar2) : spec;
|
|
19189
|
+
};
|
|
18767
19190
|
function toDestructibleSpec(structure, options = {}) {
|
|
18768
19191
|
const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
|
|
18769
19192
|
const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
|
|
@@ -18780,7 +19203,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
18780
19203
|
const sizing = { safety, redundancy, safetyOthers };
|
|
18781
19204
|
const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
|
|
18782
19205
|
const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
|
|
18783
|
-
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
|
|
19206
|
+
const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
|
|
18784
19207
|
const margin = options.terrainMargin ?? TERRAIN_MARGIN;
|
|
18785
19208
|
const x0 = Math.min(...parts.map((part) => part.aabb[0]));
|
|
18786
19209
|
const z0 = Math.min(...parts.map((part) => part.aabb[1]));
|
|
@@ -26745,7 +27168,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26745
27168
|
if (!pc || pc.unit) return sp;
|
|
26746
27169
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
26747
27170
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
26748
|
-
|
|
27171
|
+
const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
|
|
27172
|
+
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
27173
|
+
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
27174
|
+
}
|
|
27175
|
+
return out;
|
|
26749
27176
|
}
|
|
26750
27177
|
if (!pc.assembly) return sp;
|
|
26751
27178
|
const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
|
|
@@ -26780,6 +27207,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26780
27207
|
pos.array.set(saved, v0 * 3);
|
|
26781
27208
|
pos.needsUpdate = true;
|
|
26782
27209
|
}
|
|
27210
|
+
function noise2(x, y, cell) {
|
|
27211
|
+
const h = (i2, j2) => {
|
|
27212
|
+
const s = Math.sin(i2 * 127.1 + j2 * 311.7) * 43758.5453;
|
|
27213
|
+
return s - Math.floor(s);
|
|
27214
|
+
};
|
|
27215
|
+
const fx = x / cell, fy = y / cell, i = Math.floor(fx), j = Math.floor(fy), u = fx - i, v = fy - j;
|
|
27216
|
+
const su = u * u * (3 - 2 * u), sv = v * v * (3 - 2 * v);
|
|
27217
|
+
return (h(i, j) * (1 - su) + h(i + 1, j) * su) * (1 - sv) + (h(i, j + 1) * (1 - su) + h(i + 1, j + 1) * su) * sv;
|
|
27218
|
+
}
|
|
27219
|
+
var MASK_TEXEL = 0.02;
|
|
27220
|
+
var SPALL_MIN = 0.025;
|
|
27221
|
+
var SPALL_RAGGED = 0.045;
|
|
27222
|
+
var COVER2 = 0.015;
|
|
26783
27223
|
function installDestructionBreach(K, _shared) {
|
|
26784
27224
|
const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
|
|
26785
27225
|
function createBreachView(opts) {
|
|
@@ -26789,94 +27229,198 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26789
27229
|
const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
|
|
26790
27230
|
const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
26791
27231
|
const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
|
|
26792
|
-
const wallColour = (items) => {
|
|
26793
|
-
for (const it of items) {
|
|
26794
|
-
if (!it.mesh) continue;
|
|
26795
|
-
const c = it.mesh.geometry.attributes.color;
|
|
26796
|
-
if (!c) continue;
|
|
26797
|
-
const a = c.array;
|
|
26798
|
-
tmp.c.setRGB(a[it.v0 * 3], a[it.v0 * 3 + 1], a[it.v0 * 3 + 2]);
|
|
26799
|
-
return tmp.c.getHex();
|
|
26800
|
-
}
|
|
26801
|
-
return null;
|
|
26802
|
-
};
|
|
26803
27232
|
const local = (p) => {
|
|
26804
27233
|
house.updateMatrixWorld(true);
|
|
26805
27234
|
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
26806
27235
|
return [v.x, v.y, v.z];
|
|
26807
27236
|
};
|
|
26808
|
-
function
|
|
27237
|
+
function cells(part, units) {
|
|
26809
27238
|
const [u0, v0, u1, v1] = part.aabb;
|
|
26810
27239
|
const alongU = u1 - u0 >= v1 - v0;
|
|
26811
27240
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
26812
|
-
const thick = alongU ? v1 - v0 : u1 - u0;
|
|
26813
27241
|
const [ul, uh] = units.size;
|
|
26814
27242
|
const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
|
|
26815
27243
|
const rowH = (part.y1 - part.y0) / rows;
|
|
26816
27244
|
const per = Math.max(1, Math.round(len / ul));
|
|
26817
27245
|
const w = len / per;
|
|
26818
|
-
const
|
|
26819
|
-
const list2 = [];
|
|
27246
|
+
const out = [];
|
|
26820
27247
|
for (let r = 0; r < rows; r++) {
|
|
26821
27248
|
const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
|
|
26822
|
-
|
|
26823
|
-
|
|
26824
|
-
|
|
26825
|
-
|
|
26826
|
-
|
|
26827
|
-
|
|
26828
|
-
|
|
26829
|
-
|
|
26830
|
-
|
|
26831
|
-
|
|
26832
|
-
|
|
26833
|
-
|
|
26834
|
-
|
|
26835
|
-
|
|
26836
|
-
|
|
26837
|
-
|
|
27249
|
+
for (let i = 0; i < per + (shift ? 1 : 0); i++) {
|
|
27250
|
+
const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
|
|
27251
|
+
if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
|
|
27252
|
+
}
|
|
27253
|
+
}
|
|
27254
|
+
return out;
|
|
27255
|
+
}
|
|
27256
|
+
const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
|
|
27257
|
+
const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
|
|
27258
|
+
function hostOf(partId) {
|
|
27259
|
+
if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
|
|
27260
|
+
const part = structure.byId.get(partId);
|
|
27261
|
+
if (!part) return null;
|
|
27262
|
+
for (const q of structure.parts) {
|
|
27263
|
+
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;
|
|
27264
|
+
}
|
|
27265
|
+
return null;
|
|
27266
|
+
}
|
|
27267
|
+
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);
|
|
27268
|
+
function shell(it, mask) {
|
|
27269
|
+
const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
|
|
27270
|
+
const src = it.mesh.userData._p0 || g0.attributes.position.array;
|
|
27271
|
+
for (const [name, attr] of Object.entries(g0.attributes)) {
|
|
27272
|
+
const k = attr.itemSize;
|
|
27273
|
+
g.setAttribute(name, new THREE.BufferAttribute((name === "position" ? src : attr.array).slice(it.v0 * k, it.v1 * k), k));
|
|
27274
|
+
}
|
|
27275
|
+
if (g0.index) {
|
|
27276
|
+
const ix = g0.index.array.slice(it.i0, it.i1);
|
|
27277
|
+
for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
|
|
27278
|
+
g.setIndex(new THREE.BufferAttribute(ix, 1));
|
|
27279
|
+
}
|
|
27280
|
+
g.computeBoundingBox();
|
|
27281
|
+
g.computeBoundingSphere();
|
|
27282
|
+
const base = it.mesh.material;
|
|
27283
|
+
const material = base.clone();
|
|
27284
|
+
const toHouse = { value: new THREE.Matrix4() };
|
|
27285
|
+
material.onBeforeCompile = (sh) => {
|
|
27286
|
+
base.onBeforeCompile?.(sh);
|
|
27287
|
+
sh.uniforms.uToHouse = toHouse;
|
|
27288
|
+
sh.uniforms.uMask = { value: mask.texture };
|
|
27289
|
+
sh.uniforms.uMaskRect = { value: mask.rect };
|
|
27290
|
+
sh.uniforms.uMaskAlongX = { value: mask.alongU ? 1 : 0 };
|
|
27291
|
+
sh.vertexShader = sh.vertexShader.replace("void main() {", "uniform mat4 uToHouse;\nvarying vec3 vHousePos;\nvoid main() {").replace("#include <begin_vertex>", "#include <begin_vertex>\nvHousePos = (uToHouse * vec4(transformed, 1.0)).xyz;");
|
|
27292
|
+
sh.fragmentShader = sh.fragmentShader.replace("void main() {", "uniform sampler2D uMask;\nuniform vec4 uMaskRect;\nuniform float uMaskAlongX;\nvarying vec3 vHousePos;\nvoid main() {\n vec2 mUv = vec2(((uMaskAlongX > 0.5 ? vHousePos.x : vHousePos.z) - uMaskRect.x) / uMaskRect.z, (vHousePos.y - uMaskRect.y) / uMaskRect.w);\n if (mUv.x >= 0.0 && mUv.x <= 1.0 && mUv.y >= 0.0 && mUv.y <= 1.0 && texture2D(uMask, mUv).r > 0.5) discard;");
|
|
27293
|
+
};
|
|
27294
|
+
material.customProgramCacheKey = () => "breachMask" + (base.customProgramCacheKey?.() ?? "");
|
|
27295
|
+
const mesh2 = new THREE.Mesh(g, material);
|
|
27296
|
+
mesh2.name = "breach-shell";
|
|
27297
|
+
mesh2.userData.breachMask = mask;
|
|
27298
|
+
mesh2.position.copy(it.mesh.position);
|
|
27299
|
+
mesh2.quaternion.copy(it.mesh.quaternion);
|
|
27300
|
+
mesh2.scale.copy(it.mesh.scale);
|
|
27301
|
+
mesh2.castShadow = it.mesh.castShadow;
|
|
27302
|
+
mesh2.receiveShadow = it.mesh.receiveShadow;
|
|
27303
|
+
mesh2.renderOrder = it.mesh.renderOrder;
|
|
27304
|
+
mesh2.onBeforeRender = () => {
|
|
27305
|
+
house.updateMatrixWorld();
|
|
27306
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh2.matrixWorld);
|
|
27307
|
+
};
|
|
27308
|
+
it.mesh.parent.add(mesh2);
|
|
27309
|
+
mesh2.updateMatrixWorld(true);
|
|
27310
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh2.matrixWorld);
|
|
27311
|
+
return mesh2;
|
|
27312
|
+
}
|
|
27313
|
+
function maskOf(hostId, parts) {
|
|
27314
|
+
const host = structure.byId.get(hostId);
|
|
27315
|
+
const [u0, v0, u1, v1] = host.aabb;
|
|
27316
|
+
const alongU = u1 - u0 >= v1 - v0;
|
|
27317
|
+
const y0 = Math.min(...parts.map((id) => structure.byId.get(id).y0)), y1 = Math.max(...parts.map((id) => structure.byId.get(id).y1));
|
|
27318
|
+
const len = alongU ? u1 - u0 : v1 - v0;
|
|
27319
|
+
const w = Math.max(2, Math.min(2048, Math.ceil(len / MASK_TEXEL))), h = Math.max(2, Math.min(1024, Math.ceil((y1 - y0) / MASK_TEXEL)));
|
|
27320
|
+
const data = new Uint8Array(w * h);
|
|
27321
|
+
const texture = new THREE.DataTexture(data, w, h, THREE.RedFormat);
|
|
27322
|
+
texture.magFilter = THREE.LinearFilter;
|
|
27323
|
+
texture.minFilter = THREE.LinearFilter;
|
|
27324
|
+
texture.needsUpdate = true;
|
|
27325
|
+
return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
|
|
27326
|
+
}
|
|
27327
|
+
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
27328
|
+
function paint(view) {
|
|
27329
|
+
const { mask } = view;
|
|
27330
|
+
const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
|
|
27331
|
+
const holes = [];
|
|
27332
|
+
const discs = [];
|
|
27333
|
+
const shown = [];
|
|
27334
|
+
for (const id of view.parts) {
|
|
27335
|
+
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id);
|
|
27336
|
+
const all = cells(part, units2);
|
|
27337
|
+
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
27338
|
+
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
27339
|
+
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
27340
|
+
const gone = (c) => chips.some((p) => p[1] >= c.y0 && p[1] <= c.y1 && along(p) >= c.a && along(p) <= c.b) || opening && Math.hypot((c.a + c.b) / 2 - along(opening.center), (c.y0 + c.y1) / 2 - opening.center[1]) < opening.radius;
|
|
27341
|
+
const kept = [];
|
|
27342
|
+
for (const c of all) (gone(c) ? holes : kept).push(c);
|
|
27343
|
+
for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
|
|
27344
|
+
}
|
|
27345
|
+
const reach = SPALL_MIN + SPALL_RAGGED;
|
|
27346
|
+
mask.data.fill(0);
|
|
27347
|
+
const stamp = (s0, s1, y0, y1, inside2) => {
|
|
27348
|
+
const i0 = Math.max(0, Math.floor((s0 - reach) / mask.len * mask.w)), i1 = Math.min(mask.w - 1, Math.ceil((s1 + reach) / mask.len * mask.w));
|
|
27349
|
+
const j0 = Math.max(0, Math.floor((y0 - reach - mask.y0) / mask.height * mask.h)), j1 = Math.min(mask.h - 1, Math.ceil((y1 + reach - mask.y0) / mask.height * mask.h));
|
|
27350
|
+
for (let j = j0; j <= j1; j++) {
|
|
27351
|
+
const y = mask.y0 + (j + 0.5) / mask.h * mask.height;
|
|
27352
|
+
for (let i = i0; i <= i1; i++) {
|
|
27353
|
+
const s = (i + 0.5) / mask.w * mask.len;
|
|
27354
|
+
const edge = SPALL_MIN + SPALL_RAGGED * (0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011));
|
|
27355
|
+
if (inside2(s, y) <= edge) mask.data[j * mask.w + i] = 255;
|
|
27356
|
+
}
|
|
26838
27357
|
}
|
|
27358
|
+
};
|
|
27359
|
+
for (const c of holes) stamp(c.a, c.b, c.y0, c.y1, (s, y) => boxDistance(s, y, c.a, c.b, c.y0, c.y1));
|
|
27360
|
+
for (const d of discs) stamp(d.s - d.r, d.s + d.r, d.y - d.r, d.y + d.r, (s, y) => Math.max(0, Math.hypot(s - d.s, y - d.y) - d.r));
|
|
27361
|
+
mask.texture.needsUpdate = true;
|
|
27362
|
+
const near = (c) => holes.some((h) => boxDistance((c.a + c.b) / 2, (c.y0 + c.y1) / 2, h.a - (c.b - c.a) / 2, h.b + (c.b - c.a) / 2, h.y0 - (c.y1 - c.y0) / 2, h.y1 + (c.y1 - c.y0) / 2) <= reach) || discs.some((d) => boxDistance(d.s, d.y, c.a, c.b, c.y0, c.y1) <= d.r + reach);
|
|
27363
|
+
const list2 = [];
|
|
27364
|
+
const tone = (hex3, k) => tmp.c.setHex(hex3).multiplyScalar(k).getHex();
|
|
27365
|
+
for (const { c, part, units: units2, thick, mid } of shown) {
|
|
27366
|
+
if (!near(c)) continue;
|
|
27367
|
+
const centre = mask.start + (c.a + c.b) / 2, y = (c.y0 + c.y1) / 2, len = c.b - c.a, h = c.y1 - c.y0;
|
|
27368
|
+
const at = (s, dy, d, colour) => list2.push({ x: mask.alongU ? centre : mid, y, z: mask.alongU ? mid : centre, sx: mask.alongU ? s : d, sy: dy, sz: mask.alongU ? d : s, colour });
|
|
27369
|
+
const base = fragmentMaterial(part.material).color.getHex();
|
|
27370
|
+
const n = noise2(centre * 3.1, y * 2.7, 0.07);
|
|
27371
|
+
if (units2.mortar === "concrete") {
|
|
27372
|
+
at(len, h, thick - 2 * COVER2 - 0.03 * n, tone(base, 0.86 + 0.2 * n));
|
|
27373
|
+
} else {
|
|
27374
|
+
at(len, h, thick - 2 * COVER2 - 0.012, 10327948);
|
|
27375
|
+
at(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
|
|
27376
|
+
}
|
|
27377
|
+
}
|
|
27378
|
+
if (view.units) {
|
|
27379
|
+
view.units.removeFromParent();
|
|
27380
|
+
view.units.dispose();
|
|
26839
27381
|
}
|
|
26840
|
-
|
|
27382
|
+
const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
|
|
27383
|
+
units.name = "breach:" + view.host;
|
|
27384
|
+
list2.forEach((u, i) => {
|
|
27385
|
+
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
27386
|
+
units.setMatrixAt(i, tmp.m);
|
|
27387
|
+
units.setColorAt(i, tmp.c.setHex(u.colour));
|
|
27388
|
+
});
|
|
27389
|
+
units.count = list2.length;
|
|
27390
|
+
units.instanceMatrix.needsUpdate = true;
|
|
27391
|
+
if (units.instanceColor) units.instanceColor.needsUpdate = true;
|
|
27392
|
+
units.castShadow = true;
|
|
27393
|
+
units.receiveShadow = true;
|
|
27394
|
+
units.visible = lod === 0;
|
|
27395
|
+
house.add(units);
|
|
27396
|
+
view.units = units;
|
|
27397
|
+
view.gone = holes.length;
|
|
26841
27398
|
}
|
|
26842
27399
|
function draw2(partId, change) {
|
|
26843
27400
|
if (lod !== 0) return false;
|
|
26844
|
-
const
|
|
26845
|
-
|
|
26846
|
-
|
|
26847
|
-
let view = views.get(partId);
|
|
27401
|
+
const hostId = hostOf(partId);
|
|
27402
|
+
if (!hostId || !specOf(partId)?.units) return false;
|
|
27403
|
+
let view = views.get(hostId);
|
|
26848
27404
|
if (!view) {
|
|
26849
|
-
const items = structure.meshesOf(
|
|
27405
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
26850
27406
|
if (!items.length) return false;
|
|
26851
|
-
|
|
27407
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
27408
|
+
if (!parts.includes(partId)) return false;
|
|
27409
|
+
const mask = maskOf(hostId, parts);
|
|
27410
|
+
view = { host: hostId, parts, items, mask, shells: [], saved: null, units: null, gone: 0, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
|
|
26852
27411
|
}
|
|
27412
|
+
if (!view.chips.has(partId)) return false;
|
|
26853
27413
|
if (change.opening) {
|
|
26854
|
-
if (view.
|
|
26855
|
-
view.
|
|
27414
|
+
if (view.openings.has(partId)) return false;
|
|
27415
|
+
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
26856
27416
|
}
|
|
26857
|
-
if (change.chip) view.chips.push(local(change.chip));
|
|
26858
|
-
|
|
26859
|
-
|
|
26860
|
-
view.
|
|
26861
|
-
view.mesh.dispose();
|
|
27417
|
+
if (change.chip) view.chips.get(partId).push(local(change.chip));
|
|
27418
|
+
if (!view.saved) {
|
|
27419
|
+
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
27420
|
+
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
26862
27421
|
}
|
|
26863
|
-
|
|
26864
|
-
|
|
26865
|
-
list2.forEach((u, i) => {
|
|
26866
|
-
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
26867
|
-
mesh2.setMatrixAt(i, tmp.m);
|
|
26868
|
-
mesh2.setColorAt(i, tmp.c.setHex(u.colour));
|
|
26869
|
-
});
|
|
26870
|
-
mesh2.count = list2.length;
|
|
26871
|
-
mesh2.instanceMatrix.needsUpdate = true;
|
|
26872
|
-
if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
|
|
26873
|
-
mesh2.castShadow = true;
|
|
26874
|
-
mesh2.receiveShadow = true;
|
|
26875
|
-
house.add(mesh2);
|
|
26876
|
-
if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
26877
|
-
view.mesh = mesh2;
|
|
26878
|
-
view.units = list2.length;
|
|
26879
|
-
views.set(partId, view);
|
|
27422
|
+
paint(view);
|
|
27423
|
+
views.set(hostId, view);
|
|
26880
27424
|
invalidate();
|
|
26881
27425
|
invalidateShadow();
|
|
26882
27426
|
return true;
|
|
@@ -26887,30 +27431,40 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26887
27431
|
else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
|
|
26888
27432
|
}
|
|
26889
27433
|
}
|
|
27434
|
+
function dispose(view) {
|
|
27435
|
+
view.units.removeFromParent();
|
|
27436
|
+
view.units.dispose();
|
|
27437
|
+
for (const m of view.shells) {
|
|
27438
|
+
m.removeFromParent();
|
|
27439
|
+
m.geometry.dispose();
|
|
27440
|
+
m.material.dispose();
|
|
27441
|
+
}
|
|
27442
|
+
view.mask.texture.dispose();
|
|
27443
|
+
}
|
|
26890
27444
|
return {
|
|
26891
|
-
/** Handles a `stage` event of the house's object. Returns true when the wall was
|
|
27445
|
+
/** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
|
|
26892
27446
|
stage(event) {
|
|
26893
27447
|
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
26894
27448
|
},
|
|
26895
|
-
/** Handles a `chip` event: the unit that came out leaves a
|
|
27449
|
+
/** Handles a `chip` event: the unit that came out leaves a hole in the wall where it was. Returns true when the wall was cut. */
|
|
26896
27450
|
chip(event) {
|
|
26897
27451
|
return draw2(event.part, { chip: event.center });
|
|
26898
27452
|
},
|
|
26899
|
-
/** The part broke or fell:
|
|
27453
|
+
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
26900
27454
|
drop(partId) {
|
|
26901
27455
|
const v = views.get(partId);
|
|
26902
27456
|
if (!v) return;
|
|
26903
|
-
v
|
|
26904
|
-
v.mesh.dispose();
|
|
27457
|
+
dispose(v);
|
|
26905
27458
|
views.delete(partId);
|
|
26906
27459
|
},
|
|
26907
|
-
/** LOD 0 draws the
|
|
27460
|
+
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
26908
27461
|
setLod(level) {
|
|
26909
27462
|
const was = lod;
|
|
26910
27463
|
lod = level;
|
|
26911
27464
|
if (was === 0 === (level === 0)) return;
|
|
26912
27465
|
for (const v of views.values()) {
|
|
26913
|
-
v.
|
|
27466
|
+
v.units.visible = level === 0;
|
|
27467
|
+
for (const m of v.shells) m.visible = level === 0;
|
|
26914
27468
|
plain2(v, level !== 0);
|
|
26915
27469
|
}
|
|
26916
27470
|
invalidate();
|
|
@@ -26919,17 +27473,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
26919
27473
|
/** Everything back to what it was (after `world.reset`). */
|
|
26920
27474
|
restore() {
|
|
26921
27475
|
for (const v of views.values()) {
|
|
26922
|
-
v
|
|
26923
|
-
v.mesh.dispose();
|
|
27476
|
+
dispose(v);
|
|
26924
27477
|
if (lod === 0) plain2(v, true);
|
|
26925
27478
|
}
|
|
26926
27479
|
views.clear();
|
|
26927
27480
|
},
|
|
26928
|
-
/** The wall
|
|
27481
|
+
/** The wall has damage drawn. */
|
|
26929
27482
|
has(partId) {
|
|
26930
27483
|
return views.has(partId);
|
|
26931
27484
|
},
|
|
26932
|
-
|
|
27485
|
+
/** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
|
|
27486
|
+
statistics: () => ({
|
|
27487
|
+
walls: views.size,
|
|
27488
|
+
gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
|
|
27489
|
+
units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
|
|
27490
|
+
})
|
|
26933
27491
|
};
|
|
26934
27492
|
}
|
|
26935
27493
|
Object.assign(K, { createBreachView });
|
|
@@ -27929,6 +28487,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27929
28487
|
return buildApi(K, shared);
|
|
27930
28488
|
}
|
|
27931
28489
|
var WOOD_THIN = 0.035;
|
|
28490
|
+
var BOARD_TOP_THICK = 0.07;
|
|
28491
|
+
var BOARD_TOP_WIDTH = 0.5;
|
|
28492
|
+
var BOARD_WIDTH = 0.16;
|
|
27932
28493
|
var SHELF_SPAN = 1.5;
|
|
27933
28494
|
var SHELVING = /shelf|bookcase|rack/;
|
|
27934
28495
|
function carcass(parts, drawn) {
|
|
@@ -27943,6 +28504,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
27943
28504
|
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
27944
28505
|
}
|
|
27945
28506
|
}
|
|
28507
|
+
function splitTopsIntoBoards(drawn, materialOf, carcassed) {
|
|
28508
|
+
const tops = /* @__PURE__ */ new Map();
|
|
28509
|
+
if (carcassed) return tops;
|
|
28510
|
+
for (let i = drawn.length - 1; i >= 0; i--) {
|
|
28511
|
+
const p = drawn[i];
|
|
28512
|
+
if (p.key !== "wood" || materialOf.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
|
|
28513
|
+
const [sx, sy, sz] = p.shape.size;
|
|
28514
|
+
const across2 = sx < sz ? 0 : 2;
|
|
28515
|
+
const width = Math.min(sx, sz);
|
|
28516
|
+
if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
|
|
28517
|
+
const count = Math.round(width / BOARD_WIDTH);
|
|
28518
|
+
const boards = [];
|
|
28519
|
+
for (let b = 0; b < count; b++) {
|
|
28520
|
+
const size = [sx, sy, sz];
|
|
28521
|
+
size[across2] = width / count;
|
|
28522
|
+
const position = [...p.position];
|
|
28523
|
+
position[across2] += (b + 0.5) * size[across2] - width / 2;
|
|
28524
|
+
const min = [...p.min];
|
|
28525
|
+
const max = [...p.max];
|
|
28526
|
+
min[across2] = position[across2] - size[across2] / 2;
|
|
28527
|
+
max[across2] = position[across2] + size[across2] / 2;
|
|
28528
|
+
boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min, max, color: shadeBoard(p.color, b) });
|
|
28529
|
+
}
|
|
28530
|
+
drawn.splice(i, 1, ...boards);
|
|
28531
|
+
tops.set(p.id, boards);
|
|
28532
|
+
}
|
|
28533
|
+
return tops;
|
|
28534
|
+
}
|
|
28535
|
+
function shadeBoard(color2, board) {
|
|
28536
|
+
const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
|
|
28537
|
+
const channel = (shift) => Math.min(255, Math.round((color2 >> shift & 255) * k)) << shift;
|
|
28538
|
+
return channel(16) | channel(8) | channel(0);
|
|
28539
|
+
}
|
|
27946
28540
|
var FLOOR_TOUCH = 5e-3;
|
|
27947
28541
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
27948
28542
|
var BASE_MATERIAL = {
|
|
@@ -28020,10 +28614,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
28020
28614
|
};
|
|
28021
28615
|
run(A);
|
|
28022
28616
|
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
28617
|
+
const boardsOf = splitTopsIntoBoards(drawn, materialOf, Boolean(opts.carcass));
|
|
28023
28618
|
const parts = drawn.map((p) => {
|
|
28024
28619
|
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]);
|
|
28025
28620
|
let material = materialOf[p.key] ?? "plastic";
|
|
28026
|
-
if (p.key === "wood" && material === "wood_hard" &&
|
|
28621
|
+
if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
|
|
28622
|
+
else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
28027
28623
|
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
28028
28624
|
if (p.rotation) part.rotation = p.rotation;
|
|
28029
28625
|
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
@@ -28058,6 +28654,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
28058
28654
|
if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
28059
28655
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
28060
28656
|
}
|
|
28657
|
+
for (const boards of boardsOf.values()) {
|
|
28658
|
+
if (top && boards[0].max[1] <= top.y - 1e-6) continue;
|
|
28659
|
+
const mid = boards[boards.length >> 1];
|
|
28660
|
+
const min = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
|
|
28661
|
+
const max = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
|
|
28662
|
+
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) };
|
|
28663
|
+
}
|
|
28061
28664
|
if (opts.carcass) carcass(parts, byId);
|
|
28062
28665
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
28063
28666
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|
|
@@ -29047,6 +29650,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
|
|
|
29047
29650
|
const module = {
|
|
29048
29651
|
id: "items_area",
|
|
29049
29652
|
name: text.name,
|
|
29653
|
+
ground: { surface: "tile_floor" },
|
|
29050
29654
|
group: text.group,
|
|
29051
29655
|
state,
|
|
29052
29656
|
placed: () => entries2.map((entry2) => ({ id: entry2.id, itemId: entry2.itemId, group: entry2.group, kind: entry2.kind, size: entry2.size, root: entry2.root })),
|