qcast-front/src/util/canvas-util.js
2024-12-26 09:35:04 +09:00

1012 lines
30 KiB
JavaScript

import { intersect } from 'mathjs'
import * as turf from '@turf/turf'
/**
* 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 = 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 degree
* @returns {number}
*/
export const getChonByDegree = (degree) => {
return Number((Math.tan((degree * Math.PI) / 180) * 10).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 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}
*/
// 직선의 방정식.
// 방정식은 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
const result = Math.abs(a * point.x + b * point.y + c) / 100
// 점이 선 위에 있는지 확인
return result <= 10
}
/**
* 점과 가까운 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
}
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 >= 352) {
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,
},
)
}