fix(B05): 원지반 재노출 영역을 실제로 도려낸 자리 윤곽(cut-merged)으로 바꾼다
구조물 자리에 원지반이 드러나지 않거나, 드러나도 날개벽 자리에 성토면도 지형도
없는 검은 슬리버가 남던 문제.
원인은 지우는 쪽과 되살리는 쪽이 **서로 다른 자료로 영역을 만든 것**이다.
- 지우는 쪽: 성토 리본 셀 마스크(slope-projected ∪ wing-box, 0.2m 격자 + 현 절단)
- 되살리는 쪽: 원본 성토 스케치 − (구조물 ∪ 변형 성토 스케치)를 **다시 조립**
구조물 고리에는 날개벽이 패널 두께 슬리버로 섞여 있어 그 자리만 covered로 빠졌고,
BOX암거에서는 변형 성토선이 원본과 collinear가 되어 영역이 통째로 공집합이었다.
이제 지우는 쪽이 남긴 자국을 그대로 쓴다 — cut-merged 는 _Corridor_Cut.ts 의
traceMaskLoops 가 셀 마스크에서 되짚은 윤곽이다. 그 안의 지형은 도려내지 않으므로
**원본 지형 메시가 그 자리에 그대로 남는다**. 새 서피스를 만들지 않는다
(2026-08-27 사용자: "원지반 오리지널 데이터에서 가져오라니까", "회색 서피스는
있는데 원지반과 같은 메시가 아니잖아. 너가 그냥 만든 거잖아").
- Clip: withStructureHoles 를 cut-merged 한 줄로 대체. slope-original/wing-box/
structure/slope-deformed 조합과 axisLocked 분기 삭제(순 -20줄). __corridorHoles
는 고리 수 대신 exposeLoops/exposeCells/exposeRatio 를 담는다.
- BUILD_VERSION 82 -> 83.
실측(공용 브라우저):
- cut-merged 고리 8개 = 구조물 4세트 x 좌우.
- exposeCells 23/6400(0.36%) -> 164/6400(2.56%). 7배.
- 지형 유지 삼각형 474,854 -> 476,885 (+2,031).
- tsc --noEmit 0.
폐기한 시도(같은 브랜치 2d897164 이후 작업트리에서만 존재했다):
성토 격자에 원지반 표고를 재샘플링해 역마스크로 까는 방식. 경계는 정의상
일치했으나 코리도 격자로 다시 만든 면이라 원지반 메시와 형상이 달랐다.
남은 것: 유출구 일부 성토부 터짐(사용자 확인) — 다음 작업.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -183,7 +183,13 @@ function fnv1a(text: string): string {
|
|||||||
// **패널 두께 슬리버**로 섞여 있었고(실측 0.40㎡ / bbox 1.69×1.40 ×2), 그 슬리버가
|
// **패널 두께 슬리버**로 섞여 있었고(실측 0.40㎡ / bbox 1.69×1.40 ×2), 그 슬리버가
|
||||||
// "덮인 자리"로 빠져 그 자리만 성토면도 지형도 없는 검은 줄이 됐다. 에이프런은
|
// "덮인 자리"로 빠져 그 자리만 성토면도 지형도 없는 검은 줄이 됐다. 에이프런은
|
||||||
// 날개벽 발치를 얹으려고 넓혀 둔 부재라 외곽선이 날개를 이미 품는다.
|
// 날개벽 발치를 얹으려고 넓혀 둔 부재라 외곽선이 날개를 이미 품는다.
|
||||||
const BUILD_VERSION = 82;
|
// 83 = 원지반 재노출 영역을 **`cut-merged`**(성토면에서 실제로 도려낸 셀의 윤곽)로
|
||||||
|
// 바꾼다. 종전에는 `원본 성토 스케치 − (구조물 ∪ 변형 성토 스케치)`로 영역을
|
||||||
|
// **다시 조립**해, 지우는 쪽과 되살리는 쪽이 다른 커브를 쓰며 경계가 어긋났다
|
||||||
|
// (날개벽 패널 두께 슬리버 자리에 검은 줄). 이제 지우는 쪽이 남긴 자국을 그대로
|
||||||
|
// 쓰므로 **원본 지형 메시가 그 자리에 그대로 남는다** — 새 서피스를 만들지 않는다
|
||||||
|
// (2026-08-27 사용자: "원지반 오리지널 데이터에서 가져오라니까").
|
||||||
|
const BUILD_VERSION = 83;
|
||||||
|
|
||||||
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
|
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
|
||||||
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
|
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type { ModelBounds } from "./B05_Profile_UI_Markers";
|
import type { ModelBounds } from "./B05_Profile_UI_Markers";
|
||||||
import type { CorridorBuildResult } from "./B05_Profile_UI_Corridor_Build";
|
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 { TerrainBandSplit } from "./B05_Profile_UI_Corridor_Split";
|
||||||
import type { P2, PlanRegion, RegionSegment } from "./B05_Profile_UI_Corridor_Region";
|
import type { P2, PlanRegion, RegionSegment } from "./B05_Profile_UI_Corridor_Region";
|
||||||
import {
|
import {
|
||||||
@@ -371,76 +370,51 @@ function clipPoints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 구조물 안착 자리에서 **원지반을 되살린다**(2026-08-27 사용자 확정 공식) —
|
* 구조물 안착 자리에서 **원지반을 되살린다** — 그 자리 지형을 도려내지 않고 **원본
|
||||||
|
* 지형 메시 그대로** 남긴다(2026-08-27 사용자: "원지반 오리지널 데이터에서 가져오라니까",
|
||||||
|
* "회색 서피스는 있는데 원지반과 같은 메시가 아니잖아. 너가 그냥 만든 거잖아").
|
||||||
*
|
*
|
||||||
* 되살릴 영역 = 원본 성토사면 스케치(`slope-original`, 파란 커브)
|
* 되살릴 영역 = **`cut-merged`** — 성토면에서 **실제로 도려낸 셀**을 되짚어 낸 윤곽이다
|
||||||
* − (구조물 평면 투영 스케치(`structure`) ∪ 변형 성토사면 스케치(`slope-deformed`))
|
* (`_Corridor_Cut.ts` `traceMaskLoops`). 지우는 쪽이 남긴 자국을 그대로 쓰므로, 구조물
|
||||||
|
* 풋프린트로 영역을 다시 조립할 필요가 없다.
|
||||||
*
|
*
|
||||||
* 원래 성토면이 덮던 자리에서 지금도 무언가(구조물 또는 변형 성토면)가 덮는 자리를
|
* 종전에는 `원본 성토 스케치 − (구조물 ∪ 변형 성토 스케치)`로 영역을 **다시 만들었다**.
|
||||||
* 빼면, 성토가 사라져 원지반이 드러나야 하는 자리만 남는다. **변형 성토사면이 없는
|
* 지우는 쪽(성토 셀 마스크)과 되살리는 쪽(구조물 고리)이 서로 다른 커브를 써 경계가
|
||||||
* 세트도 있다**(사용자) — 그때는 구조물 스케치만 뺀다. 원본 커브가 없으면 되살릴
|
* 어긋났고, 날개벽이 패널 두께 슬리버로 남아 그 자리만 성토면도 지형도 없는 검은 줄이
|
||||||
* 것도 없다(스트립 그대로).
|
* 됐다. BOX암거에서는 변형 성토선이 원본과 collinear가 되어 영역이 통째로 공집합이 됐다.
|
||||||
|
*
|
||||||
|
* `cut-merged`가 없으면(절취를 안 했으면) 되살릴 것도 없다 — 스트립 그대로.
|
||||||
*/
|
*/
|
||||||
function withStructureHoles(
|
function withStructureHoles(
|
||||||
strip: CorridorStrip,
|
strip: CorridorStrip,
|
||||||
build: CorridorBuildResult,
|
build: CorridorBuildResult,
|
||||||
bounds: ModelBounds,
|
bounds: ModelBounds,
|
||||||
): PlanRegion {
|
): PlanRegion {
|
||||||
const loopsOf = (
|
const loops: Array<Array<[number, number]>> = [];
|
||||||
sources: ReadonlyArray<string>,
|
for (const curve of build.planCurves ?? []) {
|
||||||
only?: (curve: PlanCurve) => boolean,
|
if (curve.source !== "cut-merged") continue;
|
||||||
): Array<Array<[number, number]>> => {
|
for (const loop of curve.loops) {
|
||||||
const loops: Array<Array<[number, number]>> = [];
|
if (loop.length >= 4) loops.push(loop.map(([x, y]) => [x, y] as [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;
|
}
|
||||||
};
|
if (!loops.length) return strip;
|
||||||
/**
|
const expose = new PlanLoopRegion(loops, bounds);
|
||||||
* BOX암거 **날개 사다리꼴**(`wing-box`)도 되살릴 후보에 넣는다(2026-08-27 사용자:
|
if (expose.isEmpty) return strip;
|
||||||
* "박스암거의 날개부의 평면 스케치 영역까지 원지반이 같이 노출되어야 함").
|
// 검증용 요약(화면 밖 수치 확인) — `__corridorClip`과 같은 용도. 고리 수만으로는
|
||||||
*
|
// 되살릴 영역이 실제로 남았는지 알 수 없어(BOX에서 공집합인데도 정상값이 찍혔다)
|
||||||
* 그 자리는 절취 소스(`_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;
|
|
||||||
// 지금도 덮여 있는 자리 — 구조물 ∪ 변형 성토사면(없을 수 있다).
|
|
||||||
const coveredLoops = loopsOf(["structure", "slope-deformed"]);
|
|
||||||
const covered = coveredLoops.length ? new PlanLoopRegion(coveredLoops, bounds) : null;
|
|
||||||
const expose: PlanRegion =
|
|
||||||
covered && !covered.isEmpty ? new SubtractedRegion(original, [covered]) : original;
|
|
||||||
// 검증용 요약(화면 밖 수치 확인) — `__corridorClip`과 같은 용도. **고리 수만으로는
|
|
||||||
// 되살릴 영역이 실제로 남았는지 알 수 없다**: BOX암거에서 `covered`가 `original`을
|
|
||||||
// 통째로 삼켜 공집합이 됐는데도 이 훅은 정상값을 찍었다(2026-08-27). 되살릴 영역을
|
|
||||||
// 격자로 찍어 실적을 함께 담는다 — 코리도 빌드당 한 번이라 비용은 무시할 만하다.
|
|
||||||
const SAMPLES = 80;
|
const SAMPLES = 80;
|
||||||
let inside = 0;
|
let inside = 0;
|
||||||
for (let i = 0; i < SAMPLES; i += 1) {
|
for (let i = 0; i < SAMPLES; i += 1) {
|
||||||
const x = original.minX + ((original.maxX - original.minX) * (i + 0.5)) / SAMPLES;
|
const x = expose.minX + ((expose.maxX - expose.minX) * (i + 0.5)) / SAMPLES;
|
||||||
for (let j = 0; j < SAMPLES; j += 1) {
|
for (let j = 0; j < SAMPLES; j += 1) {
|
||||||
const z = original.minZ + ((original.maxZ - original.minZ) * (j + 0.5)) / SAMPLES;
|
const z = expose.minZ + ((expose.maxZ - expose.minZ) * (j + 0.5)) / SAMPLES;
|
||||||
if (expose.contains({ x, z })) inside += 1;
|
if (expose.contains({ x, z })) inside += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(window as unknown as { __corridorHoles?: unknown }).__corridorHoles = {
|
(window as unknown as { __corridorHoles?: unknown }).__corridorHoles = {
|
||||||
original: originalLoops.length,
|
/** 되살릴 영역 고리 수 = 실제로 도려낸 자리 윤곽(`cut-merged`) 개수. */
|
||||||
covered: coveredLoops.length,
|
exposeLoops: loops.length,
|
||||||
/** 되살릴 영역에 든 표본 점 수 / 전체 표본(원본 bbox 격자 80×80). 0이면 공집합. */
|
|
||||||
exposeCells: inside,
|
exposeCells: inside,
|
||||||
exposeRatio: Number((inside / (SAMPLES * SAMPLES)).toFixed(4)),
|
exposeRatio: Number((inside / (SAMPLES * SAMPLES)).toFixed(4)),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user