@adatechnology/game-weapons-three 0.10.1 → 0.10.2

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.
@@ -19717,7 +19717,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19717
19717
  const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
19718
19718
  band.c[L] = (lo + hi) / 2;
19719
19719
  band.s[L] = Math.max(hi - lo, 0.01);
19720
- for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
19720
+ if (inp.style === "snap") {
19721
+ 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] });
19722
+ } else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
19721
19723
  return out;
19722
19724
  };
19723
19725
  var tearStack = (inp, root, t, u, v, n, sheet) => {
@@ -19748,6 +19750,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19748
19750
  const t = sorted[0];
19749
19751
  const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
19750
19752
  if (style === "snap") {
19753
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
19751
19754
  const n2 = Math.min(maxFragments, 2 + Math.floor(rnd2() * 3));
19752
19755
  for (const b of bsp(root, n2, [0, 1, 2], rnd2)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
19753
19756
  return out;
@@ -19921,6 +19924,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19921
19924
  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]]));
19922
19925
  return out;
19923
19926
  };
19927
+ var clipBarsToBox = (bars, u, v, center, size) => {
19928
+ const out = [];
19929
+ for (const [a, b] of bars) {
19930
+ const axis2 = a[u] !== b[u] ? u : v;
19931
+ const other = axis2 === u ? v : u;
19932
+ if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
19933
+ const lo = Math.max(Math.min(a[axis2], b[axis2]), center[axis2] - size[axis2] / 2), hi = Math.min(Math.max(a[axis2], b[axis2]), center[axis2] + size[axis2] / 2);
19934
+ if (hi - lo < 1e-6) continue;
19935
+ const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
19936
+ p[axis2] = lo;
19937
+ q[axis2] = hi;
19938
+ out.push([p, q]);
19939
+ }
19940
+ return out;
19941
+ };
19942
+ var bendBar = ([a, b], thin, bow, i) => {
19943
+ const length2 = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
19944
+ const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
19945
+ m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length2;
19946
+ return [[a, m], [m, b]];
19947
+ };
19924
19948
  var clipBarsToDisc = (bars, u, v, center, radius) => {
19925
19949
  const out = [];
19926
19950
  for (const [a, b] of bars) {
@@ -20841,14 +20865,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20841
20865
  smoke: "grey"
20842
20866
  },
20843
20867
  visual: {
20844
- charColor: 1709072
20868
+ charColor: 1709072,
20869
+ innerColor: 14730131
20845
20870
  },
20846
20871
  tags: [
20847
20872
  "wood",
20848
20873
  "organic",
20849
20874
  "flammable"
20850
20875
  ],
20851
- compressive: 1e5
20876
+ compressive: 1e5,
20877
+ hits: {
20878
+ blunt: {
20879
+ tear: "splinters",
20880
+ tearJoules: 60
20881
+ }
20882
+ }
20852
20883
  },
20853
20884
  {
20854
20885
  id: "wood_hard",
@@ -20878,14 +20909,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20878
20909
  smoke: "grey"
20879
20910
  },
20880
20911
  visual: {
20881
- charColor: 1709072
20912
+ charColor: 1709072,
20913
+ innerColor: 13213802
20882
20914
  },
20883
20915
  tags: [
20884
20916
  "wood",
20885
20917
  "organic",
20886
20918
  "flammable"
20887
20919
  ],
20888
- compressive: 15e4
20920
+ compressive: 15e4,
20921
+ hits: {
20922
+ blunt: {
20923
+ tear: "splinters",
20924
+ tearJoules: 150
20925
+ }
20926
+ }
20889
20927
  },
20890
20928
  {
20891
20929
  id: "plywood",
@@ -20915,14 +20953,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20915
20953
  smoke: "grey"
20916
20954
  },
20917
20955
  visual: {
20918
- charColor: 1709072
20956
+ charColor: 1709072,
20957
+ innerColor: 14862234
20919
20958
  },
20920
20959
  tags: [
20921
20960
  "wood",
20922
20961
  "organic",
20923
20962
  "flammable"
20924
20963
  ],
20925
- compressive: 6e4
20964
+ compressive: 6e4,
20965
+ hits: {
20966
+ ballistic: {
20967
+ tear: "splinters"
20968
+ },
20969
+ blunt: {
20970
+ tear: "splinters",
20971
+ tearJoules: 80
20972
+ }
20973
+ }
20926
20974
  },
20927
20975
  {
20928
20976
  id: "mdf",
@@ -20949,14 +20997,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20949
20997
  smoke: "grey"
20950
20998
  },
20951
20999
  visual: {
20952
- charColor: 1709072
21000
+ charColor: 1709072,
21001
+ innerColor: 12096362
20953
21002
  },
20954
21003
  tags: [
20955
21004
  "wood",
20956
21005
  "organic",
20957
21006
  "flammable"
20958
21007
  ],
20959
- compressive: 3e4
21008
+ compressive: 3e4,
21009
+ hits: {
21010
+ ballistic: {
21011
+ tear: "lumps",
21012
+ tearJoules: 120
21013
+ },
21014
+ blunt: {
21015
+ tear: "lumps",
21016
+ tearJoules: 20
21017
+ }
21018
+ }
20960
21019
  },
