라인줄표시, 인덱스 재설치

This commit is contained in:
ysCha 2026-04-27 13:09:28 +09:00
parent 0b0a1786da
commit d810b89658
2 changed files with 23 additions and 13 deletions

View File

@ -7,6 +7,7 @@ import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js' import Big from 'big.js'
import { drawSkeletonRidgeRoof, drawSkeletonRidgeRoofFromBaseLines, verifyMoveBoundary } from '@/util/skeleton-utils' import { drawSkeletonRidgeRoof, drawSkeletonRidgeRoofFromBaseLines, verifyMoveBoundary } from '@/util/skeleton-utils'
import { attachDebugLabels } from '@/util/debugLabels'
export const QPolygon = fabric.util.createClass(fabric.Polygon, { export const QPolygon = fabric.util.createClass(fabric.Polygon, {
type: 'QPolygon', type: 'QPolygon',
@ -514,6 +515,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
// console.log('변별로 설정') // console.log('변별로 설정')
drawRoofByAttribute(this.id, this.canvas, textMode) drawRoofByAttribute(this.id, this.canvas, textMode)
} }
// [DEBUG] URL ?debug=labels 일 때만 라벨 부착. 운영 빌드/평소엔 noop.
attachDebugLabels(this.canvas, this.id)
}, },
/** /**

View File

@ -1339,24 +1339,29 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode, isOver
}) })
} }
// 각 라인 집합 정렬 // [정렬 정책 — 인덱스가 생명] (2026-04-27)
const sortWallLines = ensureCounterClockwiseLines(wallLines) // 1) master = roofLines. roofLine 은 외곽(처마) 경계라 사용자 mutation 영향 없음 → 시작점 안정.
// wall.baseLines를 독립적으로 CCW 정렬하면 ㅗ→붕괴 등으로 꼭짓점 집합이 달라질 때 // 2) ensureCounterClockwiseLines 가 master 의 leftTop(min-Y → min-X) 꼭짓점에서 시작해 CCW 정렬.
// 시작점(min-Y-then-min-X)이 wallLines와 어긋나 배열이 shift된다. // 3) wall.lines, wall.baseLines 는 raw 인덱스 1:1 페어링 유지된다는 전제로
// wallLines[i]와 wall.baseLines[i]는 초기 생성 시부터 raw-index 1:1 페어링이 유지되므로, // master 의 sorted 순서 → 원 인덱스 → wall.lines[idx] / wall.baseLines[idx] 매핑.
// sortWallLines의 CCW 순서에 대응하는 원 인덱스를 복원하여 같은 순서로 wall.baseLines를 매핑한다. // wall 쪽을 master 로 쓰던 구현은 OVER 흡수/usePropertiesSetting 등에서 wall.lines 가
// 재할당되며 시작점이 어긋나 페어 인덱스가 회전되던 문제(2026-04-27 ㅗ 좌측오버 케이스) 가 있어 변경.
const _keyOfEdge = (l) => { const _keyOfEdge = (l) => {
const a = `${Math.round(l.x1 * 10) / 10},${Math.round(l.y1 * 10) / 10}` const a = `${Math.round(l.x1 * 10) / 10},${Math.round(l.y1 * 10) / 10}`
const b = `${Math.round(l.x2 * 10) / 10},${Math.round(l.y2 * 10) / 10}` const b = `${Math.round(l.x2 * 10) / 10},${Math.round(l.y2 * 10) / 10}`
return a < b ? `${a}|${b}` : `${b}|${a}` return a < b ? `${a}|${b}` : `${b}|${a}`
} }
const _rawIdxByKey = new Map() const _rawIdxByKey = new Map()
wallLines.forEach((l, i) => _rawIdxByKey.set(_keyOfEdge(l), i)) roofLines.forEach((l, i) => _rawIdxByKey.set(_keyOfEdge(l), i))
const sortWallBaseLines = sortWallLines.map((sl) => { const sortRoofLines = ensureCounterClockwiseLines(roofLines)
const sortWallLines = sortRoofLines.map((sl) => {
const origIdx = _rawIdxByKey.get(_keyOfEdge(sl))
return origIdx != null && wallLines[origIdx] ? wallLines[origIdx] : sl
})
const sortWallBaseLines = sortRoofLines.map((sl) => {
const origIdx = _rawIdxByKey.get(_keyOfEdge(sl)) const origIdx = _rawIdxByKey.get(_keyOfEdge(sl))
return origIdx != null && wall.baseLines[origIdx] ? wall.baseLines[origIdx] : sl return origIdx != null && wall.baseLines[origIdx] ? wall.baseLines[origIdx] : sl
}) })
const sortRoofLines = ensureCounterClockwiseLines(roofLines)
// [MOVE-TRACE] 진입 스냅샷 (필요 시 주석 해제) // [MOVE-TRACE] 진입 스냅샷 (필요 시 주석 해제)
// console.log('[MOVE-TRACE] === getMoveUpDownLine entry ===', // console.log('[MOVE-TRACE] === getMoveUpDownLine entry ===',
@ -1596,10 +1601,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode, isOver
const roofLine = sortRoofLines[index] const roofLine = sortRoofLines[index]
const wallBaseLine = sortWallBaseLines[index] const wallBaseLine = sortWallBaseLines[index]
// [SHOULDER-ABSORBED] wall.baseLines[k] 가 이동 흡수로 zero-length(planeSize≈0)면 // [SHOULDER-ABSORBED] wall.baseLines[k] 가 이동 흡수/OVER_EPS 클램핑으로 거의 zero-length 이면
// 해당 edge 는 흡수되어 사라진 상태. pair 생성 자체가 불필요(DIR_MISMATCH + eaveHelpLine 노이즈 유발). // 해당 edge 는 흡수된 상태. pair 생성 자체가 불필요(DIR_MISMATCH + eaveHelpLine 노이즈 유발).
// 참고: 같은 파일 line 666 의 filter(planeSize > 0) 선례와 동일 패턴. // 임계 = 10: useMovementSetting OVER_EPS=0.5 (canvas unit) 시 잔여 길이=0.5 → planeSize=5 (calcLinePlaneSize × 10).
if (!wallBaseLine || (wallBaseLine.attributes?.planeSize ?? Infinity) < 1) { // 안전마진 두고 < 10 으로 차단. 정상 baseLine 은 보통 50+ 이므로 false positive 없음.
if (!wallBaseLine || (wallBaseLine.attributes?.planeSize ?? Infinity) < 10) {
console.log(`⏭️ [pair#${index}] SHOULDER_ABSORBED skip: wb=(${Math.round(wallBaseLine?.x1)},${Math.round(wallBaseLine?.y1)})→(${Math.round(wallBaseLine?.x2)},${Math.round(wallBaseLine?.y2)}) planeSize=${wallBaseLine?.attributes?.planeSize}`) console.log(`⏭️ [pair#${index}] SHOULDER_ABSORBED skip: wb=(${Math.round(wallBaseLine?.x1)},${Math.round(wallBaseLine?.y1)})→(${Math.round(wallBaseLine?.x2)},${Math.round(wallBaseLine?.y2)}) planeSize=${wallBaseLine?.attributes?.planeSize}`)
return return
} }