import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' import { getDegreeByChon } 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 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) } 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) { const 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, } } 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) { let startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x) let 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 } } } 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)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2 } /** * 박공지붕(templateA, templateB)을 그린다. * @param roofId * @param canvas */ export const drawGabledRoof = (roofId, canvas) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) const roofLines = roof.lines const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines const hasNonParallelLines = roofLines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) if (hasNonParallelLines.length > 0) { alert('대각선이 존재합니다.') return } // const roofPoints = roof.points // const minX = Math.min(...roofPoints.map((point) => point.x)) // const maxX = Math.max(...roofPoints.map((point) => point.x)) // const minY = Math.min(...roofPoints.map((point) => point.y)) // const maxY = Math.max(...roofPoints.map((point) => point.y)) // 맞은편 라인을 찾기 위해 현재 polygon 으로 만들수 있는 최대한의 길이를 구한다. // const checkLength = Math.abs(Math.sqrt(Math.pow(maxX - minX, 2) + Math.pow(maxY - minY, 2))) // 처마라인의 기본속성 입력 const eaves = [] roofLines.forEach((currentRoof, index) => { if (currentRoof.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { eaves.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize }) } }) const ridges = [] eaves.sort((a, b) => a.length - b.length) eaves.forEach((eave) => { const index = eave.index, currentRoof = eave.roof const currentWall = wallLines[index] const oppositeLine = roofLines .filter((line) => line !== currentRoof) // 현재 벽라인을 제외한 나머지 벽라인 .filter((line) => { if (currentRoof.x1 === currentRoof.x2) { const vector = Math.sign(currentRoof.y1 - currentRoof.y2) const vector2 = Math.sign(currentRoof.x1 - currentWall.x1) return line.x1 === line.x2 && Math.sign(line.y1 - line.y2) === -vector && Math.sign(currentRoof.x1 - line.x1) === vector2 } if (currentRoof.y1 === currentRoof.y2) { const vector = Math.sign(currentRoof.x1 - currentRoof.x2) const vector2 = Math.sign(currentRoof.y1 - currentWall.y1) return line.y1 === line.y2 && Math.sign(line.x1 - line.x2) === -vector && Math.sign(currentRoof.y1 - line.y1) === vector2 } }) // 현재 벽라인과 직교하는 벽라인 // 현재 벽라인과 직교하는 벽라인 사이에 마루를 그린다. oppositeLine.forEach((line) => { let points // 마루의 시작점과 끝점 if (currentRoof.x1 === currentRoof.x2) { const currentRoofYRange = [Math.min(currentRoof.y1, currentRoof.y2), Math.max(currentRoof.y1, currentRoof.y2)] const lineYRange = [Math.min(line.y1, line.y2), Math.max(line.y1, line.y2)] const overlapYRange = [Math.max(currentRoofYRange[0], lineYRange[0]), Math.min(currentRoofYRange[1], lineYRange[1])] if (overlapYRange[1] - overlapYRange[0] > 0) { const centerX = Math.round(((currentRoof.x1 + line.x1) / 2) * 10) / 10 points = [centerX, overlapYRange[0], centerX, overlapYRange[1]] } } if (currentRoof.y1 === currentRoof.y2) { const currentRoofXRange = [Math.min(currentRoof.x1, currentRoof.x2), Math.max(currentRoof.x1, currentRoof.x2)] const lineXRange = [Math.min(line.x1, line.x2), Math.max(line.x1, line.x2)] const overlapXRange = [Math.max(currentRoofXRange[0], lineXRange[0]), Math.min(currentRoofXRange[1], lineXRange[1])] if (overlapXRange[1] - overlapXRange[0] > 0) { const centerY = Math.round(((currentRoof.y1 + line.y1) / 2) * 10) / 10 points = [overlapXRange[0], centerY, overlapXRange[1], centerY] } } // 마루를 그린다. if (points) { const ridge = new QLine(points, { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 1, name: LINE_TYPE.SUBLINE.RIDGE, attributes: { roofId: roof.id, currentRoof: [currentRoof.id] }, visible: false, }) const duplicateRidge = ridges.find( (ridge) => ridge.x1 === points[0] && ridge.y1 === points[1] && ridge.x2 === points[2] && ridge.y2 === points[3], ) // 중복된 마루는 제외한다. if (duplicateRidge) { duplicateRidge.attributes.currentRoof.push(currentRoof.id) } else { canvas.add(ridge) canvas.renderAll() ridges.push(ridge) } } }) }) // 처마마다 지붕 polygon 을 그린다. eaves.forEach((eave) => { const index = eave.index, currentRoof = eave.roof const currentWall = wallLines[index] const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(eave.roof.id)) let points = [] const signX = Math.sign(currentRoof.x1 - currentRoof.x2) let currentX1 = currentRoof.x1, currentY1 = currentRoof.y1, currentX2 = currentRoof.x2, currentY2 = currentRoof.y2 let changeX1 = [Math.min(currentRoof.x1, currentRoof.x2), Math.min(currentRoof.x1, currentRoof.x2)], changeY1 = [Math.min(currentRoof.y1, currentRoof.y2), Math.min(currentRoof.y1, currentRoof.y2)], changeX2 = [Math.max(currentRoof.x2, currentRoof.x1), Math.max(currentRoof.x2, currentRoof.x1)], changeY2 = [Math.max(currentRoof.y2, currentRoof.y1), Math.max(currentRoof.y2, currentRoof.y1)] if (signX === 0) { currentY1 = Math.min(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) changeY1[1] = currentY1 currentY2 = Math.max(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) changeY2[1] = currentY2 } else { currentX1 = Math.min(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) changeX1[1] = currentX1 currentX2 = Math.max(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) changeX2[1] = currentX2 } points.push({ x: currentX1, y: currentY1 }, { x: currentX2, y: currentY2 }) currentRidges.forEach((ridge) => { let ridgeX1 = ridge.x1, ridgeY1 = ridge.y1, ridgeX2 = ridge.x2, ridgeY2 = ridge.y2 if (signX === 0) { ridgeY1 = Math.min(ridge.y1, ridge.y2) ridgeY2 = Math.max(ridge.y1, ridge.y2) } else { ridgeX1 = Math.min(ridge.x1, ridge.x2) ridgeX2 = Math.max(ridge.x1, ridge.x2) } points.push({ x: ridgeX1, y: ridgeY1 }, { x: ridgeX2, y: ridgeY2 }) }) points.forEach((point) => { if (point.x === changeX1[0] && changeX1[0] !== changeX1[1]) { point.x = changeX1[1] } if (point.x === changeX2[0] && changeX2[0] !== changeX2[1]) { point.x = changeX2[1] } if (point.y === changeY1[0] && changeY1[0] !== changeY1[1]) { point.y = changeY1[1] } if (point.y === changeY2[0] && changeY2[0] !== changeY2[1]) { point.y = changeY2[1] } }) //중복된 point 제거 points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) //point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.) const startPoint = points .filter((point) => point.x === Math.min(...points.map((point) => point.x))) .reduce((prev, current) => { return prev.y < current.y ? prev : current }) const sortedPoints = [] sortedPoints.push(startPoint) points.forEach((p, index) => { if (index === 0) { //시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점 const underStartPoint = points.filter((point) => point.y > startPoint.y) const nextPoint = underStartPoint .filter((point) => point.x === startPoint.x) .reduce((prev, current) => { if (prev === undefined) { return current } return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = underStartPoint.reduce((prev, current) => { const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) return prevHypos < currentHypos ? prev : current }, undefined) sortedPoints.push(nextPoint) } } else { const lastPoint = sortedPoints[sortedPoints.length - 1] const prevPoint = sortedPoints[sortedPoints.length - 2] const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) const nextPoint = otherPoints.reduce((prev, current) => { if (prev === undefined) { const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) const angle = Math.round( Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), ) if (angle === 90) { return current } } else { return prev } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = otherPoints.reduce((prev, current) => { if (prev !== undefined) { const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuseC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) const angleC = Math.round( Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), ) const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) const angleP = Math.round( Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), ) if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { return current } else { return prev } } else { return current } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } } } }) if (sortedPoints.length > 0) { const roofPolygon = new QPolygon(sortedPoints, { fill: 'transparent', stroke: '#1083E3', strokeWidth: 2, selectable: false, fontSize: roof.fontSize, name: 'roofPolygon', attributes: { roofId: roof.id, currentRoof: currentRoof.id, pitch: currentRoof.attributes.pitch, degree: currentRoof.attributes.degree, direction: currentRoof.direction, }, }) const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree //지붕 각도에 따른 실측치 조정 roofPolygon.lines.forEach((line) => { line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) } else { line.attributes.actualSize = line.attributes.planeSize } }) roof.separatePolygon.push(roofPolygon) canvas.add(roofPolygon) canvas.renderAll() } }) if (ridges.length > 0) { ridges.forEach((ridge) => roof.innerLines.push(ridge)) } //기준선 제거 // ridges.forEach((ridge) => canvas.remove(ridge)) eaves.forEach((eave) => { const currentRoof = eave.roof let currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(currentRoof.id)) if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { currentRidges = currentRidges.reduce((farthest, ridge) => { const currentDistance = Math.abs(ridge.x1 - currentRoof.x1) const farthestDistance = farthest ? Math.abs(farthest.x1 - currentRoof.x1) : 0 return currentDistance > farthestDistance ? ridge : farthest }, null) } else { currentRidges = currentRidges.reduce((farthest, ridge) => { const currentDistance = Math.abs(ridge.y1 - currentRoof.y1) const farthestDistance = farthest ? Math.abs(farthest.y1 - currentRoof.y1) : 0 return currentDistance > farthestDistance ? ridge : farthest }, null) } if (currentRidges) { const ridgeMidX = (currentRidges.x1 + currentRidges.x2) / 2 const ridgeMidY = (currentRidges.y1 + currentRidges.y2) / 2 const x2 = Math.sign(currentRidges.x1 - currentRidges.x2) === 0 ? currentRoof.x1 : ridgeMidX const y2 = Math.sign(currentRidges.x1 - currentRidges.x2) === 0 ? ridgeMidY : currentRoof.y1 const centerLine = new QLine([ridgeMidX, ridgeMidY, x2, y2], { stroke: '#000000', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: roof.fontSize, attributes: { roofId: roof.id, type: 'pitchSizeLine', }, }) const adjust = Math.sqrt( Math.pow(Math.round(Math.abs(centerLine.x1 - centerLine.x2) * 10), 2) + Math.pow(Math.round(Math.abs(centerLine.y1 - centerLine.y2) * 10), 2), ) const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree const height = Math.tan(currentDegree * (Math.PI / 180)) * adjust const lengthText = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2))) canvas.add(centerLine) canvas.renderAll() centerLine.setLengthText(lengthText) roof.innerLines.push(centerLine) } }) } /** * 한쪽흐름 지붕 * @param roofId * @param canvas */ export const drawShedRoof = (roofId, canvas) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) const hasNonParallelLines = roof.lines.filter((line) => Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1) if (hasNonParallelLines.length > 0) { alert('대각선이 존재합니다.') return } const sheds = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.SHED) const gables = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.GABLE) const eaves = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.EAVES) 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))) }) const pitchSizeLines = [] sheds.forEach((shed) => { let points = [] let x1, y1, x2, y2 const signX = Math.sign(shed.x1 - shed.x2) if (signX !== 0) { points.push(shed.x1, shed.x2) y1 = shed.y1 } else { points.push(shed.y1, shed.y2) x1 = shed.x1 } eaves.forEach((eave) => { if (signX !== 0) { points.push(eave.x1, eave.x2) points.sort((a, b) => a - b) x1 = (points[1] + points[2]) / 2 x2 = (points[1] + points[2]) / 2 y2 = eave.y1 } else { points.push(eave.y1, eave.y2) points.sort((a, b) => a - b) y1 = (points[1] + points[2]) / 2 y2 = (points[1] + points[2]) / 2 x2 = eave.x1 } points.sort((a, b) => a - b) const line = new QLine([x1, y1, x2, y2], { stroke: '#000000', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: roof.fontSize, attributes: { roofId: roof.id, type: 'pitchSizeLine', }, }) pitchSizeLines.push(line) }) }) const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0]) canvas.add(maxLine) canvas.renderAll() const adjust = Math.sqrt( Math.pow(Math.round(Math.abs(maxLine.x1 - maxLine.x2) * 10), 2) + Math.pow(Math.round(Math.abs(maxLine.y1 - maxLine.y2) * 10), 2), ) const height = getHeight(adjust, shedDegree) const lengthText = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2))) maxLine.setLengthText(lengthText) } export const drawRidgeRoof = (roofId, canvas) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) const hasNonParallelLines = roof.lines.filter((line) => Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1) if (hasNonParallelLines.length > 0) { alert('대각선이 존재합니다.') return } drawRidge(roof, canvas) drawHips(roof, canvas) connectLinePoint(roof, canvas) modifyRidge(roof, canvas) drawCenterLine(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] const angle1 = calculateAngle(prevRoof.startPoint, prevRoof.endPoint) const angle2 = calculateAngle(nextRoof.startPoint, nextRoof.endPoint) if (Math.abs(angle1 - angle2) === 180 && currentWall.attributes.planeSize <= currentRoof.attributes.planeSize) { ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize }) } }) // 지붕의 길이가 짧은 순으로 정렬 ridgeRoof.sort((a, b) => a.length - b.length) ridgeRoof.forEach((item) => { if (getMaxRidge(roofLines.length) > roof.ridges.length) { const index = item.index, currentRoof = item.roof const prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1] const nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1] let startXPoint, startYPoint, endXPoint, endYPoint let wallLine = wallLines.filter((w) => w.id === currentRoof.attributes.wallLine) if (wallLine.length > 0) { wallLine = wallLine[0] } const anotherRoof = roofLines.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof) let currentX1 = currentRoof.x1, currentY1 = currentRoof.y1, currentX2 = currentRoof.x2, currentY2 = currentRoof.y2 let ridgeMaxLength = Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) / 10 let ridgeMinLength = Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize) / 10 const currentAngle = Math.atan2(currentY2 - currentY1, currentX2 - currentX1) * (180 / Math.PI) anotherRoof .filter((roof) => isInnerLine(prevRoof, currentRoof, nextRoof, roof)) .forEach((innerRoof) => { const vector1 = { x: currentRoof.x2 - currentRoof.x1, y: currentRoof.y2 - currentRoof.y1 } const vector2 = { x: innerRoof.x2 - innerRoof.x1, y: innerRoof.y2 - innerRoof.y1 } 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) const angle = (Math.acos(dotProduct / (magnitude1 * magnitude2)) * 180) / Math.PI //현재 지붕선과 직각인 선 if (Math.abs(angle) === 90) { const innerBefore = roofLines.find((line) => innerRoof.x1 === line.x2 && innerRoof.y1 === line.y2) const ibAngle = Math.atan2(innerBefore.y2 - innerBefore.y1, innerBefore.x2 - innerBefore.x1) * (180 / Math.PI) if (Math.abs(currentAngle - ibAngle) === 180) { if (currentAngle === 0 || currentAngle === 180) { currentX2 = innerRoof.x1 ridgeMinLength = Math.round( Math.sqrt( Math.pow(Math.round(Math.abs(nextRoof.x1 - nextRoof.x2) * 10), 2) + Math.pow(Math.round(Math.abs(nextRoof.y1 - innerRoof.y2) * 10), 2), ), ) / 10 } if (currentAngle === 90 || currentAngle === 270) { currentY2 = innerRoof.y1 ridgeMinLength = Math.round( Math.sqrt( Math.pow(Math.round(Math.abs(nextRoof.x1 - innerRoof.x2) * 10), 2) + Math.pow(Math.round(Math.abs(nextRoof.y1 - nextRoof.y2) * 10), 2), ), ) / 10 } } if (Math.abs(currentAngle - ibAngle) === 0) { if (currentAngle === 0 || currentAngle === 180) { currentX1 = innerRoof.x2 ridgeMinLength = Math.round( Math.sqrt( Math.pow(Math.round(Math.abs(prevRoof.x1 - prevRoof.x2) * 10), 2) + Math.pow(Math.round(Math.abs(prevRoof.y1 - innerRoof.y1) * 10), 2), ), ) / 10 } if (currentAngle === 90 || currentAngle === 270) { currentY1 = innerRoof.y2 ridgeMinLength = Math.round( Math.sqrt( Math.pow(Math.round(Math.abs(prevRoof.x1 - innerRoof.x1) * 10), 2) + Math.pow(Math.round(Math.abs(prevRoof.y1 - prevRoof.y2) * 10), 2), ), ) / 10 } } } //현재 지붕선과 반대인 선 if (Math.abs(angle) === 180) { if (currentAngle === 0 || currentAngle === 180) { } if (currentAngle === 90 || currentAngle === 270) { } } }) const midX = (currentX1 + currentX2) / 2 // 지붕의 X 중심 const midY = (currentY1 + currentY2) / 2 // 지붕의 Y 중심 const alpha = (currentRoof.x1 + currentRoof.x2) / 2 - (wallLine.x1 + wallLine.x2) / 2 // 벽과 지붕의 X 거리 const beta = (currentRoof.y1 + currentRoof.y2) / 2 - (wallLine.y1 + wallLine.y2) / 2 // 벽과 지붕의 Y 거리 const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) // 벽과 지붕의 거리 const currentPlaneSize = Math.sqrt( Math.pow(Math.round(Math.abs(currentX1 - currentX2) * 10), 2) + Math.pow(Math.round(Math.abs(currentY1 - currentY2) * 10), 2), ) let ridgeBaseLength = Math.round(currentPlaneSize / 2) / 10 // 지붕의 기반 길이 startXPoint = Math.round(midX + (-1 * (alpha / hypotenuse) * (currentPlaneSize / 2)) / 10) startYPoint = Math.round(midY + (-1 * (beta / hypotenuse) * (currentPlaneSize / 2)) / 10) const checkEndXPoint = Math.round(startXPoint + Math.sign(alpha) * -1 * (ridgeMaxLength + ridgeBaseLength)) const checkEndYPoint = Math.round(startYPoint + Math.sign(beta) * -1 * (ridgeMaxLength + ridgeBaseLength)) const checkLine = new QLine([startXPoint, startYPoint, checkEndXPoint, checkEndYPoint], { fontSize: roof.fontSize, stroke: 'red', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoof: currentRoof.id, actualSize: 0 }, }) const intersectLines = [] roofLines.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: checkLine.x1, y: checkLine.y1 }, vertex2: { x: checkLine.x2, y: checkLine.y2 } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectLines.push({ x: intersection.x, y: intersection.y, line: line }) } }) if (intersectLines.length > 0) { intersectLines.reduce((prev, current) => { if (prev !== undefined) { const prevDistance = Math.sqrt(Math.pow(prev.x - startXPoint, 2) + Math.pow(prev.y - startYPoint, 2)) const currentDistance = Math.sqrt(Math.pow(current.x - startXPoint, 2) + Math.pow(current.y - startYPoint, 2)) return prevDistance > currentDistance ? current : prev } else { return current } }, undefined) } if (intersectLines.length > 0) { const intersectLine = intersectLines[0] const diffX = Math.round(intersectLine.x - startXPoint) const diffY = Math.round(intersectLine.y - startYPoint) endXPoint = Math.sign(diffX) * Math.round(Math.abs(diffX) - ridgeBaseLength) + startXPoint endYPoint = Math.sign(diffY) * Math.round(Math.abs(diffY) - ridgeBaseLength) + startYPoint const hypo = Math.sqrt(Math.pow(Math.abs(startXPoint - endXPoint), 2) + Math.pow(Math.abs(startYPoint - endYPoint), 2)) const intersectLength = Math.sqrt(Math.pow(Math.abs(midX - intersectLine.x), 2) + Math.pow(Math.abs(midY - intersectLine.y), 2)) if (intersectLength < prevRoof.attributes.planeSize / 10 && intersectLength < nextRoof.attributes.planeSize / 10) { endXPoint = startXPoint endYPoint = startYPoint } else { if (ridgeMinLength < hypo) { endXPoint = Math.round(startXPoint + Math.sign(diffX) * ridgeMinLength) endYPoint = Math.round(startYPoint + Math.sign(diffY) * ridgeMinLength) } } } else { endXPoint = Math.round(startXPoint + Math.sign(alpha) * -1 * ridgeMinLength) endYPoint = Math.round(startYPoint + Math.sign(beta) * -1 * ridgeMinLength) } const ridge = new QLine([startXPoint, startYPoint, endXPoint, endYPoint], { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, 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) if (ridge.attributes.planeSize > 0) { 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) => { 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: '#1083E3', strokeWidth: 2, 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} */ export 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 vectorX1 = ridgeCoordinate.x1 - currentRoof.x1 const vectorY1 = ridgeCoordinate.y1 - currentRoof.y1 const angle1 = Math.atan2(vectorY1, vectorX1) * (180 / Math.PI) if (Math.abs(Math.round(angle1)) % 45 === 0) { const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, 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 vectorX2 = ridgeCoordinate.x1 - currentRoof.x2 const vectorY2 = ridgeCoordinate.y1 - currentRoof.y2 const angle2 = Math.atan2(vectorY2, vectorX2) * (180 / Math.PI) if (Math.abs(Math.round(angle2)) % 45 === 0) { const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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 - currentDegree) * 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() } /** * 3개 이상 이어지지 않은 라인 포인트 계산 * 모임지붕에서 point 는 3개 이상의 라인과 접해야 함. * @param polygon */ 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: '#1083E3', strokeWidth: 2, }) 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: '#1083E3', strokeWidth: 2, }) 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: '#1083E3', strokeWidth: 2, 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 hipX2 = Math.round(midX + -1 * (alpha / hypotenuse) * (currentRoof.length / 2)) const hipY2 = Math.round(midY + -1 * (beta / hypotenuse) * (currentRoof.length / 2)) const innerLines = canvas ?.getObjects() .filter( (object) => 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 && line.name !== 'wallLine', ) .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: hipX2, y1: hipY2, 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: hipX2, y2: hipY2, }) 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) } hipLines.forEach((hip) => { roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id) canvas.remove(hip) }) canvas?.renderAll() 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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?.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 && line.name !== 'wallLine', ) .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: '#1083E3', strokeWidth: 2, 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))) let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX, midY], { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, 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))) hip1.set({ visible: false }) hip1.setViewLengthText(false) hip2.set({ visible: false }) hip2.setViewLengthText(false) 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?.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 && line.name !== 'wallLine', ) .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) { const signX = Math.sign(midX - ridge.x1) const signY = Math.sign(midY - ridge.y1) ridge.set({ x1: midX - signX * Math.abs(xWidth), y1: midY - signY * Math.abs(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) { const signX = Math.sign(midX - ridge.x2) const signY = Math.sign(midY - ridge.y2) ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: midX - signX * Math.abs(xWidth), y2: midY - signY * Math.abs(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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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 && line.name !== 'wallLine', ) .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) { const signX = Math.sign(midX - ridge.x1) const signY = Math.sign(midY - ridge.y1) ridge.set({ x1: midX - signX * Math.abs(xWidth), y1: midY - signY * Math.abs(yWidth), x2: ridge.x2, y2: ridge.y2, }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } hipX2 = ridge.x1 hipY2 = ridge.y1 } if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { const signX = Math.sign(midX - ridge.x2) const signY = Math.sign(midY - ridge.y2) ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: midX - signX * Math.abs(xWidth), y2: midY - signY * Math.abs(yWidth), }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } hipX2 = ridge.x2 hipY2 = 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 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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) hip1.set({ visible: false }) hip1.setViewLengthText(false) gable3.set({ visible: false }) gable3.setViewLengthText(false) hip2.set({ visible: false }) hip2.setViewLengthText(false) } } } /** * 벽지붕으로 변경 * @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?.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 && line.name !== 'wallLine', ) .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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, }) const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1, nextY1], { fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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: '#1083E3', strokeWidth: 2, 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) => { lines.forEach((l) => { 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 } const drawCenterLine = (roof, canvas) => { const roofLines = roof.lines roofLines.forEach((currentRoof) => { const hips = roof.innerLines.filter( (line) => (currentRoof.x1 === line.x1 && currentRoof.y1 === line.y1) || (currentRoof.x2 === line.x1 && currentRoof.y2 === line.y1), ) const ridge = roof.innerLines .filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE) .filter((line) => { if (currentRoof.x1 === currentRoof.x2) { if (line.x1 === line.x2) { return line } } else { if (line.y1 === line.y2) { return line } } }) .reduce((prev, current) => { let currentDistance, prevDistance if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { currentDistance = Math.abs(currentRoof.y1 - current.y1) prevDistance = prev ? Math.abs(currentRoof.y1 - prev.y1) : Infinity } else { currentDistance = Math.abs(currentRoof.y1 - current.y2) prevDistance = prev ? Math.abs(currentRoof.y1 - current.y2) : Infinity } return prevDistance < currentDistance ? prev : current }, null) let points = [] if (hips.length === 2 && Math.abs(hips[0].x2 - hips[1].x2) < 1 && Math.abs(hips[0].y2 - hips[1].y2) < 1) { // console.log('삼각') const x1 = (currentRoof.x1 + currentRoof.x2) / 2 const y1 = (currentRoof.y1 + currentRoof.y2) / 2 points.push(x1, y1, hips[0].x2, hips[0].y2) } else { // console.log('삼각 아님') if ( ((ridge.x1 === hips[0].x2 && ridge.y1 === hips[0].y2) || (ridge.x2 === hips[0].x2 && ridge.y2 === hips[0].y2)) && ((ridge.x1 === hips[1].x2 && ridge.y1 === hips[1].y2) || (ridge.x2 === hips[1].x2 && ridge.y2 === hips[1].y2)) ) { //사각이면 마루와 현재 라인 사이에 길이를 구한다 // console.log('사각') if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { const yPoints = [currentRoof.y1, currentRoof.y2, ridge.y1, ridge.y2].sort((a, b) => a - b) const x1 = (currentRoof.x1 + currentRoof.x2) / 2 const x2 = (ridge.x1 + ridge.x2) / 2 const y = (yPoints[1] + yPoints[2]) / 2 if ( ((currentRoof.y1 <= y && y <= currentRoof.y2) || (currentRoof.y2 <= y && y <= currentRoof.y1)) && ((ridge.y1 <= y && y <= ridge.y2) || (ridge.y2 <= y && y <= ridge.y1)) ) { points.push(x1, y, x2, y) } } else { const xPoints = [currentRoof.x1, currentRoof.x2, ridge.x1, ridge.x2].sort((a, b) => a - b) const y1 = (currentRoof.y1 + currentRoof.y2) / 2 const y2 = (ridge.y1 + ridge.y2) / 2 const x = (xPoints[1] + xPoints[2]) / 2 if ( ((currentRoof.x1 <= x && x <= currentRoof.x2) || (currentRoof.x2 <= x && x <= currentRoof.x1)) && ((ridge.x1 <= x && x <= ridge.x2) || (ridge.x2 <= x && x <= ridge.x1)) ) { points.push(x, y1, x, y2) } } } else { //사각 아님 if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { let xPoints = [] xPoints.push(ridge?.x1, ridge?.x2) hips.forEach((hip) => { xPoints.push(hip.x2) }) let maxPoint = xPoints.reduce((prev, current) => { const currentDistance = Math.abs(currentRoof.x1 - current) const prevDistance = prev ? Math.abs(currentRoof.x1 - prev) : 0 return prevDistance > currentDistance ? prev : current }, null) xPoints = xPoints.filter((point) => point === maxPoint) let oppositeLine if (xPoints.length === 1) { if (ridge?.x1 === xPoints[0] || ridge?.x2 === xPoints[0]) { oppositeLine = ridge } if (oppositeLine === undefined) { oppositeLine = hips.find((hip) => hip.x2 === xPoints[0]) } points.push(currentRoof.x1, oppositeLine.y2, oppositeLine.x2, oppositeLine.y2) } else if (xPoints.length > 1) { xPoints = [...new Set(xPoints)] // 중복제거 if (ridge.length > 0) { let boolX1 = xPoints.some((x) => x === ridge.x1) let boolX2 = xPoints.some((x) => x === ridge.x2) if (boolX1 && boolX2) { oppositeLine = ridge } if (oppositeLine) { const sortPoints = [currentRoof.y1, currentRoof.y2, oppositeLine.y1, oppositeLine.y2].sort((a, b) => a - b) const y = (sortPoints[1] + sortPoints[2]) / 2 if ( ((currentRoof.y1 <= y && y <= currentRoof.y2) || (currentRoof.y2 <= y && y <= currentRoof.y1)) && ((oppositeLine.y1 <= y && y <= oppositeLine.y2) || (oppositeLine.y2 <= y && y <= oppositeLine.y1)) ) { points.push(currentRoof.x1, y, oppositeLine.x1, y) } } } } } else { let yPoints = [] yPoints.push(ridge?.y1, ridge?.y2) hips.forEach((hip) => { yPoints.push(hip.y2) }) const maxPoint = yPoints.reduce((prev, current) => { const currentDistance = Math.abs(currentRoof.y1 - current) const prevDistance = prev ? Math.abs(currentRoof.y1 - prev) : 0 return prevDistance > currentDistance ? prev : current }) yPoints = yPoints.filter((y) => y === maxPoint) let oppositeLine if (yPoints.length === 1) { if (ridge?.y1 === yPoints[0] || ridge?.y2 === yPoints[0]) { oppositeLine = ridge } if (oppositeLine === undefined) { oppositeLine = hips.find((hip) => hip.y2 === yPoints[0]) } points.push(oppositeLine.x2, currentRoof.y1, oppositeLine.x2, oppositeLine.y2) } else if (yPoints.length > 1) { let boolY1 = yPoints.some((y) => y === ridge.y1) let boolY2 = yPoints.some((y) => y === ridge.y2) if (boolY1 && boolY2) { oppositeLine = ridge } if (oppositeLine) { const sortPoints = [currentRoof.x1, currentRoof.x2, oppositeLine.x1, oppositeLine.x2].sort((a, b) => a - b) const x = (sortPoints[1] + sortPoints[2]) / 2 if ( ((currentRoof.x1 <= x && x <= currentRoof.x2) || (currentRoof.x2 <= x && x <= currentRoof.x1)) && ((oppositeLine.x1 <= x && x <= oppositeLine.x2) || (oppositeLine.x2 <= x && x <= oppositeLine.x1)) ) { points.push(x, currentRoof.y1, x, oppositeLine.y1) } } } } } } if (points !== null) { const pitchSizeLine = new QLine(points, { stroke: '#000000', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: roof.fontSize, attributes: { roofId: roof.id, type: 'pitchSizeLine', }, }) canvas.add(pitchSizeLine) canvas.renderAll() const adjust = Math.sqrt( Math.pow(Math.round(Math.abs(pitchSizeLine.x1 - pitchSizeLine.x2) * 10), 2) + Math.pow(Math.round(Math.abs(pitchSizeLine.y1 - pitchSizeLine.y2) * 10), 2), ) const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree const height = Math.tan(currentDegree * (Math.PI / 180)) * adjust const lengthText = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2))) pitchSizeLine.setLengthText(lengthText) } }) } 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 } 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((coordinate) => { const point = turf.point(coordinate) return turf.booleanPointInPolygon(point, polygonFeature) }) // 다각형의 모든 점이 사각형 내부에 있지 않은지 확인 const noPolygonPointsInsideRect = polygonCoordinates.every((coordinate) => { const point = turf.point(coordinate) return !turf.booleanPointInPolygon(point, rectFeature) }) return allPointsInsidePolygon && noPolygonPointsInsideRect }