20961
21020
  {
20962
21021
  id: "glass",
@@ -21070,6 +21129,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21070
21129
  damageScale: {
21071
21130
  explosive: 1.2
21072
21131
  },
21132
+ hits: {
21133
+ ballistic: {
21134
+ unitJoules: 500,
21135
+ floor: 900,
21136
+ crater: true
21137
+ },
21138
+ blunt: {
21139
+ unitJoules: 1500,
21140
+ floor: 60
21141
+ }
21142
+ },
21073
21143
  hitDebris: "chips",
21074
21144
  breakDebris: "dust",
21075
21145
  thermal: {
@@ -21438,7 +21508,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21438
21508
  smoke: "white"
21439
21509
  },
21440
21510
  visual: {
21441
- charColor: 1709072
21511
+ charColor: 1709072,
21512
+ innerColor: 14207126
21442
21513
  },
21443
21514
  tags: [
21444
21515
  "plant",
@@ -21446,7 +21517,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21446
21517
  "organic",
21447
21518
  "flammable"
21448
21519
  ],
21449
- compressive: 6e4
21520
+ compressive: 6e4,
21521
+ hits: {
21522
+ blunt: {
21523
+ tear: "none"
21524
+ }
21525
+ }
21450
21526
  },
21451
21527
  {
21452
21528
  id: "cactus",
@@ -21561,6 +21637,130 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21561
21637
  "masonry"
21562
21638
  ],
21563
21639
  compressive: 2e4
21640
+ },
21641
+ {
21642
+ id: "asphalt",
21643
+ density: 2350,
21644
+ hpPerKg: 10,
21645
+ penetration: 14,
21646
+ penetrationClass: "concrete",
21647
+ penetrationScale: 0.8,
21648
+ ductility: 0.1,
21649
+ fracture: "crumble",
21650
+ fragmentSize: [
21651
+ 0.03,
21652
+ 0.15
21653
+ ],
21654
+ hitDebris: "chips",
21655
+ breakDebris: "dust",
21656
+ thermal: {
21657
+ conductivity: 0.1,
21658
+ heatCapacity: 2,
21659
+ ignitionC: 400,
21660
+ burnRate: 2e-3,
21661
+ smoke: "black"
21662
+ },
21663
+ tags: [
21664
+ "masonry",
21665
+ "brittle"
21666
+ ],
21667
+ compressive: 3e4
21668
+ },
21669
+ {
21670
+ id: "sand",
21671
+ density: 1600,
21672
+ hpPerKg: 2,
21673
+ penetration: 6,
21674
+ ductility: 0.4,
21675
+ fracture: "crumble",
21676
+ fragmentSize: [
21677
+ 0.01,
21678
+ 0.04
21679
+ ],
21680
+ hitDebris: "dust",
21681
+ breakDebris: "dust",
21682
+ thermal: {
21683
+ conductivity: 0.1,
21684
+ heatCapacity: 2
21685
+ },
21686
+ tags: [
21687
+ "soil"
21688
+ ],
21689
+ compressive: 1e4
21690
+ },
21691
+ {
21692
+ id: "gravel",
21693
+ density: 1800,
21694
+ hpPerKg: 3,
21695
+ penetration: 8,
21696
+ ductility: 0.3,
21697
+ fracture: "crumble",
21698
+ fragmentSize: [
21699
+ 0.02,
21700
+ 0.06
21701
+ ],
21702
+ hitDebris: "chips",
21703
+ breakDebris: "dust",
21704
+ thermal: {
21705
+ conductivity: 0.1,
21706
+ heatCapacity: 2
21707
+ },
21708
+ tags: [
21709
+ "soil",
21710
+ "masonry"
21711
+ ],
21712
+ compressive: 2e4
21713
+ },
21714
+ {
21715
+ id: "turf",
21716
+ density: 900,
21717
+ hpPerKg: 3,
21718
+ penetration: 4,
21719
+ ductility: 0.5,
21720
+ fracture: "crumble",
21721
+ fragmentSize: [
21722
+ 0.03,
21723
+ 0.1
21724
+ ],
21725
+ hitDebris: "dust",
21726
+ breakDebris: "dust",
21727
+ thermal: {
21728
+ conductivity: 0.1,
21729
+ heatCapacity: 1.5,
21730
+ ignitionC: 250,
21731
+ burnRate: 0.03,
21732
+ smoke: "white"
21733
+ },
21734
+ tags: [
21735
+ "soil",
21736
+ "organic"
21737
+ ],
21738
+ compressive: 15e3
21739
+ },
21740
+ {
21741
+ id: "mortar",
21742
+ density: 1900,
21743
+ hpPerKg: 10,
21744
+ penetration: 12,
21745
+ penetrationClass: "concrete",
21746
+ penetrationScale: 1.2,
21747
+ ductility: 0.03,
21748
+ fracture: "crumble",
21749
+ fragmentSize: [
21750
+ 0.02,
21751
+ 0.08
21752
+ ],
21753
+ hitDebris: "chips",
21754
+ breakDebris: "dust",
21755
+ thermal: {
21756
+ conductivity: 0.15,
21757
+ heatCapacity: 2.5
21758
+ },
21759
+ tags: [
21760
+ "masonry",
21761
+ "brittle"
21762
+ ],
21763
+ compressive: 1e5
21564
21764
  }
21565
21765
  ];
21566
21766
  var material_hints_default = {
@@ -21734,6 +21934,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21734
21934
  return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
21735
21935
  };
21736
21936
  var THIN_SHEET_DISHING = 5e-3;
