fix(B05): BOX암거 날개벽을 구체 모서리에 붙이고 에이프런을 평면 사다리꼴로
날개벽 시작점을 측점 중앙 프레임으로 잡아 곡선 구간에서 구체 끝과 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user