import { intersect } from 'mathjs' /** * 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)) } // 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 = point2.x - point1.x const dy = point2.y - point1.y return Math.sqrt(dx * dx + dy * dy) } /** * 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(2)) } /** * 두 점 사이의 방향을 반환합니다. * @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) const line1MaxX = Math.max(line1.x1, line1.x2) const line2MinX = Math.min(line2.x1, line2.x2) const line2MaxX = Math.max(line2.x1, line2.x2) const line1MinY = Math.min(line1.y1, line1.y2) const line1MaxY = Math.max(line1.y1, line1.y2) const line2MinY = Math.min(line2.y1, line2.y2) const line2MaxY = Math.max(line2.y1, line2.y2) // Check if the intersection X and Y are within the range of both lines if ( result[0] >= line1MinX && result[0] <= line1MaxX && result[0] >= line2MinX && result[0] <= line2MaxX && result[1] >= line1MinY && result[1] <= line1MaxY && result[1] >= line2MinY && result[1] <= line2MaxY ) { return { x: Math.round(result[0]), y: Math.round(result[1]) } } else { return null // Intersection is out of range } } 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} */ // 직선의 방정식. // 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. export function isPointOnLine(line, point) { const a = line.y2 - line.y1 const b = line.x1 - line.x2 const c = line.x2 * line.y1 - line.x1 * line.y2 return a * point.x + b * point.y + c === 0 } /** * 점과 가까운 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} 두 점 사이의 거리 */ 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 }