21937
+ var SWEEP_BACK = 0.01;
21938
+ function sweepOrigins(box5, d, spacing) {
21939
+ const origins = [];
21940
+ const line = (from, to) => {
21941
+ const n = Math.max(1, Math.ceil((to - from) / spacing));
21942
+ return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
21943
+ };
21944
+ for (const axis2 of [0, 1, 2]) {
21945
+ if (Math.abs(d[axis2]) < 1e-3) continue;
21946
+ const lead = d[axis2] > 0 ? box5.max[axis2] - SWEEP_BACK : box5.min[axis2] + SWEEP_BACK;
21947
+ const u = (axis2 + 1) % 3;
21948
+ const v = (axis2 + 2) % 3;
21949
+ for (const a of line(box5.min[u], box5.max[u])) {
21950
+ for (const b of line(box5.min[v], box5.max[v])) {
21951
+ const point = [0, 0, 0];
21952
+ point[axis2] = lead;
21953
+ point[u] = a;
21954
+ point[v] = b;
21955
+ origins.push(point);
21956
+ }
21957
+ }
21958
+ }
21959
+ return origins;
21960
+ }
21961
+ var THIN_SHEET_CRUSH = 5e-4;
21962
+ var THIN_SHEET_CONTACT = 0.025;
21737
21963
  var DMG_PER_J = 0.02;
21738
21964
  var BLAST_COUPLING = 0.1;
21739
21965
  var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
@@ -21750,11 +21976,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21750
21976
  var SEVER_BAND = 0.1;
21751
21977
  var SEVER_ASPECT = 3;
21752
21978
  var SEVER_BALLISTIC = 2;
21979
+ var TEAR_JOULES = 150;
21980
+ var tears = 0;
21753
21981
  var SUPPORT_TOLERANCE = 0.05;
21754
21982
  var UNIT_FRAGMENT_FACTOR = 4;
21755
21983
  var BREACH_GRID_CAP = 4096;
21756
21984
  var CHIP_ENERGY = 200;
21757
21985
  var CHIP_SPLASH = 0.5;
21986
+ var BAR_BOW = 0.08;
21758
21987
  var CUT_SLICE = 1;
21759
21988
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
21760
21989
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -21764,8 +21993,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21764
21993
  return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
21765
21994
  };
21766
21995
  var capacityScale = (p) => {
21767
- if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
21768
- return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
21996
+ const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
21997
+ if (p.st.stripped) return residual;
21998
+ const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
21999
+ return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
22000
+ };
22001
+ var sectionLoss = (p) => {
22002
+ const cells = p.st.chipped;
22003
+ if (!cells?.length) return 0;
22004
+ const [u, v] = planeAxes(p.thin);
22005
+ const along = p.dims[u] >= p.dims[v] ? u : v;
22006
+ const across2 = along === u ? v : u;
22007
+ let worst = 0;
22008
+ for (const c of cells) {
22009
+ let gone = 0;
22010
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across2];
22011
+ worst = Math.max(worst, gone / Math.max(p.dims[across2], 1e-6));
22012
+ }
22013
+ return Math.min(1, worst);
21769
22014
  };
21770
22015
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
21771
22016
  var clone = (v) => structuredClone(v);
@@ -22946,6 +23191,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22946
23191
  debris2(o, p, layer2.mat.hitDebris, wp, normal, 5, 2, src);
22947
23192
  setStage(o, p, 1, {}, src);
22948
23193
  };
