diff --git a/B06_Section/B06_Section_UI_Cross_Culvert.ts b/B06_Section/B06_Section_UI_Cross_Culvert.ts index fad32e45..a3070292 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert.ts @@ -109,6 +109,8 @@ export function appendCulvertOverlay( ...layout.walls.filter((w) => w.role === "inlet"), ]; const revetShapes = new Map(); + /** 집수정 부재 도형 — 유입 선택 강조에 쓴다(2026-08-22 사용자 ③). */ + const basinShapes: SVGPolygonElement[] = []; for (const wall of wallsInOrder) { // 합성 단면(하부 사다리꼴 + 상부 평행사변형) — 상단 배면이 사면선 접점(사용자 ①·②). const revetShape = polygon( @@ -204,14 +206,14 @@ export function appendCulvertOverlay( ? "사용자 선택 집수정(규칙상 기슭막이 자리)" : "유입측 성토사면 3m 이하 — 기슭막이 대신 집수정 기본"; for (const part of basin.parts) { - layer.append( - polygon( - part.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]), - "b06-chart__culvert-basin", - `유입 집수정(${shapeName}) ${part.kind === "floor" ? "바닥" : "벽"}` + - ` — ${reasonText} · 내공 1.0m(실무 돌집수정), 벽은 기슭막이와 동일 단면`, - ), + const shape = polygon( + part.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]), + "b06-chart__culvert-basin", + `유입 집수정(${shapeName}) ${part.kind === "floor" ? "바닥" : "벽"}` + + ` — ${reasonText} · 내공 1.0m(실무 돌집수정) · 부재 두께 0.2m`, ); + basinShapes.push(shape); + layer.append(shape); } if (basin.cutLine) { const cut = document.createElementNS(SVG_NS, "line"); @@ -227,6 +229,20 @@ export function appendCulvertOverlay( cut.append(cutTitle); layer.append(cut); } + if (basin.fillLine) { + // 구조물이 원지반 밖으로 나온 경우의 성토(되메움) 계획선 — 경사 5도(임시값, + // 2026-08-22 사용자 지정). + const fill = document.createElementNS(SVG_NS, "line"); + fill.setAttribute("x1", String(x(basin.fillLine.from.offset))); + fill.setAttribute("y1", String(toDisplayY(basin.fillLine.from.elevation))); + fill.setAttribute("x2", String(x(basin.fillLine.to.offset))); + fill.setAttribute("y2", String(toDisplayY(basin.fillLine.to.elevation))); + fill.setAttribute("class", "b06-chart__design-cross"); + const fillTitle = document.createElementNS(SVG_NS, "title"); + fillTitle.textContent = `집수정(${shapeName}) 성토(되메움)선 — 구조물이 원지반 밖으로 나와 5° 경사로 채움`; + fill.append(fillTitle); + layer.append(fill); + } const label = document.createElementNS(SVG_NS, "text"); label.setAttribute("x", String(x(basin.label.offset))); // 라벨은 I형 벽 **하단** 바깥에 붙인다(2026-08-20 사용자) — 글자 높이만큼 내린다. @@ -333,5 +349,7 @@ export function appendCulvertOverlay( // 강조는 클래스만 갈아 끼운다 — 카드를 다시 그리면 휠 줌·팬이 초기화된다. return (key) => { for (const [role, shape] of revetShapes) shape.classList.toggle("is-active", role === key); + // 유입이 집수정이면 부재 전체를 함께 강조한다(2026-08-22 사용자 ③). + for (const shape of basinShapes) shape.classList.toggle("is-active", key === "inlet"); }; } diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Basin.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Basin.ts index 2da976d3..2db3690e 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Basin.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Basin.ts @@ -54,8 +54,12 @@ export interface BasinBuildResult { trimElevation: number; } -/** 실무 내공 폭(m) — 울진 돌집수정. */ -const INNER_WIDTH_M = 1.0; +/** 실무 내공 폭(m) — 울진 돌집수정. I형 관 시작점(노견+1.0m) 계산에도 쓴다. */ +export const BASIN_INNER_WIDTH_M = 1.0; +const INNER_WIDTH_M = BASIN_INNER_WIDTH_M; + +/** 집수정 주변 성토(되메움) 표면 경사(도) — 2026-08-22 사용자 지정 "일단 5도". */ +const BASIN_FILL_ANGLE_DEG = 5; /** 집수정 단면을 만든다. 형태별 규칙은 파일 머리말 참조. */ export function buildBasin(input: BasinBuildInput): BasinBuildResult { @@ -112,19 +116,43 @@ export function buildBasin(input: BasinBuildInput): BasinBuildResult { ], }); } - // ㄷ형 = ㄴ형 + 반대측(도로측) 막음벽(I형 수직반전 — 2026-08-22 사용자 확정). + // ㄷ형 = ㄴ형 + 막음벽 — I형 벽을 **바닥부 중심 기준 수직 반전**한 자리·형상 + // (2026-08-22 사용자 ① — 종전에는 노견 쪽에 세워 방향이 틀렸다). + const floorCenter = (floorTopInner + floorOuter) / 2; + const mirroredWallBase = 2 * floorCenter - wallBase; if (shape === "U") { - parts.push({ kind: "wall", points: wallOf(anchor.offset, -outward) }); + parts.push({ kind: "wall", points: wallOf(mirroredWallBase, -outward) }); } - // ㄴ·ㄷ형이 원지반 **안쪽**에 박히면 그만큼 절토가 필요하다(2026-08-20 사용자 ①). + // 구조물 계류측 끝(ㄴ형 = 바닥 바깥 끝 상단, ㄷ형 = 막음벽 계류측 상단 꼭짓점)과 + // 원지반의 관계(2026-08-22 사용자 ①): + // · 원지반 안쪽에 박히면 → 표준단면 절토경사(1:n)로 **절토선** + // · 원지반 밖으로 나오면 → **성토(되메움)선** — 경사 5도(임시값)로 지반까지. let basinCut: BasinLayout["cutLine"] = null; + let basinFill: BasinLayout["cutLine"] = null; if (shape !== "I") { - const outerTop = { offset: floorOuter, elevation: anchor.elevation }; - if (groundAt(floorOuter) > anchor.elevation + 0.05) { + const refTop = + shape === "U" + ? { + offset: mirroredWallBase + outward * REVET_LEAN_RATIO * wallHeight, + elevation: anchor.elevation + wallHeight, + } + : { offset: floorOuter, elevation: anchor.elevation }; + const groundHere = groundAt(refTop.offset); + if (groundHere > refTop.elevation + 0.05) { for (let h = 0.05; h <= 20; h += 0.05) { - const probe = floorOuter + outward * cutSlopeRatio * h; - if (groundAt(probe) <= anchor.elevation + h) { - basinCut = { from: outerTop, to: { offset: probe, elevation: anchor.elevation + h } }; + const probe = refTop.offset + outward * cutSlopeRatio * h; + if (groundAt(probe) <= refTop.elevation + h) { + basinCut = { from: refTop, to: { offset: probe, elevation: refTop.elevation + h } }; + break; + } + } + } else if (groundHere < refTop.elevation - 0.05) { + const dropPerM = Math.tan((BASIN_FILL_ANGLE_DEG * Math.PI) / 180); + for (let run = 0.05; run <= 30; run += 0.05) { + const probe = refTop.offset + outward * run; + const elevation = refTop.elevation - dropPerM * run; + if (elevation <= groundAt(probe)) { + basinFill = { from: refTop, to: { offset: probe, elevation } }; break; } } @@ -141,6 +169,7 @@ export function buildBasin(input: BasinBuildInput): BasinBuildResult { }, reason, cutLine: basinCut, + fillLine: basinFill, }, // 관 끝선 = I형 벽의 안쪽(물받는 쪽 = 내공측) 변 복사(2026-08-22 사용자 확정). endFace: { diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts index 7699e6c9..4994bd05 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -45,7 +45,7 @@ import type { export * from "./B06_Section_UI_Cross_Culvert_Const"; export * from "./B06_Section_UI_Cross_Culvert_Types"; -import { buildBasin } from "./B06_Section_UI_Cross_Culvert_Basin"; +import { BASIN_INNER_WIDTH_M, buildBasin } from "./B06_Section_UI_Cross_Culvert_Basin"; import type { FillSlopeSegment, WallVertical } from "./B06_Section_UI_Cross_Culvert_Solve"; import { clampToFace, @@ -146,6 +146,12 @@ export function computeCulvertLayout( // 벽 높이로만 노견에 연결한다(buildWall). if (basinReason && basinShape !== "I") { inlet.elevation = inletInfo.edge.elevation_m - BASIN_INNER_HEIGHT_M; + } else if (basinReason) { + // I형 — 관 시작점(내공 1.0m 자리) invert는 **그 x의 원지반 위**에 앉힌다(토피 + // 상한 이내). 종전에는 노견 x의 원지반을 써서 지반이 오르내리는 자리에서 관이 + // 지반에 파묻히거나 떴다(2026-08-22 사용자 ② — 13+15.7 확인). + const startOffset = inlet.offset + inletInfo.outward * BASIN_INNER_WIDTH_M; + inlet.elevation = Math.min(groundAt(startOffset), invertCap(inletInfo.edge)); } // 성토측 유입 기슭막이는 노견에 붙여 두지 않는다 — 사면선이 벽 이음선 상단점을 지나야 diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts index 1db1074b..6b75e6b6 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts @@ -77,6 +77,8 @@ export interface BasinLayout { reason: "cut" | "short" | "manual"; /** 집수정이 원지반 안으로 들어갈 때의 절토선(구조물 바깥 끝 → 지반 교차점). */ cutLine: { from: OffsetPoint; to: OffsetPoint } | null; + /** 구조물이 원지반 밖으로 나올 때의 성토(되메움)선 — 경사 5도(임시), 지반 교차점까지. */ + fillLine: { from: OffsetPoint; to: OffsetPoint } | null; } /** 관 끝단을 자를 마감면 — 구조물(기슭막이·집수정)의 계류측 변을 **그대로 복사**한다. */ diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index 57d9bed4..07e11f77 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -233,7 +233,7 @@ opacity: 0.85; } -/* 유입 집수정(ㄴ형) — 절토측 또는 원지반 30% 폐색 시 자동 반영되는 박스 단면 */ +/* 유입 집수정 — 성토사면 ≤3m(절토 포함) 자동 또는 사용자 선택 단면 */ .b06-chart__culvert-basin { fill: color-mix(in srgb, #8a7a5c 32%, transparent); stroke: #6f6248; @@ -241,6 +241,13 @@ stroke-linejoin: round; } +/* 집수정 선택 강조(2026-08-22 사용자) — 기슭막이와 같은 규칙. */ +.b06-chart__culvert-basin.is-active { + fill: color-mix(in srgb, var(--color-danger) 45%, transparent); + stroke: var(--color-danger); + stroke-width: 2.2; +} + /* 유출부 돌붙임 — 성토사면을 따라 원지반까지 덮는 돌붙임 띠(실무 도면의 돌 연쇄) */ .b06-chart__culvert-pitching { fill: color-mix(in srgb, #3fa34d 18%, transparent);