innerLines 수정 시 분할 안되는 현상 수정

This commit is contained in:
hyojun.choi 2026-06-17 16:25:09 +09:00
parent 777f85f5b6
commit e14ed81db4

View File

@ -1543,6 +1543,121 @@ export const usePolygon = () => {
}
}
// [MOVED-DIVIDER 2026-06-17] 내부 분할선(예: A타입 중앙 3868)을 옮기면 경계선만 새 위치로 재분할되고
// 스켈레톤 hip 은 옛 위치에서 만나, 옮긴 보조선 끝점이 hip 에 노드로 안 붙어 분할이 안 되고 면이
// 겹쳐 생긴다(ROOF-FACE-DIAG: 거의 동일한 면 2개). 해결:
// (1) 내부 보조선 끝점이 hip 몸통 위(strict)에 있으면 그 hip 을 끝점에서 split → 보조선이 연결됨.
// (2) split 이 일어난 경우에만, 옛 접합점이 degree-2 공선(共線) 잉여 노드가 되면 merge → 옛 분할점 제거.
// 게이트: 실제 split 이 일어난 경우(=내부분할선 이동)에만 (2) 수행 → 외곽선 띠(SUPERSEDED) 케이스는 불변.
{
const innerAux = allLines.filter((l) => l.name === 'auxiliaryLine')
const isInner = (l) => l.name === 'hip' || l.name === 'ridge'
const EPS = 2
const mkSeg = (proto, sp, ep) => ({
name: proto.name,
lineName: proto.lineName,
attributes: { ...proto.attributes },
startPoint: { x: sp.x, y: sp.y },
endPoint: { x: ep.x, y: ep.y },
x1: sp.x,
y1: sp.y,
x2: ep.x,
y2: ep.y,
})
const onBodyStrict = (L, P) => {
const ax = L.startPoint.x,
ay = L.startPoint.y
const dx = L.endPoint.x - ax,
dy = L.endPoint.y - ay
const lenSq = dx * dx + dy * dy
if (lenSq < 1) return false
const t = ((P.x - ax) * dx + (P.y - ay) * dy) / lenSq
if (t <= 0.03 || t >= 0.97) return false
const px = ax + t * dx,
py = ay + t * dy
return (P.x - px) ** 2 + (P.y - py) ** 2 < EPS * EPS
}
if (innerAux.length > 0) {
const auxPts = []
innerAux.forEach((a) => {
auxPts.push(a.startPoint, a.endPoint)
})
// (1) 내부선을 보조선 끝점에서 split
let didSplit = false
const afterSplit = []
allLines.forEach((L) => {
if (!isInner(L)) {
afterSplit.push(L)
return
}
const ax = L.startPoint.x,
ay = L.startPoint.y
const dx = L.endPoint.x - ax,
dy = L.endPoint.y - ay
const lenSq = dx * dx + dy * dy || 1
const tOf = (P) => ((P.x - ax) * dx + (P.y - ay) * dy) / lenSq
const cuts = [...new Map(auxPts.filter((P) => onBodyStrict(L, P)).map((P) => [Math.round(tOf(P) * 1000), P])).values()].sort(
(p, q) => tOf(p) - tOf(q),
)
if (cuts.length === 0) {
afterSplit.push(L)
return
}
didSplit = true
let cur = L.startPoint
cuts.forEach((P) => {
afterSplit.push(mkSeg(L, cur, P))
cur = P
})
afterSplit.push(mkSeg(L, cur, L.endPoint))
})
if (didSplit) {
let work = afterSplit
const key = (p) => `${Math.round(p.x)},${Math.round(p.y)}`
// (2) degree-2 공선 내부선 접합점 merge (잉여 옛 분할점 제거)
let merged = true
let guard = 0
while (merged && guard++ < 200) {
merged = false
const incident = {}
work.forEach((L) => {
;[L.startPoint, L.endPoint].forEach((p) => {
;(incident[key(p)] || (incident[key(p)] = [])).push(L)
})
})
for (const k in incident) {
const inc = incident[k]
if (inc.length !== 2) continue
const [la, lb] = inc
if (la === lb || !isInner(la) || !isInner(lb)) continue
const [kx, ky] = k.split(',').map(Number)
const farA = Math.abs(la.startPoint.x - kx) < 1 && Math.abs(la.startPoint.y - ky) < 1 ? la.endPoint : la.startPoint
const farB = Math.abs(lb.startPoint.x - kx) < 1 && Math.abs(lb.startPoint.y - ky) < 1 ? lb.endPoint : lb.startPoint
const cross = (kx - farA.x) * (farB.y - ky) - (ky - farA.y) * (farB.x - kx)
const norm = Math.hypot(kx - farA.x, ky - farA.y) * Math.hypot(farB.x - kx, farB.y - ky)
if (norm < 1 || Math.abs(cross) / norm > 0.02) continue
work = work.filter((L) => L !== la && L !== lb)
work.push(mkSeg(la, farA, farB))
merged = true
break
}
}
// 동일 좌표 변 중복 제거
work = work.filter(
(line, idx, self) =>
idx ===
self.findIndex(
(o) =>
(isSamePoint(o.startPoint, line.startPoint) && isSamePoint(o.endPoint, line.endPoint)) ||
(isSamePoint(o.startPoint, line.endPoint) && isSamePoint(o.endPoint, line.startPoint)),
),
)
logger.log(`[MOVED-DIVIDER] 내부 분할선 이동 감지 — hip 재분할 적용 (allLines ${allLines.length}${work.length})`)
allLines = work
}
}
}
// 나눠서 중복 제거된 roof return
let newRoofs = getSplitRoofsPoints(allLines)