qcast-front/src/util/qpolygon-utils.js
2024-11-07 17:07:30 +09:00

3476 lines
130 KiB
JavaScript

import { fabric } from 'fabric'
import { QLine } from '@/components/fabric/QLine'
import {
calculateIntersection,
distanceBetweenPoints,
findClosestPoint,
getDegreeByChon,
getDirectionByPoint,
isPointOnLine,
} from '@/util/canvas-util'
import { QPolygon } from '@/components/fabric/QPolygon'
import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
const TWO_PI = Math.PI * 2
export const defineQPloygon = () => {
fabric.QPolygon.fromObject = function (object, callback) {
fabric.Object._fromObject('QPolygon', object, callback, 'points')
}
}
export const drawHelpLineInHexagon = (polygon, pitch) => {
const centerLines = drawCenterLines(polygon)
let helpLines = []
const interSectionPoints = []
const tempInterSectionPoints = []
const ridgeStartPoints = []
const ridgeEndPoints = []
let centerInterSectionPoints = []
// polygon.lines = polygon.lines.sort((a, b) => a.length - b.length)
polygon.wall.lines = getOneSideLines(polygon.wall)
const maxLength = Math.max(...polygon.lines.map((line) => line.length))
polygon.points.forEach((point, index) => {
const wallPoint = polygon.wall.points[index]
const angle = Math.atan2(wallPoint.y - point.y, wallPoint.x - point.x)
const degree = fabric.util.radiansToDegrees(angle)
const newX2 = Math.floor(point.x + maxLength * Math.cos(angle))
const newY2 = Math.floor(point.y + maxLength * Math.sin(angle))
const helpLine = new QLine([point.x, point.y, newX2, newY2], {
fontSize: polygon.fontSize,
stroke: 'green',
startPoint: point,
degree: degree,
idx: index,
})
// polygon.canvas?.add(helpLine)
helpLines.push(helpLine)
})
helpLines.forEach((line, index) => {
for (let i = index + 1; i < helpLines.length; i++) {
const nextLine = helpLines[i]
if (!line.connectedPoint) {
line.connectedPoint = null
line.connectedPoints = []
}
if (!nextLine.connectedPoint) {
nextLine.connectedPoint = null
nextLine.connectedPoints = []
}
const interSectionPoint = calculateIntersection(line, nextLine)
if (
interSectionPoint &&
polygon.inPolygon(interSectionPoint) &&
polygon.wall.inPolygon(interSectionPoint) &&
Math.abs(distanceBetweenPoints(line.startPoint, interSectionPoint) - distanceBetweenPoints(nextLine.startPoint, interSectionPoint)) < 2
) {
const area = calculateTriangleArea(line.startPoint, nextLine.startPoint, interSectionPoint)
const currentLineConnectedPoint = line.connectedPoint
const nextLineConnectedPoint = nextLine.connectedPoint
if (area <= 1) {
return
}
if (currentLineConnectedPoint && currentLineConnectedPoint.area < area) {
return
}
//startPoint는 line의 startPoint와 nextLine의 startPoint를 비교하여 x가 같은경우 y가 더 작은 값, y가 같은경우 x가 더 작은 값을 선택한다.
const startPoint =
line.startPoint.x === nextLine.startPoint.x
? line.startPoint.y < nextLine.startPoint.y
? line.startPoint
: nextLine.startPoint
: line.startPoint.x < nextLine.startPoint.x
? line.startPoint
: nextLine.startPoint
const endPoint =
line.startPoint.x === nextLine.startPoint.x
? line.startPoint.y > nextLine.startPoint.y
? line.startPoint
: nextLine.startPoint
: line.startPoint.x > nextLine.startPoint.x
? line.startPoint
: nextLine.startPoint
line.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
line.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint }
nextLine.connectedPoints.push({ interSectionPoint, area, startPoint, endPoint })
}
}
})
helpLines.forEach((line) => {
if (line.connectedPoint) {
tempInterSectionPoints.push(line.connectedPoint)
}
})
// interSectionPoints에서 interSectionPoint가 중복인 값이 있는 경우만 선택한다.
tempInterSectionPoints.forEach((point) => {
// intersectionPoint가 중복인 경우
const isDuplicated =
tempInterSectionPoints.filter((p) => p.interSectionPoint.x === point.interSectionPoint.x && p.interSectionPoint.y === point.interSectionPoint.y)
.length > 1
if (isDuplicated) {
interSectionPoints.push(point)
}
})
// interSectionPoints에서 interSectionPoint 기준으로 중복을 제거한다.
const uniqueInterSectionPoints = Array.from(
new Set(interSectionPoints.map((point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}`)),
).map((key) => {
const { interSectionPoint, area, startPoint, endPoint } = interSectionPoints.find(
(point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}` === key,
)
return { interSectionPoint, area, startPoint, endPoint }
})
uniqueInterSectionPoints.forEach((point) => {
ridgeStartPoints.push(point.interSectionPoint)
const line = new QLine([point.startPoint.x, point.startPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
const line2 = new QLine([point.endPoint.x, point.endPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = point.startPoint
line.endPoint = point.interSectionPoint
line2.startPoint = point.endPoint
line2.endPoint = point.interSectionPoint
polygon.hips.push(line)
polygon.hips.push(line2)
polygon.canvas.add(line)
polygon.canvas.add(line2)
})
const removedIdx = []
helpLines.forEach((line) => {
const connectedPoints = line.connectedPoints
connectedPoints.forEach((connectedPoint) => {
uniqueInterSectionPoints.forEach((point) => {
const interSectionPoint = point.interSectionPoint
if (connectedPoint.interSectionPoint.x === interSectionPoint.x && connectedPoint.interSectionPoint.y === interSectionPoint.y) {
removedIdx.push(line.idx)
}
})
})
})
let notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx))
notIntersectedLines = notIntersectedLines.map((line) => {
return { ...line, centerInterSectionPoints: [] }
})
notIntersectedLines.forEach((line) => {
centerLines.forEach((centerLine) => {
const interSectionPoint = calculateIntersection(line, centerLine)
if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) {
line.centerInterSectionPoints.push(interSectionPoint)
interSectionPoint.lineIdx = line.idx
centerInterSectionPoints.push(interSectionPoint)
}
})
})
// centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다.
ridgeStartPoints.forEach((point) => {
const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2)
const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2)
let closestPoint
if (xPoints.length === 0) {
closestPoint = findClosestPoint(point, yPoints)
} else if (yPoints.length === 0) {
closestPoint = findClosestPoint(point, xPoints)
}
if (closestPoint) {
const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'ridge',
direction: getDirectionByPoint(point, closestPoint),
})
line.startPoint = point
line.endPoint = closestPoint
polygon.ridges.push(line)
polygon.canvas.add(line)
ridgeEndPoints.push(closestPoint)
notIntersectedLines = notIntersectedLines.filter((line) => line.idx !== closestPoint.lineIdx)
}
})
centerInterSectionPoints = []
notIntersectedLines.forEach((line) => {
centerInterSectionPoints.push(...line.centerInterSectionPoints)
})
// ridgeEndPoints끼리 이어준다.
const remainingPoints = [...ridgeEndPoints]
// ridgeEndPoint에서 centerInterSectionPoints와 45도인 점을 찾아 이어준다.
ridgeEndPoints.forEach((ridgePoint) => {
const filteredCenterInterSectionPoints = centerInterSectionPoints.filter((centerPoint) => {
const degree = calculateAngle(ridgePoint, centerPoint)
return Math.abs(degree) === 45 || Math.abs(degree) === 135
})[0]
if (filteredCenterInterSectionPoints) {
const line = new QLine([ridgePoint.x, ridgePoint.y, filteredCenterInterSectionPoints.x, filteredCenterInterSectionPoints.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
if (line.length === 0) {
return
}
line.startPoint = ridgePoint
line.endPoint = filteredCenterInterSectionPoints
polygon.hips.push(line)
polygon.canvas.add(line)
ridgeStartPoints.push(filteredCenterInterSectionPoints)
polygon.points.forEach((point) => {
const degree = calculateAngle(ridgePoint, point)
if (Math.abs(degree) % 45 < 1) {
const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
}
})
// ridgeEndPoint끼리 연결한다.
while (remainingPoints.length > 1) {
const startPoint = remainingPoints.shift()
const endPoint = remainingPoints.shift()
if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) {
const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'connectRidge',
})
line.startPoint = startPoint
line.endPoint = endPoint
polygon.connectRidges.push(line)
polygon.points.forEach((point) => {
const degree = calculateAngle(startPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = startPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
polygon.points.forEach((point) => {
const degree = calculateAngle(endPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = endPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
polygon.canvas.add(line)
} else {
polygon.points.forEach((point) => {
const degree = calculateAngle(startPoint, point)
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
stroke: 'purple',
fontSize: polygon.fontSize,
name: 'hip',
})
line.startPoint = startPoint
line.endPoint = point
polygon.hips.push(line)
polygon.canvas.add(line)
}
})
}
}
}
export const drawCenterLines = (polygon) => {
const centerLines = []
const oneSideLines = polygon.lines.map((line) => getOneSideLine(line))
const horizontalLines = oneSideLines.filter((line) => line.direction === 'right')
const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom')
// horizontalLines 를 y1 좌표 기준으로 정렬한다.
horizontalLines.sort((a, b) => a.y1 - b.y1)
// verticalLines 를 x1 좌표 기준으로 정렬한다.
verticalLines.sort((a, b) => a.x1 - b.x1)
let maxHorizontalLineLength = 0
let maxVerticalLineLength = 0
// 모든 가로선의 중심선을 긋는다.
horizontalLines.forEach((line, index) => {
const nextLine = horizontalLines[(index + 1) % horizontalLines.length]
polygon.canvas.renderAll()
const startCenterX = Math.min(line.x1, nextLine.x1)
const startCenterY = (line.y1 + nextLine.y1) / 2
const endCenterX = line.x2 > nextLine.x2 ? line.x2 : nextLine.x2
const endCenterY = startCenterY
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
fontSize: polygon.fontSize,
stroke: 'red',
strokeWidth: 1,
direction: 'horizontal',
})
centerLines.push(centerLine)
})
// 모든 세로선의 중심선을 긋는다.
verticalLines.forEach((line, index) => {
const nextLine = verticalLines[(index + 1) % verticalLines.length]
const startCenterX = (line.x1 + nextLine.x1) / 2
const startCenterY = Math.min(line.y1, nextLine.y1)
const endCenterX = startCenterX
let endCenterY = line.y2 > nextLine.y2 ? line.y2 : nextLine.y2
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
fontSize: polygon.fontSize,
stroke: 'blue',
strokeWidth: 1,
direction: 'vertical',
})
centerLines.push(centerLine)
})
return centerLines
}
const getOneSideLines = (polygon) => {
return [...polygon.lines].map((line) => {
let newX1, newY1, newX2, newY2
if (line.direction === 'top') {
newX1 = line.x2
newY1 = line.y2
newX2 = line.x1
newY2 = line.y1
line.x1 = newX1
line.y1 = newY1
line.x2 = newX2
line.y2 = newY2
line.direction = 'bottom'
line.startPoint = { x: newX1, y: newY1 }
line.endPoint = { x: newX2, y: newY2 }
} else if (line.direction === 'left') {
newX1 = line.x2
newY1 = line.y2
newX2 = line.x1
newY2 = line.y1
line.x1 = newX1
line.y1 = newY1
line.x2 = newX2
line.y2 = newY2
line.direction = 'right'
line.startPoint = { x: newX1, y: newY1 }
line.endPoint = { x: newX2, y: newY2 }
}
return line
})
}
export const calculateAngle = (point1, point2) => {
const deltaX = point2.x - point1.x
const deltaY = point2.y - point1.y
const angleInRadians = Math.atan2(deltaY, deltaX)
return angleInRadians * (180 / Math.PI)
}
/**
* 3개의 점을 이용해 직각 이등변 삼각형인지 확인
* @param point1
* @param point2
* @param point3
* @returns {boolean}
*/
const isRightIsoscelesTriangle = (point1, point2, point3) => {
const distance = (p1, p2) => Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))
const d1 = distance(point1, point2)
const d2 = distance(point2, point3)
const d3 = distance(point3, point1)
const distances = [d1, d2, d3].sort((a, b) => a - b)
// Check if the two smaller distances are equal and the largest distance is the hypotenuse
return distances[0] === distances[1] && Math.abs(Math.pow(distances[0], 2) * 2 - Math.pow(distances[2], 2)) < 1
}
/**
* 세개의 점으로 삼각형의 넓이를 구한다.
* @param point1
* @param point2
* @param point3
* @returns {number}
*/
const calculateTriangleArea = (point1, point2, point3) => {
const { x: x1, y: y1 } = point1
const { x: x2, y: y2 } = point2
const { x: x3, y: y3 } = point3
return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2
}
// polygon을 나눈다.
export const dividePolygon = (polygon) => {
let hips = polygon.hips
const ridges = polygon.ridges.map((ridge) => getOneSideLine(ridge))
const connectRidges = polygon.connectRidges
const polygonLines = polygon.lines
hips.forEach((hip) => {
// hips의 startPoint와 endPoint를 polygon의 points와 비교하여 같은 점이 endPoint일 경우 startPoint로 변경한다.
const startPoint = polygon.points.find((point) => point.x === hip.endPoint.x && point.y === hip.endPoint.y)
if (startPoint) {
const temp = hip.startPoint
hip.startPoint = hip.endPoint
hip.endPoint = temp
}
})
hips = [...hips, ...connectRidges]
polygonLines.forEach((line, index) => {
let ridge
const startPoint = line.startPoint
const endPoint = line.endPoint
let polygonPoints = []
polygonPoints.push(startPoint)
polygonPoints.push(endPoint)
const startHip = hips.find((hip) => hip.startPoint.x === startPoint.x && hip.startPoint.y === startPoint.y)
const endHip = hips.find((hip) => hip.startPoint.x === endPoint.x && hip.startPoint.y === endPoint.y)
if (!startHip || !endHip) {
return
}
if (startHip && endHip && startHip.endPoint.x === endHip.endPoint.x && startHip.endPoint.y === endHip.endPoint.y) {
polygonPoints.push(startHip.endPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
return
}
let connectedRidge
const restRidgeConnection = connectRidges[0]
if (!restRidgeConnection || restRidgeConnection.length === 0) {
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === startHip.endPoint.x &&
ridge.startPoint.y === startHip.endPoint.y &&
ridge.endPoint.x === endHip.endPoint.x &&
ridge.endPoint.y === endHip.endPoint.y) ||
(ridge.startPoint.x === endHip.endPoint.x &&
ridge.startPoint.y === endHip.endPoint.y &&
ridge.endPoint.x === startHip.endPoint.x &&
ridge.endPoint.y === startHip.endPoint.y),
)
} else {
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
(ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
)
}
const hipStartPoint = startHip.endPoint
const hipEndPoint = endHip.endPoint
if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) {
if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) {
polygonPoints.push(connectedRidge.endPoint)
polygonPoints.push(connectedRidge.startPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
return
}
} else if (connectedRidge.endPoint.x === hipStartPoint.x && connectedRidge.endPoint.y === hipStartPoint.y) {
if (connectedRidge.startPoint.x === hipEndPoint.x && connectedRidge.startPoint.y === hipEndPoint.y) {
polygonPoints.push(connectedRidge.startPoint)
polygonPoints.push(connectedRidge.endPoint)
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
sort: true,
})
polygon.canvas.add(newPolygon)
return
}
}
// 지붕이 꺾여있는 경우
if (
(restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) ||
(restRidgeConnection.endPoint.x === startHip.endPoint.x && restRidgeConnection.endPoint.y === startHip.endPoint.y)
) {
polygonPoints = [startPoint, startHip.endPoint]
let lastPoint
if (restRidgeConnection.startPoint.x === startHip.endPoint.x && restRidgeConnection.startPoint.y === startHip.endPoint.y) {
lastPoint = restRidgeConnection.endPoint
polygonPoints.push(restRidgeConnection.endPoint)
} else {
lastPoint = restRidgeConnection.startPoint
polygonPoints.push(restRidgeConnection.startPoint)
}
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
(ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
)
if (connectedRidge.startPoint.x === lastPoint.x && connectedRidge.startPoint.y === lastPoint.y) {
polygonPoints.push(connectedRidge.endPoint)
} else {
polygonPoints.push(connectedRidge.startPoint)
}
polygonPoints.push(endPoint)
} else {
polygonPoints = [endPoint, endHip.endPoint]
let lastPoint
if (restRidgeConnection.startPoint.x === endHip.endPoint.x && restRidgeConnection.startPoint.y === endHip.endPoint.y) {
lastPoint = restRidgeConnection.endPoint
polygonPoints.push(restRidgeConnection.endPoint)
} else {
lastPoint = restRidgeConnection.startPoint
polygonPoints.push(restRidgeConnection.startPoint)
}
connectedRidge = ridges.find(
(ridge) =>
(ridge.startPoint.x === lastPoint.x && ridge.startPoint.y === lastPoint.y) ||
(ridge.endPoint.x === lastPoint.x && ridge.endPoint.y === lastPoint.y),
)
if (connectedRidge.startPoint.x === startHip.endPoint.x && connectedRidge.startPoint.y === startHip.endPoint.y) {
lastPoint = connectedRidge.startPoint
polygonPoints.push(connectedRidge.startPoint)
} else {
lastPoint = connectedRidge.endPoint
polygonPoints.push(connectedRidge.endPoint)
}
polygonPoints.push(startPoint)
}
const newPolygon = new QPolygon(polygonPoints, {
fontSize: polygon.fontSize,
parentId: polygon.id,
name: 'roof',
selectable: false,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
})
polygon.canvas.add(newPolygon)
})
}
const getOneSideLine = (line) => {
// left, top 방향의 line은 right, bottom 방향의 line으로 변경한다.
const newLine = { ...line }
let newX1, newY1, newX2, newY2
if (newLine.direction === 'top') {
newX1 = newLine.x2
newY1 = newLine.y2
newX2 = newLine.x1
newY2 = newLine.y1
newLine.x1 = newX1
newLine.y1 = newY1
newLine.x2 = newX2
newLine.y2 = newY2
newLine.direction = 'bottom'
newLine.startPoint = { x: newX1, y: newY1 }
newLine.endPoint = { x: newX2, y: newY2 }
} else if (line.direction === 'left') {
newX1 = newLine.x2
newY1 = newLine.y2
newX2 = newLine.x1
newY2 = newLine.y1
newLine.x1 = newX1
newLine.y1 = newY1
newLine.x2 = newX2
newLine.y2 = newY2
newLine.direction = 'right'
newLine.startPoint = { x: newX1, y: newY1 }
newLine.endPoint = { x: newX2, y: newY2 }
}
return newLine
}
function inwardEdgeNormal(vertex1, vertex2) {
// Assuming that polygon vertices are in clockwise order
const dx = vertex2.x - vertex1.x
const dy = vertex2.y - vertex1.y
const edgeLength = Math.sqrt(dx * dx + dy * dy)
return {
x: -dy / edgeLength,
y: dx / edgeLength,
}
}
function outwardEdgeNormal(vertex1, vertex2) {
var n = inwardEdgeNormal(vertex1, vertex2)
return {
x: -n.x,
y: -n.y,
}
}
function createPolygon(vertices) {
const edges = []
let minX = vertices.length > 0 ? vertices[0].x : undefined
let minY = vertices.length > 0 ? vertices[0].y : undefined
let maxX = minX
let maxY = minY
for (let i = 0; i < vertices.length; i++) {
const vertex1 = vertices[i]
const vertex2 = vertices[(i + 1) % vertices.length]
const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
const edge = {
vertex1,
vertex2,
index: i,
outwardNormal,
inwardNormal,
}
edges.push(edge)
const x = vertices[i].x
const y = vertices[i].y
minX = Math.min(x, minX)
minY = Math.min(y, minY)
maxX = Math.max(x, maxX)
maxY = Math.max(y, maxY)
}
return {
vertices,
edges,
minX,
minY,
maxX,
maxY,
}
}
// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
function edgesIntersection(edgeA, edgeB) {
const den =
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
(edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
if (den == 0) {
return null // lines are parallel or coincident
}
const ua =
((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
den
const ub =
((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
(edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
den
// Edges are not intersecting but the lines defined by them are
const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
return {
x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
isIntersectionOutside,
}
}
function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)
if (startAngle < 0) {
startAngle += TWO_PI
}
if (endAngle < 0) {
endAngle += TWO_PI
}
const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments
vertices.push(startVertex)
for (let i = 1; i < arcSegments; ++i) {
const angle = startAngle + angleStep * i
const vertex = {
x: center.x + Math.cos(angle) * radius,
y: center.y + Math.sin(angle) * radius,
}
vertices.push(vertex)
}
vertices.push(endVertex)
}
function createOffsetEdge(edge, dx, dy) {
return {
vertex1: {
x: edge.vertex1.x + dx,
y: edge.vertex1.y + dy,
},
vertex2: {
x: edge.vertex2.x + dx,
y: edge.vertex2.y + dy,
},
}
}
function createMarginPolygon(polygon, offset, arcSegments = 0) {
const offsetEdges = []
for (let i = 0; i < polygon.edges.length; i++) {
const edge = polygon.edges[i]
const dx = edge.outwardNormal.x * offset
const dy = edge.outwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
}
const vertices = []
for (let i = 0; i < offsetEdges.length; i++) {
const thisEdge = offsetEdges[i]
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
} else {
const arcCenter = polygon.edges[i].vertex1
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
}
}
const marginPolygon = createPolygon(vertices)
marginPolygon.offsetEdges = offsetEdges
return marginPolygon
}
function createPaddingPolygon(polygon, offset, arcSegments = 0) {
const offsetEdges = []
for (let i = 0; i < polygon.edges.length; i++) {
const edge = polygon.edges[i]
const dx = edge.inwardNormal.x * offset
const dy = edge.inwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
}
const vertices = []
for (let i = 0; i < offsetEdges.length; i++) {
const thisEdge = offsetEdges[i]
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
} else {
const arcCenter = polygon.edges[i].vertex1
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
}
}
const paddingPolygon = createPolygon(vertices)
paddingPolygon.offsetEdges = offsetEdges
return paddingPolygon
}
export default function offsetPolygon(vertices, offset) {
const polygon = createPolygon(vertices)
const arcSegments = 0
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
if (offset > 0) {
let result = createMarginPolygon(polygon, offset, arcSegments).vertices
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
if (allPointsOutside) {
return createMarginPolygon(polygon, offset, arcSegments).vertices
} else {
return createPaddingPolygon(polygon, offset, arcSegments).vertices
}
} else {
let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
if (allPointsInside) {
return createPaddingPolygon(polygon, offset, arcSegments).vertices
} else {
return createMarginPolygon(polygon, offset, arcSegments).vertices
}
}
}
/*export const splitPolygonWithLines = (polygon) => {
const roofs = []
const allLines = [...polygon.innerLines]
const polygonLines = polygon.lines
const innerLines = polygon.innerLines
allLines.forEach((line) => {
line.startPoint = { x: line.x1, y: line.y1 }
line.endPoint = { x: line.x2, y: line.y2 }
})
// allLines에 x1,y1,x2,y2를 비교해서 중복되는 값을 제거한다.
allLines.forEach((line, index) => {
const startPoint = line.startPoint
const endPoint = line.endPoint
allLines.forEach((line2, index2) => {
if (index !== index2) {
if (
(isSamePoint(startPoint, line2.startPoint) && isSamePoint(endPoint, line2.endPoint)) ||
(isSamePoint(endPoint, line2.startPoint) && isSamePoint(startPoint, line2.endPoint))
) {
allLines.splice(index2, 1)
}
}
})
})
/!**
* 좌표 테스트용
*!/
/!*allLines.forEach((line) => {
const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, {
left: line.startPoint.x,
top: line.startPoint.y,
fontSize: 15,
})
polygon.canvas.add(text)
polygon.canvas.renderAll()
const text2 = new fabric.Text(`(${line.endPoint.x},${line.endPoint.y})`, {
left: line.endPoint.x,
top: line.endPoint.y,
fontSize: 15,
})
polygon.canvas.add(text2)
polygon.canvas.renderAll()
})
polygon.points.forEach((point, index) => {
const text = new fabric.Text(`(${point.x},${point.y})`, {
left: point.x,
top: point.y,
fontSize: 15,
})
polygon.canvas.add(text)
polygon.canvas.renderAll()
})*!/
/!**
* 좌표 테스트용 끝
*!/
polygon.points.forEach((point, index) => {
allLines.forEach((line) => {
if (line.endPoint.x === point.x && line.endPoint.y === point.y) {
const temp = line.startPoint
line.startPoint = line.endPoint
line.endPoint = temp
}
})
})
polygon.points.forEach((point, index) => {
const routes = []
// 시작점은 시작 hip라인의 출발점
const startPoint = { x: Math.round(point.x), y: Math.round(point.y) }
// 도착점은 마지막 hip라인의 끝나는 점
let endPoint = polygon.points[(index + 1) % polygon.points.length]
endPoint = { x: Math.round(endPoint.x), y: Math.round(endPoint.y) }
const startLine = allLines.find((line) => line.startPoint.x === startPoint.x && line.startPoint.y === startPoint.y)
const endLine = allLines.find((line) => line.startPoint.x === endPoint.x && line.startPoint.y === endPoint.y)
const arrivalPoint = endLine.endPoint
routes.push(startLine.startPoint)
routes.push(startLine.endPoint)
//hip끼리 만나는 경우는 아무것도 안해도됨
if (!isSamePoint(startLine.endPoint, arrivalPoint)) {
// polygon line까지 추가
const allLinesCopy = [...allLines, ...polygon.lines]
// hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함
let currentPoint = startLine.endPoint
let currentLine = startLine
let movedLines = []
let subMovedLines = []
while (!isSamePoint(currentPoint, arrivalPoint)) {
// startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다.
let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint))
connectedLines = connectedLines.filter((line) => line !== currentLine)
connectedLines = connectedLines.filter((line) => !subMovedLines.includes(line))
//마지막 선이 endLine의 startPoint와 같은경우 그 전까지 movedLine을 제거한다.
const endLineMeetLineCnt = connectedLines.filter((line) => {
return isSamePoint(line.endPoint, endLine.startPoint) || isSamePoint(line.startPoint, endLine.startPoint)
}).length
if (endLineMeetLineCnt !== 0) {
movedLines.push(subMovedLines)
console.log(movedLines, index)
}
connectedLines = connectedLines.filter((line) => {
return !isSamePoint(line.endPoint, endLine.startPoint) && !isSamePoint(line.startPoint, endLine.startPoint)
})
if (connectedLines.length === 0) {
return
}
let tempPoints = []
for (let i = 0; i < connectedLines.length; i++) {
if (isSamePoint(connectedLines[i].startPoint, currentPoint)) {
tempPoints.push({ point: connectedLines[i].endPoint, index: i, line: connectedLines[i] })
} else {
tempPoints.push({ point: connectedLines[i].startPoint, index: i, line: connectedLines[i] })
}
}
//tempPoints에서 arrivalPoint와 가장 가까운 점을 찾는다.
let minDistance = Number.MAX_SAFE_INTEGER
let minIndex = 0
tempPoints.forEach((tempPoint, index) => {
const distance = Math.sqrt(Math.pow(tempPoint.point.x - arrivalPoint.x, 2) + Math.pow(tempPoint.point.y - arrivalPoint.y, 2))
if (distance < minDistance) {
minDistance = distance
minIndex = tempPoint.index
}
})
currentPoint = tempPoints[minIndex].point
currentLine = tempPoints[minIndex].line
if (currentLine !== startLine) {
subMovedLines.push(currentLine)
}
routes.push(currentPoint)
}
}
routes.push(endLine.startPoint)
roofs.push(routes)
})
// 중복 제거
roofs.forEach((roofPoint, index) => {
const samePointLengthRoofPoints = roofs.filter((roof) => roof.length === roofPoint.length && roof !== roofPoint)
samePointLengthRoofPoints.forEach((samePointRoof) => {
if (arraysHaveSamePoints(samePointRoof, roofPoint)) {
roofs.splice(roofs.indexOf(samePointRoof), 1)
}
})
})
roofs.forEach((roofPoint, index) => {
let defense, pitch
const direction = getDirectionByPoint(roofPoint[0], roofPoint[roofPoint.length - 1])
switch (direction) {
case 'top':
defense = 'east'
break
case 'right':
defense = 'south'
break
case 'bottom':
defense = 'west'
break
case 'left':
defense = 'north'
break
}
pitch = polygon.lines[index].attributes?.pitch ?? 0
const roof = new QPolygon(roofPoint, {
fontSize: polygon.fontSize,
stroke: 'black',
fill: 'transparent',
strokeWidth: 3,
name: POLYGON_TYPE.ROOF,
originX: 'center',
originY: 'center',
selectable: true,
defense: defense,
direction: defense,
pitch: pitch,
})
polygon.canvas.add(roof)
polygon.canvas.renderAll()
})
}*/
function normalizePoint(point) {
return {
x: Math.round(point.x),
y: Math.round(point.y),
}
}
function arePolygonsEqual(polygon1, polygon2) {
if (polygon1.length !== polygon2.length) return false
const normalizedPolygon1 = polygon1.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
const normalizedPolygon2 = polygon2.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index]))
}
export function removeDuplicatePolygons(polygons) {
const uniquePolygons = []
polygons.forEach((polygon) => {
const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon))
if (!isDuplicate) {
uniquePolygons.push(polygon)
}
})
return uniquePolygons
}
export const isSamePoint = (a, b) => {
return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 1 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 1
}
/**
* Calculate the angle between two lines.
* @param {Object} line1 - The first line defined by two points {x1, y1} and {x2, y2}.
* @param {Object} line2 - The second line defined by two points {x1, y1} and {x2, y2}.
* @returns {number} - The angle between the two lines in degrees.
*/
function calculateAngleBetweenLines(line1, line2) {
const { x1: x1_1, y1: y1_1, x2: x2_1, y2: y2_1 } = line1
const { x1: x1_2, y1: y1_2, x2: x2_2, y2: y2_2 } = line2
// Calculate direction vectors
const vector1 = { x: x2_1 - x1_1, y: y2_1 - y1_1 }
const vector2 = { x: x2_2 - x1_2, y: y2_2 - y1_2 }
// Calculate dot product and magnitudes
const dotProduct = vector1.x * vector2.x + vector1.y * vector2.y
const magnitude1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
const magnitude2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
// Calculate the cosine of the angle
const cosTheta = dotProduct / (magnitude1 * magnitude2)
// Calculate the angle in radians and then convert to degrees
const angleInRadians = Math.acos(cosTheta)
return (angleInRadians * 180) / Math.PI
}
/**
* 한쪽흐름 지붕
* @param roofId
* @param canvas
*/
export const drawShedRoof = (roofId, canvas) => {
const roof = canvas?.getObjects().find((object) => object.id === roofId)
const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
if (hasNonParallelLines.length > 0) {
alert('대각선이 존재합니다.')
return
}
const sheds = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
const eaves = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
const gables = roof.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.GABLE)
console.log('gable', gables)
let shedDegree = sheds[0].attributes.degree || 0
const shedChon = sheds[0].attributes.pitch || 0
if (shedDegree === 0) {
shedDegree = getDegreeByChon(shedChon)
}
const getHeight = function (adjust, degree) {
return Math.tan(degree * (Math.PI / 180)) * adjust
}
gables.forEach((gable) => {
const adjust = gable.attributes.planeSize
const height = getHeight(adjust, shedDegree)
gable.attributes.actualSize = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2)))
})
}
export const drawRidgeRoof = (roofId, canvas) => {
const roof = canvas?.getObjects().find((object) => object.id === roofId)
const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
if (hasNonParallelLines.length > 0) {
alert('대각선이 존재합니다.')
return
}
drawRidge(roof, canvas)
drawHips(roof, canvas)
connectLinePoint(roof, canvas)
modifyRidge(roof, canvas)
}
/**
* 마루가 존재하면 그린다. 마루는 지붕의 중간에 위치한다.
*
* @param roof
* @param canvas
*/
const drawRidge = (roof, canvas) => {
const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인
const roofLines = roof.lines // 지붕의 라인
let ridgeRoof = []
roofLines.forEach((currentRoof, index) => {
let prevRoof,
nextRoof,
currentWall = wallLines[index]
prevRoof = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1]
nextRoof = index === wallLines.length - 1 ? wallLines[0] : index === wallLines.length ? wallLines[1] : wallLines[index + 1]
if (prevRoof.direction !== nextRoof.direction && currentWall.length <= currentRoof.length) {
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.length })
}
})
// 지붕의 길이가 짧은 순으로 정렬
ridgeRoof.sort((a, b) => a.length - b.length)
ridgeRoof.forEach((item) => {
if (getMaxRidge(roofLines.length) > roof.ridges.length) {
let index = item.index,
beforePrevRoof,
prevRoof,
currentRoof = item.roof,
nextRoof,
afterNextRoof
let startXPoint, startYPoint, endXPoint, endYPoint
prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1]
nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1]
beforePrevRoof = index <= 1 ? roofLines[roofLines.length - 2 + index] : roofLines[index - 2]
afterNextRoof = index >= roofLines.length - 2 ? roofLines[(index + 2) % roofLines.length] : roofLines[index + 2]
const anotherRoof = roofLines.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof)
let xEqualInnerLines = anotherRoof.filter((roof) => roof.x1 === roof.x2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)), //x가 같은 내부선
yEqualInnerLines = anotherRoof.filter((roof) => roof.y1 === roof.y2 && isInnerLine(prevRoof, currentRoof, nextRoof, roof)) //y가 같은 내부선
let ridgeBaseLength = Math.round((currentRoof.length / 2) * 10) / 10, // 지붕의 기반 길이
ridgeMaxLength = Math.min(prevRoof.length, nextRoof.length), // 지붕의 최대 길이. 이전, 다음 벽 중 짧은 길이
ridgeAcrossLength = Math.round((ridgeMaxLength - currentRoof.length) * 10) / 10 // 맞은편 벽까지의 길이 - 지붕의 기반 길이
let acrossRoof = anotherRoof
.filter((roof) => {
if (roof.x1 === roof.x2) {
if ((nextRoof.direction === 'right' && roof.x1 > currentRoof.x1) || (nextRoof.direction === 'left' && roof.x1 < currentRoof.x1)) {
return roof
}
}
if (roof.y1 === roof.y2) {
if ((nextRoof.direction === 'top' && roof.y1 < currentRoof.y1) || (nextRoof.direction === 'bottom' && roof.y1 > currentRoof.y1)) {
return roof
}
}
})
.reduce((prev, current) => {
let hasBetweenRoof = false
if (current.x1 === current.x2) {
hasBetweenRoof = roofLines
.filter((roof) => roof !== current && roof !== currentRoof)
.some((line) => {
let currentY2 = currentRoof.y2
if (yEqualInnerLines.length > 0) {
yEqualInnerLines.forEach((line) => {
currentY2 = Math.abs(currentRoof.y1 - currentY2) < Math.abs(currentRoof.y1 - line.y1) ? currentY2 : line.y1
})
}
const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < currentY2) || (line.y1 > currentY2 && line.y1 < currentRoof.y1)
const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < currentY2) || (line.y2 > currentY2 && line.y2 < currentRoof.y1)
const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < current.x1) || (line.x1 > currentRoof.x1 && line.x1 < current.x1)
const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < current.x1) || (line.x2 > currentRoof.x1 && line.x2 < current.x1)
return isY1Between && isY2Between && isX1Between && isX2Between
})
}
if (current.y1 === current.y2) {
hasBetweenRoof = wallLines
.filter((roof) => roof !== current && roof !== currentRoof)
.some((line) => {
let currentX2 = currentRoof.x2
if (xEqualInnerLines.length > 0) {
xEqualInnerLines.forEach((line) => {
currentX2 = Math.abs(currentRoof.x1 - currentX2) < Math.abs(currentRoof.x1 - line.x1) ? currentX2 : line.x1
})
}
const isX1Between = (line.x1 > currentRoof.x1 && line.x1 < currentX2) || (line.x1 > currentX2 && line.x1 < currentRoof.x1)
const isX2Between = (line.x2 > currentRoof.x1 && line.x2 < currentX2) || (line.x2 > currentX2 && line.x2 < currentRoof.x1)
const isY1Between = (line.y1 > currentRoof.y1 && line.y1 < current.y1) || (line.y1 > currentRoof.y1 && line.y1 < current.y1)
const isY2Between = (line.y2 > currentRoof.y1 && line.y2 < current.y1) || (line.y2 > currentRoof.y1 && line.y2 < current.y1)
return isX1Between && isX2Between && isY1Between && isY2Between
})
}
if (prev !== undefined) {
if (currentRoof.x1 === currentRoof.x2) {
return Math.abs(currentRoof.y1 - prev.y1) > Math.abs(currentRoof.y1 - current.y1) ? prev : current
}
if (currentRoof.y1 === currentRoof.y2) {
return Math.abs(currentRoof.x1 - prev.x1) > Math.abs(currentRoof.x1 - current.x1) ? prev : current
}
} else {
if (!hasBetweenRoof) {
if (currentRoof.x1 === currentRoof.x2) {
return Math.sign(currentRoof.y1 - currentRoof.y2) !== Math.sign(current.y1 - current.y2) ? current : undefined
}
if (currentRoof.y1 === currentRoof.y2) {
return Math.sign(currentRoof.x1 - currentRoof.x2) !== Math.sign(current.x1 - current.x2) ? current : undefined
}
return undefined
} else {
return undefined
}
}
}, undefined)
if (acrossRoof !== undefined) {
if (currentRoof.x1 === currentRoof.x2) {
if (ridgeAcrossLength < Math.abs(currentRoof.x1 - acrossRoof.x1)) {
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.x1 - acrossRoof.x1) * 10) / 10 - currentRoof.length) * 10) / 10
}
}
if (currentRoof.y1 === currentRoof.y2) {
if (ridgeAcrossLength < Math.abs(currentRoof.y1 - acrossRoof.y1)) {
ridgeAcrossLength = Math.round((Math.round(Math.abs(currentRoof.y1 - acrossRoof.y1) * 10) / 10 - currentRoof.length) * 10) / 10
}
}
}
if (ridgeBaseLength > 0 && ridgeMaxLength > 0 && ridgeAcrossLength > 0) {
let ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
if (currentRoof.x1 === currentRoof.x2) {
startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * ridgeBaseLength
startYPoint = currentRoof.y1 + (currentRoof.direction === 'top' ? -1 : 1) * ridgeBaseLength
endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength
endYPoint = startYPoint
let adjustY
if (currentRoof.direction === 'top') {
if (afterNextRoof.direction === 'bottom' && beforePrevRoof.direction === 'bottom') {
adjustY =
Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1
} else if (afterNextRoof.direction === 'bottom' && afterNextRoof.y2 > currentRoof.y2 && afterNextRoof.y2 < currentRoof.y1) {
adjustY = afterNextRoof.y2
} else if (beforePrevRoof.direction === 'bottom' && beforePrevRoof.y1 > currentRoof.y2 && beforePrevRoof.y1 < currentRoof.y1) {
adjustY = beforePrevRoof.y1
}
if (adjustY) {
startYPoint = currentRoof.y1 - Math.abs(currentRoof.y1 - adjustY) / 2
endYPoint = startYPoint
}
}
if (currentRoof.direction === 'bottom') {
if (afterNextRoof.direction === 'top' && beforePrevRoof.direction === 'top') {
adjustY =
Math.abs(currentRoof.x1 - afterNextRoof.x1) < Math.abs(currentRoof.x1 - beforePrevRoof.x1) ? afterNextRoof.y2 : beforePrevRoof.y1
} else if (afterNextRoof.direction === 'top' && afterNextRoof.y2 < currentRoof.y2 && afterNextRoof.y2 > currentRoof.y1) {
adjustY = afterNextRoof.y2
} else if (beforePrevRoof.direction === 'top' && beforePrevRoof.y1 < currentRoof.y2 && beforePrevRoof.y1 > currentRoof.y1) {
adjustY = beforePrevRoof.y1
}
if (adjustY) {
startYPoint = currentRoof.y1 + Math.abs(currentRoof.y1 - adjustY) / 2
endYPoint = startYPoint
}
}
if (yEqualInnerLines.length > 0) {
yEqualInnerLines.reduce((prev, current) => {
if (prev !== undefined) {
return Math.abs(currentRoof.y1 - prev.y1) < Math.abs(currentRoof.y1 - current.y1) ? prev : current
} else {
return current
}
}, undefined)
startYPoint =
Math.abs(currentRoof.y1 - startYPoint) * 2 <= Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1)
? startYPoint
: Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1)
endYPoint = startYPoint
ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.y1 - startYPoint) * 2
if (
//yEqualInnerLines 이 다음 벽보다 안쪽에 있을때
Math.abs(currentRoof.y1 - yEqualInnerLines[0].y1) <= Math.abs(currentRoof.y1 - nextRoof.y1) &&
Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) >= Math.abs(currentRoof.x1 - nextRoof.x2)
) {
ridgeMaxLength = Math.round(Math.abs(currentRoof.x1 - yEqualInnerLines[0].x2) * 10) / 10
}
ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
startXPoint = currentRoof.x1 + (nextRoof.direction === 'right' ? 1 : -1) * Math.abs(currentRoof.y1 - startYPoint)
endXPoint = startXPoint + (nextRoof.direction === 'right' ? 1 : -1) * ridgeLength
}
}
if (currentRoof.y1 === currentRoof.y2) {
startXPoint = currentRoof.x1 + (currentRoof.direction === 'left' ? -1 : 1) * ridgeBaseLength
startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeBaseLength
endXPoint = startXPoint
endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength
let adjustX
if (currentRoof.direction === 'right') {
if (afterNextRoof.direction === 'left' && beforePrevRoof.direction === 'left') {
adjustX =
Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1
} else if (afterNextRoof.direction === 'left' && afterNextRoof.x2 < currentRoof.x2 && afterNextRoof.x2 > currentRoof.x1) {
adjustX = afterNextRoof.x2
} else if (beforePrevRoof.direction === 'left' && beforePrevRoof.x1 < currentRoof.x2 && beforePrevRoof.x1 > currentRoof.x1) {
adjustX = beforePrevRoof.x1
}
if (adjustX) {
startXPoint = currentRoof.x1 + Math.abs(currentRoof.x1 - adjustX) / 2
endXPoint = startXPoint
}
}
if (currentRoof.direction === 'left') {
if (afterNextRoof.direction === 'right' && beforePrevRoof.direction === 'right') {
adjustX =
Math.abs(currentRoof.y1 - afterNextRoof.y1) < Math.abs(currentRoof.y1 - beforePrevRoof.y1) ? afterNextRoof.x2 : beforePrevRoof.x1
} else if (afterNextRoof.direction === 'right' && afterNextRoof.x2 > currentRoof.x2 && afterNextRoof.x2 < currentRoof.x1) {
adjustX = afterNextRoof.x2
} else if (beforePrevRoof.direction === 'right' && beforePrevRoof.x1 > currentRoof.x2 && beforePrevRoof.x1 < currentRoof.x1) {
adjustX = beforePrevRoof.x1
}
if (adjustX) {
startXPoint = currentRoof.x1 - Math.abs(currentRoof.x1 - adjustX) / 2
endXPoint = startXPoint
}
}
if (xEqualInnerLines.length > 0) {
xEqualInnerLines.reduce((prev, current) => {
if (prev !== undefined) {
return Math.abs(currentRoof.x1 - prev.x1) < Math.abs(currentRoof.x1 - current.x1) ? prev : current
} else {
return current
}
}, undefined)
startXPoint =
Math.abs(currentRoof.x1 - startXPoint) * 2 <= Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1)
? startXPoint
: Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1)
endXPoint = startXPoint
ridgeAcrossLength = Math.max(prevRoof.length, nextRoof.length) - Math.abs(currentRoof.x1 - startXPoint) * 2
if (
//xEqualInnerLines 이 다음 벽보다 안쪽에 있을때
Math.abs(currentRoof.x1 - xEqualInnerLines[0].x1) <= Math.abs(currentRoof.x1 - nextRoof.x1) &&
Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) >= Math.abs(currentRoof.y1 - nextRoof.y2)
) {
ridgeMaxLength = Math.round(Math.abs(currentRoof.y1 - xEqualInnerLines[0].y2) * 10) / 10
}
ridgeLength = Math.min(ridgeMaxLength, ridgeAcrossLength)
startYPoint = currentRoof.y1 + (nextRoof.direction === 'bottom' ? 1 : -1) * Math.abs(currentRoof.x1 - startXPoint)
endYPoint = startYPoint + (nextRoof.direction === 'bottom' ? 1 : -1) * ridgeLength
}
}
}
// 마루 그리기
if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) {
startXPoint = Math.round(startXPoint * 10) / 10
startYPoint = Math.round(startYPoint * 10) / 10
endXPoint = Math.round(endXPoint * 10) / 10
endYPoint = Math.round(endYPoint * 10) / 10
const ridge = new QLine(
[Math.min(startXPoint, endXPoint), Math.min(startYPoint, endYPoint), Math.max(startXPoint, endXPoint), Math.max(startYPoint, endYPoint)],
{
fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.RIDGE,
attributes: { roofId: roof.id },
},
)
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
canvas.add(ridge)
roof.ridges.push(ridge)
roof.innerLines.push(ridge)
const distance = (x1, y1, x2, y2) => Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
const dist1 = distance(startXPoint, startYPoint, currentRoof.x1, currentRoof.y1)
const dist2 = distance(endXPoint, endYPoint, currentRoof.x1, currentRoof.y1)
currentRoof.attributes.ridgeCoordinate = {
x1: dist1 < dist2 ? startXPoint : endXPoint,
y1: dist1 < dist2 ? startYPoint : endYPoint,
}
}
}
})
//겹쳐지는 마루는 하나로 합침
roof.ridges.forEach((ridge, index) => {
roof.ridges
.filter((ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2))
.forEach((ridge2) => {
let overlap = segmentsOverlap(ridge, ridge2)
if (overlap) {
let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
const newRidge = new QLine([x1, y1, x2, y2], {
fontSize: roof.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.RIDGE,
attributes: { roofId: roof.id },
})
roof.canvas.remove(ridge)
roof.canvas.remove(ridge2)
roof.ridges = roof.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
roof.ridges = roof.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
roof.innerLines = roof.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
roof.innerLines = roof.innerLines.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
newRidge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
newRidge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
canvas.add(newRidge)
roof.ridges.push(newRidge)
roof.innerLines.push(newRidge)
}
})
})
canvas?.renderAll()
}
/**
* line 이 세 라인 사이에 존재하는지 확인한다.
* @param prevLine
* @param currentLine
* @param nextLine
* @param line
*/
const isInnerLine = (prevLine, currentLine, nextLine, line) => {
let inside = false
let minX = Math.min(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2)
let maxX = Math.max(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2)
let minY = Math.min(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2)
let maxY = Math.max(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2)
if (minX < line.x1 && line.x1 < maxX && minY < line.y1 && line.y1 < maxY && minX < line.x2 && line.x2 < maxX && minY < line.y2 && line.y2 < maxY) {
inside = true
}
return inside
}
/**
* 두 선분이 겹치는지 확인
* @param line1
* @param line2
* @returns {boolean}
*/
const segmentsOverlap = (line1, line2) => {
if (line1.y1 === line1.y2 && line2.y1 === line2.y2 && line1.y1 === line2.y1) {
if ((line1.x1 <= line2.x1 && line1.x2 >= line2.x1) || (line1.x1 <= line2.x2 && line1.x2 >= line2.x2)) {
return true
}
}
if (line1.x1 === line1.x2 && line2.x1 === line2.x2 && line1.x1 === line2.x1) {
if ((line1.y1 <= line2.y1 && line1.y2 >= line2.y1) || (line1.y1 <= line2.y2 && line1.y2 >= line2.y2)) {
return true
}
}
return false
}
/**
* 추녀마루를 그린다.
* @param roof
* @param canvas
*/
const drawHips = (roof, canvas) => {
const roofLines = roof.lines
const ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roof.id)
//마루에서 시작되는 hip을 먼저 그립니다.
roofLines
.filter((roof) => roof.attributes.type === LINE_TYPE.WALLLINE.EAVES && roof.attributes.ridgeCoordinate !== undefined)
.forEach((currentRoof, index) => {
const prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
const nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
const ridgeCoordinate = currentRoof.attributes.ridgeCoordinate
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
})
canvas.add(hip1)
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - currentDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
if (prevDegree === currentDegree) {
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
}
roof.hips.push(hip1)
roof.innerLines.push(hip1)
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
})
canvas.add(hip2)
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - currentDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
if (nextDegree === currentDegree) {
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
}
roof.hips.push(hip2)
roof.innerLines.push(hip2)
})
const hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roof.id)
//마루에서 시작되지 않는 hip을 그립니다.
roofLines
.filter((roof) => {
let isHip = false
if (hipLines.some((hip) => hip.x1 === roof.x1 && hip.y1 === roof.y1)) {
isHip = true
}
return !isHip
})
.forEach((currentRoof) => {
let prevRoof
roofLines.forEach((roof, index) => {
if (roof === currentRoof) {
prevRoof = index === 0 ? roofLines[roofLines.length - 1] : roofLines[index - 1]
}
})
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
let ridgePoints = []
ridgeLines.forEach((ridge) => {
const deltaX1 = Math.round((ridge.x1 - currentRoof.x1) * 10) / 10
const deltaY1 = Math.round((ridge.y1 - currentRoof.y1) * 10) / 10
const deltaX2 = Math.round((ridge.x2 - currentRoof.x1) * 10) / 10
const deltaY2 = Math.round((ridge.y2 - currentRoof.y1) * 10) / 10
if (Math.round(Math.abs(deltaY1 / deltaX1) * 10) / 10 === 1) {
ridgePoints.push({ x: ridge.x1, y: ridge.y1 })
}
if (Math.round(Math.abs(deltaY2 / deltaX2) * 10) / 10 === 1) {
ridgePoints.push({ x: ridge.x2, y: ridge.y2 })
}
})
ridgePoints = ridgePoints.reduce((prev, current) => {
if (prev !== undefined) {
const deltaPrevX = Math.abs(prev.x - currentRoof.x1)
const deltaPrevY = Math.abs(prev.y - currentRoof.y1)
const deltaCurrentX = Math.abs(current.x - currentRoof.x1)
const deltaCurrentY = Math.abs(current.y - currentRoof.y1)
if (deltaPrevX < deltaCurrentX && deltaPrevY < deltaCurrentY) {
return prev
} else {
return current
}
} else {
return current
}
}, undefined)
if (ridgePoints !== undefined) {
const hip = new QLine([currentRoof.x1, currentRoof.y1, ridgePoints.x, ridgePoints.y], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 },
})
canvas.add(hip)
const hipBase = ((Math.abs(hip.x1 - hip.x2) + Math.abs(hip.y1 - hip.y2)) / 2) * 10
const hipHeight = Math.round(hipBase / Math.tan(((90 - currentRoof.attributes.degree) * Math.PI) / 180))
hip.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip.x1 - hip.x2, 2) + Math.pow(hip.y1 - hip.y2, 2))) * 10
if (prevDegree === currentDegree) {
hip.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip.attributes.planeSize, 2) + Math.pow(hipHeight, 2)))
}
roof.hips.push(hip)
roof.innerLines.push(hip)
}
})
canvas?.renderAll()
}
/**
* 라인 사이가 지붕골 인지 확인.
* @param polygon
* @param line1
* @param line2
* @returns {boolean}
*/
const checkValley = (polygon, line1, line2) => {
let points = [
{ x: line1.x1, y: line1.y1 },
{ x: line1.x2, y: line1.y2 },
{ x: line2.x1, y: line2.y1 },
{ x: line2.x2, y: line2.y2 },
]
const uniquePointsMap = new Map()
points.forEach((point) => {
const key = `${point.x},${point.y}`
if (!uniquePointsMap.has(key)) {
uniquePointsMap.set(key, point)
}
})
points = Array.from(uniquePointsMap.values())
const centroidX = points.reduce((acc, point) => acc + point.x, 0) / points.length
const centroidY = points.reduce((acc, point) => acc + point.y, 0) / points.length
let isValley = false
const pPoints = polygon.points
pPoints.forEach((point, index) => {
let j = (index + 1) % pPoints.length
let xi = pPoints[index].x + polygon.left,
yi = pPoints[index].y + polygon.top
let xj = pPoints[j].x + polygon.left,
yj = pPoints[j].y + polygon.top
let intersect = yi > centroidY !== yj > centroidY && centroidX < ((xj - xi) * (centroidY - yi)) / (yj - yi) + xi
if (intersect) isValley = !isValley
})
return isValley
}
/*
3개 이상 이어지지 않은 라인 포인트 계산
모임지붕에서 point는 3개 이상의 라인과 접해야 함.
*/
const connectLinePoint = (polygon) => {
// 연결되지 않은 모든 라인의 포인트를 구한다.
let missedPoints = []
//마루
polygon.ridges.forEach((ridge) => {
if (ridge.x1 === ridge.x2) {
if (
polygon.lines
.filter((roof) => roof.y1 === roof.y2)
.filter((roof) => roof.y1 === ridge.y1 || roof.y1 === ridge.y2 || roof.y2 === ridge.y1 || roof.y2 === ridge.y2).length > 0
) {
return
}
}
if (ridge.y1 === ridge.y2) {
if (
polygon.lines
.filter((roof) => roof.x1 === roof.x2)
.filter((roof) => roof.x1 === ridge.x1 || roof.x1 === ridge.x2 || roof.x2 === ridge.x1 || roof.x2 === ridge.x2).length > 0
) {
return
}
}
if (polygon.hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1).length < 2) {
missedPoints.push({ x: ridge.x1, y: ridge.y1 })
}
if (polygon.hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2).length < 2) {
missedPoints.push({ x: ridge.x2, y: ridge.y2 })
}
})
//추녀마루
polygon.hips.forEach((hip) => {
let count = 0
count += polygon.ridges.filter((ridge) => (ridge.x1 === hip.x2 && ridge.y1 === hip.y2) || (ridge.x2 === hip.x2 && ridge.y2 === hip.y2)).length
count += polygon.hips.filter((hip2) => (hip2.x1 === hip.x2 && hip2.y1 === hip.y2) || (hip2.x2 === hip.x2 && hip2.y2 === hip.y2)).length
if (count < 3) {
missedPoints.push({ x: hip.x2, y: hip.y2 })
}
})
let missedLine = []
//중복포인트제거
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
missedPoints.forEach((p1) => {
let p2 = missedPoints
.filter((p) => p.x !== p1.x && p.y !== p1.y)
.reduce((prev, current) => {
if (prev !== undefined) {
return Math.sqrt(Math.pow(Math.abs(current.x - p1.x), 2) + Math.pow(Math.abs(current.y - p1.y), 2)) <
Math.sqrt(Math.pow(Math.abs(prev.x - p1.x), 2) + Math.pow(Math.abs(prev.y - p1.y), 2))
? current
: prev
} else {
return current
}
}, undefined)
if (p2 !== undefined) {
if (p1.x < p2.x && p1.y < p2.y) {
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
}
if (p1.x > p2.x && p1.y < p2.y) {
missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y })
}
if (p1.x > p2.x && p1.y > p2.y) {
missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y })
}
if (p1.x < p2.x && p1.y > p2.y) {
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
}
}
})
//중복라인제거
missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
missedLine.forEach((p) => {
const line = new QLine([p.x1, p.y1, p.x2, p.y2], {
attributes: { roofId: polygon.id },
fontSize: polygon.fontSize,
stroke: 'purple',
strokeWidth: 1,
})
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10) / 10
polygon.canvas.add(line)
polygon.innerLines.push(line)
})
missedPoints = []
missedLine = []
polygon.innerLines.forEach((line) => {
if (
polygon.innerLines.filter(
(innerLine) => (line.x2 === innerLine.x1 && line.y2 === innerLine.y1) || (line.x2 === innerLine.x2 && line.y2 === innerLine.y2),
).length < 3
) {
missedPoints.push({ x: line.x2, y: line.y2 })
}
})
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
missedPoints.forEach((p1) => {
let p2 = missedPoints
.filter((p) => !(p.x === p1.x && p.y === p1.y))
.reduce((prev, current) => {
if (prev !== undefined) {
return Math.abs(current.x - p1.x) + Math.abs(current.y - p1.y) < Math.abs(prev.x - p1.x) + Math.abs(prev.y - p1.y) ? current : prev
} else {
return current
}
}, undefined)
if (p2 !== undefined) {
if (p1.x === p2.x && p1.y < p2.y) {
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
}
if (p1.x === p2.x && p1.y > p2.y) {
missedLine.push({ x1: p1.x, y1: p2.y, x2: p2.x, y2: p1.y })
}
if (p1.x < p2.x && p1.y === p2.y) {
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
}
if (p1.x > p2.x && p1.y === p2.y) {
missedLine.push({ x1: p2.x, y1: p1.y, x2: p1.x, y2: p2.y })
}
}
})
//중복라인제거
missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
missedLine.forEach((p) => {
const line = new QLine([p.x1, p.y1, p.x2, p.y2], {
attributes: { roofId: polygon.id },
fontSize: polygon.fontSize,
stroke: 'purple',
strokeWidth: 1,
})
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
polygon.canvas.add(line)
polygon.innerLines.push(line)
})
//마지막으로 연결되지 않고 떨어져있는 마루를 확인한다.
let missedRidge = []
polygon.ridges.forEach((ridge) => {
let lineCheck1 = polygon.innerLines.filter((line) => {
if (
!(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) &&
((line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1))
) {
return line
}
})
let lineCheck2 = polygon.innerLines.filter((line) => {
if (
!(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) &&
((line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
) {
return line
}
})
if (lineCheck1.length === 0 || lineCheck2.length === 0) {
missedRidge.push(ridge)
}
})
missedRidge.forEach((ridge) => {
let missedRidge2 = missedRidge.filter(
(ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2),
)
missedRidge2.forEach((ridge2) => {
let overlap = false
if (ridge.x1 === ridge.x2 && ridge2.x1 === ridge2.x2 && ridge2.x1 === ridge.x1) {
overlap = true
}
if (ridge.y1 === ridge.y2 && ridge2.y1 === ridge2.y2 && ridge2.y1 === ridge.y1) {
overlap = true
}
if (overlap) {
let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
const newRidge = new QLine([x1, y1, x2, y2], {
fontSize: polygon.fontSize,
stroke: 'blue',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.RIDGE,
attributes: { roofId: polygon.id },
})
if (polygon.ridges.filter((r) => newRidge.x1 === r.x1 && newRidge.y1 === r.y1 && newRidge.x2 === r.x2 && newRidge.y2 === r.y2).length === 0) {
polygon.canvas.remove(ridge)
polygon.canvas.remove(ridge2)
polygon.ridges = polygon.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
polygon.ridges = polygon.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
polygon.innerLines = polygon.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
polygon.innerLines = polygon.innerLines.filter(
(r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2),
)
polygon.canvas.add(newRidge)
polygon.ridges.push(newRidge)
polygon.innerLines.push(newRidge)
}
}
})
})
polygon.canvas.renderAll()
}
const modifyRidge = (roof, canvas) => {
const ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roof.id)
const hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roof.id)
ridgeLines.forEach((ridge) => {
let ridgeHip1 = hipLines.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1)
let ridgeHip2 = hipLines.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2)
if (ridgeHip1.length >= 2) {
let currentRoof = roof.lines
.filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined)
.find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x1 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y1)
if (currentRoof === undefined) {
currentRoof = roof.lines.find(
(roofLine) =>
(roofLine.x1 === ridgeHip1[0].x1 &&
roofLine.y1 === ridgeHip1[0].y1 &&
roofLine.x2 === ridgeHip1[1].x1 &&
roofLine.y2 === ridgeHip1[1].y1) ||
(roofLine.x1 === ridgeHip1[1].x1 &&
roofLine.y1 === ridgeHip1[1].y1 &&
roofLine.x2 === ridgeHip1[0].x1 &&
roofLine.y2 === ridgeHip1[0].y1),
)
if (currentRoof !== undefined) {
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
}
}
if (currentRoof !== undefined) {
switch (currentRoof.attributes.type) {
case LINE_TYPE.WALLLINE.EAVES:
changeEavesRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.GABLE:
changeGableRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.HIPANDGABLE:
changeHipAndGableRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.JERKINHEAD:
changeJerkInHeadRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.WALL:
changeWallRoof(currentRoof, canvas)
break
}
}
}
if (ridgeHip2.length >= 2) {
let currentRoof = roof.lines
.filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined)
.find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x2 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y2)
if (currentRoof === undefined) {
currentRoof = roof.lines.find(
(roofLine) =>
(roofLine.x1 === ridgeHip2[0].x1 &&
roofLine.y1 === ridgeHip2[0].y1 &&
roofLine.x2 === ridgeHip2[1].x1 &&
roofLine.y2 === ridgeHip2[1].y1) ||
(roofLine.x1 === ridgeHip2[1].x1 &&
roofLine.y1 === ridgeHip2[1].y1 &&
roofLine.x2 === ridgeHip2[0].x1 &&
roofLine.y2 === ridgeHip2[0].y1),
)
if (currentRoof !== undefined) {
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
}
}
if (currentRoof !== undefined) {
switch (currentRoof.attributes.type) {
case LINE_TYPE.WALLLINE.EAVES:
changeEavesRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.GABLE:
changeGableRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.HIPANDGABLE:
changeHipAndGableRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.JERKINHEAD:
changeJerkInHeadRoof(currentRoof, canvas)
break
case LINE_TYPE.WALLLINE.WALL:
changeWallRoof(currentRoof, canvas)
break
}
}
}
})
}
/*
최대 생성 마루 갯수
*/
const getMaxRidge = (length) => {
return (length - 4) / 2 + 1
}
/**
* 처마지붕으로 변경
* @param currentRoof
* @param canvas
*/
const changeEavesRoof = (currentRoof, canvas) => {
if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
const roofId = currentRoof.attributes.roofId
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
if (wallLine.length > 0) {
wallLine = wallLine[0]
}
let prevRoof, nextRoof
roof.lines.forEach((r, index) => {
if (r.id === currentRoof.id) {
currentRoof = r
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
}
})
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심
const midWallY = (wallLine.y1 + wallLine.y2) / 2 // 벽의 Y 중심
const alpha = midX - midWallX // 벽과 지붕의 X 거리
const beta = midY - midWallY // 벽과 지붕의 Y 거리
const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리
const addHipX2 = Math.sign(midX - midWallX) * (alpha / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 X 너비
const addHipY2 = Math.sign(midY - midWallY) * (beta / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 Y 너비
let hipX2 = 0
let hipY2 = 0
const innerLines = canvas
?.getObjects()
.filter(
(object) =>
object.attributes !== undefined &&
object.attributes.roofId === roofId &&
object.attributes.currentRoof === currentRoof.id &&
object.x1 !== undefined &&
object.x2 !== undefined,
)
innerLines
.filter(
(line) =>
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
line.name !== LINE_TYPE.SUBLINE.HIP &&
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
line.name !== OUTER_LINE_TYPE.OUTER_LINE,
)
.forEach((line) => {
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
canvas?.remove(line)
})
ridgeLines = ridgeLines.filter(
(ridge) =>
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
)
hipLines = hipLines.filter(
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
)
if (hipLines === undefined || hipLines.length === 0) {
hipLines = innerLines.filter(
(line) =>
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
)
}
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
let points = []
hipLines.forEach((hip) => {
points.push({ x: hip.x1, y: hip.y1 })
points.push({ x: hip.x2, y: hip.y2 })
})
const pointSet = new Set()
const duplicatePoints = []
points.forEach((point) => {
const pointKey = `${point.x},${point.y}`
if (pointSet.has(pointKey)) {
duplicatePoints.push(point)
} else {
pointSet.add(pointKey)
}
})
ridgeLines = innerLines
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
.filter(
(r) => (r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
)
if (ridgeLines.length > 0) {
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
}
}
if (ridgeLines.length > 0) {
const ridge = ridgeLines[0]
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: midX + addHipX2,
y1: midY + addHipY2,
x2: ridge.x2,
y2: ridge.y2,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
hipX2 = midX + addHipX2
hipY2 = midY + addHipY2
}
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: ridge.x1,
y1: ridge.y1,
x2: midX - addHipX2,
y2: midY - addHipY2,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
hipX2 = midX - addHipX2
hipY2 = midY - addHipY2
}
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
}
hipLines.forEach((hip) => {
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
canvas.remove(hip)
})
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, hipX2, hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
},
})
canvas?.add(hip1)
roof.innerLines.push(hip1)
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - currentDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
if (prevDegree === currentDegree) {
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
}
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, hipX2, hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
})
canvas?.add(hip2)
roof.innerLines.push(hip2)
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - currentDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
if (currentDegree === nextDegree) {
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
}
}
}
/**
* 박공지붕으로 변경
* @param currentRoof
* @param canvas
*/
const changeGableRoof = (currentRoof, canvas) => {
if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
const roofId = currentRoof.attributes.roofId
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
const innerLines = canvas
?.getObjects()
.filter(
(object) =>
object.attributes !== undefined &&
object.attributes.roofId === roofId &&
object.attributes.currentRoof === currentRoof.id &&
object.x1 !== undefined &&
object.x2 !== undefined,
)
let prevRoof, nextRoof
roof.lines.forEach((r, index) => {
if (r.id === currentRoof.id) {
currentRoof = r
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
}
})
innerLines
.filter(
(line) =>
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
line.name !== LINE_TYPE.SUBLINE.HIP &&
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
line.name !== OUTER_LINE_TYPE.OUTER_LINE,
)
.forEach((line) => {
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
canvas?.remove(line)
})
ridgeLines = ridgeLines.filter(
(ridge) =>
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
)
hipLines = hipLines.filter(
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
)
if (hipLines === undefined || hipLines.length === 0) {
hipLines = innerLines.filter(
(line) =>
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
)
}
hipLines.forEach((hip) => {
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
canvas.remove(hip)
})
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
let points = []
hipLines.forEach((hip) => {
points.push({ x: hip.x1, y: hip.y1 })
points.push({ x: hip.x2, y: hip.y2 })
})
const pointSet = new Set()
const duplicatePoints = []
points.forEach((point) => {
const pointKey = `${point.x},${point.y}`
if (pointSet.has(pointKey)) {
duplicatePoints.push(point)
} else {
pointSet.add(pointKey)
}
})
ridgeLines = innerLines
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
.filter(
(r) => (r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
)
if (ridgeLines.length > 0) {
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
}
}
if (ridgeLines !== undefined && ridgeLines.length > 0) {
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
const ridge = ridgeLines[0]
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: midX,
y1: midY,
x2: ridge.x2,
y2: ridge.y2,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
}
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: ridge.x1,
y1: ridge.y1,
x2: midX,
y2: midY,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
}
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX, midY], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roofId,
currentRoofId: currentRoof.id,
},
})
canvas?.add(hip1)
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
roof.innerLines.push(hip1)
let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX, midY], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roofId,
currentRoofId: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
})
canvas?.add(hip2)
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
roof.innerLines.push(hip2)
canvas?.renderAll()
}
}
}
/**
* 팔작지붕으로 변경
* @param currentRoof
* @param canvas
*/
const changeHipAndGableRoof = (currentRoof, canvas) => {
if (
currentRoof.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE &&
currentRoof.attributes.width !== undefined &&
currentRoof.attributes.width > 0
) {
const roofId = currentRoof.attributes.roofId
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
if (wallLine.length > 0) {
wallLine = wallLine[0]
}
let prevRoof, nextRoof
roof.lines.forEach((r, index) => {
if (r.id === currentRoof.id) {
currentRoof = r
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
}
})
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심
const midWallY = (wallLine.y1 + wallLine.y2) / 2 // 벽의 Y 중심
const alpha = midX - midWallX // 벽과 지붕의 X 거리
const beta = midY - midWallY // 벽과 지붕의 Y 거리
const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리
const xWidth = Math.sign(midX - midWallX) * (alpha / hypotenuse) * currentRoof.attributes.width // 지붕의 X 너비
const yWidth = Math.sign(midY - midWallY) * (beta / hypotenuse) * currentRoof.attributes.width // 지붕의 Y 너비
const hipX2 = Math.sign(midX - midWallX) * (alpha / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 X 너비
const hipY2 = Math.sign(midY - midWallY) * (beta / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 Y 너비
if (Math.sqrt(Math.pow(xWidth, 2) + Math.pow(yWidth, 2)) < Math.sqrt(Math.pow(hipX2, 2) + Math.pow(hipY2, 2))) {
const innerLines = canvas
?.getObjects()
.filter(
(object) =>
object.attributes !== undefined &&
object.attributes.roofId === roofId &&
object.attributes.currentRoof === currentRoof.id &&
object.x1 !== undefined &&
object.x2 !== undefined,
)
innerLines
.filter(
(line) =>
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
line.name !== LINE_TYPE.SUBLINE.HIP &&
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
line.name !== OUTER_LINE_TYPE.OUTER_LINE,
)
.forEach((line) => {
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
canvas?.remove(line)
})
ridgeLines = ridgeLines.filter(
(ridge) =>
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
)
hipLines = hipLines.filter(
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
)
if (hipLines === undefined || hipLines.length === 0) {
hipLines = innerLines.filter(
(line) =>
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
)
}
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
let points = []
hipLines.forEach((hip) => {
points.push({ x: hip.x1, y: hip.y1 })
points.push({ x: hip.x2, y: hip.y2 })
})
const pointSet = new Set()
const duplicatePoints = []
points.forEach((point) => {
const pointKey = `${point.x},${point.y}`
if (pointSet.has(pointKey)) {
duplicatePoints.push(point)
} else {
pointSet.add(pointKey)
}
})
ridgeLines = innerLines
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
.filter(
(r) =>
(r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
)
if (ridgeLines.length > 0) {
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
}
}
hipLines.forEach((hip) => {
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
canvas.remove(hip)
})
hipLines = []
if (ridgeLines.length > 0) {
const ridge = ridgeLines[0]
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: midX + xWidth,
y1: midY + yWidth,
x2: ridge.x2,
y2: ridge.y2,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
}
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: ridge.x1,
y1: ridge.y1,
x2: midX - xWidth,
y2: midY - yWidth,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
}
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
}
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX + hipX2, midY + hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoof: currentRoof.id,
},
})
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
canvas?.add(hip1)
roof.innerLines.push(hip1)
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX + hipX2, midY + hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoof: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
})
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
canvas?.add(hip2)
roof.innerLines.push(hip2)
hipLines.push(hip1)
hipLines.push(hip2)
hipLines.forEach((hip) => {
const singHipX = Math.sign(hip.x1 - midWallX)
const singHipY = Math.sign(hip.y1 - midWallY)
hip.set({
x1: hip.x1,
y1: hip.y1,
x2: hip.x1 - singHipX * currentRoof.attributes.width,
y2: hip.y1 - singHipY * currentRoof.attributes.width,
})
})
hipLines.forEach((hip, i) => {
const gableLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.GABLE,
attributes: {
roofId: roof.id,
currentRoof: currentRoof.id,
actualSize: 0,
},
})
const gableDegree = i === 0 ? prevDegree : nextDegree
const gableBase = ((Math.abs(gableLine.x1 - gableLine.x2) + Math.abs(gableLine.y1 - gableLine.y2)) / 2) * 10
const gableHeight = Math.round(gableBase / Math.tan(((90 - gableDegree) * Math.PI) / 180))
gableLine.attributes.planeSize =
Math.round(Math.sqrt(Math.pow(gableLine.x1 - gableLine.x2, 2) + Math.pow(gableLine.y1 - gableLine.y2, 2))) * 10
gableLine.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gableLine.attributes.planeSize, 2) + Math.pow(gableHeight, 2)))
canvas?.add(gableLine)
roof.innerLines.push(gableLine)
})
}
}
canvas?.renderAll()
}
/**
* 반절처 지붕으로 변경
* @param currentRoof
* @param canvas
*/
const changeJerkInHeadRoof = (currentRoof, canvas) => {
if (
currentRoof.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD &&
currentRoof.attributes.width !== undefined &&
currentRoof.attributes.width > 0
) {
const roofId = currentRoof.attributes.roofId
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
if (wallLine.length > 0) {
wallLine = wallLine[0]
}
let prevRoof, nextRoof
roof.lines.forEach((r, index) => {
if (r.id === currentRoof.id) {
currentRoof = r
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
}
})
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
const midX = (currentRoof.x1 + currentRoof.x2) / 2 // 지붕의 X 중심
const midY = (currentRoof.y1 + currentRoof.y2) / 2 // 지붕의 Y 중심
const midWallX = (wallLine.x1 + wallLine.x2) / 2 // 벽의 X 중심
const midWallY = (wallLine.y1 + wallLine.y2) / 2 // 벽의 Y 중심
const alpha = midX - midWallX // 벽과 지붕의 X 거리
const beta = midY - midWallY // 벽과 지붕의 Y 거리
const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리
const xWidth = Math.sign(midX - midWallX) * (alpha / hypotenuse) * (currentRoof.attributes.width / 2) // 지붕의 X 너비
const yWidth = Math.sign(midY - midWallY) * (beta / hypotenuse) * (currentRoof.attributes.width / 2) // 지붕의 Y 너비
const addHipX2 = Math.sign(midX - midWallX) * (alpha / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 X 너비
const addHipY2 = Math.sign(midY - midWallY) * (beta / hypotenuse) * (currentRoof.length / 2) // 추녀마루의 Y 너비
let hipX2 = 0
let hipY2 = 0
if (Math.sqrt(Math.pow(xWidth, 2) + Math.pow(yWidth, 2)) < Math.sqrt(Math.pow(addHipX2, 2) + Math.pow(addHipY2, 2))) {
// reDrawPolygon(roof, canvas)
const innerLines = canvas
?.getObjects()
.filter(
(object) =>
object.attributes !== undefined &&
object.attributes.roofId === roofId &&
object.attributes.currentRoof === currentRoof.id &&
object.x1 !== undefined &&
object.x2 !== undefined,
)
innerLines
.filter(
(line) =>
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
line.name !== LINE_TYPE.SUBLINE.HIP &&
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
line.name !== OUTER_LINE_TYPE.OUTER_LINE,
)
.forEach((line) => {
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
canvas?.remove(line)
})
ridgeLines = ridgeLines.filter(
(ridge) =>
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
)
hipLines = hipLines.filter(
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
)
if (hipLines === undefined || hipLines.length === 0) {
hipLines = innerLines.filter(
(line) =>
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
)
}
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
let points = []
hipLines.forEach((hip) => {
points.push({ x: hip.x1, y: hip.y1 })
points.push({ x: hip.x2, y: hip.y2 })
})
const pointSet = new Set()
const duplicatePoints = []
points.forEach((point) => {
const pointKey = `${point.x},${point.y}`
if (pointSet.has(pointKey)) {
duplicatePoints.push(point)
} else {
pointSet.add(pointKey)
}
})
ridgeLines = innerLines
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
.filter(
(r) =>
(r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
)
if (ridgeLines.length > 0) {
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
}
}
hipLines.forEach((hip) => {
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
canvas.remove(hip)
})
if (ridgeLines.length > 0) {
const ridge = ridgeLines[0]
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: midX + xWidth,
y1: midY + yWidth,
x2: ridge.x2,
y2: ridge.y2,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
hipX2 = midX + xWidth
hipY2 = midY + yWidth
}
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
ridge.set({
x1: ridge.x1,
y1: ridge.y1,
x2: midX - xWidth,
y2: midY - yWidth,
})
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
hipX2 = midX - xWidth
hipY2 = midY - yWidth
}
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
}
let hipX1 = (Math.sign(currentRoof.x1 - midX) * currentRoof.attributes.width) / 2
let hipY1 = (Math.sign(currentRoof.y1 - midY) * currentRoof.attributes.width) / 2
const gable1 = new QLine([midX + hipX1, midY + hipY1, hipX2, hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.GABLE,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
const gableDegree = currentRoof.attributes.degree > 0 ? currentRoof.attributes.degree : getDegreeByChon(currentRoof.attributes.pitch)
const gable1Base = ((Math.abs(gable1.x1 - gable1.x2) + Math.abs(gable1.y1 - gable1.y2)) / 2) * 10
const gable1Height = Math.round(gable1Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
gable1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable1.x1 - gable1.x2, 2) + Math.pow(gable1.y1 - gable1.y2, 2))) * 10
gable1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable1.attributes.planeSize, 2) + Math.pow(gable1Height, 2)))
canvas?.add(gable1)
roof.innerLines.push(gable1)
hipX1 = (Math.sign(currentRoof.x2 - midX) * currentRoof.attributes.width) / 2
hipY1 = (Math.sign(currentRoof.y2 - midY) * currentRoof.attributes.width) / 2
const gable2 = new QLine([midX + hipX1, midY + hipY1, hipX2, hipY2], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.GABLE,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
const gable2Base = ((Math.abs(gable2.x1 - gable2.x2) + Math.abs(gable2.y1 - gable2.y2)) / 2) * 10
const gable2Height = Math.round(gable2Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
gable2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable2.x1 - gable2.x2, 2) + Math.pow(gable2.y1 - gable2.y2, 2))) * 10
gable2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable2.attributes.planeSize, 2) + Math.pow(gable2Height, 2)))
canvas?.add(gable2)
roof.innerLines.push(gable2)
const gable3 = new QLine([gable1.x1, gable1.y1, gable2.x1, gable2.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.GABLE,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
gable3.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
gable3.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
canvas?.add(gable3)
roof.innerLines.push(gable3)
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, gable1.x1, gable1.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.GABLE,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
canvas?.add(hip1)
roof.innerLines.push(hip1)
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, gable2.x1, gable2.y1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
canvas?.add(hip2)
roof.innerLines.push(hip2)
}
}
}
/**
* 벽지붕으로 변경
* @param currentRoof
* @param canvas
*/
const changeWallRoof = (currentRoof, canvas) => {
const roofId = currentRoof.attributes.roofId
const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId)
const roofLines = roof.lines
let prevRoof, nextRoof
roofLines.forEach((r, index) => {
if (r.id === currentRoof.id) {
currentRoof = r
prevRoof = roofLines[index === 0 ? roofLines.length - 1 : index - 1]
nextRoof = roofLines[index === roofLines.length - 1 ? 0 : index + 1]
}
})
const wall = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roofId)
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
if (wallLine.length > 0) {
wallLine = wallLine[0]
}
ridgeLines = ridgeLines.filter(
(ridge) =>
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
)
hipLines = hipLines.filter(
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
)
const wallMidX = (wallLine.x1 + wallLine.x2) / 2
const wallMidY = (wallLine.y1 + wallLine.y2) / 2
const roofMidX = (currentRoof.x1 + currentRoof.x2) / 2
const roofMidY = (currentRoof.y1 + currentRoof.y2) / 2
const alpha = wallMidX - roofMidX === 0 ? 0 : wallMidX - roofMidX
const beta = wallMidY - roofMidY === 0 ? 0 : wallMidY - roofMidY
currentRoof.set({
x1: currentRoof.x1 + alpha,
y1: currentRoof.y1 + beta,
x2: currentRoof.x2 + alpha,
y2: currentRoof.y2 + beta,
})
prevRoof.set({
x1: prevRoof.x1,
y1: prevRoof.y1,
x2: prevRoof.x2 + alpha,
y2: prevRoof.y2 + beta,
})
nextRoof.set({
x1: nextRoof.x1 + alpha,
y1: nextRoof.y1 + beta,
x2: nextRoof.x2,
y2: nextRoof.y2,
})
const innerLines = canvas
?.getObjects()
.filter(
(object) =>
object.attributes !== undefined &&
object.attributes.roofId === roofId &&
object.attributes.currentRoof === currentRoof.id &&
object.x1 !== undefined &&
object.x2 !== undefined,
)
innerLines
.filter(
(line) =>
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
line.name !== LINE_TYPE.SUBLINE.HIP &&
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
line.name !== OUTER_LINE_TYPE.OUTER_LINE,
)
.forEach((line) => {
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
canvas?.remove(line)
})
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
if (currentRoof.attributes.sleeve && currentRoof.attributes.width > 0 && prevRoof.attributes.offset > 0 && nextRoof.attributes.offset > 0) {
const prevSignX = Math.sign(prevRoof.x1 - prevRoof.x2)
const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2)
const nextSignX = Math.sign(nextRoof.x1 - nextRoof.x2)
const nextSignY = Math.sign(nextRoof.y1 - nextRoof.y2)
const prevWidthX = prevSignX === 0 ? 0 : prevSignX * currentRoof.attributes.width
const prevWidthY = prevSignY === 0 ? 0 : prevSignY * currentRoof.attributes.width
const nextWidthX = nextSignX === 0 ? 0 : nextSignX * currentRoof.attributes.width
const nextWidthY = nextSignY === 0 ? 0 : nextSignY * currentRoof.attributes.width
const prevX2 = prevRoof.x2 - prevWidthX
const prevY2 = prevRoof.y2 - prevWidthY
const nextX1 = nextRoof.x1 + nextWidthX
const nextY1 = nextRoof.y1 + nextWidthY
currentRoof.set({
x1: wallLine.x1,
y1: wallLine.y1,
x2: wallLine.x2,
y2: wallLine.y2,
})
prevRoof.set({
x1: prevRoof.x1,
y1: prevRoof.y1,
x2: prevX2,
y2: prevY2,
})
nextRoof.set({
x1: nextX1,
y1: nextY1,
x2: nextRoof.x2,
y2: nextRoof.y2,
})
const addPrevWallLine1 = new QLine([prevX2, prevY2, wallLine.x1 - prevWidthX, wallLine.y1 - prevWidthY], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: 'roofLine',
attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC },
})
const addPrevWallLine2 = new QLine(
[addPrevWallLine1.x2, addPrevWallLine1.y2, addPrevWallLine1.x2 + prevWidthX, addPrevWallLine1.y2 + prevWidthY],
{
fontSize: roof.fontSize,
stroke: 'cyan',
strokeWidth: 1,
name: 'roofLine',
attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC },
},
)
const addNextWallLine1 = new QLine([wallLine.x2, wallLine.y2, wallLine.x2 + nextWidthX, wallLine.y2 + nextWidthY], {
fontSize: roof.fontSize,
stroke: 'green',
strokeWidth: 1,
name: 'roofLine',
attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC },
})
const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1, nextY1], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: 'roofLine',
attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC },
})
canvas?.renderAll()
const prevIndex = roof.lines.indexOf(prevRoof) + 1
roof.lines.splice(prevIndex, 0, addPrevWallLine1, addPrevWallLine2)
const nextIndex = roof.lines.indexOf(currentRoof) + 1
roof.lines.splice(nextIndex, 0, addNextWallLine1, addNextWallLine2)
}
reDrawPolygon(roof, canvas)
if (ridgeLines.length > 0) {
const ridge = ridgeLines[0]
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
const diffX = ridge.x1 - wallMidX === 0 ? 0 : ridge.x1 - wallMidX
const diffY = ridge.y1 - wallMidY === 0 ? 0 : ridge.y1 - wallMidY
ridge.set({
x1: ridge.x1 - diffX,
y1: ridge.y1 - diffY,
x2: ridge.x2,
y2: ridge.y2,
})
}
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
const diffX = ridge.x2 - wallMidX === 0 ? 0 : ridge.x2 - wallMidX
const diffY = ridge.y2 - wallMidY === 0 ? 0 : ridge.y2 - wallMidY
ridge.set({
x1: ridge.x1,
y1: ridge.y1,
x2: ridge.x2 - diffX,
y2: ridge.y2 - diffY,
})
}
ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
ridge.attributes.actualSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX, wallMidY], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
actualSize: 0,
},
})
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX, wallMidY], {
fontSize: roof.fontSize,
stroke: 'red',
strokeWidth: 1,
name: LINE_TYPE.SUBLINE.HIP,
attributes: {
roofId: roof.id,
currentRoofId: currentRoof.id,
planeSize: currentRoof.length,
actualSize: currentRoof.length,
},
})
const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
canvas?.add(hip1)
canvas?.add(hip2)
roof.innerLines.push(hip1)
roof.innerLines.push(hip2)
}
if (hipLines.length > 0) {
hipLines.forEach((hip) => {
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
canvas?.remove(hip)
})
}
}
/**
* 지붕을 변경한다.
* @param currentRoof
* @param canvas
*/
export const changeCurrentRoof = (currentRoof, canvas) => {
const roofId = currentRoof.attributes.roofId
const originRoof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId)
const wall = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roofId)
const wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)[0]
const innerLines = canvas
?.getObjects()
.filter((object) => object.attributes !== undefined && object.attributes.roofId === roofId && object.x1 !== undefined && object.x2 !== undefined)
wallLine.attributes.type = currentRoof.attributes.type
wallLine.attributes.offset = currentRoof.attributes.offset
wallLine.attributes.width = currentRoof.attributes.width
wallLine.attributes.pitch = currentRoof.attributes.pitch
wallLine.attributes.sleeve = currentRoof.attributes.sleeve
canvas?.remove(originRoof)
innerLines.filter((line) => line.name !== OUTER_LINE_TYPE.OUTER_LINE).forEach((line) => canvas?.remove(line))
const polygon = createPolygon(wall.points)
const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
originPolygon.setViewLengthText(false)
let offsetPolygon
let result = createRoofMarginPolygon(polygon, wall.lines).vertices
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
if (allPointsOutside) {
offsetPolygon = createRoofMarginPolygon(polygon, wall.lines).vertices
} else {
offsetPolygon = createRoofPaddingPolygon(polygon, wall.lines).vertices
}
const newRoof = new QPolygon(offsetPolygon, {
fill: originRoof.fill,
stroke: originRoof.stroke,
strokeWidth: originRoof.strokeWidth,
selectable: originRoof.selectable,
fontSize: originRoof.fontSize,
})
newRoof.name = POLYGON_TYPE.ROOF
newRoof.setWall(wall)
newRoof.lines.forEach((line, index) => {
const lineLength = Math.sqrt(
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
)
line.attributes = {
roofId: newRoof.id,
planeSize: lineLength,
actualSize: lineLength,
wallLine: wall.lines[index].id,
type: wall.lines[index].attributes.type,
offset: wall.lines[index].attributes.offset,
width: wall.lines[index].attributes.width,
pitch: wall.lines[index].attributes.pitch,
sleeve: wall.lines[index].attributes.sleeve || false,
}
})
wall.attributes = {
roofId: newRoof.id,
}
wall.lines.forEach((line, index) => {
line.attributes.roofId = newRoof.id
line.attributes.currentRoof = newRoof.lines[index].id
})
canvas?.add(newRoof)
canvas?.renderAll()
newRoof.drawHelpLine()
}
/**
* 지붕을 변경한다.
* @param polygon
* @param canvas
*/
const reDrawPolygon = (polygon, canvas) => {
const lines = polygon.lines
let point = []
lines.forEach((line) => point.push({ x: line.x1, y: line.y1 }))
canvas?.remove(polygon)
const newPolygon = new QPolygon(point, {
id: polygon.id,
name: polygon.name,
fill: polygon.fill,
stroke: polygon.stroke,
strokeWidth: polygon.strokeWidth,
selectable: polygon.selectable,
fontSize: polygon.fontSize,
wall: polygon.wall !== undefined ? polygon.wall : null,
})
const newLines = newPolygon.lines
newLines.forEach((line, index) => {
lines.forEach((l, i) => {
if (line.x1 === l.x1 && line.y1 === l.y1) {
line.id = l.id
line.attributes = l.attributes
}
})
const lineLength = Math.sqrt(
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
)
if (line.attributes !== undefined) {
line.attributes.planeSize = lineLength
line.attributes.actualSize = line
} else {
line.attributes = {
roofId: newPolygon.id,
planeSize: lineLength,
actualSize: lineLength,
}
}
})
canvas?.add(newPolygon)
canvas?.renderAll()
return newPolygon
}
function createRoofMarginPolygon(polygon, lines, arcSegments = 0) {
const offsetEdges = []
polygon.edges.forEach((edge, i) => {
const offset = lines[i % lines.length].attributes.offset
const dx = edge.outwardNormal.x * offset
const dy = edge.outwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
})
const vertices = []
offsetEdges.forEach((thisEdge, i) => {
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
}
})
const marginPolygon = createPolygon(vertices)
marginPolygon.offsetEdges = offsetEdges
return marginPolygon
}
function createRoofPaddingPolygon(polygon, lines, arcSegments = 0) {
const offsetEdges = []
polygon.edges.forEach((edge, i) => {
const offset = lines[i % lines.length].attributes.offset
const dx = edge.inwardNormal.x * offset
const dy = edge.inwardNormal.y * offset
offsetEdges.push(createOffsetEdge(edge, dx, dy))
})
const vertices = []
offsetEdges.forEach((thisEdge, i) => {
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
const vertex = edgesIntersection(prevEdge, thisEdge)
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
vertices.push({
x: vertex.x,
y: vertex.y,
})
}
})
const paddingPolygon = createPolygon(vertices)
paddingPolygon.offsetEdges = offsetEdges
return paddingPolygon
}
function arePointsEqual(point1, point2) {
return Math.abs(point1.x - point2.x) <= 1 && Math.abs(point1.y - point2.y) <= 1
}
function arraysHaveSamePoints(array1, array2) {
if (array1.length !== array2.length) return false
const sortedArray1 = array1.slice().sort((a, b) => a.x - b.x || a.y - b.y)
const sortedArray2 = array2.slice().sort((a, b) => a.x - b.x || a.y - b.y)
for (let i = 0; i < sortedArray1.length; i++) {
if (!arePointsEqual(sortedArray1[i], sortedArray2[i])) {
return false
}
}
return true
}
export const toGeoJSON = (pointsArray) => {
// 객체 배열을 GeoJSON 형식의 좌표 배열로 변환
const coordinates = pointsArray.map((point) => [point.x, point.y])
// 닫힌 다각형을 만들기 위해 첫 번째 점을 마지막에 추가
coordinates.push([pointsArray[0].x, pointsArray[0].y])
return coordinates
}
export const inPolygon = (polygonPoints, rectPoints) => {
const polygonCoordinates = toGeoJSON(polygonPoints)
const rectCoordinates = toGeoJSON(rectPoints)
const polygonFeature = turf.polygon([polygonCoordinates])
const rectFeature = turf.polygon([rectCoordinates])
// 사각형의 모든 꼭짓점이 다각형 내부에 있는지 확인
const allPointsInsidePolygon = rectCoordinates.every((coord) => {
const point = turf.point(coord)
return turf.booleanPointInPolygon(point, polygonFeature)
})
// 다각형의 모든 점이 사각형 내부에 있지 않은지 확인
const noPolygonPointsInsideRect = polygonCoordinates.every((coord) => {
const point = turf.point(coord)
return !turf.booleanPointInPolygon(point, rectFeature)
})
return allPointsInsidePolygon && noPolygonPointsInsideRect
}