From 5f648632dded73953c8c5e7ef935fcebebcd248a Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 28 Apr 2025 18:07:54 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B5=9C=EB=8B=A8=EA=B1=B0=EB=A6=AC=20roof=20?= =?UTF-8?q?=EB=82=98=EB=88=84=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 280 ++++++++++++++++++++++++++-------------- 1 file changed, 180 insertions(+), 100 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index fd5b5a69..9a5f39f8 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' +import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine, toFixedWithoutRounding } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' @@ -757,7 +757,7 @@ export const usePolygon = () => { polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] - // innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. + /*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다. if (!innerLines || innerLines.length === 0) { let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key]) polygon.innerLines = canvas @@ -771,8 +771,7 @@ export const usePolygon = () => { ) innerLines = [...polygon.innerLines] - } - + }*/ canvas.renderAll() let polygonLines = [...polygon.lines] const roofs = [] @@ -830,8 +829,14 @@ export const usePolygon = () => { canvas.renderAll() polygonLines.forEach((line) => { + /*const originStroke = line.stroke + line.set({ stroke: 'red' }) + canvas.renderAll()*/ const intersections = [] innerLines.forEach((innerLine) => { + /*const originInnerStroke = innerLine.stroke + innerLine.set({ stroke: 'red' }) + canvas.renderAll()*/ if (isPointOnLine(line, innerLine.startPoint)) { if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) { return @@ -844,8 +849,13 @@ export const usePolygon = () => { } intersections.push(innerLine.endPoint) } + /*innerLine.set({ stroke: originInnerStroke }) + canvas.renderAll()*/ }) line.set({ intersections }) + + /*line.set({ stroke: originStroke }) + canvas.renderAll()*/ }) const divideLines = polygonLines.filter((line) => line.intersections.length > 0) @@ -924,10 +934,52 @@ export const usePolygon = () => { }) //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. - polygonLines = polygonLines.filter((line) => !divideLines.includes(line)) + polygonLines = polygonLines.filter((line) => line.intersections?.length === 0) + polygonLines = [...polygonLines, ...newLines] - const allLines = [...polygonLines, ...innerLines] + let allLines = [...polygonLines, ...innerLines] + + /*allLines.forEach((line) => { + const originColor = line.stroke + + line.set('stroke', 'red') + canvas.renderAll() + + line.set('stroke', originColor) + canvas.renderAll() + })*/ + + const allPoints = [] + + // test용 좌표 + const polygonLinesPoints = polygonLines.map((line) => { + return { startPoint: line.startPoint, endPoint: line.endPoint } + }) + + const innerLinesPoints = innerLines.map((line) => { + return { startPoint: line.startPoint, endPoint: line.endPoint } + }) + + polygonLinesPoints.forEach(({ startPoint, endPoint }) => { + allPoints.push(startPoint) + allPoints.push(endPoint) + }) + + innerLinesPoints.forEach(({ startPoint, endPoint }) => { + allPoints.push(startPoint) + allPoints.push(endPoint) + }) + + // allPoints에서 중복을 제거한다. + const uniquePoints = allPoints.filter((point, index, self) => { + return ( + index === + self.findIndex((p) => { + return isSamePoint(p, point) + }) + ) + }) // 2025-02-19 대각선은 케라바, 직선은 용마루로 세팅 innerLines.forEach((innerLine) => { @@ -978,102 +1030,23 @@ export const usePolygon = () => { line.endPoint = endPoint }) - // polygon line에서 각각 출발한다. - polygonLines.forEach((line) => { - /*line.set({ strokeWidth: 5, stroke: 'green' }) - canvas.add(line) - canvas.renderAll()*/ - const startPoint = { ...line.startPoint } // 시작점 - let arrivalPoint = { ...line.endPoint } // 도착점 + // polygonLines에서 시작점 혹은 끝점이 innerLines와 연결된 line만 가져온다. + let startLines = polygonLines.filter((line) => { + const startPoint = line.startPoint + const endPoint = line.endPoint - let currentPoint = startPoint - let roofPoints = [startPoint] - - let startLine = line - let visitPoints = [startPoint] - let visitLines = [startLine] - let notVisitedLines = [] - let cnt = 0 - - while (!isSamePoint(currentPoint, arrivalPoint)) { - //현재 점으로 부터 갈 수 있는 다른 라인을 찾는다. - let nextLines = allLines.filter( - (line2) => - (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && - line !== line2 && - innerLines.includes(line2) && - !visitLines.includes(line2), + return innerLines.some((innerLine) => { + return ( + isSamePoint(innerLine.startPoint, startPoint) || + isSamePoint(innerLine.endPoint, startPoint) || + isSamePoint(innerLine.startPoint, endPoint) || + isSamePoint(innerLine.endPoint, endPoint) ) - - if (nextLines.length === 0) { - nextLines = allLines.filter( - (line2) => - (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && - line !== line2 && - !visitLines.includes(line2), - ) - } - - if (nextLines.length === 0) { - //아직 안갔던 line중 0번째를 선택한다. - if (notVisitedLines.length === 0) { - break - } else { - let notVisitedLine = notVisitedLines.shift() - roofPoints = [...notVisitedLine.roofPoints] - currentPoint = { ...notVisitedLine.currentPoint } - continue - } - } - - let comparisonPoints = [] - - nextLines.forEach((nextLine) => { - if (isSamePoint(nextLine.startPoint, currentPoint)) { - comparisonPoints.push(nextLine.endPoint) - } else { - comparisonPoints.push(nextLine.startPoint) - } - }) - - comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point))) - comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint)) - - const minDistancePoint = comparisonPoints.reduce((prev, current) => { - const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2)) - const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2)) - - return prevDistance < currentDistance ? prev : current - }, comparisonPoints[0]) - - nextLines.forEach((nextLine) => { - if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) { - visitLines.push(nextLine) - } else { - notVisitedLines.push({ - line: nextLine, - endPoint: nextLine.endPoint, - startPoint: nextLine.startPoint, - currentPoint: { ...currentPoint }, - roofPoints: [...roofPoints], - }) - } - }) - - currentPoint = { ...minDistancePoint } - roofPoints.push(currentPoint) - cnt++ - - if (cnt > 100) { - break - } - } - roofs.push(roofPoints) - canvas.remove(line) - canvas.renderAll() + }) }) - const newRoofs = removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) + // 나눠서 중복 제거된 roof return + const newRoofs = getSplitRoofsPoints(startLines, allLines, innerLines, uniquePoints) newRoofs.forEach((roofPoint, index) => { let defense, pitch @@ -1104,7 +1077,7 @@ export const usePolygon = () => { }) // blue로 생성된 것들은 대표라인이 될 수 없음. - representLines = representLines.filter((line) => line.stroke !== 'blue') + // representLines = representLines.filter((line) => line.stroke !== 'blue') // representLines중 가장 긴 line을 찾는다. representLines.forEach((line) => { if (!representLine) { @@ -1115,7 +1088,7 @@ export const usePolygon = () => { } } }) - const direction = polygon.direction ?? representLine.direction + const direction = polygon.direction ?? representLine?.direction const polygonDirection = polygon.direction switch (direction) { @@ -1174,6 +1147,113 @@ export const usePolygon = () => { }) } + const getSplitRoofsPoints = (startLines, allLines, innerLines, uniquePoints) => { + // 거리 계산 + function calcDistance(p1, p2) { + return Math.hypot(p2.x - p1.x, p2.y - p1.y) + } + + // 바로 연결 체크 + function isDirectlyConnected(start, end, graph) { + const startKey = `${start.x},${start.y}` + if (!graph[startKey]) return false + return graph[startKey].some((neighbor) => neighbor.point.x === end.x && neighbor.point.y === end.y) + } + + // Dijkstra 최단 경로 + function findShortestPath(start, end, graph) { + const startKey = `${start.x},${start.y}` + const endKey = `${end.x},${end.y}` + + const distances = {} + const previous = {} + const visited = new Set() + const queue = [{ key: startKey, dist: 0 }] + + for (const key in graph) { + distances[key] = Infinity + } + distances[startKey] = 0 + + while (queue.length > 0) { + queue.sort((a, b) => a.dist - b.dist) + const { key } = queue.shift() + if (visited.has(key)) continue + visited.add(key) + + for (const neighbor of graph[key]) { + const neighborKey = `${neighbor.point.x},${neighbor.point.y}` + const alt = distances[key] + neighbor.distance + if (alt < distances[neighborKey]) { + distances[neighborKey] = alt + previous[neighborKey] = key + queue.push({ key: neighborKey, dist: alt }) + } + } + } + + // 경로 복구 + const path = [] + let currentKey = endKey + + if (!previous[currentKey]) { + return null // 경로 없음 + } + + while (currentKey !== startKey) { + const [x, y] = currentKey.split(',').map(Number) + path.unshift({ x, y }) + currentKey = previous[currentKey] + } + path.unshift(start) + + return path + } + + // 최종 함수 + function getPath(start, end, graph) { + if (isDirectlyConnected(start, end, graph)) { + return null + } else { + const path = findShortestPath(start, end, graph) + if (!path || path.length < 3) { + return null + } + return path + } + } + + const roofs = [] + + startLines.forEach((line) => { + // 그래프 생성 + const graph = {} + const edges = allLines + .filter((line2) => line !== line2) + .map((line) => { + return [line.startPoint, line.endPoint] + }) + + for (const [p1, p2] of edges) { + const key1 = `${p1.x},${p1.y}` + const key2 = `${p2.x},${p2.y}` + const distance = calcDistance(p1, p2) + + if (!graph[key1]) graph[key1] = [] + if (!graph[key2]) graph[key2] = [] + + graph[key1].push({ point: p2, distance }) + graph[key2].push({ point: p1, distance }) + } + + const startPoint = { ...line.startPoint } // 시작점 + let arrivalPoint = { ...line.endPoint } // 도착점 + roofs.push(getPath(startPoint, arrivalPoint, graph)) + }) + + return removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) + } + const splitPolygonWithSeparate = (separates) => { separates.forEach((separate) => { const points = separate.lines.map((line) => {