@adatechnology/game-destruction 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -13
- package/dist/index.d.ts +805 -25
- package/dist/index.global.js +1934 -207
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1783 -98
- 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;
|
|
@@ -2494,22 +4158,25 @@ var GameDestruction = (() => {
|
|
|
2494
4158
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2495
4159
|
let exitPoint;
|
|
2496
4160
|
if (penetrated) {
|
|
2497
|
-
|
|
4161
|
+
const exitDist = p.spec.contains || p.spec.fill !== void 0 && p.spec.fill < 0.5 || p.thickness < pathLen * 0.5 ? pathLen : Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant);
|
|
4162
|
+
exitPoint = vec.madd(entry, d, exitDist);
|
|
2498
4163
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2499
4164
|
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2500
4165
|
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
4166
|
+
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
4167
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2502
|
-
if (p.spec.contains && (p.st.liters ?? 0) >
|
|
4168
|
+
if (p.spec.contains && (p.st.liters ?? 0) > 1e-4) {
|
|
2503
4169
|
emit({
|
|
2504
4170
|
...base(o, src),
|
|
2505
4171
|
type: "leak",
|
|
2506
4172
|
part: id,
|
|
2507
4173
|
fluid: p.spec.contains.fluid,
|
|
2508
4174
|
point: entry,
|
|
2509
|
-
direction: n,
|
|
4175
|
+
direction: p.spec.contains.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, p.spec.contains.outflow))) : n,
|
|
2510
4176
|
rate: holeRate(hole, !!p.spec.contains.pressurized),
|
|
2511
4177
|
pressurized: !!p.spec.contains.pressurized
|
|
2512
4178
|
});
|
|
4179
|
+
rollIgnition(o, p, round);
|
|
2513
4180
|
}
|
|
2514
4181
|
} else if (mat.ductility >= 0.5) {
|
|
2515
4182
|
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 +4187,7 @@ var GameDestruction = (() => {
|
|
|
2520
4187
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2521
4188
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2522
4189
|
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 };
|
|
4190
|
+
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2524
4191
|
};
|
|
2525
4192
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2526
4193
|
const mat = p.mat;
|
|
@@ -2540,8 +4207,9 @@ var GameDestruction = (() => {
|
|
|
2540
4207
|
}
|
|
2541
4208
|
debris(o, p, mat.hitDebris, point, n, clamp2(Math.round(E / 25) + 1, 1, 10), clamp2(Math.sqrt(E) / 8, 1, 6), src);
|
|
2542
4209
|
}
|
|
2543
|
-
if (kind === "blunt")
|
|
2544
|
-
|
|
4210
|
+
if (kind === "blunt") {
|
|
4211
|
+
if (!(p.anchored && p.spec.tags?.includes("terrain"))) wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
4212
|
+
} else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2545
4213
|
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
4214
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2547
4215
|
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 +4256,8 @@ var GameDestruction = (() => {
|
|
|
2588
4256
|
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
4257
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2590
4258
|
res.damage += dealt;
|
|
2591
|
-
if (
|
|
4259
|
+
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
4260
|
+
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2592
4261
|
return reach;
|
|
2593
4262
|
};
|
|
2594
4263
|
const blastObject = (o, point, energy, radius, src) => {
|
|
@@ -2651,7 +4320,7 @@ var GameDestruction = (() => {
|
|
|
2651
4320
|
};
|
|
2652
4321
|
const FLAG_LOST = 1;
|
|
2653
4322
|
const FLAG_OPEN = 2;
|
|
2654
|
-
const blastPlain = (point, energy, radius, src) => {
|
|
4323
|
+
const blastPlain = (point, energy, radius, src, lethal) => {
|
|
2655
4324
|
const out = [];
|
|
2656
4325
|
for (const o of [...objects.values()]) {
|
|
2657
4326
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2659,7 +4328,7 @@ var GameDestruction = (() => {
|
|
|
2659
4328
|
if (r.part) out.push(r);
|
|
2660
4329
|
}
|
|
2661
4330
|
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 } : {} }));
|
|
4331
|
+
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
4332
|
}
|
|
2664
4333
|
return out;
|
|
2665
4334
|
};
|
|
@@ -2668,7 +4337,7 @@ var GameDestruction = (() => {
|
|
|
2668
4337
|
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
4338
|
});
|
|
2670
4339
|
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) => {
|
|
4340
|
+
const blastScene = (point, energy, radius, src, cfg, only, lethal) => {
|
|
2672
4341
|
const mult = confinementOf(cfg.conf, point, radius);
|
|
2673
4342
|
const E = energy * mult;
|
|
2674
4343
|
const shelter = cfg.shelter;
|
|
@@ -2775,7 +4444,7 @@ var GameDestruction = (() => {
|
|
|
2775
4444
|
den += f;
|
|
2776
4445
|
}
|
|
2777
4446
|
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 } : {} });
|
|
4447
|
+
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
4448
|
results.set(im, res);
|
|
2780
4449
|
report(im).shelter = k;
|
|
2781
4450
|
}
|
|
@@ -2873,10 +4542,11 @@ var GameDestruction = (() => {
|
|
|
2873
4542
|
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
2874
4543
|
rep.fragmentHits++;
|
|
2875
4544
|
};
|
|
2876
|
-
const blastWorld = (point, energy, radius, src, call, only) => {
|
|
4545
|
+
const blastWorld = (point, energy, radius, src, call, only, lethal) => {
|
|
2877
4546
|
const cfg = blastCfg(call);
|
|
2878
|
-
|
|
2879
|
-
|
|
4547
|
+
const res = !cfg.shelter && !cfg.frag && cfg.conf === null ? blastPlain(point, energy, radius, src, lethal) : blastScene(point, energy, radius, src, cfg, only, lethal);
|
|
4548
|
+
lightLeaksNear(point, radius);
|
|
4549
|
+
return res;
|
|
2880
4550
|
};
|
|
2881
4551
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2882
4552
|
const start = cur.length;
|
|
@@ -2895,6 +4565,7 @@ var GameDestruction = (() => {
|
|
|
2895
4565
|
p.st.temperature += (T - p.st.temperature) * (1 - Math.exp(-k * duration));
|
|
2896
4566
|
}
|
|
2897
4567
|
o.active = true;
|
|
4568
|
+
if (T >= FLAME_LIGHTS_LEAK_C && !p.rt.jet && !p.rt.lit && leaksFlammable(p)) p.rt.lit = true;
|
|
2898
4569
|
checkIgnite(o, p, src);
|
|
2899
4570
|
}
|
|
2900
4571
|
res.events = cur.slice(start);
|
|
@@ -2914,7 +4585,7 @@ var GameDestruction = (() => {
|
|
|
2914
4585
|
if (hit.type === "ballistic") {
|
|
2915
4586
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2916
4587
|
if (loc) {
|
|
2917
|
-
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit
|
|
4588
|
+
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit);
|
|
2918
4589
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2919
4590
|
}
|
|
2920
4591
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -3068,7 +4739,8 @@ var GameDestruction = (() => {
|
|
|
3068
4739
|
if (rate > 0) {
|
|
3069
4740
|
s.liters = Math.max(0, (s.liters ?? 0) - rate * dt);
|
|
3070
4741
|
hot = true;
|
|
3071
|
-
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && s.temperature >= 350 && !p.rt.jet) {
|
|
4742
|
+
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && (s.temperature >= 350 || p.rt.lit) && !p.rt.jet) {
|
|
4743
|
+
p.rt.lit = false;
|
|
3072
4744
|
if (ex && chainDelayOf(p.spec, "fire") !== void 0) {
|
|
3073
4745
|
prime(o, p, "fire");
|
|
3074
4746
|
continue;
|
|
@@ -3083,6 +4755,7 @@ var GameDestruction = (() => {
|
|
|
3083
4755
|
}
|
|
3084
4756
|
}
|
|
3085
4757
|
}
|
|
4758
|
+
if ((s.liters ?? 0) <= 0) p.rt.lit = false;
|
|
3086
4759
|
if (p.rt.jet && (s.liters ?? 0) <= 0) {
|
|
3087
4760
|
p.rt.jet = false;
|
|
3088
4761
|
s.burning = false;
|
|
@@ -3132,6 +4805,25 @@ var GameDestruction = (() => {
|
|
|
3132
4805
|
}
|
|
3133
4806
|
o.active = hot;
|
|
3134
4807
|
};
|
|
4808
|
+
const igniteLeaksNearFire = (sources, dt) => {
|
|
4809
|
+
const { radius, chancePerSecond } = ignitionRules.fireNear;
|
|
4810
|
+
if (chancePerSecond <= 0) return;
|
|
4811
|
+
const chance = 1 - Math.exp(-chancePerSecond * dt);
|
|
4812
|
+
for (const src of sources) {
|
|
4813
|
+
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
4814
|
+
const sc = partXf(src.o, src.p).c;
|
|
4815
|
+
for (const o of objects.values()) {
|
|
4816
|
+
for (const q of o.parts) {
|
|
4817
|
+
if (q === src.p || isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
|
|
4818
|
+
if (vec.distance(partXf(o, q).c, sc) - q.bound - src.p.bound > radius) continue;
|
|
4819
|
+
if (rnd() < chance) {
|
|
4820
|
+
q.rt.lit = true;
|
|
4821
|
+
o.active = true;
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
};
|
|
3135
4827
|
const spread2 = (sources, dt) => {
|
|
3136
4828
|
for (const src of sources) {
|
|
3137
4829
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
@@ -3257,8 +4949,8 @@ var GameDestruction = (() => {
|
|
|
3257
4949
|
return;
|
|
3258
4950
|
}
|
|
3259
4951
|
const o = getObj(id);
|
|
3260
|
-
o.
|
|
3261
|
-
o
|
|
4952
|
+
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
4953
|
+
applySoilPose(o);
|
|
3262
4954
|
},
|
|
3263
4955
|
splitGroup(id, group, newId) {
|
|
3264
4956
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -3281,13 +4973,17 @@ var GameDestruction = (() => {
|
|
|
3281
4973
|
const s = { parts: o.spec.parts.filter((p) => keep(p.id)), joints: (o.spec.joints ?? []).filter((j) => keep(j.a) && keep(j.b)) };
|
|
3282
4974
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
3283
4975
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
4976
|
+
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
3284
4977
|
return clone(s);
|
|
3285
4978
|
};
|
|
3286
4979
|
const inNew = (pid) => moved.has(pid);
|
|
3287
4980
|
const inOld = (pid) => !moved.has(pid);
|
|
3288
4981
|
const pos = o.st.position, rot = o.st.rotation;
|
|
3289
4982
|
const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
|
|
3290
|
-
const
|
|
4983
|
+
const restCarry = carryFor(inOld, (a, b) => !(inNew(a) && inNew(b)));
|
|
4984
|
+
if (o.st.soil) restCarry.soil = o.st.soil;
|
|
4985
|
+
if (o.soil?.sets.size) restCarry.soilSets = Object.fromEntries(o.soil.sets);
|
|
4986
|
+
const rest = build(sub(inOld), id, o.soil ? o.base.pos : pos, o.soil ? o.base.rot : rot, restCarry);
|
|
3291
4987
|
objects.set(id, rest);
|
|
3292
4988
|
objects.set(nid, fresh);
|
|
3293
4989
|
return nid;
|
|
@@ -3312,7 +5008,8 @@ var GameDestruction = (() => {
|
|
|
3312
5008
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3313
5009
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3314
5010
|
},
|
|
3315
|
-
shoot(origin, direction, energy, caliber, source,
|
|
5011
|
+
shoot(origin, direction, energy, caliber, source, round) {
|
|
5012
|
+
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
3316
5013
|
return withCollapses(run(() => {
|
|
3317
5014
|
const results = [];
|
|
3318
5015
|
const d = vec.normalize(direction);
|
|
@@ -3324,7 +5021,7 @@ var GameDestruction = (() => {
|
|
|
3324
5021
|
if (!rh) break;
|
|
3325
5022
|
const im = impls.get(rh.object);
|
|
3326
5023
|
if (im) {
|
|
3327
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...
|
|
5024
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
|
|
3328
5025
|
r.part = rh.part;
|
|
3329
5026
|
r.point = rh.point;
|
|
3330
5027
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3338,7 +5035,7 @@ var GameDestruction = (() => {
|
|
|
3338
5035
|
const o = getObj(rh.object);
|
|
3339
5036
|
const p = getPart(o, rh.part);
|
|
3340
5037
|
const start = cur.length;
|
|
3341
|
-
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source,
|
|
5038
|
+
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, extras);
|
|
3342
5039
|
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
5040
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3344
5041
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3437,6 +5134,52 @@ var GameDestruction = (() => {
|
|
|
3437
5134
|
o.active = true;
|
|
3438
5135
|
});
|
|
3439
5136
|
},
|
|
5137
|
+
setWind(next) {
|
|
5138
|
+
windOpts = { ...windOpts, ...Object.fromEntries(Object.entries(next).filter(([, v]) => v !== void 0)) };
|
|
5139
|
+
windCfg = resolveWind(windOpts);
|
|
5140
|
+
for (const o of objects.values()) {
|
|
5141
|
+
if (!o.hasStructure || o.spec.wind === false) continue;
|
|
5142
|
+
o.wind ?? (o.wind = newWind(o));
|
|
5143
|
+
o.dirty = true;
|
|
5144
|
+
o.active = true;
|
|
5145
|
+
}
|
|
5146
|
+
},
|
|
5147
|
+
vent(objectId, partId, opening = {}) {
|
|
5148
|
+
if (impls.has(objectId)) return false;
|
|
5149
|
+
const o = getObj(objectId);
|
|
5150
|
+
const p = getPart(o, partId);
|
|
5151
|
+
const c = p.spec.contains;
|
|
5152
|
+
if (!c || isLost(p) || (p.st.liters ?? 0) <= 0) return false;
|
|
5153
|
+
if (p.st.holes.some((h) => h.vent)) return true;
|
|
5154
|
+
return run(() => {
|
|
5155
|
+
const wp = opening.point ?? partXf(o, p).c;
|
|
5156
|
+
const wn = vec.normalize(opening.normal ?? [0, 1, 0]);
|
|
5157
|
+
const hole = { point: objLocal(o, wp), normal: objLocalDir(o, wn), diameter: opening.diameter ?? VENT_DIAMETER, through: true, vent: true };
|
|
5158
|
+
p.st.holes.push(hole);
|
|
5159
|
+
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
5160
|
+
o.active = true;
|
|
5161
|
+
emit({
|
|
5162
|
+
...base(o),
|
|
5163
|
+
type: "leak",
|
|
5164
|
+
part: partId,
|
|
5165
|
+
fluid: c.fluid,
|
|
5166
|
+
point: wp,
|
|
5167
|
+
direction: c.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, c.outflow))) : wn,
|
|
5168
|
+
rate: holeRate(hole, !!c.pressurized),
|
|
5169
|
+
pressurized: !!c.pressurized
|
|
5170
|
+
});
|
|
5171
|
+
return true;
|
|
5172
|
+
}).value;
|
|
5173
|
+
},
|
|
5174
|
+
seal(objectId, partId) {
|
|
5175
|
+
if (impls.has(objectId)) return 0;
|
|
5176
|
+
const o = getObj(objectId);
|
|
5177
|
+
const p = getPart(o, partId);
|
|
5178
|
+
const before = p.st.holes.length;
|
|
5179
|
+
p.st.holes = p.st.holes.filter((h) => !h.vent);
|
|
5180
|
+
o.active = true;
|
|
5181
|
+
return before - p.st.holes.length;
|
|
5182
|
+
},
|
|
3440
5183
|
setLiters(objectId, partId, liters) {
|
|
3441
5184
|
if (impls.has(objectId)) return;
|
|
3442
5185
|
const p = getPart(getObj(objectId), partId);
|
|
@@ -3450,7 +5193,12 @@ var GameDestruction = (() => {
|
|
|
3450
5193
|
time += dt;
|
|
3451
5194
|
const sources = [];
|
|
3452
5195
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
3453
|
-
if (
|
|
5196
|
+
for (const o of [...objects.values()]) if (o.soil && objects.get(o.id) === o) stepSoil(o, dt);
|
|
5197
|
+
for (const o of [...objects.values()]) if (o.wind && objects.get(o.id) === o) stepWind(o, dt);
|
|
5198
|
+
if (sources.length) {
|
|
5199
|
+
spread2(sources, dt);
|
|
5200
|
+
igniteLeaksNearFire(sources, dt);
|
|
5201
|
+
}
|
|
3454
5202
|
detonatePrimed(time);
|
|
3455
5203
|
releaseFront();
|
|
3456
5204
|
for (const im of [...impls.values()]) {
|
|
@@ -3472,7 +5220,7 @@ var GameDestruction = (() => {
|
|
|
3472
5220
|
return;
|
|
3473
5221
|
}
|
|
3474
5222
|
const o = getObj(id);
|
|
3475
|
-
objects.set(id, build(o.spec, id, o.
|
|
5223
|
+
objects.set(id, build(o.spec, id, o.base.pos, o.base.rot));
|
|
3476
5224
|
},
|
|
3477
5225
|
on(type, fn) {
|
|
3478
5226
|
let set = listeners.get(type);
|
|
@@ -3489,7 +5237,7 @@ var GameDestruction = (() => {
|
|
|
3489
5237
|
rng: rng.state(),
|
|
3490
5238
|
counter,
|
|
3491
5239
|
objects: [...objects.values()].map((o) => {
|
|
3492
|
-
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
5240
|
+
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
5241
|
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
3494
5242
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
3495
5243
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
@@ -3510,9 +5258,17 @@ var GameDestruction = (() => {
|
|
|
3510
5258
|
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
3511
5259
|
),
|
|
3512
5260
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
3513
|
-
collapsed: e.runtime?.collapsed
|
|
5261
|
+
collapsed: e.runtime?.collapsed,
|
|
5262
|
+
...e.runtime?.wind ? { wind: e.runtime.wind } : {}
|
|
3514
5263
|
};
|
|
3515
|
-
|
|
5264
|
+
let pos = state.position, rot = state.rotation;
|
|
5265
|
+
if (state.soil) {
|
|
5266
|
+
carry.soil = state.soil;
|
|
5267
|
+
if (e.runtime?.soil) carry.soilSets = e.runtime.soil;
|
|
5268
|
+
rot = quat.normalize(quat.mul(state.rotation, quat.conjugate(state.soil.offset.rotation)));
|
|
5269
|
+
pos = vec.sub(state.position, rotateVec(rot, state.soil.offset.position));
|
|
5270
|
+
}
|
|
5271
|
+
objects.set(e.id, build(clone(e.spec), e.id, pos, rot, carry));
|
|
3516
5272
|
}
|
|
3517
5273
|
for (const e of snap.impls ?? []) {
|
|
3518
5274
|
const im = impls.get(e.id);
|
|
@@ -3526,21 +5282,6 @@ var GameDestruction = (() => {
|
|
|
3526
5282
|
return world;
|
|
3527
5283
|
};
|
|
3528
5284
|
|
|
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
5285
|
// src/data/assemblies.json
|
|
3545
5286
|
var assemblies_default = [
|
|
3546
5287
|
{
|
|
@@ -3653,21 +5394,35 @@ var GameDestruction = (() => {
|
|
|
3653
5394
|
const dw = o.doorWidth ?? 1, dh = o.doorHeight ?? 2.1;
|
|
3654
5395
|
const mat = o.material ?? "concrete", wmat = o.wallMaterial ?? "brick";
|
|
3655
5396
|
const roof = o.roof ?? true;
|
|
5397
|
+
const rebarOf = (kind) => o.rebar === false ? {} : { rebar: { ratio: o.rebar?.[kind] ?? (kind === "beam" ? 0.01 : 5e-3) } };
|
|
3656
5398
|
const xs = Array.from({ length: nx }, (_, i) => -spanX / 2 + i * spanX / (nx - 1));
|
|
3657
5399
|
const zs = Array.from({ length: nz }, (_, j) => -spanZ / 2 + j * spanZ / (nz - 1));
|
|
3658
5400
|
const parts = [];
|
|
3659
5401
|
const joints = [];
|
|
3660
5402
|
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
|
-
|
|
5403
|
+
const pad = o.footing ?? cs * 2.2;
|
|
5404
|
+
const pile = o.piles ? { pile: { ...o.piles } } : {};
|
|
5405
|
+
if (o.raft) {
|
|
5406
|
+
parts.push({
|
|
5407
|
+
id: "raft",
|
|
5408
|
+
material: mat,
|
|
5409
|
+
shape: { kind: "box", size: [spanX + pad, 0.4, spanZ + pad] },
|
|
5410
|
+
position: [0, -0.2, 0],
|
|
5411
|
+
structure: { role: "foundation", ...pile },
|
|
5412
|
+
anchored: true
|
|
5413
|
+
});
|
|
5414
|
+
} else {
|
|
5415
|
+
for (let i = 0; i < nx; i++) {
|
|
5416
|
+
for (let j = 0; j < nz; j++) {
|
|
5417
|
+
parts.push({
|
|
5418
|
+
id: `foundation_${i}_${j}`,
|
|
5419
|
+
material: mat,
|
|
5420
|
+
shape: { kind: "box", size: [pad, 0.3, pad] },
|
|
5421
|
+
position: [xs[i], -0.15, zs[j]],
|
|
5422
|
+
structure: { role: "foundation", ...pile },
|
|
5423
|
+
anchored: true
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
3671
5426
|
}
|
|
3672
5427
|
}
|
|
3673
5428
|
for (let f = 0; f < floors; f++) {
|
|
@@ -3683,14 +5438,15 @@ var GameDestruction = (() => {
|
|
|
3683
5438
|
material: mat,
|
|
3684
5439
|
shape: { kind: "box", size: [cs, H, cs] },
|
|
3685
5440
|
position: [xs[i], yb + H / 2, zs[j]],
|
|
3686
|
-
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity }
|
|
5441
|
+
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity },
|
|
5442
|
+
...o.columnCage ? { reinforcement: { ...o.columnCage } } : {}
|
|
3687
5443
|
});
|
|
3688
|
-
on(id, f === 0 ? `foundation_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
5444
|
+
on(id, f === 0 ? o.raft ? "raft" : `foundation_${i}_${j}` : o.continuousColumns ? `column_${f - 1}_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
3689
5445
|
}
|
|
3690
5446
|
}
|
|
3691
5447
|
const beams = [];
|
|
3692
5448
|
const beam = (id, size, pos, c1, c2) => {
|
|
3693
|
-
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam" } });
|
|
5449
|
+
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam", ...rebarOf("beam") } });
|
|
3694
5450
|
on(id, c1, "rebar");
|
|
3695
5451
|
on(id, c2, "rebar");
|
|
3696
5452
|
beams.push(id);
|
|
@@ -3705,13 +5461,13 @@ var GameDestruction = (() => {
|
|
|
3705
5461
|
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
5462
|
}
|
|
3707
5463
|
}
|
|
3708
|
-
const thick = last && roof ? T * 0.75 : T;
|
|
5464
|
+
const thick = last && roof ? o.roofThickness ?? T * 0.75 : T;
|
|
3709
5465
|
parts.push({
|
|
3710
5466
|
id: slabId,
|
|
3711
5467
|
material: mat,
|
|
3712
5468
|
shape: { kind: "box", size: [spanX + cs + 0.2, thick, spanZ + cs + 0.2] },
|
|
3713
5469
|
position: [0, top + thick / 2, 0],
|
|
3714
|
-
structure: { role: last && roof ? "roof" : "slab" }
|
|
5470
|
+
structure: { role: last && roof ? "roof" : "slab", ...rebarOf("slab") }
|
|
3715
5471
|
});
|
|
3716
5472
|
for (const b of beams) on(slabId, b, last && roof ? "resting" : "rebar");
|
|
3717
5473
|
if (o.walls) {
|
|
@@ -3744,28 +5500,91 @@ var GameDestruction = (() => {
|
|
|
3744
5500
|
}
|
|
3745
5501
|
}
|
|
3746
5502
|
}
|
|
3747
|
-
return { name: "frame", parts, joints, tags: ["structure"] };
|
|
5503
|
+
return { name: "frame", parts, joints, tags: ["structure"], ...o.soil !== void 0 ? { soil: o.soil } : {} };
|
|
3748
5504
|
};
|
|
3749
5505
|
|
|
3750
5506
|
// src/size.ts
|
|
5507
|
+
var MAX_REBAR_RATIO = 0.04;
|
|
5508
|
+
var onRock = (spec) => {
|
|
5509
|
+
if (spec.soil === void 0) return spec;
|
|
5510
|
+
const { soil: _soil, ...rest } = spec;
|
|
5511
|
+
return rest;
|
|
5512
|
+
};
|
|
5513
|
+
var without = (spec, id) => {
|
|
5514
|
+
const reduced = { ...spec, parts: spec.parts.filter((p) => p.id !== id), joints: (spec.joints ?? []).filter((j) => j.a !== id && j.b !== id) };
|
|
5515
|
+
if (!spec.joints) delete reduced.joints;
|
|
5516
|
+
return reduced;
|
|
5517
|
+
};
|
|
5518
|
+
var sizeRebar = (spec, options = {}) => {
|
|
5519
|
+
const flexure = resolveFlexure(options.flexure);
|
|
5520
|
+
const materials = new Map(MATERIALS.map((m) => [m.id, m]));
|
|
5521
|
+
for (const m of options.materials ?? []) materials.set(m.id, m);
|
|
5522
|
+
const out = structuredClone(spec);
|
|
5523
|
+
const targets = out.parts.filter((p) => {
|
|
5524
|
+
const m = materials.get(p.material);
|
|
5525
|
+
return p.shape.kind === "box" && !!m && isReinforced(p) && isFlexural(p, m, flexure);
|
|
5526
|
+
});
|
|
5527
|
+
if (targets.length === 0) return out;
|
|
5528
|
+
const safety = options.safety ?? 0.65;
|
|
5529
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
5530
|
+
const bending = (s) => {
|
|
5531
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, structure: { flexure: options.flexure } });
|
|
5532
|
+
const id = w.add(onRock(s));
|
|
5533
|
+
return new Map(w.state(id).parts.map((p) => [p.id, p.flexure ?? 0]));
|
|
5534
|
+
};
|
|
5535
|
+
const base = bending(out);
|
|
5536
|
+
const factor = new Map(targets.map((p) => [p.id, (base.get(p.id) ?? 0) / safetyOf(p.structure.role)]));
|
|
5537
|
+
if (options.redundancy !== void 0) {
|
|
5538
|
+
const roles = options.redundancyRoles ?? ["column"];
|
|
5539
|
+
for (const lost of out.parts) {
|
|
5540
|
+
if (!lost.structure || !roles.includes(lost.structure.role)) continue;
|
|
5541
|
+
const l = bending(without(out, lost.id));
|
|
5542
|
+
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));
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
for (const p of targets) {
|
|
5546
|
+
const f = factor.get(p.id);
|
|
5547
|
+
const g = bendGeometry(p);
|
|
5548
|
+
if (f <= 1 || g.gravity < 0) continue;
|
|
5549
|
+
const section = { b: sectionWidth(g, g.gravity), h: g.dims[g.gravity] };
|
|
5550
|
+
const steel = tensionSteel(p, g.dims, section, flexure);
|
|
5551
|
+
const m = materials.get(p.material);
|
|
5552
|
+
const need = f * sectionMoments(m, section, steel, flexure).ultimate;
|
|
5553
|
+
const momentOf = (area) => steelMoment(area, steel.yieldMPa, steel.d, section.b, m.strength.compressiveMPa, flexure);
|
|
5554
|
+
const cap = MAX_REBAR_RATIO * section.b * steel.d;
|
|
5555
|
+
if (steel.area >= cap) continue;
|
|
5556
|
+
let lo = steel.area, hi = cap;
|
|
5557
|
+
if (momentOf(hi) > need) {
|
|
5558
|
+
for (let k = 0; k < 60; k++) {
|
|
5559
|
+
const mid = (lo + hi) / 2;
|
|
5560
|
+
if (momentOf(mid) < need) lo = mid;
|
|
5561
|
+
else hi = mid;
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
const ratio = hi / (section.b * steel.d);
|
|
5565
|
+
p.structure = { ...p.structure, rebar: { ...p.structure.rebar, ratio } };
|
|
5566
|
+
}
|
|
5567
|
+
return out;
|
|
5568
|
+
};
|
|
3751
5569
|
var sizeStructure = (spec, options = {}) => {
|
|
3752
5570
|
const safety = options.safety ?? 0.65;
|
|
3753
5571
|
const redundancy = options.redundancy;
|
|
3754
5572
|
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3755
5573
|
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 }]));
|
|
5574
|
+
const loads = (s, wind) => {
|
|
5575
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, ...wind ? { wind } : {} });
|
|
5576
|
+
const id = w.add(onRock(s));
|
|
5577
|
+
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0, wind: p.wind ?? 0 }]));
|
|
3760
5578
|
};
|
|
3761
5579
|
const out = structuredClone(spec);
|
|
3762
|
-
const base = loads(out);
|
|
5580
|
+
const base = loads(out, options.wind);
|
|
5581
|
+
const gamma = options.wind ? resolveWind(options.wind).loadFactor : 0;
|
|
3763
5582
|
const need = /* @__PURE__ */ new Map();
|
|
3764
5583
|
for (const p of out.parts) {
|
|
3765
5584
|
if (!p.structure || p.structure.role === "none") continue;
|
|
3766
5585
|
const b = base.get(p.id);
|
|
3767
5586
|
if (!b || b.stress <= 0) continue;
|
|
3768
|
-
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
5587
|
+
need.set(p.id, (b.load + (b.wind > 0 ? gamma * b.wind / GRAVITY : 0)) / safetyOf(p.structure.role));
|
|
3769
5588
|
}
|
|
3770
5589
|
if (redundancy !== void 0) {
|
|
3771
5590
|
for (const col of out.parts) {
|
|
@@ -3787,7 +5606,20 @@ var GameDestruction = (() => {
|
|
|
3787
5606
|
const n = need.get(p.id);
|
|
3788
5607
|
if (n !== void 0) p.structure = { ...p.structure, capacity: n };
|
|
3789
5608
|
}
|
|
3790
|
-
|
|
5609
|
+
if (options.wind) {
|
|
5610
|
+
const yieldMPa = resolveFlexure(options.flexure).rebarYieldMPa;
|
|
5611
|
+
for (const p of out.parts) {
|
|
5612
|
+
const b = base.get(p.id);
|
|
5613
|
+
if (!b || b.wind >= 0 || !p.reinforcement || p.shape.kind !== "box") continue;
|
|
5614
|
+
const pull = -(b.load + gamma * b.wind / GRAVITY) * GRAVITY;
|
|
5615
|
+
if (pull <= 0) continue;
|
|
5616
|
+
const dims = shapeDims(p.shape);
|
|
5617
|
+
const have = tensionCapacity(dims, { reinforcement: p.reinforcement, yieldMPa });
|
|
5618
|
+
const want = pull / safetyOf(p.structure?.role ?? "wall");
|
|
5619
|
+
if (have > 0 && want > have) p.reinforcement = { ...p.reinforcement, diameter: Math.min(0.04, p.reinforcement.diameter * Math.sqrt(want / have)) };
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
return sizeRebar(out, options);
|
|
3791
5623
|
};
|
|
3792
5624
|
|
|
3793
5625
|
// src/locale.ts
|
|
@@ -3869,6 +5701,8 @@ var GameDestruction = (() => {
|
|
|
3869
5701
|
primed: "about to explode",
|
|
3870
5702
|
stress: "overload",
|
|
3871
5703
|
collapse: "collapsed",
|
|
5704
|
+
settle: "ground gave way",
|
|
5705
|
+
sway: "sway",
|
|
3872
5706
|
spall: "plaster came off",
|
|
3873
5707
|
stage: "damage stage",
|
|
3874
5708
|
expose: "rebar bare",
|
|
@@ -3957,6 +5791,8 @@ var GameDestruction = (() => {
|
|
|
3957
5791
|
primed: "prestes a explodir",
|
|
3958
5792
|
stress: "sobrecarga",
|
|
3959
5793
|
collapse: "desabou",
|
|
5794
|
+
settle: "recalque do solo",
|
|
5795
|
+
sway: "balan\xE7o",
|
|
3960
5796
|
spall: "reboco soltou",
|
|
3961
5797
|
stage: "est\xE1gio de dano",
|
|
3962
5798
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
@@ -3971,115 +5807,6 @@ var GameDestruction = (() => {
|
|
|
3971
5807
|
};
|
|
3972
5808
|
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3973
5809
|
|
|
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
5810
|
// src/contracts.ts
|
|
4084
5811
|
var checkDestructible2 = (impl, options = {}) => {
|
|
4085
5812
|
const contractOptions = { materials: [...MATERIALS.map((m) => m.id), ...options.materials ?? []] };
|