From e60989d318298a09791a1dca037d91df145c4d57 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 16 Jul 2025 13:11:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?modulePoints=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useCommonUtils.js | 14 ++++++++++---- src/hooks/useEvent.js | 9 +++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/hooks/common/useCommonUtils.js b/src/hooks/common/useCommonUtils.js index c29fa062..7c53f98c 100644 --- a/src/hooks/common/useCommonUtils.js +++ b/src/hooks/common/useCommonUtils.js @@ -1,17 +1,18 @@ import { useEffect } from 'react' -import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue } from 'recoil' import { wordDisplaySelector } from '@/store/settingAtom' import { useEvent } from '@/hooks/useEvent' import { checkLineOrientation, getDistance } from '@/util/canvas-util' import { commonUtilsState, dimensionLineSettingsState } from '@/store/commonUtilsAtom' import { fontSelector } from '@/store/fontAtom' -import { canvasState, currentMenuState } from '@/store/canvasAtom' +import { canvasState } from '@/store/canvasAtom' import { v4 as uuidv4 } from 'uuid' import { usePopup } from '@/hooks/usePopup' import Distance from '@/components/floor-plan/modal/distance/Distance' import { usePolygon } from '@/hooks/usePolygon' import { useObjectBatch } from '@/hooks/object/useObjectBatch' import { BATCH_TYPE } from '@/common/common' +import { useMouse } from '@/hooks/useMouse' export function useCommonUtils() { const canvas = useRecoilValue(canvasState) @@ -25,6 +26,7 @@ export function useCommonUtils() { const { addPopup, closeAll, targetClose } = usePopup() const { drawDirectionArrow, addLengthText } = usePolygon() const { applyDormers } = useObjectBatch({}) + const { getIntersectMousePoint } = useMouse() useEffect(() => { commonTextMode() @@ -213,7 +215,7 @@ export function useCommonUtils() { addCanvasMouseEventListener('mouse:down', (e) => { let groupObjects = [] - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) let point @@ -654,7 +656,11 @@ export function useCommonUtils() { clonedObj.setCoords() clonedObj.fire('modified') clonedObj.fire('polygonMoved') - clonedObj.set({ direction: obj.direction, directionText: obj.directionText, roofMaterial: obj.roofMaterial }) + clonedObj.set({ + direction: obj.direction, + directionText: obj.directionText, + roofMaterial: obj.roofMaterial, + }) obj.lines.forEach((line, index) => { clonedObj.lines[index].set({ attributes: line.attributes }) diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 8112bb36..1d07fc5a 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -210,6 +210,14 @@ export function useEvent() { }) }) + const modulePoints = [] + const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) + modules.forEach((module) => { + module.points.forEach((point) => { + modulePoints.push({ x: point.x, y: point.y }) + }) + }) + let adsorptionPoints = [ ...getAdsorptionPoints(), ...roofAdsorptionPoints.current, @@ -229,6 +237,7 @@ export function useEvent() { y: line.y2, } }), + ...modulePoints, ] adsorptionPoints = removeDuplicatePoints(adsorptionPoints) From 911ec78055c1fe3fcd2cdeb14e9524b6c75d2b6b Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 17 Jul 2025 16:11:42 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=A9=B4=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 114 +++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 3f71712c..d6130f38 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -303,13 +303,119 @@ export function removeDuplicatePolygons(polygons, hasAuxiliaryLine = false) { } }) - if (!hasAuxiliaryLine) { - uniquePolygons = uniquePolygons.filter((polygon) => { - return isValidPoints(polygon) + uniquePolygons = uniquePolygons.filter((polygon) => { + return !checkPolygonSelfIntersection(polygon) + }) + + // uniquePolygons = uniquePolygons.filter((polygon) => { + // return isValidPoints(polygon) + // }) + + console.log('uniquePolygons2', uniquePolygons) + + return uniquePolygons +} + +/** + * 두 선분이 교차하는지 확인하는 함수 + * @param {Object} p1 첫 번째 선분의 시작점 {x, y} + * @param {Object} q1 첫 번째 선분의 끝점 {x, y} + * @param {Object} p2 두 번째 선분의 시작점 {x, y} + * @param {Object} q2 두 번째 선분의 끝점 {x, y} + * @returns {boolean} 교차하면 true, 아니면 false + */ +function doSegmentsIntersect(p1, q1, p2, q2) { + // CCW (Counter-Clockwise) 방향 확인 함수 + function orientation(p, q, r) { + const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) + if (val === 0) return 0 // 일직선 + return val > 0 ? 1 : 2 // 시계방향 또는 반시계방향 + } + + // 점 q가 선분 pr 위에 있는지 확인 + function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y) + } + + // 같은 끝점을 공유하는 경우는 교차로 보지 않음 + if ((p1.x === p2.x && p1.y === p2.y) || (p1.x === q2.x && p1.y === q2.y) || (q1.x === p2.x && q1.y === p2.y) || (q1.x === q2.x && q1.y === q2.y)) { + return false + } + + const o1 = orientation(p1, q1, p2) + const o2 = orientation(p1, q1, q2) + const o3 = orientation(p2, q2, p1) + const o4 = orientation(p2, q2, q1) + + // 일반적인 교차 경우 + if (o1 !== o2 && o3 !== o4) return true + + // 특별한 경우들 (한 점이 다른 선분 위에 있는 경우) + if (o1 === 0 && onSegment(p1, p2, q1)) return true + if (o2 === 0 && onSegment(p1, q2, q1)) return true + if (o3 === 0 && onSegment(p2, p1, q2)) return true + if (o4 === 0 && onSegment(p2, q1, q2)) return true + + return false +} + +/** + * 다각형의 자기 교차를 검사하는 메인 함수 + * @param {Array} coordinates 다각형의 좌표 배열 [{x, y}, ...] + * @returns {Object} 검사 결과 {hasSelfIntersection: boolean, intersections: Array} + */ +function checkPolygonSelfIntersection(coordinates) { + if (coordinates.length < 3) { + return { + hasSelfIntersection: false, + intersections: [], + error: '다각형은 최소 3개의 점이 필요합니다.', + } + } + + const intersections = [] + const edges = [] + + // 모든 변(edge) 생성 + for (let i = 0; i < coordinates.length; i++) { + const start = coordinates[i] + const end = coordinates[(i + 1) % coordinates.length] + edges.push({ + start: start, + end: end, + index: i, }) } - return uniquePolygons + // 모든 변 쌍에 대해 교차 검사 + for (let i = 0; i < edges.length; i++) { + for (let j = i + 1; j < edges.length; j++) { + // 인접한 변들은 제외 (끝점을 공유하므로) + if (Math.abs(i - j) === 1 || (i === 0 && j === edges.length - 1)) { + continue + } + + const edge1 = edges[i] + const edge2 = edges[j] + + if (doSegmentsIntersect(edge1.start, edge1.end, edge2.start, edge2.end)) { + intersections.push({ + edge1Index: i, + edge2Index: j, + edge1: { + from: edge1.start, + to: edge1.end, + }, + edge2: { + from: edge2.start, + to: edge2.end, + }, + }) + } + } + } + + return intersections.length > 0 } // 같은 직선상에 있는지 확인 같은 직선이라면 polygon을 생성할 수 없으므로 false