diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index f298c916..45fd8a53 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -17,7 +17,7 @@ import { import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util' import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가 import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom' -import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils' +import offsetPolygon, { calculateAngle, createLinesFromPolygon, cleanSelfIntersectingPolygon } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' import { useEvent } from '@/hooks/useEvent' import { BATCH_TYPE, LINE_TYPE, MODULE_SETUP_TYPE, POLYGON_TYPE } from '@/common/common' @@ -384,6 +384,8 @@ export function useModuleBasicSetting(tabNum) { } else { offsetPoints = createPaddingPolygon(polygon, roof.lines).vertices } + // 자기교차(꼬임) 제거 + offsetPoints = cleanSelfIntersectingPolygon(offsetPoints) } //모듈설치영역?? 생성 diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 47e8f4e9..931362f4 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -5,6 +5,7 @@ import { getAdjacent, getDegreeByChon, isPointOnLine, isPointOnLineNew } from '@ import { QPolygon } from '@/components/fabric/QPolygon' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' +import * as turf from '@turf/turf' const TWO_PI = Math.PI * 2 const EPSILON = 1e-10 //좌표계산 시 최소 차이값 @@ -248,6 +249,47 @@ function createPaddingPolygon(polygon, offset, arcSegments = 0) { return paddingPolygon } +/** + * 자기교차(self-intersection)가 있는 폴리곤을 정리하는 함수 + * turf.js의 unkinkPolygon을 사용하여 꼬인 부분을 제거하고 가장 큰 폴리곤을 반환 + * @param {Array} vertices - [{x, y}, ...] 형태의 점 배열 + * @returns {Array} 정리된 점 배열 + */ +export function cleanSelfIntersectingPolygon(vertices) { + if (!vertices || vertices.length < 3) return vertices + + try { + // vertices를 GeoJSON 폴리곤으로 변환 + const coords = vertices.map((p) => [p.x, p.y]) + coords.push([vertices[0].x, vertices[0].y]) // ring 닫기 + + const turfPoly = turf.polygon([coords]) + + // 자기교차 검사 + const kinked = turf.kinks(turfPoly) + + if (kinked.features.length === 0) { + return vertices // 꼬임 없음 + } + + // 꼬인 폴리곤을 분리 + const unkinked = turf.unkinkPolygon(turfPoly) + + if (unkinked.features.length > 0) { + // 가장 큰 면적의 폴리곤 선택 + const largest = unkinked.features.reduce((max, f) => (turf.area(f) > turf.area(max) ? f : max)) + + // GeoJSON 좌표를 다시 {x, y} 형태로 변환 + const cleanedCoords = largest.geometry.coordinates[0] + return cleanedCoords.slice(0, -1).map((c) => ({ x: c[0], y: c[1] })) + } + } catch (e) { + console.warn('Failed to clean self-intersecting polygon:', e) + } + + return vertices +} + export default function offsetPolygon(vertices, offset) { const polygon = createPolygon(vertices) const arcSegments = 0 @@ -255,25 +297,29 @@ export default function offsetPolygon(vertices, offset) { const originPolygon = new QPolygon(vertices, { fontSize: 0 }) originPolygon.setViewLengthText(false) + let result if (offset > 0) { - let result = createMarginPolygon(polygon, offset, arcSegments).vertices - const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point)) + let marginResult = createMarginPolygon(polygon, offset, arcSegments).vertices + const allPointsOutside = marginResult.every((point) => !originPolygon.inPolygon(point)) if (allPointsOutside) { - return createMarginPolygon(polygon, offset, arcSegments).vertices + result = createMarginPolygon(polygon, offset, arcSegments).vertices } else { - return createPaddingPolygon(polygon, offset, arcSegments).vertices + result = createPaddingPolygon(polygon, offset, arcSegments).vertices } } else { - let result = createPaddingPolygon(polygon, offset, arcSegments).vertices - const allPointsInside = result.every((point) => originPolygon.inPolygon(point)) + let paddingResult = createPaddingPolygon(polygon, offset, arcSegments).vertices + const allPointsInside = paddingResult.every((point) => originPolygon.inPolygon(point)) if (allPointsInside) { - return createPaddingPolygon(polygon, offset, arcSegments).vertices + result = createPaddingPolygon(polygon, offset, arcSegments).vertices } else { - return createMarginPolygon(polygon, offset, arcSegments).vertices + result = createMarginPolygon(polygon, offset, arcSegments).vertices } } + + // 자기교차(꼬임) 제거 + return cleanSelfIntersectingPolygon(result) } function normalizePoint(point) {