Files
Aislo/B06_Section/B06_Section_UI_Cross_Wall.ts
T

156 lines
6.2 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.
/* =============================================================================
* B06_Section_UI_Cross_Wall.ts
* 기슭막이 벽 **공용 기하 + 그리기** — 배관 세트 벽(`_Cross_Culvert_Geom`/`_Culvert`)과
* 독립 기슭막이(`_Cross_Revetment`)가 같은 산식·같은 도형을 쓰도록 뽑아낸 층이다
* (2026-08-28 사용자: "배관용에서 배관만 빼면 대부분 동일해야"). 벽 1매의 합성 단면
* (하부 사다리꼴 + 상부 평행사변형, 배면 수직·전면 1:0.3)과 그 돌쌓기 표현은 여기 하나뿐.
*
* 배관 벽 계산은 아직 `_Cross_Culvert_Geom`이 자체 인라인으로 만든다 — 결과가 이 함수와
* 동일함을 화면으로 확인한 뒤 그쪽도 이 함수로 바꾼다(계획서 후속). 지금은 독립 기슭막이가
* 이 함수를 써서 배관 벽과 같은 모양·자리로 선다.
* ========================================================================== */
import {
materialLabel,
REVET_EMBED_DEPTH_M,
REVET_LEAN_RATIO,
REVET_THICKNESS_M,
} from "./B06_Section_UI_Cross_Culvert_Const";
import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const";
import type { OffsetPoint, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types";
import { appendWallHatch } from "./B06_Section_UI_Cross_Wall_Hatch";
const SVG_NS = "http://www.w3.org/2000/svg";
function polygon(
points: Array<[number, number]>,
className: string,
tooltip: string,
): SVGPolygonElement {
const shape = document.createElementNS(SVG_NS, "polygon");
shape.setAttribute("points", points.map(([px, py]) => `${px},${py}`).join(" "));
shape.setAttribute("class", className);
if (tooltip) {
const title = document.createElementNS(SVG_NS, "title");
title.textContent = tooltip;
shape.append(title);
}
return shape;
}
/** 벽 1매의 기하 입력 — 자리 기준은 **하단선 중점**(anchor.offset)과 그 자리 기준표고
* (anchor.elevation = 관 invert 또는 지반). 벽은 그 위로 `height`만큼 선다. */
export interface RevetWallGeometryInput {
/** 하단선 중점 offset + 기준표고(벽이 이 위로 선다). */
anchor: OffsetPoint;
/** +1 = 화면 좌측(계류측), 1 = 우측. 전면 1:0.3이 이 부호로 기운다. */
outward: number;
/** 계산용 높이(기준표고~상단, 근입 0.5 제외). */
height: number;
material: RevetMaterial;
role: "inlet" | "outlet" | "extra";
/** 표기용 형태 — 미지정이면 재질 라벨(메/찰/콘크리트). */
form?: string | null;
lengthM?: number | null;
thickness?: number;
floatGapM?: number;
extraIndex?: number;
/** 전면 기울기 1:n 의 n — 표준경사(`leanOf`). 없으면 종전 0.3. */
lean?: number;
}
/**
* 합성 단면(하부 사다리꼴 + 상부 평행사변형) 꼭짓점을 만든다. 배관 벽(`buildWall`,
* `_Cross_Culvert_Geom` 335~376행)과 **같은 산식** — 배면 수직, 상단 변 = 사다리꼴
* 상단(t/2)+띠(t), 전면 1:0.3, 하단선 = 기준표고 −0.5m 수평 기초.
*/
export function buildRevetWallGeometry(input: RevetWallGeometryInput): WallLayout {
const { anchor, outward, height, material, role } = input;
const thickness = input.thickness ?? REVET_THICKNESS_M;
const lean = input.lean ?? REVET_LEAN_RATIO;
const baseWidth = thickness * 1.5 + lean * height;
const backOffset = anchor.offset - outward * (baseWidth / 2);
const topJoint = backOffset + outward * (thickness / 2);
const topElevation = anchor.elevation + height;
const topBack: OffsetPoint = { offset: backOffset, elevation: topElevation };
const topFront = topJoint + outward * thickness;
const frontXAt = (elevation: number): number =>
topFront + outward * lean * (topElevation - elevation);
const bottomElevation = anchor.elevation - REVET_EMBED_DEPTH_M;
const bottomBack: OffsetPoint = { offset: backOffset, elevation: bottomElevation };
const bottomFront: OffsetPoint = {
offset: frontXAt(bottomElevation),
elevation: bottomElevation,
};
return {
role,
extraIndex: input.extraIndex,
form: input.form ?? materialLabel(material),
lengthM: input.lengthM ?? null,
backOffset,
outerOffset: bottomFront.offset,
base: anchor.elevation,
height,
floatGapM: input.floatGapM ?? 0,
material,
lean,
outward,
topBack,
topJoint: { offset: topJoint, elevation: topElevation },
bottomBack,
bottomFront,
points: [bottomBack, topBack, { offset: topFront, elevation: topElevation }, bottomFront],
};
}
/**
* 벽 1매를 SVG로 그린다 — 폴리곤 + 대각 이음선 + 계류측 기운 띠의 돌쌓기 해칭.
* 배관 오버레이(`_Cross_Culvert` 161~246행)와 **같은 그리기**. 선택·강조 배선은 호출자
* 몫이라 여기서는 본체 폴리곤만 돌려준다. `keyId`는 clipPath id 중복 방지용.
*/
export function drawRevetWall(
layer: SVGElement,
wall: WallLayout,
x: (offset: number) => number,
toDisplayY: (elevation: number) => number,
className: string,
tooltip: string,
keyId: string,
): SVGPolygonElement {
const revetShape = polygon(
wall.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]),
className,
tooltip,
);
layer.append(revetShape);
// 이음선 + **형태별 표현**(돌/콘크리트/돌망태/통나무/바자)은 공용 해칭이 맡는다
// (2026-08-30 사용자 지시 3) — 배관 오버레이와 같은 함수를 쓴다.
appendWallHatch(layer, wall, x, toDisplayY, keyId);
return revetShape;
}
/** 성토·절토 접속선(공사 계획선) 폴리라인 — 설계선과 같은 보라 실선. */
export function appendPlanLine(
layer: SVGElement,
points: OffsetPoint[],
x: (offset: number) => number,
toDisplayY: (elevation: number) => number,
tooltip: string,
className = "b06-chart__design-cross",
): void {
if (points.length < 2) return;
const line = document.createElementNS(SVG_NS, "polyline");
line.setAttribute(
"points",
points.map((p) => `${x(p.offset)},${toDisplayY(p.elevation)}`).join(" "),
);
line.setAttribute("class", className);
if (tooltip) {
const title = document.createElementNS(SVG_NS, "title");
title.textContent = tooltip;
line.append(title);
}
layer.append(line);
}