Files
Aislo/resources/tester/old/helper_115_7_fill_continuity.cjs
T
eomsangdonandClaude Opus 5.5 5d774ad6d7 refactor(b05): 3D 방식 등록부 · 고르기 줄 · 옛 A · 새 ① · 제안 ② 를 Old 로 — ③ 고정(135-1 가)
- 뷰어 = loftTerrain 바로 부름 · ③ 못 만들면 원지형 그대로(_Clip 도 Old)
- 통째 이동 = _Modes · _Legacy · _Deform_Terrain · _Deform_Breakline · _Clip
- 잘라 냄(원본은 Old 같은 이름) = 뷰어코리도 · 보기줄 · _Corridor · _Build legacy · _Station silhouette · _Deform(roadRows 만) · _Deform_Structure(noFillLimits 만) · _Tin(deformDesign) · _Tin_Design(buildTinDesign) · _Region(CellRegion 까지)
- b_page_state 키 셋(corridor-deform · legacy · mode) 정리
- 시험 6개 + 도우미 5 = resources/tester/old/(수집 뺌) · test_115_8 등록부 단언 = ③ 고정 단언

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
2026-10-09 14:52:49 +09:00

211 lines
7.6 KiB
JavaScript
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.
/* PLAN 115-7 단위검증 헬퍼 — B05 3D 성토 비탈 끝 종단 이음(공통 변형 장 · 새 ① · 제안 ②).
* TS 를 그 자리에서 트랜스파일해 Node 로 돌린다. 결과는 표준출력에 JSON 한 벌.
*
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_curve_radius_floor.cjs` 머리와 같다.
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const THREE = require(path.join(ROOT, "config", "node_modules", "three"));
const cache = new Map();
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (request === "three") return THREE;
if (request.startsWith("@ui/") || request.startsWith("@config/")) {
return new Proxy({}, { get: () => () => undefined });
}
const target = request.startsWith("@util/")
? path.join(ROOT, "common_util", request.slice(6))
: path.join(path.dirname(full), request);
if (request.endsWith(".json")) return JSON.parse(fs.readFileSync(target, "utf8"));
return loadTs(fs.existsSync(`${target}.ts`) ? `${target}.ts` : path.join(target, "index.ts"));
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
const B05 = (name) => path.join(ROOT, "B05_Profile", `${name}.ts`);
const { buildCorridor } = loadTs(B05("B05_Profile_UI_Corridor_Build"));
const deform = loadTs(B05("B05_Profile_UI_Corridor_Deform"));
const r4 = (v) => (v === null || v === undefined ? v : Math.round(v * 10000) / 10000);
const HALF = 3; // 노면부 반폭(차도 2.5 + 노견 0.5)
const ROAD_Z = 100;
/** x 축 직선 노선 측점 — 좌 성토(1:1.5 · 4 m 아래 · 비탈 끝 9) · 우 절토(1:1 · 4 m 위 · 비탈 끝 −7). */
function station(chainage) {
const pts = [
[-7, 104],
[-HALF, ROAD_Z],
[-2.5, ROAD_Z],
[2.5, ROAD_Z],
[HALF, ROAD_Z],
[9, 96],
];
const samples = [];
for (let o = -30; o <= 30; o += 1) samples.push({ offset_m: o, elevation_m: o > 0 ? 96 : 104 });
return {
station_id: `S${chainage}`,
chainage_m: chainage,
label: "",
kind: "regular",
center_x: chainage,
center_y: 0,
center_z: ROAD_Z,
frame: { left_xy: [0, 1] },
samples,
design: {
section_mode: "right_cut",
road_edges: {
left: { offset_m: HALF, elevation_m: ROAD_Z },
right: { offset_m: -HALF, elevation_m: ROAD_Z },
},
carriageway_edges: {
left: { offset_m: 2.5, elevation_m: ROAD_Z },
right: { offset_m: -2.5, elevation_m: ROAD_Z },
},
design_line: pts.map(([offset_m, elevation_m]) => ({ offset_m, elevation_m })),
ditch: { type: "none" },
ditch_enabled: false,
ditch_side: "left",
},
};
}
const SECTIONS = [0, 20, 40, 60, 80, 100].map(station);
function fieldOf(groundAt) {
const build = buildCorridor(SECTIONS, [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
]);
build.sections = SECTIONS;
const field = deform.buildDeformField(build, deform.stationProfileSource(SECTIONS), groundAt);
return field;
}
/** 행 · 측 성토 비탈 끝 열(노선 길이 순). */
function catches(field, si, ki) {
return field.rows.map((row, r) => ({ ch: row.chainage_m, c: field.catchAt(r, si, ki) }));
}
/** 지느러미 행 — 성토 행끼리 ±3 m 중앙값 + 3 m 넘음. */
function fins(list) {
let count = 0;
for (const { ch, c } of list) {
if (c <= 0) continue;
const near = list
.filter((o) => o.c > 0 && Math.abs(o.ch - ch) <= 3)
.map((o) => o.c)
.sort((a, b) => a - b);
if (c > near[near.length >> 1] + 3) count += 1;
}
return count;
}
const wobble = (list) => {
let sum = 0;
for (let i = 1; i < list.length; i += 1) {
if (list[i].c > 0 && list[i - 1].c > 0) sum += Math.abs(list[i].c - list[i - 1].c);
}
return (sum / (list[list.length - 1].ch - list[0].ch)) * 100;
};
const results = {};
// ① 도로에 직각인 골짜기 — 좁은 골(49~51 m) · 넓은 골(64~72 m)에서 지반이 3 m 더 깊다. 옛 규칙은
// 골 행만 연장 구배로 10.5 m 까지 뻗어 지느러미 · 수직 벽. 새 규칙은 이웃 좋은 행(6 m)으로 보간 +
// 비탈 끝 밖 2 m 덧댐. 우측 절토는 같은 골을 그대로 따른다(절토는 안 바꿈).
{
const gully = (x) => (x > 49 && x < 51) || (x > 64 && x < 72);
const groundAt = (x, y) => {
if (y >= 0) return gully(x) && y > 5 ? 93 : 96;
return gully(x) && y < -5 ? 101 : 104;
};
const field = fieldOf(groundAt);
const fill = catches(field, 0, 0);
const inGully = fill.filter((o) => gully(o.ch));
const cutGully = catches(field, 1, 1).filter((o) => gully(o.ch) && o.ch > 50 && o.ch < 50.5);
const zAt = (x, s) => field.deformZ(x, HALF + s, groundAt(x, HALF + s));
results.valley = {
fins: fins(fill),
gullyMax: r4(Math.max(...inGully.map((o) => o.c))),
gullyMin: r4(Math.min(...inGully.map((o) => o.c))),
skirtMid: r4(zAt(68, 7)), // 비탈 끝(6) 밖 1 m — 96 에서 93 으로 반쯤
skirtOut: r4(zAt(68, 8.5)), // 덧댐 밖 — 원지반 그대로
outside: r4(zAt(30, 8)), // 골 밖 비탈 끝 밖 — 원지반
cutGully: r4(Math.max(...cutGully.map((o) => o.c))),
cutFlat: r4(
field.catchAt(
field.rows.findIndex((row) => row.chainage_m >= 30),
1,
1,
),
),
};
}
// ② 계단 지형 — 1 m 띠마다 지반이 0.6 m 오르내린다(비탈 끝 6 ↔ 5.1). 원뿔 넓히기로 출렁임이 준다.
{
const step = (x) => (Math.floor(x) % 2 ? 0.6 : 0);
const groundAt = (x, y) => (y >= 0 ? 96 + step(x) : 104);
const field = fieldOf(groundAt);
const fill = catches(field, 0, 0).filter((o) => o.ch > 1 && o.ch < 99);
// 날것 출렁임 — 띠 경계마다 0.9 m(6 − 5.1) 씩.
const raw = fill.map((o) => ({ ch: o.ch, c: step(o.ch) ? 5.1 : 6 }));
results.steps = { wobble: r4(wobble(fill)), rawWobble: r4(wobble(raw)), fins: fins(fill) };
}
// ③ ④ ⑤ 열 단위 — 못 닫힘 보간 · 측점 행 날것 · 0 행 끊음 / 짧은 틈 이음.
{
const n = 101;
const ch = Array.from({ length: n }, (_, i) => i * 0.2);
const raw = new Array(n).fill(6);
const open = new Array(n).fill(0);
const station = new Array(n).fill(0);
const shaped = new Array(n).fill(1);
for (let i = 20; i <= 24; i += 1) {
raw[i] = 15;
open[i] = 1;
}
station[50] = 1;
raw[50] = 9; // 측점 행 — 튀어도 날것
station[60] = 1;
raw[60] = 20;
open[60] = 1; // 못 닫힘 측점 — 보간
for (let i = 70; i <= 72; i += 1) raw[i] = 0; // 짧은 틈(0.4 m) — 단면 있음 → 잇는다
for (let i = 80; i <= 82; i += 1) {
raw[i] = 0;
shaped[i] = 0; // 단면 없음 → 끊음
}
const out = deform.smoothFillCatches(ch, raw, open, station, shaped);
const gap = [];
for (let i = 0; i < n; i += 1) if (raw[i] === 0 && shaped[i] === 0) gap.push(out[i]);
results.column = {
openMax: r4(Math.max(...out.slice(20, 25))),
stationKept: r4(out[50]),
stationOpen: r4(out[60]),
bridged: r4(Math.min(...out.slice(70, 73))),
cutGap: r4(Math.max(...gap)),
neighbourStep: r4(
Math.max(
...out
.slice(1)
.map((c, i) => (out[i] > 0 && c > 0 ? Math.abs(c - out[i]) / 0.2 : 0))
.filter((_, i) => i < 49 || i > 51),
),
),
};
}
process.stdout.write(JSON.stringify(results));