diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts index 8ddd04c7..5f707d51 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts @@ -112,8 +112,8 @@ export function materialFromForm(form: string | null | undefined): RevetMaterial /** 배관 기슭막이 높이 조작 하한(m) — 2~3m 구간 지시(2026-08-22)의 하한. */ export const PIPE_WALL_MIN_HEIGHT_M = 2.0; -/** 배관 기슭막이 기본 높이(m, 2026-08-22 지시 2.5) — 재질 한계로 잘린다(메→2.0). */ -export const PIPE_WALL_DEFAULT_HEIGHT_M = 2.5; +/** 배관 기슭막이 기본 높이(m) — 재질과 무관하게 2.0(2026-08-22 사용자 정정). */ +export const PIPE_WALL_DEFAULT_HEIGHT_M = 2.0; /** 일반(추가) 기슭막이 높이 조작 하한(m). */ export const EXTRA_WALL_MIN_HEIGHT_M = 0.5; /** 일반(추가) 기슭막이 기본 높이(m). */ diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts index 24e51616..2a167c55 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts @@ -217,8 +217,52 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult // 관통 금지: 상단 ≤ 윗단 하단 → 성토선 수평거리(경사부) ≥ 1.2×(시작−윗단하단). const autoJointRun = (autoOffset - src.offset) * outward - jointRunOf(height); const minJointRun = FILL_SLOPE_RATIO_MIN * Math.max(src.elevation - prevBottom, 0); - const appliedD = Math.max(adjust.d, Math.ceil((minJointRun - autoJointRun) * 10) / 10); - const appliedX = Math.max(adjust.x, 0); + const minD = Math.ceil((minJointRun - autoJointRun) * 10) / 10; + /** 후보 자리의 바닥(전면 근입 심화 반영) — buildExtraWall과 같은 계산. */ + const bottomAt = (anchorX: number, baseElevation: number): number => { + const baseWidth = REVET_THICKNESS_M * 1.5 + REVET_LEAN_RATIO * height; + const jointX = anchorX - outward * (baseWidth / 2 - REVET_THICKNESS_M / 2); + const topFrontX = jointX + outward * REVET_THICKNESS_M; + const topElevation2 = baseElevation + height; + const frontXAt = (e: number): number => + topFrontX + outward * REVET_LEAN_RATIO * (topElevation2 - e); + let bottom = baseElevation - REVET_EMBED_DEPTH_M; + if (baseElevation - groundAt(anchorX) <= 1e-6) { + for (let pass = 0; pass < 6; pass += 1) { + const toe = Math.min(groundAt(frontXAt(bottom)), baseElevation); + if (toe - REVET_EMBED_DEPTH_M >= bottom - 1e-6) break; + bottom = toe - REVET_EMBED_DEPTH_M; + } + } + return bottom; + }; + // 이동 한계 = 원지반 매몰 자리 금지(2026-08-22 사용자 확정): ① 이음선 상단이 + // 지반 아래(벽이 통째로 묻힘) ② 자기 성토선 시작점(실바닥+0.5 전면 교차점)이 + // 지반 아래 0.05m 이상(= 0.5m 이상 매몰 → 성토 불필요 자리). + const placeable = (xShift: number, dShift: number): boolean => { + const topE = src.elevation - (autoJointRun + dShift) / FILL_SLOPE_RATIO_MIN; + const aX = autoOffset + outward * (xShift + dShift); + const jointX = aX - outward * jointRunOf(height); + if (Math.max(groundAt(jointX), groundAt(jointX + outward * REVET_THICKNESS_M)) >= topE - 0.01) + return false; + const baseE = topE - height; + const bottom = bottomAt(aX, baseE); + const startE = bottom + REVET_EMBED_DEPTH_M; + const topFrontX = jointX + outward * REVET_THICKNESS_M; + const startX = topFrontX + outward * REVET_LEAN_RATIO * (topE - startE); + return groundAt(startX) < startE + 0.05; + }; + let appliedD = Math.max(adjust.d, minD); + let appliedX = Math.max(adjust.x, 0); + for (let pass = 0; pass < 400 && !placeable(appliedX, appliedD); pass += 1) { + // 매몰 자리 — 요청을 자동 자리 쪽으로 0.05m씩 되돌린다(상하 먼저, 다음 좌우). + if (appliedD > minD + 1e-9) appliedD = Math.max(minD, appliedD - 0.05); + else if (appliedX > 1e-9) appliedX = Math.max(0, appliedX - 0.05); + else break; + } + if (!placeable(appliedX, appliedD)) break; // 자동 자리조차 매몰 — 이 단은 불가. + appliedD = Math.round(appliedD * 10) / 10; + appliedX = Math.round(appliedX * 10) / 10; appliedAdjusts.push({ x: appliedX, d: appliedD, @@ -254,10 +298,14 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult ratio: rise > 1e-9 ? run / rise : FILL_SLOPE_RATIO_MIN, overLimit: Math.hypot(run, rise) >= FILL_SLOPE_MAX_LENGTH_M - 1e-6, }); - // 다음 단 시작점 = 이 벽 하단 +0.5m(기준선) 수평선과 전면 경사선(1:0.3)의 교차점. + // 다음 단 시작점 = 이 벽 **실제 바닥**(근입 심화 반영) +0.5m 수평선과 전면 + // 경사선(1:0.3)의 교차점(2026-08-22 사용자 — 관 끝점 규칙과 동일 기준). + const startElevation = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M; src = { - offset: wall.points[2].offset + outward * REVET_LEAN_RATIO * height, - elevation: base, + offset: + wall.points[2].offset + + outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - startElevation), + elevation: startElevation, }; prevBottom = wall.bottomBack.elevation; } diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts index 2155fde6..2582ddcd 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -461,12 +461,17 @@ export function computeCulvertLayout( const pipeStart = basinPipeEnd ?? inlet; /** 유출 관 하단 끝점 목표 = 벽 바닥 기준 0.5m 상단(기준선)과 **전면 경사선의 * 교차점**(2026-08-22 사용자 ① — 성토부선 시작 규칙과 같은 자리). */ - const outletPipeEnd = (wall: WallLayout): OffsetPoint => ({ - offset: - wall.points[2].offset + - wall.outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - wall.base), - elevation: wall.base, - }); + const outletPipeEnd = (wall: WallLayout): OffsetPoint => { + // 기준은 **실제 바닥**(전면 근입으로 깊어진 값) +0.5 — 기준선(anchor)을 쓰면 + // 가파른 지반에서 바닥만 내려가고 관이 벽 위쪽에 떠 보인다(2026-08-22 사용자 ②). + const reference = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M; + return { + offset: + wall.points[2].offset + + wall.outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - reference), + elevation: reference, + }; + }; // 유출 벽이 사면 끝과 다른 자리면 관 끝도 그 자리 기준으로 맞춘다(올림 연장분은 // 유출 쪽 — 2026-08-20 확정). 관 하단선은 전면 기준선 교차점을 지나 정수 길이로. if (outletWall) {