@adatechnology/game-destruction 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -13
- package/dist/index.d.ts +805 -25
- package/dist/index.global.js +1931 -205
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1780 -96
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -525,6 +525,7 @@ var isMember = (dims) => {
|
|
|
525
525
|
const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
|
|
526
526
|
return b / a < 3 && c / b >= 3;
|
|
527
527
|
};
|
|
528
|
+
var memberBarCount = (width, spacing) => spread(width, COVER, spacing, MAX_MEMBER_BARS_PER_SIDE).length;
|
|
528
529
|
var rebarBars = (dims, thin, spacing) => {
|
|
529
530
|
const out = [];
|
|
530
531
|
if (isMember(dims)) {
|
|
@@ -560,12 +561,808 @@ var clipBarsToDisc = (bars, u, v, center, radius) => {
|
|
|
560
561
|
}
|
|
561
562
|
return out;
|
|
562
563
|
};
|
|
564
|
+
var verticalBarCount = (dims, thin, spacing) => {
|
|
565
|
+
if (isMember(dims)) {
|
|
566
|
+
if (dims.indexOf(Math.max(...dims)) !== 1) return 0;
|
|
567
|
+
const us = spread(dims[0], COVER, spacing, Infinity).length, vs = spread(dims[2], COVER, spacing, Infinity).length;
|
|
568
|
+
return 2 * us + 2 * Math.max(0, vs - 2);
|
|
569
|
+
}
|
|
570
|
+
const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
|
|
571
|
+
if (u === 1) return spread(dims[v], COVER, spacing, Infinity).length;
|
|
572
|
+
if (v === 1) return spread(dims[u], COVER, spacing, Infinity).length;
|
|
573
|
+
return 0;
|
|
574
|
+
};
|
|
563
575
|
var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
|
|
564
576
|
let length = 0;
|
|
565
577
|
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]);
|
|
566
578
|
return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
|
|
567
579
|
};
|
|
568
580
|
|
|
581
|
+
// src/flexure.ts
|
|
582
|
+
var GRAVITY = 9.81;
|
|
583
|
+
var MPA = 1e6;
|
|
584
|
+
var FLEXURE_DEFAULTS = {
|
|
585
|
+
enabled: true,
|
|
586
|
+
roles: ["beam", "slab", "roof", "stair"],
|
|
587
|
+
scope: "rebar",
|
|
588
|
+
crackFactor: 1.5,
|
|
589
|
+
rebarYieldMPa: 500,
|
|
590
|
+
cover: 0.03,
|
|
591
|
+
neutralAxisLimit: 0.45,
|
|
592
|
+
failRotationDeg: 10,
|
|
593
|
+
loadFactor: 0.64,
|
|
594
|
+
massFactor: 0.5,
|
|
595
|
+
tntJoules: 4184e3,
|
|
596
|
+
impulse: 200,
|
|
597
|
+
reflection: 2,
|
|
598
|
+
reflectionNear: 4,
|
|
599
|
+
nearScaled: 0.5
|
|
600
|
+
};
|
|
601
|
+
var MASONRY_DEFAULTS = { minEccentricity: 0.05, slendernessLimit: 40, concreteFactor: 0.85 };
|
|
602
|
+
var resolveFlexure = (o) => ({ ...FLEXURE_DEFAULTS, ...definedOf(o) });
|
|
603
|
+
var resolveMasonry = (o) => ({ ...MASONRY_DEFAULTS, ...definedOf(o) });
|
|
604
|
+
var definedOf = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
|
|
605
|
+
var RATIO_BAR = 0.01;
|
|
606
|
+
var crackingMoment = (tensileMPa, b, h, alpha = FLEXURE_DEFAULTS.crackFactor) => alpha * tensileMPa * MPA * (b * h * h) / 6;
|
|
607
|
+
var steelMoment = (area, yieldMPa, d, b, compressiveMPa, options = {}) => {
|
|
608
|
+
if (area <= 0 || d <= 0 || b <= 0) return 0;
|
|
609
|
+
const T = area * yieldMPa * MPA;
|
|
610
|
+
const block = 0.68 * compressiveMPa * MPA * b;
|
|
611
|
+
const x = T / block;
|
|
612
|
+
const xl = (options.neutralAxisLimit ?? FLEXURE_DEFAULTS.neutralAxisLimit) * d;
|
|
613
|
+
if (x <= xl) return T * (d - 0.4 * x);
|
|
614
|
+
const top = Math.min(d / 2, (options.cover ?? FLEXURE_DEFAULTS.cover) + RATIO_BAR / 2);
|
|
615
|
+
return block * xl * (d - 0.4 * xl) + (T - block * xl) * (d - top);
|
|
616
|
+
};
|
|
617
|
+
var spanStiffness = (EI, span) => 384 * EI / (5 * span ** 3);
|
|
618
|
+
var elasticEnergy = (moment, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => {
|
|
619
|
+
const R = 8 * moment / span;
|
|
620
|
+
return loadFactor * R * R / (2 * spanStiffness(EI, span));
|
|
621
|
+
};
|
|
622
|
+
var momentOfEnergy = (energy, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => span / 8 * Math.sqrt(2 * Math.max(0, energy) * spanStiffness(EI, span) / loadFactor);
|
|
623
|
+
var impulseEnergy = (impulse, mass, options = {}) => {
|
|
624
|
+
const KL = options.loadFactor ?? FLEXURE_DEFAULTS.loadFactor;
|
|
625
|
+
const KM = options.massFactor ?? FLEXURE_DEFAULTS.massFactor;
|
|
626
|
+
return mass > 0 ? (KL * impulse) ** 2 / (2 * KM * mass) : 0;
|
|
627
|
+
};
|
|
628
|
+
var blastImpulse = (tnt, distance, options = {}) => {
|
|
629
|
+
if (tnt <= 0) return 0;
|
|
630
|
+
const o = { ...FLEXURE_DEFAULTS, ...definedOf(options) };
|
|
631
|
+
const cube = Math.cbrt(tnt);
|
|
632
|
+
const r = Math.max(distance, o.nearScaled * cube);
|
|
633
|
+
return (o.reflection + o.reflectionNear / (r / cube)) * (o.impulse * cube * cube / r);
|
|
634
|
+
};
|
|
635
|
+
var slendernessFactor = (h, t, options = {}) => {
|
|
636
|
+
const o = resolveMasonry(options);
|
|
637
|
+
const lambda = h / Math.max(t, 1e-3);
|
|
638
|
+
return Math.max(0, (1 - 2 * o.minEccentricity) * (1 - (lambda / o.slendernessLimit) ** 3));
|
|
639
|
+
};
|
|
640
|
+
var isReinforced = (part) => !!part.reinforcement || (part.structure?.rebar?.ratio ?? 0) > 0;
|
|
641
|
+
var isSlender = (part, material) => material.strength?.slender ?? (!!material.tags?.includes("masonry") && !isReinforced(part));
|
|
642
|
+
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);
|
|
643
|
+
var tensionSteel = (part, dims, section, options) => {
|
|
644
|
+
const rebar = part.structure?.rebar;
|
|
645
|
+
const cage = part.reinforcement;
|
|
646
|
+
const bar2 = rebar ? RATIO_BAR : cage?.diameter ?? RATIO_BAR;
|
|
647
|
+
const d = Math.max(0.01, section.h - (rebar?.cover ?? options.cover) - bar2 / 2);
|
|
648
|
+
const yieldMPa = rebar?.yieldMPa ?? options.rebarYieldMPa;
|
|
649
|
+
if (rebar && rebar.ratio > 0) return { area: rebar.ratio * section.b * d, d, yieldMPa };
|
|
650
|
+
if (!cage) return { area: 0, d, yieldMPa };
|
|
651
|
+
const one = Math.PI * cage.diameter * cage.diameter / 4;
|
|
652
|
+
const count = isMember(dims) ? memberBarCount(section.b, cage.spacing) : section.b / cage.spacing;
|
|
653
|
+
return { area: one * count, d, yieldMPa };
|
|
654
|
+
};
|
|
655
|
+
var sectionMoments = (material, section, steel, options) => {
|
|
656
|
+
const s = material.strength;
|
|
657
|
+
const cracking = crackingMoment(s.tensileMPa ?? 0, section.b, section.h, options.crackFactor);
|
|
658
|
+
const steelM = steelMoment(steel.area, steel.yieldMPa, steel.d, section.b, s.compressiveMPa, options);
|
|
659
|
+
return { cracking, steel: steelM, ultimate: Math.max(cracking, steelM), reinforced: steelM > 0 };
|
|
660
|
+
};
|
|
661
|
+
var spanMoment = (w, span) => w * span * span / 8;
|
|
662
|
+
var bendGeometry = (part) => {
|
|
663
|
+
const dims = shapeDims(part.shape);
|
|
664
|
+
const member = isMember(dims);
|
|
665
|
+
const q = quatFromEuler(...part.rotation ?? [0, 0, 0]);
|
|
666
|
+
const up = rotateVec(quat.conjugate(q), [0, 1, 0]);
|
|
667
|
+
let spanAxes;
|
|
668
|
+
let depthAxes;
|
|
669
|
+
if (member) {
|
|
670
|
+
const long = dims.indexOf(Math.max(...dims));
|
|
671
|
+
spanAxes = [long];
|
|
672
|
+
depthAxes = [0, 1, 2].filter((i) => i !== long);
|
|
673
|
+
} else {
|
|
674
|
+
const thin = thinAxisOf(dims);
|
|
675
|
+
spanAxes = [...planeAxes(thin)];
|
|
676
|
+
depthAxes = [thin];
|
|
677
|
+
}
|
|
678
|
+
let gravity = -1;
|
|
679
|
+
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;
|
|
680
|
+
return { dims, member, spanAxes, depthAxes, gravity, up };
|
|
681
|
+
};
|
|
682
|
+
var sectionWidth = (g, axis) => g.member ? g.dims[g.depthAxes.find((a) => a !== axis) ?? axis] : 1;
|
|
683
|
+
var lineLoad = (g, load) => {
|
|
684
|
+
const area = g.member ? g.dims[g.spanAxes[0]] : g.dims[g.spanAxes[0]] * g.dims[g.spanAxes[1]];
|
|
685
|
+
return load * GRAVITY / Math.max(area, 1e-6);
|
|
686
|
+
};
|
|
687
|
+
var spanOf = (g, contacts, samples = 9) => {
|
|
688
|
+
if (contacts.length === 0) return 0;
|
|
689
|
+
const axes = g.spanAxes;
|
|
690
|
+
const steps = (a) => Array.from({ length: samples }, (_, i) => (i / (samples - 1) - 0.5) * g.dims[a]);
|
|
691
|
+
const us = steps(axes[0]);
|
|
692
|
+
const vs = axes.length > 1 ? steps(axes[1]) : [0];
|
|
693
|
+
let far = 0;
|
|
694
|
+
for (const u of us) for (const v of vs) {
|
|
695
|
+
let near = Infinity;
|
|
696
|
+
for (const [lo, hi] of contacts) {
|
|
697
|
+
let gap = 0;
|
|
698
|
+
const x = [u, v];
|
|
699
|
+
axes.forEach((a, k) => gap = Math.max(gap, lo[a] - x[k], x[k] - hi[a], 0));
|
|
700
|
+
if (gap < near) near = gap;
|
|
701
|
+
}
|
|
702
|
+
if (near > far) far = near;
|
|
703
|
+
}
|
|
704
|
+
return 2 * far;
|
|
705
|
+
};
|
|
706
|
+
var FLEXURAL_ROLES = FLEXURE_DEFAULTS.roles;
|
|
707
|
+
var bearingWallThickness = (options) => {
|
|
708
|
+
const gf = options.loadFactor ?? 1.4;
|
|
709
|
+
const gm = options.materialFactor ?? 2;
|
|
710
|
+
const min = options.minThickness ?? 0.14;
|
|
711
|
+
const step = options.step ?? 0.01;
|
|
712
|
+
const fd = (options.material.strength?.compressiveMPa ?? 0) * MPA / gm;
|
|
713
|
+
const own = options.material.density * GRAVITY * options.storeyHeight;
|
|
714
|
+
const out = [];
|
|
715
|
+
let above = 0;
|
|
716
|
+
for (let i = 0; i < options.storeys; i++) {
|
|
717
|
+
const n = above + options.floorLoad;
|
|
718
|
+
let t = min;
|
|
719
|
+
for (let k = 0; k < 60; k++) {
|
|
720
|
+
const room = fd * slendernessFactor(options.storeyHeight, t, options.masonry) - gf * own;
|
|
721
|
+
if (room <= 0) {
|
|
722
|
+
t *= 1.25;
|
|
723
|
+
if (t > 20) {
|
|
724
|
+
t = Infinity;
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const next = Math.max(min, gf * n / room);
|
|
730
|
+
if (Math.abs(next - t) < 1e-5) break;
|
|
731
|
+
t = next;
|
|
732
|
+
}
|
|
733
|
+
t = Number.isFinite(t) ? Math.ceil(t / step - 1e-9) * step : Infinity;
|
|
734
|
+
out.push(t);
|
|
735
|
+
above = n + own * (Number.isFinite(t) ? t : 0);
|
|
736
|
+
}
|
|
737
|
+
return out.reverse();
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// src/data/soils.json
|
|
741
|
+
var soils_default = [
|
|
742
|
+
{ id: "rock", kind: "rock", allowableKPa: 3e3, spt: 50, elasticMPa: 1e4, poisson: 0.2, pile: { tipKPa: 9e3, skinKPa: 500 } },
|
|
743
|
+
{ id: "rock_fractured", kind: "rock", allowableKPa: 1500, spt: 50, elasticMPa: 2e3, poisson: 0.25, pile: { tipKPa: 4500, skinKPa: 300 } },
|
|
744
|
+
{ id: "gravel_dense", kind: "gravel", allowableKPa: 600, spt: 40, elasticMPa: 132, poisson: 0.3 },
|
|
745
|
+
{ id: "gravel_loose", kind: "gravel", allowableKPa: 300, spt: 8, elasticMPa: 26, poisson: 0.3 },
|
|
746
|
+
{ id: "sand_very_dense", kind: "sand", allowableKPa: 500, spt: 45, elasticMPa: 120, poisson: 0.3 },
|
|
747
|
+
{ id: "sand_dense", kind: "sand", allowableKPa: 400, spt: 25, elasticMPa: 68, poisson: 0.3 },
|
|
748
|
+
{ id: "sand_medium", kind: "sand", allowableKPa: 200, spt: 12, elasticMPa: 32, poisson: 0.3 },
|
|
749
|
+
{ id: "sand_loose", kind: "sand", allowableKPa: 50, spt: 3, elasticMPa: 8, poisson: 0.3 },
|
|
750
|
+
{ id: "silt_hard", kind: "silt", allowableKPa: 300, spt: 25, elasticMPa: 44, poisson: 0.35 },
|
|
751
|
+
{ id: "silt_stiff", kind: "silt", allowableKPa: 200, spt: 15, elasticMPa: 26, poisson: 0.35 },
|
|
752
|
+
{ id: "silt_medium", kind: "silt", allowableKPa: 100, spt: 8, elasticMPa: 14, poisson: 0.35 },
|
|
753
|
+
{ id: "clay_hard", kind: "clay", allowableKPa: 300, spt: 25, elasticMPa: 35, poisson: 0.4 },
|
|
754
|
+
{ id: "clay_stiff", kind: "clay", allowableKPa: 200, spt: 15, elasticMPa: 21, poisson: 0.4 },
|
|
755
|
+
{ id: "clay_medium", kind: "clay", allowableKPa: 100, spt: 8, elasticMPa: 11, poisson: 0.4 },
|
|
756
|
+
{ id: "clay_soft", kind: "clay", allowableKPa: 30, spt: 3, elasticMPa: 4.2, poisson: 0.4 },
|
|
757
|
+
{ id: "clay_very_soft", kind: "clay", allowableKPa: 15, spt: 1, elasticMPa: 1.4, poisson: 0.4 }
|
|
758
|
+
];
|
|
759
|
+
|
|
760
|
+
// src/soil.ts
|
|
761
|
+
var SOILS = soils_default;
|
|
762
|
+
var SOIL_DEFAULTS = {
|
|
763
|
+
safety: 3,
|
|
764
|
+
pileSafety: 2,
|
|
765
|
+
rigidity: 0.82,
|
|
766
|
+
influence: 2,
|
|
767
|
+
imperfection: 1 / 200,
|
|
768
|
+
cells: 4,
|
|
769
|
+
sinkRate: 0.05,
|
|
770
|
+
tiltRate: 1.5,
|
|
771
|
+
pileElasticGPa: 25,
|
|
772
|
+
footingWidth: 0.6
|
|
773
|
+
};
|
|
774
|
+
var resolveSoil = (o = {}) => {
|
|
775
|
+
const byId = new Map(SOILS.map((s) => [s.id, s]));
|
|
776
|
+
for (const s of o.soils ?? []) byId.set(s.id, s);
|
|
777
|
+
const { soils: _soils, site, ...rest } = o;
|
|
778
|
+
return { ...SOIL_DEFAULTS, ...rest, ...site !== void 0 ? { site } : {}, byId };
|
|
779
|
+
};
|
|
780
|
+
var profileOf = (soil) => typeof soil === "string" ? { layers: [{ soil }] } : soil;
|
|
781
|
+
var layersBetween = (profile, top, bottom, soils) => {
|
|
782
|
+
const out = [];
|
|
783
|
+
let z = 0;
|
|
784
|
+
const layers = profile.layers;
|
|
785
|
+
for (let i = 0; i < layers.length; i++) {
|
|
786
|
+
const def = soils.get(layers[i].soil);
|
|
787
|
+
if (!def) throw new Error(`unknown soil "${layers[i].soil}"`);
|
|
788
|
+
const last = i === layers.length - 1;
|
|
789
|
+
const end = last ? Infinity : z + Math.max(0, layers[i].thickness ?? 0);
|
|
790
|
+
const a = Math.max(z, top), b = Math.min(end, bottom);
|
|
791
|
+
if (b > a) out.push({ soil: def, thickness: b - a, top: a });
|
|
792
|
+
z = end;
|
|
793
|
+
if (z >= bottom) break;
|
|
794
|
+
}
|
|
795
|
+
return out;
|
|
796
|
+
};
|
|
797
|
+
var soilAt = (profile, depth, soils) => {
|
|
798
|
+
let z = 0;
|
|
799
|
+
for (let i = 0; i < profile.layers.length; i++) {
|
|
800
|
+
const def = soils.get(profile.layers[i].soil);
|
|
801
|
+
if (!def) throw new Error(`unknown soil "${profile.layers[i].soil}"`);
|
|
802
|
+
z += Math.max(0, profile.layers[i].thickness ?? 0);
|
|
803
|
+
if (i === profile.layers.length - 1 || depth < z) return def;
|
|
804
|
+
}
|
|
805
|
+
throw new Error("a soil profile needs at least one layer");
|
|
806
|
+
};
|
|
807
|
+
var footingSoil = (profile, depth, width, soils, influence = SOIL_DEFAULTS.influence) => {
|
|
808
|
+
const zone = layersBetween(profile, depth, depth + Math.max(0.01, influence * width), soils);
|
|
809
|
+
let allowable = Infinity, e = 0, nu = 0, t = 0;
|
|
810
|
+
for (const l of zone) {
|
|
811
|
+
allowable = Math.min(allowable, l.soil.allowableKPa);
|
|
812
|
+
e += l.soil.elasticMPa * l.thickness;
|
|
813
|
+
nu += l.soil.poisson * l.thickness;
|
|
814
|
+
t += l.thickness;
|
|
815
|
+
}
|
|
816
|
+
if (t <= 0) {
|
|
817
|
+
const l = soilAt(profile, depth, soils);
|
|
818
|
+
return { allowableKPa: l.allowableKPa, elasticMPa: l.elasticMPa, poisson: l.poisson };
|
|
819
|
+
}
|
|
820
|
+
return { allowableKPa: allowable, elasticMPa: e / t, poisson: nu / t };
|
|
821
|
+
};
|
|
822
|
+
var DQ_C = { clay: 120, silt: 200, sand: 400, gravel: 400, rock: 400 };
|
|
823
|
+
var BORED = {
|
|
824
|
+
clay: { alpha: 0.85, beta: 0.8 },
|
|
825
|
+
silt: { alpha: 0.6, beta: 0.65 },
|
|
826
|
+
sand: { alpha: 0.5, beta: 0.5 },
|
|
827
|
+
gravel: { alpha: 0.5, beta: 0.5 },
|
|
828
|
+
rock: { alpha: 1, beta: 1 }
|
|
829
|
+
};
|
|
830
|
+
var clampN = (n) => Math.min(50, Math.max(3, n));
|
|
831
|
+
var pileCapacity = (profile, pile, top, soils) => {
|
|
832
|
+
const d = pile.diameter;
|
|
833
|
+
const area = Math.PI * d * d / 4;
|
|
834
|
+
const perimeter = Math.PI * d;
|
|
835
|
+
const driven = pile.method === "driven";
|
|
836
|
+
let skin = 0;
|
|
837
|
+
for (const l of layersBetween(profile, top, top + pile.length, soils)) {
|
|
838
|
+
const f = driven ? 1 : BORED[l.soil.kind].beta;
|
|
839
|
+
const kPa = l.soil.pile?.skinKPa ?? f * 10 * (clampN(l.soil.spt) / 3 + 1);
|
|
840
|
+
skin += kPa * 1e3 * perimeter * l.thickness;
|
|
841
|
+
}
|
|
842
|
+
const tipDepth = top + pile.length;
|
|
843
|
+
const tipSoil = soilAt(profile, tipDepth, soils);
|
|
844
|
+
const alpha = driven ? 1 : BORED[tipSoil.kind].alpha;
|
|
845
|
+
const tipKPa = tipSoil.pile?.tipKPa ?? alpha * DQ_C[tipSoil.kind] * clampN(tipSoil.spt);
|
|
846
|
+
const tip = tipKPa * 1e3 * area;
|
|
847
|
+
return { tip, skin, ultimate: tip + skin, tipSoil };
|
|
848
|
+
};
|
|
849
|
+
var pileLength = (profile, o) => {
|
|
850
|
+
const soils = o.soils ?? resolveSoil().byId;
|
|
851
|
+
const safety = o.safety ?? SOIL_DEFAULTS.pileSafety;
|
|
852
|
+
const step = o.step ?? 0.5;
|
|
853
|
+
const max = o.maxLength ?? 60;
|
|
854
|
+
for (let L = step; L <= max + 1e-9; L += step) {
|
|
855
|
+
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;
|
|
856
|
+
}
|
|
857
|
+
return max;
|
|
858
|
+
};
|
|
859
|
+
var pileStiffness = (pile, pileElasticGPa, tipElasticMPa, tipPoisson) => {
|
|
860
|
+
const area = Math.PI * pile.diameter * pile.diameter / 4;
|
|
861
|
+
const shaft = pile.length / (2 * pileElasticGPa * 1e9 * area);
|
|
862
|
+
const tip = (1 - tipPoisson * tipPoisson) / (tipElasticMPa * 1e6 * pile.diameter);
|
|
863
|
+
return 1 / (shaft + tip);
|
|
864
|
+
};
|
|
865
|
+
var subgradeModulus = (elasticMPa, poisson, width, rigidity = SOIL_DEFAULTS.rigidity) => elasticMPa * 1e6 / (Math.max(0.05, width) * (1 - poisson * poisson) * rigidity);
|
|
866
|
+
var footingArea = (load, allowableKPa) => load / (allowableKPa * 1e3);
|
|
867
|
+
var yieldSprings = (springs, s, a, b) => {
|
|
868
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
869
|
+
return springs.map((sp) => {
|
|
870
|
+
const set = sp.set ?? 0;
|
|
871
|
+
const w = s + a * (sp.x - x0) + b * (sp.z - z0);
|
|
872
|
+
return sp.k * (w - set) > sp.cap ? w - sp.cap / sp.k : set;
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
var stiffnessCentre = (springs) => {
|
|
876
|
+
let k = 0, x = 0, z = 0;
|
|
877
|
+
for (const s of springs) {
|
|
878
|
+
k += s.k;
|
|
879
|
+
x += s.k * s.x;
|
|
880
|
+
z += s.k * s.z;
|
|
881
|
+
}
|
|
882
|
+
return k > 0 ? [x / k, z / k] : [0, 0];
|
|
883
|
+
};
|
|
884
|
+
var cholesky3 = (J, r) => {
|
|
885
|
+
const L = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
886
|
+
for (let i = 0; i < 3; i++) {
|
|
887
|
+
for (let j = 0; j <= i; j++) {
|
|
888
|
+
let sum = J[i][j];
|
|
889
|
+
for (let k = 0; k < j; k++) sum -= L[i][k] * L[j][k];
|
|
890
|
+
if (i === j) {
|
|
891
|
+
if (!(sum > 1e-9 * Math.max(1, Math.abs(J[i][i])))) return null;
|
|
892
|
+
L[i][i] = Math.sqrt(sum);
|
|
893
|
+
} else L[i][j] = sum / L[j][j];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const y = [0, 0, 0];
|
|
897
|
+
for (let i = 0; i < 3; i++) {
|
|
898
|
+
let sum = r[i];
|
|
899
|
+
for (let k = 0; k < i; k++) sum -= L[i][k] * y[k];
|
|
900
|
+
y[i] = sum / L[i][i];
|
|
901
|
+
}
|
|
902
|
+
const x = [0, 0, 0];
|
|
903
|
+
for (let i = 2; i >= 0; i--) {
|
|
904
|
+
let sum = y[i];
|
|
905
|
+
for (let k = i + 1; k < 3; k++) sum -= L[k][i] * x[k];
|
|
906
|
+
x[i] = sum / L[i][i];
|
|
907
|
+
}
|
|
908
|
+
return x;
|
|
909
|
+
};
|
|
910
|
+
var solveFoundation = (springs, weight, xg, zg, height, imperfection, start = { s: 0, a: 0, b: 0 }) => {
|
|
911
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
912
|
+
const capSum = springs.reduce((sum, s2) => sum + s2.cap, 0);
|
|
913
|
+
const span = Math.max(1, ...springs.map((s2) => Math.max(Math.abs(s2.x - x0) + s2.hx, Math.abs(s2.z - z0) + s2.hz)));
|
|
914
|
+
let s = start.s, a = start.a, b = start.b;
|
|
915
|
+
if (springs.every((sp) => s + a * (sp.x - x0) + b * (sp.z - z0) - (sp.set ?? 0) <= 0)) {
|
|
916
|
+
s = weight / Math.max(1e-9, springs.reduce((sum, sp) => sum + sp.k, 0));
|
|
917
|
+
a = b = 0;
|
|
918
|
+
}
|
|
919
|
+
const Wh = weight * height;
|
|
920
|
+
const evaluate = () => {
|
|
921
|
+
let F = 0, Mx = 0, Mz = 0, edge = 0, yielded = false;
|
|
922
|
+
const Jt = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
923
|
+
const Je = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
924
|
+
const add = (J, k, dx, dz) => {
|
|
925
|
+
J[0][0] += k;
|
|
926
|
+
J[0][1] += k * dx;
|
|
927
|
+
J[0][2] += k * dz;
|
|
928
|
+
J[1][1] += k * dx * dx;
|
|
929
|
+
J[1][2] += k * dx * dz;
|
|
930
|
+
J[2][2] += k * dz * dz;
|
|
931
|
+
};
|
|
932
|
+
for (const sp of springs) {
|
|
933
|
+
const dx = sp.x - x0, dz = sp.z - z0;
|
|
934
|
+
const w = s + a * dx + b * dz - (sp.set ?? 0);
|
|
935
|
+
if (w <= 0) continue;
|
|
936
|
+
const f = sp.k * w;
|
|
937
|
+
add(Je, sp.k, dx, dz);
|
|
938
|
+
let force = f;
|
|
939
|
+
if (f >= sp.cap) {
|
|
940
|
+
force = sp.cap;
|
|
941
|
+
yielded = true;
|
|
942
|
+
} else add(Jt, sp.k, dx, dz);
|
|
943
|
+
edge = Math.max(edge, force / sp.cap);
|
|
944
|
+
F += force;
|
|
945
|
+
Mx += force * dx;
|
|
946
|
+
Mz += force * dz;
|
|
947
|
+
}
|
|
948
|
+
for (const J of [Jt, Je]) {
|
|
949
|
+
J[1][0] = J[0][1];
|
|
950
|
+
J[2][0] = J[0][2];
|
|
951
|
+
J[2][1] = J[1][2];
|
|
952
|
+
J[1][1] -= Wh;
|
|
953
|
+
J[2][2] -= Wh;
|
|
954
|
+
}
|
|
955
|
+
const r = [weight - F, weight * (xg - x0 + height * (a + imperfection[0])) - Mx, weight * (zg - z0 + height * (b + imperfection[1])) - Mz];
|
|
956
|
+
return { r, Jt, Je, edge, yielded };
|
|
957
|
+
};
|
|
958
|
+
let ev = evaluate();
|
|
959
|
+
let converged = false;
|
|
960
|
+
const tol = 1e-7 * weight;
|
|
961
|
+
for (let it = 0; it < 120; it++) {
|
|
962
|
+
if (Math.abs(ev.r[0]) < tol && Math.abs(ev.r[1]) < tol * span && Math.abs(ev.r[2]) < tol * span) {
|
|
963
|
+
converged = true;
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
const d = cholesky3(ev.Jt, ev.r) ?? cholesky3(ev.Je, ev.r);
|
|
967
|
+
if (!d) break;
|
|
968
|
+
s += d[0];
|
|
969
|
+
a += d[1];
|
|
970
|
+
b += d[2];
|
|
971
|
+
if (!Number.isFinite(s + a + b) || Math.abs(a) > 2 || Math.abs(b) > 2) break;
|
|
972
|
+
ev = evaluate();
|
|
973
|
+
}
|
|
974
|
+
const stable = converged && cholesky3(ev.Jt, [0, 0, 0]) !== null;
|
|
975
|
+
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]] };
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// src/data/wind-terrain.json
|
|
979
|
+
var wind_terrain_default = [
|
|
980
|
+
{ 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 },
|
|
981
|
+
{ id: "II", zg: 300, b: { A: 1, B: 1, C: 1 }, p: { A: 0.085, B: 0.09, C: 0.1 }, z0: 0.05 },
|
|
982
|
+
{ 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 },
|
|
983
|
+
{ 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 },
|
|
984
|
+
{ 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 }
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
// src/wind.ts
|
|
988
|
+
var WIND_TERRAIN = wind_terrain_default;
|
|
989
|
+
var WIND_DEFAULTS = {
|
|
990
|
+
speed: 40,
|
|
991
|
+
direction: [1, 0, 0],
|
|
992
|
+
terrain: "III",
|
|
993
|
+
class: "auto",
|
|
994
|
+
topography: 1,
|
|
995
|
+
statistical: 1,
|
|
996
|
+
drag: 1.3,
|
|
997
|
+
damping: 0.016,
|
|
998
|
+
airDensity: 1.226,
|
|
999
|
+
minHeight: 5,
|
|
1000
|
+
bandHeight: 3,
|
|
1001
|
+
stiffnessFactor: 0.8,
|
|
1002
|
+
beamFactor: 0.4,
|
|
1003
|
+
flange: 0.1,
|
|
1004
|
+
driftLimit: 1 / 500,
|
|
1005
|
+
loadFactor: 1.4,
|
|
1006
|
+
chord: false,
|
|
1007
|
+
meanRatio: 0.69,
|
|
1008
|
+
gustFactor: { A: 1, B: 0.98, C: 0.95 },
|
|
1009
|
+
gust: { components: 400, maxHz: 2, scale: 1200, seed: 1 },
|
|
1010
|
+
peakDecay: 60,
|
|
1011
|
+
tuning: "den-hartog"
|
|
1012
|
+
};
|
|
1013
|
+
var definedOf2 = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
|
|
1014
|
+
var resolveWind = (o = {}) => {
|
|
1015
|
+
const terrains = new Map(WIND_TERRAIN.map((t) => [t.id, t]));
|
|
1016
|
+
for (const t of o.terrains ?? []) terrains.set(t.id, t);
|
|
1017
|
+
const { terrains: _terrains, gustFactor, gust, ...rest } = o;
|
|
1018
|
+
return {
|
|
1019
|
+
...WIND_DEFAULTS,
|
|
1020
|
+
...definedOf2(rest),
|
|
1021
|
+
direction: o.direction ?? WIND_DEFAULTS.direction,
|
|
1022
|
+
gustFactor: { ...WIND_DEFAULTS.gustFactor, ...definedOf2(gustFactor) },
|
|
1023
|
+
gust: { ...WIND_DEFAULTS.gust, ...definedOf2(gust) },
|
|
1024
|
+
terrains
|
|
1025
|
+
};
|
|
1026
|
+
};
|
|
1027
|
+
var exposureOf = (w, spec) => spec ? { ...w, ...definedOf2(spec) } : w;
|
|
1028
|
+
var windPressure = (speed, density = WIND_DEFAULTS.airDensity) => 0.5 * density * speed * speed;
|
|
1029
|
+
var windClass = (largest) => largest <= 20 ? "A" : largest <= 50 ? "B" : "C";
|
|
1030
|
+
var heightFactor = (z, terrain, cls, o = {}) => {
|
|
1031
|
+
const zz = Math.min(terrain.zg, Math.max(o.minHeight ?? WIND_DEFAULTS.minHeight, z));
|
|
1032
|
+
return terrain.b[cls] * (o.gustFactor ?? WIND_DEFAULTS.gustFactor)[cls] * (zz / 10) ** terrain.p[cls];
|
|
1033
|
+
};
|
|
1034
|
+
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 } : {} });
|
|
1035
|
+
var turbulenceIntensity = (z, z0) => Math.min(0.5, 1 / Math.log(Math.max(z, 1.5 * z0) / z0));
|
|
1036
|
+
var solveWallFrame = (hb, ei, ga, V) => {
|
|
1037
|
+
const n = hb.length;
|
|
1038
|
+
const diag = new Array(n + 1).fill(0);
|
|
1039
|
+
const off = new Array(n).fill(0);
|
|
1040
|
+
const rhs = new Array(n + 1).fill(0);
|
|
1041
|
+
for (let j = 0; j < n; j++) {
|
|
1042
|
+
const h = hb[j], e = ei[j], g = ga[j];
|
|
1043
|
+
diag[j] += e / h + g * h / 3;
|
|
1044
|
+
diag[j + 1] += e / h + g * h / 3;
|
|
1045
|
+
off[j] = -e / h + g * h / 6;
|
|
1046
|
+
rhs[j] += h / 6 * (2 * V[j] + V[j + 1]);
|
|
1047
|
+
rhs[j + 1] += h / 6 * (V[j] + 2 * V[j + 1]);
|
|
1048
|
+
}
|
|
1049
|
+
const wall = ei[0] / (ei[0] + ga[0] * hb[0] * hb[0]);
|
|
1050
|
+
diag[0] += 1e6 * (ei[0] / hb[0] + ga[0] * hb[0]) * wall;
|
|
1051
|
+
const c = new Array(n + 1).fill(0);
|
|
1052
|
+
const d = new Array(n + 1).fill(0);
|
|
1053
|
+
const psi = new Array(n + 1).fill(0);
|
|
1054
|
+
c[0] = off[0] / diag[0];
|
|
1055
|
+
d[0] = rhs[0] / diag[0];
|
|
1056
|
+
for (let i = 1; i <= n; i++) {
|
|
1057
|
+
const m = diag[i] - off[i - 1] * c[i - 1];
|
|
1058
|
+
c[i] = i < n ? off[i] / m : 0;
|
|
1059
|
+
d[i] = (rhs[i] - off[i - 1] * d[i - 1]) / m;
|
|
1060
|
+
}
|
|
1061
|
+
psi[n] = d[n];
|
|
1062
|
+
for (let i = n - 1; i >= 0; i--) psi[i] = d[i] - c[i] * psi[i + 1];
|
|
1063
|
+
const w = new Array(n + 1).fill(0);
|
|
1064
|
+
for (let j = 0; j < n; j++) w[j + 1] = w[j] + hb[j] * (psi[j] + psi[j + 1]) / 2;
|
|
1065
|
+
return { psi, w };
|
|
1066
|
+
};
|
|
1067
|
+
var shearNodes = (f) => {
|
|
1068
|
+
const V = new Array(f.length + 1).fill(0);
|
|
1069
|
+
for (let j = f.length - 1; j >= 0; j--) V[j] = V[j + 1] + f[j];
|
|
1070
|
+
return V;
|
|
1071
|
+
};
|
|
1072
|
+
var momentNodes = (hb, V) => {
|
|
1073
|
+
const M = new Array(V.length).fill(0);
|
|
1074
|
+
for (let j = hb.length - 1; j >= 0; j--) M[j] = M[j + 1] + hb[j] * (V[j] + V[j + 1]) / 2;
|
|
1075
|
+
return M;
|
|
1076
|
+
};
|
|
1077
|
+
var RIGID = 1e3;
|
|
1078
|
+
var tBeamInertia = (bw, h, bf, tf) => {
|
|
1079
|
+
const web = bw * h;
|
|
1080
|
+
const fl = Math.max(0, bf - bw) * Math.min(tf, h);
|
|
1081
|
+
const t = Math.min(tf, h);
|
|
1082
|
+
const yc = (web * (h / 2) + fl * (h - t / 2)) / (web + fl);
|
|
1083
|
+
return bw * h ** 3 / 12 + web * (h / 2 - yc) ** 2 + Math.max(0, bf - bw) * t ** 3 / 12 + fl * (h - t / 2 - yc) ** 2;
|
|
1084
|
+
};
|
|
1085
|
+
var shapeAt = (a, z) => {
|
|
1086
|
+
const { z: zs, phi } = a.shape;
|
|
1087
|
+
if (z <= zs[0]) return phi[0];
|
|
1088
|
+
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]);
|
|
1089
|
+
return phi[phi.length - 1];
|
|
1090
|
+
};
|
|
1091
|
+
var analyzeWind = (parts, c, ground) => {
|
|
1092
|
+
const [dx, dz] = c.direction;
|
|
1093
|
+
const along = (p) => {
|
|
1094
|
+
const cx = (p.min[0] + p.max[0]) / 2, cz = (p.min[2] + p.max[2]) / 2;
|
|
1095
|
+
const hx = (p.max[0] - p.min[0]) / 2, hz = (p.max[2] - p.min[2]) / 2;
|
|
1096
|
+
return {
|
|
1097
|
+
u: cx * dx + cz * dz,
|
|
1098
|
+
v: -cx * dz + cz * dx,
|
|
1099
|
+
du: 2 * (Math.abs(dx) * hx + Math.abs(dz) * hz),
|
|
1100
|
+
dv: 2 * (Math.abs(dz) * hx + Math.abs(dx) * hz)
|
|
1101
|
+
};
|
|
1102
|
+
};
|
|
1103
|
+
const g = parts.map(along);
|
|
1104
|
+
let topY = ground;
|
|
1105
|
+
let resists = false;
|
|
1106
|
+
for (const p of parts) {
|
|
1107
|
+
if (p.kind === "foundation") continue;
|
|
1108
|
+
topY = Math.max(topY, p.max[1]);
|
|
1109
|
+
if ((p.kind === "wall" || p.kind === "column") && p.elasticPa) resists = true;
|
|
1110
|
+
}
|
|
1111
|
+
const height = topY - ground;
|
|
1112
|
+
if (!(height > 0) || !resists) return null;
|
|
1113
|
+
const n = Math.max(1, Math.ceil(height / c.bandHeight - 1e-9));
|
|
1114
|
+
const bands = Array.from({ length: n }, (_, j) => Math.min(c.bandHeight, height - j * c.bandHeight));
|
|
1115
|
+
const z0 = Array.from({ length: n }, (_, j) => j * c.bandHeight);
|
|
1116
|
+
const mid = bands.map((h, j) => z0[j] + h / 2);
|
|
1117
|
+
const share = (p, j) => {
|
|
1118
|
+
const a = Math.max(p.min[1] - ground, z0[j]), b = Math.min(p.max[1] - ground, z0[j] + bands[j]);
|
|
1119
|
+
return Math.max(0, b - a);
|
|
1120
|
+
};
|
|
1121
|
+
const vmin = new Array(n).fill(Infinity), vmax = new Array(n).fill(-Infinity);
|
|
1122
|
+
const mass = new Array(n).fill(0);
|
|
1123
|
+
let weight = 0, wu = 0;
|
|
1124
|
+
let footEnd = -Infinity;
|
|
1125
|
+
let anyFoundation = false;
|
|
1126
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1127
|
+
const p = parts[i];
|
|
1128
|
+
weight += p.mass * GRAVITY;
|
|
1129
|
+
wu += p.mass * g[i].u;
|
|
1130
|
+
if (p.kind === "foundation") {
|
|
1131
|
+
anyFoundation = true;
|
|
1132
|
+
footEnd = Math.max(footEnd, g[i].u + g[i].du / 2);
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
const full = Math.max(1e-6, p.max[1] - p.min[1]);
|
|
1136
|
+
for (let j = 0; j < n; j++) {
|
|
1137
|
+
const ov = share(p, j);
|
|
1138
|
+
if (ov <= 0) continue;
|
|
1139
|
+
vmin[j] = Math.min(vmin[j], g[i].v - g[i].dv / 2);
|
|
1140
|
+
vmax[j] = Math.max(vmax[j], g[i].v + g[i].dv / 2);
|
|
1141
|
+
if (!p.damper) mass[j] += p.mass * ov / full;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (!anyFoundation) {
|
|
1145
|
+
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);
|
|
1146
|
+
}
|
|
1147
|
+
const totalMass = weight / GRAVITY;
|
|
1148
|
+
const uCg = totalMass > 0 ? wu / totalMass : 0;
|
|
1149
|
+
const width = vmax.map((hi, j) => hi > vmin[j] ? hi - vmin[j] : 0);
|
|
1150
|
+
const widest = Math.max(...width);
|
|
1151
|
+
const cls = c.cls === "auto" ? windClass(Math.max(height, widest)) : c.cls;
|
|
1152
|
+
const k0 = c.speed * c.topography * c.statistical;
|
|
1153
|
+
const pressure = mid.map((z2) => windPressure(k0 * heightFactor(z2, c.terrain, cls, { gustFactor: c.gustFactor, minHeight: c.minHeight }), c.airDensity));
|
|
1154
|
+
const meanPressure = mid.map((z2) => windPressure(k0 * meanHeightFactor(z2, c.terrain, cls, { meanRatio: c.meanRatio, minHeight: c.minHeight }), c.airDensity));
|
|
1155
|
+
const forces = bands.map((h, j) => pressure[j] * c.drag * width[j] * h);
|
|
1156
|
+
const meanForces = bands.map((h, j) => meanPressure[j] * c.drag * width[j] * h);
|
|
1157
|
+
const V = shearNodes(forces);
|
|
1158
|
+
const M = momentNodes(bands, V);
|
|
1159
|
+
const ea = new Array(n).fill(0), eau = new Array(n).fill(0);
|
|
1160
|
+
const vertical = [];
|
|
1161
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1162
|
+
const p = parts[i];
|
|
1163
|
+
if (p.kind !== "wall" && !(p.kind === "column" && c.chord) || !p.elasticPa) continue;
|
|
1164
|
+
vertical.push(i);
|
|
1165
|
+
const area = g[i].du * g[i].dv;
|
|
1166
|
+
const e = p.elasticPa * c.stiffnessFactor;
|
|
1167
|
+
for (let j = 0; j < n; j++) {
|
|
1168
|
+
const w = share(p, j) / bands[j];
|
|
1169
|
+
if (w <= 0) continue;
|
|
1170
|
+
ea[j] += e * area * w;
|
|
1171
|
+
eau[j] += e * area * g[i].u * w;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
const centre = ea.map((v, j) => v > 0 ? eau[j] / v : 0);
|
|
1175
|
+
const ei = new Array(n).fill(0);
|
|
1176
|
+
const ga = new Array(n).fill(0);
|
|
1177
|
+
for (const i of vertical) {
|
|
1178
|
+
const p = parts[i];
|
|
1179
|
+
const e = p.elasticPa * c.stiffnessFactor;
|
|
1180
|
+
for (let j = 0; j < n; j++) {
|
|
1181
|
+
const w = share(p, j) / bands[j];
|
|
1182
|
+
if (w <= 0) continue;
|
|
1183
|
+
ei[j] += e * (g[i].du ** 3 * g[i].dv / 12 + g[i].du * g[i].dv * (g[i].u - centre[j]) ** 2) * w;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1187
|
+
const p = parts[i];
|
|
1188
|
+
if (p.kind !== "column" || !p.elasticPa) continue;
|
|
1189
|
+
const h = Math.max(1e-6, p.max[1] - p.min[1]);
|
|
1190
|
+
const kc = p.elasticPa * c.stiffnessFactor * (g[i].du ** 3 * g[i].dv) / 12 / h;
|
|
1191
|
+
let beams = 0;
|
|
1192
|
+
for (const b of p.beams ?? []) {
|
|
1193
|
+
const q = parts[b];
|
|
1194
|
+
if (!q?.elasticPa) continue;
|
|
1195
|
+
const hx = q.max[0] - q.min[0], hz = q.max[2] - q.min[2];
|
|
1196
|
+
const long = Math.max(hx, hz), short = Math.min(hx, hz);
|
|
1197
|
+
const cos = hx >= hz ? Math.abs(dx) : Math.abs(dz);
|
|
1198
|
+
const depth = q.max[1] - q.min[1];
|
|
1199
|
+
const inertia = tBeamInertia(short, depth, short + 2 * c.flange * long, q.slab ?? 0);
|
|
1200
|
+
beams += cos * cos * q.elasticPa * c.beamFactor * inertia / Math.max(long, 1e-6);
|
|
1201
|
+
}
|
|
1202
|
+
const k = beams / kc;
|
|
1203
|
+
const base = p.min[1] - ground < 0.05;
|
|
1204
|
+
const a = base ? (0.5 + k) / (2 + k) : k / (2 + k);
|
|
1205
|
+
const stiffness = a * 12 * (p.elasticPa * c.stiffnessFactor) * (g[i].du ** 3 * g[i].dv / 12) / (h * h);
|
|
1206
|
+
for (let j = 0; j < n; j++) ga[j] += stiffness * share(p, j) / bands[j];
|
|
1207
|
+
}
|
|
1208
|
+
let last = -1;
|
|
1209
|
+
for (let j = 0; j < n; j++) if (ei[j] > 0 || ga[j] > 0) last = j;
|
|
1210
|
+
for (let j = last + 1; j < n && last >= 0; j++) {
|
|
1211
|
+
ei[j] = ei[last] * RIGID;
|
|
1212
|
+
ga[j] = ga[last] * RIGID;
|
|
1213
|
+
}
|
|
1214
|
+
const tiny = 1;
|
|
1215
|
+
const eiS = ei.map((v) => Math.max(v, tiny));
|
|
1216
|
+
const gaS = ga.map((v) => Math.max(v, tiny));
|
|
1217
|
+
const sol = solveWallFrame(bands, eiS, gaS, V);
|
|
1218
|
+
const deflection = sol.w[n];
|
|
1219
|
+
const lean = solveWallFrame(bands, eiS, gaS, shearNodes(meanForces)).w[n];
|
|
1220
|
+
const kappa = bands.map((h, j) => (sol.psi[j + 1] - sol.psi[j]) / h);
|
|
1221
|
+
const Mmid = bands.map((h, j) => M[j + 1] + h / 2 * (V[j + 1] + (V[j] + V[j + 1]) / 2) / 2);
|
|
1222
|
+
const Mwall = ei.map((v, j) => Math.min(Mmid[j], Math.max(0, v * kappa[j])));
|
|
1223
|
+
const axial = new Array(parts.length).fill(0);
|
|
1224
|
+
for (const i of vertical) {
|
|
1225
|
+
const p = parts[i];
|
|
1226
|
+
let sum = 0, tot = 0;
|
|
1227
|
+
for (let j = 0; j < n; j++) {
|
|
1228
|
+
const w = share(p, j);
|
|
1229
|
+
if (w <= 0) continue;
|
|
1230
|
+
sum += w * p.elasticPa * c.stiffnessFactor * kappa[j] * g[i].du * g[i].dv * (g[i].u - centre[j]);
|
|
1231
|
+
tot += w;
|
|
1232
|
+
}
|
|
1233
|
+
axial[i] = tot > 0 ? sum / tot : 0;
|
|
1234
|
+
}
|
|
1235
|
+
if (!c.chord) {
|
|
1236
|
+
const colA = new Array(n).fill(0), colAu = new Array(n).fill(0);
|
|
1237
|
+
const cols = [];
|
|
1238
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1239
|
+
if (parts[i].kind !== "column") continue;
|
|
1240
|
+
cols.push(i);
|
|
1241
|
+
for (let j = 0; j < n; j++) {
|
|
1242
|
+
const w = share(parts[i], j) / bands[j];
|
|
1243
|
+
colA[j] += g[i].du * g[i].dv * w;
|
|
1244
|
+
colAu[j] += g[i].du * g[i].dv * g[i].u * w;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const colC = colA.map((a, j) => a > 0 ? colAu[j] / a : 0);
|
|
1248
|
+
const colI = new Array(n).fill(0);
|
|
1249
|
+
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];
|
|
1250
|
+
for (const i of cols) {
|
|
1251
|
+
let sum = 0, tot = 0;
|
|
1252
|
+
for (let j = 0; j < n; j++) {
|
|
1253
|
+
const w = share(parts[i], j);
|
|
1254
|
+
if (w <= 0 || colI[j] <= 0) continue;
|
|
1255
|
+
sum += w * (Math.max(0, Mmid[j] - Mwall[j]) * g[i].du * g[i].dv * (g[i].u - colC[j])) / colI[j];
|
|
1256
|
+
tot += w;
|
|
1257
|
+
}
|
|
1258
|
+
axial[i] = tot > 0 ? sum / tot : 0;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const arm = footEnd - uCg;
|
|
1262
|
+
const overturn = arm > 0 ? c.loadFactor * M[0] / (weight * arm) : Infinity;
|
|
1263
|
+
const mode = solveWallFrame(bands, eiS, gaS, shearNodes(mass));
|
|
1264
|
+
const top = mode.w[n];
|
|
1265
|
+
const um = bands.map((_, j) => (mode.w[j] + mode.w[j + 1]) / 2);
|
|
1266
|
+
let mu = 0, muu = 0;
|
|
1267
|
+
mass.forEach((m, j) => {
|
|
1268
|
+
mu += m * um[j];
|
|
1269
|
+
muu += m * um[j] * um[j];
|
|
1270
|
+
});
|
|
1271
|
+
const omega = muu > 0 ? Math.sqrt(mu / muu) : 0;
|
|
1272
|
+
const phi = um.map((u) => top > 0 ? u / top : 0);
|
|
1273
|
+
const modalMass = mass.reduce((s, m, j) => s + m * phi[j] * phi[j], 0);
|
|
1274
|
+
const modalForce = meanForces.reduce((s, f, j) => s + f * phi[j], 0);
|
|
1275
|
+
const z = [0, ...bands.map((_, j) => z0[j] + bands[j])];
|
|
1276
|
+
const intensity = turbulenceIntensity(Math.max(0.6 * height, c.minHeight), c.terrain.z0);
|
|
1277
|
+
return {
|
|
1278
|
+
height,
|
|
1279
|
+
cls,
|
|
1280
|
+
width: widest,
|
|
1281
|
+
bands,
|
|
1282
|
+
mid,
|
|
1283
|
+
forces,
|
|
1284
|
+
meanForces,
|
|
1285
|
+
pressure: pressure[n - 1],
|
|
1286
|
+
shear: V[0],
|
|
1287
|
+
moment: M[0],
|
|
1288
|
+
deflection,
|
|
1289
|
+
drift: deflection / height,
|
|
1290
|
+
lean,
|
|
1291
|
+
weight,
|
|
1292
|
+
overturn,
|
|
1293
|
+
wallShare: M[0] > 0 ? Mwall[0] / M[0] : 0,
|
|
1294
|
+
ei,
|
|
1295
|
+
ga,
|
|
1296
|
+
frequency: omega / (2 * Math.PI),
|
|
1297
|
+
omega,
|
|
1298
|
+
modalMass,
|
|
1299
|
+
modalStiffness: omega * omega * modalMass,
|
|
1300
|
+
shape: { z, phi: mode.w.map((w) => top > 0 ? w / top : 0) },
|
|
1301
|
+
modalForce,
|
|
1302
|
+
intensity,
|
|
1303
|
+
axial
|
|
1304
|
+
};
|
|
1305
|
+
};
|
|
1306
|
+
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)) };
|
|
1307
|
+
var damperAmplification = (r, mu, g, damper, structure = 0) => {
|
|
1308
|
+
const mul = (a, b) => [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]];
|
|
1309
|
+
const div = (a, b) => {
|
|
1310
|
+
const d = b[0] * b[0] + b[1] * b[1];
|
|
1311
|
+
return [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d];
|
|
1312
|
+
};
|
|
1313
|
+
const A = [g * g, 2 * damper * g * r];
|
|
1314
|
+
const main = [1 - r * r, 2 * structure * r];
|
|
1315
|
+
const coupling = mu > 0 ? mul([mu * r * r, 0], div(A, [A[0] - r * r, A[1]])) : [0, 0];
|
|
1316
|
+
const total = [main[0] - coupling[0], main[1] - coupling[1]];
|
|
1317
|
+
return 1 / Math.hypot(total[0], total[1]);
|
|
1318
|
+
};
|
|
1319
|
+
var makeGust = (v10, o) => {
|
|
1320
|
+
const rng = createRng(o.seed);
|
|
1321
|
+
const n = Math.max(1, Math.floor(o.components));
|
|
1322
|
+
const df = o.maxHz / n;
|
|
1323
|
+
const freq = Array.from({ length: n }, (_, k) => (k + 0.5) * df);
|
|
1324
|
+
const weight = freq.map((f) => {
|
|
1325
|
+
const x = o.scale * f / Math.max(v10, 1e-6);
|
|
1326
|
+
return 4 * x * x / (1 + x * x) ** (4 / 3) / f * df;
|
|
1327
|
+
});
|
|
1328
|
+
const sum = weight.reduce((s, w) => s + w, 0);
|
|
1329
|
+
return { freq, amp: weight.map((w) => Math.sqrt(2 * w / sum)), phase: freq.map(() => rng.next() * 2 * Math.PI) };
|
|
1330
|
+
};
|
|
1331
|
+
var gustAt = (gust, t) => {
|
|
1332
|
+
let s = 0;
|
|
1333
|
+
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]);
|
|
1334
|
+
return s;
|
|
1335
|
+
};
|
|
1336
|
+
var stepSway = (s, sys, t, dt, force, maxStep = Infinity) => {
|
|
1337
|
+
const steps = Math.max(1, Math.ceil(dt / maxStep - 1e-9));
|
|
1338
|
+
const h = dt / steps;
|
|
1339
|
+
const d = sys.damper;
|
|
1340
|
+
const f = (x, v, y, w, tt) => {
|
|
1341
|
+
const fd = d ? d.k * (y - x) + d.c * (w - v) : 0;
|
|
1342
|
+
return [v, (force(tt) - sys.K * x - sys.C * v + fd) / sys.M, d ? w : 0, d ? -fd / d.m : 0];
|
|
1343
|
+
};
|
|
1344
|
+
for (let i = 0; i < steps; i++) {
|
|
1345
|
+
const tt = t + i * h;
|
|
1346
|
+
const k1 = f(s.x, s.v, s.y, s.w, tt);
|
|
1347
|
+
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);
|
|
1348
|
+
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);
|
|
1349
|
+
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);
|
|
1350
|
+
s.x += h / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]);
|
|
1351
|
+
s.v += h / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]);
|
|
1352
|
+
s.y += h / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]);
|
|
1353
|
+
s.w += h / 6 * (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]);
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
var tensionCapacity = (dims, o) => {
|
|
1357
|
+
const section = dims[0] * dims[2];
|
|
1358
|
+
if (o.reinforcement) {
|
|
1359
|
+
const bars = verticalBarCount(dims, thinAxisOf(dims), o.reinforcement.spacing);
|
|
1360
|
+
return bars * Math.PI * (o.reinforcement.diameter / 2) ** 2 * o.yieldMPa * 1e6;
|
|
1361
|
+
}
|
|
1362
|
+
if (o.ratio) return o.ratio * section * o.yieldMPa * 1e6;
|
|
1363
|
+
return (o.tensileMPa ?? 0) * 1e6 * section;
|
|
1364
|
+
};
|
|
1365
|
+
|
|
569
1366
|
// src/data/materials.json
|
|
570
1367
|
var materials_default = [
|
|
571
1368
|
{
|
|
@@ -573,6 +1370,8 @@ var materials_default = [
|
|
|
573
1370
|
density: 7850,
|
|
574
1371
|
hpPerKg: 400,
|
|
575
1372
|
penetration: 110,
|
|
1373
|
+
penetrationClass: "steel",
|
|
1374
|
+
penetrationScale: 1.3,
|
|
576
1375
|
ductility: 0.9,
|
|
577
1376
|
fracture: "none",
|
|
578
1377
|
damageScale: {
|
|
@@ -602,6 +1401,8 @@ var materials_default = [
|
|
|
602
1401
|
density: 7200,
|
|
603
1402
|
hpPerKg: 150,
|
|
604
1403
|
penetration: 160,
|
|
1404
|
+
penetrationClass: "steel",
|
|
1405
|
+
penetrationScale: 1,
|
|
605
1406
|
ductility: 0.2,
|
|
606
1407
|
fracture: "snap",
|
|
607
1408
|
fragmentSize: [
|
|
@@ -635,6 +1436,8 @@ var materials_default = [
|
|
|
635
1436
|
density: 2700,
|
|
636
1437
|
hpPerKg: 250,
|
|
637
1438
|
penetration: 60,
|
|
1439
|
+
penetrationClass: "steel",
|
|
1440
|
+
penetrationScale: 3,
|
|
638
1441
|
ductility: 0.85,
|
|
639
1442
|
fracture: "none",
|
|
640
1443
|
damageScale: {
|
|
@@ -663,6 +1466,8 @@ var materials_default = [
|
|
|
663
1466
|
density: 8960,
|
|
664
1467
|
hpPerKg: 300,
|
|
665
1468
|
penetration: 70,
|
|
1469
|
+
penetrationClass: "steel",
|
|
1470
|
+
penetrationScale: 1.5,
|
|
666
1471
|
ductility: 0.95,
|
|
667
1472
|
fracture: "none",
|
|
668
1473
|
damageScale: {
|
|
@@ -691,6 +1496,8 @@ var materials_default = [
|
|
|
691
1496
|
density: 500,
|
|
692
1497
|
hpPerKg: 90,
|
|
693
1498
|
penetration: 9,
|
|
1499
|
+
penetrationClass: "wood",
|
|
1500
|
+
penetrationScale: 1.5,
|
|
694
1501
|
ductility: 0.35,
|
|
695
1502
|
fracture: "splinter",
|
|
696
1503
|
fragmentSize: [
|
|
@@ -727,6 +1534,8 @@ var materials_default = [
|
|
|
727
1534
|
density: 750,
|
|
728
1535
|
hpPerKg: 140,
|
|
729
1536
|
penetration: 14,
|
|
1537
|
+
penetrationClass: "wood",
|
|
1538
|
+
penetrationScale: 1,
|
|
730
1539
|
ductility: 0.3,
|
|
731
1540
|
fracture: "splinter",
|
|
732
1541
|
fragmentSize: [
|
|
@@ -762,6 +1571,8 @@ var materials_default = [
|
|
|
762
1571
|
density: 600,
|
|
763
1572
|
hpPerKg: 70,
|
|
764
1573
|
penetration: 7,
|
|
1574
|
+
penetrationClass: "wood",
|
|
1575
|
+
penetrationScale: 2,
|
|
765
1576
|
ductility: 0.3,
|
|
766
1577
|
fracture: "snap",
|
|
767
1578
|
fragmentSize: [
|
|
@@ -797,6 +1608,8 @@ var materials_default = [
|
|
|
797
1608
|
density: 750,
|
|
798
1609
|
hpPerKg: 45,
|
|
799
1610
|
penetration: 6,
|
|
1611
|
+
penetrationClass: "wood",
|
|
1612
|
+
penetrationScale: 2.5,
|
|
800
1613
|
ductility: 0.15,
|
|
801
1614
|
fracture: "snap",
|
|
802
1615
|
fragmentSize: [
|
|
@@ -925,6 +1738,8 @@ var materials_default = [
|
|
|
925
1738
|
density: 2300,
|
|
926
1739
|
hpPerKg: 14,
|
|
927
1740
|
penetration: 18,
|
|
1741
|
+
penetrationClass: "concrete",
|
|
1742
|
+
penetrationScale: 1,
|
|
928
1743
|
ductility: 0.03,
|
|
929
1744
|
fracture: "crumble",
|
|
930
1745
|
fragmentSize: [
|
|
@@ -944,13 +1759,20 @@ var materials_default = [
|
|
|
944
1759
|
"masonry",
|
|
945
1760
|
"brittle"
|
|
946
1761
|
],
|
|
947
|
-
compressive: 444e3
|
|
1762
|
+
compressive: 444e3,
|
|
1763
|
+
strength: {
|
|
1764
|
+
compressiveMPa: 25,
|
|
1765
|
+
tensileMPa: 2.56,
|
|
1766
|
+
elasticGPa: 28
|
|
1767
|
+
}
|
|
948
1768
|
},
|
|
949
1769
|
{
|
|
950
1770
|
id: "brick",
|
|
951
1771
|
density: 1900,
|
|
952
1772
|
hpPerKg: 12,
|
|
953
1773
|
penetration: 14,
|
|
1774
|
+
penetrationClass: "brick",
|
|
1775
|
+
penetrationScale: 1,
|
|
954
1776
|
ductility: 0.05,
|
|
955
1777
|
fracture: "crumble",
|
|
956
1778
|
fragmentSize: [
|
|
@@ -967,13 +1789,20 @@ var materials_default = [
|
|
|
967
1789
|
"masonry",
|
|
968
1790
|
"brittle"
|
|
969
1791
|
],
|
|
970
|
-
compressive: 4e4
|
|
1792
|
+
compressive: 4e4,
|
|
1793
|
+
strength: {
|
|
1794
|
+
compressiveMPa: 2.35,
|
|
1795
|
+
tensileMPa: 0.1,
|
|
1796
|
+
elasticGPa: 2.35
|
|
1797
|
+
}
|
|
971
1798
|
},
|
|
972
1799
|
{
|
|
973
1800
|
id: "plaster",
|
|
974
1801
|
density: 850,
|
|
975
1802
|
hpPerKg: 18,
|
|
976
1803
|
penetration: 3,
|
|
1804
|
+
penetrationClass: "brick",
|
|
1805
|
+
penetrationScale: 3,
|
|
977
1806
|
ductility: 0.05,
|
|
978
1807
|
fracture: "crumble",
|
|
979
1808
|
fragmentSize: [
|
|
@@ -997,6 +1826,8 @@ var materials_default = [
|
|
|
997
1826
|
density: 2700,
|
|
998
1827
|
hpPerKg: 25,
|
|
999
1828
|
penetration: 25,
|
|
1829
|
+
penetrationClass: "concrete",
|
|
1830
|
+
penetrationScale: 0.9,
|
|
1000
1831
|
ductility: 0.03,
|
|
1001
1832
|
fracture: "crumble",
|
|
1002
1833
|
fragmentSize: [
|
|
@@ -1260,6 +2091,8 @@ var materials_default = [
|
|
|
1260
2091
|
density: 650,
|
|
1261
2092
|
hpPerKg: 5,
|
|
1262
2093
|
penetration: 10,
|
|
2094
|
+
penetrationClass: "wood",
|
|
2095
|
+
penetrationScale: 1.2,
|
|
1263
2096
|
ductility: 0.5,
|
|
1264
2097
|
fracture: "splinter",
|
|
1265
2098
|
fragmentSize: [
|
|
@@ -1561,13 +2394,26 @@ var inferJoints = (parts, tolerance = 0.01, materials = MATERIAL_MAP) => {
|
|
|
1561
2394
|
}
|
|
1562
2395
|
return out;
|
|
1563
2396
|
};
|
|
1564
|
-
var partCapacity = (part, material) => {
|
|
2397
|
+
var partCapacity = (part, material, options = {}) => {
|
|
1565
2398
|
if (part.structure?.capacity !== void 0) return part.structure.capacity;
|
|
1566
2399
|
const d = shapeDims(part.shape).sort((a, b) => a - b);
|
|
1567
2400
|
const role = part.structure?.role;
|
|
1568
2401
|
const sh = shapeDims(part.shape);
|
|
1569
2402
|
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];
|
|
1570
|
-
|
|
2403
|
+
const foundation = role === "foundation" ? 10 : 1;
|
|
2404
|
+
const strength = material.strength;
|
|
2405
|
+
if (!strength) return Math.max(1, section * (material.compressive ?? 2e4) * foundation);
|
|
2406
|
+
const fc = strength.compressiveMPa * 1e6;
|
|
2407
|
+
let newtons = fc * section;
|
|
2408
|
+
const upright = role === "wall" || role === "partition" || role === "column";
|
|
2409
|
+
if (upright && isSlender(part, material)) newtons *= slendernessFactor(sh[1], Math.min(sh[0], sh[2]), options.masonry);
|
|
2410
|
+
else if (role === "column" && part.reinforcement) {
|
|
2411
|
+
const m = resolveMasonry(options.masonry);
|
|
2412
|
+
const bars = rebarBars(sh, thinAxisOf(sh), part.reinforcement.spacing).length;
|
|
2413
|
+
const steel = bars * Math.PI * (part.reinforcement.diameter / 2) ** 2;
|
|
2414
|
+
newtons = m.concreteFactor * fc * section + steel * resolveFlexure(options.flexure).rebarYieldMPa * 1e6;
|
|
2415
|
+
}
|
|
2416
|
+
return Math.max(1, newtons / GRAVITY * foundation);
|
|
1571
2417
|
};
|
|
1572
2418
|
var fragility = (ductility, thickness) => {
|
|
1573
2419
|
if (ductility >= 0.3) return 1;
|
|
@@ -1575,22 +2421,32 @@ var fragility = (ductility, thickness) => {
|
|
|
1575
2421
|
};
|
|
1576
2422
|
|
|
1577
2423
|
// src/world.ts
|
|
2424
|
+
import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
|
|
2425
|
+
var THIN_SHEET_DISHING = 5e-3;
|
|
1578
2426
|
var DMG_PER_J = 0.02;
|
|
1579
2427
|
var BLAST_COUPLING = 0.1;
|
|
1580
2428
|
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1581
2429
|
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1582
2430
|
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1583
2431
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
2432
|
+
var VENT_DIAMETER = 8e-3;
|
|
2433
|
+
var FLAME_LIGHTS_LEAK_C = 470;
|
|
1584
2434
|
var BREACH_RADIUS = 0.55;
|
|
1585
2435
|
var BREACH_BLAST_FACTOR = 6;
|
|
1586
2436
|
var BREACH_AREA = 0.25;
|
|
1587
|
-
var BREACH_BLAST_REACH =
|
|
2437
|
+
var BREACH_BLAST_REACH = 0.12;
|
|
1588
2438
|
var RESIDUAL_CAPACITY = 0.15;
|
|
2439
|
+
var SUPPORT_TOLERANCE = 0.05;
|
|
1589
2440
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1590
2441
|
var BREACH_GRID_CAP = 4096;
|
|
1591
2442
|
var CUT_SLICE = 1;
|
|
1592
2443
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1593
2444
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
2445
|
+
var penetrationOf = (mat, table) => {
|
|
2446
|
+
if (!table) return void 0;
|
|
2447
|
+
const v = table[mat.penetrationClass ?? mat.id] ?? table[mat.id];
|
|
2448
|
+
return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
|
|
2449
|
+
};
|
|
1594
2450
|
var capacityScale = (p) => {
|
|
1595
2451
|
if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
|
|
1596
2452
|
return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
|
|
@@ -1618,8 +2474,14 @@ var createDestruction = (options = {}) => {
|
|
|
1618
2474
|
const maxHoles = options.maxHoles ?? 32;
|
|
1619
2475
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1620
2476
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
2477
|
+
const structureSafety = options.structure?.safetyFactor ?? 1;
|
|
2478
|
+
const flexure = resolveFlexure(options.structure?.flexure);
|
|
2479
|
+
const masonry = resolveMasonry(options.structure?.masonry);
|
|
2480
|
+
const soilCfg = resolveSoil(options.soil);
|
|
2481
|
+
let windOpts = options.wind;
|
|
2482
|
+
let windCfg = windOpts ? resolveWind(windOpts) : null;
|
|
2483
|
+
const ignitionRules = resolveIgnitionRules(options.ignition);
|
|
1621
2484
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1622
|
-
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1623
2485
|
const rng = createRng(options.seed ?? 1);
|
|
1624
2486
|
const rnd = () => rng.next();
|
|
1625
2487
|
let time = 0;
|
|
@@ -1646,7 +2508,7 @@ var createDestruction = (options = {}) => {
|
|
|
1646
2508
|
const pendingCollapse = /* @__PURE__ */ new Map();
|
|
1647
2509
|
let runDepth = 0;
|
|
1648
2510
|
const drainBlasts = () => {
|
|
1649
|
-
for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source);
|
|
2511
|
+
for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source, void 0, void 0, blast.lethalRadius);
|
|
1650
2512
|
};
|
|
1651
2513
|
const run = (fn) => {
|
|
1652
2514
|
const prev = cur;
|
|
@@ -1716,7 +2578,8 @@ var createDestruction = (options = {}) => {
|
|
|
1716
2578
|
collapsed: new Set(carry?.collapsed ?? []),
|
|
1717
2579
|
unsup: [],
|
|
1718
2580
|
anyAnchor: false,
|
|
1719
|
-
order: ++orderCounter
|
|
2581
|
+
order: ++orderCounter,
|
|
2582
|
+
base: { pos: [pos[0], pos[1], pos[2]], rot: [rot[0], rot[1], rot[2], rot[3]] }
|
|
1720
2583
|
};
|
|
1721
2584
|
for (const ps of spec.parts) {
|
|
1722
2585
|
if (o.byId.has(ps.id)) throw new Error(`duplicate part id "${ps.id}"`);
|
|
@@ -1768,10 +2631,17 @@ var createDestruction = (options = {}) => {
|
|
|
1768
2631
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
1769
2632
|
structural: !!ps.structure && ps.structure.role !== "none",
|
|
1770
2633
|
anchored: !!ps.anchored || ps.structure?.role === "foundation",
|
|
1771
|
-
cap: ps.structure ? partCapacity(ps, mat) : 0,
|
|
2634
|
+
cap: ps.structure ? partCapacity(ps, mat, { masonry, flexure }) : 0,
|
|
2635
|
+
orphan: false,
|
|
1772
2636
|
staged,
|
|
1773
2637
|
thin: thinAxisOf(shapeDims(ps.shape)),
|
|
1774
|
-
layers
|
|
2638
|
+
layers,
|
|
2639
|
+
span: 0,
|
|
2640
|
+
spanKey: "",
|
|
2641
|
+
restMoment: 0,
|
|
2642
|
+
bendLoad: 0,
|
|
2643
|
+
snap: false,
|
|
2644
|
+
...ps.shape.kind === "box" && isFlexural(ps, mat, flexure) ? { bend: bendGeometry(ps) } : {}
|
|
1775
2645
|
};
|
|
1776
2646
|
if (p.structural) o.hasStructure = true;
|
|
1777
2647
|
if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
|
|
@@ -1805,7 +2675,14 @@ var createDestruction = (options = {}) => {
|
|
|
1805
2675
|
updateBound(o);
|
|
1806
2676
|
o.anyAnchor = o.parts.some((p) => p.anchored);
|
|
1807
2677
|
o.unsup = o.parts.map(() => false);
|
|
2678
|
+
if (windCfg && o.hasStructure && spec.wind !== false) o.wind = newWind(o, carry?.wind);
|
|
2679
|
+
const ground = spec.soil ?? soilCfg.site;
|
|
2680
|
+
if (ground !== void 0 && o.parts.some((p) => p.spec.structure?.role === "foundation")) initSoil(o, profileOf(ground), carry?.soil, carry?.soilSets);
|
|
2681
|
+
o.seeding = true;
|
|
1808
2682
|
solveStructure(o);
|
|
2683
|
+
o.seeding = false;
|
|
2684
|
+
if (o.soil && !carry?.soil) settleSoil(o, 0);
|
|
2685
|
+
if (o.soil) o.soil.dirty = false;
|
|
1809
2686
|
recomputeGroups(o);
|
|
1810
2687
|
o.st.destroyed = o.parts.length > 0 && o.parts.every(isLost);
|
|
1811
2688
|
o.active = o.maxStress > 0.8 || o.parts.some((p) => p.st.burning || p.st.temperature > ambient + 0.5 || (p.st.liters ?? 0) > 0);
|
|
@@ -1857,12 +2734,126 @@ var createDestruction = (options = {}) => {
|
|
|
1857
2734
|
for (const p of o.parts) if (isLost(p)) p.st.group = -1;
|
|
1858
2735
|
list.forEach((g, gi) => g.ix.forEach((i) => o.parts[i].st.group = gi));
|
|
1859
2736
|
};
|
|
1860
|
-
const restsOn = (j, r) => {
|
|
2737
|
+
const restsOn = (o, j, r) => {
|
|
1861
2738
|
const b = j.bears;
|
|
1862
2739
|
if (!b) return -1;
|
|
1863
|
-
|
|
1864
|
-
if (
|
|
1865
|
-
return
|
|
2740
|
+
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;
|
|
2741
|
+
if (k < 0 || j.spec.kind !== "tie") return k;
|
|
2742
|
+
return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
|
|
2743
|
+
};
|
|
2744
|
+
const contactBox = (p, support) => {
|
|
2745
|
+
const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
|
|
2746
|
+
const lo = [0, 0, 0], hi = [0, 0, 0];
|
|
2747
|
+
for (let k = 0; k < 3; k++) {
|
|
2748
|
+
const a = Math.max(A.min[k], B.min[k]) - SUPPORT_TOLERANCE, b = Math.min(A.max[k], B.max[k]) + SUPPORT_TOLERANCE;
|
|
2749
|
+
lo[k] = Math.min(a, b);
|
|
2750
|
+
hi[k] = Math.max(a, b);
|
|
2751
|
+
}
|
|
2752
|
+
const out = [[Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity]];
|
|
2753
|
+
for (let c = 0; c < 8; c++) {
|
|
2754
|
+
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]]);
|
|
2755
|
+
for (let k = 0; k < 3; k++) {
|
|
2756
|
+
out[0][k] = Math.min(out[0][k], l[k]);
|
|
2757
|
+
out[1][k] = Math.max(out[1][k], l[k]);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
return out;
|
|
2761
|
+
};
|
|
2762
|
+
const contactCentre = (p, above) => {
|
|
2763
|
+
const [lo, hi] = contactBox(p, above);
|
|
2764
|
+
return toWorld(p.pos, p.q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
|
|
2765
|
+
};
|
|
2766
|
+
const overSupportAt = (o, p, at, lower) => {
|
|
2767
|
+
const g = p.bend;
|
|
2768
|
+
const local = toLocal(p.pos, p.q, at);
|
|
2769
|
+
const depth = g.gravity >= 0 ? g.dims[g.gravity] : Math.min(...g.depthAxes.map((a) => g.dims[a]));
|
|
2770
|
+
let best = -1;
|
|
2771
|
+
let bestGap = depth;
|
|
2772
|
+
for (const k of lower) {
|
|
2773
|
+
const [a, b] = contactBox(p, o.parts[k]);
|
|
2774
|
+
let gap = 0;
|
|
2775
|
+
for (const ax of g.spanAxes) gap = Math.max(gap, a[ax] - local[ax], local[ax] - b[ax]);
|
|
2776
|
+
if (gap <= bestGap) {
|
|
2777
|
+
best = k;
|
|
2778
|
+
bestGap = gap - 1e-9;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return best;
|
|
2782
|
+
};
|
|
2783
|
+
const sectionOf = (p, axis) => {
|
|
2784
|
+
const g = p.bend;
|
|
2785
|
+
const section = { b: sectionWidth(g, axis), h: g.dims[axis] };
|
|
2786
|
+
const rebar = tensionSteel(p.spec, g.dims, section, flexure);
|
|
2787
|
+
return { section, rebar, ...sectionMoments(p.mat, section, rebar, flexure) };
|
|
2788
|
+
};
|
|
2789
|
+
const restFlexure = (o, p, lower) => {
|
|
2790
|
+
const g = p.bend;
|
|
2791
|
+
const key = lower.join(",");
|
|
2792
|
+
if (key !== p.spanKey) {
|
|
2793
|
+
p.spanKey = key;
|
|
2794
|
+
p.span = spanOf(g, lower.map((k) => contactBox(p, o.parts[k])));
|
|
2795
|
+
}
|
|
2796
|
+
p.restMoment = 0;
|
|
2797
|
+
if (g.gravity < 0 || p.span <= 0) return 0;
|
|
2798
|
+
const sec = sectionOf(p, g.gravity);
|
|
2799
|
+
p.restMoment = spanMoment(lineLoad(g, p.bendLoad) * Math.abs(g.up[g.gravity]), p.span);
|
|
2800
|
+
const holds = (p.st.cracked ? sec.steel : sec.ultimate) * capacityScale(p);
|
|
2801
|
+
return p.restMoment * structureSafety / Math.max(holds, 1);
|
|
2802
|
+
};
|
|
2803
|
+
const bendByBlast = (o, p, point, energy, radius, reach, src) => {
|
|
2804
|
+
const g = p.bend;
|
|
2805
|
+
if (!g || p.span <= 0 || isLost(p) || p.st.stripped || reach <= 0) return;
|
|
2806
|
+
const s = p.mat.strength;
|
|
2807
|
+
if (!s.elasticGPa) return;
|
|
2808
|
+
const { c, q } = partXf(o, p);
|
|
2809
|
+
const near = surfacePoint(p.spec.shape, toLocal(c, q, point));
|
|
2810
|
+
const local = toLocal(c, q, point);
|
|
2811
|
+
const away = vec.normalize(vec.sub(near.point, local), vec.scale(near.normal, -1));
|
|
2812
|
+
let axis = g.depthAxes[0];
|
|
2813
|
+
for (const a of g.depthAxes) if (Math.abs(away[a]) > Math.abs(away[axis])) axis = a;
|
|
2814
|
+
const cos = Math.abs(away[axis]);
|
|
2815
|
+
if (cos < 1e-3) return;
|
|
2816
|
+
const tnt = energy / flexure.tntJoules;
|
|
2817
|
+
const L = p.span;
|
|
2818
|
+
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]];
|
|
2819
|
+
const face = -Math.sign(away[axis]) * g.dims[axis] / 2;
|
|
2820
|
+
let sum = 0;
|
|
2821
|
+
for (const off of offsets) {
|
|
2822
|
+
const sp = [near.point[0], near.point[1], near.point[2]];
|
|
2823
|
+
g.spanAxes.forEach((a, k) => sp[a] = Math.max(-g.dims[a] / 2, Math.min(g.dims[a] / 2, sp[a] + off[k] * L)));
|
|
2824
|
+
sp[axis] = face;
|
|
2825
|
+
const r = vec.distance(toWorld(c, q, sp), point);
|
|
2826
|
+
if (r < radius) sum += blastImpulse(tnt, r, flexure);
|
|
2827
|
+
}
|
|
2828
|
+
const width = sectionWidth(g, axis);
|
|
2829
|
+
const impulse = sum / offsets.length * cos * reach * width * L;
|
|
2830
|
+
const weight = axis === g.gravity ? p.bendLoad : p.st.mass;
|
|
2831
|
+
const kinetic = impulseEnergy(impulse, lineLoad(g, weight) / GRAVITY * L, flexure);
|
|
2832
|
+
const sec = sectionOf(p, axis);
|
|
2833
|
+
const EI = s.elasticGPa * 1e9 * sec.section.b * sec.section.h ** 3 / 12;
|
|
2834
|
+
const along = axis === g.gravity ? -Math.sign(away[axis] * g.up[axis]) : 0;
|
|
2835
|
+
const dynamic = momentOfEnergy(kinetic, L, EI, flexure.loadFactor);
|
|
2836
|
+
const peak = Math.abs(dynamic + along * p.restMoment);
|
|
2837
|
+
if (peak <= sec.cracking) return;
|
|
2838
|
+
if (!p.st.cracked) {
|
|
2839
|
+
p.st.cracked = true;
|
|
2840
|
+
const tension = [near.point[0], near.point[1], near.point[2]];
|
|
2841
|
+
tension[axis] = dynamic + along * p.restMoment >= 0 ? -face : face;
|
|
2842
|
+
const n = [0, 0, 0];
|
|
2843
|
+
n[axis] = Math.sign(tension[axis]) || 1;
|
|
2844
|
+
addCrack(o, p, toWorld(c, q, tension), rotateVec(q, n), sec.section.b, src);
|
|
2845
|
+
}
|
|
2846
|
+
if (!sec.reinforced) {
|
|
2847
|
+
p.snap = true;
|
|
2848
|
+
failPart(o, p, point, away, kinetic, 12, src, "snap");
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
solveStructure(o);
|
|
2852
|
+
if (peak <= sec.steel) return;
|
|
2853
|
+
const rotation = (elasticEnergy(peak, L, EI, flexure.loadFactor) - elasticEnergy(sec.steel, L, EI, flexure.loadFactor)) / (2 * sec.steel);
|
|
2854
|
+
p.rt.hinge = (p.rt.hinge ?? 0) + rotation;
|
|
2855
|
+
markDamaged(p);
|
|
2856
|
+
if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
|
|
1866
2857
|
};
|
|
1867
2858
|
const solveStructure = (o) => {
|
|
1868
2859
|
o.maxStress = 0;
|
|
@@ -1883,23 +2874,41 @@ var createDestruction = (options = {}) => {
|
|
|
1883
2874
|
for (const j of o.byPart.get(parts[s].spec.id)) {
|
|
1884
2875
|
if (j.st.open || !j.bears) continue;
|
|
1885
2876
|
const r = j.ia === s ? j.ib : j.ia;
|
|
1886
|
-
if (dist[r] >= 0 || !ok(r) || restsOn(j, r) !== s) continue;
|
|
2877
|
+
if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
|
|
1887
2878
|
dist[r] = dist[s] + 1;
|
|
1888
2879
|
queue.push(r);
|
|
1889
2880
|
}
|
|
1890
2881
|
}
|
|
1891
2882
|
const incoming = new Array(n).fill(0);
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
const total = parts[i].st.mass + incoming[i];
|
|
1895
|
-
parts[i].st.load = total;
|
|
2883
|
+
const lowerOf = /* @__PURE__ */ new Map();
|
|
2884
|
+
for (const i of queue) {
|
|
1896
2885
|
const lower = [];
|
|
1897
2886
|
for (const j of o.byPart.get(parts[i].spec.id)) {
|
|
1898
2887
|
if (j.st.open || !j.bears) continue;
|
|
1899
|
-
const k = restsOn(j, i);
|
|
2888
|
+
const k = restsOn(o, j, i);
|
|
1900
2889
|
if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
|
|
1901
2890
|
}
|
|
2891
|
+
lowerOf.set(i, lower);
|
|
2892
|
+
}
|
|
2893
|
+
const bending = new Array(n).fill(0);
|
|
2894
|
+
const through = parts.map(() => []);
|
|
2895
|
+
const pass = (k, at, mass) => {
|
|
2896
|
+
const over = overSupportAt(o, parts[k], at, lowerOf.get(k) ?? []);
|
|
2897
|
+
if (over < 0) bending[k] += mass;
|
|
2898
|
+
else through[k].push({ at, mass, over });
|
|
2899
|
+
};
|
|
2900
|
+
for (let h = queue.length - 1; h >= 0; h--) {
|
|
2901
|
+
const i = queue[h];
|
|
2902
|
+
const total = parts[i].st.mass + incoming[i];
|
|
2903
|
+
parts[i].st.load = total;
|
|
2904
|
+
const lower = lowerOf.get(i);
|
|
1902
2905
|
for (const k of lower) incoming[k] += total / lower.length;
|
|
2906
|
+
if (lower.length === 0) continue;
|
|
2907
|
+
const bulk = parts[i].bend ? parts[i].st.mass + bending[i] : total;
|
|
2908
|
+
for (const k of lower) if (parts[k].bend) pass(k, contactCentre(parts[k], parts[i]), bulk / lower.length);
|
|
2909
|
+
if (parts[i].bend) {
|
|
2910
|
+
for (const item of through[i]) if (parts[item.over]?.bend) pass(item.over, item.at, item.mass);
|
|
2911
|
+
}
|
|
1903
2912
|
}
|
|
1904
2913
|
for (let i = 0; i < n; i++) {
|
|
1905
2914
|
const p = parts[i];
|
|
@@ -1908,13 +2917,420 @@ var createDestruction = (options = {}) => {
|
|
|
1908
2917
|
if (dist[i] < 0 || isLost(p)) {
|
|
1909
2918
|
p.st.load = p.st.mass;
|
|
1910
2919
|
p.st.stress = 0;
|
|
1911
|
-
o.
|
|
2920
|
+
if (o.seeding && !p.anchored && !isLost(p) && p.st.status !== "collapsed" && !o.collapsed.has(p.spec.id)) p.orphan = true;
|
|
2921
|
+
o.unsup[i] = o.anyAnchor && !isLost(p) && !p.orphan;
|
|
1912
2922
|
continue;
|
|
1913
2923
|
}
|
|
1914
|
-
p.st.stress = p.st.load / (p.cap * capacityScale(p));
|
|
2924
|
+
p.st.stress = p.st.load * structureSafety / (p.cap * capacityScale(p));
|
|
2925
|
+
if (p.bend) {
|
|
2926
|
+
p.bendLoad = p.st.mass + bending[i];
|
|
2927
|
+
const bent = restFlexure(o, p, lowerOf.get(i) ?? []);
|
|
2928
|
+
p.st.flexure = bent;
|
|
2929
|
+
p.snap = bent > 1 && !p.st.stripped && !sectionOf(p, p.bend.gravity).reinforced;
|
|
2930
|
+
if (p.spec.structure?.capacity === void 0 || bent > p.st.stress) p.st.stress = bent;
|
|
2931
|
+
}
|
|
1915
2932
|
if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
|
|
1916
2933
|
}
|
|
2934
|
+
if (o.wind) applyWind(o);
|
|
1917
2935
|
if (o.maxStress > 0.8) o.active = true;
|
|
2936
|
+
if (o.soil) o.soil.dirty = true;
|
|
2937
|
+
};
|
|
2938
|
+
const newWind = (o, carried) => {
|
|
2939
|
+
const cfg = exposureOf(windCfg, o.spec.wind);
|
|
2940
|
+
const [x = 0, v = 0, y = 0, w = 0, peak = 0] = carried ?? [];
|
|
2941
|
+
return {
|
|
2942
|
+
cfg,
|
|
2943
|
+
gust: { freq: [], amp: [], phase: [] },
|
|
2944
|
+
gustKey: "",
|
|
2945
|
+
analysis: null,
|
|
2946
|
+
parts: [],
|
|
2947
|
+
sys: null,
|
|
2948
|
+
mu: 0,
|
|
2949
|
+
dyn: { x, v, y, w, peak },
|
|
2950
|
+
tension: /* @__PURE__ */ new Map(),
|
|
2951
|
+
emitted: { t: -Infinity, peak: 0, status: "ok" }
|
|
2952
|
+
};
|
|
2953
|
+
};
|
|
2954
|
+
const windDirection = (o, cfg) => {
|
|
2955
|
+
const d = rotateVec(quat.conjugate(o.base.rot), cfg.direction);
|
|
2956
|
+
const h = Math.hypot(d[0], d[2]);
|
|
2957
|
+
return h > 1e-9 ? [d[0] / h, d[2] / h] : [1, 0];
|
|
2958
|
+
};
|
|
2959
|
+
const applyWind = (o) => {
|
|
2960
|
+
const w = o.wind;
|
|
2961
|
+
const cfg = w.cfg = exposureOf(windCfg, o.spec.wind);
|
|
2962
|
+
const terrain = cfg.terrains.get(cfg.terrain);
|
|
2963
|
+
if (!terrain) throw new Error(`unknown wind terrain "${cfg.terrain}"`);
|
|
2964
|
+
const inputs = [];
|
|
2965
|
+
const at = /* @__PURE__ */ new Map();
|
|
2966
|
+
const keep = [];
|
|
2967
|
+
let ground = Infinity, foundationTop = -Infinity;
|
|
2968
|
+
o.parts.forEach((p, i) => {
|
|
2969
|
+
if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[i]) return;
|
|
2970
|
+
const box = aabbOf(p.spec.shape, p.pos, p.q);
|
|
2971
|
+
const role = p.spec.structure?.role;
|
|
2972
|
+
const e = p.mat.strength?.elasticGPa;
|
|
2973
|
+
const kind = role === "wall" ? "wall" : role === "column" ? "column" : role === "beam" ? "beam" : role === "foundation" ? "foundation" : "other";
|
|
2974
|
+
at.set(i, inputs.length);
|
|
2975
|
+
keep.push(i);
|
|
2976
|
+
ground = Math.min(ground, box.min[1]);
|
|
2977
|
+
if (kind === "foundation") foundationTop = Math.max(foundationTop, box.max[1]);
|
|
2978
|
+
inputs.push({ min: box.min, max: box.max, kind, ...e ? { elasticPa: e * 1e9 } : {}, mass: p.st.mass, ...p.spec.damper ? { damper: true } : {} });
|
|
2979
|
+
});
|
|
2980
|
+
keep.forEach((i, k) => {
|
|
2981
|
+
if (inputs[k].kind !== "column") return;
|
|
2982
|
+
const beams = [];
|
|
2983
|
+
for (const j of o.byPart.get(o.parts[i].spec.id)) {
|
|
2984
|
+
if (j.st.open) continue;
|
|
2985
|
+
const b = at.get(j.ia === i ? j.ib : j.ia);
|
|
2986
|
+
if (b !== void 0 && inputs[b].kind === "beam") beams.push(b);
|
|
2987
|
+
}
|
|
2988
|
+
inputs[k].beams = beams;
|
|
2989
|
+
});
|
|
2990
|
+
keep.forEach((i, k) => {
|
|
2991
|
+
if (inputs[k].kind !== "beam") return;
|
|
2992
|
+
for (const j of o.byPart.get(o.parts[i].spec.id)) {
|
|
2993
|
+
if (j.st.open) continue;
|
|
2994
|
+
const q = o.parts[j.ia === i ? j.ib : j.ia];
|
|
2995
|
+
const role = q.spec.structure?.role;
|
|
2996
|
+
if ((role === "slab" || role === "roof") && !isLost(q) && q.spec.shape.kind === "box") {
|
|
2997
|
+
inputs[k].slab = q.dims[1];
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
const c = {
|
|
3003
|
+
speed: cfg.speed,
|
|
3004
|
+
direction: windDirection(o, cfg),
|
|
3005
|
+
terrain,
|
|
3006
|
+
cls: cfg.class,
|
|
3007
|
+
topography: cfg.topography,
|
|
3008
|
+
statistical: cfg.statistical,
|
|
3009
|
+
drag: cfg.drag,
|
|
3010
|
+
damping: cfg.damping,
|
|
3011
|
+
airDensity: cfg.airDensity,
|
|
3012
|
+
minHeight: cfg.minHeight,
|
|
3013
|
+
bandHeight: cfg.bandHeight,
|
|
3014
|
+
stiffnessFactor: cfg.stiffnessFactor,
|
|
3015
|
+
beamFactor: cfg.beamFactor,
|
|
3016
|
+
loadFactor: cfg.loadFactor,
|
|
3017
|
+
chord: cfg.chord,
|
|
3018
|
+
meanRatio: cfg.meanRatio,
|
|
3019
|
+
gustFactor: cfg.gustFactor,
|
|
3020
|
+
flange: cfg.flange
|
|
3021
|
+
};
|
|
3022
|
+
const a = inputs.length && cfg.speed > 0 ? analyzeWind(inputs, c, foundationTop > -Infinity ? foundationTop : ground) : null;
|
|
3023
|
+
w.analysis = a;
|
|
3024
|
+
w.parts = keep;
|
|
3025
|
+
for (const p of o.parts) delete p.st.wind;
|
|
3026
|
+
if (!a) {
|
|
3027
|
+
w.sys = null;
|
|
3028
|
+
delete o.st.wind;
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
keep.forEach((i, k) => {
|
|
3032
|
+
const N = a.axial[k];
|
|
3033
|
+
const p = o.parts[i];
|
|
3034
|
+
if (!N || !p.structural || o.unsup[i]) return;
|
|
3035
|
+
p.st.wind = N;
|
|
3036
|
+
const dead = p.st.load ?? p.st.mass;
|
|
3037
|
+
const force = N * cfg.loadFactor;
|
|
3038
|
+
let stress = 0;
|
|
3039
|
+
if (force >= 0) stress = (dead * structureSafety + force / GRAVITY) / (p.cap * capacityScale(p));
|
|
3040
|
+
else if (dead + force / GRAVITY < 0) {
|
|
3041
|
+
let cap = w.tension.get(i);
|
|
3042
|
+
if (cap === void 0) {
|
|
3043
|
+
const r = p.spec.reinforcement;
|
|
3044
|
+
const ratio = p.spec.structure?.rebar?.ratio;
|
|
3045
|
+
cap = tensionCapacity(p.dims, {
|
|
3046
|
+
...r ? { reinforcement: r } : {},
|
|
3047
|
+
...ratio ? { ratio } : {},
|
|
3048
|
+
yieldMPa: flexure.rebarYieldMPa,
|
|
3049
|
+
...p.mat.strength?.tensileMPa !== void 0 ? { tensileMPa: p.mat.strength.tensileMPa } : {}
|
|
3050
|
+
});
|
|
3051
|
+
w.tension.set(i, cap);
|
|
3052
|
+
}
|
|
3053
|
+
stress = Math.min(10, -(dead + force / GRAVITY) * GRAVITY / Math.max(cap * capacityScale(p), 1));
|
|
3054
|
+
}
|
|
3055
|
+
if (stress > (p.st.stress ?? 0)) p.st.stress = stress;
|
|
3056
|
+
if ((p.st.stress ?? 0) > o.maxStress) o.maxStress = p.st.stress ?? 0;
|
|
3057
|
+
});
|
|
3058
|
+
const key = `${cfg.speed}|${cfg.terrain}|${cfg.topography}|${cfg.statistical}|${cfg.meanRatio}|${cfg.gust.components}|${cfg.gust.maxHz}|${cfg.gust.scale}|${cfg.gust.seed}`;
|
|
3059
|
+
if (key !== w.gustKey) {
|
|
3060
|
+
w.gustKey = key;
|
|
3061
|
+
const v10 = cfg.speed * cfg.topography * cfg.statistical * meanHeightFactor(10, terrain, a.cls, { meanRatio: cfg.meanRatio, minHeight: cfg.minHeight });
|
|
3062
|
+
w.gust = makeGust(v10, cfg.gust);
|
|
3063
|
+
}
|
|
3064
|
+
let md = 0, spec;
|
|
3065
|
+
for (const i of keep) {
|
|
3066
|
+
const p = o.parts[i];
|
|
3067
|
+
if (!p.spec.damper) continue;
|
|
3068
|
+
const phi = shapeAt(a, p.pos[1] - (foundationTop > -Infinity ? foundationTop : ground));
|
|
3069
|
+
md += p.st.mass * phi * phi;
|
|
3070
|
+
spec ?? (spec = p.spec.damper);
|
|
3071
|
+
}
|
|
3072
|
+
const sys = { M: a.modalMass, K: a.modalStiffness, C: 2 * cfg.damping * a.omega * a.modalMass };
|
|
3073
|
+
w.mu = 0;
|
|
3074
|
+
if (md > 0 && a.modalMass > 0) {
|
|
3075
|
+
w.mu = md / a.modalMass;
|
|
3076
|
+
const tuned = damperTuning(w.mu, cfg.tuning);
|
|
3077
|
+
const f = spec?.frequency ?? tuned.frequency * a.frequency;
|
|
3078
|
+
const zeta = spec?.damping ?? tuned.damping;
|
|
3079
|
+
sys.damper = { m: md, k: md * (2 * Math.PI * f) ** 2, c: 2 * zeta * md * 2 * Math.PI * f };
|
|
3080
|
+
}
|
|
3081
|
+
w.sys = a.modalMass > 0 && a.omega > 0 ? sys : null;
|
|
3082
|
+
const st = {
|
|
3083
|
+
speed: cfg.speed,
|
|
3084
|
+
pressure: a.pressure,
|
|
3085
|
+
shear: a.shear,
|
|
3086
|
+
moment: a.moment,
|
|
3087
|
+
deflection: a.deflection,
|
|
3088
|
+
drift: a.drift,
|
|
3089
|
+
limit: cfg.driftLimit,
|
|
3090
|
+
overturn: Math.min(a.overturn, 1e3),
|
|
3091
|
+
frequency: a.frequency,
|
|
3092
|
+
lean: a.lean,
|
|
3093
|
+
sway: w.dyn.x,
|
|
3094
|
+
peak: w.dyn.peak,
|
|
3095
|
+
status: a.overturn > 1 ? "overturn" : a.drift > cfg.driftLimit ? "drift" : "ok"
|
|
3096
|
+
};
|
|
3097
|
+
if (sys.damper) st.damper = { ratio: w.mu, travel: w.dyn.y - w.dyn.x };
|
|
3098
|
+
o.st.wind = st;
|
|
3099
|
+
};
|
|
3100
|
+
const stepWind = (o, dt) => {
|
|
3101
|
+
const w = o.wind;
|
|
3102
|
+
const a = w.analysis;
|
|
3103
|
+
const st = o.st.wind;
|
|
3104
|
+
if (!a || !st || !w.sys) return;
|
|
3105
|
+
const gust = w.gust;
|
|
3106
|
+
const I = a.intensity;
|
|
3107
|
+
const force = (t) => {
|
|
3108
|
+
const n = gustAt(gust, t);
|
|
3109
|
+
return a.modalForce * (2 * I * n + I * I * (n * n - 1));
|
|
3110
|
+
};
|
|
3111
|
+
const wn = Math.max(a.omega, w.sys.damper ? Math.sqrt(w.sys.damper.k / w.sys.damper.m) : 0);
|
|
3112
|
+
stepSway(w.dyn, w.sys, time - dt, dt, force, 2 * Math.PI / wn / 20);
|
|
3113
|
+
const d = w.dyn;
|
|
3114
|
+
d.peak = Math.max(Math.abs(d.x), d.peak * Math.exp(-dt / w.cfg.peakDecay));
|
|
3115
|
+
st.sway = d.x;
|
|
3116
|
+
st.peak = d.peak;
|
|
3117
|
+
if (st.damper) st.damper.travel = d.y - d.x;
|
|
3118
|
+
const e = w.emitted;
|
|
3119
|
+
if (d.peak < e.peak * 0.8) e.peak = d.peak;
|
|
3120
|
+
if (st.status !== e.status || time - e.t >= 0.5 && d.peak > e.peak * 1.05 + 1e-3) {
|
|
3121
|
+
w.emitted = { t: time, peak: d.peak, status: st.status };
|
|
3122
|
+
const dir = windDirection(o, w.cfg);
|
|
3123
|
+
const world2 = rotateVec(o.base.rot, [dir[0], 0, dir[1]]);
|
|
3124
|
+
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 });
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
const isTerrain = (p) => !!p.spec.tags?.includes("terrain");
|
|
3128
|
+
const isFoundation = (p) => p.spec.structure?.role === "foundation" && !isLost(p);
|
|
3129
|
+
const slopeTilt = (a, b) => Math.atan(Math.hypot(a, b));
|
|
3130
|
+
const soilSprings = (o, profile) => {
|
|
3131
|
+
const found = o.parts.filter(isFoundation);
|
|
3132
|
+
const box = (p) => aabbOf(p.spec.shape, p.pos, p.q);
|
|
3133
|
+
const surface = profile.surface ?? Math.max(...found.map((p) => box(p).max[1]));
|
|
3134
|
+
const prints = [];
|
|
3135
|
+
for (const f of found) {
|
|
3136
|
+
const b = box(f);
|
|
3137
|
+
if (!isTerrain(f)) {
|
|
3138
|
+
prints.push({ x0: b.min[0], x1: b.max[0], z0: b.min[2], z1: b.max[2], bottom: b.min[1], part: f });
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const fi = o.idx.get(f.spec.id);
|
|
3142
|
+
const width = profile.footingWidth ?? soilCfg.footingWidth;
|
|
3143
|
+
for (const j of o.byPart.get(f.spec.id)) {
|
|
3144
|
+
if (j.st.open || !j.bears) continue;
|
|
3145
|
+
const r = j.ia === fi ? j.ib : j.ia;
|
|
3146
|
+
const q = o.parts[r];
|
|
3147
|
+
if (!q.structural || isLost(q) || restsOn(o, j, r) !== fi) continue;
|
|
3148
|
+
const c = box(q);
|
|
3149
|
+
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]);
|
|
3150
|
+
const cx = (x0 + x1) / 2, cz = (z0 + z1) / 2;
|
|
3151
|
+
const wx = Math.max(width, x1 - x0), wz = Math.max(width, z1 - z0);
|
|
3152
|
+
prints.push({ x0: cx - wx / 2, x1: cx + wx / 2, z0: cz - wz / 2, z1: cz + wz / 2, bottom: c.min[1], part: q });
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
const springs = [];
|
|
3156
|
+
for (const fp of prints) {
|
|
3157
|
+
const wx = Math.max(0.01, fp.x1 - fp.x0), wz = Math.max(0.01, fp.z1 - fp.z0);
|
|
3158
|
+
const depth = Math.max(0, surface - fp.bottom);
|
|
3159
|
+
const pile = fp.part.spec.structure?.role === "foundation" ? fp.part.spec.structure.pile : void 0;
|
|
3160
|
+
if (pile) {
|
|
3161
|
+
const count = Math.max(1, Math.round(pile.count ?? 1));
|
|
3162
|
+
const nx2 = Math.ceil(Math.sqrt(count)), nz2 = Math.ceil(count / nx2);
|
|
3163
|
+
const cap = pileCapacity(profile, pile, depth, soilCfg.byId).ultimate;
|
|
3164
|
+
const tip = soilAt(profile, depth + pile.length, soilCfg.byId);
|
|
3165
|
+
const k = pileStiffness(pile, fp.part.mat.strength?.elasticGPa ?? soilCfg.pileElasticGPa, tip.elasticMPa, tip.poisson);
|
|
3166
|
+
for (let i = 0; i < count; i++) {
|
|
3167
|
+
const col = i % nx2, row = Math.floor(i / nx2);
|
|
3168
|
+
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 });
|
|
3169
|
+
}
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
const B = Math.min(wx, wz);
|
|
3173
|
+
const g = footingSoil(profile, depth, B, soilCfg.byId, soilCfg.influence);
|
|
3174
|
+
const ks = subgradeModulus(g.elasticMPa, g.poisson, B, soilCfg.rigidity);
|
|
3175
|
+
const size = Math.max(wx, wz) / Math.max(1, soilCfg.cells);
|
|
3176
|
+
const nx = Math.max(1, Math.round(wx / size)), nz = Math.max(1, Math.round(wz / size));
|
|
3177
|
+
const area = wx * wz / (nx * nz);
|
|
3178
|
+
for (let i = 0; i < nx; i++) {
|
|
3179
|
+
for (let k = 0; k < nz; k++) {
|
|
3180
|
+
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 });
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
return { springs, baseY: prints.length ? Math.min(...prints.map((fp) => fp.bottom)) : 0 };
|
|
3185
|
+
};
|
|
3186
|
+
const soilWeight = (o) => {
|
|
3187
|
+
let m = 0;
|
|
3188
|
+
const c = [0, 0, 0];
|
|
3189
|
+
for (const p of o.parts) {
|
|
3190
|
+
if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[o.idx.get(p.spec.id)]) continue;
|
|
3191
|
+
m += p.st.mass;
|
|
3192
|
+
for (let k = 0; k < 3; k++) c[k] += p.st.mass * p.pos[k];
|
|
3193
|
+
}
|
|
3194
|
+
return { w: m * GRAVITY, c: m > 0 ? [c[0] / m, c[1] / m, c[2] / m] : c };
|
|
3195
|
+
};
|
|
3196
|
+
const initSoil = (o, profile, carried, carrySets) => {
|
|
3197
|
+
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" } };
|
|
3198
|
+
const { springs, baseY } = soilSprings(o, profile);
|
|
3199
|
+
if (!springs.length) return;
|
|
3200
|
+
const [x0, z0] = stiffnessCentre(springs);
|
|
3201
|
+
so.pivot = [x0, baseY, z0];
|
|
3202
|
+
let ix = 0, iz = 0;
|
|
3203
|
+
for (const sp of springs) {
|
|
3204
|
+
ix += sp.k * (sp.x - x0) ** 2;
|
|
3205
|
+
iz += sp.k * (sp.z - z0) ** 2;
|
|
3206
|
+
}
|
|
3207
|
+
const { c } = soilWeight(o);
|
|
3208
|
+
const along = ix <= iz ? 0 : 1;
|
|
3209
|
+
const off = along === 0 ? c[0] - x0 : c[2] - z0;
|
|
3210
|
+
const sign = Math.abs(off) > 1e-6 ? Math.sign(off) : 1;
|
|
3211
|
+
so.imp = along === 0 ? [sign * soilCfg.imperfection, 0] : [0, sign * soilCfg.imperfection];
|
|
3212
|
+
if (carried) {
|
|
3213
|
+
const t = Math.tan(carried.tilt * Math.PI / 180);
|
|
3214
|
+
so.s = carried.settlement;
|
|
3215
|
+
so.a = t * carried.direction[0];
|
|
3216
|
+
so.b = t * carried.direction[2];
|
|
3217
|
+
so.emitted = { t: -Infinity, s: carried.settlement, tilt: carried.tilt, status: carried.status };
|
|
3218
|
+
o.st.soil = clone(carried);
|
|
3219
|
+
for (const [k, v] of Object.entries(carrySets ?? {})) so.sets.set(k, v);
|
|
3220
|
+
if (carried.status === "toppled") for (const p of o.parts) p.anchored = false;
|
|
3221
|
+
}
|
|
3222
|
+
o.soil = so;
|
|
3223
|
+
applySoilPose(o);
|
|
3224
|
+
};
|
|
3225
|
+
const applySoilPose = (o) => {
|
|
3226
|
+
const so = o.soil;
|
|
3227
|
+
if (!so) {
|
|
3228
|
+
o.st.position = [o.base.pos[0], o.base.pos[1], o.base.pos[2]];
|
|
3229
|
+
o.st.rotation = [o.base.rot[0], o.base.rot[1], o.base.rot[2], o.base.rot[3]];
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
const angle = slopeTilt(so.a, so.b);
|
|
3233
|
+
const R = angle > 1e-12 ? quat.fromAxisAngle(vec.normalize([so.b, 0, -so.a]), angle) : quat.identity();
|
|
3234
|
+
const t = vec.sub(vec.sub(so.pivot, rotateVec(R, so.pivot)), [0, so.s, 0]);
|
|
3235
|
+
o.st.rotation = quat.normalize(quat.mul(o.base.rot, R));
|
|
3236
|
+
o.st.position = vec.add(o.base.pos, rotateVec(o.base.rot, t));
|
|
3237
|
+
const lean = Math.hypot(so.a, so.b);
|
|
3238
|
+
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] } };
|
|
3239
|
+
st.settlement = so.s;
|
|
3240
|
+
st.tilt = angle * 180 / Math.PI;
|
|
3241
|
+
st.direction = lean > 1e-12 ? [so.a / lean, 0, so.b / lean] : [0, 0, 0];
|
|
3242
|
+
st.offset = { position: t, rotation: R };
|
|
3243
|
+
o.st.soil = st;
|
|
3244
|
+
};
|
|
3245
|
+
const topple = (o, src) => {
|
|
3246
|
+
for (const p of o.parts) p.anchored = false;
|
|
3247
|
+
solveStructure(o);
|
|
3248
|
+
recomputeGroups(o);
|
|
3249
|
+
if (!pendingCollapse.has(o)) pendingCollapse.set(o, src);
|
|
3250
|
+
};
|
|
3251
|
+
const settleSoil = (o, dt) => {
|
|
3252
|
+
const so = o.soil;
|
|
3253
|
+
const st = o.st.soil;
|
|
3254
|
+
if (st?.status === "toppled") return;
|
|
3255
|
+
const { springs } = soilSprings(o, so.profile);
|
|
3256
|
+
for (const sp of springs) sp.set = so.sets.get(sp.key) ?? 0;
|
|
3257
|
+
const { w, c } = soilWeight(o);
|
|
3258
|
+
if (!springs.length || w <= 0) {
|
|
3259
|
+
if (springs.length === 0 && w > 0) {
|
|
3260
|
+
so.s += soilCfg.sinkRate * dt;
|
|
3261
|
+
applySoilPose(o);
|
|
3262
|
+
o.st.soil.status = "yielding";
|
|
3263
|
+
}
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
const height = Math.max(0, c[1] - so.pivot[1]);
|
|
3267
|
+
const wa = o.wind?.analysis;
|
|
3268
|
+
const push = wa ? windDirection(o, o.wind.cfg) : [0, 0];
|
|
3269
|
+
const shift = wa ? wa.moment / w : 0;
|
|
3270
|
+
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 });
|
|
3271
|
+
let status = "stable";
|
|
3272
|
+
const sinkStep = soilCfg.sinkRate * dt;
|
|
3273
|
+
const tiltStep = Math.tan(soilCfg.tiltRate * Math.PI / 180) * dt;
|
|
3274
|
+
if (r.stable && (!r.yielded || dt === 0)) {
|
|
3275
|
+
so.s = r.s;
|
|
3276
|
+
so.a = r.a;
|
|
3277
|
+
so.b = r.b;
|
|
3278
|
+
} else if (r.stable) {
|
|
3279
|
+
const ds = r.s - so.s, da = r.a - so.a, db = r.b - so.b;
|
|
3280
|
+
const fs = Math.abs(ds) > sinkStep ? sinkStep / Math.abs(ds) : 1;
|
|
3281
|
+
const lean2 = Math.hypot(da, db);
|
|
3282
|
+
const ft = lean2 > tiltStep ? tiltStep / lean2 : 1;
|
|
3283
|
+
so.s += ds * fs;
|
|
3284
|
+
so.a += da * ft;
|
|
3285
|
+
so.b += db * ft;
|
|
3286
|
+
if (fs < 1 || ft < 1) status = "yielding";
|
|
3287
|
+
} else {
|
|
3288
|
+
status = "yielding";
|
|
3289
|
+
if (dt === 0) {
|
|
3290
|
+
so.s = w / springs.reduce((sum, sp) => sum + sp.k, 0);
|
|
3291
|
+
} else {
|
|
3292
|
+
so.s += sinkStep * (r.bearing >= 1 ? 1 : 0.25);
|
|
3293
|
+
const lean2 = Math.hypot(so.a, so.b);
|
|
3294
|
+
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]);
|
|
3295
|
+
so.a += ux * tiltStep;
|
|
3296
|
+
so.b += uz * tiltStep;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (so.s !== 0 || so.a !== 0 || so.b !== 0) {
|
|
3300
|
+
const sets = yieldSprings(springs, so.s, so.a, so.b);
|
|
3301
|
+
springs.forEach((sp, i) => {
|
|
3302
|
+
if (sets[i] > 0) so.sets.set(sp.key, sets[i]);
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
applySoilPose(o);
|
|
3306
|
+
const out = o.st.soil;
|
|
3307
|
+
out.bearing = r.bearing;
|
|
3308
|
+
out.edge = r.edge;
|
|
3309
|
+
out.status = status;
|
|
3310
|
+
const lean = Math.hypot(so.a, so.b);
|
|
3311
|
+
if (lean > 1e-9) {
|
|
3312
|
+
const [x0, z0] = [so.pivot[0], so.pivot[2]];
|
|
3313
|
+
const ux = so.a / lean, uz = so.b / lean;
|
|
3314
|
+
const e = (c[0] - x0 + height * (so.a + so.imp[0])) * ux + (c[2] - z0 + height * (so.b + so.imp[1])) * uz;
|
|
3315
|
+
let edge = 0;
|
|
3316
|
+
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));
|
|
3317
|
+
if (e > edge || slopeTilt(so.a, so.b) > Math.PI / 3) out.status = "toppled";
|
|
3318
|
+
}
|
|
3319
|
+
};
|
|
3320
|
+
const stepSoil = (o, dt) => {
|
|
3321
|
+
const so = o.soil;
|
|
3322
|
+
if (o.st.soil?.status === "toppled") return;
|
|
3323
|
+
if (!so.dirty && o.st.soil?.status !== "yielding") return;
|
|
3324
|
+
so.dirty = false;
|
|
3325
|
+
settleSoil(o, dt);
|
|
3326
|
+
const st = o.st.soil;
|
|
3327
|
+
const e = so.emitted;
|
|
3328
|
+
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);
|
|
3329
|
+
if (changed) {
|
|
3330
|
+
so.emitted = { t: time, s: st.settlement, tilt: st.tilt, status: st.status };
|
|
3331
|
+
emit({ ...base(o), type: "settle", settlement: st.settlement, tilt: st.tilt, direction: rotateVec(o.base.rot, st.direction), bearing: st.bearing, status: st.status });
|
|
3332
|
+
}
|
|
3333
|
+
if (st.status === "toppled") topple(o);
|
|
1918
3334
|
};
|
|
1919
3335
|
const collapseCheck = (o, src) => {
|
|
1920
3336
|
if (!o.hasStructure || !o.parts.some((p) => p.anchored)) return;
|
|
@@ -2151,7 +3567,7 @@ var createDestruction = (options = {}) => {
|
|
|
2151
3567
|
if (split && mine.length) amount /= mine.length;
|
|
2152
3568
|
for (const j of mine) {
|
|
2153
3569
|
if (j.st.open) continue;
|
|
2154
|
-
if (j.spec.kind === "rebar" && (o.parts[j.ia].spec
|
|
3570
|
+
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
2155
3571
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
2156
3572
|
j.st.hp -= amount;
|
|
2157
3573
|
if (j.st.hp <= 0) {
|
|
@@ -2307,14 +3723,37 @@ var createDestruction = (options = {}) => {
|
|
|
2307
3723
|
debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
|
|
2308
3724
|
partLost(o, p, src);
|
|
2309
3725
|
};
|
|
2310
|
-
const
|
|
3726
|
+
const hingeDrop = (o, p, src) => {
|
|
3727
|
+
const i = o.idx.get(p.spec.id);
|
|
3728
|
+
const opened = [];
|
|
3729
|
+
for (const j of o.byPart.get(p.spec.id) ?? []) {
|
|
3730
|
+
if (j.st.open || restsOn(o, j, i) < 0) continue;
|
|
3731
|
+
j.st.open = true;
|
|
3732
|
+
j.st.hp = 0;
|
|
3733
|
+
j.st.openedBy = "break";
|
|
3734
|
+
opened.push(j);
|
|
3735
|
+
}
|
|
3736
|
+
p.st.hp = Math.max(1, p.st.maxHp * 0.1);
|
|
3737
|
+
p.st.cracked = true;
|
|
3738
|
+
markDamaged(p);
|
|
3739
|
+
debris(o, p, p.mat.breakDebris, partXf(o, p).c, [0, -1, 0], 12, 2, src);
|
|
3740
|
+
solveStructure(o);
|
|
3741
|
+
recomputeGroups(o);
|
|
3742
|
+
detachEvents(o, opened, "break", src);
|
|
3743
|
+
collapseCheck(o, src);
|
|
3744
|
+
};
|
|
3745
|
+
const failPart = (o, p, origin, dir, fragEnergy, cap, src, force) => {
|
|
2311
3746
|
if (isLost(p)) return;
|
|
2312
3747
|
if (p.st.stripped) {
|
|
2313
3748
|
loseCage(o, p, src);
|
|
2314
3749
|
return;
|
|
2315
3750
|
}
|
|
3751
|
+
if (!force && p.bend && (p.st.flexure ?? 0) > 1 && sectionOf(p, p.bend.gravity).reinforced) {
|
|
3752
|
+
hingeDrop(o, p, src);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
2316
3755
|
const ex = p.spec.explodes;
|
|
2317
|
-
const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
|
|
3756
|
+
const style = force ?? (p.snap ? "snap" : p.mat.fracture === "none" ? "snap" : p.mat.fracture);
|
|
2318
3757
|
const { c, q } = partXf(o, p);
|
|
2319
3758
|
const hitW = origin ?? c;
|
|
2320
3759
|
const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
|
|
@@ -2371,8 +3810,8 @@ var createDestruction = (options = {}) => {
|
|
|
2371
3810
|
return;
|
|
2372
3811
|
}
|
|
2373
3812
|
if (ex) {
|
|
2374
|
-
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 } : {} });
|
|
2375
|
-
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
|
|
3813
|
+
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 } : {} });
|
|
3814
|
+
blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
|
|
2376
3815
|
}
|
|
2377
3816
|
partLost(o, p, src);
|
|
2378
3817
|
};
|
|
@@ -2401,8 +3840,33 @@ var createDestruction = (options = {}) => {
|
|
|
2401
3840
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2402
3841
|
return done;
|
|
2403
3842
|
};
|
|
2404
|
-
const
|
|
3843
|
+
const leaksFlammable = (p) => {
|
|
3844
|
+
const c = p.spec.contains;
|
|
3845
|
+
return !!c && FLAMMABLE_FLUIDS.includes(c.fluid) && (p.st.liters ?? 0) > 0 && p.st.holes.some((h) => h.through);
|
|
3846
|
+
};
|
|
3847
|
+
const rollIgnition = (o, p, round) => {
|
|
3848
|
+
const c = p.spec.contains;
|
|
3849
|
+
if (!c || !FLAMMABLE_FLUIDS.includes(c.fluid) || p.rt.jet || p.rt.lit) return;
|
|
3850
|
+
const fill = c.liters > 0 ? (p.st.liters ?? 0) / c.liters : 0;
|
|
3851
|
+
const chance = ignitionChance(round, { sparkTarget: sparkTargetFactor(p.mat.id, !!p.mat.tags?.includes("metal"), ignitionRules), fill, fluid: c.fluid }, ignitionRules);
|
|
3852
|
+
if (chance > 0 && rnd() < chance) {
|
|
3853
|
+
p.rt.lit = true;
|
|
3854
|
+
o.active = true;
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
const lightLeaksNear = (centre, radius) => {
|
|
3858
|
+
for (const o of objects.values()) {
|
|
3859
|
+
for (const q of o.parts) {
|
|
3860
|
+
if (isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
|
|
3861
|
+
if (vec.distance(partXf(o, q).c, centre) - q.bound > radius) continue;
|
|
3862
|
+
q.rt.lit = true;
|
|
3863
|
+
o.active = true;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
2405
3868
|
const mat = p.mat;
|
|
3869
|
+
const piercing = round.piercing ?? 1;
|
|
2406
3870
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2407
3871
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
2408
3872
|
}
|
|
@@ -2415,7 +3879,8 @@ var createDestruction = (options = {}) => {
|
|
|
2415
3879
|
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;
|
|
2416
3880
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2417
3881
|
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2418
|
-
const
|
|
3882
|
+
const penMm = penetrationOf(mat, round.penetration);
|
|
3883
|
+
const Ep = penMm !== void 0 ? penMm * mat.penetration : piercing === 1 || !hard ? E : E * piercing;
|
|
2419
3884
|
const penetrated = Ep > thr;
|
|
2420
3885
|
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2421
3886
|
const id = p.spec.id;
|
|
@@ -2436,6 +3901,7 @@ var createDestruction = (options = {}) => {
|
|
|
2436
3901
|
const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
|
|
2437
3902
|
if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2438
3903
|
addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
|
|
3904
|
+
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);
|
|
2439
3905
|
if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
|
|
2440
3906
|
if (p.spec.contains && (p.st.liters ?? 0) > 0) {
|
|
2441
3907
|
emit({
|
|
@@ -2444,10 +3910,11 @@ var createDestruction = (options = {}) => {
|
|
|
2444
3910
|
part: id,
|
|
2445
3911
|
fluid: p.spec.contains.fluid,
|
|
2446
3912
|
point: entry,
|
|
2447
|
-
direction: n,
|
|
3913
|
+
direction: p.spec.contains.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, p.spec.contains.outflow))) : n,
|
|
2448
3914
|
rate: holeRate(hole, !!p.spec.contains.pressurized),
|
|
2449
3915
|
pressurized: !!p.spec.contains.pressurized
|
|
2450
3916
|
});
|
|
3917
|
+
rollIgnition(o, p, round);
|
|
2451
3918
|
}
|
|
2452
3919
|
} else if (mat.ductility >= 0.5) {
|
|
2453
3920
|
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);
|
|
@@ -2458,7 +3925,7 @@ var createDestruction = (options = {}) => {
|
|
|
2458
3925
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2459
3926
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2460
3927
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2461
|
-
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
3928
|
+
return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2462
3929
|
};
|
|
2463
3930
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2464
3931
|
const mat = p.mat;
|
|
@@ -2478,8 +3945,9 @@ var createDestruction = (options = {}) => {
|
|
|
2478
3945
|
}
|
|
2479
3946
|
debris(o, p, mat.hitDebris, point, n, clamp2(Math.round(E / 25) + 1, 1, 10), clamp2(Math.sqrt(E) / 8, 1, 6), src);
|
|
2480
3947
|
}
|
|
2481
|
-
if (kind === "blunt")
|
|
2482
|
-
|
|
3948
|
+
if (kind === "blunt") {
|
|
3949
|
+
if (!(p.anchored && p.spec.tags?.includes("terrain"))) wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
3950
|
+
} else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2483
3951
|
if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
|
|
2484
3952
|
const scale = mat.damageScale?.[kind] ?? 1;
|
|
2485
3953
|
const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
@@ -2526,7 +3994,8 @@ var createDestruction = (options = {}) => {
|
|
|
2526
3994
|
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);
|
|
2527
3995
|
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2528
3996
|
res.damage += dealt;
|
|
2529
|
-
if (
|
|
3997
|
+
if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
|
|
3998
|
+
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2530
3999
|
return reach;
|
|
2531
4000
|
};
|
|
2532
4001
|
const blastObject = (o, point, energy, radius, src) => {
|
|
@@ -2589,7 +4058,7 @@ var createDestruction = (options = {}) => {
|
|
|
2589
4058
|
};
|
|
2590
4059
|
const FLAG_LOST = 1;
|
|
2591
4060
|
const FLAG_OPEN = 2;
|
|
2592
|
-
const blastPlain = (point, energy, radius, src) => {
|
|
4061
|
+
const blastPlain = (point, energy, radius, src, lethal) => {
|
|
2593
4062
|
const out = [];
|
|
2594
4063
|
for (const o of [...objects.values()]) {
|
|
2595
4064
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2597,7 +4066,7 @@ var createDestruction = (options = {}) => {
|
|
|
2597
4066
|
if (r.part) out.push(r);
|
|
2598
4067
|
}
|
|
2599
4068
|
for (const im of [...impls.values()]) {
|
|
2600
|
-
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
4069
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...src !== void 0 ? { source: src } : {} }));
|
|
2601
4070
|
}
|
|
2602
4071
|
return out;
|
|
2603
4072
|
};
|
|
@@ -2606,7 +4075,7 @@ var createDestruction = (options = {}) => {
|
|
|
2606
4075
|
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])) };
|
|
2607
4076
|
});
|
|
2608
4077
|
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2609
|
-
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
4078
|
+
const blastScene = (point, energy, radius, src, cfg, only, lethal) => {
|
|
2610
4079
|
const mult = confinementOf(cfg.conf, point, radius);
|
|
2611
4080
|
const E = energy * mult;
|
|
2612
4081
|
const shelter = cfg.shelter;
|
|
@@ -2713,7 +4182,7 @@ var createDestruction = (options = {}) => {
|
|
|
2713
4182
|
den += f;
|
|
2714
4183
|
}
|
|
2715
4184
|
const k = den > 0 ? num / den : 1;
|
|
2716
|
-
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
4185
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
2717
4186
|
results.set(im, res);
|
|
2718
4187
|
report(im).shelter = k;
|
|
2719
4188
|
}
|
|
@@ -2811,10 +4280,11 @@ var createDestruction = (options = {}) => {
|
|
|
2811
4280
|
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
2812
4281
|
rep.fragmentHits++;
|
|
2813
4282
|
};
|
|
2814
|
-
const blastWorld = (point, energy, radius, src, call, only) => {
|
|
4283
|
+
const blastWorld = (point, energy, radius, src, call, only, lethal) => {
|
|
2815
4284
|
const cfg = blastCfg(call);
|
|
2816
|
-
|
|
2817
|
-
|
|
4285
|
+
const res = !cfg.shelter && !cfg.frag && cfg.conf === null ? blastPlain(point, energy, radius, src, lethal) : blastScene(point, energy, radius, src, cfg, only, lethal);
|
|
4286
|
+
lightLeaksNear(point, radius);
|
|
4287
|
+
return res;
|
|
2818
4288
|
};
|
|
2819
4289
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2820
4290
|
const start = cur.length;
|
|
@@ -2833,6 +4303,7 @@ var createDestruction = (options = {}) => {
|
|
|
2833
4303
|
p.st.temperature += (T - p.st.temperature) * (1 - Math.exp(-k * duration));
|
|
2834
4304
|
}
|
|
2835
4305
|
o.active = true;
|
|
4306
|
+
if (T >= FLAME_LIGHTS_LEAK_C && !p.rt.jet && !p.rt.lit && leaksFlammable(p)) p.rt.lit = true;
|
|
2836
4307
|
checkIgnite(o, p, src);
|
|
2837
4308
|
}
|
|
2838
4309
|
res.events = cur.slice(start);
|
|
@@ -2852,7 +4323,7 @@ var createDestruction = (options = {}) => {
|
|
|
2852
4323
|
if (hit.type === "ballistic") {
|
|
2853
4324
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2854
4325
|
if (loc) {
|
|
2855
|
-
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit
|
|
4326
|
+
const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit);
|
|
2856
4327
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2857
4328
|
}
|
|
2858
4329
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -3006,7 +4477,8 @@ var createDestruction = (options = {}) => {
|
|
|
3006
4477
|
if (rate > 0) {
|
|
3007
4478
|
s.liters = Math.max(0, (s.liters ?? 0) - rate * dt);
|
|
3008
4479
|
hot = true;
|
|
3009
|
-
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && s.temperature >= 350 && !p.rt.jet) {
|
|
4480
|
+
if (FLAMMABLE_FLUIDS.includes(cont.fluid) && (s.temperature >= 350 || p.rt.lit) && !p.rt.jet) {
|
|
4481
|
+
p.rt.lit = false;
|
|
3010
4482
|
if (ex && chainDelayOf(p.spec, "fire") !== void 0) {
|
|
3011
4483
|
prime(o, p, "fire");
|
|
3012
4484
|
continue;
|
|
@@ -3021,6 +4493,7 @@ var createDestruction = (options = {}) => {
|
|
|
3021
4493
|
}
|
|
3022
4494
|
}
|
|
3023
4495
|
}
|
|
4496
|
+
if ((s.liters ?? 0) <= 0) p.rt.lit = false;
|
|
3024
4497
|
if (p.rt.jet && (s.liters ?? 0) <= 0) {
|
|
3025
4498
|
p.rt.jet = false;
|
|
3026
4499
|
s.burning = false;
|
|
@@ -3070,6 +4543,25 @@ var createDestruction = (options = {}) => {
|
|
|
3070
4543
|
}
|
|
3071
4544
|
o.active = hot;
|
|
3072
4545
|
};
|
|
4546
|
+
const igniteLeaksNearFire = (sources, dt) => {
|
|
4547
|
+
const { radius, chancePerSecond } = ignitionRules.fireNear;
|
|
4548
|
+
if (chancePerSecond <= 0) return;
|
|
4549
|
+
const chance = 1 - Math.exp(-chancePerSecond * dt);
|
|
4550
|
+
for (const src of sources) {
|
|
4551
|
+
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
4552
|
+
const sc = partXf(src.o, src.p).c;
|
|
4553
|
+
for (const o of objects.values()) {
|
|
4554
|
+
for (const q of o.parts) {
|
|
4555
|
+
if (q === src.p || isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
|
|
4556
|
+
if (vec.distance(partXf(o, q).c, sc) - q.bound - src.p.bound > radius) continue;
|
|
4557
|
+
if (rnd() < chance) {
|
|
4558
|
+
q.rt.lit = true;
|
|
4559
|
+
o.active = true;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
3073
4565
|
const spread2 = (sources, dt) => {
|
|
3074
4566
|
for (const src of sources) {
|
|
3075
4567
|
if (!src.p.st.burning && !src.p.rt.jet) continue;
|
|
@@ -3195,8 +4687,8 @@ var createDestruction = (options = {}) => {
|
|
|
3195
4687
|
return;
|
|
3196
4688
|
}
|
|
3197
4689
|
const o = getObj(id);
|
|
3198
|
-
o.
|
|
3199
|
-
o
|
|
4690
|
+
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
4691
|
+
applySoilPose(o);
|
|
3200
4692
|
},
|
|
3201
4693
|
splitGroup(id, group, newId) {
|
|
3202
4694
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -3219,13 +4711,17 @@ var createDestruction = (options = {}) => {
|
|
|
3219
4711
|
const s = { parts: o.spec.parts.filter((p) => keep(p.id)), joints: (o.spec.joints ?? []).filter((j) => keep(j.a) && keep(j.b)) };
|
|
3220
4712
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
3221
4713
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
4714
|
+
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
3222
4715
|
return clone(s);
|
|
3223
4716
|
};
|
|
3224
4717
|
const inNew = (pid) => moved.has(pid);
|
|
3225
4718
|
const inOld = (pid) => !moved.has(pid);
|
|
3226
4719
|
const pos = o.st.position, rot = o.st.rotation;
|
|
3227
4720
|
const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
|
|
3228
|
-
const
|
|
4721
|
+
const restCarry = carryFor(inOld, (a, b) => !(inNew(a) && inNew(b)));
|
|
4722
|
+
if (o.st.soil) restCarry.soil = o.st.soil;
|
|
4723
|
+
if (o.soil?.sets.size) restCarry.soilSets = Object.fromEntries(o.soil.sets);
|
|
4724
|
+
const rest = build(sub(inOld), id, o.soil ? o.base.pos : pos, o.soil ? o.base.rot : rot, restCarry);
|
|
3229
4725
|
objects.set(id, rest);
|
|
3230
4726
|
objects.set(nid, fresh);
|
|
3231
4727
|
return nid;
|
|
@@ -3250,7 +4746,8 @@ var createDestruction = (options = {}) => {
|
|
|
3250
4746
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3251
4747
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3252
4748
|
},
|
|
3253
|
-
shoot(origin, direction, energy, caliber, source,
|
|
4749
|
+
shoot(origin, direction, energy, caliber, source, round) {
|
|
4750
|
+
const extras = typeof round === "number" ? { piercing: round } : round ?? {};
|
|
3254
4751
|
return withCollapses(run(() => {
|
|
3255
4752
|
const results = [];
|
|
3256
4753
|
const d = vec.normalize(direction);
|
|
@@ -3262,7 +4759,7 @@ var createDestruction = (options = {}) => {
|
|
|
3262
4759
|
if (!rh) break;
|
|
3263
4760
|
const im = impls.get(rh.object);
|
|
3264
4761
|
if (im) {
|
|
3265
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...
|
|
4762
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
|
|
3266
4763
|
r.part = rh.part;
|
|
3267
4764
|
r.point = rh.point;
|
|
3268
4765
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3276,7 +4773,7 @@ var createDestruction = (options = {}) => {
|
|
|
3276
4773
|
const o = getObj(rh.object);
|
|
3277
4774
|
const p = getPart(o, rh.part);
|
|
3278
4775
|
const start = cur.length;
|
|
3279
|
-
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source,
|
|
4776
|
+
const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, extras);
|
|
3280
4777
|
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) });
|
|
3281
4778
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3282
4779
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3375,6 +4872,52 @@ var createDestruction = (options = {}) => {
|
|
|
3375
4872
|
o.active = true;
|
|
3376
4873
|
});
|
|
3377
4874
|
},
|
|
4875
|
+
setWind(next) {
|
|
4876
|
+
windOpts = { ...windOpts, ...Object.fromEntries(Object.entries(next).filter(([, v]) => v !== void 0)) };
|
|
4877
|
+
windCfg = resolveWind(windOpts);
|
|
4878
|
+
for (const o of objects.values()) {
|
|
4879
|
+
if (!o.hasStructure || o.spec.wind === false) continue;
|
|
4880
|
+
o.wind ?? (o.wind = newWind(o));
|
|
4881
|
+
o.dirty = true;
|
|
4882
|
+
o.active = true;
|
|
4883
|
+
}
|
|
4884
|
+
},
|
|
4885
|
+
vent(objectId, partId, opening = {}) {
|
|
4886
|
+
if (impls.has(objectId)) return false;
|
|
4887
|
+
const o = getObj(objectId);
|
|
4888
|
+
const p = getPart(o, partId);
|
|
4889
|
+
const c = p.spec.contains;
|
|
4890
|
+
if (!c || isLost(p) || (p.st.liters ?? 0) <= 0) return false;
|
|
4891
|
+
if (p.st.holes.some((h) => h.vent)) return true;
|
|
4892
|
+
return run(() => {
|
|
4893
|
+
const wp = opening.point ?? partXf(o, p).c;
|
|
4894
|
+
const wn = vec.normalize(opening.normal ?? [0, 1, 0]);
|
|
4895
|
+
const hole = { point: objLocal(o, wp), normal: objLocalDir(o, wn), diameter: opening.diameter ?? VENT_DIAMETER, through: true, vent: true };
|
|
4896
|
+
p.st.holes.push(hole);
|
|
4897
|
+
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
4898
|
+
o.active = true;
|
|
4899
|
+
emit({
|
|
4900
|
+
...base(o),
|
|
4901
|
+
type: "leak",
|
|
4902
|
+
part: partId,
|
|
4903
|
+
fluid: c.fluid,
|
|
4904
|
+
point: wp,
|
|
4905
|
+
direction: c.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, c.outflow))) : wn,
|
|
4906
|
+
rate: holeRate(hole, !!c.pressurized),
|
|
4907
|
+
pressurized: !!c.pressurized
|
|
4908
|
+
});
|
|
4909
|
+
return true;
|
|
4910
|
+
}).value;
|
|
4911
|
+
},
|
|
4912
|
+
seal(objectId, partId) {
|
|
4913
|
+
if (impls.has(objectId)) return 0;
|
|
4914
|
+
const o = getObj(objectId);
|
|
4915
|
+
const p = getPart(o, partId);
|
|
4916
|
+
const before = p.st.holes.length;
|
|
4917
|
+
p.st.holes = p.st.holes.filter((h) => !h.vent);
|
|
4918
|
+
o.active = true;
|
|
4919
|
+
return before - p.st.holes.length;
|
|
4920
|
+
},
|
|
3378
4921
|
setLiters(objectId, partId, liters) {
|
|
3379
4922
|
if (impls.has(objectId)) return;
|
|
3380
4923
|
const p = getPart(getObj(objectId), partId);
|
|
@@ -3388,7 +4931,12 @@ var createDestruction = (options = {}) => {
|
|
|
3388
4931
|
time += dt;
|
|
3389
4932
|
const sources = [];
|
|
3390
4933
|
for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
|
|
3391
|
-
if (
|
|
4934
|
+
for (const o of [...objects.values()]) if (o.soil && objects.get(o.id) === o) stepSoil(o, dt);
|
|
4935
|
+
for (const o of [...objects.values()]) if (o.wind && objects.get(o.id) === o) stepWind(o, dt);
|
|
4936
|
+
if (sources.length) {
|
|
4937
|
+
spread2(sources, dt);
|
|
4938
|
+
igniteLeaksNearFire(sources, dt);
|
|
4939
|
+
}
|
|
3392
4940
|
detonatePrimed(time);
|
|
3393
4941
|
releaseFront();
|
|
3394
4942
|
for (const im of [...impls.values()]) {
|
|
@@ -3410,7 +4958,7 @@ var createDestruction = (options = {}) => {
|
|
|
3410
4958
|
return;
|
|
3411
4959
|
}
|
|
3412
4960
|
const o = getObj(id);
|
|
3413
|
-
objects.set(id, build(o.spec, id, o.
|
|
4961
|
+
objects.set(id, build(o.spec, id, o.base.pos, o.base.rot));
|
|
3414
4962
|
},
|
|
3415
4963
|
on(type, fn) {
|
|
3416
4964
|
let set = listeners.get(type);
|
|
@@ -3427,7 +4975,7 @@ var createDestruction = (options = {}) => {
|
|
|
3427
4975
|
rng: rng.state(),
|
|
3428
4976
|
counter,
|
|
3429
4977
|
objects: [...objects.values()].map((o) => {
|
|
3430
|
-
const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
|
|
4978
|
+
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] } : {} };
|
|
3431
4979
|
for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
|
|
3432
4980
|
for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
|
|
3433
4981
|
return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
|
|
@@ -3448,9 +4996,17 @@ var createDestruction = (options = {}) => {
|
|
|
3448
4996
|
state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
|
|
3449
4997
|
),
|
|
3450
4998
|
joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
|
|
3451
|
-
collapsed: e.runtime?.collapsed
|
|
4999
|
+
collapsed: e.runtime?.collapsed,
|
|
5000
|
+
...e.runtime?.wind ? { wind: e.runtime.wind } : {}
|
|
3452
5001
|
};
|
|
3453
|
-
|
|
5002
|
+
let pos = state.position, rot = state.rotation;
|
|
5003
|
+
if (state.soil) {
|
|
5004
|
+
carry.soil = state.soil;
|
|
5005
|
+
if (e.runtime?.soil) carry.soilSets = e.runtime.soil;
|
|
5006
|
+
rot = quat.normalize(quat.mul(state.rotation, quat.conjugate(state.soil.offset.rotation)));
|
|
5007
|
+
pos = vec.sub(state.position, rotateVec(rot, state.soil.offset.position));
|
|
5008
|
+
}
|
|
5009
|
+
objects.set(e.id, build(clone(e.spec), e.id, pos, rot, carry));
|
|
3454
5010
|
}
|
|
3455
5011
|
for (const e of snap.impls ?? []) {
|
|
3456
5012
|
const im = impls.get(e.id);
|
|
@@ -3464,21 +5020,6 @@ var createDestruction = (options = {}) => {
|
|
|
3464
5020
|
return world;
|
|
3465
5021
|
};
|
|
3466
5022
|
|
|
3467
|
-
// src/piercing.ts
|
|
3468
|
-
var PIERCING_BY_KIND = {
|
|
3469
|
-
fmj: 1,
|
|
3470
|
-
hp: 0.6,
|
|
3471
|
-
ap: 1.6,
|
|
3472
|
-
buck: 0.5,
|
|
3473
|
-
slug: 0.25,
|
|
3474
|
-
birdshot: 0.3,
|
|
3475
|
-
rimfire: 1,
|
|
3476
|
-
arrow: 1,
|
|
3477
|
-
bolt: 1
|
|
3478
|
-
};
|
|
3479
|
-
var PIERCING_BY_CALIBER = { "5_56x45": 0.5, "5_45x39": 0.5, "22lr": 0.5 };
|
|
3480
|
-
var piercingOf = (caliber, kind) => PIERCING_BY_CALIBER[caliber] ?? PIERCING_BY_KIND[kind ?? "fmj"] ?? 1;
|
|
3481
|
-
|
|
3482
5023
|
// src/data/assemblies.json
|
|
3483
5024
|
var assemblies_default = [
|
|
3484
5025
|
{
|
|
@@ -3591,21 +5132,35 @@ var buildFrame = (o = {}) => {
|
|
|
3591
5132
|
const dw = o.doorWidth ?? 1, dh = o.doorHeight ?? 2.1;
|
|
3592
5133
|
const mat = o.material ?? "concrete", wmat = o.wallMaterial ?? "brick";
|
|
3593
5134
|
const roof = o.roof ?? true;
|
|
5135
|
+
const rebarOf = (kind) => o.rebar === false ? {} : { rebar: { ratio: o.rebar?.[kind] ?? (kind === "beam" ? 0.01 : 5e-3) } };
|
|
3594
5136
|
const xs = Array.from({ length: nx }, (_, i) => -spanX / 2 + i * spanX / (nx - 1));
|
|
3595
5137
|
const zs = Array.from({ length: nz }, (_, j) => -spanZ / 2 + j * spanZ / (nz - 1));
|
|
3596
5138
|
const parts = [];
|
|
3597
5139
|
const joints = [];
|
|
3598
5140
|
const on = (a, b, kind) => joints.push({ id: `${a}~${b}`, a, b, kind, bears: "a_on_b" });
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
5141
|
+
const pad = o.footing ?? cs * 2.2;
|
|
5142
|
+
const pile = o.piles ? { pile: { ...o.piles } } : {};
|
|
5143
|
+
if (o.raft) {
|
|
5144
|
+
parts.push({
|
|
5145
|
+
id: "raft",
|
|
5146
|
+
material: mat,
|
|
5147
|
+
shape: { kind: "box", size: [spanX + pad, 0.4, spanZ + pad] },
|
|
5148
|
+
position: [0, -0.2, 0],
|
|
5149
|
+
structure: { role: "foundation", ...pile },
|
|
5150
|
+
anchored: true
|
|
5151
|
+
});
|
|
5152
|
+
} else {
|
|
5153
|
+
for (let i = 0; i < nx; i++) {
|
|
5154
|
+
for (let j = 0; j < nz; j++) {
|
|
5155
|
+
parts.push({
|
|
5156
|
+
id: `foundation_${i}_${j}`,
|
|
5157
|
+
material: mat,
|
|
5158
|
+
shape: { kind: "box", size: [pad, 0.3, pad] },
|
|
5159
|
+
position: [xs[i], -0.15, zs[j]],
|
|
5160
|
+
structure: { role: "foundation", ...pile },
|
|
5161
|
+
anchored: true
|
|
5162
|
+
});
|
|
5163
|
+
}
|
|
3609
5164
|
}
|
|
3610
5165
|
}
|
|
3611
5166
|
for (let f = 0; f < floors; f++) {
|
|
@@ -3621,14 +5176,15 @@ var buildFrame = (o = {}) => {
|
|
|
3621
5176
|
material: mat,
|
|
3622
5177
|
shape: { kind: "box", size: [cs, H, cs] },
|
|
3623
5178
|
position: [xs[i], yb + H / 2, zs[j]],
|
|
3624
|
-
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity }
|
|
5179
|
+
structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity },
|
|
5180
|
+
...o.columnCage ? { reinforcement: { ...o.columnCage } } : {}
|
|
3625
5181
|
});
|
|
3626
|
-
on(id, f === 0 ? `foundation_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
5182
|
+
on(id, f === 0 ? o.raft ? "raft" : `foundation_${i}_${j}` : o.continuousColumns ? `column_${f - 1}_${i}_${j}` : `slab_${f - 1}`, "rebar");
|
|
3627
5183
|
}
|
|
3628
5184
|
}
|
|
3629
5185
|
const beams = [];
|
|
3630
5186
|
const beam = (id, size, pos, c1, c2) => {
|
|
3631
|
-
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam" } });
|
|
5187
|
+
parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam", ...rebarOf("beam") } });
|
|
3632
5188
|
on(id, c1, "rebar");
|
|
3633
5189
|
on(id, c2, "rebar");
|
|
3634
5190
|
beams.push(id);
|
|
@@ -3643,13 +5199,13 @@ var buildFrame = (o = {}) => {
|
|
|
3643
5199
|
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}`);
|
|
3644
5200
|
}
|
|
3645
5201
|
}
|
|
3646
|
-
const thick = last && roof ? T * 0.75 : T;
|
|
5202
|
+
const thick = last && roof ? o.roofThickness ?? T * 0.75 : T;
|
|
3647
5203
|
parts.push({
|
|
3648
5204
|
id: slabId,
|
|
3649
5205
|
material: mat,
|
|
3650
5206
|
shape: { kind: "box", size: [spanX + cs + 0.2, thick, spanZ + cs + 0.2] },
|
|
3651
5207
|
position: [0, top + thick / 2, 0],
|
|
3652
|
-
structure: { role: last && roof ? "roof" : "slab" }
|
|
5208
|
+
structure: { role: last && roof ? "roof" : "slab", ...rebarOf("slab") }
|
|
3653
5209
|
});
|
|
3654
5210
|
for (const b of beams) on(slabId, b, last && roof ? "resting" : "rebar");
|
|
3655
5211
|
if (o.walls) {
|
|
@@ -3682,28 +5238,91 @@ var buildFrame = (o = {}) => {
|
|
|
3682
5238
|
}
|
|
3683
5239
|
}
|
|
3684
5240
|
}
|
|
3685
|
-
return { name: "frame", parts, joints, tags: ["structure"] };
|
|
5241
|
+
return { name: "frame", parts, joints, tags: ["structure"], ...o.soil !== void 0 ? { soil: o.soil } : {} };
|
|
3686
5242
|
};
|
|
3687
5243
|
|
|
3688
5244
|
// src/size.ts
|
|
5245
|
+
var MAX_REBAR_RATIO = 0.04;
|
|
5246
|
+
var onRock = (spec) => {
|
|
5247
|
+
if (spec.soil === void 0) return spec;
|
|
5248
|
+
const { soil: _soil, ...rest } = spec;
|
|
5249
|
+
return rest;
|
|
5250
|
+
};
|
|
5251
|
+
var without = (spec, id) => {
|
|
5252
|
+
const reduced = { ...spec, parts: spec.parts.filter((p) => p.id !== id), joints: (spec.joints ?? []).filter((j) => j.a !== id && j.b !== id) };
|
|
5253
|
+
if (!spec.joints) delete reduced.joints;
|
|
5254
|
+
return reduced;
|
|
5255
|
+
};
|
|
5256
|
+
var sizeRebar = (spec, options = {}) => {
|
|
5257
|
+
const flexure = resolveFlexure(options.flexure);
|
|
5258
|
+
const materials = new Map(MATERIALS.map((m) => [m.id, m]));
|
|
5259
|
+
for (const m of options.materials ?? []) materials.set(m.id, m);
|
|
5260
|
+
const out = structuredClone(spec);
|
|
5261
|
+
const targets = out.parts.filter((p) => {
|
|
5262
|
+
const m = materials.get(p.material);
|
|
5263
|
+
return p.shape.kind === "box" && !!m && isReinforced(p) && isFlexural(p, m, flexure);
|
|
5264
|
+
});
|
|
5265
|
+
if (targets.length === 0) return out;
|
|
5266
|
+
const safety = options.safety ?? 0.65;
|
|
5267
|
+
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
5268
|
+
const bending = (s) => {
|
|
5269
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, structure: { flexure: options.flexure } });
|
|
5270
|
+
const id = w.add(onRock(s));
|
|
5271
|
+
return new Map(w.state(id).parts.map((p) => [p.id, p.flexure ?? 0]));
|
|
5272
|
+
};
|
|
5273
|
+
const base = bending(out);
|
|
5274
|
+
const factor = new Map(targets.map((p) => [p.id, (base.get(p.id) ?? 0) / safetyOf(p.structure.role)]));
|
|
5275
|
+
if (options.redundancy !== void 0) {
|
|
5276
|
+
const roles = options.redundancyRoles ?? ["column"];
|
|
5277
|
+
for (const lost of out.parts) {
|
|
5278
|
+
if (!lost.structure || !roles.includes(lost.structure.role)) continue;
|
|
5279
|
+
const l = bending(without(out, lost.id));
|
|
5280
|
+
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));
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
for (const p of targets) {
|
|
5284
|
+
const f = factor.get(p.id);
|
|
5285
|
+
const g = bendGeometry(p);
|
|
5286
|
+
if (f <= 1 || g.gravity < 0) continue;
|
|
5287
|
+
const section = { b: sectionWidth(g, g.gravity), h: g.dims[g.gravity] };
|
|
5288
|
+
const steel = tensionSteel(p, g.dims, section, flexure);
|
|
5289
|
+
const m = materials.get(p.material);
|
|
5290
|
+
const need = f * sectionMoments(m, section, steel, flexure).ultimate;
|
|
5291
|
+
const momentOf = (area) => steelMoment(area, steel.yieldMPa, steel.d, section.b, m.strength.compressiveMPa, flexure);
|
|
5292
|
+
const cap = MAX_REBAR_RATIO * section.b * steel.d;
|
|
5293
|
+
if (steel.area >= cap) continue;
|
|
5294
|
+
let lo = steel.area, hi = cap;
|
|
5295
|
+
if (momentOf(hi) > need) {
|
|
5296
|
+
for (let k = 0; k < 60; k++) {
|
|
5297
|
+
const mid = (lo + hi) / 2;
|
|
5298
|
+
if (momentOf(mid) < need) lo = mid;
|
|
5299
|
+
else hi = mid;
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
const ratio = hi / (section.b * steel.d);
|
|
5303
|
+
p.structure = { ...p.structure, rebar: { ...p.structure.rebar, ratio } };
|
|
5304
|
+
}
|
|
5305
|
+
return out;
|
|
5306
|
+
};
|
|
3689
5307
|
var sizeStructure = (spec, options = {}) => {
|
|
3690
5308
|
const safety = options.safety ?? 0.65;
|
|
3691
5309
|
const redundancy = options.redundancy;
|
|
3692
5310
|
const redundancyRoles = options.redundancyRoles ?? ["column"];
|
|
3693
5311
|
const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
|
|
3694
|
-
const loads = (s) => {
|
|
3695
|
-
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
3696
|
-
const id = w.add(s);
|
|
3697
|
-
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0 }]));
|
|
5312
|
+
const loads = (s, wind) => {
|
|
5313
|
+
const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, ...wind ? { wind } : {} });
|
|
5314
|
+
const id = w.add(onRock(s));
|
|
5315
|
+
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0, wind: p.wind ?? 0 }]));
|
|
3698
5316
|
};
|
|
3699
5317
|
const out = structuredClone(spec);
|
|
3700
|
-
const base = loads(out);
|
|
5318
|
+
const base = loads(out, options.wind);
|
|
5319
|
+
const gamma = options.wind ? resolveWind(options.wind).loadFactor : 0;
|
|
3701
5320
|
const need = /* @__PURE__ */ new Map();
|
|
3702
5321
|
for (const p of out.parts) {
|
|
3703
5322
|
if (!p.structure || p.structure.role === "none") continue;
|
|
3704
5323
|
const b = base.get(p.id);
|
|
3705
5324
|
if (!b || b.stress <= 0) continue;
|
|
3706
|
-
need.set(p.id, b.load / safetyOf(p.structure.role));
|
|
5325
|
+
need.set(p.id, (b.load + (b.wind > 0 ? gamma * b.wind / GRAVITY : 0)) / safetyOf(p.structure.role));
|
|
3707
5326
|
}
|
|
3708
5327
|
if (redundancy !== void 0) {
|
|
3709
5328
|
for (const col of out.parts) {
|
|
@@ -3725,7 +5344,20 @@ var sizeStructure = (spec, options = {}) => {
|
|
|
3725
5344
|
const n = need.get(p.id);
|
|
3726
5345
|
if (n !== void 0) p.structure = { ...p.structure, capacity: n };
|
|
3727
5346
|
}
|
|
3728
|
-
|
|
5347
|
+
if (options.wind) {
|
|
5348
|
+
const yieldMPa = resolveFlexure(options.flexure).rebarYieldMPa;
|
|
5349
|
+
for (const p of out.parts) {
|
|
5350
|
+
const b = base.get(p.id);
|
|
5351
|
+
if (!b || b.wind >= 0 || !p.reinforcement || p.shape.kind !== "box") continue;
|
|
5352
|
+
const pull = -(b.load + gamma * b.wind / GRAVITY) * GRAVITY;
|
|
5353
|
+
if (pull <= 0) continue;
|
|
5354
|
+
const dims = shapeDims(p.shape);
|
|
5355
|
+
const have = tensionCapacity(dims, { reinforcement: p.reinforcement, yieldMPa });
|
|
5356
|
+
const want = pull / safetyOf(p.structure?.role ?? "wall");
|
|
5357
|
+
if (have > 0 && want > have) p.reinforcement = { ...p.reinforcement, diameter: Math.min(0.04, p.reinforcement.diameter * Math.sqrt(want / have)) };
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
return sizeRebar(out, options);
|
|
3729
5361
|
};
|
|
3730
5362
|
|
|
3731
5363
|
// src/locale.ts
|
|
@@ -3807,6 +5439,8 @@ var locales = {
|
|
|
3807
5439
|
primed: "about to explode",
|
|
3808
5440
|
stress: "overload",
|
|
3809
5441
|
collapse: "collapsed",
|
|
5442
|
+
settle: "ground gave way",
|
|
5443
|
+
sway: "sway",
|
|
3810
5444
|
spall: "plaster came off",
|
|
3811
5445
|
stage: "damage stage",
|
|
3812
5446
|
expose: "rebar bare",
|
|
@@ -3895,6 +5529,8 @@ var locales = {
|
|
|
3895
5529
|
primed: "prestes a explodir",
|
|
3896
5530
|
stress: "sobrecarga",
|
|
3897
5531
|
collapse: "desabou",
|
|
5532
|
+
settle: "recalque do solo",
|
|
5533
|
+
sway: "balan\xE7o",
|
|
3898
5534
|
spall: "reboco soltou",
|
|
3899
5535
|
stage: "est\xE1gio de dano",
|
|
3900
5536
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
@@ -3920,39 +5556,87 @@ export {
|
|
|
3920
5556
|
ASSEMBLIES,
|
|
3921
5557
|
CONFINEMENT_DEFAULTS,
|
|
3922
5558
|
FIRE_PRIME_C,
|
|
5559
|
+
FLEXURE_DEFAULTS,
|
|
3923
5560
|
FRAGMENT_HIT_DEFAULTS,
|
|
5561
|
+
GRAVITY,
|
|
3924
5562
|
IMPACT_CONTRACT,
|
|
3925
5563
|
JOINT_BY_MATERIALS,
|
|
3926
5564
|
JOINT_DEFAULTS,
|
|
5565
|
+
MASONRY_DEFAULTS,
|
|
3927
5566
|
MATERIALS,
|
|
3928
|
-
PIERCING_BY_CALIBER,
|
|
3929
|
-
PIERCING_BY_KIND,
|
|
3930
5567
|
SHELTER_DEFAULTS,
|
|
5568
|
+
SOILS,
|
|
5569
|
+
SOIL_DEFAULTS,
|
|
5570
|
+
WIND_DEFAULTS,
|
|
5571
|
+
WIND_TERRAIN,
|
|
5572
|
+
analyzeWind,
|
|
3931
5573
|
assemblyDepth,
|
|
3932
5574
|
assemblyPart,
|
|
5575
|
+
bearingWallThickness,
|
|
5576
|
+
bendGeometry,
|
|
5577
|
+
blastImpulse,
|
|
3933
5578
|
buildFrame,
|
|
3934
5579
|
checkDestructible,
|
|
3935
5580
|
checkHit,
|
|
5581
|
+
crackingMoment,
|
|
3936
5582
|
createDestruction,
|
|
5583
|
+
damperAmplification,
|
|
5584
|
+
damperTuning,
|
|
5585
|
+
elasticEnergy,
|
|
3937
5586
|
explosivePart,
|
|
5587
|
+
exposureOf,
|
|
5588
|
+
footingArea,
|
|
5589
|
+
footingSoil,
|
|
3938
5590
|
fragility,
|
|
3939
5591
|
getAssembly,
|
|
3940
5592
|
guessMaterial,
|
|
5593
|
+
gustAt,
|
|
5594
|
+
heightFactor,
|
|
5595
|
+
impulseEnergy,
|
|
3941
5596
|
inferJoints,
|
|
5597
|
+
layersBetween,
|
|
3942
5598
|
locales,
|
|
5599
|
+
makeGust,
|
|
5600
|
+
meanHeightFactor,
|
|
5601
|
+
momentNodes,
|
|
5602
|
+
momentOfEnergy,
|
|
3943
5603
|
partCapacity,
|
|
3944
5604
|
partMass,
|
|
3945
5605
|
partThickness,
|
|
3946
5606
|
partVolume,
|
|
3947
|
-
|
|
5607
|
+
pileCapacity,
|
|
5608
|
+
pileLength,
|
|
5609
|
+
pileStiffness,
|
|
5610
|
+
profileOf,
|
|
3948
5611
|
quat,
|
|
3949
5612
|
quatFromEuler,
|
|
3950
5613
|
registerAssembly,
|
|
3951
5614
|
resolveStrings,
|
|
5615
|
+
resolveWind,
|
|
3952
5616
|
rotateVec,
|
|
5617
|
+
sectionMoments,
|
|
5618
|
+
shapeAt,
|
|
5619
|
+
shearNodes,
|
|
5620
|
+
sizeRebar,
|
|
3953
5621
|
sizeStructure,
|
|
5622
|
+
slendernessFactor,
|
|
5623
|
+
soilAt,
|
|
5624
|
+
solveFoundation,
|
|
5625
|
+
solveWallFrame,
|
|
5626
|
+
spanMoment,
|
|
5627
|
+
spanOf,
|
|
5628
|
+
steelMoment,
|
|
5629
|
+
stepSway,
|
|
5630
|
+
stiffnessCentre,
|
|
5631
|
+
subgradeModulus,
|
|
5632
|
+
tBeamInertia,
|
|
5633
|
+
tensionCapacity,
|
|
5634
|
+
tensionSteel,
|
|
3954
5635
|
toLocal,
|
|
3955
5636
|
toWorld,
|
|
3956
|
-
|
|
5637
|
+
turbulenceIntensity,
|
|
5638
|
+
vec,
|
|
5639
|
+
windClass,
|
|
5640
|
+
windPressure
|
|
3957
5641
|
};
|
|
3958
5642
|
//# sourceMappingURL=index.js.map
|