import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' import { POLYGON_TYPE } from '@/common/common' const TWO_PI = Math.PI * 2 export const defineQPloygon = () => { fabric.QPolygon.fromObject = function (object, callback) { 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 = [] let centerInterSectionPoints = [] // polygon.lines = polygon.lines.sort((a, b) => a.length - b.length) polygon.wall.lines = getOneSideLines(polygon.wall) const maxLength = Math.max(...polygon.lines.map((line) => line.length)) polygon.points.forEach((point, index) => { const wallPoint = polygon.wall.points[index] const angle = Math.atan2(wallPoint.y - point.y, wallPoint.x - point.x) 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, }) // polygon.canvas?.add(helpLine) helpLines.push(helpLine) }) 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 = [] } 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 (currentLineConnectedPoint && currentLineConnectedPoint.area < area) { return } //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 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 line.connectedPoint = { interSectionPoint, area, startPoint, endPoint } line.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint }) nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint } nextLine.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint }) } } }) helpLines.forEach((line) => { if (line.connectedPoint) { tempInterSectionPoints.push(line.connectedPoint) } }) // 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) } }) // 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 } }) uniqueInterSectionPoints.forEach((point) => { ridgeStartPoints.push(point.interSectionPoint) 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', }) line.startPoint = point.startPoint line.endPoint = point.interSectionPoint line2.startPoint = point.endPoint line2.endPoint = point.interSectionPoint polygon.hips.push(line) polygon.hips.push(line2) polygon.canvas.add(line) polygon.canvas.add(line2) }) const removedIdx = [] helpLines.forEach((line) => { const connectedPoints = line.connectedPoints connectedPoints.forEach((connectedPoint) => { uniqueInterSectionPoints.forEach((point) => { const interSectionPoint = point.interSectionPoint if (connectedPoint.interSectionPoint.x === interSectionPoint.x && connectedPoint.interSectionPoint.y === interSectionPoint.y) { removedIdx.push(line.idx) } }) }) }) let notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx)) notIntersectedLines = notIntersectedLines.map((line) => { return { ...line, centerInterSectionPoints: [] } }) notIntersectedLines.forEach((line) => { centerLines.forEach((centerLine) => { const interSectionPoint = calculateIntersection(line, centerLine) if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { line.centerInterSectionPoints.push(interSectionPoint) interSectionPoint.lineIdx = line.idx 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 if (yPoints.length === 0) { 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', direction: getDirectionByPoint(point, closestPoint), }) line.startPoint = point line.endPoint = closestPoint polygon.ridges.push(line) polygon.canvas.add(line) ridgeEndPoints.push(closestPoint) notIntersectedLines = notIntersectedLines.filter((line) => line.idx !== closestPoint.lineIdx) } }) centerInterSectionPoints = [] notIntersectedLines.forEach((line) => { centerInterSectionPoints.push(...line.centerInterSectionPoints) }) // ridgeEndPoints끼리 이어준다. const remainingPoints = [...ridgeEndPoints] // ridgeEndPoint에서 centerInterSectionPoints와 45도인 점을 찾아 이어준다. ridgeEndPoints.forEach((ridgePoint) => { const filteredCenterInterSectionPoints = centerInterSectionPoints.filter((centerPoint) => { const degree = calculateAngle(ridgePoint, centerPoint) return Math.abs(degree) === 45 || Math.abs(degree) === 135 })[0] if (filteredCenterInterSectionPoints) { const line = new QLine([ridgePoint.x, ridgePoint.y, filteredCenterInterSectionPoints.x, filteredCenterInterSectionPoints.y], { stroke: 'purple', fontSize: polygon.fontSize, name: 'hip', }) if (line.length === 0) { return } line.startPoint = ridgePoint line.endPoint = filteredCenterInterSectionPoints polygon.hips.push(line) polygon.canvas.add(line) ridgeStartPoints.push(filteredCenterInterSectionPoints) 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) } }) } }) // ridgeEndPoint끼리 연결한다. while (remainingPoints.length > 1) { const startPoint = remainingPoints.shift() const endPoint = remainingPoints.shift() if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) { const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], { stroke: 'purple', fontSize: polygon.fontSize, name: 'connectRidge', }) line.startPoint = startPoint line.endPoint = endPoint polygon.connectRidges.push(line) polygon.points.forEach((point) => { const degree = calculateAngle(startPoint, point) if (Math.abs(degree) === 45 || Math.abs(degree) === 135) { const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], { stroke: 'purple', fontSize: polygon.fontSize, name: 'hip', }) line.startPoint = startPoint line.endPoint = point polygon.hips.push(line) polygon.canvas.add(line) } }) polygon.points.forEach((point) => { const degree = calculateAngle(endPoint, point) if (Math.abs(degree) === 45 || Math.abs(degree) === 135) { const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], { stroke: 'purple', fontSize: polygon.fontSize, name: 'hip', }) line.startPoint = endPoint line.endPoint = point polygon.hips.push(line) polygon.canvas.add(line) } }) polygon.canvas.add(line) } else { polygon.points.forEach((point) => { const degree = calculateAngle(startPoint, point) if (Math.abs(degree) === 45 || Math.abs(degree) === 135) { const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], { stroke: 'purple', fontSize: polygon.fontSize, name: 'hip', }) line.startPoint = startPoint line.endPoint = point polygon.hips.push(line) polygon.canvas.add(line) } }) } } } export const drawCenterLines = (polygon) => { const centerLines = [] const oneSideLines = polygon.lines.map((line) => getOneSideLine(line)) const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') // horizontalLines 를 y1 좌표 기준으로 정렬한다. horizontalLines.sort((a, b) => a.y1 - b.y1) // verticalLines 를 x1 좌표 기준으로 정렬한다. verticalLines.sort((a, b) => a.x1 - b.x1) let maxHorizontalLineLength = 0 let maxVerticalLineLength = 0 // 모든 가로선의 중심선을 긋는다. horizontalLines.forEach((line, index) => { const nextLine = horizontalLines[(index + 1) % horizontalLines.length] polygon.canvas.renderAll() const startCenterX = Math.min(line.x1, nextLine.x1) const startCenterY = (line.y1 + nextLine.y1) / 2 const endCenterX = line.x2 > nextLine.x2 ? line.x2 : nextLine.x2 const endCenterY = startCenterY const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, direction: 'horizontal', }) centerLines.push(centerLine) }) // 모든 세로선의 중심선을 긋는다. verticalLines.forEach((line, index) => { const nextLine = verticalLines[(index + 1) % verticalLines.length] const startCenterX = (line.x1 + nextLine.x1) / 2 const startCenterY = Math.min(line.y1, nextLine.y1) const endCenterX = startCenterX let endCenterY = line.y2 > nextLine.y2 ? line.y2 : nextLine.y2 const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { fontSize: polygon.fontSize, stroke: 'blue', strokeWidth: 1, direction: 'vertical', }) centerLines.push(centerLine) }) return centerLines } const getOneSideLines = (polygon) => { return [...polygon.lines].map((line) => { let newX1, newY1, newX2, newY2 if (line.direction === 'top') { newX1 = line.x2 newY1 = line.y2 newX2 = line.x1 newY2 = line.y1 line.x1 = newX1 line.y1 = newY1 line.x2 = newX2 line.y2 = newY2 line.direction = 'bottom' line.startPoint = { x: newX1, y: newY1 } line.endPoint = { x: newX2, y: newY2 } } else if (line.direction === 'left') { newX1 = line.x2 newY1 = line.y2 newX2 = line.x1 newY2 = line.y1 line.x1 = newX1 line.y1 = newY1 line.x2 = newX2 line.y2 = newY2 line.direction = 'right' line.startPoint = { x: newX1, y: newY1 } line.endPoint = { x: newX2, y: newY2 } } return line }) } export const calculateAngle = (point1, point2) => { const deltaX = point2.x - point1.x const deltaY = point2.y - point1.y 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 } // polygon을 나눈다. export const dividePolygon = (polygon) => { let hips = polygon.hips const ridges = polygon.ridges.map((ridge) => getOneSideLine(ridge)) const connectRidges = polygon.connectRidges const polygonLines = polygon.lines hips.forEach((hip) => { // hips의 startPoint와 endPoint를 polygon의 points와 비교하여 같은 점이 endPoint일 경우 startPoint로 변경한다. const startPoint = polygon.points.find((point) => point.x === hip.endPoint.x && point.y === hip.endPoint.y) if (startPoint) { const temp = hip.startPoint hip.startPoint = hip.endPoint hip.endPoint = temp } }) hips = [...hips, ...connectRidges] polygonLines.forEach((line, index) => { let ridge const startPoint = line.startPoint const endPoint = line.endPoint let polygonPoints = [] polygonPoints.push(startPoint) polygonPoints.push(endPoint) const startHip = hips.find((hip) => hip.startPoint.x === startPoint.x && hip.startPoint.y === startPoint.y) const endHip = hips.find((hip) => hip.startPoint.x === endPoint.x && hip.startPoint.y === endPoint.y) if (!startHip || !endHip) { return } if (startHip && endHip && startHip.endPoint.x === endHip.endPoint.x && startHip.endPoint.y === endHip.endPoint.y) { polygonPoints.push(startHip.endPoint) const newPolygon = new QPolygon(polygonPoints, { fontSize: polygon.fontSize, parentId: polygon.id, name: 'roof', selectable: false, stroke: 'black', fill: 'transparent', strokeWidth: 3, }) polygon.canvas.add(newPolygon) return } let connectedRidge const restRidgeConnection = connectRidges[0] if (!restRidgeConnection || restRidgeConnection.length === 0) { connectedRidge = ridges.find( (ridge) => (ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y && ridge.endPoint.x === endHip.endPoint.x && ridge.endPoint.y === endHip.endPoint.y) || (ridge.startPoint.x === endHip.endPoint.x && ridge.startPoint.y === endHip.endPoint.y && ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y), ) } else { connectedRidge = ridges.find( (ridge) => (ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) || (ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y), ) } const hipStartPoint = startHip.endPoint const hipEndPoint = endHip.endPoint if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) { if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) { polygonPoints.push(connectedRidge.endPoint) polygonPoints.push(connectedRidge.startPoint) const newPolygon = new QPolygon(polygonPoints, { fontSize: polygon.fontSize, parentId: polygon.id, name: 'roof', selectable: false, stroke: 'black', fill: 'transparent', strokeWidth: 3, }) polygon.canvas.add(newPolygon) return } } else if (connectedRidge.endPoint.x === hipStartPoint.x && connectedRidge.endPoint.y === hipStartPoint.y) { if (connectedRidge.startPoint.x === hipEndPoint.x && connectedRidge.startPoint.y === hipEndPoint.y) { polygonPoints.push(connectedRidge.startPoint) polygonPoints.push(connectedRidge.endPoint) const newPolygon = new QPolygon(polygonPoints, { fontSize: polygon.fontSize, parentId: polygon.id, name: 'roof', selectable: false, stroke: 'black', fill: 'transparent', strokeWidth: 3, sort: true, }) polygon.canvas.add(newPolygon) return } } // 지붕이 꺾여있는 경우 if ( (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) || (restRidgeConnection.endPoint.x === startHip.endPoint.x && restRidgeConnection.endPoint.y === startHip.endPoint.y) ) { polygonPoints = [startPoint, startHip.endPoint] let lastPoint if (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) { lastPoint = restRidgeConnection.endPoint polygonPoints.push(restRidgeConnection.endPoint) } else { lastPoint = restRidgeConnection.startPoint polygonPoints.push(restRidgeConnection.startPoint) } connectedRidge = ridges.find( (ridge) => (ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) || (ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y), ) if (connectedRidge.startPoint.x === lastPoint.x && connectedRidge.startPoint.y === lastPoint.y) { polygonPoints.push(connectedRidge.endPoint) } else { polygonPoints.push(connectedRidge.startPoint) } polygonPoints.push(endPoint) } else { polygonPoints = [endPoint, endHip.endPoint] let lastPoint if (restRidgeConnection.startPoint.x === endHip.endPoint.x && restRidgeConnection.startPoint.y === endHip.endPoint.y) { lastPoint = restRidgeConnection.endPoint polygonPoints.push(restRidgeConnection.endPoint) } else { lastPoint = restRidgeConnection.startPoint polygonPoints.push(restRidgeConnection.startPoint) } connectedRidge = ridges.find( (ridge) => (ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) || (ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y), ) if (connectedRidge.startPoint.x === startHip.endPoint.x && connectedRidge.startPoint.y === startHip.endPoint.y) { lastPoint = connectedRidge.startPoint polygonPoints.push(connectedRidge.startPoint) } else { lastPoint = connectedRidge.endPoint polygonPoints.push(connectedRidge.endPoint) } polygonPoints.push(startPoint) } const newPolygon = new QPolygon(polygonPoints, { fontSize: polygon.fontSize, parentId: polygon.id, name: 'roof', selectable: false, stroke: 'black', fill: 'transparent', strokeWidth: 3, }) polygon.canvas.add(newPolygon) }) } const getOneSideLine = (line) => { // left, top 방향의 line은 right, bottom 방향의 line으로 변경한다. const newLine = { ...line } let newX1, newY1, newX2, newY2 if (newLine.direction === 'top') { newX1 = newLine.x2 newY1 = newLine.y2 newX2 = newLine.x1 newY2 = newLine.y1 newLine.x1 = newX1 newLine.y1 = newY1 newLine.x2 = newX2 newLine.y2 = newY2 newLine.direction = 'bottom' newLine.startPoint = { x: newX1, y: newY1 } newLine.endPoint = { x: newX2, y: newY2 } } else if (line.direction === 'left') { newX1 = newLine.x2 newY1 = newLine.y2 newX2 = newLine.x1 newY2 = newLine.y1 newLine.x1 = newX1 newLine.y1 = newY1 newLine.x2 = newX2 newLine.y2 = newY2 newLine.direction = 'right' newLine.startPoint = { x: newX1, y: newY1 } newLine.endPoint = { x: newX2, y: newY2 } } return newLine } function inwardEdgeNormal(vertex1, vertex2) { // Assuming that polygon vertices are in clockwise order const dx = vertex2.x - vertex1.x const dy = vertex2.y - vertex1.y const edgeLength = Math.sqrt(dx * dx + dy * dy) return { x: -dy / edgeLength, y: dx / edgeLength, } } function outwardEdgeNormal(vertex1, vertex2) { var n = inwardEdgeNormal(vertex1, vertex2) return { x: -n.x, y: -n.y, } } function createPolygon(vertices) { const edges = [] let minX = vertices.length > 0 ? vertices[0].x : undefined let minY = vertices.length > 0 ? vertices[0].y : undefined let maxX = minX let maxY = minY for (let i = 0; i < vertices.length; i++) { const vertex1 = vertices[i] const vertex2 = vertices[(i + 1) % vertices.length] const outwardNormal = outwardEdgeNormal(vertex1, vertex2) const inwardNormal = inwardEdgeNormal(vertex1, vertex2) const edge = { vertex1, vertex2, index: i, outwardNormal, inwardNormal, } edges.push(edge) const x = vertices[i].x const y = vertices[i].y minX = Math.min(x, minX) minY = Math.min(y, minY) maxX = Math.max(x, maxX) maxY = Math.max(y, maxY) } return { vertices, edges, minX, minY, maxX, maxY, } } // based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b" function edgesIntersection(edgeA, edgeB) { const den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y) if (den == 0) { return null // lines are parallel or coincident } const ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den const ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den // Edges are not intersecting but the lines defined by them are const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1 return { x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y), isIntersectionOutside, } } function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) { var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x) var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x) if (startAngle < 0) { startAngle += TWO_PI } if (endAngle < 0) { endAngle += TWO_PI } const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments vertices.push(startVertex) for (let i = 1; i < arcSegments; ++i) { const angle = startAngle + angleStep * i const vertex = { x: center.x + Math.cos(angle) * radius, y: center.y + Math.sin(angle) * radius, } vertices.push(vertex) } vertices.push(endVertex) } function createOffsetEdge(edge, dx, dy) { return { vertex1: { x: edge.vertex1.x + dx, y: edge.vertex1.y + dy, }, vertex2: { x: edge.vertex2.x + dx, y: edge.vertex2.y + dy, }, } } function createMarginPolygon(polygon, offset, arcSegments = 0) { const offsetEdges = [] for (let i = 0; i < polygon.edges.length; i++) { const edge = polygon.edges[i] const dx = edge.outwardNormal.x * offset const dy = edge.outwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) } const vertices = [] for (let i = 0; i < offsetEdges.length; i++) { const thisEdge = offsetEdges[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, }) } else { const arcCenter = polygon.edges[i].vertex1 appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false) } } const marginPolygon = createPolygon(vertices) marginPolygon.offsetEdges = offsetEdges return marginPolygon } function createPaddingPolygon(polygon, offset, arcSegments = 0) { const offsetEdges = [] for (let i = 0; i < polygon.edges.length; i++) { const edge = polygon.edges[i] const dx = edge.inwardNormal.x * offset const dy = edge.inwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) } const vertices = [] for (let i = 0; i < offsetEdges.length; i++) { const thisEdge = offsetEdges[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, }) } else { const arcCenter = polygon.edges[i].vertex1 appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true) } } const paddingPolygon = createPolygon(vertices) paddingPolygon.offsetEdges = offsetEdges return paddingPolygon } export default function offsetPolygon(vertices, offset) { const polygon = createPolygon(vertices) const arcSegments = 0 const originPolygon = new QPolygon(vertices, { fontSize: 0 }) if (offset > 0) { let result = createMarginPolygon(polygon, offset, arcSegments).vertices const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point)) if (allPointsOutside) { return createMarginPolygon(polygon, offset, arcSegments).vertices } else { return createPaddingPolygon(polygon, offset, arcSegments).vertices } } else { let result = createPaddingPolygon(polygon, offset, arcSegments).vertices const allPointsInside = result.every((point) => originPolygon.inPolygon(point)) if (allPointsInside) { return createPaddingPolygon(polygon, offset, arcSegments).vertices } else { return createMarginPolygon(polygon, offset, arcSegments).vertices } } } export const splitPolygonWithLines = (polygon) => { const roofs = [] const allLines = [...polygon.innerLines] allLines.forEach((line) => { line.startPoint = { x: line.x1, y: line.y1 } line.endPoint = { x: line.x2, y: line.y2 } }) // allLines에 x1,y1,x2,y2를 비교해서 중복되는 값을 제거한다. allLines.forEach((line, index) => { const startPoint = line.startPoint const endPoint = line.endPoint allLines.forEach((line2, index2) => { if (index !== index2) { if ( (isSamePoint(startPoint, line2.startPoint) && isSamePoint(endPoint, line2.endPoint)) || (isSamePoint(endPoint, line2.startPoint) && isSamePoint(startPoint, line2.endPoint)) ) { allLines.splice(index2, 1) } } }) }) /** * 좌표 테스트용 */ /*allLines.forEach((line) => { const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, { left: line.startPoint.x, top: line.startPoint.y, fontSize: 15, }) polygon.canvas.add(text) polygon.canvas.renderAll() const text2 = new fabric.Text(`(${line.endPoint.x},${line.endPoint.y})`, { left: line.endPoint.x, top: line.endPoint.y, fontSize: 15, }) polygon.canvas.add(text2) polygon.canvas.renderAll() }) polygon.points.forEach((point, index) => { const text = new fabric.Text(`(${point.x},${point.y})`, { left: point.x, top: point.y, fontSize: 15, }) polygon.canvas.add(text) polygon.canvas.renderAll() })*/ /** * 좌표 테스트용 끝 */ polygon.points.forEach((point, index) => { allLines.forEach((line) => { if (line.endPoint.x === point.x && line.endPoint.y === point.y) { const temp = line.startPoint line.startPoint = line.endPoint line.endPoint = temp } }) }) polygon.points.forEach((point, index) => { const routes = [] // 시작점은 시작 hip라인의 출발점 const startPoint = point // 도착점은 마지막 hip라인의 끝나는 점 const endPoint = polygon.points[(index + 1) % polygon.points.length] const startLine = allLines.find((line) => line.startPoint.x === startPoint.x && line.startPoint.y === startPoint.y) const endLine = allLines.find((line) => line.startPoint.x === endPoint.x && line.startPoint.y === endPoint.y) const arrivalPoint = endLine.endPoint routes.push(startLine.startPoint) routes.push(startLine.endPoint) //hip끼리 만나는 경우는 아무것도 안해도됨 if (!isSamePoint(startLine.endPoint, arrivalPoint)) { // polygon line까지 추가 const allLinesCopy = [...allLines, ...polygon.lines] // hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함 let currentPoint = startLine.endPoint let currentLine = startLine let movedLines = [] let subMovedLines = [] while (!isSamePoint(currentPoint, arrivalPoint)) { // startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다. let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint)) connectedLines = connectedLines.filter((line) => line !== currentLine) connectedLines = connectedLines.filter((line) => !subMovedLines.includes(line)) //마지막 선이 endLine의 startPoint와 같은경우 그 전까지 movedLine을 제거한다. const endLineMeetLineCnt = connectedLines.filter((line) => { return isSamePoint(line.endPoint, endLine.startPoint) || isSamePoint(line.startPoint, endLine.startPoint) }).length if (endLineMeetLineCnt !== 0) { movedLines.push(subMovedLines) console.log(movedLines, index) } connectedLines = connectedLines.filter((line) => { return !isSamePoint(line.endPoint, endLine.startPoint) && !isSamePoint(line.startPoint, endLine.startPoint) }) if (connectedLines.length === 0) { return } let tempPoints = [] for (let i = 0; i < connectedLines.length; i++) { if (isSamePoint(connectedLines[i].startPoint, currentPoint)) { tempPoints.push({ point: connectedLines[i].endPoint, index: i, line: connectedLines[i] }) } else { tempPoints.push({ point: connectedLines[i].startPoint, index: i, line: connectedLines[i] }) } } //tempPoints에서 arrivalPoint와 가장 가까운 점을 찾는다. let minDistance = Number.MAX_SAFE_INTEGER let minIndex = 0 tempPoints.forEach((tempPoint, index) => { const distance = Math.sqrt(Math.pow(tempPoint.point.x - arrivalPoint.x, 2) + Math.pow(tempPoint.point.y - arrivalPoint.y, 2)) if (distance < minDistance) { minDistance = distance minIndex = tempPoint.index } }) currentPoint = tempPoints[minIndex].point currentLine = tempPoints[minIndex].line if (currentLine !== startLine) { subMovedLines.push(currentLine) } routes.push(currentPoint) } } routes.push(endLine.startPoint) roofs.push(routes) }) // 중복 제거 roofs.forEach((roofPoint, index) => { const samePointLengthRoofPoints = roofs.filter((roof) => roof.length === roofPoint.length && roof !== roofPoint) samePointLengthRoofPoints.forEach((samePointRoof) => { if (arraysHaveSamePoints(samePointRoof, roofPoint)) { roofs.splice(roofs.indexOf(samePointRoof), 1) } }) }) roofs.forEach((roofPoint, index) => { let defense const direction = getDirectionByPoint(roofPoint[0], roofPoint[roofPoint.length - 1]) switch (direction) { case 'top': defense = 'east' break case 'right': defense = 'south' break case 'bottom': defense = 'west' break case 'left': defense = 'north' break } const roof = new QPolygon(roofPoint, { fontSize: polygon.fontSize, stroke: 'black', fill: 'transparent', strokeWidth: 3, name: POLYGON_TYPE.ROOF, selectable: true, defense: defense, }) polygon.canvas.add(roof) polygon.canvas.renderAll() }) } const isSamePoint = (a, b) => { return a.x === b.x && a.y === b.y } /** * Calculate the angle between two lines. * @param {Object} line1 - The first line defined by two points {x1, y1} and {x2, y2}. * @param {Object} line2 - The second line defined by two points {x1, y1} and {x2, y2}. * @returns {number} - The angle between the two lines in degrees. */ function calculateAngleBetweenLines(line1, line2) { const { x1: x1_1, y1: y1_1, x2: x2_1, y2: y2_1 } = line1 const { x1: x1_2, y1: y1_2, x2: x2_2, y2: y2_2 } = line2 // Calculate direction vectors const vector1 = { x: x2_1 - x1_1, y: y2_1 - y1_1 } const vector2 = { x: x2_2 - x1_2, y: y2_2 - y1_2 } // Calculate dot product and magnitudes const dotProduct = vector1.x * vector2.x + vector1.y * vector2.y const magnitude1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) const magnitude2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) // Calculate the cosine of the angle const cosTheta = dotProduct / (magnitude1 * magnitude2) // Calculate the angle in radians and then convert to degrees const angleInRadians = Math.acos(cosTheta) return (angleInRadians * 180) / Math.PI } export const drawHippedRoof = (polygon, chon) => { const hasNonParallelLines = polygon.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) if (hasNonParallelLines.length > 0) { alert('대각선이 존재합니다.') return } drawRidgeRoof(polygon, chon) drawHips(polygon) connectLinePoint(polygon) } /** * * @param polygon * @param chon */ const drawRidgeRoof = (polygon, chon) => { const walls = polygon.wall.lines // 외벽의 라인 const roofs = polygon.lines // 지붕의 라인 let ridgeRoof = [] roofs.forEach((currentRoof, index) => { let prevRoof, nextRoof, currentWall = walls[index] prevRoof = index === 0 ? walls[walls.length - 1] : walls[index - 1] nextRoof = index === walls.length - 1 ? walls[0] : index === walls.length ? walls[1] : walls[index + 1] if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) { ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length }) } }) // 지붕의 길이가 짧은 순으로 정렬 ridgeRoof.sort((a, b) => a.length - b.length) ridgeRoof.forEach((item) => { if (getMaxRidge(roofs.length) > polygon.ridges.length) { let index = item.index, beforePrevRoof, prevRoof, currentRoof = item.roof, nextRoof, afterNextRoof let startXPoint, startYPoint, endXPoint, endYPoint prevRoof = index === 0 ? roofs[walls.length - 1] : roofs[index - 1] nextRoof = index === roofs.length - 1 ? roofs[0] : index === roofs.length ? roofs[1] : roofs[index + 1] beforePrevRoof = index <= 1 ? roofs[roofs.length - 2 + index] : roofs[index - 2] afterNextRoof = index >= roofs.length - 2 ? roofs[(index + 2) % roofs.length] : roofs[index + 2] const anotherRoof = roofs.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof) let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선 yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선 let ridgeBaseLength = currentRoof.length / 2, // 지붕의 기반 길이 ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이 ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - currentRoof.length // 맞은편 벽까지의 길이 - 지붕의 기반 길이 let acrossRoof = anotherRoof .filter((roof) => { if (roof.x1 === roof.x2) { if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) { return roof } } if (roof.y1 === roof.y2) { if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) { return roof } } }) .reduce((prev, current) => { let hasBetweenRoof = false if (current.x1 === current.x2) { hasBetweenRoof = roofs .filter((roof) => roof !== current && roof !== currentRoof) .some((line) => { let currentY2 = currentRoof.y2 if (yEqualInnerLines.length > 0) { yEqualInnerLines.forEach((line) => { currentY2 = Math.abs(currentRoof.y1 - currentY2) < Math.abs(currentRoof.y1 - line.y1) ? currentY2 : line.y1 }) } const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < currentY2) || (line.y1 > currentY2 && line.y1 < currentRoof.y1) const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1) const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1) const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1) return isY1Between && isY2Between && isX1Between && isX2Between }) } if (current.y1 === current.y2) { hasBetweenRoof = walls .filter((roof) => roof !== current && roof !== currentRoof) .some((line) => { let currentX2 = currentRoof.x2 if (xEqualInnerLines.length > 0) { xEqualInnerLines.forEach((line) => { currentX2 = Math.abs(currentRoof.x1 - currentX2) < Math.abs(currentRoof.x1 - line.x1) ? currentX2 : line.x1 }) } const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < currentX2) || (line.x1 > currentX2 && line.x1 < currentRoof.x1) const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < currentX2) || (line.x2 > currentX2 && line.x2 < currentRoof.x1) const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < current.y1) || (line.y1 > currentRoof.y1 && line.y1 < current.y1) const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < current.y1) || (line.y2 > currentRoof.y1 && line.y2 < current.y1) return isX1Between && isX2Between && isY1Between && isY2Between }) } if (prev !== undefined) { if (currentRoof.x1 === currentRoof.x2) { return Math.abs(currentRoof.y1 - prev.y1) > Math.abs(currentRoof.y1 - current.y1) ? prev : current } if (currentRoof.y1 === currentRoof.y2) { return Math.abs(currentRoof.x1 - prev.x1) > Math.abs(currentRoof.x1 - current.x1) ? prev : current } } else { if (!hasBetweenRoof) { if (currentRoof.x1 === currentRoof.x2) { return Math.sign(currentRoof.y1 - currentRoof.y2) !== Math.sign(current.y1 - current.y2) ? current : undefined } if (currentRoof.y1 === currentRoof.y2) { return Math.sign(currentRoof.x1 - currentRoof.x2) !== Math.sign(current.x1 - current.x2) ? current : undefined } return undefined } else { return undefined } } }, undefined) if (acrossRoof !== undefined) { if (currentRoof.x1 === currentRoof.x2) { if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) { ridgeAcrossLength = Math.abs(currentRoof.x1 - acrossRoof.x1) - currentRoof.length } } if (currentRoof.y1 === currentRoof.y2) { if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) { ridgeAcrossLength = Math.abs(currentRoof.y1 - acrossRoof.y1) - currentRoof.length } } } if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) { let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) if (currentRoof.x1 === currentRoof.x2) { startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * ridgeBaseLength startYPoint = currentRoof.y1 + (currentRoof.direction === 'top' ? -1 : 1) * ridgeBaseLength endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength endYPoint = startYPoint let adjustY if (currentRoof.direction === 'top') { if (afterNextRoof.direction === 'bottom' && beforePrevRoof.direction === 'bottom') { adjustY = Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1 } else if (afterNextRoof.direction === 'bottom' && afterNextRoof.y2 > currentRoof.y2 && afterNextRoof.y2 < currentRoof.y1) { adjustY = afterNextRoof.y2 } else if (beforePrevRoof.direction === 'bottom' && beforePrevRoof.y1 > currentRoof.y2 && beforePrevRoof.y1 < currentRoof.y1) { adjustY = beforePrevRoof.y1 } if (adjustY) { startYPoint = currentRoof.y1 - Math.abs(currentRoof.y1 - adjustY) / 2 endYPoint = startYPoint } } if (currentRoof.direction === 'bottom') { if (afterNextRoof.direction === 'top' && beforePrevRoof.direction === 'top') { adjustY = Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1 } else if (afterNextRoof.direction === 'top' && afterNextRoof.y2 < currentRoof.y2 && afterNextRoof.y2 > currentRoof.y1) { adjustY = afterNextRoof.y2 } else if (beforePrevRoof.direction === 'top' && beforePrevRoof.y1 < currentRoof.y2 && beforePrevRoof.y1 > currentRoof.y1) { adjustY = beforePrevRoof.y1 } if (adjustY) { startYPoint = currentRoof.y1 + Math.abs(currentRoof.y1 - adjustY) / 2 endYPoint = startYPoint } } if (yEqualInnerLines.length > 0) { yEqualInnerLines.reduce((prev, current) => { if (prev !== undefined) { return Math.abs(currentRoof.y1 - prev.y1) < Math.abs(currentRoof.y1 - current.y1) ? prev : current } else { return current } }, undefined) startYPoint = Math.abs(currentRoof.y1 - startYPoint) * 2 <= Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) ? startYPoint : Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) endYPoint = startYPoint ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.y1 - startYPoint) * 2 if ( //yEqualInnerLines 이 다음 벽보다 안쪽에 있을때 Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) <= Math.abs(currentRoof.y1 - nextRoof.y1) && Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) >= Math.abs(currentRoof.x1 - nextRoof.x2) ) { ridgeMaxLength = Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) } ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * Math.abs(currentRoof.y1 - startYPoint) endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength } } if (currentRoof.y1 === currentRoof.y2) { startXPoint = currentRoof.x1 + (currentRoof.direction === 'left' ? -1 : 1) * ridgeBaseLength startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeBaseLength endXPoint = startXPoint endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength let adjustX if (currentRoof.direction === 'right') { if (afterNextRoof.direction === 'left' && beforePrevRoof.direction === 'left') { adjustX = Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1 } else if (afterNextRoof.direction === 'left' && afterNextRoof.x2 < currentRoof.x2 && afterNextRoof.x2 > currentRoof.x1) { adjustX = afterNextRoof.x2 } else if (beforePrevRoof.direction === 'left' && beforePrevRoof.x1 < currentRoof.x2 && beforePrevRoof.x1 > currentRoof.x1) { adjustX = beforePrevRoof.x1 } if (adjustX) { startXPoint = currentRoof.x1 + Math.abs(currentRoof.x1 - adjustX) / 2 endXPoint = startXPoint } } if (currentRoof.direction === 'left') { if (afterNextRoof.direction === 'right' && beforePrevRoof.direction === 'right') { adjustX = Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1 } else if (afterNextRoof.direction === 'right' && afterNextRoof.x2 > currentRoof.x2 && afterNextRoof.x2 < currentRoof.x1) { adjustX = afterNextRoof.x2 } else if (beforePrevRoof.direction === 'right' && beforePrevRoof.x1 > currentRoof.x2 && beforePrevRoof.x1 < currentRoof.x1) { adjustX = beforePrevRoof.x1 } if (adjustX) { startXPoint = currentRoof.x1 - Math.abs(currentRoof.x1 - adjustX) / 2 endXPoint = startXPoint } } if (xEqualInnerLines.length > 0) { xEqualInnerLines.reduce((prev, current) => { if (prev !== undefined) { return Math.abs(currentRoof.x1 - prev.x1) < Math.abs(currentRoof.x1 - current.x1) ? prev : current } else { return current } }, undefined) startXPoint = Math.abs(currentRoof.x1 - startXPoint) * 2 <= Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) ? startXPoint : Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) endXPoint = startXPoint ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.x1 - startXPoint) * 2 if ( //xEqualInnerLines 이 다음 벽보다 안쪽에 있을때 Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) <= Math.abs(currentRoof.x1 - nextRoof.x1) && Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) >= Math.abs(currentRoof.y1 - nextRoof.y2) ) { ridgeMaxLength = Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) } ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength) startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * Math.abs(currentRoof.x1 - startXPoint) endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength } } } const currentWall = walls[index] if (currentWall.attributes.type === 'gable') { if (currentRoof.x1 === currentRoof.x2) { startXPoint = currentRoof.x1 } if (currentRoof.y1 === currentRoof.y2) { startYPoint = currentRoof.y1 } } // 마루 그리기 if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) { const ridge = new QLine( [Math.min(startXPoint, endXPoint), Math.min(startYPoint, endYPoint), Math.max(startXPoint, endXPoint), Math.max(startYPoint, endYPoint)], { fontSize: polygon.fontSize, stroke: 'blue', strokeWidth: 1, name: 'ridgeLine', }, ) polygon.canvas.add(ridge) polygon.ridges.push(ridge) polygon.innerLines.push(ridge) } } }) //겹쳐지는 마루는 하나로 합침 polygon.ridges.forEach((ridge, index) => { polygon.ridges .filter((ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2)) .forEach((ridge2) => { let overlap = segmentsOverlap(ridge, ridge2) if (overlap) { let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = new QLine([x1, y1, x2, y2], { fontSize: polygon.fontSize, stroke: 'blue', strokeWidth: 1, }) polygon.canvas.remove(ridge) polygon.canvas.remove(ridge2) polygon.ridges = polygon.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) polygon.ridges = polygon.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2)) polygon.innerLines = polygon.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) polygon.innerLines = polygon.innerLines.filter( (r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2), ) polygon.canvas.add(newRidge) polygon.ridges.push(newRidge) polygon.innerLines.push(newRidge) } }) }) } /** * line 이 세 라인 사이에 존재하는지 확인한다. * @param prevLine * @param currentLine * @param nextLine * @param line */ const isInnerLine = (prevLine, currentLine, nextLine, line) => { let inside = false let minX = Math.min(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) let maxX = Math.max(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) let minY = Math.min(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) let maxY = Math.max(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) if (minX < line.x1 && line.x1 < maxX && minY < line.y1 && line.y1 < maxY && minX < line.x2 && line.x2 < maxX && minY < line.y2 && line.y2 < maxY) { inside = true } return inside } /** * 두 선분이 겹치는지 확인 * @param line1 * @param line2 * @returns {boolean} */ const segmentsOverlap = (line1, line2) => { if (line1.y1 === line1.y2 && line2.y1 === line2.y2 && line1.y1 === line2.y1) { if ((line1.x1 <= line2.x1 && line1.x2 >= line2.x1) || (line1.x1 <= line2.x2 && line1.x2 >= line2.x2)) { return true } } if (line1.x1 === line1.x2 && line2.x1 === line2.x2 && line1.x1 === line2.x1) { if ((line1.y1 <= line2.y1 && line1.y2 >= line2.y1) || (line1.y1 <= line2.y2 && line1.y2 >= line2.y2)) { return true } } return false } const drawHips = (polygon) => { /* 마루에서 시작되는 hip을 먼저 그립니다. */ polygon.ridges.forEach((ridge) => { let leftTop, rightTop, leftBottom, rightBottom if (ridge.y1 === ridge.y2) { //왼쪽 좌표 기준 225, 315도 방향 라인확인 leftTop = polygon.lines .filter((line) => line.x1 < ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(ridge.x1 - current.x1) < Math.min(ridge.x1 - prev.x1) ? current : prev } }, undefined) leftBottom = polygon.lines .filter((line) => line.x1 < ridge.x1 && line.y1 > ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(ridge.x1 - current.x1) < Math.min(ridge.x1 - prev.x1) ? current : prev } }, undefined) //오른쪽 좌표 기준 45, 135도 방향 라인확인 rightTop = polygon.lines .filter((line) => line.x1 > ridge.x2 && line.y1 < ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(current.x1 - ridge.x2) < Math.min(prev.x1 - ridge.x2) ? current : prev } }, undefined) rightBottom = polygon.lines .filter((line) => line.x1 > ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(current.x1 - ridge.x2) < Math.min(prev.x1 - ridge.x2) ? current : prev } }, undefined) if (leftTop !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 < ridge.x1 && r.y1 < ridge.y1 && (r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1) ) { isRidgePointOnLine = true } if ( r.x2 < ridge.x1 && r.y2 < ridge.y1 && (r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([leftTop.x1, leftTop.y1, ridge.x1, ridge.y1], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (leftBottom !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 < ridge.x1 && r.y1 > ridge.y1 && (r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1) ) { isRidgePointOnLine = true } if ( r.x2 < ridge.x1 && r.y2 > ridge.y1 && (r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([leftBottom.x1, leftBottom.y1, ridge.x1, ridge.y1], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (rightTop !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 > ridge.x2 && r.y1 < ridge.y2 && (r.y1 - rightTop.y1) * (ridge.x2 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y2 - rightTop.y1) ) { isRidgePointOnLine = true } if ( r.x2 > ridge.x2 && r.y2 < ridge.y2 && (r.y2 - rightTop.y1) * (ridge.x2 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y2 - rightTop.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([rightTop.x1, rightTop.y1, ridge.x2, ridge.y2], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (rightBottom !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 > ridge.x2 && r.y1 > ridge.y2 && (r.y1 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x1 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) ) { isRidgePointOnLine = true } if ( r.x2 > ridge.x2 && r.y2 > ridge.y2 && (r.y2 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x2 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([rightBottom.x1, rightBottom.y1, ridge.x2, ridge.y2], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } } if (ridge.x1 === ridge.x2) { //위쪽 좌표 기준 45, 315도 방향 라인확인 leftTop = polygon.lines .filter((line) => line.x1 < ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(ridge.y1 - current.y1) < Math.min(ridge.y1 - prev.y1) ? current : prev } }, undefined) rightTop = polygon.lines .filter((line) => line.x1 > ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(ridge.y1 - current.y1) < Math.min(ridge.y1 - prev.y1) ? current : prev } }, undefined) //아래쪽 좌표 기준 135, 225도 방향 라인확인 leftBottom = polygon.lines .filter((line) => line.x1 < ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(current.y1 - ridge.y2) < Math.min(prev.y1 - ridge.y2) ? current : prev } }, undefined) rightBottom = polygon.lines .filter((line) => line.x1 > ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) .reduce((prev, current) => { if (prev === undefined) { return current } else { return Math.min(current.y1 - ridge.y2) < Math.min(prev.y1 - ridge.y2) ? current : prev } }, undefined) if (leftTop !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if (r.x1 < ridge.x1 && r.y1 < ridge.y1) { if ((r.y1 - leftTop.y1) * (ridge.x1 - leftTop.x1) === (r.x1 - leftTop.x1) * (ridge.y1 - leftTop.y1)) { isRidgePointOnLine = true } } if (r.x2 < ridge.x1 && r.y2 < ridge.y1) { if ((r.y2 - leftTop.y1) * (ridge.x1 - leftTop.x1) === (r.x2 - leftTop.x1) * (ridge.y1 - leftTop.y1)) { isRidgePointOnLine = true } } }) if (!isRidgePointOnLine) { const hip = new QLine([leftTop.x1, leftTop.y1, ridge.x1, ridge.y1], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (rightTop !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if (r.x1 > ridge.x1 && r.y1 < ridge.y1) { if ((r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1)) { isRidgePointOnLine = true } } if (r.x2 > ridge.x1 && r.y2 < ridge.y1) { if ((r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1)) { isRidgePointOnLine = true } } }) if (!isRidgePointOnLine) { const hip = new QLine([rightTop.x1, rightTop.y1, ridge.x1, ridge.y1], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (leftBottom !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 < ridge.x2 && r.y1 > ridge.y2 && (r.y1 - leftBottom.y1) * (ridge.x2 - leftBottom.x1) === (r.x1 - leftBottom.x1) * (ridge.y2 - leftBottom.y1) ) { isRidgePointOnLine = true } if ( r.x2 < ridge.x2 && r.y2 > ridge.y2 && (r.y2 - leftBottom.y1) * (ridge.x2 - leftBottom.x1) === (r.x2 - leftBottom.x1) * (ridge.y2 - leftBottom.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([leftBottom.x1, leftBottom.y1, ridge.x2, ridge.y2], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } if (rightBottom !== undefined) { let isRidgePointOnLine = false polygon.ridges.forEach((r) => { if ( r.x1 < ridge.x2 && r.y1 > ridge.y2 && (r.y1 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x1 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) ) { isRidgePointOnLine = true } if ( r.x2 < ridge.x2 && r.y2 > ridge.y2 && (r.y2 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x2 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) ) { isRidgePointOnLine = true } }) if (!isRidgePointOnLine) { const hip = new QLine([rightBottom.x1, rightBottom.y1, ridge.x2, ridge.y2], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } } }) // 가장 가까운 마루를 확인하여 그릴 수 있는 라인이 존재하면 먼저 그린다. let prevLine, currentLine, nextLine polygon.lines.forEach((value, index) => { if (index === 0) { prevLine = polygon.lines[polygon.lines.length - 1] } else { prevLine = polygon.lines[index - 1] } currentLine = polygon.lines[index] if (index === polygon.lines.length - 1) { nextLine = polygon.lines[0] } else if (index === polygon.lines.length) { nextLine = polygon.lines[1] } else { nextLine = polygon.lines[index + 1] } if (!isAlreadyHip(polygon, currentLine)) { let dVector = getDirectionForDegree(prevLine, currentLine) let nearRidge switch (dVector) { case 45: nearRidge = polygon.ridges .filter( (ridge) => ((currentLine.x1 < ridge.x1 && currentLine.y1 > ridge.y1) || (currentLine.x1 < ridge.x2 && currentLine.y1 > ridge.y2)) && (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), ) .reduce((prev, current) => { if (prev !== undefined) { if ( currentLine.x1 < current.x1 && currentLine.y1 > current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x1, y: current.y1, } : prev } else if ( currentLine.x1 < current.x2 && currentLine.y1 > current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return Math.min(Math.abs(current.x2 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x2, y: current.y2, } : prev } else { return prev } } else { if ( currentLine.x1 < current.x1 && currentLine.y1 > current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return { x: current.x1, y: current.y1 } } else if ( currentLine.x1 < current.x2 && currentLine.y1 > current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return { x: current.x2, y: current.y2 } } else { return undefined } } }, undefined) break case 135: nearRidge = polygon.ridges .filter( (ridge) => ((currentLine.x1 < ridge.x1 && currentLine.y1 < ridge.y1) || (currentLine.x1 < ridge.x2 && currentLine.y1 < ridge.y2)) && (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), ) .reduce((prev, current) => { if (prev !== undefined) { if ( currentLine.x1 < current.x1 && currentLine.y1 < current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x1, y: current.y1, } : prev } else if ( currentLine.x1 < current.x2 && currentLine.y1 < current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x2, y: current.y2, } : prev } else { return prev } } else { if ( currentLine.x1 < current.x1 && currentLine.y1 < current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return { x: current.x1, y: current.y1 } } else if ( currentLine.x1 < current.x2 && currentLine.y1 < current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return { x: current.x2, y: current.y2 } } else { return undefined } } }, undefined) break case 225: nearRidge = polygon.ridges .filter( (ridge) => ((currentLine.x1 > ridge.x1 && currentLine.y1 < ridge.y1) || (currentLine.x1 > ridge.x2 && currentLine.y1 < ridge.y2)) && (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), ) .reduce((prev, current) => { if (prev !== undefined) { if ( currentLine.x1 > current.x1 && currentLine.y1 < current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x1, y: current.y1, } : prev } else if ( currentLine.x1 > current.x2 && currentLine.y1 < current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x2, y: current.y2, } : prev } else { return prev } } else { if ( currentLine.x1 > current.x1 && currentLine.y1 < current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return { x: current.x1, y: current.y1 } } else if ( currentLine.x1 > current.x2 && currentLine.y1 < current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return { x: current.x2, y: current.y2 } } else { return undefined } } }, undefined) break case 315: nearRidge = polygon.ridges .filter( (ridge) => ((currentLine.x1 > ridge.x1 && currentLine.y1 > ridge.y1) || (currentLine.x1 > ridge.x2 && currentLine.y1 > ridge.y2)) && (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), ) .reduce((prev, current) => { if (prev !== undefined) { if ( currentLine.x1 > current.x1 && currentLine.y1 > current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x1, y: current.y1, } : prev } else if ( currentLine.x1 > current.x2 && currentLine.y1 > current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) ? { x: current.x2, y: current.y2, } : prev } else { return prev } } else { if ( currentLine.x1 > current.x1 && currentLine.y1 > current.y1 && Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) ) { return { x: current.x1, y: current.y1 } } else if ( currentLine.x1 > current.x2 && currentLine.y1 > current.y2 && Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) ) { return { x: current.x2, y: current.y2 } } else { return undefined } } }, undefined) break } if (nearRidge !== undefined) { let endXPoint, endYPoint let minX, maxX, minY, maxY switch (dVector) { case 45: endXPoint = nearRidge.x endYPoint = nearRidge.y minX = Math.min(currentLine.x1, nearRidge.x) minY = Math.min(currentLine.y1, nearRidge.y) maxX = Math.max(currentLine.x1, nearRidge.x) maxY = Math.max(currentLine.y1, nearRidge.y) break case 135: endXPoint = nearRidge.x endYPoint = nearRidge.y minX = Math.min(currentLine.x1, nearRidge.x) minY = Math.min(currentLine.y1, nearRidge.y) maxX = Math.max(currentLine.x1, nearRidge.x) maxY = Math.max(currentLine.y1, nearRidge.y) break case 225: endXPoint = nearRidge.x endYPoint = nearRidge.y minX = Math.min(currentLine.x1, nearRidge.x) minY = Math.min(currentLine.y1, nearRidge.y) maxX = Math.max(currentLine.x1, nearRidge.x) maxY = Math.max(currentLine.y1, nearRidge.y) break case 315: endXPoint = nearRidge.x endYPoint = nearRidge.y minX = Math.min(currentLine.x1, nearRidge.x) minY = Math.min(currentLine.y1, nearRidge.y) maxX = Math.max(currentLine.x1, nearRidge.x) maxY = Math.max(currentLine.y1, nearRidge.y) break } let lineCoordinate = [ { x: minX, y: minY }, { x: minX, y: maxY }, { x: maxX, y: maxY }, { x: maxX, y: minY }, ] let innerPoint = polygon.lines.filter((line) => { if (getPointInPolygon(lineCoordinate, { x: line.x1, y: line.y1 })) { return line } }) if (innerPoint <= 0) { const hip = new QLine([currentLine.x1, currentLine.y1, endXPoint, endYPoint], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } } } }) // 마루와 연결되지 않은 hip을 그린다. /*polygon.lines.forEach((line, index) => { if (!isAlreadyHip(polygon, line)) { console.log(' 확인 : ', line) let prevLine, currentLine, nextLine if (index === 0) { prevLine = polygon.lines[polygon.lines.length - 1] } else { prevLine = polygon.lines[index - 1] } currentLine = polygon.lines[index] if (index === polygon.lines.length - 1) { nextLine = polygon.lines[0] } else if (index === polygon.lines.length) { nextLine = polygon.lines[1] } else { nextLine = polygon.lines[index + 1] } let endXPoint, endYPoint let dVector = getDirectionForDegree(prevLine, currentLine) let minX = Math.min(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) let maxX = Math.max(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) let minY = Math.min(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) let maxY = Math.max(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) let lineCoordinate = [ { x: minX, y: minY }, { x: minX, y: maxY }, { x: maxX, y: maxY }, { x: maxX, y: minY }, ] let acrossLine = getAcrossLine(polygon, currentLine, dVector) let hypotenuse, adjacent console.log(acrossLine) if (getLineDirection(prevLine) === getLineDirection(nextLine)) { hypotenuse = Math.round(getRoofHypotenuse(Math.abs(currentLine.x1 - acrossLine.x1) / 2)) } else { hypotenuse = Math.min( Math.round(getRoofHypotenuse(currentLine.length / 2)), Math.round(getRoofHypotenuse(Math.abs(currentLine.x1 - acrossLine.x1) / 2)), ) } adjacent = getAdjacent(hypotenuse) switch (dVector) { case 45: endXPoint = currentLine.x1 + adjacent endYPoint = currentLine.y1 - adjacent break case 135: endXPoint = currentLine.x1 + adjacent endYPoint = currentLine.y1 + adjacent break case 225: endXPoint = currentLine.x1 - adjacent endYPoint = currentLine.y1 + adjacent break case 315: endXPoint = currentLine.x1 - adjacent endYPoint = currentLine.y1 - adjacent break } const hip = new QLine([currentLine.x1, currentLine.y1, endXPoint, endYPoint], { fontSize: polygon.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', }) polygon.canvas.add(hip) polygon.hips.push(hip) polygon.innerLines.push(hip) } })*/ } const getPointInPolygon = (polygon, point, isInclude = false) => { let inside = false let minX = Math.min(polygon[0].x, polygon[1].x, polygon[2].x, polygon[3].x), maxX = Math.max(polygon[0].x, polygon[1].x, polygon[2].x, polygon[3].x), minY = Math.min(polygon[0].y, polygon[1].y, polygon[2].y, polygon[3].y), maxY = Math.max(polygon[0].y, polygon[1].y, polygon[2].y, polygon[3].y) if (!isInclude && minX < point.x && point.x < maxX && minY < point.y && point.y < maxY) { inside = true } if (isInclude && minX <= point.x && point.x <= maxX && minY <= point.y && point.y <= maxY) { inside = true } return inside } /** * 라인과 마주하는 다른 라인과의 가장 가까운 거리를 구한다. * @param polygon * @param currentLine 현재 라인 * @param dVector 현재 라인의 방향 * @returns {*[]|null} */ const getAcrossLine = (polygon, currentLine, dVector) => { let acrossLine console.log('dVector : ', dVector) switch (dVector) { case 45: acrossLine = polygon.lines .filter((line) => line.x1 > currentLine.x1 && line.y1 <= currentLine.y1) .reduce((prev, current) => { if (prev.length > 0) { return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev } else { return current } }, []) break case 135: acrossLine = polygon.lines .filter((line) => line.x1 > currentLine.x1 && line.y1 >= currentLine.y1) .reduce((prev, current) => { if (prev.length > 0) { return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev } else { return current } }, []) break case 225: acrossLine = polygon.lines .filter((line) => line.x1 < currentLine.x1 && line.y1 >= currentLine.y1) .reduce((prev, current) => { if (prev.length > 0) { return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev } else { return current } }, []) break case 315: acrossLine = polygon.lines .filter((line) => line.x1 < currentLine.x1 && line.y1 <= currentLine.y1) .reduce((prev, current) => { if (prev.length > 0) { return Math.abs(currentLine.x1 - current.x1) < Math.abs(currentLine.x1 - prev.x1) ? current : prev } else { return current } }, []) break } return acrossLine } /* 추녀마루(hip) 중복방지를 위해 마루와 함께 그려진 추녀마루를 확인한다 */ const isAlreadyHip = (polygon, line) => { let isAlreadyHip = false polygon.hips.forEach((hip) => { if (line.x1 === hip.x1 && line.y1 === hip.y1) { isAlreadyHip = true } }) return isAlreadyHip } /* 3개 이상 이어지지 않은 라인 포인트 계산 모임지붕에서 point는 3개 이상의 라인과 접해야 함. */ const connectLinePoint = (polygon) => { // 연결되지 않은 모든 라인의 포인트를 구한다. let missedPoints = [] //마루 polygon.ridges.forEach((ridge) => { if (ridge.x1 === ridge.x2) { if ( polygon.lines .filter((roof) => roof.y1 === roof.y2) .filter((roof) => roof.y1 === ridge.y1 || roof.y1 === ridge.y2 || roof.y2 === ridge.y1 || roof.y2 === ridge.y2).length > 0 ) { return } } if (ridge.y1 === ridge.y2) { if ( polygon.lines .filter((roof) => roof.x1 === roof.x2) .filter((roof) => roof.x1 === ridge.x1 || roof.x1 === ridge.x2 || roof.x2 === ridge.x1 || roof.x2 === ridge.x2).length > 0 ) { return } } if (polygon.hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1).length < 2) { missedPoints.push({ x: ridge.x1, y: ridge.y1 }) } if (polygon.hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2).length < 2) { missedPoints.push({ x: ridge.x2, y: ridge.y2 }) } }) //추녀마루 polygon.hips.forEach((hip) => { let count = 0 count += polygon.ridges.filter((ridge) => (ridge.x1 === hip.x2 && ridge.y1 === hip.y2) || (ridge.x2 === hip.x2 && ridge.y2 === hip.y2)).length count += polygon.hips.filter((hip2) => (hip2.x1 === hip.x2 && hip2.y1 === hip.y2) || (hip2.x2 === hip.x2 && hip2.y2 === hip.y2)).length if (count < 3) { missedPoints.push({ x: hip.x2, y: hip.y2 }) } }) let missedLine = [] //중복포인트제거 missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line)) missedPoints.forEach((p1) => { let p2 = missedPoints .filter((p) => p.x !== p1.x && p.y !== p1.y) .reduce((prev, current) => { if (prev !== undefined) { return Math.sqrt(Math.pow(Math.abs(current.x - p1.x), 2) + Math.pow(Math.abs(current.y - p1.y), 2)) < Math.sqrt(Math.pow(Math.abs(prev.x - p1.x), 2) + Math.pow(Math.abs(prev.y - p1.y), 2)) ? current : prev } else { return current } }, undefined) if (p2 !== undefined) { if (p1.x < p2.x && p1.y < p2.y) { missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }) } if (p1.x > p2.x && p1.y < p2.y) { missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y }) } if (p1.x > p2.x && p1.y > p2.y) { missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y }) } if (p1.x < p2.x && p1.y > p2.y) { missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }) } } }) //중복라인제거 missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line)) missedLine.forEach((p, index) => { const line = new QLine([p.x1, p.y1, p.x2, p.y2], { fontSize: polygon.fontSize, stroke: 'purple', strokeWidth: 1, }) polygon.canvas.add(line) polygon.innerLines.push(line) }) missedPoints = [] missedLine = [] polygon.innerLines.forEach((line, index) => { if ( polygon.innerLines.filter( (innerLine) => (line.x2 === innerLine.x1 && line.y2 === innerLine.y1) || (line.x2 === innerLine.x2 && line.y2 === innerLine.y2), ).length < 3 ) { missedPoints.push({ x: line.x2, y: line.y2 }) } }) missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line)) missedPoints.forEach((p1) => { let p2 = missedPoints .filter((p) => !(p.x === p1.x && p.y === p1.y)) .reduce((prev, current) => { if (prev !== undefined) { return Math.abs(current.x - p1.x) + Math.abs(current.y - p1.y) < Math.abs(prev.x - p1.x) + Math.abs(prev.y - p1.y) ? current : prev } else { return current } }, undefined) if (p2 !== undefined) { if (p1.x === p2.x && p1.y < p2.y) { missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }) } if (p1.x === p2.x && p1.y > p2.y) { missedLine.push({ x1: p1.x, y1: p2.y, x2: p2.x, y2: p1.y }) } if (p1.x < p2.x && p1.y === p2.y) { missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }) } if (p1.x > p2.x && p1.y === p2.y) { missedLine.push({ x1: p2.x, y1: p1.y, x2: p1.x, y2: p2.y }) } } }) //중복라인제거 missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line)) missedLine.forEach((p, index) => { const line = new QLine([p.x1, p.y1, p.x2, p.y2], { fontSize: polygon.fontSize, stroke: 'purple', strokeWidth: 1, }) polygon.canvas.add(line) polygon.innerLines.push(line) }) //마지막으로 연결되지 않고 떨어져있는 마루를 확인한다. let missedRidge = [] polygon.ridges.forEach((ridge) => { let lineCheck1 = polygon.innerLines.filter((line) => { if ( !(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) && ((line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1)) ) { return line } }) let lineCheck2 = polygon.innerLines.filter((line) => { if ( !(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) && ((line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) ) { return line } }) if (lineCheck1.length === 0 || lineCheck2.length === 0) { missedRidge.push(ridge) } }) missedRidge.forEach((ridge) => { let missedRidge2 = missedRidge.filter( (ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2), ) missedRidge2.forEach((ridge2) => { let overlap = false if (ridge.x1 === ridge.x2 && ridge2.x1 === ridge2.x2 && ridge2.x1 === ridge.x1) { overlap = true } if (ridge.y1 === ridge.y2 && ridge2.y1 === ridge2.y2 && ridge2.y1 === ridge.y1) { overlap = true } if (overlap) { let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = new QLine([x1, y1, x2, y2], { fontSize: polygon.fontSize, stroke: 'blue', strokeWidth: 1, name: 'ridgeLine', }) if (polygon.ridges.filter((r) => newRidge.x1 === r.x1 && newRidge.y1 === r.y1 && newRidge.x2 === r.x2 && newRidge.y2 === r.y2).length === 0) { polygon.canvas.remove(ridge) polygon.canvas.remove(ridge2) polygon.ridges = polygon.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) polygon.ridges = polygon.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2)) polygon.innerLines = polygon.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) polygon.innerLines = polygon.innerLines.filter( (r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2), ) polygon.canvas.add(newRidge) polygon.ridges.push(newRidge) polygon.innerLines.push(newRidge) } } }) }) console.log('polygon : ', polygon) } /* 최대 생성 마루 갯수 */ const getMaxRidge = (length) => { return (length - 4) / 2 + 1 } /* 두 라인의 사잇각 계산 */ const getDirectionForDegree = (line1, line2) => { let degree = getLineDirection(line1) + getLineDirection(line2) let vector switch (degree) { case 'rb': vector = 45 break case 'br': vector = 45 break case 'lb': vector = 135 break case 'bl': vector = 135 break case 'lt': vector = 225 break case 'tl': vector = 225 break case 'rt': vector = 315 break case 'tr': vector = 315 break } return vector } /* 현재 라인의 방향을 계산 */ const getLineDirection = (line) => { let x1, x2, y1, y2, xp, yp x1 = Math.round(line.x1) x2 = Math.round(line.x2) y1 = Math.round(line.y1) y2 = Math.round(line.y2) xp = x1 - x2 yp = y1 - y2 if (xp === 0) { if (yp < 0) { return 'b' } else { return 't' } } if (yp === 0) { if (xp < 0) { return 'r' } else { return 'l' } } } export const changeAllHipAndGableRoof = (polygon, offset, canvas) => { const roof = polygon.filter((p) => p.name === 'roofBase')[0] // 지붕 const roofLines = roof.lines // 지붕의 라인 const ridges = roof.ridges // 마루의 라인 const hips = roof.hips // 추녀마루의 라인 console.log('roofLines : ', roofLines) ridges.forEach((ridge) => { let ridgeHip1 = hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1) let ridgeHip2 = hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2) let gableLines = [] if (ridgeHip1.length > 1) { let x1 = ridgeHip1[0].x1, y1 = ridgeHip1[0].y1, x2 = ridgeHip1[1].x1, y2 = ridgeHip1[1].y1 roofLines.filter((roofLine) => { if ( (roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) || (roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1) ) { gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip1[0], ridgeHip1[1], offset, canvas)) } }) } if (ridgeHip2.length > 1) { let x1 = ridgeHip2[0].x1, y1 = ridgeHip2[0].y1, x2 = ridgeHip2[1].x1, y2 = ridgeHip2[1].y1 roofLines.filter((roofLine) => { if ( (roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) || (roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1) ) { gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip2[0], ridgeHip2[1], offset, canvas)) } }) } gableLines.forEach((gableLine) => { roof.innerLines.push(gableLine) }) }) // splitPolygonWithLines(roof) } /** * 모임지붕 -> 팔작지붕 변경 * @param roof * @param ridge * @param hip1 * @param hip2 * @param offset * @param canvas * @returns {*} */ const setHipAndGableRoof = (roof, ridge, hip1, hip2, offset, canvas) => { let x1 = hip1.x1, y1 = hip1.y1 let gableLine, diffOffset if (ridge.direction === 'top') { if (ridge.y1 > y1 && ridge.y2 > y1) { offset = Math.abs(ridge.y1 - y1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 + offset, }) gableLine = new QLine([ridge.x2 - offset, ridge.y2, ridge.x2 + offset, ridge.y2], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x1 < hip1.x2 ? hip1.x2 - offset : hip1.x2 + offset, y2: hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x1 < hip2.x2 ? hip2.x2 - offset : hip2.x2 + offset, y2: hip2.y2 + offset, }) } if (ridge.y1 < y1 && ridge.y2 < y1) { offset = Math.abs(ridge.y2 - y1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1 - offset, x2: ridge.x2, y2: ridge.y2, }) gableLine = new QLine([ridge.x1 - offset, ridge.y1, ridge.x1 + offset, ridge.y1], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x1 < hip1.x2 ? hip1.x2 - offset : hip1.x2 + offset, y2: hip1.y2 - offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x1 < hip2.x2 ? hip2.x2 - offset : hip2.x2 + offset, y2: hip2.y2 - offset, }) } } if (ridge.direction === 'bottom') { if (ridge.y1 > y1 && ridge.y2 > y1) { offset = Math.abs(ridge.y1 - y1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1 - offset, x2: ridge.x2, y2: ridge.y2, }) gableLine = new QLine([ridge.x1 - offset, ridge.y1, ridge.x1 + offset, ridge.y1], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x1 < hip1.x2 ? hip1.x2 - offset : hip1.x2 + offset, y2: hip1.y2 - offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x1 < hip2.x2 ? hip2.x2 - offset : hip2.x2 + offset, y2: hip2.y2 - offset, }) } if (ridge.y1 < y1 && ridge.y2 < y1) { offset = Math.abs(ridge.y2 - y1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 + offset, }) gableLine = new QLine([ridge.x2 - offset, ridge.y2, ridge.x2 + offset, ridge.y2], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x1 < hip1.x2 ? hip1.x2 - offset : hip1.x2 + offset, y2: hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x1 < hip2.x2 ? hip2.x2 - offset : hip2.x2 + offset, y2: hip2.y2 + offset, }) } } if (ridge.direction === 'right') { if (ridge.x1 > x1 && ridge.x2 > x1) { offset = Math.abs(ridge.x1 - x1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1 - offset, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2, }) gableLine = new QLine([ridge.x1, ridge.y1 - offset, ridge.x1, ridge.y1 + offset], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x2 - offset, y2: hip1.y1 < hip1.y2 ? hip1.y2 - offset : hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x2 - offset, y2: hip2.y1 < hip2.y2 ? hip2.y2 - offset : hip2.y2 + offset, }) } if (ridge.x1 < x1 && ridge.x2 < x1) { offset = Math.abs(ridge.x2 - x1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2 + offset, y2: ridge.y2, }) gableLine = new QLine([ridge.x2, ridge.y2 - offset, ridge.x2, ridge.y2 + offset], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x2 + offset, y2: hip1.y1 < hip1.y2 ? hip1.y2 - offset : hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x2 + offset, y2: hip2.y1 < hip2.y2 ? hip2.y2 - offset : hip2.y2 + offset, }) } } if (ridge.direction === 'left') { if (ridge.x1 > x1 && ridge.x2 > x1) { offset = Math.abs(ridge.x1 - x1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2 + offset, y2: ridge.y2, }) gableLine = new QLine([ridge.x2, ridge.y2 - offset, ridge.x2, ridge.y2 + offset], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x2 + offset, y2: hip1.y1 < hip1.y2 ? hip1.y2 - offset : hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x2 + offset, y2: hip2.y1 < hip2.y2 ? hip2.y2 - offset : hip2.y2 + offset, }) } if (ridge.x1 < x1 && ridge.x2 < x1) { offset = Math.abs(ridge.x2 - x1) - offset offset = offset < 0 ? 0 : offset ridge.set({ x1: ridge.x1 - offset, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2, }) gableLine = new QLine([ridge.x1, ridge.y1 - offset, ridge.x1, ridge.y1 + offset], { fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'gableLine', }) canvas?.add(gableLine) hip1.set({ x1: hip1.x1, y1: hip1.y1, x2: hip1.x2 - offset, y2: hip1.y1 < hip1.y2 ? hip1.y2 - offset : hip1.y2 + offset, }) hip2.set({ x1: hip2.x1, y1: hip2.y1, x2: hip2.x2 - offset, y2: hip2.y1 < hip2.y2 ? hip2.y2 - offset : hip2.y2 + offset, }) } } canvas?.renderAll() return gableLine } function arePointsEqual(point1, point2) { return point1.x === point2.x && point1.y === point2.y } function arraysHaveSamePoints(array1, array2) { if (array1.length !== array2.length) return false const sortedArray1 = array1.slice().sort((a, b) => a.x - b.x || a.y - b.y) const sortedArray2 = array2.slice().sort((a, b) => a.x - b.x || a.y - b.y) for (let i = 0; i < sortedArray1.length; i++) { if (!arePointsEqual(sortedArray1[i], sortedArray2[i])) { return false } } return true } export const toGeoJSON = (pointsArray) => { // 객체 배열을 GeoJSON 형식의 좌표 배열로 변환 const coordinates = pointsArray.map((point) => [point.x, point.y]) // 닫힌 다각형을 만들기 위해 첫 번째 점을 마지막에 추가 coordinates.push([pointsArray[0].x, pointsArray[0].y]) return coordinates } export const inPolygon = (polygonPoints, rectPoints) => { const polygonCoordinates = toGeoJSON(polygonPoints) const rectCoordinates = toGeoJSON(rectPoints) const polygonFeature = turf.polygon([polygonCoordinates]) const rectFeature = turf.polygon([rectCoordinates]) // 사각형의 모든 꼭짓점이 다각형 내부에 있는지 확인 const allPointsInsidePolygon = rectCoordinates.every((coord) => { const point = turf.point(coord) return turf.booleanPointInPolygon(point, polygonFeature) }) // 다각형의 모든 점이 사각형 내부에 있지 않은지 확인 const noPolygonPointsInsideRect = polygonCoordinates.every((coord) => { const point = turf.point(coord) return !turf.booleanPointInPolygon(point, rectFeature) }) return allPointsInsidePolygon && noPolygonPointsInsideRect } /** * poolygon의 방향에 따라 화살표를 추가한다. * @param polygon */ export const drawDirectionArrow = (polygon) => { const direction = polygon.direction if (!direction) { return } polygon.canvas .getObjects() .filter((obj) => obj.name === 'directionText' && obj.parent === polygon.arrow) .forEach((obj) => polygon.canvas.remove(obj)) let arrow = null let points = [] if (polygon.arrow) { polygon.canvas.remove(polygon.arrow) } let centerPoint = { x: polygon.width / 2 + polygon.left, y: polygon.height / 2 + polygon.top } let stickeyPoint const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMinX = Math.min(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMaxY = Math.max(...polygon.getCurrentPoints().map((point) => point.y)) const polygonMinY = Math.min(...polygon.getCurrentPoints().map((point) => point.y)) switch (direction) { case 'north': points = [ { x: centerPoint.x, y: polygonMinY - 20 }, { x: centerPoint.x + 20, y: polygonMinY - 20 }, { x: centerPoint.x + 20, y: polygonMinY - 50 }, { x: centerPoint.x + 50, y: polygonMinY - 50 }, { x: centerPoint.x, y: polygonMinY - 80 }, { x: centerPoint.x - 50, y: polygonMinY - 50 }, { x: centerPoint.x - 20, y: polygonMinY - 50 }, { x: centerPoint.x - 20, y: polygonMinY - 20 }, ] stickeyPoint = { x: centerPoint.x, y: polygonMinY - 80 } break case 'south': points = [ { x: centerPoint.x, y: polygonMaxY + 20 }, { x: centerPoint.x + 20, y: polygonMaxY + 20 }, { x: centerPoint.x + 20, y: polygonMaxY + 50 }, { x: centerPoint.x + 50, y: polygonMaxY + 50 }, { x: centerPoint.x, y: polygonMaxY + 80 }, { x: centerPoint.x - 50, y: polygonMaxY + 50 }, { x: centerPoint.x - 20, y: polygonMaxY + 50 }, { x: centerPoint.x - 20, y: polygonMaxY + 20 }, ] stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 80 } break case 'west': points = [ { x: polygonMinX - 20, y: centerPoint.y }, { x: polygonMinX - 20, y: centerPoint.y + 20 }, { x: polygonMinX - 50, y: centerPoint.y + 20 }, { x: polygonMinX - 50, y: centerPoint.y + 50 }, { x: polygonMinX - 80, y: centerPoint.y }, { x: polygonMinX - 50, y: centerPoint.y - 50 }, { x: polygonMinX - 50, y: centerPoint.y - 20 }, { x: polygonMinX - 20, y: centerPoint.y - 20 }, ] stickeyPoint = { x: polygonMinX - 80, y: centerPoint.y } break case 'east': points = [ { x: polygonMaxX + 20, y: centerPoint.y }, { x: polygonMaxX + 20, y: centerPoint.y + 20 }, { x: polygonMaxX + 50, y: centerPoint.y + 20 }, { x: polygonMaxX + 50, y: centerPoint.y + 50 }, { x: polygonMaxX + 80, y: centerPoint.y }, { x: polygonMaxX + 50, y: centerPoint.y - 50 }, { x: polygonMaxX + 50, y: centerPoint.y - 20 }, { x: polygonMaxX + 20, y: centerPoint.y - 20 }, ] stickeyPoint = { x: polygonMaxX + 80, y: centerPoint.y } break } arrow = new fabric.Polygon(points, { selectable: false, name: 'arrow', fill: 'transparent', stroke: 'black', direction: direction, parent: polygon, stickeyPoint: stickeyPoint, }) polygon.arrow = arrow polygon.canvas.add(arrow) polygon.canvas.renderAll() drawDirectionStringToArrow(polygon.canvas, 0) } /** * 방향을 나타낸 화살표에 각도에 따라 글씨 추가 * @param canvas * @param compass */ export const drawDirectionStringToArrow = (canvas, compass = 0) => { const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow') if (arrows.length === 0) { return } const eastArrows = arrows.filter((arrow) => arrow.direction === 'east') const westArrows = arrows.filter((arrow) => arrow.direction === 'west') const northArrows = arrows.filter((arrow) => arrow.direction === 'north') const southArrows = arrows.filter((arrow) => arrow.direction === 'south') let southText = '南' let eastText = '東' let westText = '西' let northText = '北' if (compass === 0 || compass === 360) { // 남,동,서 가능 // 그대로 } else if (compass < 45) { //남(남남동),동(동북동),서(서남서) 가능 //북(북북서) southText = '南南東' eastText = '東北東' westText = '西南西' northText = '北北西' } else if (compass === 45) { // 남, 서 가능 // 남(남동) // 서(남서) // 북(북서) // 동(북동) southText = '南東' westText = '南西' northText = '北西' eastText = '北東' } else if (compass < 90) { // 북(서북서) // 동 (북북동) // 남(동남동) // 서(남남서) northText = '北西北' eastText = '北北東' southText = '東南東' westText = '南南西' } else if (compass === 90) { // 동(북) // 서(남) // 남(동) // 북(서) eastText = '北' westText = '南' southText = '東' northText = '西' } else if (compass < 135) { // 남,서,북 가능 // 동(북북서) // 서(남남동) // 남(동북동) // 북(서남서) eastText = '北北西' westText = '南南東' southText = '東北東' northText = '西南西' } else if (compass === 135) { // 서,북 가능 // 서(남동) // 북(남서) // 남(북동) // 동(북서) westText = '南東' northText = '南西' southText = '北東' eastText = '北西' } else if (compass < 180) { // 북,동,서 가능 // 북(남남서) // 동(서북서) // 남(북북동) // 서(동남동) northText = '南南西' eastText = '西北西' southText = '北北東' westText = '東南東' } else if (compass === 180) { // 북,동,서 가능 // 북(남) // 동(서) // 남(북) // 서(동) northText = '南' eastText = '西' southText = '北' westText = '東' } else if (compass < 225) { // 서,북,동 가능 // 북(남남동) // 동(서남서) // 남(북북서) // 서(동남동) northText = '南南東' eastText = '西南西' southText = '北北西' westText = '東南東' } else if (compass === 225) { // 북,동 가능 // 북(남동) // 동(남서) // 남(북서) // 서(북동) northText = '南東' eastText = '南西' southText = '北西' westText = '北東' } else if (compass < 270) { // 북동남 가능 // 북(동남동) // 동(남남서) // 남(서북서) // 서(북북동) northText = '東南東' eastText = '南南西' southText = '西北西' westText = '北北東' } else if (compass === 270) { // 북동남 가능 // 북(동) // 동(남) // 남(서) // 서(북) northText = '東' eastText = '南' southText = '西' westText = '北' } else if (compass < 315) { // 북,동,남 가능 // 북(동북동) // 동(남남동) // 남(서남서) // 서(북북서) northText = '東北東' eastText = '南南東' southText = '西南西' westText = '北北西' } else if (compass === 315) { // 동,남 가능 // 북(북동) // 동(남동) // 남(남서) // 서(북서) northText = '北東' eastText = '南東' southText = '南西' westText = '北西' } else if (compass < 360) { // 남,동,서 가능 // 북(북북동) // 동(동남동) // 남(남남서) // 서(서북서) northText = '北北東' eastText = '東南東' southText = '南南西' westText = '西北西' } clearDirectionText(canvas) addTextByArrows(eastArrows, eastText, canvas) addTextByArrows(westArrows, westText, canvas) addTextByArrows(northArrows, northText, canvas) addTextByArrows(southArrows, southText, canvas) } const clearDirectionText = (canvas) => { const texts = canvas.getObjects().filter((obj) => obj.name === 'directionText') texts.forEach((text) => { canvas.remove(text) }) } const addTextByArrows = (arrows, txt, canvas) => { arrows.forEach((arrow, index) => { const text = new fabric.Text(`${txt}${index + 1}`, { fontSize: arrow.parent.fontSize, fill: 'black', originX: 'center', originY: 'center', name: 'directionText', selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, parent: arrow, }) canvas.add(text) }) }