innerLines 수정 시 분할 안되는 현상 수정
This commit is contained in:
parent
271c132bac
commit
0b5b91ba15
@ -1549,6 +1549,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
|
// 나눠서 중복 제거된 roof return
|
||||||
let newRoofs = getSplitRoofsPoints(allLines)
|
let newRoofs = getSplitRoofsPoints(allLines)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user