From 7b8b995e1cecbb968328cbfba83bcf9bf988d46b Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 27 Mar 2026 10:46:38 +0900 Subject: [PATCH] =?UTF-8?q?1805=20=EC=84=A4=EC=B9=98=EC=98=81=EC=97=AD=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=84=A4=EC=B9=98=20=EC=95=88?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModuleBasicSetting.js | 6 +-- src/hooks/useMode.js | 30 ++++++----- src/util/qpolygon-utils.js | 62 +++++++++++++++-------- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index cc315e32..ef477e67 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -395,7 +395,7 @@ export function useModuleBasicSetting(tabNum) { const cleanedLines = cleaned.lines const originPolygon = new QPolygon(roof.getCurrentPoints(), { fontSize: 0 }) - let result = createPaddingPolygon(polygon, cleanedLines).vertices + let result = createPaddingPolygon(polygon, cleanedLines, 0, true).vertices //margin polygon 의 point가 기준 polygon의 밖에 있는지 판단한다. const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point)) @@ -408,9 +408,9 @@ export function useModuleBasicSetting(tabNum) { } else { //육지붕이 아닐때 if (allPointsOutside) { - offsetPoints = createMarginPolygon(polygon, cleanedLines).vertices + offsetPoints = createMarginPolygon(polygon, cleanedLines, 0, true).vertices } else { - offsetPoints = createPaddingPolygon(polygon, cleanedLines).vertices + offsetPoints = createPaddingPolygon(polygon, cleanedLines, 0, true).vertices } // 자기교차(꼬임) 제거 offsetPoints = cleanSelfIntersectingPolygon(offsetPoints) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 69c2db78..8492042a 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1746,7 +1746,7 @@ export function useMode() { return { vertices: cleanedVertices, lines: cleanedLines } } - function createMarginPolygon(polygon, lines, arcSegments = 0) { + function createMarginPolygon(polygon, lines, arcSegments = 0, bevelJoin = false) { const offsetEdges = [] polygon.edges.forEach((edge, i) => { @@ -1761,11 +1761,14 @@ export function useMode() { offsetEdges.forEach((thisEdge, i) => { const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length] const vertex = edgesIntersection(prevEdge, thisEdge) - if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { - vertices.push({ - x: vertex.x, - y: vertex.y, - }) + if (vertex) { + if (!vertex.isIntersectionOutside || !bevelJoin) { + vertices.push({ x: vertex.x, y: vertex.y }) + } else { + // 둔각 + bevelJoin: offset edge 끝점 사용 + vertices.push({ x: prevEdge.vertex2.x, y: prevEdge.vertex2.y }) + vertices.push({ x: thisEdge.vertex1.x, y: thisEdge.vertex1.y }) + } } }) @@ -1781,7 +1784,7 @@ export function useMode() { * @param arcSegments * @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}} */ - function createPaddingPolygon(polygon, lines, arcSegments = 0) { + function createPaddingPolygon(polygon, lines, arcSegments = 0, bevelJoin = false) { const offsetEdges = [] polygon.edges.forEach((edge, i) => { @@ -1797,11 +1800,14 @@ export function useMode() { offsetEdges.forEach((thisEdge, i) => { const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length] const vertex = edgesIntersection(prevEdge, thisEdge) - if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) { - vertices.push({ - x: vertex.x, - y: vertex.y, - }) + if (vertex) { + if (!vertex.isIntersectionOutside || !bevelJoin) { + vertices.push({ x: vertex.x, y: vertex.y }) + } else { + // 둔각 + bevelJoin: offset edge 끝점 사용 + vertices.push({ x: prevEdge.vertex2.x, y: prevEdge.vertex2.y }) + vertices.push({ x: thisEdge.vertex1.x, y: thisEdge.vertex1.y }) + } } }) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index bf4c87cb..adee3ff3 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -256,8 +256,8 @@ function createPaddingPolygon(polygon, offset, arcSegments = 0) { * @returns {Array} 정리된 점 배열 */ /** - * offset 폴리곤을 원본 폴리곤 경계 안으로 클리핑한다. - * 둔각 꼭짓점에서 offset 교차점이 원본 바깥으로 튀어나가는 문제를 해결한다. + * offset 폴리곤의 꼭짓점 중 원본 폴리곤 바깥에 나간 점을 + * 원본 폴리곤 경계 위의 가장 가까운 점으로 투영한다. * @param {Array} offsetVertices - offset된 점 배열 [{x, y}, ...] * @param {Array} originalVertices - 원본 폴리곤 점 배열 [{x, y}, ...] * @returns {Array} 클리핑된 점 배열 @@ -266,27 +266,49 @@ export function clipOffsetToOriginal(offsetVertices, originalVertices) { if (!offsetVertices || offsetVertices.length < 3) return offsetVertices if (!originalVertices || originalVertices.length < 3) return offsetVertices - try { - const offsetCoords = offsetVertices.map((p) => [p.x, p.y]) - offsetCoords.push([offsetVertices[0].x, offsetVertices[0].y]) - - const origCoords = originalVertices.map((p) => [p.x, p.y]) - origCoords.push([originalVertices[0].x, originalVertices[0].y]) - - const offsetPoly = turf.polygon([offsetCoords]) - const origPoly = turf.polygon([origCoords]) - - const clipped = turf.intersect(turf.featureCollection([offsetPoly, origPoly])) - - if (clipped) { - const clippedCoords = clipped.geometry.coordinates[0] - return clippedCoords.slice(0, -1).map((c) => ({ x: c[0], y: c[1] })) + // 점이 폴리곤 내부에 있는지 판단 (ray casting) + function pointInPolygon(point, polygon) { + let inside = false + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].x, yi = polygon[i].y + const xj = polygon[j].x, yj = polygon[j].y + if (((yi > point.y) !== (yj > point.y)) && + (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi)) { + inside = !inside + } } - } catch (e) { - console.warn('Failed to clip offset polygon to original:', e) + return inside } - return offsetVertices + // 점을 선분 위의 가장 가까운 점으로 투영 + function projectOnSegment(p, a, b) { + const dx = b.x - a.x + const dy = b.y - a.y + const lenSq = dx * dx + dy * dy + if (lenSq === 0) return { x: a.x, y: a.y } + let t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq + t = Math.max(0, Math.min(1, t)) + return { x: a.x + t * dx, y: a.y + t * dy } + } + + return offsetVertices.map((p) => { + if (pointInPolygon(p, originalVertices)) return p + + // 바깥에 있으면 원본 폴리곤의 가장 가까운 변 위의 점으로 이동 + let minDist = Infinity + let nearest = p + for (let i = 0; i < originalVertices.length; i++) { + const a = originalVertices[i] + const b = originalVertices[(i + 1) % originalVertices.length] + const proj = projectOnSegment(p, a, b) + const dist = (proj.x - p.x) * (proj.x - p.x) + (proj.y - p.y) * (proj.y - p.y) + if (dist < minDist) { + minDist = dist + nearest = proj + } + } + return nearest + }) } export function cleanSelfIntersectingPolygon(vertices) {