fix(B06): 관 축을 최종 벽 자리로 재유도 — 벽 바닥 아래 삐짐 해소
- 관 하단선은 관 시작점과 유출 벽 하단선 중점(invert)을 잇는 직선으로 고정 — 정수 맞춤이 벽을 옮길 때마다 축·법선 재유도(deriveAxis). 종전엔 옛 축을 그대로 늘려 가파른 지반(13+15.7)에서 관이 벽 바닥 아래로 삐져나갔다. - 관 시작 기준도 유입 노견점이 아니라 실제 관 시작점(집수정 내공 벽) 으로 통일. - 끝단면 교점을 마감면 선분 안으로 클램프(clampToFace) — 연장선 교차로 벽 밖에 꼭짓점이 생기는 것 차단. - 검증: 13+15.7 SVG 실좌표(관 꼭짓점 385→371, 벽 하단 379 위), 4+4.3 회귀, dz 스윕 24건 이탈 0건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -491,32 +491,61 @@ export function computeCulvertLayout(
|
||||
REVET_THICKNESS_M,
|
||||
wallVertical.outlet,
|
||||
);
|
||||
// 유출 벽이 사면 끝보다 안쪽으로 당겨졌으면 **관 끝도 그 자리에 맞춘다** — 관만 사면
|
||||
// 끝까지 남으면 벽과 떨어져 마감면 교차가 실패한다(2026-08-20 사용자 ② 원인).
|
||||
if (outletWall && Math.abs(outletWallAnchor.offset - outletAnchor.offset) > 0.05) {
|
||||
const runW = outletWallAnchor.offset - inlet.offset;
|
||||
const riseW = outletWallAnchor.elevation - inlet.elevation;
|
||||
// ── 관 축 확정. 관 하단선은 **관 시작점(집수정 내공 벽 또는 유입 벽 자리)과
|
||||
// 유출 벽 하단선 중점(invert)을 잇는 직선**이다 — 벽이 어디로 가든 관은 그 벽
|
||||
// 바닥을 지난다(2026-08-22 사용자: 기슭막이는 관을 감싸는 구조물). 종전에는 정수
|
||||
// 맞춤이 벽을 옮긴 뒤에도 옛 축을 그대로 써서, 가파른 지반에서 관이 벽 바닥
|
||||
// 아래로 삐져나갔다(13+15.7 사용자 지적).
|
||||
const pipeStart = basinPipeEnd ?? inlet;
|
||||
// 유출 벽이 사면 끝과 다른 자리면 관 끝도 그 자리 기준으로 맞춘다(올림 연장분은
|
||||
// 유출 쪽 — 2026-08-20 확정). 관 끝은 벽 하단선 중점을 지나 정수 길이까지 나간다.
|
||||
if (outletWall) {
|
||||
const runW = outletWallAnchor.offset - pipeStart.offset;
|
||||
const riseW = outletWallAnchor.elevation - pipeStart.elevation;
|
||||
const lenW = Math.hypot(runW, riseW);
|
||||
if (lenW > 0.5) {
|
||||
const scaleW = Math.ceil(lenW - 1e-6) / lenW;
|
||||
outlet.offset = inlet.offset + runW * scaleW;
|
||||
outlet.elevation = inlet.elevation + riseW * scaleW;
|
||||
outlet.offset = pipeStart.offset + runW * scaleW;
|
||||
outlet.elevation = pipeStart.elevation + riseW * scaleW;
|
||||
lengthM = Math.ceil(lenW - 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 관 단면 꼭짓점 — 끝단면을 구조물 계류측 변에 맞춰 자른다(2026-08-20 사용자 ③).
|
||||
// 보호공 시작점이 **관 하단 꼭짓점**을 그대로 써야 하므로 여기서 확정한다.
|
||||
const pipeStart = basinPipeEnd ?? inlet;
|
||||
const runP = outlet.offset - pipeStart.offset;
|
||||
const riseP = outlet.elevation - pipeStart.elevation;
|
||||
const axisLength = Math.hypot(runP, riseP) || 1;
|
||||
const axis: OffsetPoint = { offset: runP / axisLength, elevation: riseP / axisLength };
|
||||
let normal: OffsetPoint = { offset: -axis.elevation, elevation: axis.offset };
|
||||
if (normal.elevation < 0) normal = { offset: -normal.offset, elevation: -normal.elevation };
|
||||
const topAnchor: OffsetPoint = {
|
||||
offset: pipeStart.offset + normal.offset * diameter,
|
||||
elevation: pipeStart.elevation + normal.elevation * diameter,
|
||||
// 축·법선은 정수 맞춤이 벽(=관 끝)을 옮길 때마다 다시 유도한다.
|
||||
let axis: OffsetPoint = { offset: 1, elevation: 0 };
|
||||
let normal: OffsetPoint = { offset: 0, elevation: 1 };
|
||||
let topAnchor: OffsetPoint = pipeStart;
|
||||
const deriveAxis = (): void => {
|
||||
const runP = outlet.offset - pipeStart.offset;
|
||||
const riseP = outlet.elevation - pipeStart.elevation;
|
||||
const axisLength = Math.hypot(runP, riseP) || 1;
|
||||
axis = { offset: runP / axisLength, elevation: riseP / axisLength };
|
||||
normal = { offset: -axis.elevation, elevation: axis.offset };
|
||||
if (normal.elevation < 0) normal = { offset: -normal.offset, elevation: -normal.elevation };
|
||||
topAnchor = {
|
||||
offset: pipeStart.offset + normal.offset * diameter,
|
||||
elevation: pipeStart.elevation + normal.elevation * diameter,
|
||||
};
|
||||
};
|
||||
deriveAxis();
|
||||
/** 교점을 마감면 **선분 안**으로 자른다 — 선분 밖(벽 하단 아래 등)으로 새지 않게. */
|
||||
const clampToFace = (
|
||||
point: OffsetPoint,
|
||||
face: { base: OffsetPoint; direction: { offset: number; elevation: number } },
|
||||
): OffsetPoint => {
|
||||
const dot =
|
||||
(point.offset - face.base.offset) * face.direction.offset +
|
||||
(point.elevation - face.base.elevation) * face.direction.elevation;
|
||||
const len2 =
|
||||
face.direction.offset * face.direction.offset +
|
||||
face.direction.elevation * face.direction.elevation;
|
||||
const t = len2 > 1e-12 ? Math.min(Math.max(dot / len2, 0), 1) : 0;
|
||||
return {
|
||||
offset: face.base.offset + face.direction.offset * t,
|
||||
elevation: face.base.elevation + face.direction.elevation * t,
|
||||
};
|
||||
};
|
||||
const endCorners = (role: "inlet" | "outlet", endPoint: OffsetPoint): PipeEnd => {
|
||||
const fallback: PipeEnd = {
|
||||
@@ -536,7 +565,8 @@ export function computeCulvertLayout(
|
||||
STRAY_LIMIT_M ||
|
||||
Math.hypot(top.offset - fallback.top.offset, top.elevation - fallback.top.elevation) >
|
||||
STRAY_LIMIT_M;
|
||||
return strayed ? fallback : { bottom, top };
|
||||
if (strayed) return fallback;
|
||||
return { bottom: clampToFace(bottom, face), top: clampToFace(top, face) };
|
||||
};
|
||||
let pipeCorners = {
|
||||
inlet: endCorners("inlet", pipeStart),
|
||||
@@ -565,8 +595,14 @@ export function computeCulvertLayout(
|
||||
null,
|
||||
outletThickness,
|
||||
);
|
||||
outlet.offset = pipeStart.offset + axis.offset * target;
|
||||
outlet.elevation = pipeStart.elevation + axis.elevation * target;
|
||||
// 관 하단선은 옮겨진 벽의 **하단선 중점을 지나야** 한다 — 옛 축을 그대로 늘리면
|
||||
// 가파른 지반에서 관이 벽 바닥 아래로 삐져나간다(2026-08-22 13+15.7 사용자 지적).
|
||||
const runW = outletWallAnchor.offset - pipeStart.offset;
|
||||
const riseW = outletWallAnchor.elevation - pipeStart.elevation;
|
||||
const lenW = Math.hypot(runW, riseW) || 1;
|
||||
outlet.offset = pipeStart.offset + (runW / lenW) * target;
|
||||
outlet.elevation = pipeStart.elevation + (riseW / lenW) * target;
|
||||
deriveAxis();
|
||||
pipeCorners = {
|
||||
inlet: endCorners("inlet", pipeStart),
|
||||
outlet: endCorners("outlet", outlet),
|
||||
|
||||
Reference in New Issue
Block a user