23194
+ const crater = (o, p, wp, normal, radius, src) => {
23195
+ markDamaged(p);
23196
+ emit2({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
23197
+ debris2(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
23198
+ setStage(o, p, 1, {}, src);
23199
+ };
22949
23200
  const insideOpening = (o, p, objectPoint) => {
22950
23201
  const op = p.st.opening;
22951
23202
  if (!op) return false;
@@ -23022,9 +23273,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23022
23273
  const lp = toLocal(p.pos, p.q, objectPoint);
23023
23274
  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);
23024
23275
  };
23025
- const chipUnits = (o, p, wp, energy, dir, radius, src) => {
23276
+ const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
23026
23277
  var _a, _b;
23027
- if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
23278
+ if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
23279
+ const rule = p.mat.hits?.[kind];
23280
+ const perKg = rule?.unitJoules ?? chipEnergy;
23281
+ const energy = hitEnergy - (rule?.floor ?? 0);
23282
+ if (perKg <= 0 || energy <= 0) return;
23028
23283
  const [u, v] = planeAxes(p.thin);
23029
23284
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
23030
23285
  const grid = gridOf(p);
@@ -23038,7 +23293,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23038
23293
  const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
23039
23294
  const total = (worn[i] ?? 0) + energy * share;
23040
23295
  const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
23041
- if (total < chipEnergy * mass) {
23296
+ if (total < perKg * mass) {
23042
23297
  worn[i] = total;
23043
23298
  continue;
23044
23299
  }
@@ -23063,7 +23318,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23063
23318
  emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
23064
23319
  debris2(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
23065
23320
  }
23321
+ if (p.spec.reinforcement) exposeRebar(o, p, src);
23066
23322
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
23323
+ if (p.structural && !isLost(p)) {
23324
+ solveStructure(o);
23325
+ collapseCheck(o, src);
23326
+ }
23327
+ };
23328
+ const exposeRebar = (o, p, src) => {
23329
+ const r = p.spec.reinforcement;
23330
+ if (!r) return;
23331
+ const [u, v] = planeAxes(p.thin);
23332
+ const all = rebarBars(p.dims, p.thin, r.spacing);
23333
+ let bars;
23334
+ if (p.st.stripped) bars = all;
23335
+ else {
23336
+ bars = [];
23337
+ for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
23338
+ const op = p.st.opening;
23339
+ if (op) {
23340
+ const lc = toLocal(p.pos, p.q, op.center);
23341
+ bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
23342
+ }
23343
+ }
23344
+ const bow = clamp2(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
23345
+ if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
23346
+ emit2({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
23067
23347
  };
23068
23348
  const openBreach = (o, p, center, total, energy, dir, src) => {
23069
23349
  const [u, v] = planeAxes(p.thin);
@@ -23091,10 +23371,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23091
23371
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
23092
23372
  debris2(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
23093
23373
  const reinforcement = p.spec.reinforcement;
23094
- if (reinforcement) {
23095
- const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
23096
- if (bars.length > 0) emit2({ ...base(o, src), type: "expose", part: p.spec.id, bars });
23097
- }
23374
+ if (reinforcement) exposeRebar(o, p, src);
23098
23375
  if (p.structural) solveStructure(o);
23099
23376
  };
23100
23377
  const stripPart = (o, p, reinforcement, src) => {
@@ -23106,8 +23383,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23106
23383
  p.st.burning = false;
23107
23384
  p.rt.jet = false;
23108
23385
  markDamaged(p);
23109
- emit2({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
23110
23386
  solveStructure(o);
23387
+ exposeRebar(o, p, src);
23111
23388
  collapseCheck(o, src);
23112
23389
  };
23113
23390
  const unitFragmentPlan = (p, cap) => {
@@ -23161,7 +23438,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23161
23438
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
23162
23439
  const hitLocal = toLocal(c, q, hitW);
23163
23440
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
23164
- const axis2 = style === "splinter" ? severAxis(p) : -1;
23441
+ const axis2 = style === "splinter" || style === "snap" ? severAxis(p) : -1;
23165
23442
  const cutAt2 = axis2 < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis2] : void 0);
23166
23443
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
23167
23444
  style,
@@ -23233,7 +23510,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23233
23510
  };
23234
23511
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
23235
23512
  const severAxis = (p) => {
23236
- if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
23513
+ if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
23237
23514
  const d = p.dims;
23238
23515
  const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
23239
23516
  return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
@@ -23294,6 +23571,49 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23294
23571
  }
23295
23572
  }
23296
23573
  };
23574
+ const tearOf = (mat, kind) => {
23575
+ const h = mat.hits?.[kind];
23576
+ const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
23577
+ return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
23578
+ };
23579
+ const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
23580
+ const { tear, joules } = tearOf(p.mat, kind);
23581
+ if (tear === "none") return;
23582
+ const shot = kind === "ballistic";
23583
+ const n = shot ? exit ? clamp2(Math.round(E / joules), 1, 7) : 1 : clamp2(Math.floor(E / joules), 0, 5);
23584
+ if (n === 0) return;
23585
+ const fibre = tear === "lumps";
23586
+ const { q } = partXf(o, p);
23587
+ const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
23588
+ const fragments = [];
23589
+ for (let i = 0; i < n; i++) {
23590
+ const back = !exit || i % 3 === 2;
23591
+ const len = scale * (fibre ? 1.2 + 1.2 * rnd2() : 3 + 4 * rnd2()), w = scale * (0.8 + 0.8 * rnd2()), t = scale * (fibre ? 0.7 + 0.6 * rnd2() : 0.4 + 0.4 * rnd2());
23592
+ const size = [w, w, w];
23593
+ size[along] = len;
23594
+ size[(along + 1) % 3] = w;
23595
+ size[(along + 2) % 3] = t;
23596
+ const spread3 = [(rnd2() - 0.5) * 0.8, (rnd2() - 0.2) * 0.8, (rnd2() - 0.5) * 0.8];
23597
+ const speed = shot ? back ? 1.5 + 2 * rnd2() : 2 + 4 * rnd2() : 0.8 + 1.2 * rnd2();
23598
+ const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
23599
+ const mass = size[0] * size[1] * size[2] * p.mat.density;
23600
+ fragments.push({
23601
+ id: `${p.spec.id}#t${tears++}-${i}`,
23602
+ part: p.spec.id,
23603
+ material: p.spec.material,
23604
+ shape: fibre ? "chunk" : "splinter",
23605
+ center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
23606
+ size,
23607
+ rotation: q,
23608
+ velocity: v,
23609
+ inner: true,
23610
+ angularVelocity: [(rnd2() - 0.5) * 30, (rnd2() - 0.5) * 30, (rnd2() - 0.5) * 30],
23611
+ mass
23612
+ });
23613
+ }
23614
+ const patch = [scale * 4, scale * 4, scale * 4];
23615
+ emit2({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
23616
+ };
23297
23617
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round5 = {}) => {
23298
23618
  const mat = p.mat;
23299
23619
  const piercing = round5.piercing ?? 1;
@@ -23322,6 +23642,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23322
23642
  return { penetrated: false, exitEnergy: 0, damage: dmg2 };
23323
23643
  }
23324
23644
  if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
23645
+ else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp2(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
23325
23646
  const brittle = mat.ductility < 0.3;
23326
23647
  const ratio = Ep / Math.max(thr, 1e-6);
23327
23648
  const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
@@ -23355,8 +23676,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23355
23676
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
23356
23677
  debris2(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
23357
23678
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
23358
- const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
23359
- chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
23679
+ const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
23680
+ const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
23681
+ if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
23682
+ 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);
23360
23683
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
23361
23684
  };
23362
23685
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -23364,14 +23687,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23364
23687
  const thrMm = mat.penetration * p.thickness * 1e3;
23365
23688
  const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
23366
23689
  const Ec = E * fragile;
23690
+ const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
23691
+ const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
23692
+ const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
23367
23693
  if (E > 0) {
23368
23694
  if (kind === "blunt") {
23369
- if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(contactR, 0.015), clamp2(E * 1e-4, 1e-3, 0.06), src);
23695
+ 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);
23370
23696
  else if (mat.ductility < 0.3) {
23371
23697
  if (Ec >= 5) addCrack(o, p, point, n, clamp2(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
23372
23698
  } 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);
23373
23699
  } else if (mat.fracture === "tear") {
23374
23700
  addHole(o, p, point, n, clamp2(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
23701
+ } else if (mat.ductility >= 0.3 && E >= thrMm) {
23702
+ addHole(o, p, point, n, clamp2(contactR, 0.01, 0.08), true, void 0, src);
23703
+ if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp2(E * 4e-5 * fold, 5e-4, 0.02), src);
23375
23704
  } else if (mat.ductility >= 0.5) {
23376
23705
  addDent(o, p, point, n, Math.max(contactR, 0.01), clamp2(E * 4e-5, 5e-4, 0.02), src);
23377
23706
  }
@@ -23384,7 +23713,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23384
23713
  const scale = mat.damageScale?.[kind] ?? 1;
23385
23714
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
23386
23715
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
23387
- if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
23716
+ if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
23717
+ if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp2(contactR * 0.2, 6e-3, 0.02), src);
23388
23718
  return done;
23389
23719
  };
23390
23720
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -23963,6 +24293,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23963
24293
  if (p.rt.stressT <= 0) {
23964
24294
  p.rt.stressT = 0.5;
23965
24295
  emit2({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
24296
+ if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
23966
24297
  }
23967
24298
  applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
23968
24299
  } else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
@@ -24179,6 +24510,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24179
24510
  const r = raycastInternal(origin, direction, maxDistance);
24180
24511
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
24181
24512
  },
24513
+ sweepBox(box5, direction, distance3, options2 = {}) {
24514
+ const d = vec.normalize(direction);
24515
+ const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
24516
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
24517
+ let best = null;
24518
+ for (const origin of sweepOrigins(box5, d, options2.spacing ?? 0.2)) {
24519
+ const r = raycastInternal(origin, d, distance3 + SWEEP_BACK, ignore);
24520
+ if (!r || best && r.distance >= best.distance) continue;
24521
+ best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
24522
+ }
24523
+ return best;
24524
+ },
24182
24525
  shoot(origin, direction, energy, caliber, source, round5) {
24183
24526
  const extras = typeof round5 === "number" ? { piercing: round5 } : round5 ?? {};
24184
24527
  return withCollapses(run(() => {
@@ -25750,7 +26093,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25750
26093
  }
25751
26094
  return spec;
25752
26095
  };
25753
- var partSpecOf = (part, capacity, detailed) => detailed && part.assembly ? assemblySpec(part, capacity) : boxSpec(part, capacity);
26096
+ var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
26097
+ var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
26098
+ var caged = (spec, part, rebar2) => {
26099
+ if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
26100
+ const size = spec.shape.kind === "box" ? spec.shape.size : null;
26101
+ if (!size) return spec;
26102
+ const thin = Math.min(...size);
26103
+ return {
26104
+ ...spec,
26105
+ reinforcement: { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
26106
+ units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
26107
+ };
26108
+ };
26109
+ var partSpecOf = (part, capacity, detailed, rebar2) => {
26110
+ if (detailed && part.assembly) return assemblySpec(part, capacity);
26111
+ const spec = boxSpec(part, capacity);
26112
+ return detailed ? caged(spec, part, rebar2) : spec;
26113
+ };
25754
26114
  function toDestructibleSpec(structure, options = {}) {
25755
26115
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
25756
26116
  const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
@@ -25767,7 +26127,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25767
26127
  const sizing = { safety, redundancy, safetyOthers };
25768
26128
  const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
25769
26129
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
25770
- const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
26130
+ const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
25771
26131
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
25772
26132
  const x0 = Math.min(...parts.map((part) => part.aabb[0]));
25773
26133
  const z0 = Math.min(...parts.map((part) => part.aabb[1]));
@@ -33732,7 +34092,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33732
34092
  if (!pc || pc.unit) return sp;
33733
34093
  if (pc.role !== "wall" && pc.role !== "partition") {
33734
34094
  const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
33735
- return def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
34095
+ const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
34096
+ if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
34097
+ return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
34098
+ }
34099
+ return out;
33736
34100
  }
33737
34101
  if (!pc.assembly) return sp;
33738
34102
  const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
@@ -33826,29 +34190,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33826
34190
  }
33827
34191
  return list2;
33828
34192
  }
34193
+ const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
34194
+ const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
34195
+ function hostOf(partId) {
34196
+ if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
34197
+ const part = structure.byId.get(partId);
34198
+ if (!part) return null;
34199
+ for (const q of structure.parts) {
34200
+ 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;
34201
+ }
34202
+ return null;
34203
+ }
34204
+ 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);
33829
34205
  function draw2(partId, change) {
33830
34206
  if (lod !== 0) return false;
33831
- const part = structure.byId.get(partId);
33832
- const spec = opts.spec()?.parts?.find((q) => q.id === partId);
33833
- if (!part || !spec || !spec.units) return false;
33834
- let view = views.get(partId);
34207
+ const hostId = hostOf(partId);
34208
+ if (!hostId || !specOf(partId)?.units) return false;
34209
+ let view = views.get(hostId);
33835
34210
  if (!view) {
33836
- const items = structure.meshesOf(partId).filter((it) => it.mesh);
34211
+ const items = structure.meshesOf(hostId).filter((it) => it.mesh);
33837
34212
  if (!items.length) return false;
33838
- view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: [] };
34213
+ const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
34214
+ view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
33839
34215
  }
34216
+ if (!view.chips.has(partId)) return false;
33840
34217
  if (change.opening) {
33841
- if (view.opening) return false;
33842
- view.opening = { center: local2(change.opening.center), radius: change.opening.radius };
34218
+ if (view.openings.has(partId)) return false;
34219
+ view.openings.set(partId, { center: local2(change.opening.center), radius: change.opening.radius });
34220
+ if (partId === hostId) view.opening = view.openings.get(partId);
33843
34221
  }
33844
- if (change.chip) view.chips.push(local2(change.chip));
33845
- const list2 = courses(part, spec.units, view.opening, view.chips, view.paint);
34222
+ if (change.chip) view.chips.get(partId).push(local2(change.chip));
34223
+ const list2 = [];
34224
+ for (const [id, chips2] of view.chips) list2.push(...courses(structure.byId.get(id), specOf(id).units, view.openings.get(id) ?? null, chips2, view.paint));
33846
34225
  if (view.mesh) {
33847
34226
  view.mesh.removeFromParent();
33848
34227
  view.mesh.dispose();
33849
34228
  }
33850
34229
  const mesh3 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
33851
- mesh3.name = "breach:" + partId;
34230
+ mesh3.name = "breach:" + hostId;
33852
34231
  list2.forEach((u, i) => {
33853
34232
  tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
33854
34233
  mesh3.setMatrixAt(i, tmp.m);
@@ -33863,7 +34242,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33863
34242
  if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
33864
34243
  view.mesh = mesh3;
33865
34244
  view.units = list2.length;
33866
- views.set(partId, view);
34245
+ views.set(hostId, view);
33867
34246
  invalidate();
33868
34247
  invalidateShadow();
33869
34248
  return true;
@@ -34916,6 +35295,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34916
35295
  return buildApi(K, shared);
34917
35296
  }
34918
35297
  var WOOD_THIN = 0.035;
35298
+ var BOARD_TOP_THICK = 0.07;
35299
+ var BOARD_TOP_WIDTH = 0.5;
35300
+ var BOARD_WIDTH = 0.16;
34919
35301
  var SHELF_SPAN = 1.5;
34920
35302
  var SHELVING = /shelf|bookcase|rack/;
34921
35303
  function carcass(parts, drawn) {
@@ -34930,6 +35312,39 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34930
35312
  } else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
34931
35313
  }
34932
35314
  }
35315
+ function splitTopsIntoBoards(drawn, materialOf2, carcassed) {
35316
+ const tops = /* @__PURE__ */ new Map();
35317
+ if (carcassed) return tops;
35318
+ for (let i = drawn.length - 1; i >= 0; i--) {
35319
+ const p = drawn[i];
35320
+ if (p.key !== "wood" || materialOf2.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
35321
+ const [sx, sy, sz] = p.shape.size;
35322
+ const across2 = sx < sz ? 0 : 2;
35323
+ const width = Math.min(sx, sz);
35324
+ if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
35325
+ const count = Math.round(width / BOARD_WIDTH);
35326
+ const boards = [];
35327
+ for (let b = 0; b < count; b++) {
35328
+ const size = [sx, sy, sz];
35329
+ size[across2] = width / count;
35330
+ const position = [...p.position];
35331
+ position[across2] += (b + 0.5) * size[across2] - width / 2;
35332
+ const min2 = [...p.min];
35333
+ const max2 = [...p.max];
35334
+ min2[across2] = position[across2] - size[across2] / 2;
35335
+ max2[across2] = position[across2] + size[across2] / 2;
35336
+ boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min: min2, max: max2, color: shadeBoard(p.color, b) });
35337
+ }
35338
+ drawn.splice(i, 1, ...boards);
35339
+ tops.set(p.id, boards);
35340
+ }
35341
+ return tops;
35342
+ }
35343
+ function shadeBoard(color2, board) {
35344
+ const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
35345
+ const channel = (shift) => Math.min(255, Math.round((color2 >> shift & 255) * k)) << shift;
35346
+ return channel(16) | channel(8) | channel(0);
35347
+ }
34933
35348
  var FLOOR_TOUCH = 5e-3;
34934
35349
  var LOOSE = /* @__PURE__ */ new Set(["paper"]);
34935
35350
  var BASE_MATERIAL = {
@@ -35007,10 +35422,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
35007
35422
  };
35008
35423
  run(A);
35009
35424
  if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
35425
+ const boardsOf = splitTopsIntoBoards(drawn, materialOf2, Boolean(opts.carcass));
35010
35426
  const parts = drawn.map((p) => {
35011
35427
  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]);
35012
35428
  let material = materialOf2[p.key] ?? "plastic";
35013
- if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
35429
+ if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
35430
+ else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
35014
35431
  const part = { id: p.id, material, shape: p.shape, position: p.position };
35015
35432
  if (p.rotation) part.rotation = p.rotation;
35016
35433
  if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
@@ -35045,6 +35462,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
35045
35462
  if (area3 < 0.6 * footprint || p.max[1] < 0.2) continue;
35046
35463
  if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
35047
35464
  }
35465
+ for (const boards of boardsOf.values()) {
35466
+ if (top && boards[0].max[1] <= top.y - 1e-6) continue;
35467
+ const mid = boards[boards.length >> 1];
35468
+ const min2 = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
35469
+ const max2 = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
35470
+ if ((max2[0] - min2[0]) * (max2[1] - min2[1]) >= 0.6 * footprint && mid.max[1] >= 0.2) top = { part: mid.id, y: mid.max[1], parts: boards.map((b) => b.id) };
35471
+ }
35048
35472
  if (opts.carcass) carcass(parts, byId);
35049
35473
  const spec = { name, parts, joints, tags: ["furniture"] };
35050
35474
  const colors = new Map(drawn.map((p) => [p.id, p.color]));
@@ -39099,14 +39523,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39099
39523
  },
