From 4b635493fe0a312faf6858e5ad5d9bffdd603606 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 12 Feb 2026 14:22:41 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B0=84=EA=B2=A9=EC=9D=B4=20=EB=8B=A4?= =?UTF-8?q?=EB=A5=BC=20=EB=95=8C=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4?= =?UTF-8?q?=EC=9D=B4=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 93 ++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index a8a4063e..aeb63ffa 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -401,26 +401,70 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { console.log('baseLinePoints:', orderedBaseLinePoints) // baseLinePoint에서 roofLinePoint 방향으로 45도 대각선 확장 후 가장 가까운 접점 계산 - let roofLineContactPoints = orderedBaseLinePoints.map((point, index) => { + // 각 포인트의 dx, dy, sign 정보 수집 + const contactData = orderedBaseLinePoints.map((point, index) => { const roofPoint = roofLinePoints[index] - if (!roofPoint) return point + if (!roofPoint) return { dx: 0, dy: 0, signDx: 0, signDy: 0 } const dx = roofPoint.x - point.x const dy = roofPoint.y - point.y - const absDx = Math.abs(dx) - const absDy = Math.abs(dy) + return { dx, dy, signDx: Math.sign(dx), signDy: Math.sign(dy) } + }) + + // maxStep: 모든 포인트 중 가장 큰 45도 step + const maxStep = contactData.reduce((max, data) => { + return Math.max(max, Math.min(Math.abs(data.dx), Math.abs(data.dy))) + }, 0) + + // baseLine 폴리곤의 중심 계산 (확장 방향 결정용) + const centroid = orderedBaseLinePoints.reduce((acc, p) => { + acc.x += p.x / orderedBaseLinePoints.length + acc.y += p.y / orderedBaseLinePoints.length + return acc + }, { x: 0, y: 0 }) + + // 모든 포인트를 동일한 maxStep으로 45도 확장 + let roofLineContactPoints = orderedBaseLinePoints.map((point, index) => { + const data = contactData[index] + const { dx, dy } = data + + // dx 또는 dy가 0인 경우 중심에서 바깥쪽 방향으로 확장 + let signDx = data.signDx + let signDy = data.signDy + if (signDx === 0) { + signDx = point.x >= centroid.x ? 1 : -1 + } + if (signDy === 0) { + signDy = point.y >= centroid.y ? 1 : -1 + } + + return { + x: point.x + signDx * maxStep, + y: point.y + signDy * maxStep + } + }) + + const maxContactDistance = Math.hypot(maxStep, maxStep) + + let changRoofLinePoints = orderedBaseLinePoints.map((point, index) => { + const contactPoint = roofLineContactPoints[index] + if (!contactPoint) return point + + const dx = contactPoint.x - point.x + const dy = contactPoint.y - point.y + const len = Math.hypot(dx, dy) // 거리가 0이면 이미 같은 위치 - if (absDx === 0 && absDy === 0) return point + if (len === 0) return point - const step = Math.min(absDx, absDy) + const step = maxContactDistance if (step === 0) return point - const nextX = point.x + Math.sign(dx) * step - const nextY = point.y + Math.sign(dy) * step + const nextX = point.x + (dx / len) * step + const nextY = point.y + (dy / len) * step return { - x: Number(nextX.toFixed(1)), - y: Number(nextY.toFixed(1)) + x: nextX, + y: nextY } }) @@ -429,8 +473,11 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { roofLineContactPoints = movingLineFromSkeleton(roofId, canvas) } - console.log('points:', roofLineContactPoints) - const geoJSONPolygon = toGeoJSON(roofLineContactPoints) + // changRoofLinePoints 좌표를 roof.skeletonPoints에 저장 (원본 roof.points는 유지) + roof.skeletonPoints = changRoofLinePoints.map(p => ({ x: p.x, y: p.y })) + + console.log('points:', changRoofLinePoints) + const geoJSONPolygon = toGeoJSON(changRoofLinePoints) try { // SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거 @@ -440,7 +487,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { // 스켈레톤 데이터를 기반으로 내부선 생성 roof.innerLines = roof.innerLines || [] roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode) - + //console.log("roofInnerLines:::", roof.innerLines); // 캔버스에 스켈레톤 상태 저장 if (!canvas.skeletonStates) { canvas.skeletonStates = {} @@ -1722,10 +1769,28 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { } let eavesLines = [] + // 확장된 외곽선 판별용 + const skPts = roof.skeletonPoints || [] + const isSkeletonOuterEdge = (p1, p2, tolerance = 0.5) => { + for (let si = 0; si < skPts.length; si++) { + const sp1 = skPts[si] + const sp2 = skPts[(si + 1) % skPts.length] + if ((Math.abs(p1.x - sp1.x) < tolerance && Math.abs(p1.y - sp1.y) < tolerance && + Math.abs(p2.x - sp2.x) < tolerance && Math.abs(p2.y - sp2.y) < tolerance) || + (Math.abs(p1.x - sp2.x) < tolerance && Math.abs(p1.y - sp2.y) < tolerance && + Math.abs(p2.x - sp1.x) < tolerance && Math.abs(p2.y - sp1.y) < tolerance)) { + return true + } + } + return false + } + for (let i = 0; i < polygonPoints.length; i++) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; + // 확장된 외곽선에 해당하는 edge는 스킵 + if (skPts.length > 0 && isSkeletonOuterEdge(p1, p2)) continue // 지붕 경계선과 교차 확인 및 클리핑 const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine); @@ -1900,7 +1965,7 @@ function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, is }; skeletonLines.push(newLine); - console.log('skeletonLines', skeletonLines); + //console.log('skeletonLines', skeletonLines); } /**