Files
Aislo/E03_StabSection/logic/E03_StabSection_Support.ts
T

222 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* E03_StabSection_Support.ts
* E03 보강 검토 네일 · 앵커 화면 계산(PLAN 536-3 뒷부분) — 화면 · fetch · sessionStorage 없음 · 순수 함수만.
* 배치(행 수 · 첫 행 높이 · 수직 · 수평 간격 · 제원) → supports 목록(서버 POST whatif 입력 꼴 그대로) ·
* 네일 선 그림 좌표(머리 · 끝 · 정착 구간 · 임계 활동면과 만나는 점).
* 좌표 = 서버 결과 그대로(x = B06 offset · 좌 + · z = 표고). 수평 방향 = 그 비탈 활동 방향 반대(비탈 속으로).
* 높이 = 비탈 밑에서 위로 — 깎기 밑 = 노면(중심 표고) · 쌓기 밑 = 노면 − 비탈 높이.
* ========================================================================== */
import type { Point } from "./E03_StabSection_Logic";
export type SupportType = "nail" | "anchor";
export type SupportMode = "tensile" | "pullout" | "stripping" | "not_crossing" | "prestress";
/** 서버 · 엔진 입력 꼴(브레인 정함 · sub4 엔진 같은 이름) */
export interface SupportIn {
type: SupportType;
side: string;
x: number;
z: number;
/** 수평 아래로 + */
angle_deg: number;
length_m: number;
/** 종방향 간격 — 단위 폭 힘 = 힘 / 간격 */
spacing_m: number;
tensile_kN: number;
/** 그라우트 · 지반 부착(인발 안전율 넣은 값 · 엔진이 안 나눔) — 네일 = 전 길이 · 앵커 = 정착장만 */
bond_kPa: number;
/** 앵커 정착장 · 네일은 무시 */
bond_length_m: number;
hole_dia_mm: number;
/** 앵커만 · 0 = 수동 */
prestress_kN: number;
}
export interface SupportForce {
index: number;
mobilized_kN_per_m: number;
mode: SupportMode;
}
/** 화면 배치 입력 */
export interface SupportLayout {
type: SupportType;
rows: number;
first_m: number;
v_spacing_m: number;
h_spacing_m: number;
length_m: number;
angle_deg: number;
tensile_kN: number;
bond_kPa: number;
bond_length_m: number;
hole_dia_mm: number;
prestress_kN: number;
}
/** 기본값 후보(사용자 확정 전) — 지식DB 안정성평가 02 §4(천공 φ100 · 풍화토 주면마찰 50~100 kPa ·
* SD400 D25 허용 0.6 fy·As = 121.6 kN · 부착 = 극한 주면마찰 ÷ 인발 안전율 2.0) ·
* 앵커 = 강연선 φ12.7 × 4 허용 0.6 fpu = 441 kN · 풍화암 주면마찰 150 kPa ÷ 2.0 흔한 값 */
export const SUPPORT_DEFAULTS: Record<SupportType, SupportLayout> = {
nail: {
type: "nail",
rows: 3,
first_m: 1.0,
v_spacing_m: 1.5,
h_spacing_m: 1.5,
length_m: 6.0,
angle_deg: 15,
tensile_kN: 121.6,
bond_kPa: 40,
bond_length_m: 0,
hole_dia_mm: 100,
prestress_kN: 0,
},
anchor: {
type: "anchor",
rows: 2,
first_m: 1.5,
v_spacing_m: 2.5,
h_spacing_m: 2.5,
length_m: 12.0,
angle_deg: 30,
tensile_kN: 441,
bond_kPa: 75,
bond_length_m: 6.0,
hole_dia_mm: 115,
prestress_kN: 200,
},
};
/** 비탈 면 위 표고 z 인 점의 x — 노면 중심(x = 0)에서 그 쪽 바깥으로 처음 만나는 곳 */
export function faceX(surface: Point[], side: string, z: number): number | null {
const s = side === "left" ? 1 : -1;
const pts = surface
.filter((p) => s * p[0] >= 0)
.slice()
.sort((a, b) => s * a[0] - s * b[0]);
for (let i = 1; i < pts.length; i += 1) {
const [a, b] = [pts[i - 1], pts[i]];
if ((a[1] - z) * (b[1] - z) <= 0 && a[1] !== b[1]) {
return a[0] + ((z - a[1]) / (b[1] - a[1])) * (b[0] - a[0]);
}
}
return null;
}
function roadZ(surface: Point[]): number | null {
const pts = surface.slice().sort((a, b) => a[0] - b[0]);
for (let i = 1; i < pts.length; i += 1) {
const [a, b] = [pts[i - 1], pts[i]];
if (a[0] <= 0 && b[0] >= 0)
return b[0] === a[0] ? a[1] : a[1] + (-a[0] / (b[0] - a[0])) * (b[1] - a[1]);
}
return null;
}
/** 배치 → supports — 행 높이 = 첫 행 + i × 수직 간격(비탈 높이 안만) · 머리 = 그 높이의 비탈 면 */
export function layoutSupports(
surface: Point[],
side: string,
role: "cut" | "fill",
heightM: number | null,
layout: SupportLayout,
): SupportIn[] {
const road = roadZ(surface);
if (road === null) return [];
const H = heightM ?? Infinity;
const base = role === "cut" ? road : road - (heightM ?? 0);
const out: SupportIn[] = [];
for (let i = 0; i < Math.max(0, Math.floor(layout.rows)); i += 1) {
const h = layout.first_m + i * layout.v_spacing_m;
if (h <= 0 || h >= H) break;
const z = base + h;
const x = faceX(surface, side, z);
if (x === null) break;
out.push({
type: layout.type,
side,
x: round(x),
z: round(z),
angle_deg: layout.angle_deg,
length_m: layout.length_m,
spacing_m: layout.h_spacing_m,
tensile_kN: layout.tensile_kN,
bond_kPa: layout.bond_kPa,
bond_length_m: layout.type === "anchor" ? layout.bond_length_m : 0,
hole_dia_mm: layout.hole_dia_mm,
prestress_kN: layout.type === "anchor" ? layout.prestress_kN : 0,
});
}
return out;
}
const round = (v: number): number => Math.round(v * 1000) / 1000;
export interface SupportDraw {
index: number;
type: SupportType;
head: Point;
tail: Point;
/** 부착 구간 — 네일 = 전 길이 · 앵커 = 끝에서 정착장 */
bond: [Point, Point];
/** 임계 활동면과 만나는 점(없으면 null) */
cross: Point | null;
/** 결과 mode · 결과 없음 = "pending" */
mode: SupportMode | "pending";
force: number | null;
}
/** 두 선분 교점(a→b 위 비율 t 와 함께) */
function hit(a: Point, b: Point, c: Point, d: Point): [number, Point] | null {
const r = [b[0] - a[0], b[1] - a[1]];
const q = [d[0] - c[0], d[1] - c[1]];
const den = r[0] * q[1] - r[1] * q[0];
if (Math.abs(den) < 1e-12) return null;
const t = ((c[0] - a[0]) * q[1] - (c[1] - a[1]) * q[0]) / den;
const u = ((c[0] - a[0]) * r[1] - (c[1] - a[1]) * r[0]) / den;
if (t < 0 || t > 1 || u < 0 || u > 1) return null;
return [t, [a[0] + t * r[0], a[1] + t * r[1]]];
}
/** 선분이 꺾은선과 처음(머리 쪽) 만나는 점 */
export function crossPoint(head: Point, tail: Point, line: Point[]): Point | null {
let best: [number, Point] | null = null;
for (let i = 1; i < line.length; i += 1) {
const h = hit(head, tail, line[i - 1], line[i]);
if (h && (!best || h[0] < best[0])) best = h;
}
return best ? best[1] : null;
}
/** 그림 좌표 — direction = 그 비탈 활동 방향(+1 = +x 쪽으로 미끄러짐) */
export function supportDraws(
supports: SupportIn[],
direction: number,
critical: Point[] | null,
forces: SupportForce[] | null | undefined,
): SupportDraw[] {
const into = direction >= 0 ? -1 : 1;
return supports.map((s, index) => {
const a = (s.angle_deg * Math.PI) / 180;
const ux = into * Math.cos(a);
const uz = -Math.sin(a);
const head: Point = [s.x, s.z];
const at = (d: number): Point => [round(s.x + ux * d), round(s.z + uz * d)];
const tail = at(s.length_m);
const bondFrom = s.type === "anchor" ? Math.max(0, s.length_m - s.bond_length_m) : 0;
const f = forces?.find((v) => v.index === index) ?? null;
return {
index,
type: s.type,
head,
tail,
bond: [at(bondFrom), tail],
cross: critical ? crossPoint(head, tail, critical) : null,
mode: f ? f.mode : "pending",
force: f ? f.mobilized_kN_per_m : null,
};
});
}