39100
39524
  tessellate: {
39101
39525
  cylinder: {
39102
- heightSegments: 6,
39103
- radialSegments: 20
39526
+ heightSegments: 24,
39527
+ radialSegments: 40,
39528
+ capRings: 6
39104
39529
  },
39105
39530
  box: {
39106
39531
  widthSegments: 6,
39107
39532
  heightSegments: 1,
39108
39533
  depthSegments: 4
39109
39534
  }
39535
+ },
39536
+ wall: {
39537
+ body: 1e-4,
39538
+ ends: 25e-5
39110
39539
  }
39111
39540
  }
39112
39541
  },
@@ -39130,14 +39559,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39130
39559
  },
39131
39560
  tessellate: {
39132
39561
  cylinder: {
39133
- heightSegments: 6,
39134
- radialSegments: 20
39562
+ heightSegments: 24,
39563
+ radialSegments: 40,
39564
+ capRings: 6
39135
39565
  },
39136
39566
  box: {
39137
39567
  widthSegments: 6,
39138
39568
  heightSegments: 1,
39139
39569
  depthSegments: 4
39140
39570
  }
39571
+ },
39572
+ wall: {
39573
+ body: 18e-5,
39574
+ ends: 22e-5
39141
39575
  }
39142
39576
  }
39143
39577
  }
