import { intersect } from 'mathjs' import * as turf from '@turf/turf' import Big from 'big.js' /** * Collection of function to use on canvas */ // define a function that can locate the controls export function polygonPositionHandler(dim, finalMatrix, fabricObject) { // @ts-ignore let x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x // @ts-ignore let y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y return fabric.util.transformPoint( { x, y }, fabric.util.multiplyTransformMatrices(fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix()), ) } function getObjectSizeWithStroke(object) { let stroke = new fabric.Point(object.strokeUniform ? 1 / object.scaleX : 1, object.strokeUniform ? 1 / object.scaleY : 1).multiply( object.strokeWidth, ) return new fabric.Point(object.width + stroke.x, object.height + stroke.y) } // define a function that will define what the control does export function actionHandler(eventData, transform, x, y) { let polygon = transform.target, currentControl = polygon.controls[polygon.__corner], mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'), polygonBaseSize = getObjectSizeWithStroke(polygon), size = polygon._getTransformedDimensions(0, 0) polygon.points[currentControl.pointIndex] = { x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x, y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y, } return true } // define a function that can keep the polygon in the same position when we change its width/height/top/left export function anchorWrapper(anchorIndex, fn) { return function (eventData, transform, x, y) { let fabricObject = transform.target let originX = fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y let absolutePoint = fabric.util.transformPoint( { x: originX, y: originY, }, fabricObject.calcTransformMatrix(), ) let actionPerformed = fn(eventData, transform, x, y) let newDim = fabricObject._setPositionDimensions({}) let polygonBaseSize = getObjectSizeWithStroke(fabricObject) let newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x let newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5) return actionPerformed } } /** * 두 좌표의 중간점 좌표를 계산해서 반환하는 함수 * @param {number} point1 * @param {number} point2 방향에 상관없이 항상 큰 값이 뒤에 위치해야 함 * @returns */ export const getCenterPoint = (point1, point2) => { return point1 + (point2 - point1) / 2 } /** * 두 점 사이의 거리를 계산하는 함수 * @param {*} x1 첫번째 점 x좌표 * @param {*} y1 첫번째 점 y좌표 * @param {*} x2 두번째 점 x좌표 * @param {*} y2 두번째 점 y좌표 * @returns */ export const getDistance = (x1, y1, x2, y2) => { return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)).toFixed(0) } // polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수 export function addDistanceTextToPolygon(polygon) { const points = polygon.get('points') const texts = [] for (let i = 0; i < points.length; i++) { const start = points[i] const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로) const distance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)) // 두 점 사이의 거리 계산 const text = new fabric.Textbox(distance.toFixed(2), { // 소수 둘째자리까지 표시 left: (start.x + end.x) / 2, // 텍스트의 위치는 두 점의 중간 top: (start.y + end.y) / 2, fontSize: 20, }) texts.push(text) } return new fabric.Group([polygon, ...texts], { // polygon과 텍스트들을 그룹화 selectable: true, }) } /** * * @param {number} value * @param {boolean} useDefault * @param {string} delimeter * @returns * ex) 1,100 mm */ export const formattedWithComma = (value, unit = 'mm') => { let formatterdData = value.toLocaleString('ko-KR') if (unit === 'cm') { formatterdData = value.toLocaleString('ko-KR') / 10 } else if (unit === 'm') { formatterdData = value.toLocaleString('ko-KR') / 1000 } return `${formatterdData} ${unit}` } export const distanceBetweenPoints = (point1, point2) => { const dx = Big(point2.x).minus(Big(point1.x)) const dy = Big(point2.y).minus(Big(point1.y)) return dx.pow(2).plus(dy.pow(2)).sqrt().round(1).toNumber() } /** * line의 시작점을 찾는 함수 * @param lines * @returns {number} */ export const getStartIndex = (lines) => { let smallestIndex = 0 let smallestX1 = lines[0].x1 let smallestY1 = lines[0].y1 for (let i = 1; i < lines.length; i++) { if (lines[i].x1 < smallestX1 || (lines[i].x1 === smallestX1 && lines[i].y1 < smallestY1)) { smallestIndex = i smallestX1 = lines[i].x1 smallestY1 = lines[i].y1 } } return smallestIndex } /** * points 배열에서 시작점을 찾는 함수 * @param points * @returns {number} */ export const getStartIndexPoint = (points) => { let smallestIndex = 0 let smallestX1 = points[0].x let smallestY1 = points[0].y for (let i = 1; i < points.length; i++) { if (points[i].x < smallestX1 || (points[i].x === smallestX1 && points[i].y < smallestY1)) { smallestIndex = i smallestX1 = points[i].x smallestY1 = points[i].y } } return smallestIndex } /** * 이 함수는 두 개의 매개변수를 받습니다: array와 index. * array는 재배열할 대상 배열입니다. * index는 재배열의 기준이 될 배열 내의 위치입니다. * 함수는 먼저 index 위치부터 배열의 마지막 요소까지를 추출합니다(fromIndexToEnd). * 그 다음, 배열의 처음부터 index 위치까지의 요소를 추출합니다(fromStartToIndex). * 마지막으로, fromIndexToEnd와 fromStartToIndex 두 부분을 concat 메소드를 이용해 합칩니다. * 따라서, 이 함수는 주어진 index를 기준으로 배열을 두 부분으로 나누고, index부터 시작하는 부분을 앞에 두고, 그 뒤에 index 이전의 부분을 이어붙여 새로운 배열을 생성합니다. 이는 배열의 회전(rotating) 연산을 수행하는 것과 유사합니다. * @param array 재배열할 대상 배열 * @param index 재배열 기준이 될 배열 내의 인덱스 * @returns {*} 새로 재배열된 배열 */ export const rearrangeArray = (array, index) => { // 배열의 특정 인덱스부터 마지막 요소까지를 가져옵니다. const fromIndexToEnd = array.slice(index) // 배열의 처음부터 특정 인덱스까지의 요소를 가져옵니다. const fromStartToIndex = array.slice(0, index) // 두 부분을 concat 메소드를 이용해 합칩니다. return fromIndexToEnd.concat(fromStartToIndex) } export const findTopTwoIndexesByDistance = (objArr) => { if (objArr.length < 2) { return [] // 배열의 길이가 2보다 작으면 빈 배열 반환 } let firstIndex = -1 let secondIndex = -1 let firstDistance = -Infinity let secondDistance = -Infinity for (let i = 0; i < objArr.length; i++) { const distance = objArr[i].length if (distance > firstDistance) { secondDistance = firstDistance secondIndex = firstIndex firstDistance = distance firstIndex = i } else if (distance > secondDistance) { secondDistance = distance secondIndex = i } } return [firstIndex, secondIndex] } /** * 지붕의 누워있는 높이 * @param base 밑변 * @param degree 각도 ex(4촌의 경우 21.8) */ export const getRoofHeight = (base, degree) => { return base / Math.cos((degree * Math.PI) / 180) } /** * 지붕 빗변의 길이 * @param base 밑변 */ export const getRoofHypotenuse = (base) => { return Math.sqrt(base * base * 2) } /** * 빗변의 길이를 입력받아 밑변의 길이를 반환 * @param base 빗변 */ export const getAdjacent = (base) => { return Math.round(Math.sqrt(Math.pow(base, 2) / 2)) } /** * 촌을 입력받아 각도를 반환 * @param chon * @returns {number} */ export const getDegreeByChon = (chon) => { // tan(theta) = height / base const radians = Math.atan(chon / 10) // 라디안을 도 단위로 변환 return Number((radians * (180 / Math.PI)).toFixed(1)) } /** * 각도를 입력받아 촌을 반환 * @param degree * @returns {number} */ export const getChonByDegree = (degree) => { return Number((Math.tan((degree * Math.PI) / 180) * 10).toFixed(1)) } /** * 두 점 사이의 방향을 반환합니다. * @param a {fabric.Object} * @param b {fabric.Object} * @returns {string} */ export const getDirection = (a, b) => { const vector = { x: b.left - a.left, y: b.top - a.top, } if (Math.abs(vector.x) > Math.abs(vector.y)) { // x축 방향으로 더 많이 이동 return vector.x > 0 ? 'right' : 'left' } else { // y축 방향으로 더 많이 이동 return vector.y > 0 ? 'bottom' : 'top' } } /** * 두 점 사이의 방향을 반환합니다. */ export const getDirectionByPoint = (a, b) => { const vector = { x: b.x - a.x, y: b.y - a.y, } if (Math.abs(vector.x) > Math.abs(vector.y)) { // x축 방향으로 더 많이 이동 return vector.x > 0 ? 'right' : 'left' } else { // y축 방향으로 더 많이 이동 return vector.y > 0 ? 'bottom' : 'top' } } export const findIntersection1 = (line1, line2) => { const { x1, y1, x2, y2 } = line1 // 첫 번째 선의 두 점 const x3 = line2.x1 const y3 = line2.y1 const x4 = line2.x2 const y4 = line2.y2 // 선의 방정식의 계수 계산 const A1 = y2 - y1 const B1 = x1 - x2 const C1 = A1 * x1 + B1 * y1 const A2 = y4 - y3 const B2 = x3 - x4 const C2 = A2 * x3 + B2 * y3 const determinant = A1 * B2 - A2 * B1 if (determinant === 0) { // 두 선이 평행하거나 일직선일 경우 return null } const x = (B1 * C2 - B2 * C1) / determinant const y = (A1 * C2 - A2 * C1) / determinant return { x, y } } export const calculateIntersection = (line1, line2) => { const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2]) if (!result) { return null } // Determine the min and max for line1 and line2 for both x and y const line1MinX = Math.min(line1.x1, line1.x2) - 5 const line1MaxX = Math.max(line1.x1, line1.x2) + 5 const line2MinX = Math.min(line2.x1, line2.x2) - 5 const line2MaxX = Math.max(line2.x1, line2.x2) + 5 const line1MinY = Math.min(line1.y1, line1.y2) - 5 const line1MaxY = Math.max(line1.y1, line1.y2) + 5 const line2MinY = Math.min(line2.y1, line2.y2) - 5 const line2MaxY = Math.max(line2.y1, line2.y2) + 5 // Check if the intersection X and Y are within the range of both lines if ( result[0] >= line1MinX - 1 && result[0] <= line1MaxX + 1 && result[0] >= line2MinX - 1 && result[0] <= line2MaxX + 1 && result[1] >= line1MinY - 1 && result[1] <= line1MaxY + 1 && result[1] >= line2MinY - 1 && result[1] <= line2MaxY + 1 ) { return { x: result[0], y: result[1] } } else { return null // Intersection is out of range } } export const getInterSectionLineNotOverCoordinate = (line1, line2) => { const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2]) if (result) { return { x: Math.round(result[0]), y: Math.round(result[1]) } } return null } export function findOrthogonalPoint(line1, line2) { // Calculate the intersection point of two lines const intersectionX = ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.x2 - line2.x1) - (line1.x1 - line1.x2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) / ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1)) const intersectionY = ((line1.x1 * line2.y1 - line1.y1 * line2.x1) * (line2.y2 - line2.y1) - (line1.y1 - line1.y2) * (line2.x2 * line2.y2 - line2.y1 * line2.x1)) / ((line1.x1 - line1.x2) * (line2.y1 - line2.y2) - (line1.y1 - line1.y2) * (line2.x2 - line2.x1)) return { x: intersectionX, y: intersectionY } } /** * points배열을 입력받아 반시계방향으로 정렬된 points를 반환합니다. * @param points */ export const sortedPoints = (points) => { const copyPoints = [...points] copyPoints.forEach((point) => { point.x1 = point.x point.y1 = point.y const nextPoint = copyPoints[(copyPoints.indexOf(point) + 1) % copyPoints.length] point.x2 = nextPoint.x point.y2 = nextPoint.y }) // copyPoint에서 x1, y1 값을 기준으로 시작 인덱스 const startIndex = getStartIndex(copyPoints) const startDirection = getDirectionByPoint( { x: copyPoints[startIndex].x1, y: copyPoints[startIndex].y1 }, { x: copyPoints[startIndex].x2, y: copyPoints[startIndex].y2 }, ) const resultPoints = [copyPoints[startIndex]] let currentPoint = copyPoints[startIndex] switch (startDirection) { case 'right': { copyPoints.forEach((point, index) => { if (index === startIndex) return const nextPoint = copyPoints.find((p) => p.x2 === currentPoint.x && p.y2 === currentPoint.y) resultPoints.push(nextPoint) currentPoint = nextPoint }) break } case 'bottom': { copyPoints.forEach((point, index) => { if (index === startIndex) return const nextPoint = copyPoints.find((p) => p.x1 === currentPoint.x2 && p.y1 === currentPoint.y2) resultPoints.push(nextPoint) currentPoint = nextPoint }) break } } return resultPoints.map((point) => { return { x: point.x, y: point.y } }) } export const sortedPointLessEightPoint = (points) => { const copyPoints = [...points] //points를 x,y좌표를 기준으로 정렬합니다. copyPoints.sort((a, b) => { if (a.x === b.x) { return a.y - b.y } return a.x - b.x }) const resultPoints = [copyPoints[0]] let index = 1 let currentPoint = { ...copyPoints[0] } copyPoints.splice(0, 1) while (index < points.length) { if (index === points.length - 1) { resultPoints.push(copyPoints[0]) index++ break } else if (index % 2 === 0) { // 짝수번째는 y값이 같은 점을 찾는다. for (let i = 0; i < copyPoints.length; i++) { // y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다. let temp = copyPoints.filter((point) => point.y === currentPoint.y) if (temp.length === 0) { // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다. temp = Array.of(findClosestPointByY(currentPoint, copyPoints)) } // temp중 x값이 가장 가까운 값 const min = temp.reduce((prev, current) => (Math.abs(current.x - currentPoint.x) <= Math.abs(prev.x - currentPoint.x) ? current : prev)) resultPoints.push(min) currentPoint = min copyPoints.splice(copyPoints.indexOf(min), 1) index++ break } } else { // 홀수번째는 x값이 같은 점을 찾는다. for (let i = 0; i < copyPoints.length; i++) { // x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다. let temp = copyPoints.filter((point) => point.x === currentPoint.x) if (temp.length === 0) { // temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다. temp = Array.of(findClosestPointByX(currentPoint, copyPoints)) } // temp중 y값이 가장 가까운 값 const min = temp.reduce((prev, current) => (Math.abs(current.y - currentPoint.y) <= Math.abs(prev.y - currentPoint.y) ? current : prev)) resultPoints.push(min) currentPoint = min copyPoints.splice(copyPoints.indexOf(min), 1) index++ break } } } return resultPoints } /** * point가 선 위에 있는지 확인 * @param line * @param point * @returns {boolean} * @param epsilon */ // 직선의 방정식. // 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. export function isPointOnLine({ x1, y1, x2, y2 }, { x, y }, epsilon = 2) { /*const a = y2 - y1 const b = x1 - x2 const c = x2 * y1 - x1 * y2 return Math.abs(a * x + b * y + c) < 1000*/ /*/!*const a = line.y2 - line.y1 const b = line.x1 - line.x2 const c = line.x2 * line.y1 - line.x1 * line.y2 const result = Math.abs(a * point.x + b * point.y + c) / 100 // 점이 선 위에 있는지 확인 return result <= 10*!*/ // 직선 방정식 만족 여부 확인 const crossProduct = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1) if (Math.abs(crossProduct) > 1000) return false // 작은 오차 허용 const isSameX = Math.abs(x1 - x2) < 2 const isSameY = Math.abs(y1 - y2) < 2 // 점이 선분의 범위 내에 있는지 확인 let withinXRange = Math.min(x1, x2) - x <= 2 if (!isSameX) { withinXRange = withinXRange && 2 <= Math.max(x1, x2) - x } let withinYRange = Math.min(y1, y2) - y <= 2 if (!isSameY) { withinYRange = withinYRange && 2 <= Math.max(y1, y2) - y } return withinXRange && withinYRange } /** * 점과 가까운 line 찾기 * @param point * @param lines {Array} * @returns {null} */ export function findClosestLineToPoint(point, lines) { let closestLine = null let closestDistance = Infinity for (const line of lines) { const distance = calculateDistance(point, line) if (distance < closestDistance) { closestDistance = distance closestLine = line } } return closestLine } /** * x값이 가장 가까운 점 * @param targetPoint * @param points * @returns {*|null} */ function findClosestPointByX(targetPoint, points) { if (points.length === 0) { return null // Return null if the points array is empty } let closestPoint = points[0] let smallestDistance = Math.abs(targetPoint.x - points[0].x) for (let i = 1; i < points.length; i++) { const currentDistance = Math.abs(targetPoint.x - points[i].x) if (currentDistance < smallestDistance) { smallestDistance = currentDistance closestPoint = points[i] } } return closestPoint } /** * y값이 가장 가까운 점 * @param targetPoint * @param points * @returns {*|null} */ function findClosestPointByY(targetPoint, points) { if (points.length === 0) { return null // Return null if the points array is empty } let closestPoint = points[0] let smallestDistance = Math.abs(targetPoint.y - points[0].y) for (let i = 1; i < points.length; i++) { const currentDistance = Math.abs(targetPoint.y - points[i].y) if (currentDistance < smallestDistance) { smallestDistance = currentDistance closestPoint = points[i] } } return closestPoint } /** * 주어진 점에서 points 배열 중 가장 가까운 점을 찾는 함수입니다. * @param {Object} targetPoint - { x: number, y: number } 형태의 대상 점 객체 * @param {Array} points - { x: number, y: number } 형태의 점 객체들의 배열 * @returns {Object} targetPoint에 가장 가까운 점 객체 */ export function findClosestPoint(targetPoint, points) { if (points.length === 0) { return null // points 배열이 비어있으면 null 반환 } let closestPoint = points[0] let smallestDistance = calculateDistancePoint(targetPoint, closestPoint) for (let i = 1; i < points.length; i++) { const currentDistance = calculateDistancePoint(targetPoint, points[i]) if (currentDistance < smallestDistance) { smallestDistance = currentDistance closestPoint = points[i] } } return closestPoint } /** * 점과 직선사이의 최단 거리 * @param point * @param line * @returns {number} */ export function calculateDistance(point, line) { const x1 = line.x1 const y1 = line.y1 const x2 = line.x2 const y2 = line.y2 const x0 = point.x const y0 = point.y const numerator = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)) return numerator / denominator } /** * 두 점 사이의 거리를 계산하는 함수입니다. * @param {Object} point1 - 첫 번째 점 { x: number, y: number } * @param {Object} point2 - 두 번째 점 { x: number, y: number } * @returns {number} 두 점 사이의 거리 */ export function calculateDistancePoint(point1, point2) { return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)) } /** * {x, y} 형태의 배열을 받아 중복된 점을 제거하는 함수 * @param points * @returns {*[]} */ export function removeDuplicatePoints(points) { const uniquePoints = [] const seen = new Set() points.forEach((point) => { const identifier = `${point.x}:${point.y}` if (!seen.has(identifier)) { seen.add(identifier) uniquePoints.push(point) } }) return uniquePoints } /** * x,y가 다르면서 가장 가까운 점 * @param targetPoint * @param points * @returns {null} */ export function findClosestPointWithDifferentXY(targetPoint, points) { let closestPoint = null let smallestDistance = Infinity points.forEach((point) => { if (point.x !== targetPoint.x && point.y !== targetPoint.y) { const distance = Math.sqrt(Math.pow(point.x - targetPoint.x, 2) + Math.pow(point.y - targetPoint.y, 2)) if (distance < smallestDistance) { smallestDistance = distance closestPoint = point } } }) return closestPoint } export const getClosestHorizontalLine = (pointer, horizontalLineArray) => { let closestLine = null let minDistance = Infinity horizontalLineArray.forEach((line) => { const distance = Math.abs(line.y1 - pointer.y) // Assuming horizontal lines have the same y1 and y2 if (distance < minDistance) { minDistance = distance closestLine = line } }) return closestLine } export const getClosestVerticalLine = (pointer, verticalLineArray) => { let closestLine = null let minDistance = Infinity verticalLineArray.forEach((line) => { const distance = Math.abs(line.x1 - pointer.x) // Assuming horizontal lines have the same y1 and y2 if (distance < minDistance) { minDistance = distance closestLine = line } }) return closestLine } /** * 빗변과 높이를 가지고 빗변의 x값을 구함 * @param {} p1 * @param {*} p2 * @param {*} y * @returns */ export const getIntersectionPoint = (p1, p2, y) => { const { x: x1, y: y1 } = p1 const { x: x2, y: y2 } = p2 // 기울기와 y 절편을 이용해 선의 방정식을 구합니다. const slope = (y2 - y1) / (x2 - x1) const intercept = y1 - slope * x1 // y = 150일 때의 x 좌표를 구합니다. const x = (y - intercept) / slope return { x, y } } export const pointsToTurfPolygon = (points) => { const coordinates = points.map((point) => [point.x, point.y]) coordinates.push(coordinates[0]) return turf.polygon([coordinates]) } export function isOverlap(polygon1, polygon2) { return turf.booleanOverlap(polygon1, polygon2) } export const triangleToPolygon = (triangle) => { const points = [] const halfWidth = triangle.width / 2 const height = triangle.height points.push({ x: triangle.left, y: triangle.top }) points.push({ x: triangle.left - halfWidth, y: triangle.top + height }) points.push({ x: triangle.left + halfWidth, y: triangle.top + height }) return points } export const rectToPolygon = (rect) => { const points = [] const left = rect.left const top = rect.top const width = rect.width * rect.scaleX // 스케일 적용 const height = rect.height * rect.scaleY // 스케일 적용 // 네 개의 꼭짓점 좌표 계산 points.push({ x: left, y: top }) // 좌상단 points.push({ x: left + width, y: top }) // 우상단 points.push({ x: left + width, y: top + height }) // 우하단 points.push({ x: left, y: top + height }) // 좌하단 return points } //면형상 선택 클릭시 지붕 패턴 입히기 function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 let height = 150 / 10 let roofStyle = 2 const inputPatternSize = { width: width, height: height } //임시 사이즈 const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 if (polygon.direction === 'east' || polygon.direction === 'west') { //세로형이면 width height를 바꿈 ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] } // 패턴 소스를 위한 임시 캔버스 생성 const patternSourceCanvas = document.createElement('canvas') patternSourceCanvas.width = polygon.width * ratio patternSourceCanvas.height = polygon.height * ratio const ctx = patternSourceCanvas.getContext('2d') let offset = roofStyle === 1 ? 0 : patternSize.width / 2 const rows = Math.floor(patternSourceCanvas.height / patternSize.height) const cols = Math.floor(patternSourceCanvas.width / patternSize.width) ctx.strokeStyle = mode === 'allPainted' ? 'black' : 'green' ctx.lineWidth = mode === 'allPainted' ? 1 : 0.4 ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' if (trestleMode) { ctx.strokeStyle = 'black' ctx.lineWidth = 0.2 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' } else { ctx.fillStyle = 'rgba(255, 255, 255, 1)' } if (polygon.direction === 'east' || polygon.direction === 'west') { offset = roofStyle === 1 ? 0 : patternSize.height / 2 for (let col = 0; col <= cols; col++) { const x = col * patternSize.width const yStart = 0 const yEnd = patternSourceCanvas.height ctx.beginPath() ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } for (let row = 0; row <= rows; row++) { const y = row * patternSize.height + (col % 2 === 0 ? 0 : offset) const xStart = col * patternSize.width const xEnd = xStart + patternSize.width ctx.beginPath() ctx.moveTo(xStart, y) // 선 시작점 ctx.lineTo(xEnd, y) // 선 끝점 ctx.stroke() if (mode === 'allPainted' || trestleMode) { ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) } } } } else { for (let row = 0; row <= rows; row++) { const y = row * patternSize.height ctx.beginPath() ctx.moveTo(0, y) // 선 시작점 ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 ctx.stroke() if (mode === 'allPainted' || trestleMode) { ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) } for (let col = 0; col <= cols; col++) { const x = col * patternSize.width + (row % 2 === 0 ? 0 : offset) const yStart = row * patternSize.height const yEnd = yStart + patternSize.height ctx.beginPath() ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } } } } const hachingPatternSourceCanvas = document.createElement('canvas') if (mode === 'lineHatch') { hachingPatternSourceCanvas.width = polygon.width * ratio hachingPatternSourceCanvas.height = polygon.height * ratio const ctx1 = hachingPatternSourceCanvas.getContext('2d') const gap = 10 ctx1.strokeStyle = 'green' // 선 색상 ctx1.lineWidth = 0.3 // 선 두께 for (let x = 0; x < hachingPatternSourceCanvas.width + hachingPatternSourceCanvas.height; x += gap) { ctx1.beginPath() ctx1.moveTo(x, 0) // 선 시작점 ctx1.lineTo(0, x) // 선 끝점 ctx1.stroke() } } const combinedPatternCanvas = document.createElement('canvas') combinedPatternCanvas.width = polygon.width * ratio combinedPatternCanvas.height = polygon.height * ratio const combinedCtx = combinedPatternCanvas.getContext('2d') // 첫 번째 패턴을 그린 후 두 번째 패턴을 덧입힘 combinedCtx.drawImage(patternSourceCanvas, 0, 0) combinedCtx.drawImage(hachingPatternSourceCanvas, 0, 0) // 패턴 생성 const pattern = new fabric.Pattern({ source: combinedPatternCanvas, repeat: 'repeat', }) polygon.set('fill', null) polygon.set('fill', pattern) polygon.canvas?.renderAll() } export function checkLineOrientation(line) { if (line.y1 === line.y2) { return 'horizontal' // 수평 } else if (line.x1 === line.x2) { return 'vertical' // 수직 } else { return 'diagonal' // 대각선 } } // 최상위 parentId를 통해 모든 하위 객체를 찾는 함수 export const getAllRelatedObjects = (id, canvas) => { const result = [] const map = new Map() // Create a map of objects by their id canvas.getObjects().forEach((obj) => { map.set(obj.id, obj) }) // Helper function to recursively find all related objects function findRelatedObjects(id) { const obj = map.get(id) if (obj) { result.push(obj) canvas.getObjects().forEach((o) => { if (o.parentId === id) { findRelatedObjects(o.id) } }) } } // Start the search with the given parentId findRelatedObjects(id) return result } // 모듈,회로 구성에서 사용하는 degree 범위 별 값 export const getDegreeInOrientation = (degree) => { if (degree === 180 || degree === -180) { return 180 } if (degree >= 180 || degree < -180) { return 0 } if (degree % 15 === 0) return degree let value = Math.floor(degree / 15) const remain = ((degree / 15) % 1).toFixed(5) if (remain > 0.4) { value++ } return value * 15 } export function findAndRemoveClosestPoint(targetPoint, points) { if (points.length === 0) { return null } let closestPoint = points[0] let closestDistance = distanceBetweenPoints(targetPoint, points[0]) let closestIndex = 0 for (let i = 1; i < points.length; i++) { const distance = distanceBetweenPoints(targetPoint, points[i]) if (distance < closestDistance) { closestDistance = distance closestPoint = points[i] closestIndex = i } } // Remove the closest point from the array points.splice(closestIndex, 1) return closestPoint } export function polygonToTurfPolygon(object, current = false) { let coordinates coordinates = object.points.map((point) => [point.x, point.y]) if (current) coordinates = object.getCurrentPoints().map((point) => [point.x, point.y]) coordinates.push(coordinates[0]) return turf.polygon( [coordinates], {}, { parentId: object.parentId, }, ) } export function calculateVisibleModuleHeight(sourceWidth, sourceHeight, angle, direction) { // 각도를 라디안으로 변환 const radians = (angle * Math.PI) / 180 let visibleWidth = sourceWidth // 가로는 기울어도 변하지 않음 let visibleHeight = sourceHeight * Math.cos(radians) // 세로는 각도에 따라 줄어듦 // 가로와 세로의 보이는 크기 계산 if (direction === 'south' || direction === 'north') { visibleWidth = sourceWidth // 가로는 기울어도 변하지 않음 visibleHeight = sourceHeight * Math.cos(radians) // 세로는 각도에 따라 줄어듦 } else { visibleWidth = sourceWidth * Math.cos(radians) // 가로는 기울어도 변하지 않음 visibleHeight = sourceHeight // 세로는 각도에 따라 줄어듦 } return { width: Number(visibleWidth.toFixed(1)), height: Number(visibleHeight.toFixed(1)), // 소수점 두 자리로 고정 } } //숫자, 몇자리수 export function toFixedWithoutRounding(number, decimals) { return Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals) } export function getTrianglePoints(triangle) { const matrix = triangle.calcTransformMatrix() const w = triangle.width / 2 const h = triangle.height / 2 const points = [ { x: 0, y: -h }, { x: -w, y: h }, { x: w, y: h }, ] return points.map((point) => fabric.util.transformPoint(point, matrix)) }