From 6e87dd6cdf7f7ace56da850d2c6cd70147ada10e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 25 Aug 2026 15:41:31 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05):=20BOX=EC=95=94=EA=B1=B0=20=EB=82=A0?= =?UTF-8?q?=EA=B0=9C=EB=B2=BD=EC=9D=84=20=EA=B5=AC=EC=B2=B4=20=EB=AA=A8?= =?UTF-8?q?=EC=84=9C=EB=A6=AC=EC=97=90=20=EB=B6=99=EC=9D=B4=EA=B3=A0=20?= =?UTF-8?q?=EC=97=90=EC=9D=B4=ED=94=84=EB=9F=B0=EC=9D=84=20=ED=8F=89?= =?UTF-8?q?=EB=A9=B4=20=EC=82=AC=EB=8B=A4=EB=A6=AC=EA=BC=B4=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 날개벽 시작점을 측점 중앙 프레임으로 잡아 곡선 구간에서 구체 끝과 0.15m 어긋났다. 그 모서리 링의 프레임으로 잡아 정확히 붙인다. 또 에이프런이 평면에서 직사각형이라 각도만큼 벌어진 날개벽 발치가 바닥 밖으로 나갔다. 날개벽 선을 따라 안쪽 변이 물러나는 **사다리꼴**로 바꿔 발치를 받는다 (2026-08-25 사용자 지적). 검증(10+0.9): 날개벽 4매 모두 구체 모서리와 거리 0.000m, 에이프런 링별 안쪽 변 [4.158, 2.744, 2.744, 4.158] — 바깥 링에서 폭 0으로 닫힌다. Co-Authored-By: Claude Opus 5 (1M context) --- .../B05_Profile_UI_Corridor_Structures_Box.ts | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts index 6427f480..84093cd8 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts @@ -109,6 +109,14 @@ export function boreRingChainages( return [...values].sort((a, b) => a - b); } +/** 그 측의 날개벽 제원 — 유입/유출은 상단측(uphill)이 어느 쪽이냐로 갈린다. */ +function wingOf(section: CrossSection, role: "left" | "right") { + const box = section.box; + if (!box) return null; + const inletOnLeft = (section.uphill_side ?? "left") === "left"; + return (role === "left") === inletOnLeft ? box.wing_in : box.wing_out; +} + /** 링 누가거리 목록 → 프레임(실패하면 측점 프레임으로 대체). */ function ringsAt( chainages: number[], @@ -189,14 +197,46 @@ export function boxSolids( }); } - // 에이프런 2매 — 날개벽 구간 바닥, 각 끝 표고에서 수평. + // 에이프런 2매 — 날개벽 구간 바닥. 각 끝 표고에서 수평이고, **평면에서는 사다리꼴** + // 이다(2026-08-25 사용자) — 날개벽이 각도만큼 벌어지므로 바닥도 그만큼 넓어져야 + // 날개벽 발치가 바닥에 얹힌다. for (const side of layout.sides) { if (!side.apron.length) continue; + const wing = wingOf(section, side.role); + const angle = ((wing?.angle_deg ?? 45) * Math.PI) / 180; + const length = Math.max(wing?.length_m ?? 0, 0); + const flare = length * Math.sin(angle); + const outward = side.role === "left" ? 1 : -1; + const innerX = side.apron[0].offset; + const outerX = side.apron[1].offset; + const top = side.apron[0].elevation; + const bottom = side.apron[3].elevation; + // |y| ≤ 구체 반폭이면 구체 끝면부터, 그 밖은 날개벽 선을 따라 안쪽 변이 물러난다. + const innerAt = (offsetFromCenter: number): number => { + const beyond = Math.abs(offsetFromCenter) - halfSpan; + if (beyond <= 0) return innerX; + const run = flare > 1e-6 ? (beyond / flare) * Math.abs(outerX - innerX) : 0; + return innerX + outward * Math.min(run, Math.abs(outerX - innerX)); + }; + const reach = halfSpan + flare; + const apronPlan = [-reach, -halfSpan, halfSpan, reach]; solids.push({ chainage_m: chainage, kind: "basin", - polygons: repeat(side.apron, bodyRings.length), - rings: bodyRings, + polygons: apronPlan.map((at) => { + const inner = innerAt(at); + return [ + [inner, top], + [outerX, top], + [outerX, bottom], + [inner, bottom], + ] as SectionPolygon; + }), + rings: ringsAt( + apronPlan.map((at) => chainage + at), + frameAt, + stationFrame, + ), }); } @@ -246,8 +286,11 @@ function wingSolids( const height0 = side.topElevation - side.bottomElevation; const height1 = Math.max(wing.height_m ?? height0, 0.3); for (const along of [1, -1]) { - const startX = here.cx + here.leftX * side.offset + dirX * along * halfSpan; - const startY = here.cy + here.leftY * side.offset + dirY * along * halfSpan; + // 시작점은 **그 모서리 링의 프레임**으로 잡는다 — 중앙 프레임으로 잡으면 곡선 + // 구간에서 구체 끝과 어긋난다(2026-08-25 사용자 지적). + const cornerFrame = frameAt(chainage + along * halfSpan) ?? here; + const startX = cornerFrame.cx + cornerFrame.leftX * side.offset; + const startY = cornerFrame.cy + cornerFrame.leftY * side.offset; // 날개 방향 = 구체 축을 도로 방향으로 각도만큼 튼 단위벡터. const wingX = here.leftX * axisSign * Math.cos(angle) + dirX * along * Math.sin(angle); const wingY = here.leftY * axisSign * Math.cos(angle) + dirY * along * Math.sin(angle);