feat(B05): BOX암거 구조물 자리 원지반을 재노출한다 (영역 뺄셈 방식)
BOX암거(측점 200.92) 자리에서 구조물이 들어와도 원지반이 드러나지 않던 문제. 원지반 재노출 공식은 `원본 성토선 − (구조물 ∪ 변형 성토선)`인데, BOX는 변형 성토선이 원본과 기본값에서 정확히 collinear가 되어 결과가 공집합이었다. - Station_Structure: StructureSilhouette.endsAtStructure 신설, boxSilhouettes만 true. BOX 실루엣은 "구체 밖으로는 잇지 않는다" 원칙대로 구체 최상단에서 끊긴다. - Plan: endsAtStructure면 slope-deformed를 trimSlopeToGround로 지반까지 외삽하지 않는다. 종전엔 무조건 최대 30m 외삽해 원본 성토선과 겹쳤다. 기하(낙차 0.5m)로 판정하지 않는 이유는 기슭막이 유출측 회귀 때문이다(실측 84.3 우측 5.10 -> 5.69m). - Clip: 재노출 후보에 BOX의 wing-box(날개 사다리꼴) 추가 — 절취 소스에는 있고 되살릴 목록에는 없어 그 자리가 성토면도 지반도 없는 구멍이었다. 세월교는 제외 (그 자리는 되메움 패치가 덮는다). __corridorHoles에 exposeCells/exposeRatio 추가 — 고리 수만으로는 되살릴 영역이 공집합인지 알 수 없었다. - Structures/Structures_Box/Plan_Footprint: planOutline 표식(상판+에이프런)과 outlineLoop 신설. BOX structure 탑뷰 커브를 부재별 7고리가 아니라 아령 외곽선 한 줄로 낸다 — 부재별로 내면 날개벽이 패널 두께 슬리버로 남는다. 기하 불리언은 쓰지 않는다(BOX 부재는 전부 같은 고정축 위 평행 링). - BUILD_VERSION 80 -> 82. 실측(공용 브라우저, git stash 로 코드만 교체): - slope-deformed bbox 200.92 좌 2.85x4.46 -> 2.44x1.02, 우 2.76x3.76 -> 2.42x0.88. 세월교 149.73·배수관 84.3/275.71 은 소수점까지 불변 = 무회귀. - structure 고리 200.92 좌우 각 7 -> 1(면적 5.18 = 상판 1.79 + 에이프런 3.39 정확 일치). 슬리버 0.40x2, 0.15x2 소멸. - __corridorHoles.original 8 -> 12, covered 44 -> 32. - 지형 유지 삼각형 477,153 -> 477,613. - tsc --noEmit 0. 미해결: 날개벽 안쪽 검은 슬리버가 화면에 남아 있고 면이 삐져나온다(사용자 확인). 다음 커밋에서 접근을 바꾼다 — 영역 뺄셈 대신 성토 격자의 groundZ(원지반 표고)를 역마스크로 복사해 도려낸 셀에 그대로 깐다. 이 커밋은 그 되돌림 지점이다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -162,7 +162,28 @@ function fnv1a(text: string): string {
|
||||
// 벽 아래 다단·집수정 성토선만 있어 **기슭막이 상단부가 통째로 비었다**
|
||||
// (2026-08-27 사용자). 새 면이 아니라 실루엣이 이미 쓰던 `designTrim` 사면선
|
||||
// 그대로다. 벽 루프·집수정 분기가 같은 선을 담으므로 측당 한 번만 등록한다.
|
||||
const BUILD_VERSION = 80;
|
||||
// 81 = `slope-deformed` 커브를 **패치 리본과 같은 규칙**으로 끝낸다 — 실루엣 바깥
|
||||
// 끝이 그 측점 지반에 안 닿으면(BOX암거: 구체 최상단) 외삽하지 않고 실루엣이
|
||||
// 끝나는 자리를 바깥 끝으로 쓴다. 종전에는 이 커브만 무조건 최대 30m 외삽해
|
||||
// 성토 끝선까지 갔고, BOX는 기본값에서 변형 성토선이 원본 성토선과 정확히
|
||||
// collinear가 되어 `원본 − (구조물 ∪ 변형)` = ∅ — **구체 밖 원지반 재노출이
|
||||
// 통째로 미실행**이었다(2026-08-27 사용자 확정: "구체 밖은 원지반이어야 한다").
|
||||
// 실루엣이 구조물에서 끝나는지는 기하로 재지 않고 `endsAtStructure` 표식으로
|
||||
// 가른다 — 낙차로 재면 기슭막이 유출측처럼 지반보다 바깥으로 나간 실루엣까지
|
||||
// 걸려 원래 짧게 잘리던 것이 안 잘린다(실측 84.3 우측 5.10 → 5.69m).
|
||||
// 아울러 BOX 날개 사다리꼴(`wing-box`)을 원지반 재노출 후보에 넣는다 — 절취
|
||||
// 소스에는 있고 되살릴 목록에는 없어 그 자리가 성토면도 지반도 없는 구멍이었다
|
||||
// (2026-08-27 사용자: "날개부의 평면 스케치 영역까지 원지반이 같이 노출").
|
||||
// 세월교는 제외 — 그 자리는 되메움 패치가 덮는다(`wingRangeAt`, isFord 전용).
|
||||
// 82 = BOX암거 `structure` 탑뷰 커브를 부재별 7고리가 아니라 **아령 외곽선 한 줄**로
|
||||
// 낸다(2026-08-27 사용자: "바닥을 위에서 보면 아령형상이고 제일 외곽에 있는 선
|
||||
// 성분을 평면 투영하면 경계"). 상판+에이프런에 `planOutline` 표식을 달고
|
||||
// `_Plan_Footprint.ts`가 링 자리마다 편거리 창을 합친다 — 기하 불리언 없음
|
||||
// (BOX 부재는 전부 같은 고정축 위 평행 링이다). 종전 부재별 고리에는 날개벽이
|
||||
// **패널 두께 슬리버**로 섞여 있었고(실측 0.40㎡ / bbox 1.69×1.40 ×2), 그 슬리버가
|
||||
// "덮인 자리"로 빠져 그 자리만 성토면도 지형도 없는 검은 줄이 됐다. 에이프런은
|
||||
// 날개벽 발치를 얹으려고 넓혀 둔 부재라 외곽선이 날개를 이미 품는다.
|
||||
const BUILD_VERSION = 82;
|
||||
|
||||
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
|
||||
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import * as THREE from "three";
|
||||
import type { ModelBounds } from "./B05_Profile_UI_Markers";
|
||||
import type { CorridorBuildResult } from "./B05_Profile_UI_Corridor_Build";
|
||||
import type { PlanCurve } from "./B05_Profile_UI_Corridor_Plan";
|
||||
import type { TerrainBandSplit } from "./B05_Profile_UI_Corridor_Split";
|
||||
import type { P2, PlanRegion, RegionSegment } from "./B05_Profile_UI_Corridor_Region";
|
||||
import {
|
||||
@@ -385,17 +386,36 @@ function withStructureHoles(
|
||||
build: CorridorBuildResult,
|
||||
bounds: ModelBounds,
|
||||
): PlanRegion {
|
||||
const loopsOf = (sources: ReadonlyArray<string>): Array<Array<[number, number]>> => {
|
||||
const loopsOf = (
|
||||
sources: ReadonlyArray<string>,
|
||||
only?: (curve: PlanCurve) => boolean,
|
||||
): Array<Array<[number, number]>> => {
|
||||
const loops: Array<Array<[number, number]>> = [];
|
||||
for (const curve of build.planCurves ?? []) {
|
||||
if (!sources.includes(curve.source)) continue;
|
||||
if (only && !only(curve)) continue;
|
||||
for (const loop of curve.loops) {
|
||||
if (loop.length >= 4) loops.push(loop.map(([x, y]) => [x, y] as [number, number]));
|
||||
}
|
||||
}
|
||||
return loops;
|
||||
};
|
||||
const originalLoops = loopsOf(["slope-original"]);
|
||||
/**
|
||||
* BOX암거 **날개 사다리꼴**(`wing-box`)도 되살릴 후보에 넣는다(2026-08-27 사용자:
|
||||
* "박스암거의 날개부의 평면 스케치 영역까지 원지반이 같이 노출되어야 함").
|
||||
*
|
||||
* 그 자리는 절취 소스(`_Corridor_Cut.ts` `CUT_SOURCES`)에 들어 있어 성토면이
|
||||
* 파이는데, 되살릴 후보 목록(`slope-original`)에는 없어 **성토면도 원지반도 없는
|
||||
* 구멍**으로 남았다. 지우는 이름 목록과 되살리는 이름 목록이 어긋난 것이다.
|
||||
*
|
||||
* 세월교는 넣지 않는다 — 그 측 날개 자리는 되메움 패치가 덮는다(`_Corridor_Carve.ts`
|
||||
* `wingRangeAt`, `isFord` 전용). 지형까지 올리면 패치면과 같은 높이에서 겹쳐
|
||||
* z-fight가 난다. `axisLocked`가 곧 BOX 표식이다(`_Corridor_Plan.ts` `boxAxis`).
|
||||
*/
|
||||
const originalLoops = [
|
||||
...loopsOf(["slope-original"]),
|
||||
...loopsOf(["wing-box"], (curve) => curve.axisLocked === true),
|
||||
];
|
||||
if (!originalLoops.length) return strip;
|
||||
const original = new PlanLoopRegion(originalLoops, bounds);
|
||||
if (original.isEmpty) return strip;
|
||||
@@ -404,10 +424,25 @@ function withStructureHoles(
|
||||
const covered = coveredLoops.length ? new PlanLoopRegion(coveredLoops, bounds) : null;
|
||||
const expose: PlanRegion =
|
||||
covered && !covered.isEmpty ? new SubtractedRegion(original, [covered]) : original;
|
||||
// 검증용 요약(화면 밖 수치 확인) — 고리 수. `__corridorClip`과 같은 용도.
|
||||
// 검증용 요약(화면 밖 수치 확인) — `__corridorClip`과 같은 용도. **고리 수만으로는
|
||||
// 되살릴 영역이 실제로 남았는지 알 수 없다**: BOX암거에서 `covered`가 `original`을
|
||||
// 통째로 삼켜 공집합이 됐는데도 이 훅은 정상값을 찍었다(2026-08-27). 되살릴 영역을
|
||||
// 격자로 찍어 실적을 함께 담는다 — 코리도 빌드당 한 번이라 비용은 무시할 만하다.
|
||||
const SAMPLES = 80;
|
||||
let inside = 0;
|
||||
for (let i = 0; i < SAMPLES; i += 1) {
|
||||
const x = original.minX + ((original.maxX - original.minX) * (i + 0.5)) / SAMPLES;
|
||||
for (let j = 0; j < SAMPLES; j += 1) {
|
||||
const z = original.minZ + ((original.maxZ - original.minZ) * (j + 0.5)) / SAMPLES;
|
||||
if (expose.contains({ x, z })) inside += 1;
|
||||
}
|
||||
}
|
||||
(window as unknown as { __corridorHoles?: unknown }).__corridorHoles = {
|
||||
original: originalLoops.length,
|
||||
covered: coveredLoops.length,
|
||||
/** 되살릴 영역에 든 표본 점 수 / 전체 표본(원본 bbox 격자 80×80). 0이면 공집합. */
|
||||
exposeCells: inside,
|
||||
exposeRatio: Number((inside / (SAMPLES * SAMPLES)).toFixed(4)),
|
||||
};
|
||||
return new SubtractedRegion(strip, [expose]);
|
||||
}
|
||||
|
||||
@@ -502,10 +502,10 @@ export function buildPlanCurves(
|
||||
const inner = silhouette.widenTo?.offset_m ?? roadEdge;
|
||||
const from = section.chainage_m - silhouette.span.beforeM;
|
||||
const to = section.chainage_m + silhouette.span.afterM;
|
||||
const stationGround = groundInterpolator(section.samples);
|
||||
// "auto" = 실루엣 가운데가 지반보다 높으면 성토, 낮으면 절토(패치 리본과 같은 판정).
|
||||
let kind: "cut" | "fill" = silhouette.kind === "auto" ? "fill" : silhouette.kind;
|
||||
if (silhouette.kind === "auto") {
|
||||
const stationGround = groundInterpolator(section.samples);
|
||||
const mid = silhouette.points[Math.floor(silhouette.points.length / 2)];
|
||||
if (stationGround && mid.elevation_m < stationGround(mid.offset_m) - 0.05) kind = "cut";
|
||||
}
|
||||
@@ -523,9 +523,19 @@ export function buildPlanCurves(
|
||||
offset_m: point.offset_m,
|
||||
elevation_m: point.elevation_m + shift,
|
||||
}));
|
||||
const trimmed = trimSlopeToGround(placed, kind, ground);
|
||||
if (!trimmed) return null;
|
||||
const outer = trimmed[trimmed.length - 1].offset_m;
|
||||
// **구조물에서 끝나는 실루엣**(BOX암거 — 구체 최상단 모서리)은 지반까지
|
||||
// 연장하지 않는다. 실루엣이 끝나는 자리가 곧 변형 성토면이 끝나는 자리이고,
|
||||
// 그 바깥은 구조물 솔리드와 **원지반** 몫이다(2026-08-27 사용자 확정).
|
||||
// 연장하면 변형 성토선이 원본 성토선과 겹쳐 원지반 재노출 공식
|
||||
// `원본 − (구조물 ∪ 변형)`이 공집합이 된다(`_Corridor_Clip.ts`).
|
||||
let outer: number;
|
||||
if (silhouette.endsAtStructure) {
|
||||
outer = placed[placed.length - 1].offset_m;
|
||||
} else {
|
||||
const trimmed = trimSlopeToGround(placed, kind, ground);
|
||||
if (!trimmed) return null;
|
||||
outer = trimmed[trimmed.length - 1].offset_m;
|
||||
}
|
||||
if ((outer - inner) * sign <= MIN_WIDTH_M) return null;
|
||||
return [at(frame, inner), at(frame, outer)];
|
||||
});
|
||||
|
||||
@@ -98,6 +98,90 @@ function wingSide(rings: ReadonlyArray<StructureFrame>, axis: StationAxis): Side
|
||||
return routeOffset(axis, middle.cx, middle.cy) >= 0 ? "left" : "right";
|
||||
}
|
||||
|
||||
/** 링 한 줄 + 그 링의 **축 방향 좌표** — 부재끼리 링 자리를 맞추는 기준이다. */
|
||||
interface AxisRow extends Row {
|
||||
t: number;
|
||||
}
|
||||
|
||||
/** 고정축을 따라 잰 좌표 — 좌향 벡터에 수직인 방향. 부호는 일관되기만 하면 된다. */
|
||||
function axisCoord(axis: StationAxis, cx: number, cy: number): number {
|
||||
return (cx - axis.cx) * axis.leftY - (cy - axis.cy) * axis.leftX;
|
||||
}
|
||||
|
||||
/** 그 부재의 축 좌표 `t`에서의 편거리 창 — 링 사이는 선형 보간, 범위 밖은 null. */
|
||||
function spanAt(rows: ReadonlyArray<AxisRow>, t: number): Span | null {
|
||||
if (t < rows[0].t - EPS_M || t > rows[rows.length - 1].t + EPS_M) return null;
|
||||
for (let i = 0; i < rows.length - 1; i += 1) {
|
||||
const a = rows[i];
|
||||
const b = rows[i + 1];
|
||||
if (t < a.t - EPS_M || t > b.t + EPS_M) continue;
|
||||
const width = b.t - a.t;
|
||||
const k = width > EPS_M ? (t - a.t) / width : 0;
|
||||
return [a.span[0] + (b.span[0] - a.span[0]) * k, a.span[1] + (b.span[1] - a.span[1]) * k];
|
||||
}
|
||||
return rows[rows.length - 1].span;
|
||||
}
|
||||
|
||||
/**
|
||||
* **아령 외곽선**(2026-08-27 사용자) — `planOutline` 표식이 붙은 부재(BOX암거 상판 +
|
||||
* 에이프런)를 하나로 합친 탑뷰 경계 고리. 표식이 없으면 `null`.
|
||||
*
|
||||
* 사용자 원문: "박스암거의 경우에는 바닥을 위에서 보면 아령형상이고 제일 외곽에 있는
|
||||
* 선 성분을 가져가 평면 투영하면 경계가 되고 이걸 경계로 사용하면 가능할 것 같은데?"
|
||||
*
|
||||
* 기하 불리언은 여전히 쓰지 않는다(옛 `unionOutline`이 겹친 스트립에서 무너진 이유는
|
||||
* 이 파일 머리말 참조). BOX 부재는 전부 **같은 고정축 위 평행 링**이라, 링 자리마다
|
||||
* 각 부재의 편거리 창을 재서 **제일 바깥·제일 안쪽**을 취하면 그게 곧 외곽선이다.
|
||||
* 퇴화(겹침·포함·맞닿은 변)가 생길 여지가 없다.
|
||||
*/
|
||||
function outlineLoop(
|
||||
set: ReadonlyArray<CorridorStructure>,
|
||||
side: Side,
|
||||
roadEdgeOffset: number,
|
||||
planZ: number,
|
||||
axis: StationAxis,
|
||||
): Array<[number, number, number]> | null {
|
||||
const members: AxisRow[][] = [];
|
||||
for (const structure of set) {
|
||||
if (!structure.planOutline) continue;
|
||||
const rings = structure.rings;
|
||||
if (!rings || rings.length < 2) continue;
|
||||
const rows: AxisRow[] = [];
|
||||
rings.forEach((frame, index) => {
|
||||
const span = polygonSpan(structure.polygons?.[index] ?? structure.polygon);
|
||||
if (span) rows.push({ frame, span, t: axisCoord(axis, frame.cx, frame.cy) });
|
||||
});
|
||||
if (rows.length >= 2) members.push(rows.sort((a, b) => a.t - b.t));
|
||||
}
|
||||
if (!members.length) return null;
|
||||
// 링 자리는 부재마다 다르다(상판 ±halfSpan·±innerHalf, 에이프런 ±reach·±halfSpan) —
|
||||
// 전부 모아 그 자리마다 창을 합친다. 프레임은 그 자리에 실제로 링이 있는 부재 것을
|
||||
// 그대로 쓴다(전부 같은 축이라 프레임 보간이 필요 없다).
|
||||
const stops = new Map<number, StructureFrame>();
|
||||
for (const rows of members) {
|
||||
for (const row of rows) {
|
||||
const key = Math.round(row.t / EPS_M) * EPS_M;
|
||||
if (!stops.has(key)) stops.set(key, row.frame);
|
||||
}
|
||||
}
|
||||
const rows: Row[] = [];
|
||||
for (const t of [...stops.keys()].sort((a, b) => a - b)) {
|
||||
let lo = Infinity;
|
||||
let hi = -Infinity;
|
||||
for (const member of members) {
|
||||
const span = spanAt(member, t);
|
||||
if (!span) continue;
|
||||
if (span[0] < lo) lo = span[0];
|
||||
if (span[1] > hi) hi = span[1];
|
||||
}
|
||||
if (hi - lo <= EPS_M) continue;
|
||||
const clipped = clipOutsideRoad([lo, hi], side, roadEdgeOffset);
|
||||
const frame = stops.get(t);
|
||||
if (clipped && frame) rows.push({ frame, span: clipped });
|
||||
}
|
||||
return runLoop(rows, side, planZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조물 세트의 그 측 탑뷰 외곽 고리들 — **부재마다 따로**, 도로부(노견 안쪽)는 뺀다.
|
||||
*
|
||||
@@ -114,6 +198,11 @@ export function structureFootprintLoops(
|
||||
planZ: number,
|
||||
axis: StationAxis,
|
||||
): Array<Array<[number, number, number]>> {
|
||||
// 아령 외곽선을 낼 수 있는 세트(BOX암거)는 **그 한 줄만** 낸다 — 부재별 고리를
|
||||
// 함께 내면 날개 패널 슬리버가 다시 섞여 지형 재노출에 구멍을 만든다.
|
||||
// 저판·측벽은 상판 폭 안, 날개는 에이프런 안이라 외곽선이 전부 품는다.
|
||||
const outline = outlineLoop(set, side, roadEdgeOffset, planZ, axis);
|
||||
if (outline) return [outline];
|
||||
const loops: Array<Array<[number, number, number]>> = [];
|
||||
for (const structure of set) {
|
||||
if (structure.kind === "pipe") continue;
|
||||
|
||||
@@ -67,6 +67,22 @@ export interface StructureSilhouette {
|
||||
* 솟는다. 세월교만 싣는다(다른 구조물은 안쪽 확장 대상이 아니다).
|
||||
*/
|
||||
floorTopZ?: number;
|
||||
/**
|
||||
* 실루엣이 **구조물에서 끝나는가**(2026-08-27 사용자 확정 — BOX암거 "구체 밖은
|
||||
* 원지반이어야 한다"). BOX는 구체 최상단 모서리에서 선을 끊는다 — 2D 횡단도가
|
||||
* 거기서 끝나고 그 아래·바깥은 구체·에이프런·날개벽과 **원지반** 몫이기 때문이다.
|
||||
*
|
||||
* 이 표식이 붙은 실루엣은 평면 변형 성토선(`_Corridor_Plan.ts` `slope-deformed`)을
|
||||
* 낼 때 `trimSlopeToGround`로 지반까지 **연장하지 않는다**. 연장하면 변형 성토선이
|
||||
* 원본 성토선과 겹쳐(BOX 기본값에선 정확히 collinear) 원지반 재노출 공식
|
||||
* `원본 − (구조물 ∪ 변형)`이 공집합이 되고, 구체 바깥이 성토면도 원지반도 없는
|
||||
* 빈 자리로 남는다.
|
||||
*
|
||||
* 기하로 판정하지 않는 이유: "바깥 끝이 지반에 닿는가"로 재면 기슭막이 유출측처럼
|
||||
* **지반보다 바깥으로 나간** 실루엣까지 걸려, 원래 짧게 잘리던 것이 안 잘린다
|
||||
* (실측 84.3 우측 폭 5.10 → 5.69m 확대). 끊는 게 설계인 구조물만 명시적으로 단다.
|
||||
*/
|
||||
endsAtStructure?: boolean;
|
||||
}
|
||||
|
||||
/** 실루엣에서 노견 확장점 찾기 — 첫 점(노견) 표고 그대로 바깥으로 나간 가장 먼 점. */
|
||||
@@ -122,6 +138,9 @@ export function boxSilhouettes(section: CrossSection): StructureSilhouette[] {
|
||||
widenTo: widenToOf(points, side.role),
|
||||
// 구체 최상단에서 끝나는 성토선 한 줄 — BOX는 다단이 없다.
|
||||
fillRuns: [points],
|
||||
// 선이 구체 최상단에서 끊긴다 — 그 바깥은 원지반 몫이므로 평면 변형
|
||||
// 성토선도 여기서 끝내야 한다(연장하면 원지반 재노출이 상쇄된다).
|
||||
endsAtStructure: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -82,6 +82,22 @@ export interface CorridorStructure {
|
||||
* `"route"` = 측벽 끝단부 자리의 노선 프레임을 탄 날개(세월교).
|
||||
*/
|
||||
wing?: "station" | "route";
|
||||
/**
|
||||
* 탑뷰 **외곽 경계**를 이루는 부재(2026-08-27 사용자) — BOX암거 **상판 + 에이프런**.
|
||||
*
|
||||
* 위에서 보면 상판(구체 폭 직사각형)과 양쪽 에이프런(날개 각도만큼 벌어진
|
||||
* 사다리꼴)이 **아령 형상**을 이루고, 그 제일 바깥 선이 곧 구조물이 지면을 덮는
|
||||
* 경계다(사용자: "바닥을 위에서 보면 아령형상이고 제일 외곽에 있는 선 성분을
|
||||
* 평면 투영하면 경계"). 표식이 붙은 부재들은 `_Corridor_Plan_Footprint.ts`가
|
||||
* 링별로 편거리 창을 합쳐 **고리 한 줄**로 낸다.
|
||||
*
|
||||
* 부재별로 따로 내면 날개벽이 **패널 두께짜리 슬리버**로 남고, 그 슬리버가
|
||||
* 지형 재노출 공식에서 "덮인 자리"로 빠져 그 자리만 성토면도 지형도 없는
|
||||
* 구멍이 됐다(2026-08-27 실측: 면적 0.40㎡ / bbox 1.69×1.40 대각 슬리버 ×2).
|
||||
* 에이프런은 "날개벽 발치가 바닥에 얹히도록" 그만큼 넓혀 둔 부재라 날개
|
||||
* 풋프린트를 이미 품는다 — 아령 외곽선 하나면 슬리버가 사라진다.
|
||||
*/
|
||||
planOutline?: boolean;
|
||||
/**
|
||||
* 성토끝 박스 옆선을 그을 **바깥 방향**(단위벡터, 좌향 기준) — 세월교 전용.
|
||||
* 없으면 기준 측점의 좌향을 쓴다(BOX암거 현행 — 2026-08-26 사용자 "박스 암거는
|
||||
|
||||
@@ -183,6 +183,8 @@ export function boxSolids(
|
||||
kind: "basin",
|
||||
polygons: repeat(layout.topSlab, bodyRings.length),
|
||||
rings: bodyRings,
|
||||
// 아령 외곽선의 **손잡이** — 에이프런과 합쳐 탑뷰 경계를 이룬다(2026-08-27 사용자).
|
||||
planOutline: true,
|
||||
},
|
||||
{
|
||||
chainage_m: chainage,
|
||||
@@ -247,6 +249,9 @@ export function boxSolids(
|
||||
chainage,
|
||||
stationFrame,
|
||||
),
|
||||
// 아령 외곽선의 **원판** — 날개벽 발치를 품는 부재라 이것만 있으면 날개
|
||||
// 슬리버를 따로 낼 필요가 없다(2026-08-27 사용자).
|
||||
planOutline: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user