@@ -39227,12 +39661,53 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39227
39661
  if (!box22) return 0;
39228
39662
  return (box22.max.x - box22.min.x) * (box22.max.y - box22.min.y) * (box22.max.z - box22.min.z);
39229
39663
  }
39230
- function tessellated(geometry, rule) {
39664
+ function ringedCylinder(T2, p, radial, maxRows, rings) {
39665
+ const half = p.height / 2;
39666
+ const column = 2 * Math.PI * Math.max(p.radiusTop, p.radiusBottom, 1e-6) / radial;
39667
+ const rows = Math.max(1, Math.min(maxRows, Math.round(p.height / column)));
39668
+ const points = [];
39669
+ if (!p.openEnded) for (let i = 0; i <= rings; i++) points.push(new T2.Vector2(p.radiusBottom * i / rings, -half));
39670
+ 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));
39671
+ if (!p.openEnded) for (let i = rings; i >= 0; i--) points.push(new T2.Vector2(p.radiusTop * i / rings, half));
39672
+ return lathed(T2, points, radial, p.thetaStart, p.thetaLength);
39673
+ }
39674
+ function lathed(T2, points, radial, phiStart, phiLength) {
39675
+ const geometry = new T2.LatheGeometry([...points], radial, phiStart, phiLength);
39676
+ geometry.computeVertexNormals();
39677
+ const position = geometry.getAttribute("position");
39678
+ const normal = geometry.getAttribute("normal");
39679
+ const middle = (Math.min(...points.map((point) => point.y)) + Math.max(...points.map((point) => point.y))) / 2;
39680
+ for (let i = 0; i < normal.count; i++) {
39681
+ if (Math.hypot(normal.getX(i), normal.getY(i), normal.getZ(i)) > 0.5) continue;
39682
+ normal.setXYZ(i, 0, position.getY(i) >= middle ? 1 : -1, 0);
39683
+ }
39684
+ return geometry;
39685
+ }
39686
+ function finerProfile(T2, points, step) {
39687
+ const out = [];
39688
+ points.forEach((point, index) => {
39689
+ const next = points[index + 1];
39690
+ out.push(point.clone());
39691
+ if (!next) return;
39692
+ const pieces = Math.max(1, Math.round(point.distanceTo(next) / step));
39693
+ for (let k = 1; k < pieces; k++) out.push(point.clone().lerp(next, k / pieces));
39694
+ });
39695
+ return out;
39696
+ }
39697
+ function tessellated(geometry, rule, T2) {
39231
39698
  const Shape = geometry.constructor;
39232
39699
  if (geometry.type === "CylinderGeometry" && rule.cylinder) {
39233
- const { radiusTop, radiusBottom, height, openEnded, thetaStart, thetaLength } = geometry.parameters;
39700
+ const parameters = geometry.parameters;
39701
+ const { radiusTop, radiusBottom, height, openEnded, thetaStart, thetaLength } = parameters;
39702
+ if (T2 && rule.cylinder.capRings !== void 0) return ringedCylinder(T2, parameters, rule.cylinder.radialSegments, rule.cylinder.heightSegments, rule.cylinder.capRings);
39234
39703
  return new Shape(radiusTop, radiusBottom, height, rule.cylinder.radialSegments, rule.cylinder.heightSegments, openEnded, thetaStart, thetaLength);
39235
39704
  }
39705
+ if (geometry.type === "LatheGeometry" && rule.cylinder && T2 && rule.cylinder.capRings !== void 0) {
39706
+ const { points, segments, phiStart, phiLength } = geometry.parameters;
39707
+ if (segments >= rule.cylinder.radialSegments) return void 0;
39708
+ const widest = Math.max(...points.map((point) => point.x), 1e-6);
39709
+ return lathed(T2, finerProfile(T2, points, 2 * Math.PI * widest / rule.cylinder.radialSegments), rule.cylinder.radialSegments, phiStart, phiLength);
39710
+ }
39236
39711
  if (geometry.type === "BoxGeometry" && rule.box) {
39237
39712
  const { width, height, depth } = geometry.parameters;
39238
39713
  return new Shape(width, height, depth, rule.box.widthSegments, rule.box.heightSegments, rule.box.depthSegments);
@@ -39240,14 +39715,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39240
39715
  return void 0;
39241
39716
  }
39242
39717
  var triangleCount = (geometry) => (geometry.index ? geometry.index.count : geometry.getAttribute("position").count) / 3;
39243
- function tessellateShapes(root, rule) {
39718
+ function tessellateShapes(root, rule, T2) {
39244
39719
  const rebuilt = /* @__PURE__ */ new Map();
39245
39720
  root.traverse((object2) => {
39246
39721
  const mesh3 = object2;
39247
39722
  if (!mesh3.isMesh) return;
39248
39723
  let fresh = rebuilt.get(mesh3.geometry);
39249
39724
  if (!fresh) {
39250
- fresh = tessellated(mesh3.geometry, rule);
39725
+ fresh = tessellated(mesh3.geometry, rule, T2);
39251
39726
  if (!fresh) return;
39252
39727
  rebuilt.set(mesh3.geometry, fresh);
39253
39728
  }
@@ -39261,10 +39736,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39261
39736
  root.userData.tessellationTriangles = added;
39262
39737
  }
39263
39738
  function tagForDestruction(root, options = {}) {
39264
- const { modelId, weight, size } = options;
39739
+ const { modelId, weight, size, three } = options;
39265
39740
  if (weight !== void 0 && weight > 0) root.userData.destructionMass = weight;
39266
39741
  const rule = modelId === void 0 ? void 0 : itemDestruction(modelId);
39267
- if (rule?.tessellate) tessellateShapes(root, rule.tessellate);
39742
+ if (rule?.tessellate) tessellateShapes(root, rule.tessellate, three);
39268
39743
  const solid2 = [];
39269
39744
  root.traverse((object2) => {
39270
39745
  const mesh3 = object2;
@@ -39284,6 +39759,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39284
39759
  const anchor = solid2.reduce((largest, mesh3) => volumeOf(mesh3) > volumeOf(largest) ? mesh3 : largest);
39285
39760
  solid2.forEach((mesh3, index) => {
39286
39761
  mesh3.userData.destructionPart = mesh3 === anchor ? { id: SHELL_PART_ID } : { id: `shell_${index}`, parent: SHELL_PART_ID, kind: "weld" };
39762
+ if (rule.wall) mesh3.userData.destructionThickness = mesh3 === anchor ? rule.wall.body : rule.wall.ends;
39287
39763
  });
39288
39764
  const contents = size === void 0 ? void 0 : itemContents({ ...modelId === void 0 ? {} : { model: modelId }, size, ...weight === void 0 ? {} : { weight } });
39289
39765
  if (contents) anchor.userData.destructionContains = { fluid: contents.liquid, liters: contents.liters };
@@ -44895,7 +45371,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
44895
45371
  const materials = kit.materials.slice(first);
44896
45372
  if (options.wear) setWear(materials, options.wear);
44897
45373
  root.userData.wear = options.wear ?? 0;
44898
- if (options.destructible) tagForDestruction(root, { ...kind === "model" && item.model !== void 0 ? { modelId: item.model } : {}, ...item.weight === void 0 ? {} : { weight: item.weight }, size: item.size });
45374
+ 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 });
44899
45375
  markKitOrigin(root, { owner: "game-items-three", kind: "item", id: item.id, ...kind === "shape" ? { standIn: true } : {} });
44900
45376
  return { root, kind, ...shape ? { shape } : {}, size: item.size, materials };
44901
45377
  }
@@ -60375,6 +60851,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
60375
60851
  return {
60376
60852
  id: "weapons_area",
60377
60853
  name: text.name,
60854
+ ground: { surface: "concrete_slab" },
60378
60855
  group: text.group,
60379
60856
  get audio() {
60380
60857
  return audio;
@@ -61437,6 +61914,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
61437
61914
  return {
61438
61915
  id: EXPLOSIVES_AREA_ID,
61439
61916
  name: text.name,
61917
+ ground: { surface: "concrete_slab" },
61440
61918
  group: text.group,
61441
61919
  bench,
61442
61920
  // the cylinder of this area is the world explosive of the weapons module with the bench's strength: the same id, the same model, the same part