@adatechnology/game-destruction 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -13
- package/dist/index.d.ts +805 -25
- package/dist/index.global.js +1931 -205
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1780 -96
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.global.js
CHANGED
|
@@ -24,40 +24,88 @@ var GameDestruction = (() => {
|
|
|
24
24
|
ASSEMBLIES: () => ASSEMBLIES,
|
|
25
25
|
CONFINEMENT_DEFAULTS: () => CONFINEMENT_DEFAULTS,
|
|
26
26
|
FIRE_PRIME_C: () => FIRE_PRIME_C,
|
|
27
|
+
FLEXURE_DEFAULTS: () => FLEXURE_DEFAULTS,
|
|
27
28
|
FRAGMENT_HIT_DEFAULTS: () => FRAGMENT_HIT_DEFAULTS,
|
|
29
|
+
GRAVITY: () => GRAVITY,
|
|
28
30
|
IMPACT_CONTRACT: () => IMPACT_CONTRACT,
|
|
29
31
|
JOINT_BY_MATERIALS: () => JOINT_BY_MATERIALS,
|
|
30
32
|
JOINT_DEFAULTS: () => JOINT_DEFAULTS,
|
|
33
|
+
MASONRY_DEFAULTS: () => MASONRY_DEFAULTS,
|
|
31
34
|
MATERIALS: () => MATERIALS,
|
|
32
|
-
PIERCING_BY_CALIBER: () => PIERCING_BY_CALIBER,
|
|
33
|
-
PIERCING_BY_KIND: () => PIERCING_BY_KIND,
|
|
34
35
|
SHELTER_DEFAULTS: () => SHELTER_DEFAULTS,
|
|
36
|
+
SOILS: () => SOILS,
|
|
37
|
+
SOIL_DEFAULTS: () => SOIL_DEFAULTS,
|
|
38
|
+
WIND_DEFAULTS: () => WIND_DEFAULTS,
|
|
39
|
+
WIND_TERRAIN: () => WIND_TERRAIN,
|
|
40
|
+
analyzeWind: () => analyzeWind,
|
|
35
41
|
assemblyDepth: () => assemblyDepth,
|
|
36
42
|
assemblyPart: () => assemblyPart,
|
|
43
|
+
bearingWallThickness: () => bearingWallThickness,
|
|
44
|
+
bendGeometry: () => bendGeometry,
|
|
45
|
+
blastImpulse: () => blastImpulse,
|
|
37
46
|
buildFrame: () => buildFrame,
|
|
38
47
|
checkDestructible: () => checkDestructible2,
|
|
39
48
|
checkHit: () => checkHit,
|
|
49
|
+
crackingMoment: () => crackingMoment,
|
|
40
50
|
createDestruction: () => createDestruction,
|
|
51
|
+
damperAmplification: () => damperAmplification,
|
|
52
|
+
damperTuning: () => damperTuning,
|
|
53
|
+
elasticEnergy: () => elasticEnergy,
|
|
41
54
|
explosivePart: () => explosivePart,
|
|
55
|
+
exposureOf: () => exposureOf,
|
|
56
|
+
footingArea: () => footingArea,
|
|
57
|
+
footingSoil: () => footingSoil,
|
|
42
58
|
fragility: () => fragility,
|
|
43
59
|
getAssembly: () => getAssembly,
|
|
44
60
|
guessMaterial: () => guessMaterial,
|
|
61
|
+
gustAt: () => gustAt,
|
|
62
|
+
heightFactor: () => heightFactor,
|
|
63
|
+
impulseEnergy: () => impulseEnergy,
|
|
45
64
|
inferJoints: () => inferJoints,
|
|
65
|
+
layersBetween: () => layersBetween,
|
|
46
66
|
locales: () => locales,
|
|
67
|
+
makeGust: () => makeGust,
|
|
68
|
+
meanHeightFactor: () => meanHeightFactor,
|
|
69
|
+
momentNodes: () => momentNodes,
|
|
70
|
+
momentOfEnergy: () => momentOfEnergy,
|
|
47
71
|
partCapacity: () => partCapacity,
|
|
48
72
|
partMass: () => partMass,
|
|
49
73
|
partThickness: () => partThickness,
|
|
50
74
|
partVolume: () => partVolume,
|
|
51
|
-
|
|
75
|
+
pileCapacity: () => pileCapacity,
|
|
76
|
+
pileLength: () => pileLength,
|
|
77
|
+
pileStiffness: () => pileStiffness,
|
|
78
|
+
profileOf: () => profileOf,
|
|
52
79
|
quat: () => quat,
|
|
53
80
|
quatFromEuler: () => quatFromEuler,
|
|
54
81
|
registerAssembly: () => registerAssembly,
|
|
55
82
|
resolveStrings: () => resolveStrings,
|
|
83
|
+
resolveWind: () => resolveWind,
|
|
56
84
|
rotateVec: () => rotateVec,
|
|
85
|
+
sectionMoments: () => sectionMoments,
|
|
86
|
+
shapeAt: () => shapeAt,
|
|
87
|
+
shearNodes: () => shearNodes,
|
|
88
|
+
sizeRebar: () => sizeRebar,
|
|
57
89
|
sizeStructure: () => sizeStructure,
|
|
90
|
+
slendernessFactor: () => slendernessFactor,
|
|
91
|
+
soilAt: () => soilAt,
|
|
92
|
+
solveFoundation: () => solveFoundation,
|
|
93
|
+
solveWallFrame: () => solveWallFrame,
|
|
94
|
+
spanMoment: () => spanMoment,
|
|
95
|
+
spanOf: () => spanOf,
|
|
96
|
+
steelMoment: () => steelMoment,
|
|
97
|
+
stepSway: () => stepSway,
|
|
98
|
+
stiffnessCentre: () => stiffnessCentre,
|
|
99
|
+
subgradeModulus: () => subgradeModulus,
|
|
100
|
+
tBeamInertia: () => tBeamInertia,
|
|
101
|
+
tensionCapacity: () => tensionCapacity,
|
|
102
|
+
tensionSteel: () => tensionSteel,
|
|
58
103
|
toLocal: () => toLocal,
|
|
59
104
|
toWorld: () => toWorld,
|
|
60
|
-
|
|
105
|
+
turbulenceIntensity: () => turbulenceIntensity,
|
|
106
|
+
vec: () => vec,
|
|
107
|
+
windClass: () => windClass,
|
|
108
|
+
windPressure: () => windPressure
|
|
61
109
|
});
|
|
62
110
|
|
|
63
111
|
// src/chain.ts
|
|
@@ -587,6 +635,7 @@ var GameDestruction = (() => {
|
|
|
587
635
|
const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
|
|
588
636
|
return b / a < 3 && c / b >= 3;
|
|
589
637
|
};
|
|
638
|
+
var memberBarCount = (width, spacing) => spread(width, COVER, spacing, MAX_MEMBER_BARS_PER_SIDE).length;
|
|
590
639
|
var rebarBars = (dims, thin, spacing) => {
|
|
591
640
|
const out = [];
|
|
592
641
|
if (isMember(dims)) {
|
|
@@ -622,12 +671,808 @@ var GameDestruction = (() => {
|
|
|
622
671
|
}
|
|
623
672
|
return out;
|
|
624
673
|
};
|
|
674
|
+
var verticalBarCount = (dims, thin, spacing) => {
|
|
675
|
+
if (isMember(dims)) {
|
|
676
|
+
if (dims.indexOf(Math.max(...dims)) !== 1) return 0;
|
|
677
|
+
const us = spread(dims[0], COVER, spacing, Infinity).length, vs = spread(dims[2], COVER, spacing, Infinity).length;
|
|
678
|
+
return 2 * us + 2 * Math.max(0, vs - 2);
|
|
679
|
+
}
|
|
680
|
+
const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
|
|
681
|
+
if (u === 1) return spread(dims[v], COVER, spacing, Infinity).length;
|
|
682
|
+
if (v === 1) return spread(dims[u], COVER, spacing, Infinity).length;
|
|
683
|
+
return 0;
|
|
684
|
+
};
|
|
625
685
|
var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
|
|
626
686
|
let length = 0;
|
|
627
687
|
for (const [a, b] of bars) length += Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
|
|
628
688
|
return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
|
|
629
689
|
};
|
|
630
690
|
|
|
691
|
+
// src/flexure.ts
|
|
692
|
+
var GRAVITY = 9.81;
|
|
693
|
+
var MPA = 1e6;
|
|
694
|
+
var FLEXURE_DEFAULTS = {
|
|
695
|
+
enabled: true,
|
|
696
|
+
roles: ["beam", "slab", "roof", "stair"],
|
|
697
|
+
scope: "rebar",
|
|
698
|
+
crackFactor: 1.5,
|
|
699
|
+
rebarYieldMPa: 500,
|
|
700
|
+
cover: 0.03,
|
|
701
|
+
neutralAxisLimit: 0.45,
|
|
702
|
+
failRotationDeg: 10,
|
|
703
|
+
loadFactor: 0.64,
|
|
704
|
+
massFactor: 0.5,
|
|
705
|
+
tntJoules: 4184e3,
|
|
706
|
+
impulse: 200,
|
|
707
|
+
reflection: 2,
|
|
708
|
+
reflectionNear: 4,
|
|
709
|
+
nearScaled: 0.5
|
|
710
|
+
};
|
|
711
|
+
var MASONRY_DEFAULTS = { minEccentricity: 0.05, slendernessLimit: 40, concreteFactor: 0.85 };
|
|
712
|
+
var resolveFlexure = (o) => ({ ...FLEXURE_DEFAULTS, ...definedOf(o) });
|
|
713
|
+
var resolveMasonry = (o) => ({ ...MASONRY_DEFAULTS, ...definedOf(o) });
|
|
714
|
+
var definedOf = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
|
|
715
|
+
var RATIO_BAR = 0.01;
|
|
716
|
+
var crackingMoment = (tensileMPa, b, h, alpha = FLEXURE_DEFAULTS.crackFactor) => alpha * tensileMPa * MPA * (b * h * h) / 6;
|
|
717
|
+
var steelMoment = (area, yieldMPa, d, b, compressiveMPa, options = {}) => {
|
|
718
|
+
if (area <= 0 || d <= 0 || b <= 0) return 0;
|
|
719
|
+
const T = area * yieldMPa * MPA;
|
|
720
|
+
const block = 0.68 * compressiveMPa * MPA * b;
|
|
721
|
+
const x = T / block;
|
|
722
|
+
const xl = (options.neutralAxisLimit ?? FLEXURE_DEFAULTS.neutralAxisLimit) * d;
|
|
723
|
+
if (x <= xl) return T * (d - 0.4 * x);
|
|
724
|
+
const top = Math.min(d / 2, (options.cover ?? FLEXURE_DEFAULTS.cover) + RATIO_BAR / 2);
|
|
725
|
+
return block * xl * (d - 0.4 * xl) + (T - block * xl) * (d - top);
|
|
726
|
+
};
|
|
727
|
+
var spanStiffness = (EI, span) => 384 * EI / (5 * span ** 3);
|
|
728
|
+
var elasticEnergy = (moment, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => {
|
|
729
|
+
const R = 8 * moment / span;
|
|
730
|
+
return loadFactor * R * R / (2 * spanStiffness(EI, span));
|
|
731
|
+
};
|
|
732
|
+
var momentOfEnergy = (energy, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => span / 8 * Math.sqrt(2 * Math.max(0, energy) * spanStiffness(EI, span) / loadFactor);
|
|
733
|
+
var impulseEnergy = (impulse, mass, options = {}) => {
|
|
734
|
+
const KL = options.loadFactor ?? FLEXURE_DEFAULTS.loadFactor;
|
|
735
|
+
const KM = options.massFactor ?? FLEXURE_DEFAULTS.massFactor;
|
|
736
|
+
return mass > 0 ? (KL * impulse) ** 2 / (2 * KM * mass) : 0;
|
|
737
|
+
};
|
|
738
|
+
var blastImpulse = (tnt, distance, options = {}) => {
|
|
739
|
+
if (tnt <= 0) return 0;
|
|
740
|
+
const o = { ...FLEXURE_DEFAULTS, ...definedOf(options) };
|
|
741
|
+
const cube = Math.cbrt(tnt);
|
|
742
|
+
const r = Math.max(distance, o.nearScaled * cube);
|
|
743
|
+
return (o.reflection + o.reflectionNear / (r / cube)) * (o.impulse * cube * cube / r);
|
|
744
|
+
};
|
|
745
|
+
var slendernessFactor = (h, t, options = {}) => {
|
|
746
|
+
const o = resolveMasonry(options);
|
|
747
|
+
const lambda = h / Math.max(t, 1e-3);
|
|
748
|
+
return Math.max(0, (1 - 2 * o.minEccentricity) * (1 - (lambda / o.slendernessLimit) ** 3));
|
|
749
|
+
};
|
|
750
|
+
var isReinforced = (part) => !!part.reinforcement || (part.structure?.rebar?.ratio ?? 0) > 0;
|
|
751
|
+
var isSlender = (part, material) => material.strength?.slender ?? (!!material.tags?.includes("masonry") && !isReinforced(part));
|
|
752
|
+
var isFlexural = (part, material, options) => options.enabled && material.strength?.tensileMPa !== void 0 && !!part.structure && options.roles.includes(part.structure.role) && (part.structure.rebar !== void 0 || options.scope === "all" || options.scope === "reinforced" && !!part.reinforcement);
|
|
753
|
+
var tensionSteel = (part, dims, section, options) => {
|
|
754
|
+
const rebar = part.structure?.rebar;
|
|
755
|
+
const cage = part.reinforcement;
|
|
756
|
+
const bar2 = rebar ? RATIO_BAR : cage?.diameter ?? RATIO_BAR;
|
|
757
|
+
const d = Math.max(0.01, section.h - (rebar?.cover ?? options.cover) - bar2 / 2);
|
|
758
|
+
const yieldMPa = rebar?.yieldMPa ?? options.rebarYieldMPa;
|
|
759
|
+
if (rebar && rebar.ratio > 0) return { area: rebar.ratio * section.b * d, d, yieldMPa };
|
|
760
|
+
if (!cage) return { area: 0, d, yieldMPa };
|
|
761
|
+
const one = Math.PI * cage.diameter * cage.diameter / 4;
|
|
762
|
+
const count = isMember(dims) ? memberBarCount(section.b, cage.spacing) : section.b / cage.spacing;
|
|
763
|
+
return { area: one * count, d, yieldMPa };
|
|
764
|
+
};
|
|
765
|
+
var sectionMoments = (material, section, steel, options) => {
|
|
766
|
+
const s = material.strength;
|
|
767
|
+
const cracking = crackingMoment(s.tensileMPa ?? 0, section.b, section.h, options.crackFactor);
|
|
768
|
+
const steelM = steelMoment(steel.area, steel.yieldMPa, steel.d, section.b, s.compressiveMPa, options);
|
|
769
|
+
return { cracking, steel: steelM, ultimate: Math.max(cracking, steelM), reinforced: steelM > 0 };
|
|
770
|
+
};
|
|
771
|
+
var spanMoment = (w, span) => w * span * span / 8;
|
|
772
|
+
var bendGeometry = (part) => {
|
|
773
|
+
const dims = shapeDims(part.shape);
|
|
774
|
+
const member = isMember(dims);
|
|
775
|
+
const q = quatFromEuler(...part.rotation ?? [0, 0, 0]);
|
|
776
|
+
const up = rotateVec(quat.conjugate(q), [0, 1, 0]);
|
|
777
|
+
let spanAxes;
|
|
778
|
+
let depthAxes;
|
|
779
|
+
if (member) {
|
|
780
|
+
const long = dims.indexOf(Math.max(...dims));
|
|
781
|
+
spanAxes = [long];
|
|
782
|
+
depthAxes = [0, 1, 2].filter((i) => i !== long);
|
|
783
|
+
} else {
|
|
784
|
+
const thin = thinAxisOf(dims);
|
|
785
|
+
spanAxes = [...planeAxes(thin)];
|
|
786
|
+
depthAxes = [thin];
|
|
787
|
+
}
|
|
788
|
+
let gravity = -1;
|
|
789
|
+
for (const a of depthAxes) if (Math.abs(up[a]) > Math.SQRT1_2 && (gravity < 0 || Math.abs(up[a]) > Math.abs(up[gravity]))) gravity = a;
|
|
790
|
+
return { dims, member, spanAxes, depthAxes, gravity, up };
|
|
791
|
+
};
|
|
792
|
+
var sectionWidth = (g, axis) => g.member ? g.dims[g.depthAxes.find((a) => a !== axis) ?? axis] : 1;
|
|
793
|
+
var lineLoad = (g, load) => {
|
|
794
|
+
const area = g.member ? g.dims[g.spanAxes[0]] : g.dims[g.spanAxes[0]] * g.dims[g.spanAxes[1]];
|
|
795
|
+
return load * GRAVITY / Math.max(area, 1e-6);
|
|
796
|
+
};
|
|
797
|
+
var spanOf = (g, contacts, samples = 9) => {
|
|
798
|
+
if (contacts.length === 0) return 0;
|
|
799
|
+
const axes = g.spanAxes;
|
|
800
|
+
const steps = (a) => Array.from({ length: samples }, (_, i) => (i / (samples - 1) - 0.5) * g.dims[a]);
|
|
801
|
+
const us = steps(axes[0]);
|
|
802
|
+
const vs = axes.length > 1 ? steps(axes[1]) : [0];
|
|
803
|
+
let far = 0;
|
|
804
|
+
for (const u of us) for (const v of vs) {
|
|
805
|
+
let near = Infinity;
|
|
806
|
+
for (const [lo, hi] of contacts) {
|
|
807
|
+
let gap = 0;
|
|
808
|
+
const x = [u, v];
|
|
809
|
+
axes.forEach((a, k) => gap = Math.max(gap, lo[a] - x[k], x[k] - hi[a], 0));
|
|
810
|
+
if (gap < near) near = gap;
|
|
811
|
+
}
|
|
812
|
+
if (near > far) far = near;
|
|
813
|
+
}
|
|
814
|
+
return 2 * far;
|
|
815
|
+
};
|
|
816
|
+
var FLEXURAL_ROLES = FLEXURE_DEFAULTS.roles;
|
|
817
|
+
var bearingWallThickness = (options) => {
|
|
818
|
+
const gf = options.loadFactor ?? 1.4;
|
|
819
|
+
const gm = options.materialFactor ?? 2;
|
|
820
|
+
const min = options.minThickness ?? 0.14;
|
|
821
|
+
const step = options.step ?? 0.01;
|
|
822
|
+
const fd = (options.material.strength?.compressiveMPa ?? 0) * MPA / gm;
|
|
823
|
+
const own = options.material.density * GRAVITY * options.storeyHeight;
|
|
824
|
+
const out = [];
|
|
825
|
+
let above = 0;
|
|
826
|
+
for (let i = 0; i < options.storeys; i++) {
|
|
827
|
+
const n = above + options.floorLoad;
|
|
828
|
+
let t = min;
|
|
829
|
+
for (let k = 0; k < 60; k++) {
|
|
830
|
+
const room = fd * slendernessFactor(options.storeyHeight, t, options.masonry) - gf * own;
|
|
831
|
+
if (room <= 0) {
|
|
832
|
+
t *= 1.25;
|
|
833
|
+
if (t > 20) {
|
|
834
|
+
t = Infinity;
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const next = Math.max(min, gf * n / room);
|
|
840
|
+
if (Math.abs(next - t) < 1e-5) break;
|
|
841
|
+
t = next;
|
|
842
|
+
}
|
|
843
|
+
t = Number.isFinite(t) ? Math.ceil(t / step - 1e-9) * step : Infinity;
|
|
844
|
+
out.push(t);
|
|
845
|
+
above = n + own * (Number.isFinite(t) ? t : 0);
|
|
846
|
+
}
|
|
847
|
+
return out.reverse();
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// src/data/soils.json
|
|
851
|
+
var soils_default = [
|
|
852
|
+
{ id: "rock", kind: "rock", allowableKPa: 3e3, spt: 50, elasticMPa: 1e4, poisson: 0.2, pile: { tipKPa: 9e3, skinKPa: 500 } },
|
|
853
|
+
{ id: "rock_fractured", kind: "rock", allowableKPa: 1500, spt: 50, elasticMPa: 2e3, poisson: 0.25, pile: { tipKPa: 4500, skinKPa: 300 } },
|
|
854
|
+
{ id: "gravel_dense", kind: "gravel", allowableKPa: 600, spt: 40, elasticMPa: 132, poisson: 0.3 },
|
|
855
|
+
{ id: "gravel_loose", kind: "gravel", allowableKPa: 300, spt: 8, elasticMPa: 26, poisson: 0.3 },
|
|
856
|
+
{ id: "sand_very_dense", kind: "sand", allowableKPa: 500, spt: 45, elasticMPa: 120, poisson: 0.3 },
|
|
857
|
+
{ id: "sand_dense", kind: "sand", allowableKPa: 400, spt: 25, elasticMPa: 68, poisson: 0.3 },
|
|
858
|
+
{ id: "sand_medium", kind: "sand", allowableKPa: 200, spt: 12, elasticMPa: 32, poisson: 0.3 },
|
|
859
|
+
{ id: "sand_loose", kind: "sand", allowableKPa: 50, spt: 3, elasticMPa: 8, poisson: 0.3 },
|
|
860
|
+
{ id: "silt_hard", kind: "silt", allowableKPa: 300, spt: 25, elasticMPa: 44, poisson: 0.35 },
|
|
861
|
+
{ id: "silt_stiff", kind: "silt", allowableKPa: 200, spt: 15, elasticMPa: 26, poisson: 0.35 },
|
|
862
|
+
{ id: "silt_medium", kind: "silt", allowableKPa: 100, spt: 8, elasticMPa: 14, poisson: 0.35 },
|
|
863
|
+
{ id: "clay_hard", kind: "clay", allowableKPa: 300, spt: 25, elasticMPa: 35, poisson: 0.4 },
|
|
864
|
+
{ id: "clay_stiff", kind: "clay", allowableKPa: 200, spt: 15, elasticMPa: 21, poisson: 0.4 },
|
|
865
|
+
{ id: "clay_medium", kind: "clay", allowableKPa: 100, spt: 8, elasticMPa: 11, poisson: 0.4 },
|
|
866
|
+
{ id: "clay_soft", kind: "clay", allowableKPa: 30, spt: 3, elasticMPa: 4.2, poisson: 0.4 },
|
|
867
|
+
{ id: "clay_very_soft", kind: "clay", allowableKPa: 15, spt: 1, elasticMPa: 1.4, poisson: 0.4 }
|
|
868
|
+
];
|
|
869
|
+
|
|
870
|
+
// src/soil.ts
|
|
871
|
+
var SOILS = soils_default;
|
|
872
|
+
var SOIL_DEFAULTS = {
|
|
873
|
+
safety: 3,
|
|
874
|
+
pileSafety: 2,
|
|
875
|
+
rigidity: 0.82,
|
|
876
|
+
influence: 2,
|
|
877
|
+
imperfection: 1 / 200,
|
|
878
|
+
cells: 4,
|
|
879
|
+
sinkRate: 0.05,
|
|
880
|
+
tiltRate: 1.5,
|
|
881
|
+
pileElasticGPa: 25,
|
|
882
|
+
footingWidth: 0.6
|
|
883
|
+
};
|
|
884
|
+
var resolveSoil = (o = {}) => {
|
|
885
|
+
const byId = new Map(SOILS.map((s) => [s.id, s]));
|
|
886
|
+
for (const s of o.soils ?? []) byId.set(s.id, s);
|
|
887
|
+
const { soils: _soils, site, ...rest } = o;
|
|
888
|
+
return { ...SOIL_DEFAULTS, ...rest, ...site !== void 0 ? { site } : {}, byId };
|
|
889
|
+
};
|
|
890
|
+
var profileOf = (soil) => typeof soil === "string" ? { layers: [{ soil }] } : soil;
|
|
891
|
+
var layersBetween = (profile, top, bottom, soils) => {
|
|
892
|
+
const out = [];
|
|
893
|
+
let z = 0;
|
|
894
|
+
const layers = profile.layers;
|
|
895
|
+
for (let i = 0; i < layers.length; i++) {
|
|
896
|
+
const def = soils.get(layers[i].soil);
|
|
897
|
+
if (!def) throw new Error(`unknown soil "${layers[i].soil}"`);
|
|
898
|
+
const last = i === layers.length - 1;
|
|
899
|
+
const end = last ? Infinity : z + Math.max(0, layers[i].thickness ?? 0);
|
|
900
|
+
const a = Math.max(z, top), b = Math.min(end, bottom);
|
|
901
|
+
if (b > a) out.push({ soil: def, thickness: b - a, top: a });
|
|
902
|
+
z = end;
|
|
903
|
+
if (z >= bottom) break;
|
|
904
|
+
}
|
|
905
|
+
return out;
|
|
906
|
+
};
|
|
907
|
+
var soilAt = (profile, depth, soils) => {
|
|
908
|
+
let z = 0;
|
|
909
|
+
for (let i = 0; i < profile.layers.length; i++) {
|
|
910
|
+
const def = soils.get(profile.layers[i].soil);
|
|
911
|
+
if (!def) throw new Error(`unknown soil "${profile.layers[i].soil}"`);
|
|
912
|
+
z += Math.max(0, profile.layers[i].thickness ?? 0);
|
|
913
|
+
if (i === profile.layers.length - 1 || depth < z) return def;
|
|
914
|
+
}
|
|
915
|
+
throw new Error("a soil profile needs at least one layer");
|
|
916
|
+
};
|
|
917
|
+
var footingSoil = (profile, depth, width, soils, influence = SOIL_DEFAULTS.influence) => {
|
|
918
|
+
const zone = layersBetween(profile, depth, depth + Math.max(0.01, influence * width), soils);
|
|
919
|
+
let allowable = Infinity, e = 0, nu = 0, t = 0;
|
|
920
|
+
for (const l of zone) {
|
|
921
|
+
allowable = Math.min(allowable, l.soil.allowableKPa);
|
|
922
|
+
e += l.soil.elasticMPa * l.thickness;
|
|
923
|
+
nu += l.soil.poisson * l.thickness;
|
|
924
|
+
t += l.thickness;
|
|
925
|
+
}
|
|
926
|
+
if (t <= 0) {
|
|
927
|
+
const l = soilAt(profile, depth, soils);
|
|
928
|
+
return { allowableKPa: l.allowableKPa, elasticMPa: l.elasticMPa, poisson: l.poisson };
|
|
929
|
+
}
|
|
930
|
+
return { allowableKPa: allowable, elasticMPa: e / t, poisson: nu / t };
|
|
931
|
+
};
|
|
932
|
+
var DQ_C = { clay: 120, silt: 200, sand: 400, gravel: 400, rock: 400 };
|
|
933
|
+
var BORED = {
|
|
934
|
+
clay: { alpha: 0.85, beta: 0.8 },
|
|
935
|
+
silt: { alpha: 0.6, beta: 0.65 },
|
|
936
|
+
sand: { alpha: 0.5, beta: 0.5 },
|
|
937
|
+
gravel: { alpha: 0.5, beta: 0.5 },
|
|
938
|
+
rock: { alpha: 1, beta: 1 }
|
|
939
|
+
};
|
|
940
|
+
var clampN = (n) => Math.min(50, Math.max(3, n));
|
|
941
|
+
var pileCapacity = (profile, pile, top, soils) => {
|
|
942
|
+
const d = pile.diameter;
|
|
943
|
+
const area = Math.PI * d * d / 4;
|
|
944
|
+
const perimeter = Math.PI * d;
|
|
945
|
+
const driven = pile.method === "driven";
|
|
946
|
+
let skin = 0;
|
|
947
|
+
for (const l of layersBetween(profile, top, top + pile.length, soils)) {
|
|
948
|
+
const f = driven ? 1 : BORED[l.soil.kind].beta;
|
|
949
|
+
const kPa = l.soil.pile?.skinKPa ?? f * 10 * (clampN(l.soil.spt) / 3 + 1);
|
|
950
|
+
skin += kPa * 1e3 * perimeter * l.thickness;
|
|
951
|
+
}
|
|
952
|
+
const tipDepth = top + pile.length;
|
|
953
|
+
const tipSoil = soilAt(profile, tipDepth, soils);
|
|
954
|
+
const alpha = driven ? 1 : BORED[tipSoil.kind].alpha;
|
|
955
|
+
const tipKPa = tipSoil.pile?.tipKPa ?? alpha * DQ_C[tipSoil.kind] * clampN(tipSoil.spt);
|
|
956
|
+
const tip = tipKPa * 1e3 * area;
|
|
957
|
+
return { tip, skin, ultimate: tip + skin, tipSoil };
|
|
958
|
+
};
|
|
959
|
+
var pileLength = (profile, o) => {
|
|
960
|
+
const soils = o.soils ?? resolveSoil().byId;
|
|
961
|
+
const safety = o.safety ?? SOIL_DEFAULTS.pileSafety;
|
|
962
|
+
const step = o.step ?? 0.5;
|
|
963
|
+
const max = o.maxLength ?? 60;
|
|
964
|
+
for (let L = step; L <= max + 1e-9; L += step) {
|
|
965
|
+
if (pileCapacity(profile, { length: L, diameter: o.diameter, ...o.method ? { method: o.method } : {} }, o.top ?? 0, soils).ultimate / safety >= o.load) return Math.round(L / step) * step;
|
|
966
|
+
}
|
|
967
|
+
return max;
|
|
968
|
+
};
|
|
969
|
+
var pileStiffness = (pile, pileElasticGPa, tipElasticMPa, tipPoisson) => {
|
|
970
|
+
const area = Math.PI * pile.diameter * pile.diameter / 4;
|
|
971
|
+
const shaft = pile.length / (2 * pileElasticGPa * 1e9 * area);
|
|
972
|
+
const tip = (1 - tipPoisson * tipPoisson) / (tipElasticMPa * 1e6 * pile.diameter);
|
|
973
|
+
return 1 / (shaft + tip);
|
|
974
|
+
};
|
|
975
|
+
var subgradeModulus = (elasticMPa, poisson, width, rigidity = SOIL_DEFAULTS.rigidity) => elasticMPa * 1e6 / (Math.max(0.05, width) * (1 - poisson * poisson) * rigidity);
|
|
976
|
+
var footingArea = (load, allowableKPa) => load / (allowableKPa * 1e3);
|
|
977
|
+
var yieldSprings = (springs, s, a, b) => {
|
|
978
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
979
|
+
return springs.map((sp) => {
|
|
980
|
+
const set = sp.set ?? 0;
|
|
981
|
+
const w = s + a * (sp.x - x0) + b * (sp.z - z0);
|
|
982
|
+
return sp.k * (w - set) > sp.cap ? w - sp.cap / sp.k : set;
|
|
983
|
+
});
|
|
984
|
+
};
|
|
985
|
+
var stiffnessCentre = (springs) => {
|
|
986
|
+
let k = 0, x = 0, z = 0;
|
|
987
|
+
for (const s of springs) {
|
|
988
|
+
k += s.k;
|
|
989
|
+
x += s.k * s.x;
|
|
990
|
+
z += s.k * s.z;
|
|
991
|
+
}
|
|
992
|
+
return k > 0 ? [x / k, z / k] : [0, 0];
|
|
993
|
+
};
|
|
994
|
+
var cholesky3 = (J, r) => {
|
|
995
|
+
const L = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
996
|
+
for (let i = 0; i < 3; i++) {
|
|
997
|
+
for (let j = 0; j <= i; j++) {
|
|
998
|
+
let sum = J[i][j];
|
|
999
|
+
for (let k = 0; k < j; k++) sum -= L[i][k] * L[j][k];
|
|
1000
|
+
if (i === j) {
|
|
1001
|
+
if (!(sum > 1e-9 * Math.max(1, Math.abs(J[i][i])))) return null;
|
|
1002
|
+
L[i][i] = Math.sqrt(sum);
|
|
1003
|
+
} else L[i][j] = sum / L[j][j];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const y = [0, 0, 0];
|
|
1007
|
+
for (let i = 0; i < 3; i++) {
|
|
1008
|
+
let sum = r[i];
|
|
1009
|
+
for (let k = 0; k < i; k++) sum -= L[i][k] * y[k];
|
|
1010
|
+
y[i] = sum / L[i][i];
|
|
1011
|
+
}
|
|
1012
|
+
const x = [0, 0, 0];
|
|
1013
|
+
for (let i = 2; i >= 0; i--) {
|
|
1014
|
+
let sum = y[i];
|
|
1015
|
+
for (let k = i + 1; k < 3; k++) sum -= L[k][i] * x[k];
|
|
1016
|
+
x[i] = sum / L[i][i];
|
|
1017
|
+
}
|
|
1018
|
+
return x;
|
|
1019
|
+
};
|
|
1020
|
+
var solveFoundation = (springs, weight, xg, zg, height, imperfection, start = { s: 0, a: 0, b: 0 }) => {
|
|
1021
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
1022
|
+
const capSum = springs.reduce((sum, s2) => sum + s2.cap, 0);
|
|
1023
|
+
const span = Math.max(1, ...springs.map((s2) => Math.max(Math.abs(s2.x - x0) + s2.hx, Math.abs(s2.z - z0) + s2.hz)));
|
|
1024
|
+
let s = start.s, a = start.a, b = start.b;
|
|
1025
|
+
if (springs.every((sp) => s + a * (sp.x - x0) + b * (sp.z - z0) - (sp.set ?? 0) <= 0)) {
|
|
1026
|
+
s = weight / Math.max(1e-9, springs.reduce((sum, sp) => sum + sp.k, 0));
|
|
1027
|
+
a = b = 0;
|
|
1028
|
+
}
|
|
1029
|
+
const Wh = weight * height;
|
|
1030
|
+
const evaluate = () => {
|
|
1031
|
+
let F = 0, Mx = 0, Mz = 0, edge = 0, yielded = false;
|
|
1032
|
+
const Jt = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
1033
|
+
const Je = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
1034
|
+
const add = (J, k, dx, dz) => {
|
|
1035
|
+
J[0][0] += k;
|
|
1036
|
+
J[0][1] += k * dx;
|
|
1037
|
+
J[0][2] += k * dz;
|
|
1038
|
+
J[1][1] += k * dx * dx;
|
|
1039
|
+
J[1][2] += k * dx * dz;
|
|
1040
|
+
J[2][2] += k * dz * dz;
|
|
1041
|
+
};
|
|
1042
|
+
for (const sp of springs) {
|
|
1043
|
+
const dx = sp.x - x0, dz = sp.z - z0;
|
|
1044
|
+
const w = s + a * dx + b * dz - (sp.set ?? 0);
|
|
1045
|
+
if (w <= 0) continue;
|
|
1046
|
+
const f = sp.k * w;
|
|
1047
|
+
add(Je, sp.k, dx, dz);
|
|
1048
|
+
let force = f;
|
|
1049
|
+
if (f >= sp.cap) {
|
|
1050
|
+
force = sp.cap;
|
|
1051
|
+
yielded = true;
|
|
1052
|
+
} else add(Jt, sp.k, dx, dz);
|
|
1053
|
+
edge = Math.max(edge, force / sp.cap);
|
|
1054
|
+
F += force;
|
|
1055
|
+
Mx += force * dx;
|
|
1056
|
+
Mz += force * dz;
|
|
1057
|
+
}
|
|
1058
|
+
for (const J of [Jt, Je]) {
|
|
1059
|
+
J[1][0] = J[0][1];
|
|
1060
|
+
J[2][0] = J[0][2];
|
|
1061
|
+
J[2][1] = J[1][2];
|
|
1062
|
+
J[1][1] -= Wh;
|
|
1063
|
+
J[2][2] -= Wh;
|
|
1064
|
+
}
|
|
1065
|
+
const r = [weight - F, weight * (xg - x0 + height * (a + imperfection[0])) - Mx, weight * (zg - z0 + height * (b + imperfection[1])) - Mz];
|
|
1066
|
+
return { r, Jt, Je, edge, yielded };
|
|
1067
|
+
};
|
|
1068
|
+
let ev = evaluate();
|
|
1069
|
+
let converged = false;
|
|
1070
|
+
const tol = 1e-7 * weight;
|
|
1071
|
+
for (let it = 0; it < 120; it++) {
|
|
1072
|
+
if (Math.abs(ev.r[0]) < tol && Math.abs(ev.r[1]) < tol * span && Math.abs(ev.r[2]) < tol * span) {
|
|
1073
|
+
converged = true;
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
const d = cholesky3(ev.Jt, ev.r) ?? cholesky3(ev.Je, ev.r);
|
|
1077
|
+
if (!d) break;
|
|
1078
|
+
s += d[0];
|
|
1079
|
+
a += d[1];
|
|
1080
|
+
b += d[2];
|
|
1081
|
+
if (!Number.isFinite(s + a + b) || Math.abs(a) > 2 || Math.abs(b) > 2) break;
|
|
1082
|
+
ev = evaluate();
|
|
1083
|
+
}
|
|
1084
|
+
const stable = converged && cholesky3(ev.Jt, [0, 0, 0]) !== null;
|
|
1085
|
+
return { s, a, b, stable, yielded: ev.yielded, bearing: capSum > 0 ? weight / capSum : Infinity, edge: ev.edge, residual: [ev.r[0], ev.r[1], ev.r[2]] };
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// src/data/wind-terrain.json
|
|
1089
|
+
var wind_terrain_default = [
|
|
1090
|
+
{ id: "I", zg: 250, b: { A: 1.1, B: 1.11, C: 1.12 }, p: { A: 0.06, B: 0.065, C: 0.07 }, z0: 5e-3 },
|
|
1091
|
+
{ id: "II", zg: 300, b: { A: 1, B: 1, C: 1 }, p: { A: 0.085, B: 0.09, C: 0.1 }, z0: 0.05 },
|
|
1092
|
+
{ id: "III", zg: 350, b: { A: 0.94, B: 0.94, C: 0.93 }, p: { A: 0.1, B: 0.105, C: 0.115 }, z0: 0.3 },
|
|
1093
|
+
{ id: "IV", zg: 420, b: { A: 0.86, B: 0.85, C: 0.84 }, p: { A: 0.12, B: 0.125, C: 0.135 }, z0: 1 },
|
|
1094
|
+
{ id: "V", zg: 500, b: { A: 0.74, B: 0.73, C: 0.71 }, p: { A: 0.15, B: 0.16, C: 0.175 }, z0: 2.5 }
|
|
1095
|
+
];
|
|
1096
|
+
|
|
1097
|
+
// src/wind.ts
|
|
1098
|
+
var WIND_TERRAIN = wind_terrain_default;
|
|
1099
|
+
var WIND_DEFAULTS = {
|
|
1100
|
+
speed: 40,
|
|
1101
|
+
direction: [1, 0, 0],
|
|
1102
|
+
terrain: "III",
|
|
1103
|
+
class: "auto",
|
|
1104
|
+
topography: 1,
|
|
1105
|
+
statistical: 1,
|
|
1106
|
+
drag: 1.3,
|
|
1107
|
+
damping: 0.016,
|
|
1108
|
+
airDensity: 1.226,
|
|
1109
|
+
minHeight: 5,
|
|
1110
|
+
bandHeight: 3,
|
|
1111
|
+
stiffnessFactor: 0.8,
|
|
1112
|
+
beamFactor: 0.4,
|
|
1113
|
+
flange: 0.1,
|
|
1114
|
+
driftLimit: 1 / 500,
|
|
1115
|
+
loadFactor: 1.4,
|
|
1116
|
+
chord: false,
|
|
1117
|
+
meanRatio: 0.69,
|
|
1118
|
+
gustFactor: { A: 1, B: 0.98, C: 0.95 },
|
|
1119
|
+
gust: { components: 400, maxHz: 2, scale: 1200, seed: 1 },
|
|
1120
|
+
peakDecay: 60,
|
|
1121
|
+
tuning: "den-hartog"
|
|
1122
|
+
};
|
|
1123
|
+
var definedOf2 = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
|
|
1124
|
+
var resolveWind = (o = {}) => {
|
|
1125
|
+
const terrains = new Map(WIND_TERRAIN.map((t) => [t.id, t]));
|
|
1126
|
+
for (const t of o.terrains ?? []) terrains.set(t.id, t);
|
|
1127
|
+
const { terrains: _terrains, gustFactor, gust, ...rest } = o;
|
|
1128
|
+
return {
|
|
1129
|
+
...WIND_DEFAULTS,
|
|
1130
|
+
...definedOf2(rest),
|
|
1131
|
+
direction: o.direction ?? WIND_DEFAULTS.direction,
|
|
1132
|
+
gustFactor: { ...WIND_DEFAULTS.gustFactor, ...definedOf2(gustFactor) },
|
|
1133
|
+
gust: { ...WIND_DEFAULTS.gust, ...definedOf2(gust) },
|
|
1134
|
+
terrains
|
|
1135
|
+
};
|
|
1136
|
+
};
|
|
1137
|
+
var exposureOf = (w, spec) => spec ? { ...w, ...definedOf2(spec) } : w;
|
|
1138
|
+
var windPressure = (speed, density = WIND_DEFAULTS.airDensity) => 0.5 * density * speed * speed;
|
|
1139
|
+
var windClass = (largest) => largest <= 20 ? "A" : largest <= 50 ? "B" : "C";
|
|
1140
|
+
var heightFactor = (z, terrain, cls, o = {}) => {
|
|
1141
|
+
const zz = Math.min(terrain.zg, Math.max(o.minHeight ?? WIND_DEFAULTS.minHeight, z));
|
|
1142
|
+
return terrain.b[cls] * (o.gustFactor ?? WIND_DEFAULTS.gustFactor)[cls] * (zz / 10) ** terrain.p[cls];
|
|
1143
|
+
};
|
|
1144
|
+
var meanHeightFactor = (z, terrain, cls, o = {}) => (o.meanRatio ?? WIND_DEFAULTS.meanRatio) * heightFactor(z, terrain, cls, { gustFactor: { A: 1, B: 1, C: 1 }, ...o.minHeight !== void 0 ? { minHeight: o.minHeight } : {} });
|
|
1145
|
+
var turbulenceIntensity = (z, z0) => Math.min(0.5, 1 / Math.log(Math.max(z, 1.5 * z0) / z0));
|
|
1146
|
+
var solveWallFrame = (hb, ei, ga, V) => {
|
|
1147
|
+
const n = hb.length;
|
|
1148
|
+
const diag = new Array(n + 1).fill(0);
|
|
1149
|
+
const off = new Array(n).fill(0);
|
|
1150
|
+
const rhs = new Array(n + 1).fill(0);
|
|
1151
|
+
for (let j = 0; j < n; j++) {
|
|
1152
|
+
const h = hb[j], e = ei[j], g = ga[j];
|
|
1153
|
+
diag[j] += e / h + g * h / 3;
|
|
1154
|
+
diag[j + 1] += e / h + g * h / 3;
|
|
1155
|
+
off[j] = -e / h + g * h / 6;
|
|
1156
|
+
rhs[j] += h / 6 * (2 * V[j] + V[j + 1]);
|
|
1157
|
+
rhs[j + 1] += h / 6 * (V[j] + 2 * V[j + 1]);
|
|
1158
|
+
}
|
|
1159
|
+
const wall = ei[0] / (ei[0] + ga[0] * hb[0] * hb[0]);
|
|
1160
|
+
diag[0] += 1e6 * (ei[0] / hb[0] + ga[0] * hb[0]) * wall;
|
|
1161
|
+
const c = new Array(n + 1).fill(0);
|
|
1162
|
+
const d = new Array(n + 1).fill(0);
|
|
1163
|
+
const psi = new Array(n + 1).fill(0);
|
|
1164
|
+
c[0] = off[0] / diag[0];
|
|
1165
|
+
d[0] = rhs[0] / diag[0];
|
|
1166
|
+
for (let i = 1; i <= n; i++) {
|
|
1167
|
+
const m = diag[i] - off[i - 1] * c[i - 1];
|
|
1168
|
+
c[i] = i < n ? off[i] / m : 0;
|
|
1169
|
+
d[i] = (rhs[i] - off[i - 1] * d[i - 1]) / m;
|
|
1170
|
+
}
|
|
1171
|
+
psi[n] = d[n];
|
|
1172
|
+
for (let i = n - 1; i >= 0; i--) psi[i] = d[i] - c[i] * psi[i + 1];
|
|
1173
|
+
const w = new Array(n + 1).fill(0);
|
|
1174
|
+
for (let j = 0; j < n; j++) w[j + 1] = w[j] + hb[j] * (psi[j] + psi[j + 1]) / 2;
|
|
1175
|
+
return { psi, w };
|
|
1176
|
+
};
|
|
1177
|
+
var shearNodes = (f) => {
|
|
1178
|
+
const V = new Array(f.length + 1).fill(0);
|
|
1179
|
+
for (let j = f.length - 1; j >= 0; j--) V[j] = V[j + 1] + f[j];
|
|
1180
|
+
return V;
|
|
1181
|
+
};
|
|
1182
|
+
var momentNodes = (hb, V) => {
|
|
1183
|
+
const M = new Array(V.length).fill(0);
|
|
1184
|
+
for (let j = hb.length - 1; j >= 0; j--) M[j] = M[j + 1] + hb[j] * (V[j] + V[j + 1]) / 2;
|
|
1185
|
+
return M;
|
|
1186
|
+
};
|
|
1187
|
+
var RIGID = 1e3;
|
|
1188
|
+
var tBeamInertia = (bw, h, bf, tf) => {
|
|
1189
|
+
const web = bw * h;
|
|
1190
|
+
const fl = Math.max(0, bf - bw) * Math.min(tf, h);
|
|
1191
|
+
const t = Math.min(tf, h);
|
|
1192
|
+
const yc = (web * (h / 2) + fl * (h - t / 2)) / (web + fl);
|
|
1193
|
+
return bw * h ** 3 / 12 + web * (h / 2 - yc) ** 2 + Math.max(0, bf - bw) * t ** 3 / 12 + fl * (h - t / 2 - yc) ** 2;
|
|
1194
|
+
};
|
|
1195
|
+
var shapeAt = (a, z) => {
|
|
1196
|
+
const { z: zs, phi } = a.shape;
|
|
1197
|
+
if (z <= zs[0]) return phi[0];
|
|
1198
|
+
for (let i = 1; i < zs.length; i++) if (z <= zs[i]) return phi[i - 1] + (phi[i] - phi[i - 1]) * (z - zs[i - 1]) / (zs[i] - zs[i - 1]);
|
|
1199
|
+
return phi[phi.length - 1];
|
|
1200
|
+
};
|
|
1201
|
+
var analyzeWind = (parts, c, ground) => {
|
|
1202
|
+
const [dx, dz] = c.direction;
|
|
1203
|
+
const along = (p) => {
|
|
1204
|
+
const cx = (p.min[0] + p.max[0]) / 2, cz = (p.min[2] + p.max[2]) / 2;
|
|
1205
|
+
const hx = (p.max[0] - p.min[0]) / 2, hz = (p.max[2] - p.min[2]) / 2;
|
|
1206
|
+
return {
|
|
1207
|
+
u: cx * dx + cz * dz,
|
|
1208
|
+
v: -cx * dz + cz * dx,
|
|
1209
|
+
du: 2 * (Math.abs(dx) * hx + Math.abs(dz) * hz),
|
|
1210
|
+
dv: 2 * (Math.abs(dz) * hx + Math.abs(dx) * hz)
|
|
1211
|
+
};
|
|
1212
|
+
};
|
|
1213
|
+
const g = parts.map(along);
|
|
1214
|
+
let topY = ground;
|
|
1215
|
+
let resists = false;
|
|
1216
|
+
for (const p of parts) {
|
|
1217
|
+
if (p.kind === "foundation") continue;
|
|
1218
|
+
topY = Math.max(topY, p.max[1]);
|
|
1219
|
+
if ((p.kind === "wall" || p.kind === "column") && p.elasticPa) resists = true;
|
|
1220
|
+
}
|
|
1221
|
+
const height = topY - ground;
|
|
1222
|
+
if (!(height > 0) || !resists) return null;
|
|
1223
|
+
const n = Math.max(1, Math.ceil(height / c.bandHeight - 1e-9));
|
|
1224
|
+
const bands = Array.from({ length: n }, (_, j) => Math.min(c.bandHeight, height - j * c.bandHeight));
|
|
1225
|
+
const z0 = Array.from({ length: n }, (_, j) => j * c.bandHeight);
|
|
1226
|
+
const mid = bands.map((h, j) => z0[j] + h / 2);
|
|
1227
|
+
const share = (p, j) => {
|
|
1228
|
+
const a = Math.max(p.min[1] - ground, z0[j]), b = Math.min(p.max[1] - ground, z0[j] + bands[j]);
|
|
1229
|
+
return Math.max(0, b - a);
|
|
1230
|
+
};
|
|
1231
|
+
const vmin = new Array(n).fill(Infinity), vmax = new Array(n).fill(-Infinity);
|
|
1232
|
+
const mass = new Array(n).fill(0);
|
|
1233
|
+
let weight = 0, wu = 0;
|
|
1234
|
+
let footEnd = -Infinity;
|
|
1235
|
+
let anyFoundation = false;
|
|
1236
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1237
|
+
const p = parts[i];
|
|
1238
|
+
weight += p.mass * GRAVITY;
|
|
1239
|
+
wu += p.mass * g[i].u;
|
|
1240
|
+
if (p.kind === "foundation") {
|
|
1241
|
+
anyFoundation = true;
|
|
1242
|
+
footEnd = Math.max(footEnd, g[i].u + g[i].du / 2);
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
const full = Math.max(1e-6, p.max[1] - p.min[1]);
|
|
1246
|
+
for (let j = 0; j < n; j++) {
|
|
1247
|
+
const ov = share(p, j);
|
|
1248
|
+
if (ov <= 0) continue;
|
|
1249
|
+
vmin[j] = Math.min(vmin[j], g[i].v - g[i].dv / 2);
|
|
1250
|
+
vmax[j] = Math.max(vmax[j], g[i].v + g[i].dv / 2);
|
|
1251
|
+
if (!p.damper) mass[j] += p.mass * ov / full;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (!anyFoundation) {
|
|
1255
|
+
for (let i = 0; i < parts.length; i++) if (parts[i].min[1] - ground < 0.1) footEnd = Math.max(footEnd, g[i].u + g[i].du / 2);
|
|
1256
|
+
}
|
|
1257
|
+
const totalMass = weight / GRAVITY;
|
|
1258
|
+
const uCg = totalMass > 0 ? wu / totalMass : 0;
|
|
1259
|
+
const width = vmax.map((hi, j) => hi > vmin[j] ? hi - vmin[j] : 0);
|
|
1260
|
+
const widest = Math.max(...width);
|
|
1261
|
+
const cls = c.cls === "auto" ? windClass(Math.max(height, widest)) : c.cls;
|
|
1262
|
+
const k0 = c.speed * c.topography * c.statistical;
|
|
1263
|
+
const pressure = mid.map((z2) => windPressure(k0 * heightFactor(z2, c.terrain, cls, { gustFactor: c.gustFactor, minHeight: c.minHeight }), c.airDensity));
|
|
1264
|
+
const meanPressure = mid.map((z2) => windPressure(k0 * meanHeightFactor(z2, c.terrain, cls, { meanRatio: c.meanRatio, minHeight: c.minHeight }), c.airDensity));
|
|
1265
|
+
const forces = bands.map((h, j) => pressure[j] * c.drag * width[j] * h);
|
|
1266
|
+
const meanForces = bands.map((h, j) => meanPressure[j] * c.drag * width[j] * h);
|
|
1267
|
+
const V = shearNodes(forces);
|
|
1268
|
+
const M = momentNodes(bands, V);
|
|
1269
|
+
const ea = new Array(n).fill(0), eau = new Array(n).fill(0);
|
|
1270
|
+
const vertical = [];
|
|
1271
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1272
|
+
const p = parts[i];
|
|
1273
|
+
if (p.kind !== "wall" && !(p.kind === "column" && c.chord) || !p.elasticPa) continue;
|
|
1274
|
+
vertical.push(i);
|
|
1275
|
+
const area = g[i].du * g[i].dv;
|
|
1276
|
+
const e = p.elasticPa * c.stiffnessFactor;
|
|
1277
|
+
for (let j = 0; j < n; j++) {
|
|
1278
|
+
const w = share(p, j) / bands[j];
|
|
1279
|
+
if (w <= 0) continue;
|
|
1280
|
+
ea[j] += e * area * w;
|
|
1281
|
+
eau[j] += e * area * g[i].u * w;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const centre = ea.map((v, j) => v > 0 ? eau[j] / v : 0);
|
|
1285
|
+
const ei = new Array(n).fill(0);
|
|
1286
|
+
const ga = new Array(n).fill(0);
|
|
1287
|
+
for (const i of vertical) {
|
|
1288
|
+
const p = parts[i];
|
|
1289
|
+
const e = p.elasticPa * c.stiffnessFactor;
|
|
1290
|
+
for (let j = 0; j < n; j++) {
|
|
1291
|
+
const w = share(p, j) / bands[j];
|
|
1292
|
+
if (w <= 0) continue;
|
|
1293
|
+
ei[j] += e * (g[i].du ** 3 * g[i].dv / 12 + g[i].du * g[i].dv * (g[i].u - centre[j]) ** 2) * w;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1297
|
+
const p = parts[i];
|
|
1298
|
+
if (p.kind !== "column" || !p.elasticPa) continue;
|
|
1299
|
+
const h = Math.max(1e-6, p.max[1] - p.min[1]);
|
|
1300
|
+
const kc = p.elasticPa * c.stiffnessFactor * (g[i].du ** 3 * g[i].dv) / 12 / h;
|
|
1301
|
+
let beams = 0;
|
|
1302
|
+
for (const b of p.beams ?? []) {
|
|
1303
|
+
const q = parts[b];
|
|
1304
|
+
if (!q?.elasticPa) continue;
|
|
1305
|
+
const hx = q.max[0] - q.min[0], hz = q.max[2] - q.min[2];
|
|
1306
|
+
const long = Math.max(hx, hz), short = Math.min(hx, hz);
|
|
1307
|
+
const cos = hx >= hz ? Math.abs(dx) : Math.abs(dz);
|
|
1308
|
+
const depth = q.max[1] - q.min[1];
|
|
1309
|
+
const inertia = tBeamInertia(short, depth, short + 2 * c.flange * long, q.slab ?? 0);
|
|
1310
|
+
beams += cos * cos * q.elasticPa * c.beamFactor * inertia / Math.max(long, 1e-6);
|
|
1311
|
+
}
|
|
1312
|
+
const k = beams / kc;
|
|
1313
|
+
const base = p.min[1] - ground < 0.05;
|
|
1314
|
+
const a = base ? (0.5 + k) / (2 + k) : k / (2 + k);
|
|
1315
|
+
const stiffness = a * 12 * (p.elasticPa * c.stiffnessFactor) * (g[i].du ** 3 * g[i].dv / 12) / (h * h);
|
|
1316
|
+
for (let j = 0; j < n; j++) ga[j] += stiffness * share(p, j) / bands[j];
|
|
1317
|
+
}
|
|
1318
|
+
let last = -1;
|
|
1319
|
+
for (let j = 0; j < n; j++) if (ei[j] > 0 || ga[j] > 0) last = j;
|
|
1320
|
+
for (let j = last + 1; j < n && last >= 0; j++) {
|
|
1321
|
+
ei[j] = ei[last] * RIGID;
|
|
1322
|
+
ga[j] = ga[last] * RIGID;
|
|
1323
|
+
}
|
|
1324
|
+
const tiny = 1;
|
|
1325
|
+
const eiS = ei.map((v) => Math.max(v, tiny));
|
|
1326
|
+
const gaS = ga.map((v) => Math.max(v, tiny));
|
|
1327
|
+
const sol = solveWallFrame(bands, eiS, gaS, V);
|
|
1328
|
+
const deflection = sol.w[n];
|
|
1329
|
+
const lean = solveWallFrame(bands, eiS, gaS, shearNodes(meanForces)).w[n];
|
|
1330
|
+
const kappa = bands.map((h, j) => (sol.psi[j + 1] - sol.psi[j]) / h);
|
|
1331
|
+
const Mmid = bands.map((h, j) => M[j + 1] + h / 2 * (V[j + 1] + (V[j] + V[j + 1]) / 2) / 2);
|
|
1332
|
+
const Mwall = ei.map((v, j) => Math.min(Mmid[j], Math.max(0, v * kappa[j])));
|
|
1333
|
+
const axial = new Array(parts.length).fill(0);
|
|
1334
|
+
for (const i of vertical) {
|
|
1335
|
+
const p = parts[i];
|
|
1336
|
+
let sum = 0, tot = 0;
|
|
1337
|
+
for (let j = 0; j < n; j++) {
|
|
1338
|
+
const w = share(p, j);
|
|
1339
|
+
if (w <= 0) continue;
|
|
1340
|
+
sum += w * p.elasticPa * c.stiffnessFactor * kappa[j] * g[i].du * g[i].dv * (g[i].u - centre[j]);
|
|
1341
|
+
tot += w;
|
|
1342
|
+
}
|
|
1343
|
+
axial[i] = tot > 0 ? sum / tot : 0;
|
|
1344
|
+
}
|
|
1345
|
+
if (!c.chord) {
|
|
1346
|
+
const colA = new Array(n).fill(0), colAu = new Array(n).fill(0);
|
|
1347
|
+
const cols = [];
|
|
1348
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1349
|
+
if (parts[i].kind !== "column") continue;
|
|
1350
|
+
cols.push(i);
|
|
1351
|
+
for (let j = 0; j < n; j++) {
|
|
1352
|
+
const w = share(parts[i], j) / bands[j];
|
|
1353
|
+
colA[j] += g[i].du * g[i].dv * w;
|
|
1354
|
+
colAu[j] += g[i].du * g[i].dv * g[i].u * w;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const colC = colA.map((a, j) => a > 0 ? colAu[j] / a : 0);
|
|
1358
|
+
const colI = new Array(n).fill(0);
|
|
1359
|
+
for (const i of cols) for (let j = 0; j < n; j++) colI[j] += g[i].du * g[i].dv * (g[i].u - colC[j]) ** 2 * share(parts[i], j) / bands[j];
|
|
1360
|
+
for (const i of cols) {
|
|
1361
|
+
let sum = 0, tot = 0;
|
|
1362
|
+
for (let j = 0; j < n; j++) {
|
|
1363
|
+
const w = share(parts[i], j);
|
|
1364
|
+
if (w <= 0 || colI[j] <= 0) continue;
|
|
1365
|
+
sum += w * (Math.max(0, Mmid[j] - Mwall[j]) * g[i].du * g[i].dv * (g[i].u - colC[j])) / colI[j];
|
|
1366
|
+
tot += w;
|
|
1367
|
+
}
|
|
1368
|
+
axial[i] = tot > 0 ? sum / tot : 0;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const arm = footEnd - uCg;
|
|
1372
|
+
const overturn = arm > 0 ? c.loadFactor * M[0] / (weight * arm) : Infinity;
|
|
1373
|
+
const mode = solveWallFrame(bands, eiS, gaS, shearNodes(mass));
|
|
1374
|
+
const top = mode.w[n];
|
|
1375
|
+
const um = bands.map((_, j) => (mode.w[j] + mode.w[j + 1]) / 2);
|
|
1376
|
+
let mu = 0, muu = 0;
|
|
1377
|
+
mass.forEach((m, j) => {
|
|
1378
|
+
mu += m * um[j];
|
|
1379
|
+
muu += m * um[j] * um[j];
|
|
1380
|
+
});
|
|
1381
|
+
const omega = muu > 0 ? Math.sqrt(mu / muu) : 0;
|
|
1382
|
+
const phi = um.map((u) => top > 0 ? u / top : 0);
|
|
1383
|
+
const modalMass = mass.reduce((s, m, j) => s + m * phi[j] * phi[j], 0);
|
|
1384
|
+
const modalForce = meanForces.reduce((s, f, j) => s + f * phi[j], 0);
|
|
1385
|
+
const z = [0, ...bands.map((_, j) => z0[j] + bands[j])];
|
|
1386
|
+
const intensity = turbulenceIntensity(Math.max(0.6 * height, c.minHeight), c.terrain.z0);
|
|
1387
|
+
return {
|
|
1388
|
+
height,
|
|
1389
|
+
cls,
|
|
1390
|
+
width: widest,
|
|
1391
|
+
bands,
|
|
1392
|
+
mid,
|
|
1393
|
+
forces,
|
|
1394
|
+
meanForces,
|
|
1395
|
+
pressure: pressure[n - 1],
|
|
1396
|
+
shear: V[0],
|
|
1397
|
+
moment: M[0],
|
|
1398
|
+
deflection,
|
|
1399
|
+
drift: deflection / height,
|
|
1400
|
+
lean,
|
|
1401
|
+
weight,
|
|
1402
|
+
overturn,
|
|
1403
|
+
wallShare: M[0] > 0 ? Mwall[0] / M[0] : 0,
|
|
1404
|
+
ei,
|
|
1405
|
+
ga,
|
|
1406
|
+
frequency: omega / (2 * Math.PI),
|
|
1407
|
+
omega,
|
|
1408
|
+
modalMass,
|
|
1409
|
+
modalStiffness: omega * omega * modalMass,
|
|
1410
|
+
shape: { z, phi: mode.w.map((w) => top > 0 ? w / top : 0) },
|
|
1411
|
+
modalForce,
|
|
1412
|
+
intensity,
|
|
1413
|
+
axial
|
|
1414
|
+
};
|
|
1415
|
+
};
|
|
1416
|
+
var damperTuning = (mu, kind = "den-hartog") => kind === "warburton" ? { frequency: Math.sqrt(1 - mu / 2) / (1 + mu), damping: Math.sqrt(mu * (1 - mu / 4) / (4 * (1 + mu) * (1 - mu / 2))) } : { frequency: 1 / (1 + mu), damping: Math.sqrt(3 * mu / (8 * (1 + mu) ** 3)) };
|
|
1417
|
+
var damperAmplification = (r, mu, g, damper, structure = 0) => {
|
|
1418
|
+
const mul = (a, b) => [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]];
|
|
1419
|
+
const div = (a, b) => {
|
|
1420
|
+
const d = b[0] * b[0] + b[1] * b[1];
|
|
1421
|
+
return [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d];
|
|
1422
|
+
};
|
|
1423
|
+
const A = [g * g, 2 * damper * g * r];
|
|
1424
|
+
const main = [1 - r * r, 2 * structure * r];
|
|
1425
|
+
const coupling = mu > 0 ? mul([mu * r * r, 0], div(A, [A[0] - r * r, A[1]])) : [0, 0];
|
|
1426
|
+
const total = [main[0] - coupling[0], main[1] - coupling[1]];
|
|
1427
|
+
return 1 / Math.hypot(total[0], total[1]);
|
|
1428
|
+
};
|
|
1429
|
+
var makeGust = (v10, o) => {
|
|
1430
|
+
const rng = createRng(o.seed);
|
|
1431
|
+
const n = Math.max(1, Math.floor(o.components));
|
|
1432
|
+
const df = o.maxHz / n;
|
|
1433
|
+
const freq = Array.from({ length: n }, (_, k) => (k + 0.5) * df);
|
|
1434
|
+
const weight = freq.map((f) => {
|
|
1435
|
+
const x = o.scale * f / Math.max(v10, 1e-6);
|
|
1436
|
+
return 4 * x * x / (1 + x * x) ** (4 / 3) / f * df;
|
|
1437
|
+
});
|
|
1438
|
+
const sum = weight.reduce((s, w) => s + w, 0);
|
|
1439
|
+
return { freq, amp: weight.map((w) => Math.sqrt(2 * w / sum)), phase: freq.map(() => rng.next() * 2 * Math.PI) };
|
|
1440
|
+
};
|
|
1441
|
+
var gustAt = (gust, t) => {
|
|
1442
|
+
let s = 0;
|
|
1443
|
+
for (let k = 0; k < gust.freq.length; k++) s += gust.amp[k] * Math.cos(2 * Math.PI * gust.freq[k] * t + gust.phase[k]);
|
|
1444
|
+
return s;
|
|
1445
|
+
};
|
|
1446
|
+
var stepSway = (s, sys, t, dt, force, maxStep = Infinity) => {
|
|
1447
|
+
const steps = Math.max(1, Math.ceil(dt / maxStep - 1e-9));
|
|
1448
|
+
const h = dt / steps;
|
|
1449
|
+
const d = sys.damper;
|
|
1450
|
+
const f = (x, v, y, w, tt) => {
|
|
1451
|
+
const fd = d ? d.k * (y - x) + d.c * (w - v) : 0;
|
|
1452
|
+
return [v, (force(tt) - sys.K * x - sys.C * v + fd) / sys.M, d ? w : 0, d ? -fd / d.m : 0];
|
|
1453
|
+
};
|
|
1454
|
+
for (let i = 0; i < steps; i++) {
|
|
1455
|
+
const tt = t + i * h;
|
|
1456
|
+
const k1 = f(s.x, s.v, s.y, s.w, tt);
|
|
1457
|
+
const k2 = f(s.x + h / 2 * k1[0], s.v + h / 2 * k1[1], s.y + h / 2 * k1[2], s.w + h / 2 * k1[3], tt + h / 2);
|
|
1458
|
+
const k3 = f(s.x + h / 2 * k2[0], s.v + h / 2 * k2[1], s.y + h / 2 * k2[2], s.w + h / 2 * k2[3], tt + h / 2);
|
|
1459
|
+
const k4 = f(s.x + h * k3[0], s.v + h * k3[1], s.y + h * k3[2], s.w + h * k3[3], tt + h);
|
|
1460
|
+
s.x += h / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]);
|
|
1461
|
+
s.v += h / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]);
|
|
1462
|
+
s.y += h / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]);
|
|
1463
|
+
s.w += h / 6 * (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]);
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
var tensionCapacity = (dims, o) => {
|
|
1467
|
+
const section = dims[0] * dims[2];
|
|
1468
|
+
if (o.reinforcement) {
|
|
1469
|
+
const bars = verticalBarCount(dims, thinAxisOf(dims), o.reinforcement.spacing);
|
|
1470
|
+
return bars * Math.PI * (o.reinforcement.diameter / 2) ** 2 * o.yieldMPa * 1e6;
|
|
1471
|
+
}
|
|
1472
|
+
if (o.ratio) return o.ratio * section * o.yieldMPa * 1e6;
|
|
1473
|
+
return (o.tensileMPa ?? 0) * 1e6 * section;
|
|
1474
|
+
};
|
|
1475
|
+
|
|
631
1476
|
// src/data/materials.json
|
|
632
1477
|
var materials_default = [
|
|
633
1478
|
{
|
|
@@ -635,6 +1480,8 @@ var GameDestruction = (() => {
|
|
|
635
1480
|
density: 7850,
|
|
636
1481
|
hpPerKg: 400,
|
|
637
1482
|
penetration: 110,
|
|
1483
|
+
penetrationClass: "steel",
|
|
1484
|
+
penetrationScale: 1.3,
|
|
638
1485
|
ductility: 0.9,
|
|
639
1486
|
fracture: "none",
|
|
640
1487
|
damageScale: {
|
|
@@ -664,6 +1511,8 @@ var GameDestruction = (() => {
|
|
|
664
1511
|
density: 7200,
|
|
665
1512
|
hpPerKg: 150,
|
|
666
1513
|
penetration: 160,
|
|
1514
|
+
penetrationClass: "steel",
|
|
1515
|
+
penetrationScale: 1,
|
|
667
1516
|
ductility: 0.2,
|
|
668
1517
|
fracture: "snap",
|
|
669
1518
|
fragmentSize: [
|
|
@@ -697,6 +1546,8 @@ var GameDestruction = (() => {
|
|
|
697
1546
|
density: 2700,
|
|
698
1547
|
hpPerKg: 250,
|
|
699
1548
|
penetration: 60,
|
|
1549
|
+
penetrationClass: "steel",
|
|
1550
|
+
penetrationScale: 3,
|
|
700
1551
|
ductility: 0.85,
|
|
701
1552
|
fracture: "none",
|
|
702
1553
|
damageScale: {
|
|
@@ -725,6 +1576,8 @@ var GameDestruction = (() => {
|
|
|
725
1576
|
density: 8960,
|
|
726
1577
|
hpPerKg: 300,
|
|
727
1578
|
penetration: 70,
|
|
1579
|
+
penetrationClass: "steel",
|
|
1580
|
+
penetrationScale: 1.5,
|
|
728
1581
|
ductility: 0.95,
|
|
729
1582
|
fracture: "none",
|
|
730
1583
|
damageScale: {
|
|
@@ -753,6 +1606,8 @@ var GameDestruction = (() => {
|
|
|
753
1606
|
density: 500,
|
|
754
1607
|
hpPerKg: 90,
|
|
755
1608
|
penetration: 9,
|
|
1609
|
+
penetrationClass: "wood",
|
|
1610
|
+
penetrationScale: 1.5,
|
|
756
1611
|
ductility: 0.35,
|
|
757
1612
|
fracture: "splinter",
|
|
758
1613
|
fragmentSize: [
|
|
@@ -789,6 +1644,8 @@ var GameDestruction = (() => {
|
|
|
789
1644
|
density: 750,
|
|
790
1645
|
hpPerKg: 140,
|
|
791
1646
|
penetration: 14,
|
|
1647
|
+
penetrationClass: "wood",
|
|
1648
|
+
penetrationScale: 1,
|
|
792
1649
|
ductility: 0.3,
|
|
793
1650
|
fracture: "splinter",
|
|
794
1651
|
fragmentSize: [
|
|
@@ -824,6 +1681,8 @@ var GameDestruction = (() => {
|
|
|
824
1681
|
density: 600,
|
|
825
1682
|
hpPerKg: 70,
|
|
826
1683
|
penetration: 7,
|
|
1684
|
+
penetrationClass: "wood",
|
|
1685
|
+
penetrationScale: 2,
|
|
827
1686
|
ductility: 0.3,
|
|
828
1687
|
fracture: "snap",
|
|
829
1688
|
fragmentSize: [
|
|
@@ -859,6 +1718,8 @@ var GameDestruction = (() => {
|
|
|
859
1718
|
density: 750,
|
|
860
1719
|
hpPerKg: 45,
|
|
861
1720
|
penetration: 6,
|
|
1721
|
+
penetrationClass: "wood",
|
|
1722
|
+
penetrationScale: 2.5,
|
|
862
1723
|
ductility: 0.15,
|
|
863
1724
|
fracture: "snap",
|
|
864
1725
|
fragmentSize: [
|
|
@@ -987,6 +1848,8 @@ var GameDestruction = (() => {
|
|
|
987
1848
|
density: 2300,
|
|
988
1849
|
hpPerKg: 14,
|
|
989
1850
|
penetration: 18,
|
|
1851
|
+
penetrationClass: "concrete",
|
|
1852
|
+
penetrationScale: 1,
|
|
990
1853
|
ductility: 0.03,
|
|
991
1854
|
fracture: "crumble",
|
|
992
1855
|
fragmentSize: [
|
|
@@ -1006,13 +1869,20 @@ var GameDestruction = (() => {
|
|
|
1006
1869
|
"masonry",
|
|
1007
1870
|
"brittle"
|
|
1008
1871
|
],
|
|
1009
|
-
compressive: 444e3
|
|
1872
|
+
compressive: 444e3,
|
|
1873
|
+
strength: {
|
|
1874
|
+
compressiveMPa: 25,
|
|
1875
|
+
tensileMPa: 2.56,
|
|
1876
|
+
elasticGPa: 28
|
|
1877
|
+
}
|
|
1010
1878
|
},
|
|
1011
1879
|
{
|
|
1012
1880
|
id: "brick",
|
|
1013
1881
|
density: 1900,
|
|
1014
1882
|
hpPerKg: 12,
|
|
1015
1883
|
penetration: 14,
|
|
1884
|
+
penetrationClass: "brick",
|
|
1885
|
+
penetrationScale: 1,
|
|
1016
1886
|
ductility: 0.05,
|
|
1017
1887
|
fracture: "crumble",
|
|
1018
1888
|
fragmentSize: [
|
|
@@ -1029,13 +1899,20 @@ var GameDestruction = (() => {
|
|
|
1029
1899
|
"masonry",
|
|
1030
1900
|
"brittle"
|
|
1031
1901
|
],
|
|
1032
|
-
compressive: 4e4
|
|
1902
|
+
compressive: 4e4,
|
|
1903
|
+
strength: {
|
|
1904
|
+
compressiveMPa: 2.35,
|
|
1905
|
+
tensileMPa: 0.1,
|
|
1906
|
+
elasticGPa: 2.35
|
|
1907
|
+
}
|
|
1033
1908
|
},
|
|
1034
1909
|
{
|
|
1035
1910
|
id: "plaster",
|
|
1036
1911
|
density: 850,
|
|
1037
1912
|
hpPerKg: 18,
|
|
1038
1913
|
penetration: 3,
|
|
1914
|
+
penetrationClass: "brick",
|
|
1915
|
+
penetrationScale: 3,
|
|
1039
1916
|
ductility: 0.05,
|
|
1040
1917
|
fracture: "crumble",
|
|
1041
1918
|
fragmentSize: [
|
|
@@ -1059,6 +1936,8 @@ var GameDestruction = (() => {
|
|
|
1059
1936
|
density: 2700,
|
|
1060
1937
|
hpPerKg: 25,
|
|
1061
1938
|
penetration: 25,
|
|
1939
|
+
penetrationClass: "concrete",
|
|
1940
|
+
penetrationScale: 0.9,
|
|
1062
1941
|
ductility: 0.03,
|
|
1063
1942
|
fracture: "crumble",
|
|
1064
1943
|
fragmentSize: [
|
|
@@ -1322,6 +2201,8 @@ var GameDestruction = (() => {
|
|
|
1322
2201
|
density: 650,
|
|
1323
2202
|
hpPerKg: 5,
|
|
1324
2203
|
penetration: 10,
|
|
2204
|
+
penetrationClass: "wood",
|
|
2205
|
+
penetrationScale: 1.2,
|
|
1325
2206
|
ductility: 0.5,
|
|
1326
2207
|
fracture: "splinter",
|
|
1327
2208
|
fragmentSize: [
|
|
@@ -1623,36 +2504,210 @@ var GameDestruction = (() => {
|
|
|
1623
2504
|
}
|
|
1624
2505
|
return out;
|
|
1625
2506
|
};
|
|
1626
|
-
var partCapacity = (part, material) => {
|
|
2507
|
+
var partCapacity = (part, material, options = {}) => {
|
|
1627
2508
|
if (part.structure?.capacity !== void 0) return part.structure.capacity;
|
|
1628
2509
|
const d = shapeDims(part.shape).sort((a, b) => a - b);
|
|
1629
2510
|
const role = part.structure?.role;
|
|
1630
2511
|
const sh = shapeDims(part.shape);
|
|
1631
2512
|
const section = role === "wall" || role === "partition" ? Math.min(sh[0], sh[2]) * Math.max(sh[0], sh[2]) : role === "slab" ? d[0] * d[2] : d[0] * d[1];
|
|
1632
|
-
|
|
2513
|
+
const foundation = role === "foundation" ? 10 : 1;
|
|
2514
|
+
const strength = material.strength;
|
|
2515
|
+
if (!strength) return Math.max(1, section * (material.compressive ?? 2e4) * foundation);
|
|
2516
|
+
const fc = strength.compressiveMPa * 1e6;
|
|
2517
|
+
let newtons = fc * section;
|
|
2518
|
+
const upright = role === "wall" || role === "partition" || role === "column";
|
|
2519
|
+
if (upright && isSlender(part, material)) newtons *= slendernessFactor(sh[1], Math.min(sh[0], sh[2]), options.masonry);
|
|
2520
|
+
else if (role === "column" && part.reinforcement) {
|
|
2521
|
+
const m = resolveMasonry(options.masonry);
|
|
2522
|
+
const bars = rebarBars(sh, thinAxisOf(sh), part.reinforcement.spacing).length;
|
|
2523
|
+
const steel = bars * Math.PI * (part.reinforcement.diameter / 2) ** 2;
|
|
2524
|
+
newtons = m.concreteFactor * fc * section + steel * resolveFlexure(options.flexure).rebarYieldMPa * 1e6;
|
|
2525
|
+
}
|
|
2526
|
+
return Math.max(1, newtons / GRAVITY * foundation);
|
|
1633
2527
|
};
|
|
1634
2528
|
var fragility = (ductility, thickness) => {
|
|
1635
2529
|
if (ductility >= 0.3) return 1;
|
|
1636
2530
|
return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
|
|
1637
2531
|
};
|
|
1638
2532
|
|
|
2533
|
+
// ../game-contracts/dist/index.js
|
|
2534
|
+
var IMPACT_CONTRACT = {
|
|
2535
|
+
ballistic: { required: ["type", "point", "direction", "energy", "caliber"], optional: ["space", "source"], note: "bullets and pellets: joules and diameter in millimetres" },
|
|
2536
|
+
blunt: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "contact radius in metres (default 3 cm)" },
|
|
2537
|
+
slash: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "axes, knives, machetes" },
|
|
2538
|
+
explosive: { required: ["type", "point", "energy", "radius"], optional: ["lethalRadius", "space", "source"], note: "point is the centre of the blast, radius the range in metres" },
|
|
2539
|
+
fire: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "flame temperature in \xB0C; duration in seconds (call every frame with `dt`)" },
|
|
2540
|
+
heat: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "heat without flame (cutting torch, hot plate)" }
|
|
2541
|
+
};
|
|
2542
|
+
var checkHit = (hit) => {
|
|
2543
|
+
const c = IMPACT_CONTRACT[hit.type];
|
|
2544
|
+
if (!c) return [`unknown damage type "${String(hit.type)}"`];
|
|
2545
|
+
const out = [];
|
|
2546
|
+
for (const f of c.required) {
|
|
2547
|
+
const v = hit[f];
|
|
2548
|
+
if (v === void 0 || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === "number" && !Number.isFinite(v))) out.push(`${hit.type} hit needs "${f}"`);
|
|
2549
|
+
}
|
|
2550
|
+
return out;
|
|
2551
|
+
};
|
|
2552
|
+
var finiteJson = (v, path, out) => {
|
|
2553
|
+
if (typeof v === "number") {
|
|
2554
|
+
if (!Number.isFinite(v)) out.push(`${path} is not a finite number`);
|
|
2555
|
+
} else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out));
|
|
2556
|
+
else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out);
|
|
2557
|
+
else if (typeof v === "function" || typeof v === "symbol" || typeof v === "bigint") out.push(`${path} is not JSON`);
|
|
2558
|
+
};
|
|
2559
|
+
var checkDestructible = (impl, options = {}) => {
|
|
2560
|
+
const problems = [];
|
|
2561
|
+
const known = options.materials ? new Set(options.materials) : null;
|
|
2562
|
+
let spec;
|
|
2563
|
+
try {
|
|
2564
|
+
spec = impl.describe();
|
|
2565
|
+
} catch (e) {
|
|
2566
|
+
return { ok: false, problems: [`describe() threw: ${String(e)}`] };
|
|
2567
|
+
}
|
|
2568
|
+
if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ["describe() must return at least one part"] };
|
|
2569
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2570
|
+
for (const p of spec.parts) {
|
|
2571
|
+
if (ids.has(p.id)) problems.push(`duplicate part id "${p.id}"`);
|
|
2572
|
+
ids.add(p.id);
|
|
2573
|
+
if (known && !known.has(p.material)) problems.push(`part "${p.id}" has unknown material "${p.material}"`);
|
|
2574
|
+
if (!p.shape || !["box", "cylinder", "sphere"].includes(p.shape.kind)) problems.push(`part "${p.id}" has no valid shape`);
|
|
2575
|
+
if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part "${p.id}" has no position`);
|
|
2576
|
+
}
|
|
2577
|
+
const jointIds = /* @__PURE__ */ new Set();
|
|
2578
|
+
for (const j of spec.joints ?? []) {
|
|
2579
|
+
if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint "${j.id ?? `${j.a}~${j.b}`}" references an unknown part`);
|
|
2580
|
+
const jid = j.id ?? `${j.a}~${j.b}`;
|
|
2581
|
+
if (jointIds.has(jid)) problems.push(`duplicate joint id "${jid}"`);
|
|
2582
|
+
jointIds.add(jid);
|
|
2583
|
+
}
|
|
2584
|
+
const checkState = (label) => {
|
|
2585
|
+
let st;
|
|
2586
|
+
try {
|
|
2587
|
+
st = impl.state();
|
|
2588
|
+
} catch (e) {
|
|
2589
|
+
problems.push(`state() threw ${label}: ${String(e)}`);
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
const sids = st.parts.map((p) => p.id).sort().join(",");
|
|
2593
|
+
if (sids !== [...ids].sort().join(",")) problems.push(`state() part ids do not match describe() ${label}`);
|
|
2594
|
+
for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part "${p.id}" has negative or invalid hp ${label}`);
|
|
2595
|
+
};
|
|
2596
|
+
const checkEvents = (events, label) => {
|
|
2597
|
+
if (!Array.isArray(events)) {
|
|
2598
|
+
problems.push(`${label} must return an array`);
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
const evs = events;
|
|
2602
|
+
evs.forEach((e, i) => {
|
|
2603
|
+
if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object "${String(e.object)}", expected "${impl.id}"`);
|
|
2604
|
+
const part = e.part;
|
|
2605
|
+
if (part !== void 0 && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part "${part}"`);
|
|
2606
|
+
});
|
|
2607
|
+
const bad = [];
|
|
2608
|
+
finiteJson(evs, "events", bad);
|
|
2609
|
+
problems.push(...bad.map((b) => `${label}: ${b}`));
|
|
2610
|
+
try {
|
|
2611
|
+
const s = JSON.stringify(evs);
|
|
2612
|
+
if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`);
|
|
2613
|
+
} catch (e) {
|
|
2614
|
+
problems.push(`${label} events are not JSON-serializable: ${String(e)}`);
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
checkState("before the hit");
|
|
2618
|
+
const target = spec.parts[0];
|
|
2619
|
+
try {
|
|
2620
|
+
const hit = { type: "ballistic", point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 };
|
|
2621
|
+
checkEvents(impl.hit(hit), "hit()");
|
|
2622
|
+
} catch (e) {
|
|
2623
|
+
problems.push(`hit() threw: ${String(e)}`);
|
|
2624
|
+
}
|
|
2625
|
+
checkState("after the hit");
|
|
2626
|
+
if (impl.update) {
|
|
2627
|
+
try {
|
|
2628
|
+
checkEvents(impl.update(0.016), "update()");
|
|
2629
|
+
} catch (e) {
|
|
2630
|
+
problems.push(`update() threw: ${String(e)}`);
|
|
2631
|
+
}
|
|
2632
|
+
checkState("after update");
|
|
2633
|
+
}
|
|
2634
|
+
try {
|
|
2635
|
+
impl.reset?.();
|
|
2636
|
+
} catch (e) {
|
|
2637
|
+
problems.push(`reset() threw: ${String(e)}`);
|
|
2638
|
+
}
|
|
2639
|
+
return { ok: problems.length === 0, problems };
|
|
2640
|
+
};
|
|
2641
|
+
var DEFAULT_IGNITION_RULES = {
|
|
2642
|
+
fillCurve: [
|
|
2643
|
+
[0, 0.6],
|
|
2644
|
+
[0.1, 1],
|
|
2645
|
+
[0.35, 0.6],
|
|
2646
|
+
[0.65, 0.3],
|
|
2647
|
+
[0.9, 0.15],
|
|
2648
|
+
[1, 0.12]
|
|
2649
|
+
],
|
|
2650
|
+
flameFillInfluence: 0.5,
|
|
2651
|
+
flameDefaults: { incendiary: 0.7, tracer: 0.2 },
|
|
2652
|
+
sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
|
|
2653
|
+
sparkMetalDefault: 0.5,
|
|
2654
|
+
sparkFluid: { diesel: 0.15 },
|
|
2655
|
+
fireNear: { radius: 1.5, chancePerSecond: 0.5 }
|
|
2656
|
+
};
|
|
2657
|
+
var resolveIgnitionRules = (rules) => ({
|
|
2658
|
+
...DEFAULT_IGNITION_RULES,
|
|
2659
|
+
...rules,
|
|
2660
|
+
flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
|
|
2661
|
+
sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
|
|
2662
|
+
sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
|
|
2663
|
+
fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
|
|
2664
|
+
});
|
|
2665
|
+
var fillFactor = (fill, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
|
|
2666
|
+
if (curve.length === 0) return 1;
|
|
2667
|
+
const f = Math.min(1, Math.max(0, fill));
|
|
2668
|
+
if (f <= curve[0][0]) return curve[0][1];
|
|
2669
|
+
for (let i = 1; i < curve.length; i++) {
|
|
2670
|
+
const [x1, y1] = curve[i];
|
|
2671
|
+
const [x0, y0] = curve[i - 1];
|
|
2672
|
+
if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
|
|
2673
|
+
}
|
|
2674
|
+
return curve[curve.length - 1][1];
|
|
2675
|
+
};
|
|
2676
|
+
var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
|
|
2677
|
+
var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
|
|
2678
|
+
const k = fillFactor(target.fill, rules.fillCurve);
|
|
2679
|
+
const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
|
|
2680
|
+
const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
|
|
2681
|
+
const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
|
|
2682
|
+
return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
|
|
2683
|
+
};
|
|
2684
|
+
|
|
1639
2685
|
// src/world.ts
|
|
2686
|
+
var THIN_SHEET_DISHING = 5e-3;
|
|
1640
2687
|
var DMG_PER_J = 0.02;
|
|
1641
2688
|
var BLAST_COUPLING = 0.1;
|
|
1642
2689
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1643
2690
|
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1644
2691
|
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1645
2692
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
2693
|
+
var VENT_DIAMETER = 8e-3;
|
|
2694
|
+
var FLAME_LIGHTS_LEAK_C = 470;
|
|
1646
2695
|
var BREACH_RADIUS = 0.55;
|
|
1647
2696
|
var BREACH_BLAST_FACTOR = 6;
|
|
1648
2697
|
var BREACH_AREA = 0.25;
|
|
1649
|
-
var BREACH_BLAST_REACH =
|
|
2698
|
+
var BREACH_BLAST_REACH = 0.12;
|
|
1650
2699
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2700
|
+
var SUPPORT_TOLERANCE = 0.05;
|
|
1651
2701
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1652
2702
|
var BREACH_GRID_CAP = 4096;
|
|
1653
2703
|
var CUT_SLICE = 1;
|
|
1654
2704
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1655
2705
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
2706
|
+
var penetrationOf = (mat, table) => {
|
|
2707
|
+
if (!table) return void 0;
|
|
2708
|
+
const v = table[mat.penetrationClass ?? mat.id] ?? table[mat.id];
|
|
2709
|
+
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
2710
|
+
};
|
|
1656
2711
|
var capacityScale = (p) => {
|
|
1657
2712
|
if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
1658
2713
|
return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
|
|
@@ -1680,8 +2735,14 @@ var GameDestruction = (() => {
|
|
|
1680
2735
|
const maxHoles = options.maxHoles ?? 32;
|
|
1681
2736
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1682
2737
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
2738
|
+
const structureSafety = options.structure?.safetyFactor ?? 1;
|
|
2739
|
+
const flexure = resolveFlexure(options.structure?.flexure);
|
|
2740
|
+
const masonry = resolveMasonry(options.structure?.masonry);
|
|
2741
|
+
const soilCfg = resolveSoil(options.soil);
|
|
2742
|
+
let windOpts = options.wind;
|
|
2743
|
+
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2744
|
+
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
1683
2745
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1684
|
-
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1685
2746
|
const rng = createRng(options.seed ?? 1);
|
|
1686
2747
|
const rnd = () => rng.next();
|
|
1687
2748
|
let time = 0;
|
|
@@ -1708,7 +2769,7 @@ var GameDestruction = (() => {
|
|
|
1708
2769
|
const pendingCollapse = /* @__PURE__ */ new Map();
|
|
1709
2770
|
let runDepth = 0;
|
|
1710
2771
|
const drainBlasts = () => {
|
|
1711
|
-
for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source);
|
|
2772
|
+
for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source, void 0, void 0, blast.lethalRadius);
|
|
1712
2773
|
};
|
|
1713
2774
|
const run = (fn) => {
|
|
1714
2775
|
const prev = cur;
|
|
@@ -1778,7 +2839,8 @@ var GameDestruction = (() => {
|
|
|
1778
2839
|
collapsed: new Set(carry?.collapsed ?? []),
|
|
1779
2840
|
unsup: [],
|
|
1780
2841
|
anyAnchor: false,
|
|
1781
|
-
order: ++orderCounter
|
|
2842
|
+
order: ++orderCounter,
|
|
2843
|
+
base: { pos: [pos[0], pos[1], pos[2]], rot: [rot[0], rot[1], rot[2], rot[3]] }
|
|
1782
2844
|
};
|
|
1783
2845
|
for (const ps of spec.parts) {
|
|
1784
2846
|
if (o.byId.has(ps.id)) throw new Error(`duplicate part id "${ps.id}"`);
|
|
@@ -1830,10 +2892,17 @@ var GameDestruction = (() => {
|
|
|
1830
2892
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1831
2893
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1832
2894
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1833
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
2895
|
+
cap: ps.structure ? partCapacity(ps, mat, { masonry, flexure }) : 0,
|
|
2896
|
+
orphan: false,
|
|
1834
2897
|
staged,
|
|
1835
2898
|
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1836
|
-
layers
|
|
2899
|
+
layers,
|
|
2900
|
+
span: 0,
|
|
2901
|
+
spanKey: "",
|
|
2902
|
+
restMoment: 0,
|
|
2903
|
+
bendLoad: 0,
|
|
2904
|
+
snap: false,
|
|
2905
|
+
...ps.shape.kind === "box" && isFlexural(ps, mat, flexure) ? { bend: bendGeometry(ps) } : {}
|
|
1837
2906
|
};
|
|
1838
2907
|
if (p.structural) o.hasStructure = true;
|
|
1839
2908
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1867,7 +2936,14 @@ var GameDestruction = (() => {
|
|
|
1867
2936
|
updateBound(o);
|
|
1868
2937
|
o.anyAnchor = o.parts.some((p) => p.anchored);
|
|
1869
2938
|
o.unsup = o.parts.map(() => false);
|
|
2939
|
+
if (windCfg && o.hasStructure && spec.wind !== false) o.wind = newWind(o, carry?.wind);
|
|
2940
|
+
const ground = spec.soil ?? soilCfg.site;
|
|
2941
|
+
if (ground !== void 0 && o.parts.some((p) => p.spec.structure?.role === "foundation")) initSoil(o, profileOf(ground), carry?.soil, carry?.soilSets);
|
|
2942
|
+
o.seeding = true;
|
|
1870
2943
|
solveStructure(o);
|
|
2944
|
+
o.seeding = false;
|
|
2945
|
+
if (o.soil && !carry?.soil) settleSoil(o, 0);
|
|
2946
|
+
if (o.soil) o.soil.dirty = false;
|
|
1871
2947
|
recomputeGroups(o);
|
|
1872
2948
|
o.st.destroyed = o.parts.length > 0 && o.parts.every(isLost);
|
|
1873
2949
|
o.active = o.maxStress > 0.8 || o.parts.some((p) => p.st.burning || p.st.temperature > ambient + 0.5 || (p.st.liters ?? 0) > 0);
|
|
@@ -1919,12 +2995,126 @@ var GameDestruction = (() => {
|
|
|
1919
2995
|
for (const p of o.parts) if (isLost(p)) p.st.group = -1;
|
|
1920
2996
|
list.forEach((g, gi) => g.ix.forEach((i) => o.parts[i].st.group = gi));
|
|
1921
2997
|
};
|
|
1922
|
-
const restsOn = (j, r) => {
|
|
2998
|
+
const restsOn = (o, j, r) => {
|
|
1923
2999
|
const b = j.bears;
|
|
1924
3000
|
if (!b) return -1;
|
|
1925
|
-
|
|
1926
|
-
if (
|
|
1927
|
-
return
|
|
3001
|
+
const k = b === true ? j.ia === r ? j.ib : j.ia : b === "a_on_b" ? j.ia === r ? j.ib : -1 : j.ib === r ? j.ia : -1;
|
|
3002
|
+
if (k < 0 || j.spec.kind !== "tie") return k;
|
|
3003
|
+
return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
|
|
3004
|
+
};
|
|
3005
|
+
const contactBox = (p, support) => {
|
|
3006
|
+
const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
|
|
3007
|
+
const lo = [0, 0, 0], hi = [0, 0, 0];
|
|
3008
|
+
for (let k = 0; k < 3; k++) {
|
|
3009
|
+
const a = Math.max(A.min[k], B.min[k]) - SUPPORT_TOLERANCE, b = Math.min(A.max[k], B.max[k]) + SUPPORT_TOLERANCE;
|
|
3010
|
+
lo[k] = Math.min(a, b);
|
|
3011
|
+
hi[k] = Math.max(a, b);
|
|
3012
|
+
}
|
|
3013
|
+
const out = [[Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity]];
|
|
3014
|
+
for (let c = 0; c < 8; c++) {
|
|
3015
|
+
const l = toLocal(p.pos, p.q, [c & 1 ? hi[0] : lo[0], c & 2 ? hi[1] : lo[1], c & 4 ? hi[2] : lo[2]]);
|
|
3016
|
+
for (let k = 0; k < 3; k++) {
|
|
3017
|
+
out[0][k] = Math.min(out[0][k], l[k]);
|
|
3018
|
+
out[1][k] = Math.max(out[1][k], l[k]);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return out;
|
|
3022
|
+
};
|
|
3023
|
+
const contactCentre = (p, above) => {
|
|
3024
|
+
const [lo, hi] = contactBox(p, above);
|
|
3025
|
+
return toWorld(p.pos, p.q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
3026
|
+
};
|
|
3027
|
+
const overSupportAt = (o, p, at, lower) => {
|
|
3028
|
+
const g = p.bend;
|
|
3029
|
+
const local = toLocal(p.pos, p.q, at);
|
|
3030
|
+
const depth = g.gravity >= 0 ? g.dims[g.gravity] : Math.min(...g.depthAxes.map((a) => g.dims[a]));
|
|
3031
|
+
let best = -1;
|
|
3032
|
+
let bestGap = depth;
|
|
3033
|
+
for (const k of lower) {
|
|
3034
|
+
const [a, b] = contactBox(p, o.parts[k]);
|
|
3035
|
+
let gap = 0;
|
|
3036
|
+
for (const ax of g.spanAxes) gap = Math.max(gap, a[ax] - local[ax], local[ax] - b[ax]);
|
|
3037
|
+
if (gap <= bestGap) {
|
|
3038
|
+
best = k;
|
|
3039
|
+
bestGap = gap - 1e-9;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
return best;
|
|
3043
|
+
};
|
|
3044
|
+
const sectionOf = (p, axis) => {
|
|
3045
|
+
const g = p.bend;
|
|
3046
|
+
const section = { b: sectionWidth(g, axis), h: g.dims[axis] };
|
|
3047
|
+
const rebar = tensionSteel(p.spec, g.dims, section, flexure);
|
|
3048
|
+
return { section, rebar, ...sectionMoments(p.mat, section, rebar, flexure) };
|
|
3049
|
+
};
|
|
3050
|
+
const restFlexure = (o, p, lower) => {
|
|
3051
|
+
const g = p.bend;
|
|
3052
|
+
const key = lower.join(",");
|
|
3053
|
+
if (key !== p.spanKey) {
|
|
3054
|
+
p.spanKey = key;
|
|
3055
|
+
p.span = spanOf(g, lower.map((k) => contactBox(p, o.parts[k])));
|
|
3056
|
+
}
|
|
3057
|
+
p.restMoment = 0;
|
|
3058
|
+
if (g.gravity < 0 || p.span <= 0) return 0;
|
|
3059
|
+
const sec = sectionOf(p, g.gravity);
|
|
3060
|
+
p.restMoment = spanMoment(lineLoad(g, p.bendLoad) * Math.abs(g.up[g.gravity]), p.span);
|
|
3061
|
+
const holds = (p.st.cracked ? sec.steel : sec.ultimate) * capacityScale(p);
|
|
3062
|
+
return p.restMoment * structureSafety / Math.max(holds, 1);
|
|
3063
|
+
};
|
|
3064
|
+
const bendByBlast = (o, p, point, energy, radius, reach, src) => {
|
|
3065
|
+
const g = p.bend;
|
|
3066
|
+
if (!g || p.span <= 0 || isLost(p) || p.st.stripped || reach <= 0) return;
|
|
3067
|
+
const s = p.mat.strength;
|
|
3068
|
+
if (!s.elasticGPa) return;
|
|
3069
|
+
const { c, q } = partXf(o, p);
|
|
3070
|
+
const near = surfacePoint(p.spec.shape, toLocal(c, q, point));
|
|
3071
|
+
const local = toLocal(c, q, point);
|
|
3072
|
+
const away = vec.normalize(vec.sub(near.point, local), vec.scale(near.normal, -1));
|
|
3073
|
+
let axis = g.depthAxes[0];
|
|
3074
|
+
for (const a of g.depthAxes) if (Math.abs(away[a]) > Math.abs(away[axis])) axis = a;
|
|
3075
|
+
const cos = Math.abs(away[axis]);
|
|
3076
|
+
if (cos < 1e-3) return;
|
|
3077
|
+
const tnt = energy / flexure.tntJoules;
|
|
3078
|
+
const L = p.span;
|
|
3079
|
+
const offsets = g.member ? [[-0.5], [-0.25], [0], [0.25], [0.5]] : [[0, 0], [-0.25, 0], [0.25, 0], [0, -0.25], [0, 0.25]];
|
|
3080
|
+
const face = -Math.sign(away[axis]) * g.dims[axis] / 2;
|
|
3081
|
+
let sum = 0;
|
|
3082
|
+
for (const off of offsets) {
|
|
3083
|
+
const sp = [near.point[0], near.point[1], near.point[2]];
|
|
3084
|
+
g.spanAxes.forEach((a, k) => sp[a] = Math.max(-g.dims[a] / 2, Math.min(g.dims[a] / 2, sp[a] + off[k] * L)));
|
|
3085
|
+
sp[axis] = face;
|
|
3086
|
+
const r = vec.distance(toWorld(c, q, sp), point);
|
|
3087
|
+
if (r < radius) sum += blastImpulse(tnt, r, flexure);
|
|
3088
|
+
}
|
|
3089
|
+
const width = sectionWidth(g, axis);
|
|
3090
|
+
const impulse = sum / offsets.length * cos * reach * width * L;
|
|
3091
|
+
const weight = axis === g.gravity ? p.bendLoad : p.st.mass;
|
|
3092
|
+
const kinetic = impulseEnergy(impulse, lineLoad(g, weight) / GRAVITY * L, flexure);
|
|
3093
|
+
const sec = sectionOf(p, axis);
|
|
3094
|
+
const EI = s.elasticGPa * 1e9 * sec.section.b * sec.section.h ** 3 / 12;
|
|
3095
|
+
const along = axis === g.gravity ? -Math.sign(away[axis] * g.up[axis]) : 0;
|
|
3096
|
+
const dynamic = momentOfEnergy(kinetic, L, EI, flexure.loadFactor);
|
|
3097
|
+
const peak = Math.abs(dynamic + along * p.restMoment);
|
|
3098
|
+
if (peak <= sec.cracking) return;
|
|
3099
|
+
if (!p.st.cracked) {
|
|
3100
|
+
p.st.cracked = true;
|
|
3101
|
+
const tension = [near.point[0], near.point[1], near.point[2]];
|
|
3102
|
+
tension[axis] = dynamic + along * p.restMoment >= 0 ? -face : face;
|
|
3103
|
+
const n = [0, 0, 0];
|
|
3104
|
+
n[axis] = Math.sign(tension[axis]) || 1;
|
|
3105
|
+
addCrack(o, p, toWorld(c, q, tension), rotateVec(q, n), sec.section.b, src);
|
|
3106
|
+
}
|
|
3107
|
+
if (!sec.reinforced) {
|
|
3108
|
+
p.snap = true;
|
|
3109
|
+
failPart(o, p, point, away, kinetic, 12, src, "snap");
|
|
3110
|
+
return;
|
|
3111
|
+
}
|
|
3112
|
+
solveStructure(o);
|
|
3113
|
+
if (peak <= sec.steel) return;
|
|
3114
|
+
const rotation = (elasticEnergy(peak, L, EI, flexure.loadFactor) - elasticEnergy(sec.steel, L, EI, flexure.loadFactor)) / (2 * sec.steel);
|
|
3115
|
+
p.rt.hinge = (p.rt.hinge ?? 0) + rotation;
|
|
3116
|
+
markDamaged(p);
|
|
3117
|
+
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
1928
3118
|
};
|
|
1929
3119
|
const solveStructure = (o) => {
|
|
1930
3120
|
o.maxStress = 0;
|
|
@@ -1945,23 +3135,41 @@ var GameDestruction = (() => {
|
|
|
1945
3135
|
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
1946
3136
|
if (j.st.open || !j.bears) continue;
|
|
1947
3137
|
const r = j.ia === s ? j.ib : j.ia;
|
|
1948
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(j, r) !== s) continue;
|
|
3138
|
+
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
1949
3139
|
dist[r] = dist[s] + 1;
|
|
1950
3140
|
queue.push(r);
|
|
1951
3141
|
}
|
|
1952
3142
|
}
|
|
1953
3143
|
const incoming = new Array(n).fill(0);
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
const total = parts[i].st.mass + incoming[i];
|
|
1957
|
-
parts[i].st.load = total;
|
|
3144
|
+
const lowerOf = /* @__PURE__ */ new Map();
|
|
3145
|
+
for (const i of queue) {
|
|
1958
3146
|
const lower = [];
|
|
1959
3147
|
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
1960
3148
|
if (j.st.open || !j.bears) continue;
|
|
1961
|
-
const k = restsOn(j, i);
|
|
3149
|
+
const k = restsOn(o, j, i);
|
|
1962
3150
|
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
1963
3151
|
}
|
|
3152
|
+
lowerOf.set(i, lower);
|
|
3153
|
+
}
|
|
3154
|
+
const bending = new Array(n).fill(0);
|
|
3155
|
+
const through = parts.map(() => []);
|
|
3156
|
+
const pass = (k, at, mass) => {
|
|
3157
|
+
const over = overSupportAt(o, parts[k], at, lowerOf.get(k) ?? []);
|
|
3158
|
+
if (over < 0) bending[k] += mass;
|
|
3159
|
+
else through[k].push({ at, mass, over });
|
|
3160
|
+
};
|
|
3161
|
+
for (let h = queue.length - 1; h >= 0; h--) {
|
|
3162
|
+
const i = queue[h];
|
|
3163
|
+
const total = parts[i].st.mass + incoming[i];
|
|
3164
|
+
parts[i].st.load = total;
|
|
3165
|
+
const lower = lowerOf.get(i);
|
|
1964
3166
|
for (const k of lower) incoming[k] += total / lower.length;
|
|
3167
|
+
if (lower.length === 0) continue;
|
|
3168
|
+
const bulk = parts[i].bend ? parts[i].st.mass + bending[i] : total;
|
|
3169
|
+
for (const k of lower) if (parts[k].bend) pass(k, contactCentre(parts[k], parts[i]), bulk / lower.length);
|
|
3170
|
+
if (parts[i].bend) {
|
|
3171
|
+
for (const item of through[i]) if (parts[item.over]?.bend) pass(item.over, item.at, item.mass);
|
|
3172
|
+
}
|
|
1965
3173
|
}
|
|
1966
3174
|
for (let i = 0; i < n; i++) {
|
|
1967
3175
|
const p = parts[i];
|
|
@@ -1970,13 +3178,420 @@ var GameDestruction = (() => {
|
|
|
1970
3178
|
if (dist[i] < 0 || isLost(p)) {
|
|
1971
3179
|
p.st.load = p.st.mass;
|
|
1972
3180
|
p.st.stress = 0;
|
|
1973
|
-
o.
|
|
3181
|
+
if (o.seeding && !p.anchored && !isLost(p) && p.st.status !== "collapsed" && !o.collapsed.has(p.spec.id)) p.orphan = true;
|
|
3182
|
+
o.unsup[i] = o.anyAnchor && !isLost(p) && !p.orphan;
|
|
1974
3183
|
continue;
|
|
1975
3184
|
}
|
|
1976
|
-
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
3185
|
+
p.st.stress = p.st.load * structureSafety / (p.cap * capacityScale(p));
|
|
3186
|
+
if (p.bend) {
|
|
3187
|
+
p.bendLoad = p.st.mass + bending[i];
|
|
3188
|
+
const bent = restFlexure(o, p, lowerOf.get(i) ?? []);
|
|
3189
|
+
p.st.flexure = bent;
|
|
3190
|
+
p.snap = bent > 1 && !p.st.stripped && !sectionOf(p, p.bend.gravity).reinforced;
|
|
3191
|
+
if (p.spec.structure?.capacity === void 0 || bent > p.st.stress) p.st.stress = bent;
|
|
3192
|
+
}
|
|
1977
3193
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1978
3194
|
}
|
|
3195
|
+
if (o.wind) applyWind(o);
|
|
1979
3196
|
if (o.maxStress > 0.8) o.active = true;
|
|
3197
|
+
if (o.soil) o.soil.dirty = true;
|
|
3198
|
+
};
|
|
3199
|
+
const newWind = (o, carried) => {
|
|
3200
|
+
const cfg = exposureOf(windCfg, o.spec.wind);
|
|
3201
|
+
const [x = 0, v = 0, y = 0, w = 0, peak = 0] = carried ?? [];
|
|
3202
|
+
return {
|
|
3203
|
+
cfg,
|
|
3204
|
+
gust: { freq: [], amp: [], phase: [] },
|
|
3205
|
+
gustKey: "",
|
|
3206
|
+
analysis: null,
|
|
3207
|
+
parts: [],
|
|
3208
|
+
sys: null,
|
|
3209
|
+
mu: 0,
|
|
3210
|
+
dyn: { x, v, y, w, peak },
|
|
3211
|
+
tension: /* @__PURE__ */ new Map(),
|
|
3212
|
+
emitted: { t: -Infinity, peak: 0, status: "ok" }
|
|
3213
|
+
};
|
|
3214
|
+
};
|
|
3215
|
+
const windDirection = (o, cfg) => {
|
|
3216
|
+
const d = rotateVec(quat.conjugate(o.base.rot), cfg.direction);
|
|
3217
|
+
const h = Math.hypot(d[0], d[2]);
|
|
3218
|
+
return h > 1e-9 ? [d[0] / h, d[2] / h] : [1, 0];
|
|
3219
|
+
};
|
|
3220
|
+
const applyWind = (o) => {
|
|
3221
|
+
const w = o.wind;
|
|
3222
|
+
const cfg = w.cfg = exposureOf(windCfg, o.spec.wind);
|
|
3223
|
+
const terrain = cfg.terrains.get(cfg.terrain);
|
|
3224
|
+
if (!terrain) throw new Error(`unknown wind terrain "${cfg.terrain}"`);
|
|
3225
|
+
const inputs = [];
|
|
3226
|
+
const at = /* @__PURE__ */ new Map();
|
|
3227
|
+
const keep = [];
|
|
3228
|
+
let ground = Infinity, foundationTop = -Infinity;
|
|
3229
|
+
o.parts.forEach((p, i) => {
|
|
3230
|
+
if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[i]) return;
|
|
3231
|
+
const box = aabbOf(p.spec.shape, p.pos, p.q);
|
|
3232
|
+
const role = p.spec.structure?.role;
|
|
3233
|
+
const e = p.mat.strength?.elasticGPa;
|
|
3234
|
+
const kind = role === "wall" ? "wall" : role === "column" ? "column" : role === "beam" ? "beam" : role === "foundation" ? "foundation" : "other";
|
|
3235
|
+
at.set(i, inputs.length);
|
|
3236
|
+
keep.push(i);
|
|
3237
|
+
ground = Math.min(ground, box.min[1]);
|
|
3238
|
+
if (kind === "foundation") foundationTop = Math.max(foundationTop, box.max[1]);
|
|
3239
|
+
inputs.push({ min: box.min, max: box.max, kind, ...e ? { elasticPa: e * 1e9 } : {}, mass: p.st.mass, ...p.spec.damper ? { damper: true } : {} });
|
|
3240
|
+
});
|
|
3241
|
+
keep.forEach((i, k) => {
|
|
3242
|
+
if (inputs[k].kind !== "column") return;
|
|
3243
|
+
const beams = [];
|
|
3244
|
+
for (const j of o.byPart.get(o.parts[i].spec.id)) {
|
|
3245
|
+
if (j.st.open) continue;
|
|
3246
|
+
const b = at.get(j.ia === i ? j.ib : j.ia);
|
|
3247
|
+
if (b !== void 0 && inputs[b].kind === "beam") beams.push(b);
|
|
3248
|
+
}
|
|
3249
|
+
inputs[k].beams = beams;
|
|
3250
|
+
});
|
|
3251
|
+
keep.forEach((i, k) => {
|
|
3252
|
+
if (inputs[k].kind !== "beam") return;
|
|
3253
|
+
for (const j of o.byPart.get(o.parts[i].spec.id)) {
|
|
3254
|
+
if (j.st.open) continue;
|
|
3255
|
+
const q = o.parts[j.ia === i ? j.ib : j.ia];
|
|
3256
|
+
const role = q.spec.structure?.role;
|
|
3257
|
+
if ((role === "slab" || role === "roof") && !isLost(q) && q.spec.shape.kind === "box") {
|
|
3258
|
+
inputs[k].slab = q.dims[1];
|
|
3259
|
+
break;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
const c = {
|
|
3264
|
+
speed: cfg.speed,
|
|
3265
|
+
direction: windDirection(o, cfg),
|
|
3266
|
+
terrain,
|
|
3267
|
+
cls: cfg.class,
|
|
3268
|
+
topography: cfg.topography,
|
|
3269
|
+
statistical: cfg.statistical,
|
|
3270
|
+
drag: cfg.drag,
|
|
3271
|
+
damping: cfg.damping,
|
|
3272
|
+
airDensity: cfg.airDensity,
|
|
3273
|
+
minHeight: cfg.minHeight,
|
|
3274
|
+
bandHeight: cfg.bandHeight,
|
|
3275
|
+
stiffnessFactor: cfg.stiffnessFactor,
|
|
3276
|
+
beamFactor: cfg.beamFactor,
|
|
3277
|
+
loadFactor: cfg.loadFactor,
|
|
3278
|
+
chord: cfg.chord,
|
|
3279
|
+
meanRatio: cfg.meanRatio,
|
|
3280
|
+
gustFactor: cfg.gustFactor,
|
|
3281
|
+
flange: cfg.flange
|
|
3282
|
+
};
|
|
3283
|
+
const a = inputs.length && cfg.speed > 0 ? analyzeWind(inputs, c, foundationTop > -Infinity ? foundationTop : ground) : null;
|
|
3284
|
+
w.analysis = a;
|
|
3285
|
+
w.parts = keep;
|
|
3286
|
+
for (const p of o.parts) delete p.st.wind;
|
|
3287
|
+
if (!a) {
|
|
3288
|
+
w.sys = null;
|
|
3289
|
+
delete o.st.wind;
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
keep.forEach((i, k) => {
|
|
3293
|
+
const N = a.axial[k];
|
|
3294
|
+
const p = o.parts[i];
|
|
3295
|
+
if (!N || !p.structural || o.unsup[i]) return;
|
|
3296
|
+
p.st.wind = N;
|
|
3297
|
+
const dead = p.st.load ?? p.st.mass;
|
|
3298
|
+
const force = N * cfg.loadFactor;
|
|
3299
|
+
let stress = 0;
|
|
3300
|
+
if (force >= 0) stress = (dead * structureSafety + force / GRAVITY) / (p.cap * capacityScale(p));
|
|
3301
|
+
else if (dead + force / GRAVITY < 0) {
|
|
3302
|
+
let cap = w.tension.get(i);
|
|
3303
|
+
if (cap === void 0) {
|
|
3304
|
+
const r = p.spec.reinforcement;
|
|
3305
|
+
const ratio = p.spec.structure?.rebar?.ratio;
|
|
3306
|
+
cap = tensionCapacity(p.dims, {
|
|
3307
|
+
...r ? { reinforcement: r } : {},
|
|
3308
|
+
...ratio ? { ratio } : {},
|
|
3309
|
+
yieldMPa: flexure.rebarYieldMPa,
|
|
3310
|
+
...p.mat.strength?.tensileMPa !== void 0 ? { tensileMPa: p.mat.strength.tensileMPa } : {}
|
|
3311
|
+
});
|
|
3312
|
+
w.tension.set(i, cap);
|
|
3313
|
+
}
|
|
3314
|
+
stress = Math.min(10, -(dead + force / GRAVITY) * GRAVITY / Math.max(cap * capacityScale(p), 1));
|
|
3315
|
+
}
|
|
3316
|
+
if (stress > (p.st.stress ?? 0)) p.st.stress = stress;
|
|
3317
|
+
if ((p.st.stress ?? 0) > o.maxStress) o.maxStress = p.st.stress ?? 0;
|
|
3318
|
+
});
|
|
3319
|
+
const key = `${cfg.speed}|${cfg.terrain}|${cfg.topography}|${cfg.statistical}|${cfg.meanRatio}|${cfg.gust.components}|${cfg.gust.maxHz}|${cfg.gust.scale}|${cfg.gust.seed}`;
|
|
3320
|
+
if (key !== w.gustKey) {
|
|
3321
|
+
w.gustKey = key;
|
|
3322
|
+
const v10 = cfg.speed * cfg.topography * cfg.statistical * meanHeightFactor(10, terrain, a.cls, { meanRatio: cfg.meanRatio, minHeight: cfg.minHeight });
|
|
3323
|
+
w.gust = makeGust(v10, cfg.gust);
|
|
3324
|
+
}
|
|
3325
|
+
let md = 0, spec;
|
|
3326
|
+
for (const i of keep) {
|
|
3327
|
+
const p = o.parts[i];
|
|
3328
|
+
if (!p.spec.damper) continue;
|
|
3329
|
+
const phi = shapeAt(a, p.pos[1] - (foundationTop > -Infinity ? foundationTop : ground));
|
|
3330
|
+
md += p.st.mass * phi * phi;
|
|
3331
|
+
spec ?? (spec = p.spec.damper);
|
|
3332
|
+
}
|
|
3333
|
+
const sys = { M: a.modalMass, K: a.modalStiffness, C: 2 * cfg.damping * a.omega * a.modalMass };
|
|
3334
|
+
w.mu = 0;
|
|
3335
|
+
if (md > 0 && a.modalMass > 0) {
|
|
3336
|
+
w.mu = md / a.modalMass;
|
|
3337
|
+
const tuned = damperTuning(w.mu, cfg.tuning);
|
|
3338
|
+
const f = spec?.frequency ?? tuned.frequency * a.frequency;
|
|
3339
|
+
const zeta = spec?.damping ?? tuned.damping;
|
|
3340
|
+
sys.damper = { m: md, k: md * (2 * Math.PI * f) ** 2, c: 2 * zeta * md * 2 * Math.PI * f };
|
|
3341
|
+
}
|
|
3342
|
+
w.sys = a.modalMass > 0 && a.omega > 0 ? sys : null;
|
|
3343
|
+
const st = {
|
|
3344
|
+
speed: cfg.speed,
|
|
3345
|
+
pressure: a.pressure,
|
|
3346
|
+
shear: a.shear,
|
|
3347
|
+
moment: a.moment,
|
|
3348
|
+
deflection: a.deflection,
|
|
3349
|
+
drift: a.drift,
|
|
3350
|
+
limit: cfg.driftLimit,
|
|
3351
|
+
overturn: Math.min(a.overturn, 1e3),
|
|
3352
|
+
frequency: a.frequency,
|
|
3353
|
+
lean: a.lean,
|
|
3354
|
+
sway: w.dyn.x,
|
|
3355
|
+
peak: w.dyn.peak,
|
|
3356
|
+
status: a.overturn > 1 ? "overturn" : a.drift > cfg.driftLimit ? "drift" : "ok"
|
|
3357
|
+
};
|
|
3358
|
+
if (sys.damper) st.damper = { ratio: w.mu, travel: w.dyn.y - w.dyn.x };
|
|
3359
|
+
o.st.wind = st;
|
|
3360
|
+
};
|
|
3361
|
+
const stepWind = (o, dt) => {
|
|
3362
|
+
const w = o.wind;
|
|
3363
|
+
const a = w.analysis;
|
|
3364
|
+
const st = o.st.wind;
|
|
3365
|
+
if (!a || !st || !w.sys) return;
|
|
3366
|
+
const gust = w.gust;
|
|
3367
|
+
const I = a.intensity;
|
|
3368
|
+
const force = (t) => {
|
|
3369
|
+
const n = gustAt(gust, t);
|
|
3370
|
+
return a.modalForce * (2 * I * n + I * I * (n * n - 1));
|
|
3371
|
+
};
|
|
3372
|
+
const wn = Math.max(a.omega, w.sys.damper ? Math.sqrt(w.sys.damper.k / w.sys.damper.m) : 0);
|
|
3373
|
+
stepSway(w.dyn, w.sys, time - dt, dt, force, 2 * Math.PI / wn / 20);
|
|
3374
|
+
const d = w.dyn;
|
|
3375
|
+
d.peak = Math.max(Math.abs(d.x), d.peak * Math.exp(-dt / w.cfg.peakDecay));
|
|
3376
|
+
st.sway = d.x;
|
|
3377
|
+
st.peak = d.peak;
|
|
3378
|
+
if (st.damper) st.damper.travel = d.y - d.x;
|
|
3379
|
+
const e = w.emitted;
|
|
3380
|
+
if (d.peak < e.peak * 0.8) e.peak = d.peak;
|
|
3381
|
+
if (st.status !== e.status || time - e.t >= 0.5 && d.peak > e.peak * 1.05 + 1e-3) {
|
|
3382
|
+
w.emitted = { t: time, peak: d.peak, status: st.status };
|
|
3383
|
+
const dir = windDirection(o, w.cfg);
|
|
3384
|
+
const world2 = rotateVec(o.base.rot, [dir[0], 0, dir[1]]);
|
|
3385
|
+
emit({ ...base(o), type: "sway", sway: d.x, peak: d.peak, drift: st.drift, overturn: st.overturn, frequency: st.frequency, direction: world2, status: st.status });
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
const isTerrain = (p) => !!p.spec.tags?.includes("terrain");
|
|
3389
|
+
const isFoundation = (p) => p.spec.structure?.role === "foundation" && !isLost(p);
|
|
3390
|
+
const slopeTilt = (a, b) => Math.atan(Math.hypot(a, b));
|
|
3391
|
+
const soilSprings = (o, profile) => {
|
|
3392
|
+
const found = o.parts.filter(isFoundation);
|
|
3393
|
+
const box = (p) => aabbOf(p.spec.shape, p.pos, p.q);
|
|
3394
|
+
const surface = profile.surface ?? Math.max(...found.map((p) => box(p).max[1]));
|
|
3395
|
+
const prints = [];
|
|
3396
|
+
for (const f of found) {
|
|
3397
|
+
const b = box(f);
|
|
3398
|
+
if (!isTerrain(f)) {
|
|
3399
|
+
prints.push({ x0: b.min[0], x1: b.max[0], z0: b.min[2], z1: b.max[2], bottom: b.min[1], part: f });
|
|
3400
|
+
continue;
|
|
3401
|
+
}
|
|
3402
|
+
const fi = o.idx.get(f.spec.id);
|
|
3403
|
+
const width = profile.footingWidth ?? soilCfg.footingWidth;
|
|
3404
|
+
for (const j of o.byPart.get(f.spec.id)) {
|
|
3405
|
+
if (j.st.open || !j.bears) continue;
|
|
3406
|
+
const r = j.ia === fi ? j.ib : j.ia;
|
|
3407
|
+
const q = o.parts[r];
|
|
3408
|
+
if (!q.structural || isLost(q) || restsOn(o, j, r) !== fi) continue;
|
|
3409
|
+
const c = box(q);
|
|
3410
|
+
const x0 = Math.max(c.min[0], b.min[0]), x1 = Math.min(c.max[0], b.max[0]), z0 = Math.max(c.min[2], b.min[2]), z1 = Math.min(c.max[2], b.max[2]);
|
|
3411
|
+
const cx = (x0 + x1) / 2, cz = (z0 + z1) / 2;
|
|
3412
|
+
const wx = Math.max(width, x1 - x0), wz = Math.max(width, z1 - z0);
|
|
3413
|
+
prints.push({ x0: cx - wx / 2, x1: cx + wx / 2, z0: cz - wz / 2, z1: cz + wz / 2, bottom: c.min[1], part: q });
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
const springs = [];
|
|
3417
|
+
for (const fp of prints) {
|
|
3418
|
+
const wx = Math.max(0.01, fp.x1 - fp.x0), wz = Math.max(0.01, fp.z1 - fp.z0);
|
|
3419
|
+
const depth = Math.max(0, surface - fp.bottom);
|
|
3420
|
+
const pile = fp.part.spec.structure?.role === "foundation" ? fp.part.spec.structure.pile : void 0;
|
|
3421
|
+
if (pile) {
|
|
3422
|
+
const count = Math.max(1, Math.round(pile.count ?? 1));
|
|
3423
|
+
const nx2 = Math.ceil(Math.sqrt(count)), nz2 = Math.ceil(count / nx2);
|
|
3424
|
+
const cap = pileCapacity(profile, pile, depth, soilCfg.byId).ultimate;
|
|
3425
|
+
const tip = soilAt(profile, depth + pile.length, soilCfg.byId);
|
|
3426
|
+
const k = pileStiffness(pile, fp.part.mat.strength?.elasticGPa ?? soilCfg.pileElasticGPa, tip.elasticMPa, tip.poisson);
|
|
3427
|
+
for (let i = 0; i < count; i++) {
|
|
3428
|
+
const col = i % nx2, row = Math.floor(i / nx2);
|
|
3429
|
+
springs.push({ key: `${fp.part.spec.id}#${i}`, x: fp.x0 + (col + 0.5) * wx / nx2, z: fp.z0 + (row + 0.5) * wz / nz2, hx: wx / (2 * nx2), hz: wz / (2 * nz2), k, cap });
|
|
3430
|
+
}
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
const B = Math.min(wx, wz);
|
|
3434
|
+
const g = footingSoil(profile, depth, B, soilCfg.byId, soilCfg.influence);
|
|
3435
|
+
const ks = subgradeModulus(g.elasticMPa, g.poisson, B, soilCfg.rigidity);
|
|
3436
|
+
const size = Math.max(wx, wz) / Math.max(1, soilCfg.cells);
|
|
3437
|
+
const nx = Math.max(1, Math.round(wx / size)), nz = Math.max(1, Math.round(wz / size));
|
|
3438
|
+
const area = wx * wz / (nx * nz);
|
|
3439
|
+
for (let i = 0; i < nx; i++) {
|
|
3440
|
+
for (let k = 0; k < nz; k++) {
|
|
3441
|
+
springs.push({ key: `${fp.part.spec.id}#${i}.${k}`, x: fp.x0 + (i + 0.5) * wx / nx, z: fp.z0 + (k + 0.5) * wz / nz, hx: wx / (2 * nx), hz: wz / (2 * nz), k: ks * area, cap: soilCfg.safety * g.allowableKPa * 1e3 * area });
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
return { springs, baseY: prints.length ? Math.min(...prints.map((fp) => fp.bottom)) : 0 };
|
|
3446
|
+
};
|
|
3447
|
+
const soilWeight = (o) => {
|
|
3448
|
+
let m = 0;
|
|
3449
|
+
const c = [0, 0, 0];
|
|
3450
|
+
for (const p of o.parts) {
|
|
3451
|
+
if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[o.idx.get(p.spec.id)]) continue;
|
|
3452
|
+
m += p.st.mass;
|
|
3453
|
+
for (let k = 0; k < 3; k++) c[k] += p.st.mass * p.pos[k];
|
|
3454
|
+
}
|
|
3455
|
+
return { w: m * GRAVITY, c: m > 0 ? [c[0] / m, c[1] / m, c[2] / m] : c };
|
|
3456
|
+
};
|
|
3457
|
+
const initSoil = (o, profile, carried, carrySets) => {
|
|
3458
|
+
const so = { profile, s: 0, a: 0, b: 0, imp: [0, 0], pivot: [0, 0, 0], dirty: true, sets: /* @__PURE__ */ new Map(), emitted: { t: -Infinity, s: 0, tilt: 0, status: "stable" } };
|
|
3459
|
+
const { springs, baseY } = soilSprings(o, profile);
|
|
3460
|
+
if (!springs.length) return;
|
|
3461
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
3462
|
+
so.pivot = [x0, baseY, z0];
|
|
3463
|
+
let ix = 0, iz = 0;
|
|
3464
|
+
for (const sp of springs) {
|
|
3465
|
+
ix += sp.k * (sp.x - x0) ** 2;
|
|
3466
|
+
iz += sp.k * (sp.z - z0) ** 2;
|
|
3467
|
+
}
|
|
3468
|
+
const { c } = soilWeight(o);
|
|
3469
|
+
const along = ix <= iz ? 0 : 1;
|
|
3470
|
+
const off = along === 0 ? c[0] - x0 : c[2] - z0;
|
|
3471
|
+
const sign = Math.abs(off) > 1e-6 ? Math.sign(off) : 1;
|
|
3472
|
+
so.imp = along === 0 ? [sign * soilCfg.imperfection, 0] : [0, sign * soilCfg.imperfection];
|
|
3473
|
+
if (carried) {
|
|
3474
|
+
const t = Math.tan(carried.tilt * Math.PI / 180);
|
|
3475
|
+
so.s = carried.settlement;
|
|
3476
|
+
so.a = t * carried.direction[0];
|
|
3477
|
+
so.b = t * carried.direction[2];
|
|
3478
|
+
so.emitted = { t: -Infinity, s: carried.settlement, tilt: carried.tilt, status: carried.status };
|
|
3479
|
+
o.st.soil = clone(carried);
|
|
3480
|
+
for (const [k, v] of Object.entries(carrySets ?? {})) so.sets.set(k, v);
|
|
3481
|
+
if (carried.status === "toppled") for (const p of o.parts) p.anchored = false;
|
|
3482
|
+
}
|
|
3483
|
+
o.soil = so;
|
|
3484
|
+
applySoilPose(o);
|
|
3485
|
+
};
|
|
3486
|
+
const applySoilPose = (o) => {
|
|
3487
|
+
const so = o.soil;
|
|
3488
|
+
if (!so) {
|
|
3489
|
+
o.st.position = [o.base.pos[0], o.base.pos[1], o.base.pos[2]];
|
|
3490
|
+
o.st.rotation = [o.base.rot[0], o.base.rot[1], o.base.rot[2], o.base.rot[3]];
|
|
3491
|
+
return;
|
|
3492
|
+
}
|
|
3493
|
+
const angle = slopeTilt(so.a, so.b);
|
|
3494
|
+
const R = angle > 1e-12 ? quat.fromAxisAngle(vec.normalize([so.b, 0, -so.a]), angle) : quat.identity();
|
|
3495
|
+
const t = vec.sub(vec.sub(so.pivot, rotateVec(R, so.pivot)), [0, so.s, 0]);
|
|
3496
|
+
o.st.rotation = quat.normalize(quat.mul(o.base.rot, R));
|
|
3497
|
+
o.st.position = vec.add(o.base.pos, rotateVec(o.base.rot, t));
|
|
3498
|
+
const lean = Math.hypot(so.a, so.b);
|
|
3499
|
+
const st = o.st.soil ?? { settlement: 0, tilt: 0, direction: [0, 0, 0], bearing: 0, edge: 0, status: "stable", offset: { position: [0, 0, 0], rotation: [0, 0, 0, 1] } };
|
|
3500
|
+
st.settlement = so.s;
|
|
3501
|
+
st.tilt = angle * 180 / Math.PI;
|
|
3502
|
+
st.direction = lean > 1e-12 ? [so.a / lean, 0, so.b / lean] : [0, 0, 0];
|
|
3503
|
+
st.offset = { position: t, rotation: R };
|
|
3504
|
+
o.st.soil = st;
|
|
3505
|
+
};
|
|
3506
|
+
const topple = (o, src) => {
|
|
3507
|
+
for (const p of o.parts) p.anchored = false;
|
|
3508
|
+
solveStructure(o);
|
|
3509
|
+
recomputeGroups(o);
|
|
3510
|
+
if (!pendingCollapse.has(o)) pendingCollapse.set(o, src);
|
|
3511
|
+
};
|
|
3512
|
+
const settleSoil = (o, dt) => {
|
|
3513
|
+
const so = o.soil;
|
|
3514
|
+
const st = o.st.soil;
|
|
3515
|
+
if (st?.status === "toppled") return;
|
|
3516
|
+
const { springs } = soilSprings(o, so.profile);
|
|
3517
|
+
for (const sp of springs) sp.set = so.sets.get(sp.key) ?? 0;
|
|
3518
|
+
const { w, c } = soilWeight(o);
|
|
3519
|
+
if (!springs.length || w <= 0) {
|
|
3520
|
+
if (springs.length === 0 && w > 0) {
|
|
3521
|
+
so.s += soilCfg.sinkRate * dt;
|
|
3522
|
+
applySoilPose(o);
|
|
3523
|
+
o.st.soil.status = "yielding";
|
|
3524
|
+
}
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
const height = Math.max(0, c[1] - so.pivot[1]);
|
|
3528
|
+
const wa = o.wind?.analysis;
|
|
3529
|
+
const push = wa ? windDirection(o, o.wind.cfg) : [0, 0];
|
|
3530
|
+
const shift = wa ? wa.moment / w : 0;
|
|
3531
|
+
const r = solveFoundation(springs, w, c[0] + shift * push[0], c[2] + shift * push[1], height, so.imp, { s: so.s, a: so.a, b: so.b });
|
|
3532
|
+
let status = "stable";
|
|
3533
|
+
const sinkStep = soilCfg.sinkRate * dt;
|
|
3534
|
+
const tiltStep = Math.tan(soilCfg.tiltRate * Math.PI / 180) * dt;
|
|
3535
|
+
if (r.stable && (!r.yielded || dt === 0)) {
|
|
3536
|
+
so.s = r.s;
|
|
3537
|
+
so.a = r.a;
|
|
3538
|
+
so.b = r.b;
|
|
3539
|
+
} else if (r.stable) {
|
|
3540
|
+
const ds = r.s - so.s, da = r.a - so.a, db = r.b - so.b;
|
|
3541
|
+
const fs = Math.abs(ds) > sinkStep ? sinkStep / Math.abs(ds) : 1;
|
|
3542
|
+
const lean2 = Math.hypot(da, db);
|
|
3543
|
+
const ft = lean2 > tiltStep ? tiltStep / lean2 : 1;
|
|
3544
|
+
so.s += ds * fs;
|
|
3545
|
+
so.a += da * ft;
|
|
3546
|
+
so.b += db * ft;
|
|
3547
|
+
if (fs < 1 || ft < 1) status = "yielding";
|
|
3548
|
+
} else {
|
|
3549
|
+
status = "yielding";
|
|
3550
|
+
if (dt === 0) {
|
|
3551
|
+
so.s = w / springs.reduce((sum, sp) => sum + sp.k, 0);
|
|
3552
|
+
} else {
|
|
3553
|
+
so.s += sinkStep * (r.bearing >= 1 ? 1 : 0.25);
|
|
3554
|
+
const lean2 = Math.hypot(so.a, so.b);
|
|
3555
|
+
const ux = lean2 > 1e-9 ? so.a / lean2 : Math.sign(so.imp[0]), uz = lean2 > 1e-9 ? so.b / lean2 : Math.sign(so.imp[1]);
|
|
3556
|
+
so.a += ux * tiltStep;
|
|
3557
|
+
so.b += uz * tiltStep;
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
if (so.s !== 0 || so.a !== 0 || so.b !== 0) {
|
|
3561
|
+
const sets = yieldSprings(springs, so.s, so.a, so.b);
|
|
3562
|
+
springs.forEach((sp, i) => {
|
|
3563
|
+
if (sets[i] > 0) so.sets.set(sp.key, sets[i]);
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
applySoilPose(o);
|
|
3567
|
+
const out = o.st.soil;
|
|
3568
|
+
out.bearing = r.bearing;
|
|
3569
|
+
out.edge = r.edge;
|
|
3570
|
+
out.status = status;
|
|
3571
|
+
const lean = Math.hypot(so.a, so.b);
|
|
3572
|
+
if (lean > 1e-9) {
|
|
3573
|
+
const [x0, z0] = [so.pivot[0], so.pivot[2]];
|
|
3574
|
+
const ux = so.a / lean, uz = so.b / lean;
|
|
3575
|
+
const e = (c[0] - x0 + height * (so.a + so.imp[0])) * ux + (c[2] - z0 + height * (so.b + so.imp[1])) * uz;
|
|
3576
|
+
let edge = 0;
|
|
3577
|
+
for (const sp of springs) edge = Math.max(edge, (sp.x - x0) * ux + (sp.z - z0) * uz + sp.hx * Math.abs(ux) + sp.hz * Math.abs(uz));
|
|
3578
|
+
if (e > edge || slopeTilt(so.a, so.b) > Math.PI / 3) out.status = "toppled";
|
|
3579
|
+
}
|
|
3580
|
+
};
|
|
3581
|
+
const stepSoil = (o, dt) => {
|
|
3582
|
+
const so = o.soil;
|
|
3583
|
+
if (o.st.soil?.status === "toppled") return;
|
|
3584
|
+
if (!so.dirty && o.st.soil?.status !== "yielding") return;
|
|
3585
|
+
so.dirty = false;
|
|
3586
|
+
settleSoil(o, dt);
|
|
3587
|
+
const st = o.st.soil;
|
|
3588
|
+
const e = so.emitted;
|
|
3589
|
+
const changed = st.status !== e.status || (time - e.t >= 0.5 || e.t === -Infinity) && (Math.abs(st.tilt - e.tilt) > 0.01 || Math.abs(st.settlement - e.s) > 2e-3);
|
|
3590
|
+
if (changed) {
|
|
3591
|
+
so.emitted = { t: time, s: st.settlement, tilt: st.tilt, status: st.status };
|
|
3592
|
+
emit({ ...base(o), type: "settle", settlement: st.settlement, tilt: st.tilt, direction: rotateVec(o.base.rot, st.direction), bearing: st.bearing, status: st.status });
|
|
3593
|
+
}
|
|
3594
|
+
if (st.status === "toppled") topple(o);
|
|
1980
3595
|
};
|
|
1981
3596
|
const collapseCheck = (o, src) => {
|
|
1982
3597
|
if (!o.hasStructure || !o.parts.some((p) => p.anchored)) return;
|
|
@@ -2213,7 +3828,7 @@ var GameDestruction = (() => {
|
|
|
2213
3828
|
if (split && mine.length) amount /= mine.length;
|
|
2214
3829
|
for (const j of mine) {
|
|
2215
3830
|
if (j.st.open) continue;
|
|
2216
|
-
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec
|
|
3831
|
+
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
2217
3832
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
2218
3833
|
j.st.hp -= amount;
|
|
2219
3834
|
if (j.st.hp <= 0) {
|
|
@@ -2369,14 +3984,37 @@ var GameDestruction = (() => {
|
|
|
2369
3984
|
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2370
3985
|
partLost(o, p, src);
|
|
2371
3986
|
};
|
|
2372
|
-
const
|
|
3987
|
+
const hingeDrop = (o, p, src) => {
|
|
3988
|
+
const i = o.idx.get(p.spec.id);
|
|
3989
|
+
const opened = [];
|
|
3990
|
+
for (const j of o.byPart.get(p.spec.id) ?? []) {
|
|
3991
|
+
if (j.st.open || restsOn(o, j, i) < 0) continue;
|
|
3992
|
+
j.st.open = true;
|
|
3993
|
+
j.st.hp = 0;
|
|
3994
|
+
j.st.openedBy = "break";
|
|
3995
|
+
opened.push(j);
|
|
3996
|
+
}
|
|
3997
|
+
p.st.hp = Math.max(1, p.st.maxHp * 0.1);
|
|
3998
|
+
p.st.cracked = true;
|
|
3999
|
+
markDamaged(p);
|
|
4000
|
+
debris(o, p, p.mat.breakDebris, partXf(o, p).c, [0, -1, 0], 12, 2, src);
|
|
4001
|
+
solveStructure(o);
|
|
4002
|
+
recomputeGroups(o);
|
|
4003
|
+
detachEvents(o, opened, "break", src);
|
|
4004
|
+
collapseCheck(o, src);
|
|
4005
|
+
};
|
|
4006
|
+
const failPart = (o, p, origin, dir, fragEnergy, cap, src, force) => {
|
|
2373
4007
|
if (isLost(p)) return;
|
|
2374
4008
|
if (p.st.stripped) {
|
|
2375
4009
|
loseCage(o, p, src);
|
|
2376
4010
|
return;
|
|
2377
4011
|
}
|
|
4012
|
+
if (!force && p.bend && (p.st.flexure ?? 0) > 1 && sectionOf(p, p.bend.gravity).reinforced) {
|
|
4013
|
+
hingeDrop(o, p, src);
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
2378
4016
|
const ex = p.spec.explodes;
|
|
2379
|
-
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
4017
|
+
const style = force ?? (p.snap ? "snap" : p.mat.fracture === "none" ? "snap" : p.mat.fracture);
|
|
2380
4018
|
const { c, q } = partXf(o, p);
|
|
2381
4019
|
const hitW = origin ?? c;
|
|
2382
4020
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
@@ -2433,8 +4071,8 @@ var GameDestruction = (() => {
|
|
|
2433
4071
|
return;
|
|
2434
4072
|
}
|
|
2435
4073
|
if (ex) {
|
|
2436
|
-
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
|
|
2437
|
-
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
4074
|
+
emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
|
|
4075
|
+
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
|
|
2438
4076
|
}
|
|
2439
4077
|
partLost(o, p, src);
|
|
2440
4078
|
};
|
|
@@ -2463,8 +4101,33 @@ var GameDestruction = (() => {
|
|
|
2463
4101
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2464
4102
|
return done;
|
|
2465
4103
|
};
|
|
2466
|
-
const
|
|
4104
|
+
const leaksFlammable = (p) => {
|
|
4105
|
+
const c = p.spec.contains;
|
|
4106
|
+
return !!c && FLAMMABLE_FLUIDS.includes(c.fluid) && (p.st.liters ?? 0) > 0 && p.st.holes.some((h) => h.through);
|
|
4107
|
+
};
|
|
4108
|
+
const rollIgnition = (o, p, round) => {
|
|
4109
|
+
const c = p.spec.contains;
|
|
4110
|
+
if (!c || !FLAMMABLE_FLUIDS.includes(c.fluid) || p.rt.jet || p.rt.lit) return;
|
|
4111
|
+
const fill = c.liters > 0 ? (p.st.liters ?? 0) / c.liters : 0;
|
|
4112
|
+
const chance = ignitionChance(round, { sparkTarget: sparkTargetFactor(p.mat.id, !!p.mat.tags?.includes("metal"), ignitionRules), fill, fluid: c.fluid }, ignitionRules);
|
|
4113
|
+
if (chance > 0 && rnd() < chance) {
|
|
4114
|
+
p.rt.lit = true;
|
|
4115
|
+
o.active = true;
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
const lightLeaksNear = (centre, radius) => {
|
|
4119
|
+
for (const o of objects.values()) {
|
|
4120
|
+
for (const q of o.parts) {
|
|
4121
|
+
if (isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
|
|
4122
|
+
if (vec.distance(partXf(o, q).c, centre) - q.bound > radius) continue;
|
|
4123
|
+
q.rt.lit = true;
|
|
4124
|
+
o.active = true;
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
2467
4129
|
const mat = p.mat;
|
|
4130
|
+
const piercing = round.piercing ?? 1;
|
|
2468
4131
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2469
4132
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2470
4133
|
}
|
|
@@ -2477,7 +4140,8 @@ var GameDestruction = (() => {
|
|
|
2477
4140
|
const coats = p.layers.length > 0 ? layersEnergy(p.layers, p.rt.spalls, face, objLocal(o, entry)) + layersEnergy(p.layers, p.rt.spalls, -face, exitLocal) : 0;
|
|
2478
4141
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2479
4142
|
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2480
|
-
const
|
|
4143
|
+
const penMm = penetrationOf(mat, round.penetration);
|
|
4144
|
+
const Ep = penMm !== void 0 ? penMm * mat.penetration : piercing === 1 || !hard ? E : E * piercing;
|
|
2481
4145
|
const penetrated = Ep > thr;
|
|
2482
4146
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2483
4147
|
const id = p.spec.id;
|
|
@@ -2498,6 +4162,7 @@ var GameDestruction = (() => {
|
|
|
2498
4162
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2499
4163
|
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2500
4164
|
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
4165
|
+
if (mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_DISHING) addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(E * 2e-5, 1e-3, 0.03), src);
|
|
2501
4166
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2502
4167
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2503
4168
|
emit({
|
|
@@ -2506,10 +4171,11 @@ var GameDestruction = (() => {
|
|
|
2506
4171
|
part: id,
|
|
2507
4172
|
fluid: p.spec.contains.fluid,
|
|
2508
4173
|
point: entry,
|
|
2509
|
-
direction: n,
|
|
4174
|
+
direction: p.spec.contains.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, p.spec.contains.outflow))) : n,
|
|
2510
4175
|
rate: holeRate(hole, !!p.spec.contains.pressurized),
|
|
2511
4176
|
pressurized: !!p.spec.contains.pressurized
|
|
2512
4177
|
});
|
|
4178
|
+
rollIgnition(o, p, round);
|
|
2513
4179
|
}
|
|
2514
4180
|
} else if (mat.ductility >= 0.5) {
|
|
2515
4181
|
addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
|
|
@@ -2520,7 +4186,7 @@ var GameDestruction = (() => {
|
|
|
2520
4186
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2521
4187
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2522
4188
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2523
|
-
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
4189
|
+
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2524
4190
|
};
|
|
2525
4191
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2526
4192
|
const mat = p.mat;
|
|
@@ -2540,8 +4206,9 @@ var GameDestruction = (() => {
|
|
|
2540
4206
|
}
|
|
2541
4207
|
debris(o, p, mat.hitDebris, point, n, clamp2(Math.round(E / 25) + 1, 1, 10), clamp2(Math.sqrt(E) / 8, 1, 6), src);
|
|
2542
4208
|
}
|
|
2543
|
-
if (kind === "blunt")
|
|
2544
|
-
|
|
4209
|
+
if (kind === "blunt") {
|
|
4210
|
+
if (!(p.anchored && p.spec.tags?.includes("terrain"))) wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
4211
|
+
} else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2545
4212
|
if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
|
|
2546
4213
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2547
4214
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
@@ -2588,7 +4255,8 @@ var GameDestruction = (() => {
|
|
|
2588
4255
|
if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
|
|
2589
4256
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2590
4257
|
res.damage += dealt;
|
|
2591
|
-
if (
|
|
4258
|
+
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
4259
|
+
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2592
4260
|
return reach;
|
|
2593
4261
|
};
|
|
2594
4262
|
const blastObject = (o, point, energy, radius, src) => {
|
|
@@ -2651,7 +4319,7 @@ var GameDestruction = (() => {
|
|
|
2651
4319
|
};
|
|
2652
4320
|
const FLAG_LOST = 1;
|
|
2653
4321
|
const FLAG_OPEN = 2;
|
|
2654
|
-
const blastPlain = (point, energy, radius, src) => {
|
|
4322
|
+
const blastPlain = (point, energy, radius, src, lethal) => {
|
|
2655
4323
|
const out = [];
|
|
2656
4324
|
for (const o of [...objects.values()]) {
|
|
2657
4325
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2659,7 +4327,7 @@ var GameDestruction = (() => {
|
|
|
2659
4327
|
if (r.part) out.push(r);
|
|
2660
4328
|
}
|
|
2661
4329
|
for (const im of [...impls.values()]) {
|
|
2662
|
-
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
4330
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...src !== void 0 ? { source: src } : {} }));
|
|
2663
4331
|
}
|
|
2664
4332
|
return out;
|
|
2665
4333
|
};
|
|
@@ -2668,7 +4336,7 @@ var GameDestruction = (() => {
|
|
|
2668
4336
|
return { ps, c: toWorld(im.position, im.rotation, ov?.pos ?? ps.position), q: quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0])) };
|
|
2669
4337
|
});
|
|
2670
4338
|
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2671
|
-
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
4339
|
+
const blastScene = (point, energy, radius, src, cfg, only, lethal) => {
|
|
2672
4340
|
const mult = confinementOf(cfg.conf, point, radius);
|
|
2673
4341
|
const E = energy * mult;
|
|
2674
4342
|
const shelter = cfg.shelter;
|
|
@@ -2775,7 +4443,7 @@ var GameDestruction = (() => {
|
|
|
2775
4443
|
den += f;
|
|
2776
4444
|
}
|
|
2777
4445
|
const k = den > 0 ? num / den : 1;
|
|
2778
|
-
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
4446
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
2779
4447
|
results.set(im, res);
|
|
2780
4448
|
report(im).shelter = k;
|
|
2781
4449
|
}
|
|
@@ -2873,10 +4541,11 @@ var GameDestruction = (() => {
|
|
|
2873
4541
|
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
2874
4542
|
rep.fragmentHits++;
|
|
2875
4543
|
};
|
|
2876
|
-
const blastWorld = (point, energy, radius, src, call, only) => {
|
|
4544
|
+
const blastWorld = (point, energy, radius, src, call, only, lethal) => {
|
|
2877
4545
|
const cfg = blastCfg(call);
|
|
2878
|
-
|
|
2879
|
-
|
|
4546
|
+
const res = !cfg.shelter && !cfg.frag && cfg.conf === null ? blastPlain(point, energy, radius, src, lethal) : blastScene(point, energy, radius, src, cfg, only, lethal);
|
|
4547
|
+
lightLeaksNear(point, radius);
|
|
4548
|
+
return res;
|
|
2880
4549
|
};
|
|
2881
4550
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2882
4551
|
const start = cur.length;
|
|
@@ -2895,6 +4564,7 @@ var GameDestruction = (() => {
|
|
|
2895
4564
|
p.st.temperature += (T - p.st.temperature) * (1 - Math.exp(-k * duration));
|
|
2896
4565
|
}
|
|
2897
4566
|
o.active = true;
|
|
4567
|
+
if (T >= FLAME_LIGHTS_LEAK_C && !p.rt.jet && !p.rt.lit && leaksFlammable(p)) p.rt.lit = true;
|
|
2898
4568
|
checkIgnite(o, p, src);
|
|
2899
4569
|
}
|
|
2900
4570
|
res.events = cur.slice(start);
|
|
@@ -2914,7 +4584,7 @@ var GameDestruction = (() => {
|
|
|
2914
4584
|
if (hit.type === "ballistic") {
|
|
2915
4585
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2916
4586
|
if (loc) {
|
|
2917
|
-
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit
|
|
4587
|
+
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit);
|
|
2918
4588
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2919
4589
|
}
|
|
2920
4590
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -3068,7 +4738,8 @@ var GameDestruction = (() => {
|
|
|
3068
4738
|
if (rate > 0) {
|
|
3069
4739
|
s.liters = Math.max(0, (s.liters ?? 0) - rate * dt);
|
|
3070
4740
|
hot = true;
|
|
3071
|
-
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && s.temperature >= 350 && !p.rt.jet) {
|
|
4741
|
+
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && (s.temperature >= 350 || p.rt.lit) && !p.rt.jet) {
|
|
4742
|
+
p.rt.lit = false;
|
|
3072
4743
|
if (ex && chainDelayOf(p.spec, "fire") !== void 0) {
|
|
3073
4744
|
prime(o, p, "fire");
|
|
3074
4745
|
continue;
|
|
@@ -3083,6 +4754,7 @@ var GameDestruction = (() => {
|
|
|
3083
4754
|
}
|
|
3084
4755
|
}
|
|
3085
4756
|
}
|
|
4757
|
+
if ((s.liters ?? 0) <= 0) p.rt.lit = false;
|
|
3086
4758
|
if (p.rt.jet && (s.liters ?? 0) <= 0) {
|
|
3087
4759
|
p.rt.jet = false;
|
|
3088
4760
|
s.burning = false;
|
|
@@ -3132,6 +4804,25 @@ var GameDestruction = (() => {
|
|
|
3132
4804
|
}
|
|
3133
4805
|
o.active = hot;
|
|
3134
4806
|
};
|
|
4807
|
+
const igniteLeaksNearFire = (sources, dt) => {
|
|
4808
|
+
const { radius, chancePerSecond } = ignitionRules.fireNear;
|
|
4809
|
+
if (chancePerSecond <= 0) return;
|
|
4810
|
+
const chance = 1 - Math.exp(-chancePerSecond * dt);
|
|
4811
|
+
for (const src of sources) {
|
|
4812
|
+
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
4813
|
+
const sc = partXf(src.o, src.p).c;
|
|
4814
|
+
for (const o of objects.values()) {
|
|
4815
|
+
for (const q of o.parts) {
|
|
4816
|
+
if (q === src.p || isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
|
|
4817
|
+
if (vec.distance(partXf(o, q).c, sc) - q.bound - src.p.bound > radius) continue;
|
|
4818
|
+
if (rnd() < chance) {
|
|
4819
|
+
q.rt.lit = true;
|
|
4820
|
+
o.active = true;
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
};
|
|
3135
4826
|
const spread2 = (sources, dt) => {
|
|
3136
4827
|
for (const src of sources) {
|
|
3137
4828
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
@@ -3257,8 +4948,8 @@ var GameDestruction = (() => {
|
|
|
3257
4948
|
return;
|
|
3258
4949
|
}
|
|
3259
4950
|
const o = getObj(id);
|
|
3260
|
-
o.
|
|
3261
|
-
o
|
|
4951
|
+
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
4952
|
+
applySoilPose(o);
|
|
3262
4953
|
},
|
|
3263
4954
|
splitGroup(id, group, newId) {
|
|
3264
4955
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -3281,13 +4972,17 @@ var GameDestruction = (() => {
|
|
|
3281
4972
|
const s = { parts: o.spec.parts.filter((p) => keep(p.id)), joints: (o.spec.joints ?? []).filter((j) => keep(j.a) && keep(j.b)) };
|
|
3282
4973
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
3283
4974
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
4975
|
+
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
3284
4976
|
return clone(s);
|
|
3285
4977
|
};
|
|
3286
4978
|
const inNew = (pid) => moved.has(pid);
|
|
3287
4979
|
const inOld = (pid) => !moved.has(pid);
|
|
3288
4980
|
const pos = o.st.position, rot = o.st.rotation;
|
|
3289
4981
|
const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
|
|
3290
|
-
const
|
|
4982
|
+
const restCarry = carryFor(inOld, (a, b) => !(inNew(a) && inNew(b)));
|
|
4983
|
+
if (o.st.soil) restCarry.soil = o.st.soil;
|
|
4984
|
+
if (o.soil?.sets.size) restCarry.soilSets = Object.fromEntries(o.soil.sets);
|
|
4985
|
+
const rest = build(sub(inOld), id, o.soil ? o.base.pos : pos, o.soil ? o.base.rot : rot, restCarry);
|
|
3291
4986
|
objects.set(id, rest);
|
|
3292
4987
|
objects.set(nid, fresh);
|
|
3293
4988
|
return nid;
|
|
@@ -3312,7 +5007,8 @@ var GameDestruction = (() => {
|
|
|
3312
5007
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3313
5008
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3314
5009
|
},
|
|
3315
|
-
shoot(origin, direction, energy, caliber, source,
|
|
5010
|
+
shoot(origin, direction, energy, caliber, source, round) {
|
|
5011
|
+
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
3316
5012
|
return withCollapses(run(() => {
|
|
3317
5013
|
const results = [];
|
|
3318
5014
|
const d = vec.normalize(direction);
|
|
@@ -3324,7 +5020,7 @@ var GameDestruction = (() => {
|
|
|
3324
5020
|
if (!rh) break;
|
|
3325
5021
|
const im = impls.get(rh.object);
|
|
3326
5022
|
if (im) {
|
|
3327
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...
|
|
5023
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
|
|
3328
5024
|
r.part = rh.part;
|
|
3329
5025
|
r.point = rh.point;
|
|
3330
5026
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3338,7 +5034,7 @@ var GameDestruction = (() => {
|
|
|
3338
5034
|
const o = getObj(rh.object);
|
|
3339
5035
|
const p = getPart(o, rh.part);
|
|
3340
5036
|
const start = cur.length;
|
|
3341
|
-
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source,
|
|
5037
|
+
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, extras);
|
|
3342
5038
|
results.push({ object: o.id, part: p.spec.id, point: rh.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy, events: cur.slice(start) });
|
|
3343
5039
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3344
5040
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3437,6 +5133,52 @@ var GameDestruction = (() => {
|
|
|
3437
5133
|
o.active = true;
|
|
3438
5134
|
});
|
|
3439
5135
|
},
|
|
5136
|
+
setWind(next) {
|
|
5137
|
+
windOpts = { ...windOpts, ...Object.fromEntries(Object.entries(next).filter(([, v]) => v !== void 0)) };
|
|
5138
|
+
windCfg = resolveWind(windOpts);
|
|
5139
|
+
for (const o of objects.values()) {
|
|
5140
|
+
if (!o.hasStructure || o.spec.wind === false) continue;
|
|
5141
|
+
o.wind ?? (o.wind = newWind(o));
|
|
5142
|
+
o.dirty = true;
|
|
5143
|
+
o.active = true;
|
|
5144
|
+
}
|
|
5145
|
+
},
|
|
5146
|
+
vent(objectId, partId, opening = {}) {
|
|
5147
|
+
if (impls.has(objectId)) return false;
|
|
5148
|
+
const o = getObj(objectId);
|
|
5149
|
+
const p = getPart(o, partId);
|
|
5150
|
+
const c = p.spec.contains;
|
|
5151
|
+
if (!c || isLost(p) || (p.st.liters ?? 0) <= 0) return false;
|
|
5152
|
+
if (p.st.holes.some((h) => h.vent)) return true;
|
|
5153
|
+
return run(() => {
|
|
5154
|
+
const wp = opening.point ?? partXf(o, p).c;
|
|
5155
|
+
const wn = vec.normalize(opening.normal ?? [0, 1, 0]);
|
|
5156
|
+
const hole = { point: objLocal(o, wp), normal: objLocalDir(o, wn), diameter: opening.diameter ?? VENT_DIAMETER, through: true, vent: true };
|
|
5157
|
+
p.st.holes.push(hole);
|
|
5158
|
+
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
5159
|
+
o.active = true;
|
|
5160
|
+
emit({
|
|
5161
|
+
...base(o),
|
|
5162
|
+
type: "leak",
|
|
5163
|
+
part: partId,
|
|
5164
|
+
fluid: c.fluid,
|
|
5165
|
+
point: wp,
|
|
5166
|
+
direction: c.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, c.outflow))) : wn,
|
|
5167
|
+
rate: holeRate(hole, !!c.pressurized),
|
|
5168
|
+
pressurized: !!c.pressurized
|
|
5169
|
+
});
|
|
5170
|
+
return true;
|
|
5171
|
+
}).value;
|
|
5172
|
+
},
|
|
5173
|
+
seal(objectId, partId) {
|
|
5174
|
+
if (impls.has(objectId)) return 0;
|
|
5175
|
+
const o = getObj(objectId);
|
|
5176
|
+
const p = getPart(o, partId);
|
|
5177
|
+
const before = p.st.holes.length;
|
|
5178
|
+
p.st.holes = p.st.holes.filter((h) => !h.vent);
|
|
5179
|
+
o.active = true;
|
|
5180
|
+
return before - p.st.holes.length;
|
|
5181
|
+
},
|
|
3440
5182
|
setLiters(objectId, partId, liters) {
|
|
3441
5183
|
if (impls.has(objectId)) return;
|
|
3442
5184
|
const p = getPart(getObj(objectId), partId);
|
|
@@ -3450,7 +5192,12 @@ var GameDestruction = (() => {
|
|
|
3450
5192
|
time += dt;
|
|
3451
5193
|
const sources = [];
|
|
3452
5194
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
3453
|
-
if (
|
|
5195
|
+
for (const o of [...objects.values()]) if (o.soil && objects.get(o.id) === o) stepSoil(o, dt);
|
|
5196
|
+
for (const o of [...objects.values()]) if (o.wind && objects.get(o.id) === o) stepWind(o, dt);
|
|
5197
|
+
if (sources.length) {
|
|
5198
|
+
spread2(sources, dt);
|
|
5199
|
+
igniteLeaksNearFire(sources, dt);
|
|
5200
|
+
}
|
|
3454
5201
|
detonatePrimed(time);
|
|
3455
5202
|
releaseFront();
|
|
3456
5203
|
for (const im of [...impls.values()]) {
|
|
@@ -3472,7 +5219,7 @@ var GameDestruction = (() => {
|
|
|
3472
5219
|
return;
|
|
3473
5220
|
}
|
|
3474
5221
|
const o = getObj(id);
|
|
3475
|
-
objects.set(id, build(o.spec, id, o.
|
|
5222
|
+
objects.set(id, build(o.spec, id, o.base.pos, o.base.rot));
|
|
3476
5223
|
},
|
|
3477
5224
|
on(type, fn) {
|
|
3478
5225
|
let set = listeners.get(type);
|
|
@@ -3489,7 +5236,7 @@ var GameDestruction = (() => {
|
|
|
3489
5236
|
rng: rng.state(),
|
|
3490
5237
|
counter,
|
|
3491
5238
|
objects: [...objects.values()].map((o) => {
|
|
3492
|
-
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
5239
|
+
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed], ...o.soil?.sets.size ? { soil: Object.fromEntries(o.soil.sets) } : {}, ...o.wind ? { wind: [o.wind.dyn.x, o.wind.dyn.v, o.wind.dyn.y, o.wind.dyn.w, o.wind.dyn.peak] } : {} };
|
|
3493
5240
|
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
3494
5241
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
3495
5242
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
@@ -3510,9 +5257,17 @@ var GameDestruction = (() => {
|
|
|
3510
5257
|
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
3511
5258
|
),
|
|
3512
5259
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
3513
|
-
collapsed: e.runtime?.collapsed
|
|
5260
|
+
collapsed: e.runtime?.collapsed,
|
|
5261
|
+
...e.runtime?.wind ? { wind: e.runtime.wind } : {}
|
|
3514
5262
|
};
|
|
3515
|
-
|
|
5263
|
+
let pos = state.position, rot = state.rotation;
|
|
5264
|
+
if (state.soil) {
|
|
5265
|
+
carry.soil = state.soil;
|
|
5266
|
+
if (e.runtime?.soil) carry.soilSets = e.runtime.soil;
|
|
5267
|
+
rot = quat.normalize(quat.mul(state.rotation, quat.conjugate(state.soil.offset.rotation)));
|
|
5268
|
+
pos = vec.sub(state.position, rotateVec(rot, state.soil.offset.position));
|
|
5269
|
+
}
|
|
5270
|
+
objects.set(e.id, build(clone(e.spec), e.id, pos, rot, carry));
|
|
3516
5271
|
}
|
|
3517
5272
|
for (const e of snap.impls ?? []) {
|
|
3518
5273
|
const im = impls.get(e.id);
|
|
@@ -3526,21 +5281,6 @@ var GameDestruction = (() => {
|
|
|
3526
5281
|
return world;
|
|
3527
5282
|
};
|
|
3528
5283
|
|
|
3529
|
-
// src/piercing.ts
|
|
3530
|
-
var PIERCING_BY_KIND = {
|
|
3531
|
-
fmj: 1,
|
|
3532
|
-
hp: 0.6,
|
|
3533
|
-
ap: 1.6,
|
|
3534
|
-
buck: 0.5,
|
|
3535
|
-
slug: 0.25,
|
|
3536
|
-
birdshot: 0.3,
|
|
3537
|
-
rimfire: 1,
|
|
3538
|
-
arrow: 1,
|
|
3539
|
-
bolt: 1
|
|
3540
|
-
};
|
|
3541
|
-
var PIERCING_BY_CALIBER = { "5_56x45": 0.5, "5_45x39": 0.5, "22lr": 0.5 };
|
|
3542
|
-
var piercingOf = (caliber, kind) => PIERCING_BY_CALIBER[caliber] ?? PIERCING_BY_KIND[kind ?? "fmj"] ?? 1;
|
|
3543
|
-
|
|
3544
5284
|
// src/data/assemblies.json
|
|
3545
5285
|
var assemblies_default = [
|
|
3546
5286
|
{
|
|
@@ -3653,21 +5393,35 @@ var GameDestruction = (() => {
|
|
|
3653
5393
|
const dw = o.doorWidth ?? 1, dh = o.doorHeight ?? 2.1;
|
|
3654
5394
|
const mat = o.material ?? "concrete", wmat = o.wallMaterial ?? "brick";
|
|
3655
5395
|
const roof = o.roof ?? true;
|
|
5396
|
+
const rebarOf = (kind) => o.rebar === false ? {} : { rebar: { ratio: o.rebar?.[kind] ?? (kind === "beam" ? 0.01 : 5e-3) } };
|
|
3656
5397
|
const xs = Array.from({ length: nx }, (_, i) => -spanX / 2 + i * spanX / (nx - 1));
|
|
3657
5398
|
const zs = Array.from({ length: nz }, (_, j) => -spanZ / 2 + j * spanZ / (nz - 1));
|
|
3658
5399
|
const parts = [];
|
|
3659
5400
|
const joints = [];
|
|
3660
5401
|
const on = (a, b, kind) => joints.push({ id: `${a}~${b}`, a, b, kind, bears: "a_on_b" });
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
5402
|
+
const pad = o.footing ?? cs * 2.2;
|
|
5403
|
+
const pile = o.piles ? { pile: { ...o.piles } } : {};
|
|
5404
|
+
if (o.raft) {
|
|
5405
|
+
parts.push({
|
|
5406
|
+
id: "raft",
|
|
5407
|
+
material: mat,
|
|
5408
|
+
shape: { kind: "box", size: [spanX + pad, 0.4, spanZ + pad] },
|
|
5409
|
+
position: [0, -0.2, 0],
|
|
5410
|
+
structure: { role: "foundation", ...pile },
|
|
5411
|
+
anchored: true
|
|
5412
|
+
});
|
|
5413
|
+
} else {
|
|
5414
|
+
for (let i = 0; i < nx; i++) {
|
|
5415
|
+
for (let j = 0; j < nz; j++) {
|
|
5416
|
+
parts.push({
|
|
5417
|
+
id: `foundation_${i}_${j}`,
|
|
5418
|
+
material: mat,
|
|
5419
|
+
shape: { kind: "box", size: [pad, 0.3, pad] },
|
|
5420
|
+
position: [xs[i], -0.15, zs[j]],
|
|
5421
|
+
structure: { role: "foundation", ...pile },
|
|
5422
|
+
anchored: true
|
|
5423
|
+
});
|
|
5424
|
+
}
|
|
3671
5425
|
}
|
|
3672
5426
|
}
|
|
3673
5427
|
for (let f = 0; f < floors; f++) {
|
|
@@ -3683,14 +5437,15 @@ var GameDestruction = (() => {
|
|
|
3683
5437
|
material: mat,
|
|
3684
5438
|
shape: { kind: "box", size: [cs, H, cs] },
|
|
3685
5439
|
position: [xs[i], yb + H / 2, zs[j]],
|
|
3686
|
-
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity }
|
|
5440
|
+
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity },
|
|
5441
|
+
...o.columnCage ? { reinforcement: { ...o.columnCage } } : {}
|
|
3687
5442
|
});
|
|
3688
|
-
on(id, f === 0 ? `foundation_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
5443
|
+
on(id, f === 0 ? o.raft ? "raft" : `foundation_${i}_${j}` : o.continuousColumns ? `column_${f - 1}_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
3689
5444
|
}
|
|
3690
5445
|
}
|
|
3691
5446
|
const beams = [];
|
|
3692
5447
|
const beam = (id, size, pos, c1, c2) => {
|
|
3693
|
-
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam" } });
|
|
5448
|
+
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam", ...rebarOf("beam") } });
|
|
3694
5449
|
on(id, c1, "rebar");
|
|
3695
5450
|
on(id, c2, "rebar");
|
|
3696
5451
|
beams.push(id);
|
|
@@ -3705,13 +5460,13 @@ var GameDestruction = (() => {
|
|
|
3705
5460
|
beam(`beam_z_${f}_${i}_${j}`, [bw, bh, zs[j + 1] - zs[j] - cs], [xs[i], top - bh / 2, (zs[j] + zs[j + 1]) / 2], `column_${f}_${i}_${j}`, `column_${f}_${i}_${j + 1}`);
|
|
3706
5461
|
}
|
|
3707
5462
|
}
|
|
3708
|
-
const thick = last && roof ? T * 0.75 : T;
|
|
5463
|
+
const thick = last && roof ? o.roofThickness ?? T * 0.75 : T;
|
|
3709
5464
|
parts.push({
|
|
3710
5465
|
id: slabId,
|
|
3711
5466
|
material: mat,
|
|
3712
5467
|
shape: { kind: "box", size: [spanX + cs + 0.2, thick, spanZ + cs + 0.2] },
|
|
3713
5468
|
position: [0, top + thick / 2, 0],
|
|
3714
|
-
structure: { role: last && roof ? "roof" : "slab" }
|
|
5469
|
+
structure: { role: last && roof ? "roof" : "slab", ...rebarOf("slab") }
|
|
3715
5470
|
});
|
|
3716
5471
|
for (const b of beams) on(slabId, b, last && roof ? "resting" : "rebar");
|
|
3717
5472
|
if (o.walls) {
|
|
@@ -3744,28 +5499,91 @@ var GameDestruction = (() => {
|
|
|
3744
5499
|
}
|
|
3745
5500
|
}
|
|
3746
5501
|
}
|
|
3747
|
-
return { name: "frame", parts, joints, tags: ["structure"] };
|
|
5502
|
+
return { name: "frame", parts, joints, tags: ["structure"], ...o.soil !== void 0 ? { soil: o.soil } : {} };
|
|
3748
5503
|
};
|
|
3749
5504
|
|
|
3750
5505
|
// src/size.ts
|
|
5506
|
+
var MAX_REBAR_RATIO = 0.04;
|
|
5507
|
+
var onRock = (spec) => {
|
|
5508
|
+
if (spec.soil === void 0) return spec;
|
|
5509
|
+
const { soil: _soil, ...rest } = spec;
|
|
5510
|
+
return rest;
|
|
5511
|
+
};
|
|
5512
|
+
var without = (spec, id) => {
|
|
5513
|
+
const reduced = { ...spec, parts: spec.parts.filter((p) => p.id !== id), joints: (spec.joints ?? []).filter((j) => j.a !== id && j.b !== id) };
|
|
5514
|
+
if (!spec.joints) delete reduced.joints;
|
|
5515
|
+
return reduced;
|
|
5516
|
+
};
|
|
5517
|
+
var sizeRebar = (spec, options = {}) => {
|
|
5518
|
+
const flexure = resolveFlexure(options.flexure);
|
|
5519
|
+
const materials = new Map(MATERIALS.map((m) => [m.id, m]));
|
|
5520
|
+
for (const m of options.materials ?? []) materials.set(m.id, m);
|
|
5521
|
+
const out = structuredClone(spec);
|
|
5522
|
+
const targets = out.parts.filter((p) => {
|
|
5523
|
+
const m = materials.get(p.material);
|
|
5524
|
+
return p.shape.kind === "box" && !!m && isReinforced(p) && isFlexural(p, m, flexure);
|
|
5525
|
+
});
|
|
5526
|
+
if (targets.length === 0) return out;
|
|
5527
|
+
const safety = options.safety ?? 0.65;
|
|
5528
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
5529
|
+
const bending = (s) => {
|
|
5530
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, structure: { flexure: options.flexure } });
|
|
5531
|
+
const id = w.add(onRock(s));
|
|
5532
|
+
return new Map(w.state(id).parts.map((p) => [p.id, p.flexure ?? 0]));
|
|
5533
|
+
};
|
|
5534
|
+
const base = bending(out);
|
|
5535
|
+
const factor = new Map(targets.map((p) => [p.id, (base.get(p.id) ?? 0) / safetyOf(p.structure.role)]));
|
|
5536
|
+
if (options.redundancy !== void 0) {
|
|
5537
|
+
const roles = options.redundancyRoles ?? ["column"];
|
|
5538
|
+
for (const lost of out.parts) {
|
|
5539
|
+
if (!lost.structure || !roles.includes(lost.structure.role)) continue;
|
|
5540
|
+
const l = bending(without(out, lost.id));
|
|
5541
|
+
for (const p of targets) if (p.id !== lost.id) factor.set(p.id, Math.max(factor.get(p.id), (l.get(p.id) ?? 0) / options.redundancy));
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
for (const p of targets) {
|
|
5545
|
+
const f = factor.get(p.id);
|
|
5546
|
+
const g = bendGeometry(p);
|
|
5547
|
+
if (f <= 1 || g.gravity < 0) continue;
|
|
5548
|
+
const section = { b: sectionWidth(g, g.gravity), h: g.dims[g.gravity] };
|
|
5549
|
+
const steel = tensionSteel(p, g.dims, section, flexure);
|
|
5550
|
+
const m = materials.get(p.material);
|
|
5551
|
+
const need = f * sectionMoments(m, section, steel, flexure).ultimate;
|
|
5552
|
+
const momentOf = (area) => steelMoment(area, steel.yieldMPa, steel.d, section.b, m.strength.compressiveMPa, flexure);
|
|
5553
|
+
const cap = MAX_REBAR_RATIO * section.b * steel.d;
|
|
5554
|
+
if (steel.area >= cap) continue;
|
|
5555
|
+
let lo = steel.area, hi = cap;
|
|
5556
|
+
if (momentOf(hi) > need) {
|
|
5557
|
+
for (let k = 0; k < 60; k++) {
|
|
5558
|
+
const mid = (lo + hi) / 2;
|
|
5559
|
+
if (momentOf(mid) < need) lo = mid;
|
|
5560
|
+
else hi = mid;
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
const ratio = hi / (section.b * steel.d);
|
|
5564
|
+
p.structure = { ...p.structure, rebar: { ...p.structure.rebar, ratio } };
|
|
5565
|
+
}
|
|
5566
|
+
return out;
|
|
5567
|
+
};
|
|
3751
5568
|
var sizeStructure = (spec, options = {}) => {
|
|
3752
5569
|
const safety = options.safety ?? 0.65;
|
|
3753
5570
|
const redundancy = options.redundancy;
|
|
3754
5571
|
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3755
5572
|
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
3756
|
-
const loads = (s) => {
|
|
3757
|
-
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
3758
|
-
const id = w.add(s);
|
|
3759
|
-
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0 }]));
|
|
5573
|
+
const loads = (s, wind) => {
|
|
5574
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, ...wind ? { wind } : {} });
|
|
5575
|
+
const id = w.add(onRock(s));
|
|
5576
|
+
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0, wind: p.wind ?? 0 }]));
|
|
3760
5577
|
};
|
|
3761
5578
|
const out = structuredClone(spec);
|
|
3762
|
-
const base = loads(out);
|
|
5579
|
+
const base = loads(out, options.wind);
|
|
5580
|
+
const gamma = options.wind ? resolveWind(options.wind).loadFactor : 0;
|
|
3763
5581
|
const need = /* @__PURE__ */ new Map();
|
|
3764
5582
|
for (const p of out.parts) {
|
|
3765
5583
|
if (!p.structure || p.structure.role === "none") continue;
|
|
3766
5584
|
const b = base.get(p.id);
|
|
3767
5585
|
if (!b || b.stress <= 0) continue;
|
|
3768
|
-
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
5586
|
+
need.set(p.id, (b.load + (b.wind > 0 ? gamma * b.wind / GRAVITY : 0)) / safetyOf(p.structure.role));
|
|
3769
5587
|
}
|
|
3770
5588
|
if (redundancy !== void 0) {
|
|
3771
5589
|
for (const col of out.parts) {
|
|
@@ -3787,7 +5605,20 @@ var GameDestruction = (() => {
|
|
|
3787
5605
|
const n = need.get(p.id);
|
|
3788
5606
|
if (n !== void 0) p.structure = { ...p.structure, capacity: n };
|
|
3789
5607
|
}
|
|
3790
|
-
|
|
5608
|
+
if (options.wind) {
|
|
5609
|
+
const yieldMPa = resolveFlexure(options.flexure).rebarYieldMPa;
|
|
5610
|
+
for (const p of out.parts) {
|
|
5611
|
+
const b = base.get(p.id);
|
|
5612
|
+
if (!b || b.wind >= 0 || !p.reinforcement || p.shape.kind !== "box") continue;
|
|
5613
|
+
const pull = -(b.load + gamma * b.wind / GRAVITY) * GRAVITY;
|
|
5614
|
+
if (pull <= 0) continue;
|
|
5615
|
+
const dims = shapeDims(p.shape);
|
|
5616
|
+
const have = tensionCapacity(dims, { reinforcement: p.reinforcement, yieldMPa });
|
|
5617
|
+
const want = pull / safetyOf(p.structure?.role ?? "wall");
|
|
5618
|
+
if (have > 0 && want > have) p.reinforcement = { ...p.reinforcement, diameter: Math.min(0.04, p.reinforcement.diameter * Math.sqrt(want / have)) };
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
return sizeRebar(out, options);
|
|
3791
5622
|
};
|
|
3792
5623
|
|
|
3793
5624
|
// src/locale.ts
|
|
@@ -3869,6 +5700,8 @@ var GameDestruction = (() => {
|
|
|
3869
5700
|
primed: "about to explode",
|
|
3870
5701
|
stress: "overload",
|
|
3871
5702
|
collapse: "collapsed",
|
|
5703
|
+
settle: "ground gave way",
|
|
5704
|
+
sway: "sway",
|
|
3872
5705
|
spall: "plaster came off",
|
|
3873
5706
|
stage: "damage stage",
|
|
3874
5707
|
expose: "rebar bare",
|
|
@@ -3957,6 +5790,8 @@ var GameDestruction = (() => {
|
|
|
3957
5790
|
primed: "prestes a explodir",
|
|
3958
5791
|
stress: "sobrecarga",
|
|
3959
5792
|
collapse: "desabou",
|
|
5793
|
+
settle: "recalque do solo",
|
|
5794
|
+
sway: "balan\xE7o",
|
|
3960
5795
|
spall: "reboco soltou",
|
|
3961
5796
|
stage: "est\xE1gio de dano",
|
|
3962
5797
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
@@ -3971,115 +5806,6 @@ var GameDestruction = (() => {
|
|
|
3971
5806
|
};
|
|
3972
5807
|
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3973
5808
|
|
|
3974
|
-
// ../game-contracts/dist/index.js
|
|
3975
|
-
var IMPACT_CONTRACT = {
|
|
3976
|
-
ballistic: { required: ["type", "point", "direction", "energy", "caliber"], optional: ["space", "source"], note: "bullets and pellets: joules and diameter in millimetres" },
|
|
3977
|
-
blunt: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "contact radius in metres (default 3 cm)" },
|
|
3978
|
-
slash: { required: ["type", "point", "energy"], optional: ["direction", "radius", "space", "source"], note: "axes, knives, machetes" },
|
|
3979
|
-
explosive: { required: ["type", "point", "energy", "radius"], optional: ["space", "source"], note: "point is the centre of the blast, radius the range in metres" },
|
|
3980
|
-
fire: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "flame temperature in \xB0C; duration in seconds (call every frame with `dt`)" },
|
|
3981
|
-
heat: { required: ["type", "point", "temperature", "radius"], optional: ["duration", "space", "source"], note: "heat without flame (cutting torch, hot plate)" }
|
|
3982
|
-
};
|
|
3983
|
-
var checkHit = (hit) => {
|
|
3984
|
-
const c = IMPACT_CONTRACT[hit.type];
|
|
3985
|
-
if (!c) return [`unknown damage type "${String(hit.type)}"`];
|
|
3986
|
-
const out = [];
|
|
3987
|
-
for (const f of c.required) {
|
|
3988
|
-
const v = hit[f];
|
|
3989
|
-
if (v === void 0 || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === "number" && !Number.isFinite(v))) out.push(`${hit.type} hit needs "${f}"`);
|
|
3990
|
-
}
|
|
3991
|
-
return out;
|
|
3992
|
-
};
|
|
3993
|
-
var finiteJson = (v, path, out) => {
|
|
3994
|
-
if (typeof v === "number") {
|
|
3995
|
-
if (!Number.isFinite(v)) out.push(`${path} is not a finite number`);
|
|
3996
|
-
} else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out));
|
|
3997
|
-
else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out);
|
|
3998
|
-
else if (typeof v === "function" || typeof v === "symbol" || typeof v === "bigint") out.push(`${path} is not JSON`);
|
|
3999
|
-
};
|
|
4000
|
-
var checkDestructible = (impl, options = {}) => {
|
|
4001
|
-
const problems = [];
|
|
4002
|
-
const known = options.materials ? new Set(options.materials) : null;
|
|
4003
|
-
let spec;
|
|
4004
|
-
try {
|
|
4005
|
-
spec = impl.describe();
|
|
4006
|
-
} catch (e) {
|
|
4007
|
-
return { ok: false, problems: [`describe() threw: ${String(e)}`] };
|
|
4008
|
-
}
|
|
4009
|
-
if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ["describe() must return at least one part"] };
|
|
4010
|
-
const ids = /* @__PURE__ */ new Set();
|
|
4011
|
-
for (const p of spec.parts) {
|
|
4012
|
-
if (ids.has(p.id)) problems.push(`duplicate part id "${p.id}"`);
|
|
4013
|
-
ids.add(p.id);
|
|
4014
|
-
if (known && !known.has(p.material)) problems.push(`part "${p.id}" has unknown material "${p.material}"`);
|
|
4015
|
-
if (!p.shape || !["box", "cylinder", "sphere"].includes(p.shape.kind)) problems.push(`part "${p.id}" has no valid shape`);
|
|
4016
|
-
if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part "${p.id}" has no position`);
|
|
4017
|
-
}
|
|
4018
|
-
const jointIds = /* @__PURE__ */ new Set();
|
|
4019
|
-
for (const j of spec.joints ?? []) {
|
|
4020
|
-
if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint "${j.id ?? `${j.a}~${j.b}`}" references an unknown part`);
|
|
4021
|
-
const jid = j.id ?? `${j.a}~${j.b}`;
|
|
4022
|
-
if (jointIds.has(jid)) problems.push(`duplicate joint id "${jid}"`);
|
|
4023
|
-
jointIds.add(jid);
|
|
4024
|
-
}
|
|
4025
|
-
const checkState = (label) => {
|
|
4026
|
-
let st;
|
|
4027
|
-
try {
|
|
4028
|
-
st = impl.state();
|
|
4029
|
-
} catch (e) {
|
|
4030
|
-
problems.push(`state() threw ${label}: ${String(e)}`);
|
|
4031
|
-
return;
|
|
4032
|
-
}
|
|
4033
|
-
const sids = st.parts.map((p) => p.id).sort().join(",");
|
|
4034
|
-
if (sids !== [...ids].sort().join(",")) problems.push(`state() part ids do not match describe() ${label}`);
|
|
4035
|
-
for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part "${p.id}" has negative or invalid hp ${label}`);
|
|
4036
|
-
};
|
|
4037
|
-
const checkEvents = (events, label) => {
|
|
4038
|
-
if (!Array.isArray(events)) {
|
|
4039
|
-
problems.push(`${label} must return an array`);
|
|
4040
|
-
return;
|
|
4041
|
-
}
|
|
4042
|
-
const evs = events;
|
|
4043
|
-
evs.forEach((e, i) => {
|
|
4044
|
-
if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object "${String(e.object)}", expected "${impl.id}"`);
|
|
4045
|
-
const part = e.part;
|
|
4046
|
-
if (part !== void 0 && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part "${part}"`);
|
|
4047
|
-
});
|
|
4048
|
-
const bad = [];
|
|
4049
|
-
finiteJson(evs, "events", bad);
|
|
4050
|
-
problems.push(...bad.map((b) => `${label}: ${b}`));
|
|
4051
|
-
try {
|
|
4052
|
-
const s = JSON.stringify(evs);
|
|
4053
|
-
if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`);
|
|
4054
|
-
} catch (e) {
|
|
4055
|
-
problems.push(`${label} events are not JSON-serializable: ${String(e)}`);
|
|
4056
|
-
}
|
|
4057
|
-
};
|
|
4058
|
-
checkState("before the hit");
|
|
4059
|
-
const target = spec.parts[0];
|
|
4060
|
-
try {
|
|
4061
|
-
const hit = { type: "ballistic", point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 };
|
|
4062
|
-
checkEvents(impl.hit(hit), "hit()");
|
|
4063
|
-
} catch (e) {
|
|
4064
|
-
problems.push(`hit() threw: ${String(e)}`);
|
|
4065
|
-
}
|
|
4066
|
-
checkState("after the hit");
|
|
4067
|
-
if (impl.update) {
|
|
4068
|
-
try {
|
|
4069
|
-
checkEvents(impl.update(0.016), "update()");
|
|
4070
|
-
} catch (e) {
|
|
4071
|
-
problems.push(`update() threw: ${String(e)}`);
|
|
4072
|
-
}
|
|
4073
|
-
checkState("after update");
|
|
4074
|
-
}
|
|
4075
|
-
try {
|
|
4076
|
-
impl.reset?.();
|
|
4077
|
-
} catch (e) {
|
|
4078
|
-
problems.push(`reset() threw: ${String(e)}`);
|
|
4079
|
-
}
|
|
4080
|
-
return { ok: problems.length === 0, problems };
|
|
4081
|
-
};
|
|
4082
|
-
|
|
4083
5809
|
// src/contracts.ts
|
|
4084
5810
|
var checkDestructible2 = (impl, options = {}) => {
|
|
4085
5811
|
const contractOptions = { materials: [...MATERIALS.map((m) => m.id), ...options.materials ?? []] };
|