diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ba5be5e7..af5d676e 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1,292 +1,248 @@ import { fabric } from 'fabric' -import QPolygon from '@/components/fabric/QPolygon3' import { QLine } from '@/components/fabric/QLine' -import { calculateIntersection2, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' +import { calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' export const defineQPloygon = () => { - fabric.QPolygon = fabric.util.createClass(fabric.Group, {}) - // fromObject 메서드를 QLine 클래스에 직접 추가 fabric.QPolygon.fromObject = function (object, callback) { - const { initOption, initPoints, initLengthTxt } = object - - fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { - return callback(new QPolygon(initPoints, object, initLengthTxt)) - }) + fabric.Object._fromObject('QPolygon', object, callback, 'points') } } export const drawHelpLineInHexagon = (polygon, chon) => { const centerLines = drawCenterLines(polygon) + let helpLines = [] + const interSectionPoints = [] + const tempInterSectionPoints = [] + const ridgeStartPoints = [] const ridgeEndPoints = [] const centerInterSectionPoints = [] - let maxLength = 0 - - polygon.lines = polygon.lines.sort((a, b) => a.length - b.length) + // polygon.lines = polygon.lines.sort((a, b) => a.length - b.length) polygon.wall.lines = getOneSideLines(polygon.wall) - // 짧은 라인 순서대로 삼각 지붕을 그린다. - polygon.lines.forEach((line, index) => { - if (line.length > maxLength) { - maxLength = line.length - } - const wallLine = polygon.wall.lines.filter((wallLine) => wallLine.idx === line.idx)[0] + const maxLength = Math.max(...polygon.lines.map((line) => line.length)) - const checkPoint1 = polygon.points.filter((point) => point.x === line.x1 && point.y === line.y1)[0] - const checkPoint2 = polygon.points.filter((point) => point.x === line.x2 && point.y === line.y2)[0] - - if (checkPoint1.alreadyIntersected || checkPoint2.alreadyIntersected) { - return - } - - const angle1 = Math.atan2(wallLine.y1 - line.y1, wallLine.x1 - line.x1) - const angle2 = Math.atan2(wallLine.y2 - line.y2, wallLine.x2 - line.x2) - - // line을 이등변 삼각형의 밑변으로 보고 높이를 구한다. - - const helpLineLength = Math.sqrt(2 * Math.pow(line.length / 2, 2)) - - const firstX2 = Math.floor(line.x1 + helpLineLength * Math.cos(angle1)) - const firstY2 = Math.floor(line.y1 + helpLineLength * Math.sin(angle1)) - - const secondX2 = Math.floor(line.x2 + helpLineLength * Math.cos(angle2)) - const secondY2 = Math.floor(line.y2 + helpLineLength * Math.sin(angle2)) - - const firstHelpLine = new QLine([line.x1, line.y1, firstX2, firstY2], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - const secondHelpLine = new QLine([line.x2, line.y2, secondX2, secondY2], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - const interSectionPoint = calculateIntersection2(firstHelpLine, secondHelpLine) - - if (interSectionPoint) { - if (polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { - checkPoint1.alreadyIntersected = true - checkPoint2.alreadyIntersected = true - - const helpLine1 = new QLine([line.x1, line.y1, interSectionPoint.x, interSectionPoint.y], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - const helpLine2 = new QLine([line.x2, line.y2, interSectionPoint.x, interSectionPoint.y], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - helpLines.push(helpLine1) - helpLines.push(helpLine2) - - polygon.canvas.add(helpLine1) - polygon.canvas.add(helpLine2) - - ridgeStartPoints.push(interSectionPoint) - } - } - - polygon.canvas.renderAll() - }) - // points를 순회하면서 이미 그려진 점이 아닌 경우 centerLine과 만나는 점을 찾는다. polygon.points.forEach((point, index) => { - if (point.alreadyIntersected) { - return - } - const wallPoint = polygon.wall.points[index] const angle = Math.atan2(wallPoint.y - point.y, wallPoint.x - point.x) - const newX2 = Math.floor(point.x + (maxLength / 2 + 50) * Math.cos(angle)) - const newY2 = Math.floor(point.y + (maxLength / 2 + 50) * Math.sin(angle)) + const degree = fabric.util.radiansToDegrees(angle) + + const newX2 = Math.floor(point.x + maxLength * Math.cos(angle)) + const newY2 = Math.floor(point.y + maxLength * Math.sin(angle)) const helpLine = new QLine([point.x, point.y, newX2, newY2], { fontSize: polygon.fontSize, stroke: 'green', + startPoint: point, + degree: degree, + idx: index, }) - const relatedPoints = [] + // polygon.canvas?.add(helpLine) - centerLines.forEach((centerLine) => { - const interSectionPoint = calculateIntersection2(helpLine, centerLine) - - if (interSectionPoint) { - if (polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { - relatedPoints.push(interSectionPoint) - - centerInterSectionPoints.push(interSectionPoint) - } - } - }) - - helpLine.set({ relatedPoints: relatedPoints }) helpLines.push(helpLine) }) - helpLines = helpLines.filter((line) => line.relatedPoints?.length > 0) + helpLines.forEach((line, index) => { + for (let i = index + 1; i < helpLines.length; i++) { + const nextLine = helpLines[i] + if (!line.connectedPoint) { + line.connectedPoint = null + line.connectedPoints = [] + } + if (!nextLine.connectedPoint) { + nextLine.connectedPoint = null + nextLine.connectedPoints = [] + } - ridgeStartPoints.forEach((point) => { - point.alreadyIntersected = false - // x 혹은 y가 같으면서 가장 가까운 점을 찾는다. - let arrivalPoint - let hipLine - let distance = Infinity - let startPoint - helpLines.forEach((line) => { - line.relatedPoints.forEach((relatedPoint) => { - if (Math.abs(point.x - relatedPoint.x) <= 2 || Math.abs(point.y - relatedPoint.y) <= 2) { - if (distanceBetweenPoints(point, relatedPoint) < distance) { - startPoint = point - distance = distanceBetweenPoints(point, relatedPoint) - hipLine = line - arrivalPoint = relatedPoint - } + const interSectionPoint = calculateIntersection(line, nextLine) + + if ( + interSectionPoint && + polygon.inPolygon(interSectionPoint) && + polygon.wall.inPolygon(interSectionPoint) && + Math.abs(distanceBetweenPoints(line.startPoint, interSectionPoint) - distanceBetweenPoints(nextLine.startPoint, interSectionPoint)) < 2 + ) { + const area = calculateTriangleArea(line.startPoint, nextLine.startPoint, interSectionPoint) + const currentLineConnectedPoint = line.connectedPoint + const nextLineConnectedPoint = nextLine.connectedPoint + + if (area <= 1) { + return } - }) - }) - if (arrivalPoint) { - hipLine.relatedPoints.forEach((relatedPoint) => { - if (relatedPoint.x !== arrivalPoint.x && relatedPoint.y !== arrivalPoint.y) { - centerInterSectionPoints.splice(centerInterSectionPoints.indexOf(relatedPoint), 1) + if (currentLineConnectedPoint && currentLineConnectedPoint.area < area) { + return } - }) - helpLines.splice(helpLines.indexOf(hipLine), 1) + //startPoint는 line의 startPoint와 nextLine의 startPoint를 비교하여 x가 같은경우 y가 더 작은 값, y가 같은경우 x가 더 작은 값을 선택한다. + const startPoint = + line.startPoint.x === nextLine.startPoint.x + ? line.startPoint.y < nextLine.startPoint.y + ? line.startPoint + : nextLine.startPoint + : line.startPoint.x < nextLine.startPoint.x + ? line.startPoint + : nextLine.startPoint - const helpLine = new QLine([hipLine.x1, hipLine.y1, arrivalPoint.x, arrivalPoint.y], { - fontSize: polygon.fontSize, - stroke: 'red', - }) - const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], { - stroke: 'green', - fontSize: polygon.fontSize, - }) + const endPoint = + line.startPoint.x === nextLine.startPoint.x + ? line.startPoint.y > nextLine.startPoint.y + ? line.startPoint + : nextLine.startPoint + : line.startPoint.x > nextLine.startPoint.x + ? line.startPoint + : nextLine.startPoint - polygon.canvas.add(helpLine) - polygon.canvas.add(ridge) - - polygon.canvas.renderAll() - ridgeEndPoints.push(arrivalPoint) - - point.alreadyIntersected = true + line.connectedPoint = { interSectionPoint, area, startPoint, endPoint } + line.connectedPoints.push(interSectionPoint) + nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint } + nextLine.connectedPoints.push(interSectionPoint) + } } }) - /** - * 안쓰는 점 제거 - */ - - const ridgeEndRemainingPoints = [...ridgeEndPoints] - - const uniqueInterSectionPoints = Array.from(new Set(centerInterSectionPoints.map((point) => `${point.x},${point.y}`))).map((key) => { - const [x, y] = key.split(',').map(Number) - return { x, y } + helpLines.forEach((line) => { + if (line.connectedPoint) { + tempInterSectionPoints.push(line.connectedPoint) + } }) - while (ridgeEndRemainingPoints.length > 0) { - const point = ridgeEndRemainingPoints.shift() - let isExist = false - uniqueInterSectionPoints.forEach((uniquePoint) => { - const degree = calculateAngle(point, uniquePoint) + // interSectionPoints에서 interSectionPoint가 중복인 값이 있는 경우만 선택한다. + tempInterSectionPoints.forEach((point) => { + // intersectionPoint가 중복인 경우 + const isDuplicated = + tempInterSectionPoints.filter((p) => p.interSectionPoint.x === point.interSectionPoint.x && p.interSectionPoint.y === point.interSectionPoint.y) + .length > 1 + if (isDuplicated) { + interSectionPoints.push(point) + } + }) - if (Math.abs(45 - Math.abs(degree)) <= 5 || Math.abs(135 - Math.abs(degree)) <= 5) { - const line = new QLine([point.x, point.y, uniquePoint.x, uniquePoint.y], { - stroke: 'purple', - fontSize: polygon.fontSize, - }) + // interSectionPoints에서 interSectionPoint 기준으로 중복을 제거한다. + const uniqueInterSectionPoints = Array.from( + new Set(interSectionPoints.map((point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}`)), + ).map((key) => { + const { interSectionPoint, area, startPoint, endPoint } = interSectionPoints.find( + (point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}` === key, + ) + return { interSectionPoint, area, startPoint, endPoint } + }) - ridgeEndPoints.push(uniquePoint) + uniqueInterSectionPoints.forEach((point) => { + ridgeStartPoints.push(point.interSectionPoint) - ridgeEndPoints.splice(ridgeEndPoints.indexOf(point), 1) - - isExist = true - - polygon.canvas.add(line) - polygon.canvas.renderAll() - } - - if (isExist) { - return - } - }) - } - - const ridgeEndRemainingPoints2 = [...ridgeEndPoints] - - while (ridgeEndRemainingPoints2.length > 0) { - // 남아있는 점끼리 연결한다. - const point = ridgeEndRemainingPoints2.shift() - const closestPoint = findClosestPoint(point, ridgeEndRemainingPoints2) - if (!closestPoint) continue - const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { + const line = new QLine([point.startPoint.x, point.startPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], { stroke: 'purple', fontSize: polygon.fontSize, + name: 'hip', }) + const line2 = new QLine([point.endPoint.x, point.endPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'hip', + }) + + polygon.hips.push(line) + polygon.hips.push(line2) + polygon.canvas.add(line) - polygon.canvas.renderAll() - } + polygon.canvas.add(line2) + }) + + const removedIdx = [] - // ridgeEndPoints와 가까운 centerInterSectionPoints를 찾아서 연결한다. - const remainingPoints = centerInterSectionPoints - /* helpLines.forEach((line) => { - remainingPoints.forEach((point) => { - if (line.relatedPoints.includes(point)) { - const hip = new QLine([line.x1, line.y1, point.x, point.y], { - stroke: 'red', - fontSize: polygon.fontSize, - }) + const connectedPoints = line.connectedPoints + connectedPoints.forEach((connectedPoint) => { + uniqueInterSectionPoints.forEach((point) => { + const interSectionPoint = point.interSectionPoint - polygon.canvas.add(hip) - polygon.canvas.renderAll() + if (connectedPoint.x === interSectionPoint.x && connectedPoint.y === interSectionPoint.y) { + removedIdx.push(line.idx) + } + }) + }) + }) + + const notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx)) + + notIntersectedLines.forEach((line) => { + centerLines.forEach((centerLine) => { + const interSectionPoint = calculateIntersection(line, centerLine) + + if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { + centerInterSectionPoints.push(interSectionPoint) } }) }) + // centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다. + ridgeStartPoints.forEach((point) => { + const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2) + const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2) + let closestPoint + if (xPoints.length === 0) { + closestPoint = findClosestPoint(point, yPoints) + } else { + closestPoint = findClosestPoint(point, xPoints) + } + if (closestPoint) { + const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'ridge', + }) + polygon.ridges.push(line) + polygon.canvas.add(line) + ridgeEndPoints.push(closestPoint) + } + }) + + // ridgeEndPoints끼리 이어준다. + const remainingPoints = ridgeEndPoints + + remainingPoints.forEach((ridgePoint) => { + polygon.points.forEach((point) => { + const degree = calculateAngle(ridgePoint, point) + + if (Math.abs(degree) % 45 < 1) { + const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'hip', + }) + + polygon.hips.push(line) + polygon.canvas.add(line) + } + }) + }) - // centerInterSectionPoints에 남아있는 점들을 가까운 점끼리 연결한다. while (remainingPoints.length > 0) { const point = remainingPoints.shift() const closestPoint = findClosestPoint(point, remainingPoints) if (!closestPoint) continue + // 마루끼리 연결 const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { stroke: 'purple', fontSize: polygon.fontSize, + name: 'connectRidge', }) + polygon.connectRidges.push(line) polygon.canvas.add(line) - polygon.canvas.renderAll() } - - const notIntersectedRidgeStartPoints = ridgeStartPoints.filter((point) => !point.alreadyIntersected) - // 만나지 않은 마루 시작점 - while (notIntersectedRidgeStartPoints.length > 0) { - const point = notIntersectedRidgeStartPoints.shift() - const closestPoint = findClosestPoint(point, notIntersectedRidgeStartPoints) - if (!closestPoint) continue - const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { - stroke: 'purple', - fontSize: polygon.fontSize, - }) - - polygon.canvas.add(line) - polygon.canvas.renderAll() - }*/ } -export const drawHelpLineInHexagon2 = (polygon, chon) => {} - export const drawCenterLines = (polygon) => { const centerLines = [] @@ -384,3 +340,38 @@ const calculateAngle = (point1, point2) => { const angleInRadians = Math.atan2(deltaY, deltaX) return angleInRadians * (180 / Math.PI) } + +/** + * 3개의 점을 이용해 직각 이등변 삼각형인지 확인 + * @param point1 + * @param point2 + * @param point3 + * @returns {boolean} + */ +const isRightIsoscelesTriangle = (point1, point2, point3) => { + const distance = (p1, p2) => Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) + + const d1 = distance(point1, point2) + const d2 = distance(point2, point3) + const d3 = distance(point3, point1) + + const distances = [d1, d2, d3].sort((a, b) => a - b) + + // Check if the two smaller distances are equal and the largest distance is the hypotenuse + return distances[0] === distances[1] && Math.abs(Math.pow(distances[0], 2) * 2 - Math.pow(distances[2], 2)) < 1 +} + +/** + * 세개의 점으로 삼각형의 넓이를 구한다. + * @param point1 + * @param point2 + * @param point3 + * @returns {number} + */ +const calculateTriangleArea = (point1, point2, point3) => { + const { x: x1, y: y1 } = point1 + const { x: x2, y: y2 } = point2 + const { x: x3, y: y3 } = point3 + + return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2 +}