fix(B05): 3D 제안 ② 비탈 끝 드레이프 — 115-7 로 짧아진 행의 설계면 높이 끝과 바깥 지형 턱 없앰(측점 아닌 행 · 지반 차 0.1 m 넘는 행 · 측점 행은 B06 그대로)(115-8)
- 성토 닿은 70° 삼각형 760 → 150(226) · 측점 오차 그대로 - test_115_6 드레이프 단언 하나 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -38,8 +38,8 @@ export const SECTION_STEP_M = 1;
|
||||
const MIN_CATCH_M = 0.02;
|
||||
/** 비탈 끝에서 이 안쪽 꺾임만 점으로 둔다(m). */
|
||||
const BEND_GAP_M = 0.05;
|
||||
/** 비탈 끝 단면선 높이가 지반과 이만큼 안이면 지반 높이(m) — 못 찾은 끝(단면 끝)은 설계 높이. */
|
||||
const DAYLIGHT_SNAP_M = 0.1;
|
||||
/** 비탈 끝 높이와 지반 차가 이보다 작으면 끝 점만 지반으로(단면 안 보정 없음 · 측점 단면 그대로)(m). */
|
||||
const DRAPE_MIN_M = 0.1;
|
||||
/** 윗면으로 보는 높이 여유(m) · 윗면 최소 폭(m). */
|
||||
const TOP_TOLERANCE_M = 0.05;
|
||||
const TOP_MIN_WIDTH_M = 0.05;
|
||||
@@ -169,8 +169,9 @@ export function buildTinDesign(
|
||||
pslg.addEdge(anchors[0][0], anchors[1][0], EDGE_ANCHOR);
|
||||
pslg.addEdge(anchors[0][n - 1], anchors[1][n - 1], EDGE_ANCHOR);
|
||||
|
||||
/** 그 행 · 측 단면의 설계 높이(모델) — 성토는 지반 위로 · 절토는 아래로만 · 성토 금지 고리. */
|
||||
const designZ = (r: number, si: number, ki: number, s: number): number | null => {
|
||||
/** 그 행 · 측 단면의 설계 높이(모델) — 성토는 지반 위로 · 절토는 아래로만 · 성토 금지 고리.
|
||||
* `lift` = 드레이프 보정(비탈 끝에서 지형에 맞춘 몫 × s / 비탈 끝). */
|
||||
const designZ = (r: number, si: number, ki: number, s: number, lift = 0): number | null => {
|
||||
const row = rows[r];
|
||||
const anchor = si === 0 ? row.left : row.right;
|
||||
const shapes = source(row.chainage_m, SIDES[si]);
|
||||
@@ -180,7 +181,7 @@ export function buildTinDesign(
|
||||
if (rel === null) return null;
|
||||
const [x, y] = field.pointAt(r, si, s);
|
||||
const ground = groundAt(x, y);
|
||||
let z = anchor[2] + rel;
|
||||
let z = anchor[2] + rel + lift;
|
||||
if (ground !== null) {
|
||||
z = ki === 0 ? Math.max(z, ground) : Math.min(z, ground);
|
||||
for (const limit of limits) {
|
||||
@@ -200,9 +201,13 @@ export function buildTinDesign(
|
||||
return reach >= MIN_CATCH_M ? { ki, reach } : null;
|
||||
};
|
||||
|
||||
const isStation = (chainage: number): boolean =>
|
||||
stationChainages.some((c) => Math.abs(c - chainage) < 1e-3);
|
||||
|
||||
// 비탈 끝 점 · 곡선 안쪽 겹침 청소 — 앞 행들 단면 선분에 닿으면 처음 닿는 자리에서 자른다(겹친 점 제거).
|
||||
const kindOf = SIDES.map(() => new Int8Array(n).fill(-1));
|
||||
const reachOf = SIDES.map(() => new Float64Array(n));
|
||||
const liftOf = SIDES.map(() => new Float64Array(n));
|
||||
const daylight = SIDES.map(() => new Int32Array(n).fill(-1));
|
||||
SIDES.forEach((_, si) => {
|
||||
const fan = new SegGrid(2);
|
||||
@@ -224,8 +229,14 @@ export function buildTinDesign(
|
||||
const [px, py] = field.pointAt(r, si, reach);
|
||||
const line = designZ(r, si, slope.ki, reach);
|
||||
const ground = groundAt(px, py);
|
||||
// 드레이프(115-8 · 제안 ③ 과 같은 규칙) — 비탈 끝 높이 = 그 자리 지형 · 보정량은 단면에 s 비율로.
|
||||
// 비탈 끝이 짧아진 행(115-7 튐 자름 · 못 닫힘)에서 설계면 높이 끝이 바깥 지형과 턱 지던 자리.
|
||||
// 측점 행은 B06 단면 그대로(끝 점만 0.1 m 안이면 지반).
|
||||
const step = ground !== null && line !== null ? ground - line : 0;
|
||||
const station = isStation(rows[r].chainage_m);
|
||||
if (hit >= 1 && !station && Math.abs(step) >= DRAPE_MIN_M) liftOf[si][r] = step;
|
||||
const z =
|
||||
hit >= 1 && ground !== null && (line === null || Math.abs(line - ground) < DAYLIGHT_SNAP_M)
|
||||
hit >= 1 && ground !== null && (!station || line === null || Math.abs(step) < DRAPE_MIN_M)
|
||||
? ground
|
||||
: (line ?? ground ?? rows[r].left[2]);
|
||||
daylight[si][r] = pslg.addPoint(sx(px), sz(py), z - oz, slope.ki === 0 ? TAG_FILL : TAG_CUT);
|
||||
@@ -261,8 +272,6 @@ export function buildTinDesign(
|
||||
.map((id): [number, number] => [pslg.x[id], pslg.z[id]]);
|
||||
|
||||
// 단면 행 — 측점 + 1 m 걸음. 횡방향 선(노면 끝 → 꺾임 → 비탈 끝) · 같은 열 꺾임 종방향 선.
|
||||
const isStation = (chainage: number): boolean =>
|
||||
stationChainages.some((c) => Math.abs(c - chainage) < 1e-3);
|
||||
const sectionRows: number[] = [];
|
||||
for (let r = 0; r < n; r += 1) {
|
||||
const chainage = rows[r].chainage_m;
|
||||
@@ -292,7 +301,7 @@ export function buildTinDesign(
|
||||
const path = [anchors[si][r]];
|
||||
for (const [col, s] of field.bendsAt(r, si, ki)) {
|
||||
if (s <= MIN_CATCH_M || s >= reach - BEND_GAP_M) continue;
|
||||
const z = designZ(r, si, ki, s);
|
||||
const z = designZ(r, si, ki, s, (liftOf[si][r] * s) / reach);
|
||||
if (z === null) continue;
|
||||
const [x, y] = field.pointAt(r, si, s);
|
||||
const id = pslg.addPoint(sx(x), sz(y), z - oz, ki === 0 ? TAG_FILL : TAG_CUT);
|
||||
|
||||
@@ -382,7 +382,20 @@ const r6 = (v) => Math.round(v * 1e6) / 1e6;
|
||||
}
|
||||
toeErr = Math.max(toeErr, best === Infinity ? 99 : best);
|
||||
}
|
||||
// 드레이프(115-8) — 측점 아닌 행의 비탈 끝(둘레 점)은 그 자리 망 지형 높이.
|
||||
let offGround = 0;
|
||||
let daylightRows = 0;
|
||||
out.design.outer.forEach(([dx, dz]) => {
|
||||
const tag = tagAt.get(fkey(dx, dz));
|
||||
if (tag !== PSLG.TAG_FILL && tag !== PSLG.TAG_CUT) return;
|
||||
if ([-20, 0, 20].some((x) => Math.abs(dx - x) < 1e-6)) return;
|
||||
const i = [...Array(pslg.count).keys()].find((k) => pslg.x[k] === dx && pslg.z[k] === dz);
|
||||
daylightRows += 1;
|
||||
if (Math.abs(pslg.y[i] - heightIndex.heightAt(dx, dz)) > 1e-4) offGround += 1;
|
||||
});
|
||||
results.straight = {
|
||||
daylightRows,
|
||||
offGround,
|
||||
stats: out.stats,
|
||||
gaps: gap.gaps,
|
||||
maxGapLen: r6(gap.maxGapLen),
|
||||
|
||||
@@ -41,6 +41,12 @@ def test_straight_station_section_matches_b06(results):
|
||||
assert r["toeErr"] < 0.06 # 비탈 끝 자리(시험 걸음 0.05 m 안)
|
||||
|
||||
|
||||
def test_straight_daylight_draped(results):
|
||||
r = results["straight"]
|
||||
assert r["daylightRows"] > 300
|
||||
assert r["offGround"] == 0 # 측점 아닌 행 비탈 끝 = 망 지형 높이(드레이프 · 115-8)
|
||||
|
||||
|
||||
def test_straight_terrain_outside_untouched(results):
|
||||
r = results["straight"]
|
||||
assert r["originalUntouched"] is True # 원본 정점 버퍼 그대로
|
||||
|
||||
Reference in New Issue
Block a user