1012 lines
30 KiB
JavaScript
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,
|
|
},
|
|
)
|
|
}
|