import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' import { getDegreeByChon, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { OUTER_LINE_TYPE } from '@/store/outerLineAtom' import Big from 'big.js' const TWO_PI = Math.PI * 2 export const defineQPloygon = () => { fabric.QPolygon.fromObject = function (object, callback) { fabric.Object._fromObject('QPolygon', object, callback, 'points') } } /** * point1에서 point2를 잇는 방향의 각도를 구한다. * @param point1 * @param point2 * @returns {number} */ export const calculateAngle = (point1, point2) => { const deltaX = Big(point2.x ?? 0) .minus(point1.x ?? 0) .toNumber() const deltaY = Big(point2.y ?? 0) .minus(point1.y ?? 0) .toNumber() const angleInRadians = Math.atan2(deltaY, deltaX) return Big(angleInRadians * (180 / Math.PI)) .round() .toNumber() } 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, } } /** * edgeA와 edgeB가 마주치는 포인트의 좌표를 반환한다. * @param edgeA * @param edgeB * @returns {{x: *, y: *, isIntersectionOutside: boolean}|null} */ 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 // 선들이 평행하거나 일치합니다 } 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 // 교차점이 두 선분의 범위 내에 있는지 확인 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 }) originPolygon.setViewLengthText(false) 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) { let uniquePolygons = [] // x가 전부 같거나, y가 전부 같은 경우 제거 polygons.forEach((polygon) => { const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon)) if (!isDuplicate) { uniquePolygons.push(polygon) } }) uniquePolygons = uniquePolygons.filter((polygon) => { return isValidPoints(polygon) }) return uniquePolygons } // 같은 직선상에 있는지 확인 같은 직선이라면 polygon을 생성할 수 없으므로 false const isValidPoints = (points) => { function isColinear(p1, p2, p3) { return (p2.x - p1.x) * (p3.y - p1.y) === (p3.x - p1.x) * (p2.y - p1.y) } function segmentsOverlap(a1, a2, b1, b2) { // 같은 직선 상에 있는가? if (!isColinear(a1, a2, b1) || !isColinear(a1, a2, b2)) { return false } const isHorizontal = a1.y === a2.y if (isHorizontal) { const aMin = Math.min(a1.x, a2.x), aMax = Math.max(a1.x, a2.x) const bMin = Math.min(b1.x, b2.x), bMax = Math.max(b1.x, b2.x) return Math.max(aMin, bMin) < Math.min(aMax, bMax) } else { const aMin = Math.min(a1.y, a2.y), aMax = Math.max(a1.y, a2.y) const bMin = Math.min(b1.y, b2.y), bMax = Math.max(b1.y, b2.y) return Math.max(aMin, bMin) < Math.min(aMax, bMax) } } for (let i = 0; i < points.length - 2; i++) { const a1 = points[i] const a2 = points[i + 1] const b1 = points[i + 1] // 연속되는 점 const b2 = points[i + 2] if (segmentsOverlap(a1, a2, b1, b2)) { return false } } return true } export const isSamePoint = (a, b) => { if (!a || !b) { return false } 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 * @param textMode */ export const drawGabledRoof = (roofId, canvas, textMode) => { 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 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, { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 1, name: LINE_TYPE.SUBLINE.RIDGE, attributes: { roofId: roof.id, currentRoofId: [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.currentRoofId.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.currentRoofId.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, { parentId: roof.id, fill: 'transparent', stroke: '#1083E3', strokeWidth: 2, selectable: false, fontSize: roof.fontSize, name: 'roofPolygon', attributes: { roofId: roof.id, currentRoofId: currentRoof.id, pitch: currentRoof.attributes.pitch, degree: currentRoof.attributes.degree, direction: currentRoof.direction, }, originX: 'center', originY: 'center', }) 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.currentRoofId.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 pitchSizeLine = new QLine([ridgeMidX, ridgeMidY, x2, y2], { parentId: roof.id, 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) } }) } /** * 한쪽흐름 지붕 * @param roofId * @param canvas * @param textMode */ export const drawShedRoof = (roofId, canvas, textMode) => { 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) => (gable.attributes.actualSize = calcLineActualSize({ x1: gable.x1, y1: gable.y1, x2: gable.x2, y2: gable.y2 }, shedDegree)), ) 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 planeSize = Math.round(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) * 10) const planeSize = calcLinePlaneSize({ x1, y1, x2, y2 }) // const actualSize = Math.round(Math.sqrt (Math.pow(planeSize, 2) + Math.pow(getHeight(planeSize, shedDegree), 2)) * 10) const actualSize = calcLineActualSize({ x1, y1, x2, y2 }, shedDegree) const line = new QLine([x1, y1, x2, y2], { parentId: roof.id, stroke: '#000000', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: roof.fontSize, textMode: textMode, attributes: { roofId: roof.id, type: 'pitchSizeLine', planeSize: actualSize, actualSize: actualSize, }, }) pitchSizeLines.push(line) }) }) const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0]) canvas.add(maxLine) canvas.renderAll() } /** * 마루가 있는 지붕을 그린다. * @param roofId * @param canvas * @param textMode */ export const drawRidgeRoof = (roofId, canvas, textMode) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) console.log('wall :', wall) console.log('roof.lines :', roof.lines) //Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1 const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) if (hasNonParallelLines.length > 0) { // alert('대각선이 존재합니다.') return } const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] // // const eaves = roof.lines.filter((line) => eavesType.includes(line.attributes?.type)) // const gables = roof.lines.filter((line) => gableType.includes(line.attributes?.type)) /** 외벽선 */ const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) /** 모양 판단을 위한 라인 처리. * 평행한 라인이 나누어져 있는 경우 하나의 선으로 판단 한다. */ const drawBaseLines = [] baseLines.forEach((currentLine, index) => { let nextLine = baseLines[(index + 1) % baseLines.length] let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) let { x1, y1, x2, y2 } = currentLine if (currentAngle !== prevAngle || (currentAngle === prevAngle && currentLine.attributes.type !== prevLine.attributes.type)) { if (currentAngle === nextAngle && currentLine.attributes.type === nextLine.attributes.type) { let nextIndex = baseLines.findIndex((line) => line === nextLine) while (nextIndex !== index) { const checkNextLine = baseLines[(nextIndex + 1 + baseLines.length) % baseLines.length] const checkAngle = calculateAngle(checkNextLine.startPoint, checkNextLine.endPoint) if (currentAngle !== checkAngle) { x2 = checkNextLine.x1 y2 = checkNextLine.y1 break } else { nextIndex = nextIndex + 1 } } } drawBaseLines.push({ x1, y1, x2, y2, line: currentLine, size: calcLinePlaneSize({ x1, y1, x2, y2 }) }) } }) console.log('drawBaseLines : ', drawBaseLines) /** baseLine을 기준으로 확인용 polygon 작성 */ const checkWallPolygon = new QPolygon(baseLinePoints, {}) /** * 외벽선이 시계방향인지 시계반대 방향인지 확인 * @type {boolean} */ let counterClockwise = true let signedArea = 0 baseLinePoints.forEach((point, index) => { const nextPoint = baseLinePoints[(index + 1) % baseLinePoints.length] signedArea += point.x * nextPoint.y - point.y * nextPoint.x }) if (signedArea > 0) { counterClockwise = false } const drawEavesFirstLines = [] const drawEavesSecondLines = [] const drawGableRidgeFirst = [] const drawGableRidgeSecond = [] const drawGableFirstLines = [] const drawGableSecondLines = [] const drawGablePolygonFirst = [] const drawGablePolygonSecond = [] /** 모양을 판단한다. */ drawBaseLines.forEach((currentBaseLine, index) => { let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] // console.log('currentLine :', currentBaseLine) // console.log('prevLine :', prevBaseLine) // console.log('nextLine :', nextBaseLine) const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const checkScale = Big(10) const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) const checkPoints = { x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), } // console.log('prevAngle :', prevAngle, 'nextAngle :', nextAngle) /** 현재 라인이 처마유형일 경우 */ if (eavesType.includes(currentLine.attributes?.type)) { if (eavesType.includes(nextLine.attributes?.type)) { /** * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. */ if ( eavesType.includes(prevLine.attributes?.type) && eavesType.includes(nextLine.attributes?.type) && Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) ) { /** * 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다. * @type {{x: *, y: *}} */ if (checkWallPolygon.inPolygon(checkPoints)) { drawEavesFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else { drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) { console.log('currentLine :', currentBaseLine.size, 'prevAngle :', prevAngle, 'nextAngle :', nextAngle) if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { const checkPoints = { x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), } if (checkWallPolygon.inPolygon(checkPoints)) { drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } // if (!checkWallPolygon.inPolygon(checkPoints)) { drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) // } } else { drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } } if (gableType.includes(currentLine.attributes?.type)) { if (eavesType.includes(nextLine.attributes?.type)) { if ( eavesType.includes(prevLine.attributes?.type) && eavesType.includes(nextLine.attributes?.type) && Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) ) { if (checkWallPolygon.inPolygon(checkPoints)) { drawGableRidgeFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) drawGableFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else { drawGableSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } } }) drawEavesFirstLines.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) drawGableRidgeFirst.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) drawGableRidgeSecond.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) /** 추녀마루 */ let baseHipLines = [] /** 용마루 */ let baseRidgeLines = [] /** 박공지붕 마루*/ let baseGableRidgeLines = [] /** 박공지붕 라인*/ let baseGableLines = [] /** 용마루의 갯수*/ let baseRidgeCount = 0 // console.log('drawGableFirstLines :', drawGableFirstLines) // console.log('drawGableSecondLines :', drawGableSecondLines) console.log('drawEavesFirstLines :', drawEavesFirstLines) console.log('drawEavesSecondLines :', drawEavesSecondLines) console.log('drawGableRidgeFirst : ', drawGableRidgeFirst) console.log('drawGableRidgeSecond : ', drawGableRidgeSecond) console.log('drawGablePolygonFirst :', drawGablePolygonFirst) console.log('drawGablePolygonSecond :', drawGablePolygonSecond) /** 박공지붕에서 파생되는 마루를 그린다. ㄷ 형태*/ drawGableRidgeFirst.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2, size } = currentBaseLine let beforePrevBaseLine, afterNextBaseLine /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree /** 다음 라인의 경사 */ const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree /** 현재 라인의 경사 */ const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ drawBaseLines.forEach((line, index) => { if (line === prevBaseLine) { beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] } if (line === nextBaseLine) { afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] } }) const beforePrevLine = beforePrevBaseLine?.line const afterNextLine = afterNextBaseLine?.line /** 각 라인의 흐름 방향을 확인한다. */ const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) /** 현재라인의 vector*/ const currentVectorX = Math.sign(Big(x2).minus(Big(x1)).toNumber()) const currentVectorY = Math.sign(Big(y2).minus(Big(y1)).toNumber()) /** 이전라인의 vector*/ const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1))) const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1))) /** 다음라인의 vector*/ const nextVectorX = Math.sign(Big(nextLine.x2).minus(Big(nextLine.x1))) const nextVectorY = Math.sign(Big(nextLine.y2).minus(Big(nextLine.y1))) /** 현재라인의 기준점*/ const currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) const currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) /** 마루 반대 좌표*/ let oppositeMidX = currentMidX, oppositeMidY = currentMidY if (prevAngle === beforePrevAngle || nextAngle === afterNextAngle) { console.log('지붕선 분할 : start') const checkLine1 = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'checkLine', }) const checkLine2 = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], { stroke: 'blue', strokeWidth: 4, parentId: roofId, name: 'checkLine', }) const checkPoint = new fabric.Circle({ left: currentMidX.toNumber(), top: currentMidY.toNumber(), radius: 5, fill: 'red', parentId: roofId, name: 'checkPoint', }) canvas.add(checkLine1) canvas.add(checkLine2) canvas.add(checkPoint) canvas.renderAll() if (currentVectorX === 0) { } else { } console.log('지붕선 분할 : end') } else { if (beforePrevBaseLine === afterNextBaseLine) { const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2) const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2) const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX)) const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY)) let oppositeMidX, oppositeMidY if (eavesType.includes(afterNextLine.attributes?.type)) { const checkSize = currentMidX .minus(afterNextMidX) .pow(2) .plus(currentMidY.minus(afterNextMidY).pow(2)) .sqrt() .minus(Big(afterNextLine.attributes.planeSize).div(20)) .round(1) oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg()) oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg()) const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber()) const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber()) const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber()) const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber()) let addOppositeX1 = 0, addOppositeY1 = 0, addOppositeX2 = 0, addOppositeY2 = 0 if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt() addOppositeX1 = checkScale.times(xVector1).toNumber() addOppositeY1 = checkScale.times(yVector1).toNumber() addOppositeX2 = checkScale.times(xVector2).toNumber() addOppositeY2 = checkScale.times(yVector2).toNumber() } let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() scale1 = scale1.eq(0) ? Big(1) : scale1 let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() scale2 = scale2.eq(0) ? Big(1) : scale2 const checkHip1 = { x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(), y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(), x2: oppositeMidX.plus(addOppositeX1).toNumber(), y2: oppositeMidY.plus(addOppositeY1).toNumber(), } const checkHip2 = { x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(), y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(), x2: oppositeMidX.plus(addOppositeX2).toNumber(), y2: oppositeMidY.plus(addOppositeY2).toNumber(), } const intersection1 = findRoofIntersection(roof, checkHip1, { x: oppositeMidX.plus(addOppositeX1), y: oppositeMidY.plus(addOppositeY1) }) const intersection2 = findRoofIntersection(roof, checkHip2, { x: oppositeMidX.plus(addOppositeX2), y: oppositeMidY.plus(addOppositeY2) }) const afterNextDegree = afterNextLine.attributes.pitch > 0 ? getDegreeByChon(afterNextLine.attributes.pitch) : afterNextLine.attributes.degree if (intersection1) { const hipLine = drawHipLine( [intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)], canvas, roof, textMode, null, nextDegree, afterNextDegree, ) baseHipLines.push({ x1: afterNextLine.x1, y1: afterNextLine.y1, x2: oppositeMidX.plus(addOppositeX1).toNumber(), y2: oppositeMidY.plus(addOppositeY1).toNumber(), line: hipLine, }) } if (intersection2) { const hipLine = drawHipLine( [intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)], canvas, roof, textMode, null, prevDegree, afterNextDegree, ) baseHipLines.push({ x1: afterNextLine.x2, y1: afterNextLine.y2, x2: oppositeMidX.plus(addOppositeX2).toNumber(), y2: oppositeMidY.plus(addOppositeY2).toNumber(), line: hipLine, }) } } else { oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset)) oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset)) } const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY && baseRidgeCount < getMaxRidge(baseLines.length)) { const ridge = drawRidgeLine( [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], canvas, roof, textMode, ) baseGableRidgeLines.push(ridge) baseRidgeCount++ } } else { const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1)) const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1)) let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY const beforePrevOffset = currentAngle === beforePrevAngle ? Big(beforePrevLine.attributes.offset) : Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset) const afterNextOffset = currentAngle === afterNextAngle ? Big(afterNextLine.attributes.offset) : Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset) const prevSize = Big(prevLine.attributes.planeSize).div(10) const nextSize = Big(nextLine.attributes.planeSize).div(10) let prevHipCoords, nextHipCoords /** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ if (eavesType.includes(afterNextLine.attributes?.type)) { /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() const nextHalfVector = getHalfAngleVector(nextLine, afterNextLine) let nextHipVector = { x: nextHalfVector.x, y: nextHalfVector.y } /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(nextLine.x2).plus(Big(nextHalfVector.x).times(10)), y: Big(nextLine.y2).plus(Big(nextHalfVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } } const nextEndPoint = { x: Big(nextLine.x2).plus(Big(nextHipVector.x).times(hipLength)), y: Big(nextLine.y2).plus(Big(nextHipVector.y).times(hipLength)), } let ridgeEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: currentMidX.plus(Big(nextVectorX).times(nextBaseLine.size)).toNumber(), y: currentMidY.plus(Big(nextVectorY).times(nextBaseLine.size)).toNumber(), }, } let hipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: nextEndPoint.x, y: nextEndPoint.y } } let intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { nextHipCoords = { x1: nextLine.x2, y1: nextLine.y2, x2: intersection.x, y2: intersection.y } nextOppositeMidY = Big(intersection.y) nextOppositeMidX = Big(intersection.x) } } else { if (vectorMidX === 0) { nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY)) nextOppositeMidX = currentMidX } else { nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX)) nextOppositeMidY = currentMidY } } /** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ if (eavesType.includes(beforePrevLine.attributes?.type)) { /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() const prevHalfVector = getHalfAngleVector(prevLine, beforePrevLine) let prevHipVector = { x: prevHalfVector.x, y: prevHalfVector.y } /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(prevLine.x1).plus(Big(prevHalfVector.x).times(10)), y: Big(prevLine.y1).plus(Big(prevHalfVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } } const prevEndPoint = { x: Big(prevLine.x1).plus(Big(prevHipVector.x).times(hipLength)), y: Big(prevLine.y1).plus(Big(prevHipVector.y).times(hipLength)), } let ridgeEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: currentMidX.plus(Big(prevVectorX).times(prevBaseLine.size)).toNumber(), y: currentMidY.plus(Big(prevVectorY).times(prevBaseLine.size)).toNumber(), }, } let hipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevEndPoint.x, y: prevEndPoint.y } } let intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { prevHipCoords = { x1: prevLine.x1, y1: prevLine.y1, x2: intersection.x, y2: intersection.y } prevOppositeMidY = Big(intersection.y) prevOppositeMidX = Big(intersection.x) } } else { if (vectorMidX === 0) { prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY)) prevOppositeMidX = currentMidX } else { prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX)) prevOppositeMidY = currentMidY } } const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() /** 두 포인트 중에 current와 가까운 포인트를 사용*/ if (checkPrevSize.gt(checkNextSize)) { if (nextHipCoords) { let intersectPoints = [] const hipEdge = { vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 }, } /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if ( intersection && Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) && Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y) ) { const intersectEdge = { vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, vertex2: { x: intersection.x, y: intersection.y }, } const is = edgesIntersection(intersectEdge, lineEdge) if (!is.isIntersectionOutside) { const intersectSize = Big(nextHipCoords.x2) .minus(Big(intersection.x)) .pow(2) .plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, line, }) } } }) const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] if (intersect) { const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree const hipLine = drawHipLine( [intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()], canvas, roof, textMode, null, degree, degree, ) baseHipLines.push({ x1: nextHipCoords.x1, y1: nextHipCoords.y1, x2: nextHipCoords.x2, y2: nextHipCoords.y2, line: hipLine, }) } } oppositeMidY = nextOppositeMidY oppositeMidX = nextOppositeMidX } else { if (prevHipCoords) { let intersectPoints = [] const hipEdge = { vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 }, } /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if ( intersection && Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) && Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y) ) { const intersectEdge = { vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, vertex2: { x: intersection.x, y: intersection.y }, } const is = edgesIntersection(intersectEdge, lineEdge) if (!is.isIntersectionOutside) { const intersectSize = Big(prevHipCoords.x2) .minus(Big(intersection.x)) .pow(2) .plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, line, }) } } }) const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] if (intersect) { const degree = intersect.line.attributes.pitch > 0 ? getDegreeByChon(intersect.line.attributes.pitch) : intersect.line.attributes.degree const hipLine = drawHipLine( [intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()], canvas, roof, textMode, null, degree, degree, ) baseHipLines.push({ x1: prevHipCoords.x1, y1: prevHipCoords.y1, x2: prevHipCoords.x2, y2: prevHipCoords.y2, line: hipLine, }) } } oppositeMidY = prevOppositeMidY oppositeMidX = prevOppositeMidX } } if (baseRidgeCount < getMaxRidge(baseLines.length)) { /** 마루가 맞은편 외벽선에 닿는 경우 해당 부분까지로 한정한다. */ const ridgeEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } const ridgeVectorX = Math.sign(currentMidX.minus(oppositeMidX).toNumber()) const ridgeVectorY = Math.sign(currentMidY.minus(oppositeMidY).toNumber()) roof.lines .filter((line) => { const lineVectorX = Math.sign(Big(line.x2).minus(Big(line.x1)).toNumber()) const lineVectorY = Math.sign(Big(line.y2).minus(Big(line.y1)).toNumber()) return ( (lineVectorX === currentVectorX && lineVectorY !== currentVectorY) || (lineVectorX !== currentVectorX && lineVectorY === currentVectorY) ) }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(ridgeEdge, lineEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(currentMidX).minus(intersection.x).toNumber()) const isVectorY = Math.sign(Big(currentMidY).minus(intersection.y).toNumber()) if (isVectorX === ridgeVectorX && isVectorY === ridgeVectorY) { oppositeMidX = Big(intersection.x) oppositeMidY = Big(intersection.y) } } }) const ridgeLine = drawRidgeLine([currentMidX, currentMidY, oppositeMidX, oppositeMidY], canvas, roof, textMode) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ } } }) /** 박공지붕에서 파생되는 마루를 그린다. 첫번째에서 처리 하지 못한 라인이 있는 경우 */ drawGableRidgeSecond.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2, size } = currentBaseLine let beforePrevBaseLine, afterNextBaseLine /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree /** 다음 라인의 경사 */ const nextDegree = nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree /** 현재 라인의 경사 */ const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const currentVectorX = Big(currentLine.x2).minus(currentLine.x1) const currentVectorY = Big(currentLine.y2).minus(currentLine.y1) const checkVectorX = Big(nextLine.x2).minus(Big(nextLine.x1)) const checkVectorY = Big(nextLine.y2).minus(Big(nextLine.y1)) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) const checkSize = Big(10) const checkPoints = { x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.toNumber()))).toNumber(), y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.toNumber()))).toNumber(), } if (!checkWallPolygon.inPolygon(checkPoints)) { const currentMidEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(), y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(), }, } let oppositeLines = [] baseLines .filter((line, index) => { let nextLine = baseLines[(index + 1) % baseLines.length] let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] if ( (gableType.includes(nextLine.attributes.type) && gableType.includes(prevLine.attributes.type)) || (eavesType.includes(nextLine.attributes.type) && eavesType.includes(prevLine.attributes.type)) ) { const angle = calculateAngle(line.startPoint, line.endPoint) switch (currentAngle) { case 90: return angle === -90 case -90: return angle === 90 case 0: return angle === 180 case 180: return angle === 0 } } return false }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(currentMidEdge, lineEdge) if (intersection) { oppositeLines.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } }) if (oppositeLines.length === 0) { return } const oppositeLine = oppositeLines.sort((a, b) => a.size - b.size)[0] let points = [] if (eavesType.includes(oppositeLine.line.attributes.type)) { const oppositeCurrentLine = oppositeLine.line let oppositePrevLine, oppositeNextLine baseLines.forEach((line, index) => { if (line === oppositeCurrentLine) { oppositePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] oppositeNextLine = baseLines[(index + 1) % baseLines.length] } }) if (gableType.includes(oppositeNextLine.attributes.type) && gableType.includes(oppositePrevLine.attributes.type)) { if (currentVectorX.eq(0)) { const centerX = currentMidX.plus(oppositeLine.intersection.x).div(2).toNumber() points = [centerX, currentLine.y1, centerX, currentLine.y2] } else { const centerY = currentMidY.plus(oppositeLine.intersection.y).div(2).toNumber() points = [currentLine.x1, centerY, currentLine.x2, centerY] } } if (eavesType.includes(oppositeNextLine.attributes.type) && eavesType.includes(oppositePrevLine.attributes.type)) { /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(oppositePrevLine, oppositeCurrentLine) let nextVector = getHalfAngleVector(oppositeCurrentLine, oppositeNextLine) let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(oppositeCurrentLine.x1).plus(Big(prevHipVector.x).times(10)), y: Big(oppositeCurrentLine.y1).plus(Big(prevHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(oppositeCurrentLine.x2).plus(Big(nextHipVector.x).times(10)), y: Big(oppositeCurrentLine.y2).plus(Big(nextHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } } /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ let hipLength = Big(oppositeCurrentLine.attributes.planeSize) .div(2) .pow(2) .plus(Big(oppositeCurrentLine.attributes.planeSize).div(2).pow(2)) .sqrt() .div(10) .round(2) const ridgeEndPoint = { x: Big(oppositeCurrentLine.x1).plus(hipLength.times(prevHipVector.x)).round(1), y: Big(oppositeCurrentLine.y1).plus(hipLength.times(prevHipVector.y)).round(1), } const prevHypotenuse = Big(oppositePrevLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() const prevHipPoints = { x1: Big(oppositeCurrentLine.x1).plus(prevHypotenuse.times(prevHipVector.x.neg())).round(1).toNumber(), y1: Big(oppositeCurrentLine.y1).plus(prevHypotenuse.times(prevHipVector.y.neg())).round(1).toNumber(), x2: ridgeEndPoint.x.toNumber(), y2: ridgeEndPoint.y.toNumber(), } const nextHypotenuse = Big(oppositeNextLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() const nextHipPoints = { x1: Big(oppositeCurrentLine.x2).plus(nextHypotenuse.times(nextHipVector.x.neg())).round(1).toNumber(), y1: Big(oppositeCurrentLine.y2).plus(nextHypotenuse.times(nextHipVector.y.neg())).round(1).toNumber(), x2: ridgeEndPoint.x.toNumber(), y2: ridgeEndPoint.y.toNumber(), } const prevIntersection = findRoofIntersection(roof, prevHipPoints, ridgeEndPoint) const nextIntersection = findRoofIntersection(roof, nextHipPoints, ridgeEndPoint) if (prevIntersection) { const prevHip = drawHipLine( [prevIntersection.intersection.x, prevIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], canvas, roof, textMode, null, prevDegree, prevDegree, ) baseHipLines.push({ x1: oppositeCurrentLine.x1, y1: oppositeCurrentLine.y1, x2: ridgeEndPoint.x, y2: ridgeEndPoint.y, line: prevHip, }) } if (nextIntersection) { const nextHip = drawHipLine( [nextIntersection.intersection.x, nextIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], canvas, roof, textMode, null, nextDegree, nextDegree, ) baseHipLines.push({ x1: ridgeEndPoint.x, y1: ridgeEndPoint.y, x2: oppositeCurrentLine.x2, y2: oppositeCurrentLine.y2, line: nextHip, }) } const ridgeVectorX = Math.sign(currentMidX.minus(ridgeEndPoint.x).toNumber()) const ridgeVectorY = Math.sign(currentMidY.minus(ridgeEndPoint.y).toNumber()) const ridgePoints = { x1: currentMidX.plus(Big(currentLine.attributes.offset).times(ridgeVectorX)).toNumber(), y1: currentMidY.plus(Big(currentLine.attributes.offset).times(ridgeVectorY)).toNumber(), x2: ridgeEndPoint.x.toNumber(), y2: ridgeEndPoint.y.toNumber(), } const ridgeIntersection = findRoofIntersection(roof, ridgePoints, { x: Big(ridgePoints.x2), y: Big(ridgePoints.y2) }) if (ridgeIntersection) { points = [ridgeIntersection.intersection.x, ridgeIntersection.intersection.y, ridgeEndPoint.x, ridgeEndPoint.y] } } } else { if (currentVectorX.eq(0)) { points = [oppositeLine.intersection.x, currentLine.y1, oppositeLine.intersection.x, currentLine.y2] } else { points = [currentLine.x1, oppositeLine.intersection.y, currentLine.x2, oppositeLine.intersection.y] } } if (baseRidgeCount < getMaxRidge(baseLines.length)) { const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ } canvas .getObjects() .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() } else { const oppositeLines = baseLines.filter((line) => { const lineAngle = calculateAngle(line.startPoint, line.endPoint) switch (currentAngle) { case 90: return lineAngle === -90 case -90: return lineAngle === 90 case 0: return lineAngle === 180 case 180: return lineAngle === 0 } }) if (oppositeLines.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { let ridgePoints = [] const oppositeLine = oppositeLines.sort((a, b) => { let diffCurrentA, diffCurrentB if (Math.sign(currentVectorX) === 0) { diffCurrentA = currentMidY.minus(a.y1).abs() diffCurrentB = currentMidY.minus(b.y1).abs() } else { diffCurrentA = currentMidX.minus(a.x1).abs() diffCurrentB = currentMidX.minus(b.x1).abs() } return diffCurrentA.minus(diffCurrentB).toNumber() })[0] const prevOffset = prevLine.attributes.offset const nextOffset = nextLine.attributes.offset if (Math.sign(currentVectorX) === 0) { const prevY = Big(currentLine.y1) .plus(Big(Math.sign(currentVectorY)).neg().times(prevOffset)) .toNumber() const nextY = Big(currentLine.y2) .plus(Big(Math.sign(currentVectorY)).times(nextOffset)) .toNumber() const midX = Big(currentLine.x1).plus(oppositeLine.x1).div(2).toNumber() ridgePoints = [midX, prevY, midX, nextY] } else { const prevX = Big(currentLine.x1) .plus(Big(Math.sign(currentVectorX)).neg().times(prevOffset)) .toNumber() const nextX = Big(currentLine.x2) .plus(Big(Math.sign(currentVectorX)).times(nextOffset)) .toNumber() const midY = Big(currentLine.y1).plus(oppositeLine.y1).div(2).toNumber() ridgePoints = [prevX, midY, nextX, midY] } const ridge = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseGableRidgeLines.push(ridge) baseRidgeCount++ } } canvas .getObjects() .filter((obj) => obj.name === 'checkLine' || obj.name === 'checkPoint') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() }) const uniqueRidgeLines = [] /** 중복제거 */ baseGableRidgeLines.forEach((currentLine, index) => { if (index === 0) { uniqueRidgeLines.push(currentLine) } else { const duplicateLines = uniqueRidgeLines.filter( (line) => (currentLine.x1 === line.x1 && currentLine.y1 === line.y1 && currentLine.x2 === line.x2 && currentLine.y2 === line.y2) || (currentLine.x1 === line.x2 && currentLine.y1 === line.y2 && currentLine.x2 === line.x1 && currentLine.y2 === line.y1), ) if (duplicateLines.length === 0) { uniqueRidgeLines.push(currentLine) } } }) baseGableRidgeLines = uniqueRidgeLines console.log('baseGableRidgeLines : ', baseGableRidgeLines) /** 박공지붕 polygon 생성 */ drawGablePolygonFirst.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2 } = currentBaseLine let prevLineRidges = [], nextLineRidges = [] const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const prevVectorX = Math.sign(prevLine.x2 - prevLine.x1) const prevVectorY = Math.sign(prevLine.y2 - prevLine.y1) const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree const currentOffset = currentLine.attributes.offset const prevOffset = prevLine.attributes.offset const nextOffset = nextLine.attributes.offset let roofX1, roofY1, roofX2, roofY2 if (currentVectorX === 0) { roofX1 = Big(x1).plus(Big(currentLine.attributes.offset).times(prevVectorX)) roofX2 = roofX1 roofY1 = Big(y1).plus(Big(prevLine.attributes.offset).times(currentVectorY * -1)) roofY2 = Big(y2).plus(Big(nextLine.attributes.offset).times(currentVectorY)) } else { roofX1 = Big(x1).plus(Big(prevLine.attributes.offset).times(currentVectorX * -1)) roofX2 = Big(x2).plus(Big(nextLine.attributes.offset).times(currentVectorX)) roofY1 = Big(y1).plus(Big(currentLine.attributes.offset).times(prevVectorY)) roofY2 = roofY1 } let prevRoofLine, nextRoofLine const roofEdge = { vertex1: { x: roofX1.toNumber(), y: roofY1.toNumber() }, vertex2: { x: roofX2.toNumber(), y: roofY2.toNumber() } } roof.lines .filter((line) => (currentVectorX === 0 ? line.y1 === line.y2 : line.x1 === line.x2)) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(roofEdge, lineEdge) if (intersection) { if (roofX1.eq(intersection.x) && roofY1.eq(intersection.y)) { prevRoofLine = line } if (roofX2.eq(intersection.x) && roofY2.eq(intersection.y)) { nextRoofLine = line } } }) const prevRoofEdge = { vertex1: { x: prevRoofLine.x2, y: prevRoofLine.y2 }, vertex2: { x: prevRoofLine.x1, y: prevRoofLine.y1 } } const nextRoofEdge = { vertex1: { x: nextRoofLine.x1, y: nextRoofLine.y1 }, vertex2: { x: nextRoofLine.x2, y: nextRoofLine.y2 } } baseGableRidgeLines.forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const prevIs = edgesIntersection(prevRoofEdge, ridgeEdge) const nextIs = edgesIntersection(nextRoofEdge, ridgeEdge) if (prevIs) { prevLineRidges.push({ ridge, size: calcLinePlaneSize({ x1: roofX1, y1: roofY1, x2: prevIs.x, y2: prevIs.y }), }) } if (nextIs) { nextLineRidges.push({ ridge, size: calcLinePlaneSize({ x1: roofX2, y1: roofY2, x2: nextIs.x, y2: nextIs.y }), }) } }) const polygonPoints = [] let prevLineRidge, nextLineRidge if (prevLineRidges.length > 0) { prevLineRidges = prevLineRidges .filter((line) => { const ridge = line.ridge if (currentVectorX === 0) { return ridge.y1 === roofY1.toNumber() || ridge.y2 === roofY1.toNumber() } else { return ridge.x1 === roofX1.toNumber() || ridge.x2 === roofX1.toNumber() } }) .sort((a, b) => a.size - b.size) prevLineRidge = prevLineRidges[0].ridge } if (nextLineRidges.length > 0) { nextLineRidges = nextLineRidges .filter((line) => { const ridge = line.ridge if (currentVectorX === 0) { return ridge.y1 === roofY2.toNumber() || ridge.y2 === roofY2.toNumber() } else { return ridge.x1 === roofX2.toNumber() || ridge.x2 === roofX2.toNumber() } }) .sort((a, b) => a.size - b.size) nextLineRidge = nextLineRidges[0].ridge } /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: prevLineRidge.x1, y: roofY1.toNumber() }, vertex2: { x: roofX1.toNumber(), y: roofY1.toNumber() } } } else { checkEdge = { vertex1: { x: roofX1.toNumber(), y: prevLineRidge.y1 }, vertex2: { x: roofX1.toNumber(), y: roofY1.toNumber() } } } const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) const intersectPoints = [] roof.lines .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(checkEdge, lineEdge) if (is) { const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) const isLineOtherPoint = is.x === line.x1 && is.y === line.y1 ? { x: line.x2, y: line.y2 } : { x: line.x1, y: line.y1 } const lineVectorX = Math.sign(isLineOtherPoint.x - is.x) const lineVectorY = Math.sign(isLineOtherPoint.y - is.y) if (checkVectorX === isVectorX && checkVectorY === isVectorY && lineVectorX === currentVectorX && lineVectorY === currentVectorY) { intersectPoints.push(is) } } }) let intersect = intersectPoints[0] if (currentVectorX === 0) { polygonPoints.push({ x: intersect.x, y: roofY1.toNumber() }, { x: intersect.x, y: roofY2.toNumber() }) } else { polygonPoints.push({ x: roofX1.toNumber(), y: intersect.y }, { x: roofX2.toNumber(), y: intersect.y }) } if (prevLineRidge === nextLineRidge) { /** 4각*/ polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) } else { /** 6각이상*/ let isOverLap = currentVectorX === 0 ? (prevLineRidge.y1 <= nextLineRidge.y1 && prevLineRidge.y2 >= nextLineRidge.y1) || (prevLineRidge.y1 >= nextLineRidge.y1 && prevLineRidge.y2 <= nextLineRidge.y1) || (prevLineRidge.y1 <= nextLineRidge.y2 && prevLineRidge.y2 >= nextLineRidge.y2) || (prevLineRidge.y1 >= nextLineRidge.y2 && prevLineRidge.y2 <= nextLineRidge.y2) : (prevLineRidge.x1 <= nextLineRidge.x1 && prevLineRidge.x2 >= nextLineRidge.x1) || (prevLineRidge.x1 >= nextLineRidge.x1 && prevLineRidge.x2 <= nextLineRidge.x1) || (prevLineRidge.x1 <= nextLineRidge.x2 && prevLineRidge.x2 >= nextLineRidge.x2) || (prevLineRidge.x1 >= nextLineRidge.x2 && prevLineRidge.x2 <= nextLineRidge.x2) if (isOverLap) { const prevDistance = currentVectorX === 0 ? Math.abs(prevLineRidge.x1 - roofX1.toNumber()) : Math.abs(prevLineRidge.y1 - roofY1.toNumber()) const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - roofX1.toNumber()) : Math.abs(nextLineRidge.y1 - roofY1.toNumber()) /** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */ if (prevDistance === nextDistance) { console.log('prevDistance === nextDistance') } else if (prevDistance < nextDistance) { polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }) /** 이전라인과 교차한 마루의 포인트*/ const prevRidgePoint1 = currentVectorX === 0 ? roofY1.toNumber() === prevLineRidge.y1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } : roofX1.toNumber() === prevLineRidge.x1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } polygonPoints.push(prevRidgePoint1) /** 다음 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ const checkRidgePoint = currentVectorX === 0 ? roofY2.toNumber() !== nextLineRidge.y1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } : roofX2.toNumber() !== nextLineRidge.x1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } const prevRidgePoint2 = currentVectorX === 0 ? { x: prevRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: prevRidgePoint1.y } polygonPoints.push(prevRidgePoint2) } else { polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) /** 다음라인과 교차한 마루의 포인트*/ const nextRidgePoint1 = currentVectorX === 0 ? roofY2.toNumber() === nextLineRidge.y1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } : roofX2.toNumber() === nextLineRidge.x1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } polygonPoints.push(nextRidgePoint1) /** 이전 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ const checkRidgePoint = currentVectorX === 0 ? roofY1.toNumber() !== prevLineRidge.y1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } : roofX1.toNumber() !== prevLineRidge.x1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } const nextRidgePoint2 = currentVectorX === 0 ? { x: nextRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: nextRidgePoint1.y } polygonPoints.push(nextRidgePoint2) } } else { /** 마루가 겹치지 않을때 */ const otherRidgeLines = [] baseGableRidgeLines .filter((ridge) => ridge !== prevLineRidge && ridge !== nextLineRidge) .filter((ridge) => (currentVectorX === 0 ? ridge.x1 === ridge.x2 : ridge.y1 === ridge.y2)) .filter((ridge) => currentVectorX === 0 ? nextVectorX === Math.sign(nextLine.x1 - ridge.x1) : nextVectorY === Math.sign(nextLine.y1 - ridge.y1), ) .forEach((ridge) => { const size = currentVectorX === 0 ? Math.abs(nextLine.x1 - ridge.x1) : Math.abs(nextLine.y1 - ridge.y1) otherRidgeLines.push({ ridge, size }) }) if (otherRidgeLines.length > 0) { const otherRidge = otherRidgeLines.sort((a, b) => a.size - b.size)[0].ridge /** * otherRidge이 prevRidgeLine, nextRidgeLine 과 currentLine의 사이에 있는지 확인해서 분할하여 작업 * 지붕의 덮힘이 다르기 때문 */ const isInside = currentVectorX === 0 ? Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - prevLineRidge.x1) && Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - nextLineRidge.x1) : Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - prevLineRidge.y1) && Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - nextLineRidge.y1) if (isInside) { polygonPoints.push( { x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }, { x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }, ) let ridgeAllPoints = [ { x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }, { x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }, ] let ridgePoints = [] ridgeAllPoints.forEach((point) => { let isOnLine = false roof.lines.forEach((line) => { if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { isOnLine = true } }) if (!isOnLine) { ridgePoints.push(point) } }) if (ridgePoints.length === 2) { if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { polygonPoints.push({ x: otherRidge.x1, y: ridgePoints[0].y }, { x: otherRidge.x1, y: ridgePoints[1].y }) } else { polygonPoints.push({ x: ridgePoints[0].x, y: otherRidge.y1 }, { x: ridgePoints[1].x, y: otherRidge.y1 }) } } } else { polygonPoints.push({ x: otherRidge.x1, y: otherRidge.y1 }, { x: otherRidge.x2, y: otherRidge.y2 }) let ridgePoints = [ { x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }, { x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }, ] ridgePoints.forEach((point) => { let isOnLine = false roof.lines.forEach((line) => { if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { isOnLine = true } }) if (isOnLine) { polygonPoints.push(point) } }) if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { const prevY = (prevLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= prevLineRidge.y2) || (prevLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= prevLineRidge.y2) ? otherRidge.y1 : otherRidge.y2 const nextY = (nextLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= nextLineRidge.y2) || (nextLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= nextLineRidge.y2) ? otherRidge.y1 : otherRidge.y2 polygonPoints.push({ x: prevLineRidge.x1, y: prevY }, { x: nextLineRidge.x1, y: nextY }) } else { const prevX = (prevLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= prevLineRidge.x2) || (prevLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= prevLineRidge.x2) ? otherRidge.x1 : otherRidge.x2 const nextX = (nextLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= nextLineRidge.x2) || (nextLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= nextLineRidge.x2) ? otherRidge.x1 : otherRidge.x2 polygonPoints.push({ x: prevX, y: prevLineRidge.y1 }, { x: nextX, y: nextLineRidge.y1 }) } } } } } const sortedPolygonPoints = getSortedPoint(polygonPoints) /** 외벽선 밖으로 나가있는 포인트*/ const outsidePoints = polygonPoints.filter((point) => { let isOutside = true roof.lines.forEach((line) => { if ( (line.x1 <= point.x && line.x2 >= point.x && line.y1 <= point.y && line.y2 >= point.y) || (line.x1 >= point.x && line.x2 <= point.x && line.y1 >= point.y && line.y2 <= point.y) ) { isOutside = false } }) baseGableRidgeLines.forEach((line) => { if ( (line.x1 <= point.x && line.x2 >= point.x && line.y1 <= point.y && line.y2 >= point.y) || (line.x1 >= point.x && line.x2 <= point.x && line.y1 >= point.y && line.y2 <= point.y) ) { isOutside = false } }) return isOutside }) if (outsidePoints.length > 0) { sortedPolygonPoints.forEach((currentPoint, index) => { if (outsidePoints.includes(currentPoint)) { const prevPoint = sortedPolygonPoints[(index - 1 + sortedPolygonPoints.length) % sortedPolygonPoints.length] const nextPoint = sortedPolygonPoints[(index + 1) % sortedPolygonPoints.length] const vectorX = Math.sign(currentPoint.x - prevPoint.x) const vectorY = Math.sign(currentPoint.y - prevPoint.y) const checkEdge = { vertex1: { x: prevPoint.x, y: prevPoint.y }, vertex2: { x: currentPoint.x, y: currentPoint.y } } const intersectPoints = [] roof.lines .filter((line) => (vectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(checkEdge, lineEdge) if (is && !is.isIntersectionOutside) { const isVectorX = Math.sign(is.x - prevPoint.x) const isVectorY = Math.sign(is.y - prevPoint.y) if ((vectorX === 0 && vectorY === isVectorY) || (vectorY === 0 && vectorX === isVectorX)) { intersectPoints.push(is) } } }) if (intersectPoints.length > 0) { const intersection = intersectPoints[0] if (vectorX === 0) { currentPoint.y = intersection.y nextPoint.y = intersection.y } else { currentPoint.x = intersection.x nextPoint.x = intersection.x } } } }) } sortedPolygonPoints.forEach((startPoint, index) => { let endPoint if (index === sortedPolygonPoints.length - 1) { endPoint = sortedPolygonPoints[0] } else { endPoint = sortedPolygonPoints[index + 1] } const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) if (currentVectorX === 0) { if (Math.sign(startPoint.x - endPoint.x) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } else { if (Math.sign(startPoint.y - endPoint.y) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) }) }) drawGablePolygonSecond.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2 } = currentBaseLine let prevLineRidges = [], nextLineRidges = [] const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const prevVectorX = Math.sign(prevLine.x2 - prevLine.x1) const prevVectorY = Math.sign(prevLine.y2 - prevLine.y1) const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree const currentOffset = currentLine.attributes.offset const prevOffset = prevLine.attributes.offset const nextOffset = nextLine.attributes.offset const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const polygonPoints = [] if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { const currentRidge = baseGableRidgeLines.find((line) => currentVectorX === 0 ? (line.y1 === y1 && line.y2 === y2) || (line.y1 === y2 && line.y2 === y1) : (line.x1 === x1 && line.x2 === x2) || (line.x1 === x2 && line.x2 === x1), ) if (currentRidge) { const ridgeVectorX = Math.sign(currentRidge.x1 - currentRidge.x2) const ridgeVectorY = Math.sign(currentRidge.y1 - currentRidge.y2) let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentLine.x1, y: currentRidge.y1 } } } else { checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x1, y: currentLine.y1 } } } const isRoofLines = [] roof.lines .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(checkEdge, lineEdge) if (is) { const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) if ((ridgeVectorX === 0 && checkVectorX === isVectorX) || (ridgeVectorY === 0 && checkVectorY === isVectorY)) { const size = ridgeVectorX === 0 ? Math.abs(checkEdge.vertex1.x - is.x) : Math.abs(checkEdge.vertex1.y - is.y) isRoofLines.push({ line, size }) } } }) isRoofLines.sort((a, b) => a.size - b.size) const roofLine = isRoofLines[0].line polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) if (ridgeVectorX === 0) { polygonPoints.push({ x: roofLine.x1, y: currentRidge.y1 }, { x: roofLine.x1, y: currentRidge.y2 }) } else { polygonPoints.push({ x: currentRidge.x1, y: roofLine.y1 }, { x: currentRidge.x2, y: roofLine.y1 }) } } } else { const prevEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevLine.x2, y: prevLine.y2 } } const prevRidge = baseGableRidgeLines.find((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const is = edgesIntersection(prevEdge, ridgeEdge) if (is && !is.isIntersectionOutside) { return ridge } }) const nextEdge = { vertex1: { x: nextLine.x1, y: nextLine.y1 }, vertex2: { x: nextLine.x2, y: nextLine.y2 } } const nextRidge = baseGableRidgeLines.find((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const is = edgesIntersection(nextEdge, ridgeEdge) if (is && !is.isIntersectionOutside) { return ridge } }) let currentRidge if (prevRidge) { if ( currentVectorX === 0 && ((prevRidge.y1 <= currentLine.y1 && prevRidge.y2 >= currentLine.y1 && prevRidge.y1 <= currentLine.y2 && prevRidge.y2 >= currentLine.y2) || (prevRidge.y1 >= currentLine.y1 && prevRidge.y2 <= currentLine.y1 && prevRidge.y1 >= currentLine.y2 && prevRidge.y2 <= currentLine.y2)) ) { currentRidge = prevRidge } if ( currentVectorY === 0 && ((prevRidge.x1 <= currentLine.x1 && prevRidge.x2 >= currentLine.x1 && prevRidge.x1 <= currentLine.x2 && prevRidge.x2 >= currentLine.x2) || (prevRidge.x1 >= currentLine.x1 && prevRidge.x2 <= currentLine.x1 && prevRidge.x1 >= currentLine.x2 && prevRidge.x2 <= currentLine.x2)) ) { currentRidge = prevRidge } } if (nextRidge) { if ( currentVectorX === 0 && ((nextRidge.y1 <= currentLine.y1 && nextRidge.y2 >= currentLine.y1 && nextRidge.y1 <= currentLine.y2 && nextRidge.y2 >= currentLine.y2) || (nextRidge.y1 >= currentLine.y1 && nextRidge.y2 <= currentLine.y1 && nextRidge.y1 >= currentLine.y2 && nextRidge.y2 <= currentLine.y2)) ) { currentRidge = nextRidge } if ( currentVectorY === 0 && ((nextRidge.x1 <= currentLine.x1 && nextRidge.x2 >= currentLine.x1 && nextRidge.x1 <= currentLine.x2 && nextRidge.x2 >= currentLine.x2) || (nextRidge.x1 >= currentLine.x1 && nextRidge.x2 <= currentLine.x1 && nextRidge.x1 >= currentLine.x2 && nextRidge.x2 <= currentLine.x2)) ) { currentRidge = nextRidge } } if (currentRidge) { polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentLine.x1, y: currentRidge.y1 }, } } else { checkEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x1, y: currentLine.y1 }, } } const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) const intersectPoints = [] roof.lines .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(checkEdge, lineEdge) if (is) { const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) const isLineOtherPoint = is.x === line.x1 && is.y === line.y1 ? { x: line.x2, y: line.y2 } : { x: line.x1, y: line.y1, } const isInPoint = checkVectorX === 0 ? (currentRidge.x1 <= isLineOtherPoint.x && isLineOtherPoint.x <= currentRidge.x2) || (currentRidge.x1 >= isLineOtherPoint.x && isLineOtherPoint.x >= currentRidge.x2) : (currentRidge.y1 <= isLineOtherPoint.y && isLineOtherPoint.y <= currentRidge.y2) || (currentRidge.y1 >= isLineOtherPoint.y && isLineOtherPoint.y >= currentRidge.y2) if (checkVectorX === isVectorX && checkVectorY === isVectorY && isInPoint) { const size = Big(checkEdge.vertex1.x).minus(is.x).abs().pow(2).plus(Big(checkEdge.vertex1.y).minus(is.y).abs().pow(2)).sqrt() intersectPoints.push({ is, size }) } } }) intersectPoints.sort((a, b) => a.size - b.size) let intersect = intersectPoints[0].is if (currentVectorX === 0) { polygonPoints.push({ x: intersect.x, y: currentRidge.y1 }, { x: intersect.x, y: currentRidge.y2 }) } else { polygonPoints.push({ x: currentRidge.x1, y: intersect.y }, { x: currentRidge.x2, y: intersect.y }) } } } const sortedPolygonPoints = getSortedPoint(polygonPoints) sortedPolygonPoints.forEach((startPoint, index) => { let endPoint if (index === sortedPolygonPoints.length - 1) { endPoint = sortedPolygonPoints[0] } else { endPoint = sortedPolygonPoints[index + 1] } const hipLine = drawHipLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], canvas, roof, textMode, null, currentDegree, currentDegree) if (currentVectorX === 0) { if (Math.sign(startPoint.x - endPoint.x) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } else { if (Math.sign(startPoint.y - endPoint.y) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) }) }) /** ⨆ 모양 처마에 추녀마루를 그린다. */ drawEavesFirstLines.forEach((current) => { // 확인용 라인, 포인트 제거 const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2, size } = currentBaseLine let beforePrevLine, afterNextLine /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree /** 다음 라인의 경사 */ const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ drawBaseLines.forEach((line, index) => { if (line === prevBaseLine) { beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] } if (line === nextBaseLine) { afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] } }) /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevLine, currentLine) let nextVector = getHalfAngleVector(currentLine, nextLine) let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } /** 각 라인의 흐름 방향을 확인한다. */ const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const beforePrevAngle = calculateAngle(beforePrevLine.line.startPoint, beforePrevLine.line.endPoint) const afterNextAngle = calculateAngle(afterNextLine.line.startPoint, afterNextLine.line.endPoint) /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ let currentSize = Big(size).div(10) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(10)), y: Big(y1).plus(Big(prevHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(10)), y: Big(y2).plus(Big(nextHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } } /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() /** * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. */ if (beforePrevLine === afterNextLine || (currentAngle === beforePrevAngle && currentAngle === afterNextAngle)) { const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) const currentMidX = Big(x1).plus(Big(x2)).div(2) const currentMidY = Big(y1).plus(Big(y2)).div(2) const midLineEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: currentMidX.plus(currentSize.times(Math.sign(xVector))).toNumber(), y: currentMidY.plus(currentSize.times(Math.sign(yVector))).toNumber(), }, } /** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/ baseLines .filter((line) => line !== currentLine) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(midLineEdge, lineEdge) /** 현재라인의 길이만큼 거리가 모자라면 해당 길이 만큼을 현재라인의 길이로 판단하고 나머지 계산을 진행한다.*/ if (intersection && !intersection.isIntersectionOutside) { const intersectionSize = Big(intersection.x) .minus(Big(currentMidX)) .plus(Big(intersection.y).minus(Big(currentMidY))) if (intersectionSize.lt(currentSize)) { currentSize = intersectionSize } } }) hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() } else { if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) { console.log('4각 아님') const beforePrevX1 = beforePrevLine.x1, beforePrevY1 = beforePrevLine.y1, beforePrevX2 = beforePrevLine.x2, beforePrevY2 = beforePrevLine.y2 const afterNextX1 = afterNextLine.x1, afterNextY1 = afterNextLine.y1, afterNextX2 = afterNextLine.x2, afterNextY2 = afterNextLine.y2 /** beforePrevLine 과 afterNextLine 을 연결하는 사각형의 경우 6각으로 판단 */ const connectBAPoint = { x1: afterNextX2, y1: afterNextY2, x2: beforePrevX1, y2: beforePrevY1 } const isConnect = baseLines.filter( (line) => line.x1 === connectBAPoint.x1 && line.y1 === connectBAPoint.y1 && line.x2 === connectBAPoint.x2 && line.y2 === connectBAPoint.y2, ).length > 0 /** 6각 */ // console.log('isConnect :', isConnect) if (isConnect) { const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() const intersectBaseLine = [] if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { const prevEndPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(checkScale)), y: Big(y1).plus(Big(prevHipVector.y).times(checkScale)), } baseLines .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) .forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectBaseLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x1)) .pow(2) .plus(Big(intersection.y).minus(Big(y1)).pow(2)) .sqrt(), }) } }) } if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { const nextEndPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(checkScale)), y: Big(y2).plus(Big(nextHipVector.y).times(checkScale)), } baseLines .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) .forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectBaseLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x2)) .pow(2) .plus(Big(intersection.y).minus(Big(y2)).pow(2)) .sqrt(), }) } }) } const intersection = intersectBaseLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectBaseLine[0]) if (intersection) { hipLength = intersection.distance } } else { const rightAngleLine = baseLines .filter( (line) => line !== prevLine && line !== nextLine && (prevAngle === calculateAngle(line.startPoint, line.endPoint) || nextAngle === calculateAngle(line.startPoint, line.endPoint)), ) .filter((line) => { const index = baseLines.indexOf(line) const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const nextLine = baseLines[(index + 1) % baseLines.length] const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) switch (prevAngle) { case 90: return nextAngle === -90 case -90: return nextAngle === 90 case 0: return nextAngle === 180 case 180: return nextAngle === 0 } }) const oppositeCurrentLine = baseLines .filter((line) => { const angle = calculateAngle(line.startPoint, line.endPoint) switch (currentAngle) { case 90: return angle === -90 case -90: return angle === 90 case 0: return angle === 180 case 180: return angle === 0 } }) .filter((line) => { const index = baseLines.indexOf(line) const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const nextLine = baseLines[(index + 1) % baseLines.length] const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) switch (prevAngle) { case 90: return nextAngle === -90 case -90: return nextAngle === 90 case 0: return nextAngle === 180 case 180: return nextAngle === 0 } }) let checkHipPoints if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { checkHipPoints = [ x1, y1, Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), ] } if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { checkHipPoints = [ x2, y2, Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), ] } if (checkHipPoints) { const intersectPoints = [] rightAngleLine.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection) { intersectPoints.push({ intersection, distance: Big(intersection.x) .minus(Big(checkHipPoints[0])) .pow(2) .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) .sqrt(), }) } }) oppositeCurrentLine.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection) { intersectPoints.push({ intersection, distance: Big(intersection.x) .minus(Big(checkHipPoints[0])) .pow(2) .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) .sqrt(), }) } }) const intersection = intersectPoints.reduce((prev, current) => (prev.distance.lt(current.distance) ? prev : current), intersectPoints[0]) if (intersection) { hipLength = intersection.distance.div(2) } } } } } let prevHipLine, nextHipLine /** 이전라인과의 연결지점에 추녀마루를 그린다. */ if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { const prevEndPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(1), y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(1), } const intersectRidgeLine = [] baseRidgeLines.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x1)) .pow(2) .plus(Big(intersection.y).minus(Big(y1)).pow(2)) .sqrt(), }) } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) if (intersectRidge) { prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) } const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() scale = scale.eq(0) ? Big(1) : scale /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ const hipEdge = { vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, vertex2: prevEndPoint, } let intersectPoints = [] /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if ( intersection && !intersection.isIntersectionOutside && Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) ) { const intersectSize = prevEndPoint.x .minus(Big(intersection.x)) .pow(2) .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, }) } }) intersectPoints = intersectPoints.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) // console.log('intersectPoints', intersectPoints) /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ if (intersectPoints && intersectPoints.intersection) { console.log('prevDegree', prevDegree, 'currentDegree', currentDegree) prevHipLine = drawHipLine( [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], canvas, roof, textMode, null, prevDegree, currentDegree, ) baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) } } if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { const nextEndPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(1), y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(1), } const intersectRidgeLine = [] baseRidgeLines.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x1)) .pow(2) .plus(Big(intersection.y).minus(Big(y1)).pow(2)) .sqrt(), }) } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) if (intersectRidge) { nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) } const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() scale = scale.eq(0) ? Big(1) : scale const hipEdge = { vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, vertex2: nextEndPoint, } let intersectPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if ( intersection && !intersection.isIntersectionOutside && Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) ) { const intersectSize = nextEndPoint.x .minus(Big(intersection.x)) .pow(2) .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, }) } }) intersectPoints = intersectPoints.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) if (intersectPoints && intersectPoints.intersection) { nextHipLine = drawHipLine( [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], canvas, roof, textMode, null, prevDegree, currentDegree, ) baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) } } console.log('prevHipLine : ', prevHipLine) console.log('nextHipLine : ', nextHipLine) /** 두 선이 교차하면 해당 포인트까지로 선 조정*/ if (prevHipLine !== undefined && nextHipLine !== undefined) { const prevEdge = { vertex1: { x: prevHipLine.x1, y: prevHipLine.y1 }, vertex2: { x: prevHipLine.x2, y: prevHipLine.y2 } } const nextEdge = { vertex1: { x: nextHipLine.x1, y: nextHipLine.y1 }, vertex2: { x: nextHipLine.x2, y: nextHipLine.y2 } } const intersection = edgesIntersection(prevEdge, nextEdge) console.log('intersection : ', intersection) if (intersection) { const checkPoint = new fabric.Circle({ left: intersection.x, top: intersection.y, radius: 4, fill: 'yellow', parentId: roofId, name: 'checkPoint', }) canvas.add(checkPoint) canvas.renderAll() /** 포인트 조정*/ baseHipLines .filter((line) => line.line === prevHipLine || line.line === nextHipLine) .forEach((line) => { line.x2 = intersection.x line.y2 = intersection.y line.line.set({ x2: intersection.x, y2: intersection.y }) }) prevHipLine.x2 = intersection.x prevHipLine.y2 = intersection.y const prevSize = reCalculateSize(prevHipLine) prevHipLine.attributes.planeSize = prevSize.planeSize prevHipLine.attributes.actualSize = prevSize.actualSize prevHipLine.fire('modified') nextHipLine.x2 = intersection.x nextHipLine.y2 = intersection.y const nextSize = reCalculateSize(nextHipLine) nextHipLine.attributes.planeSize = nextSize.planeSize nextHipLine.attributes.actualSize = nextSize.actualSize nextHipLine.fire('modified') canvas.renderAll() } } /** 두 추녀마루가 한점에서 만나는 경우 해당 점을 기점으로 마루를 작성한다.*/ if ( prevHipLine !== undefined && nextHipLine !== undefined && Big(prevHipLine.x2).minus(Big(nextHipLine.x2)).abs().lte(1) && Big(prevHipLine.y2).minus(Big(nextHipLine.y2)).abs().lte(1) ) { const startPoint = { x: prevHipLine.x2, y: prevHipLine.y2 } let ridgeSize = 0 const currentMidX = Big(currentLine.x2).plus(Big(currentLine.x1)).div(2) const currentMidY = Big(currentLine.y2).plus(Big(currentLine.y1)).div(2) const xVector = Big(currentMidX).minus(Big(startPoint.x)).round(0, Big.roundDown) const yVector = Big(currentMidY).minus(Big(startPoint.y)).round(0, Big.roundDown) console.log('beforePrevLine', beforePrevLine) if (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) { console.log('박공지붕 확인 후 마루 작성 ') const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line const oppositeAngle = calculateAngle(oppositeLine.startPoint, oppositeLine.endPoint) console.log('currentAngle', currentAngle, 'oppositeAngle', oppositeAngle) let checkEdge if (Math.sign(oppositeLine.x1 - oppositeLine.x2) === 0) { checkEdge = { vertex1: startPoint, vertex2: { x: oppositeLine.x1, y: startPoint.y } } } else { checkEdge = { vertex1: startPoint, vertex2: { x: startPoint.x, y: oppositeLine.y1 } } } if (currentAngle === oppositeAngle) { const oppositeEdge = { vertex1: { x: oppositeLine.x1, y: oppositeLine.y1 }, vertex2: { x: oppositeLine.x2, y: oppositeLine.y2 } } const intersection = edgesIntersection(oppositeEdge, checkEdge) console.log('intersection', intersection) if (intersection) { ridgeSize = Big(intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() } console.log('aaa') } else { const intersectPoints = [] roof.lines .filter( (line) => Math.sign(oppositeLine.x1 - oppositeLine.x2) === Math.sign(line.x1 - line.x2) && Math.sign(oppositeLine.y1 - oppositeLine.y2) === Math.sign(line.y1 - line.y2), ) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(lineEdge, checkEdge) console.log('intersection', intersection) if (intersection) { const checkPoint = new fabric.Circle({ left: intersection.x, top: intersection.y, radius: 4, fill: 'red', parentId: roofId, name: 'checkPoint', }) canvas.add(checkPoint) canvas.renderAll() const size = Big(startPoint.x) .minus(Big(intersection.x)) .pow(2) .plus(Big(startPoint.y).minus(Big(intersection.y)).pow(2)) .sqrt() .toNumber() intersectPoints.push({ intersection, size }) } }) intersectPoints.sort((a, b) => a.size - b.size) console.log('intersectPoints', intersectPoints) if (intersectPoints.length > 0) { ridgeSize = Big(intersectPoints[0].size) } console.log('bbb') } } else { console.log('마루 작성') // baseLines에서 가장 작은 x1과 가장 큰 x1, 가장 작은 y1과 가장 큰 y1을 계산 let minX = Infinity let maxX = -Infinity let minY = Infinity let maxY = -Infinity baseLines.forEach((line) => { if (line.x1 < minX) { minX = line.x1 } if (line.x1 > maxX) { maxX = line.x1 } if (line.y1 < minY) { minY = line.y1 } if (line.y1 > maxY) { maxY = line.y1 } }) const checkLength = Big(maxX) .minus(Big(minX)) .pow(2) .plus(Big(maxY).minus(Big(minY)).pow(2)) .sqrt() const checkEdges = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: Big(startPoint.x).minus(checkLength.times(Math.sign(xVector))), y: Big(startPoint.y).minus(checkLength.times(Math.sign(yVector))), }, } /** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/ const intersectBaseLine = [] baseLines .filter((line) => { /** currentAngle 의 반대 각도인 라인 */ const angle = calculateAngle(line.startPoint, line.endPoint) switch (currentAngle) { case 90: return angle === -90 case -90: return angle === 90 case 0: return angle === 180 case 180: return angle === 0 } }) .filter((line) => { const currentMinX = Math.min(x1, x2) const currentMaxX = Math.max(x1, x2) const currentMinY = Math.min(y1, y2) const currentMaxY = Math.max(y1, y2) const lineMinX = Math.min(line.x1, line.x2) const lineMaxX = Math.max(line.x1, line.x2) const lineMinY = Math.min(line.y1, line.y2) const lineMaxY = Math.max(line.y1, line.y2) /** currentLine 의 안쪽에 있거나 currentLine이 line의 안쪽에 있는 라인 */ if (Big(currentLine.y1).minus(Big(currentLine.y2)).abs().lte(1)) { return ( (currentMinX <= lineMinX && lineMinX <= currentMaxX) || (currentMinX <= lineMaxX && lineMaxX <= currentMaxX) || (lineMinX <= currentMinX && currentMinX <= lineMaxX) || (lineMinX <= currentMaxX && currentMaxX <= lineMaxX) ) } else { return ( (currentMinY <= lineMinY && lineMinY <= currentMaxY) || (currentMinY <= lineMaxY && lineMaxY <= currentMaxY) || (lineMinY <= currentMinY && currentMinY <= lineMaxY) || (lineMinY <= currentMaxY && currentMaxY <= lineMaxY) ) } }) .forEach((line, index) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdges, lineEdge) if (intersection) { intersectBaseLine.push({ intersection, line }) } }) /** 맞은편 라인 */ const oppositeLine = intersectBaseLine.reduce((prev, current) => { const prevDistance = Big(prev.intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(prev.intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() const currentDistance = Big(current.intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(current.intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() return prevDistance < currentDistance ? prev : current }, intersectBaseLine[0]) /** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */ const oppositeSize = oppositeLine ? Big(oppositeLine.intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(oppositeLine.intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() .minus(currentSize.div(2)) .round(1) : Infinity /** 이전, 다음 라인중 길이가 짧은 길이*/ const lineMinSize = prevBaseLine.size < nextBaseLine.size ? Big(prevBaseLine.size).div(10) : Big(nextBaseLine.size).div(10) /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) } console.log('ridgeSize', ridgeSize) if (ridgeSize.gt(0) && baseRidgeCount < getMaxRidge(baseLines.length)) { const points = [ startPoint.x, startPoint.y, Big(startPoint.x).minus(ridgeSize.times(Math.sign(xVector))), Big(startPoint.y).minus(ridgeSize.times(Math.sign(yVector))), ] /** 동일 라인이 있는지 확인. */ if ( baseRidgeLines.filter((line) => { const ridgeMinX = Math.min(points[0], points[2]) const ridgeMaxX = Math.max(points[0], points[2]) const ridgeMinY = Math.min(points[1], points[3]) const ridgeMaxY = Math.max(points[1], points[3]) const lineMinX = Math.min(line.x1, line.x2) const lineMaxX = Math.max(line.x1, line.x2) const lineMinY = Math.min(line.y1, line.y2) const lineMaxY = Math.max(line.y1, line.y2) return ridgeMinX === lineMinX && ridgeMaxX === lineMaxX && ridgeMinY === lineMinY && ridgeMaxY === lineMaxY }).length > 0 ) { return } const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) if (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) { baseGableRidgeLines.push(ridgeLine) } else { baseRidgeLines.push(ridgeLine) } baseRidgeCount = baseRidgeCount + 1 /** 포인트 조정*/ baseHipLines .filter((line) => line.line === prevHipLine || line.line === nextHipLine) .forEach((line) => { line.x2 = points[0] line.y2 = points[1] line.line.set({ x2: points[0], y2: points[1] }) // line.line.fire('modified') }) prevHipLine.x2 = points[0] prevHipLine.y2 = points[1] const prevSize = reCalculateSize(prevHipLine) prevHipLine.attributes.planeSize = prevSize.planeSize prevHipLine.attributes.actualSize = prevSize.actualSize prevHipLine.fire('modified') nextHipLine.x2 = points[0] nextHipLine.y2 = points[1] const nextSize = reCalculateSize(nextHipLine) nextHipLine.attributes.planeSize = nextSize.planeSize nextHipLine.attributes.actualSize = nextSize.actualSize nextHipLine.fire('modified') canvas.renderAll() if (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) { const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line const oppositeAngle = calculateAngle(oppositeLine.startPoint, oppositeLine.endPoint) if (Math.sign(ridgeLine.x1 - ridgeLine.x2) === 0) { const gableVector = Math.sign(ridgeLine.x1 - oppositeLine.x1) const prevVector = ridgeLine.x1 === prevHipLine.x1 ? Math.sign(ridgeLine.x1 - prevHipLine.x2) : Math.sign(ridgeLine.x2 - prevHipLine.x1) const nextVector = ridgeLine.x1 === nextHipLine.x1 ? Math.sign(ridgeLine.x1 - nextHipLine.x2) : Math.sign(ridgeLine.x2 - nextHipLine.x1) const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine const firstDegree = gableVector === Math.sign(ridgeLine.x1 - prevLine.x1) ? prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree : nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree const oppositeRoofPoints = [ ridgeLine.x2, ridgeLine.y2, ridgeLine.x1 === firstHipLine.x1 ? firstHipLine.x2 : firstHipLine.x1, ridgeLine.y2, ] const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, firstDegree, firstDegree) baseHipLines.push({ x1: oppositeRoofLine.x1, y1: oppositeRoofLine.y1, x2: oppositeRoofLine.x2, y2: oppositeRoofLine.y2, line: oppositeRoofLine, }) const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] if (ridgeLine.x1 === firstHipLine.x1) { connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2) } else { connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1) } const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) baseHipLines.push({ x1: connectRoofLine.x1, y1: connectRoofLine.y1, x2: connectRoofLine.x2, y2: connectRoofLine.y2, line: connectRoofLine, }) /** 다른 방향의 추녀마루 */ const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine const secondDegree = gableVector === Math.sign(ridgeLine.x1 - prevLine.x1) ? nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree : prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree const intersections = [] const checkEdge = { vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 }, vertex2: { x: ridgeLine.x1 === secondHipLine.x1 ? secondHipLine.x2 : secondHipLine.x1, y: ridgeLine.y2 }, } baseGableRidgeLines .filter((ridge) => ridge !== ridgeLine) .forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const intersection = edgesIntersection(ridgeEdge, checkEdge) if (intersection && !intersections.includes(intersection)) { const size = Big(intersection.x) .minus(Big(ridgeLine.x2)) .pow(2) .plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2)) .sqrt() .toNumber() intersections.push({ intersection, size, ridge }) } }) intersections.sort((a, b) => a.size - b.size) if (intersections.length > 0) { const intersection = intersections[0].intersection const intersectRidge = intersections[0].ridge const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y] const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, secondDegree, secondDegree) baseHipLines.push({ x1: oppositeLine.x1, y1: oppositeLine.y1, x2: oppositeLine.x2, y2: oppositeLine.y2, line: oppositeRoofLine }) const ridgeVector = Math.sign(ridgeLine.y1 - ridgeLine.y2) const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] if (ridgeVector === Math.sign(oppositeRoofLine.y1 - intersectRidge.y2)) { connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2) } else { connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1) } const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) baseHipLines.push({ x1: connectRoofLine.x1, y1: connectRoofLine.y1, x2: connectRoofLine.x2, y2: connectRoofLine.y2, line: connectRoofLine, }) } } else { const gableVector = Math.sign(ridgeLine.y1 - oppositeLine.y1) const prevVector = ridgeLine.y1 === prevHipLine.y1 ? Math.sign(ridgeLine.y1 - prevHipLine.y2) : Math.sign(ridgeLine.y1 - prevHipLine.y1) const nextVector = ridgeLine.y1 === nextHipLine.y1 ? Math.sign(ridgeLine.y1 - nextHipLine.y2) : Math.sign(ridgeLine.y1 - nextHipLine.y1) /** 마루와 박공지붕을 연결하기위한 추녀마루 라인 */ const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine const firstDegree = gableVector === Math.sign(ridgeLine.y1 - prevLine.y1) ? prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree : nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree const oppositeRoofPoints = [ ridgeLine.x2, ridgeLine.y2, ridgeLine.x2, ridgeLine.y1 === firstHipLine.y1 ? firstHipLine.y2 : firstHipLine.y1, ] const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, firstDegree, firstDegree) baseHipLines.push({ x1: oppositeRoofLine.x1, y1: oppositeRoofLine.y1, x2: oppositeRoofLine.x2, y2: oppositeRoofLine.y2, line: oppositeRoofLine, }) const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] if (ridgeLine.y1 === firstHipLine.y1) { connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2) } else { connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1) } const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) baseHipLines.push({ x1: connectRoofLine.x1, y1: connectRoofLine.y1, x2: connectRoofLine.x2, y2: connectRoofLine.y2, line: connectRoofLine, }) /** 다른 방향의 추녀마루 */ const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine const secondDegree = gableVector === Math.sign(ridgeLine.y1 - prevLine.y1) ? nextLine.attributes.pitch > 0 ? getDegreeByChon(nextLine.attributes.pitch) : nextLine.attributes.degree : prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree const intersections = [] const checkEdge = { vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y1 === secondHipLine.y1 ? secondHipLine.y2 : secondHipLine.y1 }, } baseGableRidgeLines .filter((ridge) => ridge !== ridgeLine) .forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const intersection = edgesIntersection(ridgeEdge, checkEdge) if (intersection && !intersections.includes(intersection)) { const size = Big(intersection.x) .minus(Big(ridgeLine.x2)) .pow(2) .plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2)) .sqrt() .toNumber() intersections.push({ intersection, size, ridge }) } }) intersections.sort((a, b) => a.size - b.size) if (intersections.length > 0) { const intersection = intersections[0].intersection const intersectRidge = intersections[0].ridge const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y] const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, secondDegree, secondDegree) baseHipLines.push({ x1: oppositeLine.x1, y1: oppositeLine.y1, x2: oppositeLine.x2, y2: oppositeLine.y2, line: oppositeRoofLine }) const ridgeVector = Math.sign(ridgeLine.x1 - ridgeLine.x2) const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] if (ridgeVector === Math.sign(oppositeRoofLine.x1 - intersectRidge.x2)) { connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2) } else { connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1) } const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) baseHipLines.push({ x1: connectRoofLine.x1, y1: connectRoofLine.y1, x2: connectRoofLine.x2, y2: connectRoofLine.y2, line: connectRoofLine, }) } } } } } canvas .getObjects() .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() }) /** 중복제거 */ baseRidgeLines.forEach((ridge) => { baseRidgeLines .filter((ridge2) => ridge !== ridge2) .forEach((ridge2) => { let overlap = segmentsOverlap(ridge, ridge2) if (overlap) { roof.canvas.remove(ridge) roof.canvas.remove(ridge2) baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2) baseRidgeCount = baseRidgeCount - 2 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 = drawRidgeLine([x1, y1, x2, y2], canvas, roof, textMode) baseRidgeLines.push(newRidge) baseRidgeCount = baseRidgeCount + 1 } }) }) // console.log('drawEavesSecondLines : ', drawEavesSecondLines) /** ㄴ 모양 처마에 추녀마루를 그린다. */ drawEavesSecondLines.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let { x1, x2, y1, y2, size } = currentBaseLine // const checkLine = new fabric.Line([x1, y1, x2, y2], { // stroke: 'yellow', // strokeWidth: 4, // parentId: roof.id, // name: 'checkLine', // }) // canvas.add(checkLine) // canvas.renderAll() // checkLine.bringToFront() /** 이전 라인의 경사 */ const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree /** 다음 라인의 경사 */ const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree //라인 확인용 /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { stroke: 'red', strokeWidth: 4, parentId: roof.id, }) canvas.add(checkCurrentLine) canvas.renderAll()*/ // console.log('currentLine : ', currentLine.attributes.planeSize) /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevLine, currentLine) let nextVector = getHalfAngleVector(currentLine, nextLine) let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } /** 각 라인의 흐름 방향을 확인한다. */ const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) /*const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint)*/ /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ let hipLength = Big(x2) .minus(Big(x1)) .plus(Big(y2).minus(Big(y1))) .abs() // console.log('currentLine : ', currentLine.attributes.planeSize) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(10)), y: Big(y1).plus(Big(prevHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(10)), y: Big(y2).plus(Big(nextHipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } } let prevHipLine, nextHipLine /** 이전라인과의 연결지점에 추녀마루를 그린다. */ // console.log('이전라인 : ', baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0 && eavesType.includes(prevLine.attributes.type)) { let prevEndPoint = { x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), } const prevEndEdge = { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint } // console.log('prevHipVector : ', prevHipVector.x.toNumber(), prevHipVector.x.s, prevHipVector.y.toNumber(), prevHipVector.y.s) const intersectBaseLine = [] baseLines .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) .filter((line) => { if (currentAngle === 0 || currentAngle === 180) { return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s } else { return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s } }) .forEach((line) => { // console.log('line : ', line.attributes.planeSize) const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(prevEndEdge, lineEdge) if (intersection && Big(intersection.x - x1).s === nextHipVector.x.s && Big(intersection.y - y1).s === nextHipVector.y.s) { /* const circle = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'red', parentId: roof.id, }) canvas.add(circle) canvas.renderAll()*/ const size = Big(intersection.x - x1) .abs() .pow(2) .plus( Big(intersection.y - y1) .pow(2) .abs(), ) .sqrt() if (size.gt(0)) { intersectBaseLine.push({ intersection, size, }) } } }) const intersectBase = intersectBaseLine.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectBaseLine[0]) if (intersectBase) { prevEndPoint = { x: Big(x1) .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) .round(2), y: Big(y1) .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) .round(2), } } const intersectRidgeLine = [] baseRidgeLines.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) /*const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { stroke: 'red', strokeWidth: 2, parentId: roof.id, name: 'checkLine', }) canvas.add(checkLine) canvas.renderAll()*/ /* if (intersection) { const intersectCircle = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'red', parentId: roof.id, name: 'checkPoint', }) canvas.add(intersectCircle) canvas.renderAll() }*/ if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x1)) .abs() .pow(2) .plus(Big(intersection.y).minus(Big(y1)).pow(2).abs()) .sqrt(), }) } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) if (intersectRidge) { prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) } const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() scale = scale.eq(0) ? Big(1) : scale /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ const hipEdge = { vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, vertex2: prevEndPoint, } /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { stroke: 'yellow', strokeWidth: 2, parentId: roof.id, name: 'checkLine', }) canvas.add(checkLine) canvas.renderAll()*/ let intersectPoints = [] /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if ( intersection && !intersection.isIntersectionOutside && Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) ) { /*const intersectPoint = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'cyan', parentId: roof.id, }) canvas.add(intersectPoint) canvas.renderAll()*/ const intersectSize = prevEndPoint.x .minus(Big(intersection.x)) .abs() .pow(2) .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2).abs()) .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, }) } }) intersectPoints = intersectPoints.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) // console.log('intersectPoints', intersectPoints) /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ if (intersectPoints && intersectPoints.intersection) { prevHipLine = drawHipLine( [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], canvas, roof, textMode, null, prevDegree, currentDegree, ) baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) } } /** 다음라인과의 연결지점에 추녀마루를 그린다. */ // console.log('다음라인 : ', baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0 && eavesType.includes(nextLine.attributes.type)) { let nextEndPoint = { x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), } /*const nextEndLine = new fabric.Line([x2, y2, nextEndPoint.x, nextEndPoint.y], { stroke: 'red', strokeWidth: 2, parentId: roof.id, }) canvas.add(nextEndLine) canvas.renderAll()*/ const nextEndEdge = { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint, } const intersectBaseLine = [] baseLines .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) .filter((line) => { if (currentAngle === 0 || currentAngle === 180) { return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s } else { return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s } }) .forEach((line) => { // console.log('line : ', line.attributes.planeSize) const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(nextEndEdge, lineEdge) if (intersection && Big(intersection.x - x2).s === nextHipVector.x.s && Big(intersection.y - y2).s === nextHipVector.y.s) { /*console.log('next intersection ============') const circle = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'red', parentId: roof.id, }) canvas.add(circle) canvas.renderAll()*/ const size = Big(intersection.x - x2) .abs() .pow(2) .plus( Big(intersection.y - y2) .abs() .pow(2), ) .sqrt() if (size.gt(0)) { intersectBaseLine.push({ intersection, size, }) } } }) const intersectBase = intersectBaseLine.reduce((prev, current) => { return prev.size.lt(current.size) ? prev : current }, intersectBaseLine[0]) if (intersectBase) { nextEndPoint = { x: Big(x2) .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) .round(2), y: Big(y2) .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) .round(2), } } const intersectRidgeLine = [] baseRidgeLines.forEach((line) => { // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) const intersection = edgesIntersection( { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { intersectRidgeLine.push({ intersection, distance: Big(intersection.x) .minus(Big(x1)) .pow(2) .plus(Big(intersection.y).minus(Big(y1)).pow(2)) .sqrt(), }) } }) const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) // console.log('intersectRidge : ', intersectRidge) if (intersectRidge) { nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) } const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() scale = scale.eq(0) ? Big(1) : scale const hipEdge = { vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, vertex2: nextEndPoint, } /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { stroke: 'yellow', strokeWidth: 2, parentId: roof.id, }) canvas.add(checkLine) canvas.renderAll()*/ let intersectPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) // console.log('intersection', intersection) if ( intersection && !intersection.isIntersectionOutside && Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) ) { /*const intersectPoint = new fabric.Circle({ left: intersection.x - 2, top: intersection.y - 2, radius: 4, fill: 'blue', parentId: roof.id, }) canvas.add(intersectPoint) canvas.renderAll()*/ // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) // console.log('intersection', intersection.x, intersection.y) const intersectSize = nextEndPoint.x .minus(Big(intersection.x)) .pow(2) .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, }) } }) intersectPoints = intersectPoints.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) // console.log('intersectPoints : ', intersectPoints) if (intersectPoints && intersectPoints.intersection) { nextHipLine = drawHipLine( [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], canvas, roof, textMode, null, prevDegree, currentDegree, ) baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) } } }) /** baseHipLine 이 ridge에 붙지 않은 경우 확인 */ baseHipLines.forEach((hipLine) => { const ridgeCount = baseRidgeLines.filter( (ridgeLine) => (hipLine.x2 === ridgeLine.x1 && hipLine.y2 === ridgeLine.y1) || (hipLine.x2 === ridgeLine.x2 && hipLine.y2 === ridgeLine.y2), ).length if (ridgeCount === 0) { const hipXVector = Big(hipLine.x2).minus(hipLine.x1) const hipYVector = Big(hipLine.y2).minus(hipLine.y1) const hipSize = hipXVector.abs().pow(2).plus(hipYVector.abs().pow(2)).sqrt() const intersectRidgePoints = [] const hipLineEdge = { vertex1: { x: hipLine.x1, y: hipLine.y1 }, vertex2: { x: hipLine.x2, y: hipLine.y2 } } baseRidgeLines.forEach((ridgeLine) => { const ridgeLineEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } const intersection = edgesIntersection(hipLineEdge, ridgeLineEdge) if (intersection) { const intersectXVector = Big(intersection.x).minus(Big(hipLine.x1)) const intersectYVector = Big(intersection.y).minus(Big(hipLine.y1)) const intersectSize = intersectXVector.pow(2).plus(intersectYVector.pow(2)).sqrt() if (!intersection.isIntersectionOutside) { intersectRidgePoints.push({ x: intersection.x, y: intersection.y, size: intersectSize, }) } else if ( ((intersection.x === ridgeLine.x1 && intersection.y === ridgeLine.y1) || (intersection.x === ridgeLine.x2 && intersection.y === ridgeLine.y2)) && Math.sign(hipXVector.toNumber()) === Math.sign(intersectXVector.toNumber()) && Math.sign(hipYVector.toNumber()) === Math.sign(intersectYVector.toNumber()) && intersectSize.gt(0) && intersectSize.lt(hipSize) ) { intersectRidgePoints.push({ x: intersection.x, y: intersection.y, size: intersectSize, }) } } }) intersectRidgePoints.sort((prev, current) => prev.size.minus(current.size).toNumber()) if (intersectRidgePoints.length > 0) { const oldPlaneSize = hipLine.line.attributes.planeSize const oldActualSize = hipLine.line.attributes.actualSize const theta = Big(Math.acos(Big(oldPlaneSize).div(oldActualSize))) .times(180) .div(Math.PI) const planeSize = calcLinePlaneSize({ x1: hipLine.line.x1, y1: hipLine.line.y1, x2: hipLine.line.x2, y2: hipLine.line.y2 }) hipLine.x2 = intersectRidgePoints[0].x hipLine.y2 = intersectRidgePoints[0].y hipLine.line.set({ x2: intersectRidgePoints[0].x, y2: intersectRidgePoints[0].y }) hipLine.line.attributes.planeSize = planeSize hipLine.line.attributes.actualSize = planeSize === oldActualSize ? 0 : Big(planeSize).div(theta).round(1).toNumber() hipLine.line.fire('modified') } } }) console.log('baseHipLines : ', baseHipLines) console.log('baseRidgeLines : ', baseRidgeLines) /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ let unFinishedRidge = [] baseRidgeLines.forEach((current) => { let checkPoint = [ { x: current.x1, y: current.y1, line: current, cnt: 0, onRoofLine: false }, { x: current.x2, y: current.y2, line: current, cnt: 0, onRoofLine: false }, ] baseHipLines.forEach((line) => { if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { checkPoint[0].cnt = checkPoint[0].cnt + 1 } if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { checkPoint[1].cnt = checkPoint[1].cnt + 1 } }) /** 마루의 포인트가 지붕선 위에 있는경우는 제외 (케라바 등)*/ roof.lines.forEach((line) => { if ( line.x1 === line.x2 && checkPoint[0].x === line.x1 && ((line.y1 <= checkPoint[0].y && line.y2 >= checkPoint[0].y) || (line.y1 >= checkPoint[0].y && line.y2 <= checkPoint[0].y)) ) { checkPoint[0].onRoofLine = true } if ( line.y1 === line.y2 && checkPoint[0].y === line.y1 && ((line.x1 <= checkPoint[0].x && line.x2 >= checkPoint[0].x) || (line.x1 >= checkPoint[0].x && line.x2 <= checkPoint[0].x)) ) { checkPoint[0].onRoofLine = true } if ( line.x1 === line.x2 && checkPoint[1].x === line.x1 && ((line.y1 <= checkPoint[1].y && line.y2 >= checkPoint[1].y) || (line.y1 >= checkPoint[1].y && line.y2 <= checkPoint[1].y)) ) { checkPoint[1].onRoofLine = true } if ( line.y1 === line.y2 && checkPoint[1].y === line.y1 && ((line.x1 <= checkPoint[1].x && line.x2 >= checkPoint[1].x) || (line.x1 >= checkPoint[1].x && line.x2 <= checkPoint[1].x)) ) { checkPoint[1].onRoofLine = true } }) // console.log('checkPoint : ', checkPoint) if ((checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) && !checkPoint[0].onRoofLine) { unFinishedRidge.push(checkPoint[0]) } if ((checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) && !checkPoint[1].onRoofLine) { unFinishedRidge.push(checkPoint[1]) } }) /** 2. 그려진 추녀마루 중 완성되지 않은 것을 찾는다. 완성되지 않았다는 것은 연결된 포인트가 홀수인 경우로 판단한다.*/ const findUnFinishedPoints = (baseHipLines) => { let unFinishedPoint = [] baseHipLines.forEach((current) => { let checkPoint = [ { x: current.x1, y: current.y1, checked: true, line: current.line }, { x: current.x2, y: current.y2, checked: true, line: current.line }, ] baseLines.forEach((line) => { if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { checkPoint[0].checked = false } if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { checkPoint[1].checked = false } }) const samePoints = [] checkPoint .filter((point) => point.checked) .forEach((point) => { baseHipLines.forEach((line) => { if (line.x1 === point.x && line.y1 === point.y) { samePoints.push({ x: point.x, y: point.y, line: line }) } if (line.x2 === point.x && line.y2 === point.y) { samePoints.push({ x: point.x, y: point.y, line: line }) } }) }) if (samePoints.length > 0 && samePoints.length % 2 !== 0) { unFinishedPoint.push(samePoints[0]) } }) return unFinishedPoint } let unFinishedPoint = findUnFinishedPoints(baseHipLines) /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ let degreeAllLine = [] baseLines .filter((line) => eavesType.includes(line.attributes.type)) .forEach((line) => { const pitch = line.attributes.pitch const degree = line.attributes.degree degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) }) let currentDegree, prevDegree degreeAllLine = [...new Set(degreeAllLine)] currentDegree = degreeAllLine[0] if (degreeAllLine.length === 1) { prevDegree = currentDegree } else { prevDegree = degreeAllLine[1] } console.log('unFinishedRidge : ', unFinishedRidge) console.log('unFinishedPoint : ', unFinishedPoint) /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ unFinishedRidge.forEach((current) => { let checkPoints = [] unFinishedPoint .filter( (point) => point.x !== current.x && point.y !== current.y && Big(point.x) .minus(Big(current.x)) .abs() .minus(Big(point.y).minus(Big(current.y)).abs()) .abs() .lt(1), ) .forEach((point) => { const pointEdge = { vertex1: { x: point.x, y: point.y }, vertex2: { x: current.x, y: current.y } } let isIntersection = false baseLines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(pointEdge, lineEdge) if (intersection && !intersection.isIntersectionOutside) { isIntersection = true } }) if (!isIntersection) { checkPoints.push({ point, size: Big(point.x) .minus(Big(current.x)) .abs() .pow(2) .plus(Big(point.y).minus(Big(current.y)).abs().pow(2)) .sqrt(), }) } }) if (checkPoints.length > 0) { checkPoints.sort((a, b) => a.size - b.size) const maxCnt = Big(2).minus(Big(current.cnt)).toNumber() for (let i = 0; i < maxCnt; i++) { const checkPoint = checkPoints[i] if (checkPoint) { let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y] let hipBasePoint baseHipLines.forEach((line) => { const checkAngel1 = calculateAngle({ x: point[0], y: point[1] }, { x: point[2], y: point[3] }) const checkAngel2 = calculateAngle({ x: line.line.x1, y: line.line.y1 }, { x: line.line.x2, y: line.line.y2 }) const isConnectLine = ((line.line.x1 === point[0] && line.line.y1 === point[1]) || (line.line.x2 === point[0] && line.line.y2 === point[1])) && checkAngel1 === checkAngel2 const isOverlap = segmentsOverlap(line.line, { x1: point[0], y1: point[1], x2: point[2], y2: point[3] }) const isSameLine = (point[0] === line.x2 && point[1] === line.y2 && point[2] === line.x1 && point[3] === line.y1) || (point[0] === line.x1 && point[1] === line.y1 && point[2] === line.x2 && point[3] === line.y2) if (isConnectLine || isOverlap || isSameLine) { /** 겹치는 추녀마루와 하나의 선으로 변경*/ const mergePoint = [ { x: point[0], y: point[1] }, { x: point[2], y: point[3] }, { x: line.line.x1, y: line.line.y1 }, { x: line.line.x2, y: line.line.y2 }, ] /** baseHipLines도 조정*/ mergePoint.sort((a, b) => a.x - b.x) hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] const theta = Big(Math.acos(Big(line.line.attributes.planeSize).div(line.line.attributes.actualSize))) .times(180) .div(Math.PI) .round(1) console.log('theta : ', theta.toNumber()) prevDegree = theta.toNumber() currentDegree = theta.toNumber() canvas.remove(line.line) baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line) } }) const hipLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) if (hipBasePoint) { baseHipLines.push({ x1: hipBasePoint.x1, y1: hipBasePoint.y1, x2: point[2], y2: point[3], line: hipLine }) } else { baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: hipLine }) } current.cnt = current.cnt + 1 } } } /** 라인이 다 그려지지 않은 경우 */ if (current.cnt % 2 !== 0) { let basePoints = baseLinePoints .filter((point) => Big(point.x) .minus(Big(current.x)) .abs() .minus(Big(point.y).minus(Big(current.y)).abs()) .abs() .lt(1), ) .filter((point) => { const pointEdge = { vertex1: { x: current.x, y: current.y }, vertex2: { x: point.x, y: point.y } } const intersectPoints = [] baseLines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(pointEdge, lineEdge) if (intersection && !intersection.isIntersectionOutside) { intersectPoints.push(intersection) } }) return ( !intersectPoints.filter( (intersect) => !(Big(intersect.x).minus(Big(point.x)).abs().lt(1) && Big(intersect.y).minus(Big(point.y)).abs().lt(1)), ).length > 0 ) }) /** hip을 그리기 위한 기본 길이*/ let hipSize = Big(0) if (basePoints.length > 0) { basePoints.sort((a, b) => { const aSize = Big(a.x) .minus(Big(current.x)) .abs() .pow(2) .plus(Big(a.y).minus(Big(current.y)).abs().pow(2)) .sqrt() const bSize = Big(b.x) .minus(Big(current.x)) .abs() .pow(2) .plus(Big(b.y).minus(Big(current.y)).abs().pow(2)) .sqrt() return aSize - bSize }) const baseHips = baseHipLines.filter((line) => line.x1 === basePoints[0].x && line.y1 === basePoints[0].y) if (baseHips.length > 0) { hipSize = Big(baseHips[0].line.attributes.planeSize) } } if (hipSize.eq(0)) { const ridge = current.line basePoints = baseHipLines .filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) .filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0) basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize) hipSize = Big(basePoints[0].line.attributes.planeSize) } hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber() // console.log('hipSize : ', hipSize.toNumber()) /** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */ const checkEdge45 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y - hipSize } } const checkEdge135 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x + hipSize, y: current.y + hipSize } } const checkEdge225 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x - hipSize, y: current.y + hipSize } } const checkEdge315 = { vertex1: { x: current.x, y: current.y }, vertex2: { x: current.x - hipSize, y: current.y - hipSize } } let intersectPoints = [] let notIntersect45 = true, notIntersect135 = true, notIntersect225 = true, notIntersect315 = true baseLines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection45 = edgesIntersection(checkEdge45, lineEdge) const intersection135 = edgesIntersection(checkEdge135, lineEdge) const intersection225 = edgesIntersection(checkEdge225, lineEdge) const intersection315 = edgesIntersection(checkEdge315, lineEdge) if (intersection45 && !intersection45.isIntersectionOutside) { intersectPoints.push(intersection45) notIntersect45 = false } if (intersection135 && !intersection135.isIntersectionOutside) { intersectPoints.push(intersection135) notIntersect135 = false } if (intersection225 && !intersection225.isIntersectionOutside) { intersectPoints.push(intersection225) notIntersect225 = false } if (intersection315 && !intersection315.isIntersectionOutside) { intersectPoints.push(intersection315) notIntersect315 = false } }) /** baseLine과 교차하지 않는 포인트를 추가한다.*/ if (notIntersect45) { intersectPoints.push(checkEdge45.vertex2) } if (notIntersect135) { intersectPoints.push(checkEdge135.vertex2) } if (notIntersect225) { intersectPoints.push(checkEdge225.vertex2) } if (notIntersect315) { intersectPoints.push(checkEdge315.vertex2) } /** baseLine의 각 좌표와 교차하는 경우로 한정*/ intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0) /** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */ intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0) /** 중복제거 */ intersectPoints = intersectPoints.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) intersectPoints.forEach((is) => { const points = [current.x, current.y, is.x, is.y] /** 추녀마루의 연결점이 처마라인이 아닌경우 return */ let hasGable = false baseLines .filter((line) => (line.x1 === points[2] && line.y1 === points[3]) || (line.x2 === points[2] && line.y2 === points[3])) .forEach((line) => { if (!eavesType.includes(line.attributes.type)) { hasGable = true } }) if (hasGable) return const pointEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } const vectorX = Math.sign(Big(points[2]).minus(Big(points[0])).toNumber()) const vectorY = Math.sign(Big(points[3]).minus(Big(points[1])).toNumber()) const roofIntersections = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(pointEdge, lineEdge) if (intersection) { const vectorIntersectionX = Math.sign(Big(intersection.x).minus(Big(points[0])).toNumber()) const vectorIntersectionY = Math.sign(Big(intersection.y).minus(Big(points[1])).toNumber()) if (vectorIntersectionX === vectorX && vectorIntersectionY === vectorY) { roofIntersections.push({ x: intersection.x, y: intersection.y, size: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: intersection.x, y2: intersection.y }), }) } } }) roofIntersections.sort((a, b) => a.size - b.size) const hipLine = drawHipLine( [points[0], points[1], roofIntersections[0].x, roofIntersections[0].y], canvas, roof, textMode, null, prevDegree, currentDegree, ) baseHipLines.push({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], line: hipLine }) current.cnt = current.cnt + 1 }) } }) /** hip이 짝수개가 맞다아있는데 마루와 연결되지 않는 포인트를 찾는다. 그려지지 않은 마루를 찾기 위함.*/ let noRidgeHipPoints = baseHipLines .filter((current) => current.x1 !== current.x2 && current.y1 !== current.y2) .filter((current) => { const lines = baseHipLines .filter((line) => line !== current) .filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) return lines.length !== 0 && lines.length % 2 !== 0 }) .filter( (current) => baseRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) .length === 0 && baseGableRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) .length === 0, ) noRidgeHipPoints.forEach((current) => { const orthogonalPoints = noRidgeHipPoints.filter((point) => { if (point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2))) { return true } }) // canvas.remove(checkPoint) // canvas.renderAll() /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ if (orthogonalPoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { baseRidgeCount = baseRidgeCount + 1 const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) } }) /** 중복제거*/ baseRidgeLines.forEach((current) => { const sameRidge = baseRidgeLines.filter( (line) => line !== current && ((line.x1 === current.x1 && line.y1 === current.y1 && line.x2 === current.x2 && line.y2 === current.y2) || (line.x1 === current.x2 && line.y1 === current.y2 && line.x2 === current.x1 && line.y2 === current.y1)), ) if (sameRidge.length > 0) { sameRidge.forEach((duplicateLine) => { const index = baseRidgeLines.indexOf(duplicateLine) if (index !== -1) { baseRidgeLines.splice(index, 1) } canvas.remove(duplicateLine) }) } }) /** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */ const checkEdgeLines = [] noRidgeHipPoints.forEach((current) => { noRidgeHipPoints.forEach((current) => { noRidgeHipPoints .filter((point) => point !== current) .forEach((point) => { checkEdgeLines.push( { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: current.x2, y: point.y2 } }, { vertex1: { x: current.x2, y: point.y2 }, vertex2: { x: point.x2, y: point.y2 } }, { vertex1: { x: point.x2, y: point.y2 }, vertex2: { x: point.x2, y: current.y2 } }, { vertex1: { x: point.x2, y: current.y2 }, vertex2: { x: current.x2, y: current.y2 } }, ) }) }) }) /** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */ let unFinishedPoints = [] let unFinishedLines = [] let intersectPoints = [] baseHipLines.forEach((line) => { if (baseLinePoints.filter((point) => point.x === line.x1 && point.y === line.y1).length === 0) { unFinishedPoints.push({ x: line.x1, y: line.y1 }) } if (baseLinePoints.filter((point) => point.x === line.x2 && point.y === line.y2).length === 0) { unFinishedPoints.push({ x: line.x2, y: line.y2 }) } }) unFinishedPoints .filter((point) => unFinishedPoints.filter((p) => p !== point && p.x === point.x && p.y === point.y).length === 0) .forEach((point) => { baseHipLines .filter((line) => (line.x1 === point.x && line.y1 === point.y) || (line.x2 === point.x && line.y2 === point.y)) .forEach((line) => unFinishedLines.push(line)) }) unFinishedLines.forEach((line) => { const xVector = Math.sign(Big(line.x2).minus(Big(line.x1))) const yVector = Math.sign(Big(line.y2).minus(Big(line.y1))) let lineIntersectPoints = [] checkEdgeLines.forEach((edge) => { const intersectEdge = edgesIntersection(edge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersectEdge) { const isXVector = Math.sign(Big(intersectEdge.x).minus(Big(line.x1))) === xVector const isYVector = Math.sign(Big(intersectEdge.y).minus(Big(line.y1))) === yVector if (isXVector && isYVector) { lineIntersectPoints.push({ intersection: intersectEdge, size: Big(intersectEdge.x) .minus(Big(line.x1)) .abs() .pow(2) .plus(Big(intersectEdge.y).minus(Big(line.y1)).abs().pow(2)) .sqrt() .round(1) .toNumber(), }) } } }) lineIntersectPoints = lineIntersectPoints.filter( (point, index, self) => index === self.findIndex((p) => p.intersection.x === point.intersection.x && p.intersection.y === point.intersection.y), ) lineIntersectPoints.sort((a, b) => a.size - b.size) if (lineIntersectPoints.length > 0) { intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) } }) console.log('noRidgeHipPoints : ', noRidgeHipPoints) /** 마루를 그릴수 있는지 찾는다. */ noRidgeHipPoints.forEach((hipPoint) => { const checkLine = new fabric.Line([hipPoint.x2, hipPoint.y2, hipPoint.x1, hipPoint.y1], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'checkLine', }) canvas.add(checkLine) canvas.renderAll() const ridgePoints = [] intersectPoints .filter( (intersectPoint) => (intersectPoint.intersection.x !== hipPoint.x2 && intersectPoint.intersection.y === hipPoint.y2) || (intersectPoint.intersection.x === hipPoint.x2 && intersectPoint.intersection.y !== hipPoint.y2), ) .forEach((intersectPoint) => { ridgePoints.push({ intersection: intersectPoint, distance: Big(intersectPoint.intersection.x) .minus(Big(hipPoint.x2)) .abs() .pow(2) .plus(Big(intersectPoint.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) .sqrt() .round(1) .toNumber(), }) }) ridgePoints.sort((a, b) => a.distance - b.distance) if (ridgePoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { const intersection = ridgePoints[0].intersection const isPoint = intersection.intersection const points = [hipPoint.x2, hipPoint.y2, isPoint.x, isPoint.y] const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 baseRidgeLines.push(ridgeLine) let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] baseHipLine.x2 = isPoint.x baseHipLines.y2 = isPoint.y /** 보조선 라인 조정*/ const hipLine = intersection.line.line /** 평면길이 */ const planeSize = calcLinePlaneSize({ x1: hipLine.x1, y1: hipLine.y1, x2: isPoint.x, y2: isPoint.y, }) /** 실제길이 */ const actualSize = prevDegree === currentDegree ? calcLineActualSize( { x1: hipLine.x1, y1: hipLine.y1, x2: isPoint.x, y2: isPoint.y, }, currentDegree, ) : 0 hipLine.set({ x2: isPoint.x, y2: isPoint.y, attributes: { roofId: roof.id, planeSize, actualSize }, }) hipLine.fire('modified') intersectPoints = intersectPoints.filter((isp) => isp !== intersection) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } else { const linePoints = [] intersectPoints.forEach((intersectPoint) => { const intersection = intersectPoint.intersection const xVector = Math.sign(Big(intersection.x).minus(Big(hipPoint.x2))) const yVector = Math.sign(Big(intersection.y).minus(Big(hipPoint.y2))) const checkEdge = { vertex1: { x: intersection.x, y: intersection.y }, vertex2: { x: Big(intersection.x).plus(Big(xVector).times(10)).toNumber(), y: Big(intersection.y).plus(Big(yVector).times(10)).toNumber() }, } const intersectX = edgesIntersection( { vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, vertex2: { x: Big(hipPoint.x2).plus(Big(xVector).neg().times(10)).toNumber(), y: hipPoint.y2 }, }, checkEdge, ) const intersectY = edgesIntersection( { vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, vertex2: { x: hipPoint.x2, y: Big(hipPoint.y2).plus(Big(yVector).neg().times(10)).toNumber() }, }, checkEdge, ) let distanceX = Infinity, distanceY = Infinity if (intersectX) { distanceX = Big(intersectX.x) .minus(Big(intersection.x)) .abs() .pow(2) .plus(Big(intersectX.y).minus(Big(intersection.y)).abs().pow(2)) .sqrt() .round(1) .toNumber() } if (intersectY) { distanceY = Big(intersectY.x) .minus(Big(intersection.x)) .abs() .pow(2) .plus(Big(intersectY.y).minus(Big(intersection.y)).abs().pow(2)) .sqrt() .round(1) .toNumber() } if (distanceX < distanceY) { linePoints.push({ intersection: intersectX, intersectPoint }) } if (distanceX > distanceY) { linePoints.push({ intersection: intersectY, intersectPoint }) } }) console.log('linePoints : ', linePoints) const linePoint = linePoints.reduce((prev, current) => { const prevDistance = Big(prev.intersection.x) .minus(Big(hipPoint.x2)) .abs() .pow(2) .plus(Big(prev.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) .sqrt() const currentDistance = Big(current.intersection.x) .minus(Big(hipPoint.x2)) .abs() .pow(2) .plus(Big(current.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) .sqrt() if (prevDistance < currentDistance) { return prev } else { return current } }, linePoints[0]) if (!linePoint) return const hipStartPoint = [hipPoint.x2, hipPoint.y2, linePoint.intersection.x, linePoint.intersection.y] // console.log('hipStartPoint : ', hipStartPoint) /** 직선인 경우 마루를 그린다.*/ if ( ((hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3])) && baseRidgeCount < getMaxRidge(baseLines.length) ) { // console.log('릿지1') const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 baseRidgeLines.push(ridgeLine) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } // console.log( // Big(hipStartPoint[0]).minus(Big(hipStartPoint[2])).abs().toNumber(), // Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs().toNumber(), // Big(hipStartPoint[0]) // .minus(Big(hipStartPoint[2])) // .abs() // .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) // .abs() // .lt(1), // ) /** 대각선인경우 hip을 그린다. */ if ( Big(hipStartPoint[0]) .minus(Big(hipStartPoint[2])) .abs() .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) .abs() .lt(1) ) { // console.log('힙1') const hipLine = drawHipLine(hipStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: hipStartPoint[0], y1: hipStartPoint[1], x2: hipStartPoint[2], y2: hipStartPoint[3], line: hipLine }) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } const isStartPoint = [ linePoint.intersection.x, linePoint.intersection.y, linePoint.intersectPoint.intersection.x, linePoint.intersectPoint.intersection.y, ] if ( ((isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) || (isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3])) && baseRidgeCount < getMaxRidge(baseLines.length) ) { const ridgeLine = drawRidgeLine(isStartPoint, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 baseRidgeLines.push(ridgeLine) } // console.log('isStartPoint : ', isStartPoint) // console.log(Big(isStartPoint[0]).minus(Big(isStartPoint[2])).toNumber()) if ( Big(isStartPoint[0]) .minus(Big(isStartPoint[2])) .abs() .minus(Big(isStartPoint[1]).minus(Big(isStartPoint[3])).abs()) .abs() .lt(1) ) { const hipLine = drawHipLine(isStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: isStartPoint[0], y1: isStartPoint[1], x2: isStartPoint[2], y2: isStartPoint[3], line: hipLine }) } } }) const ridgeAllPoints = [] baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) // console.log('ridge count', baseRidgeCount, getMaxRidge(baseLines.length)) console.log('baseLinePoints : ', baseLinePoints) /** hip 중에 지붕의 라인과 만나지 않은 선을 확인.*/ baseHipLines .filter( (hip) => baseLinePoints.filter((point) => (point.x === hip.x1 && point.y === hip.y1) || (point.x === hip.x2 && point.y === hip.y2)).length > 0, ) .filter((hip) => { const hipEdge = { vertex1: { x: hip.line.x1, y: hip.line.y1 }, vertex2: { x: hip.line.x2, y: hip.line.y2 } } const hipVectorX = Math.sign(Big(hip.x1).minus(Big(hip.x2))) const hipVectorY = Math.sign(Big(hip.y1).minus(Big(hip.y2))) let isIntersect = false roof.lines.forEach((line) => { const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(edge, hipEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(intersection.x).minus(Big(hip.x2))) const isVectorY = Math.sign(Big(intersection.y).minus(Big(hip.y2))) if (isVectorX === hipVectorX && isVectorY === hipVectorY) { isIntersect = true } } }) return !isIntersect }) .forEach((hip) => { console.log('hip : ', hip) const checkLine = new fabric.Line([hip.line.x1, hip.line.y1, hip.line.x2, hip.line.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'checkLine', }) canvas.add(checkLine) canvas.renderAll() const hipLine = hip.line if (hipLine) { const hipVectorX = Big(hipLine.x2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.x1).minus(Big(hipLine.x2)).neg().s)) const hipVectorY = Big(hipLine.y2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.y1).minus(Big(hipLine.y2)).neg().s)) const overlapLineX = roof.lines .filter((roofLine) => roofLine.x1 !== roofLine.x2 && roofLine.y1 === hipLine.y2 && roofLine.y2 === hipLine.y2) .filter((roofLine) => { const minX = Math.min(roofLine.x1, roofLine.x2, hipLine.x2) const maxX = Math.max(roofLine.x1, roofLine.x2, hipLine.x2) const checkLineEdge = { vertex1: { x: minX, y: hipLine.y2 }, vertex2: { x: maxX, y: hipLine.y2 } } let isIntersect = false baseHipLines.forEach((baseHipLine) => { const edge = { vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 } } const intersection = edgesIntersection(edge, checkLineEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(intersection.x).minus(Big(baseHipLine.x2))) const isVectorY = Math.sign(Big(intersection.y).minus(Big(baseHipLine.y2))) if (isVectorX === hipVectorX && isVectorY === hipVectorY) { isIntersect = true } } }) baseRidgeLines.forEach((baseRidgeLine) => { const edge = { vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 } } const intersection = edgesIntersection(edge, checkLineEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) if (isVectorX === hipVectorX && isVectorY === hipVectorY) { isIntersect = true } } }) return !isIntersect }) const overlapLineY = roof.lines .filter((roofLine) => roofLine.y1 !== roofLine.y2 && roofLine.x1 === hipLine.x2 && roofLine.x2 === hipLine.x2) .filter((roofLine) => { const minY = Math.min(roofLine.y1, roofLine.y2, hipLine.y2) const maxY = Math.max(roofLine.y1, roofLine.y2, hipLine.y2) const checkLineEdge = { vertex1: { x: hipLine.x2, y: minY }, vertex2: { x: hipLine.x2, y: maxY } } let isIntersect = false baseHipLines.forEach((baseHipLine) => { const edge = { vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 } } const intersection = edgesIntersection(edge, checkLineEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) if (isVectorX === hipVectorX && isVectorY === hipVectorY) { isIntersect = true } } }) baseRidgeLines.forEach((baseRidgeLine) => { const edge = { vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 } } const intersection = edgesIntersection(edge, checkLineEdge) if (intersection && !intersection.isIntersectionOutside) { const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) if (isVectorX === hipVectorX && isVectorY === hipVectorY) { isIntersect = true } } }) return !isIntersect }) overlapLineX.reduce((prev, current) => { const prevDistance = Big(prev.x1) .minus(Big(hipLine.x2)) .abs() .lt(Big(prev.x2).minus(Big(hipLine.x2)).abs()) ? Big(prev.x1).minus(Big(hipLine.x2)).abs() : Big(prev.x2).minus(Big(hipLine.x2)).abs() const currentDistance = Big(current.x1) .minus(Big(hipLine.x2)) .abs() .lt(Big(current.x2).minus(Big(hipLine.x2)).abs()) ? Big(current.x1).minus(Big(hipLine.x2)).abs() : Big(current.x2).minus(Big(hipLine.x2)).abs() return prevDistance.lt(currentDistance) ? prev : current }, overlapLineX[0]) overlapLineY.reduce((prev, current) => { const prevDistance = Big(prev.y1) .minus(Big(hipLine.y2)) .abs() .lt(Big(prev.y2).minus(Big(hipLine.y2)).abs()) ? Big(prev.y1).minus(Big(hipLine.y2)).abs() : Big(prev.y2).minus(Big(hipLine.y2)).abs() const currentDistance = Big(current.y1) .minus(Big(hipLine.y2)) .abs() .lt(Big(current.y2).minus(Big(hipLine.y2)).abs()) ? Big(current.y1).minus(Big(hipLine.y2)).abs() : Big(current.y2).minus(Big(hipLine.y2)).abs() return prevDistance.lt(currentDistance) ? prev : current }, overlapLineY[0]) if (overlapLineX.length > 0) { const overlapLine = overlapLineX[0] const maxX = Math.max(overlapLine.x1, overlapLine.x2) const point = [hipLine.x2, hipLine.y2, maxX, hipLine.y2] const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) } if (overlapLineY.length > 0) { const overlapLine = overlapLineY[0] const maxY = Math.max(overlapLine.y1, overlapLine.y2) const point = [hipLine.x2, hipLine.y2, hipLine.x2, maxY] const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) } } const modifiedBaseLine = baseLines.filter((line) => (hip.x2 === line.x1 && hip.y2 === line.y1) || (hip.x2 === line.x2 && hip.y2 === line.y2)) console.log('modifiedBaseLine : ', modifiedBaseLine) if (modifiedBaseLine.length === 0) return const verticalLine = modifiedBaseLine.find( (line) => (hip.x2 === line.attributes.originPoint.x1 && hip.x2 === line.attributes.originPoint.x2) || (hip.y2 === line.attributes.originPoint.y1 && hip.y2 === line.attributes.originPoint.y2), ) const horizonLine = modifiedBaseLine.find((line) => line !== verticalLine) console.log('verticalLine', verticalLine) console.log('horizonLine : ', horizonLine) const horizonRoof = roof.lines.find((line) => { const originPoint = horizonLine.attributes.originPoint if (originPoint.y1 === originPoint.y2) { return ( line.y1 === line.y2 && Math.sign(originPoint.x1 - originPoint.x2) === Math.sign(line.x1 - line.x2) && Big(originPoint.y1).minus(Big(line.y1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) ) } else { return ( line.x1 === line.x2 && Math.sign(originPoint.y1 - originPoint.y2) === Math.sign(line.y1 - line.y2) && Big(originPoint.x1).minus(Big(line.x1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) ) } }) if (horizonRoof) { let horizonPoint if (horizonRoof.y1 === horizonRoof.y2) { const minX = Math.min(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) const maxX = Math.max(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) horizonPoint = [minX, horizonRoof.y1, maxX, horizonRoof.y1] } else { const minY = Math.min(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) const maxY = Math.max(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) horizonPoint = [horizonRoof.x1, minY, horizonRoof.x1, maxY] } let addLine const alreadyHorizonLines = baseHipLines.find( (hipLine) => hipLine.x1 === horizonPoint[0] && hipLine.y1 === horizonPoint[1] && hipLine.x2 === horizonPoint[2] && hipLine.y2 === horizonPoint[3], ) console.log('alreadyHorizonLines : ', alreadyHorizonLines) if (!alreadyHorizonLines) { addLine = drawHipLine(horizonPoint, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: horizonPoint[0], y1: horizonPoint[1], x2: horizonPoint[2], y2: horizonPoint[3], line: addLine }) } else { addLine = alreadyHorizonLines } let verticalPoint if (addLine.y1 === addLine.y2) { verticalPoint = [hip.x2, hip.y2, hip.x2, addLine.y1] } else { verticalPoint = [hip.x2, hip.y2, addLine.x1, hip.y2] } const alreadyVerticalLine = baseHipLines.find( (hipLine) => hipLine.x1 === verticalPoint[0] && hipLine.y1 === verticalPoint[1] && hipLine.x2 === verticalPoint[2] && hipLine.y2 === verticalPoint[3], ) if (!alreadyVerticalLine) { addLine = drawHipLine(verticalPoint, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: verticalPoint[0], y1: verticalPoint[1], x2: verticalPoint[2], y2: verticalPoint[3], line: addLine }) } } }) ridgeAllPoints.forEach((current) => { ridgeAllPoints .filter((point) => point !== current) .forEach((point) => { let checkRidgeLine, checkHipLine /** 직선인 경우 마루 확인*/ if ( baseRidgeCount < getMaxRidge(baseLines.length) && ((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y)) ) { checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } } /** 대각선인 경우 hip확인*/ const hipX = Big(current.x).minus(Big(point.x)).abs() const hipY = Big(current.y).minus(Big(point.y)).abs() if (hipX.eq(hipY) && hipX.gt(0) && hipY.gt(0)) { checkHipLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } } if (checkRidgeLine) { const ridgePoints = [checkRidgeLine.x1, checkRidgeLine.y1, checkRidgeLine.x2, checkRidgeLine.y2] let baseIntersection = false const ridgeInterSection = [] const ridgeEdge = { vertex1: { x: ridgePoints[0], y: ridgePoints[1] }, vertex2: { x: ridgePoints[2], y: ridgePoints[3] } } baseLines.forEach((line) => { const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside) { ridgeInterSection.push(intersection) } }) baseRidgeLines.forEach((line) => { const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside) { ridgeInterSection.push(intersection) } }) baseHipLines.forEach((line) => { const intersection = edgesIntersection(ridgeEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside) { ridgeInterSection.push(intersection) } }) const otherRidgeInterSection = ridgeInterSection.filter( (intersection) => !( (intersection.x === ridgePoints[0] && intersection.y === ridgePoints[1]) || (intersection.x === ridgePoints[2] && intersection.y === ridgePoints[3]) ), ) const alreadyRidges = baseRidgeLines.filter( (line) => (line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) || (line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]), ) if ( !baseIntersection && alreadyRidges.length === 0 && otherRidgeInterSection.length === 0 && baseRidgeCount < getMaxRidge(baseLines.length) ) { baseRidgeCount = baseRidgeCount + 1 const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) } } if (checkHipLine) { const hipPoints = [checkHipLine.x1, checkHipLine.y1, checkHipLine.x2, checkHipLine.y2] let baseIntersection = false let hipInterSection = [] const hipEdge = { vertex1: { x: hipPoints[0], y: hipPoints[1] }, vertex2: { x: hipPoints[2], y: hipPoints[3] } } baseLines.forEach((line) => { const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside && eavesType.includes(line.attributes.type)) { baseIntersection = true } }) baseRidgeLines.forEach((line) => { const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside) { hipInterSection.push(intersection) } }) baseHipLines.forEach((line) => { const intersection = edgesIntersection(hipEdge, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }) if (intersection && !intersection.isIntersectionOutside) { hipInterSection.push(intersection) } }) const otherHipInterSection = hipInterSection.filter( (intersection) => !( (intersection.x === hipPoints[0] && intersection.y === hipPoints[1]) || (intersection.x === hipPoints[2] && intersection.y === hipPoints[3]) ), ) const alreadyHips = baseHipLines.filter( (line) => (line.x1 === hipPoints[0] && line.y1 === hipPoints[1] && line.x2 === hipPoints[2] && line.y2 === hipPoints[3]) || (line.x1 === hipPoints[2] && line.y1 === hipPoints[3] && line.x2 === hipPoints[0] && line.y2 === hipPoints[1]), ) if (!baseIntersection && alreadyHips.length === 0 && otherHipInterSection.length === 0) { const hipLine = drawHipLine(hipPoints, canvas, roof, textMode, null, prevDegree, currentDegree) baseHipLines.push({ x1: hipPoints[0], y1: hipPoints[1], x2: hipPoints[2], y2: hipPoints[3], line: hipLine }) } } }) }) /** 중복 제거 */ baseHipLines.forEach((hipLine) => { baseHipLines.filter((hipLine2) => hipLine !== hipLine2).forEach((hipLine2) => {}) }) const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseHipLines.map((line) => line.line)] roof.innerLines = innerLines /*const uniqueInnerLines = [] innerLines.forEach((currentLine) => { const sameLines = uniqueInnerLines.filter( (line) => line !== currentLine && ((line.x1 === currentLine.x1 && line.y1 === currentLine.y1 && line.x2 === currentLine.x2 && line.y2 === currentLine.y2) || (line.x1 === currentLine.x2 && line.y1 === currentLine.y2 && line.x2 === currentLine.x1 && line.y2 === currentLine.y1)), ) if (sameLines.length === 0) { uniqueInnerLines.push(currentLine) } else { sameLines.forEach((line) => canvas.remove(line)) } }) canvas.renderAll() roof.innerLines = uniqueInnerLines*/ /** 확인용 라인, 포인트 제거 */ canvas .getObjects() .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() /*drawRidge(roof, canvas, textMode) drawHips(roof, canvas, textMode) connectLinePoint(roof, canvas, textMode) modifyRidge(roof, canvas, textMode) drawCenterLine(roof, canvas, textMode)*/ } /** * 추녀 마루를 그린다. * @param points * @param canvas * @param roof * @param textMode * @param currentRoof * @param prevDegree * @param currentDegree */ const drawHipLine = (points, canvas, roof, textMode, currentRoof, prevDegree, currentDegree) => { const hip = new QLine(points, { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, // currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], }), actualSize: prevDegree === currentDegree ? calcLineActualSize( { x1: points[0], y1: points[1], x2: points[2], y2: points[3], }, currentDegree, ) : 0, }, }) canvas.add(hip) hip.bringToFront() canvas.renderAll() return hip } /** * 라인의 흐름 방향에서 마주치는 지붕선의 포인트를 찾는다. * @param roof * @param baseLine * @param endPoint * @returns {*} */ const findRoofIntersection = (roof, baseLine, endPoint) => { let intersectPoints = [] const { x1, y1, x2, y2 } = baseLine const baseEdge = { vertex1: { x: x1, y: y1 }, vertex2: { x: x2, y: y2 }, } /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(baseEdge, lineEdge) if ( intersection && !intersection.isIntersectionOutside && Math.sign(endPoint.x - baseLine.x1) === Math.sign(endPoint.x - intersection.x) && Math.sign(endPoint.y - baseLine.y1) === Math.sign(endPoint.y - intersection.y) ) { const intersectSize = endPoint.x .minus(Big(intersection.x)) .pow(2) .plus(endPoint.y.minus(Big(intersection.y)).pow(2)) .abs() .sqrt() .toNumber() intersectPoints.push({ intersection, size: intersectSize, }) } }) return intersectPoints.reduce((prev, current) => { return prev.size < current.size ? prev : current }, intersectPoints[0]) } /** * 마루를 그린다. * @param points * @param canvas * @param roof * @param textMode * @returns {*} */ const drawRidgeLine = (points, canvas, roof, textMode) => { const ridge = new QLine(points, { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.RIDGE, textMode: textMode, attributes: { roofId: roof.id, planeSize: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], }), actualSize: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], }), }, }) canvas.add(ridge) ridge.bringToFront() canvas.renderAll() return ridge } /** * 지붕선을 그린다. * @param points * @param canvas * @param roof * @param textMode * @returns {*} */ const drawRoofLine = (points, canvas, roof, textMode) => { const ridge = new QLine(points, { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, planeSize: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], }), actualSize: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], }), }, }) canvas.add(ridge) ridge.bringToFront() canvas.renderAll() return ridge } /** * 벡터를 정규화(Normalization)하는 함수 * @param v * @returns {{x: *, y: *}|{x: number, y: number}} */ const normalizeVector = (v) => { /** 벡터의 크기(길이)*/ const magnitude = Big(v.x).pow(2).plus(Big(v.y).pow(2)).sqrt() if (magnitude.eq(0)) return { x: 0, y: 0 } // 크기가 0일 경우 (예외 처리) return { x: Big(v.x).div(magnitude).toNumber(), y: Big(v.y).div(magnitude).toNumber() } } /** * 사잇각의 절반 방향 벡터 계산 함수 * @param line1 * @param line2 * @returns {{x: *, y: *}|{x: number, y: number}} */ const getHalfAngleVector = (line1, line2) => { const v1 = { x: Big(line1.x2).minus(Big(line1.x1)).toNumber(), y: Big(line1.y1).minus(Big(line1.y2)).toNumber() } const v2 = { x: Big(line2.x2).minus(Big(line2.x1)).toNumber(), y: Big(line2.y1).minus(Big(line2.y2)).toNumber() } /** * 벡터 정규화 * @type {{x: *, y: *}|{x: number, y: number}} */ const unitV1 = normalizeVector(v1) // 첫 번째 벡터를 정규화 const unitV2 = normalizeVector(v2) // 두 번째 벡터를 정규화 /** * 두 벡터를 더합니다 * @type {{x: *, y: *}} */ const summedVector = { x: Big(unitV1.x).plus(Big(unitV2.x)).toNumber(), y: Big(unitV1.y).plus(Big(unitV2.y)).toNumber() } /** 결과 벡터를 정규화하여 사잇각 벡터를 반환합니다 */ return normalizeVector(summedVector) } /** * 마루가 존재하면 그린다. 마루는 지붕의 중간에 위치한다. * * @param roof * @param canvas * @param textMode */ const drawRidges = (roof, canvas, textMode) => { console.log('roof.lines : ', roof.lines) 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 = Big(calculateAngle(prevRoof.startPoint, prevRoof.endPoint)) const angle2 = Big(calculateAngle(nextRoof.startPoint, nextRoof.endPoint)) if ( angle1.minus(angle2).abs().round(Big.roundHalfUp).toNumber() === 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 = Big(currentRoof.x1), currentY1 = Big(currentRoof.y1), currentX2 = Big(currentRoof.x2), currentY2 = Big(currentRoof.y2) let ridgeMaxLength = Big(Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize)).div(10) let ridgeMinLength = Big(Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize)).div(10) const currentAngle = Math.atan2(currentY2.minus(currentY1).toNumber(), currentX2.minus(currentX1).toNumber()) * (180 / Math.PI) anotherRoof .filter((roof) => isInnerLine(prevRoof, currentRoof, nextRoof, roof)) .forEach((innerRoof) => { const vector1 = { x: currentX2.minus(currentX1), y: currentY2.minus(currentY1) } const vector2 = { x: Big(innerRoof.x2).minus(Big(innerRoof.x1)), y: Big(innerRoof.y2).minus(Big(innerRoof.y1)), } const dotProduct = vector1.x.times(vector2.x).plus(vector1.y.times(vector2.y)) const magnitude1 = vector1.x.pow(2).plus(vector1.y.pow(2)).sqrt() const magnitude2 = vector2.x.pow(2).plus(vector2.y.pow(2)).sqrt() const angle = (Math.acos(dotProduct.div(magnitude1.times(magnitude2).toNumber())) * 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(Big(innerBefore.y2).minus(Big(innerBefore.y1)).toNumber(), Big(innerBefore.x2).minus(Big(innerBefore.x1)).toNumber()) * (180 / Math.PI) if (Math.abs(Big(currentAngle).minus(Big(ibAngle))) === 180) { if (currentAngle === 0 || currentAngle === 180) { currentX2 = innerRoof.x1 ridgeMinLength = Big(nextRoof.x1) .minus(Big(nextRoof.x2)) .times(10) .round() .pow(2) .plus(Big(nextRoof.y1).minus(Big(nextRoof.y2)).times(10).round().pow(2)) .sqrt() .div(10) } if (currentAngle === 90 || currentAngle === 270) { currentY2 = innerRoof.y1 ridgeMinLength = Big(nextRoof.x1) .minus(Big(innerRoof.x2)) .times(10) .round() .pow(2) .plus(Big(nextRoof.y1).minus(Big(nextRoof.y2)).times(10).round().pow(2)) .sqrt() .div(10) } } if (Math.abs(currentAngle - ibAngle) === 0) { if (currentAngle === 0 || currentAngle === 180) { currentX1 = innerRoof.x2 ridgeMinLength = Big(prevRoof.x1) .minus(Big(prevRoof.x2)) .times(10) .round() .pow(2) .plus(Big(prevRoof.y1).minus(Big(innerRoof.y1)).times(10).round().pow(2)) .sqrt() .div(10) } if (currentAngle === 90 || currentAngle === 270) { currentY1 = innerRoof.y2 ridgeMinLength = Big(prevRoof.x1) .minus(innerRoof.x1) .times(10) .round() .pow(2) .plus(Big(prevRoof.y2).minus(prevRoof.y2).times(10).round().pow(2)) .sqrt() .div(10) } } } // 현재 지붕선과 반대인 선 if (Math.abs(angle) === 180) { if (currentAngle === 0 || currentAngle === 180) { } if (currentAngle === 90 || currentAngle === 270) { } } }) // 지붕선의 X 중심 const midX = currentX1.plus(currentX2).div(2) // 지붕선의 Y 중심 const midY = currentY1.plus(currentY2).div(2) // 외선벽과 지붕선의 X 거리 const alpha = Big(currentRoof.x1) .plus(Big(currentRoof.x2)) .div(2) .minus(Big(wallLine.x1).plus(Big(wallLine.x2)).div(2)) // 외벽벽과 지붕선의 Y 거리 const beta = Big(currentRoof.y1) .plus(Big(currentRoof.y2)) .div(2) .minus(Big(wallLine.y1).plus(Big(wallLine.y2)).div(2)) // 외벽벽과 지붕선의 거리 const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 지붕선의 평면길이 const currentPlaneSize = Big(calcLinePlaneSize({ x1: currentX1, y1: currentY1, x2: currentX2, y2: currentY2 })) // 용마루의 기반길이(지붕선에서 떨어진 정도) let ridgeBaseLength = currentPlaneSize.div(2).round().div(10) // 시작점은 현재 지붕선을 대각선으로 한 직각이등변삼각형으로 판단하고 현재 지붕선의 가운데에서 지붕에서 90도방향으로 지붕선의 절반만큼 떨어진 방향으로 설정한다. startXPoint = midX.plus(alpha.div(hypotenuse).times(currentPlaneSize.div(2)).neg().div(10)) startYPoint = midY.plus(beta.div(hypotenuse).times(currentPlaneSize.div(2)).neg().div(10)) // 종료점은 시작점에서 용마루의 최대길이 + 기반길이 만큼 그려진것으로 판단하여 늘린다. const checkEndXPoint = startXPoint.plus(Big(Math.sign(alpha.toNumber())).times(ridgeMaxLength.plus(ridgeBaseLength)).neg()) const checkEndYPoint = startYPoint.plus(Big(Math.sign(beta.toNumber())).times(ridgeMaxLength.plus(ridgeBaseLength)).neg()) // 맞은편 외벽선을 확인하기 위한 라인을 생성 const checkLine = new QLine([startXPoint.toNumber(), startYPoint.toNumber(), checkEndXPoint.toNumber(), checkEndYPoint.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: 'red', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, actualSize: 0 }, }) // 디버그시 주석제거해서 라인 확인 // canvas.add(checkLine) // canvas.renderAll() // checkLine 과 마주치는 지붕선을 모두 찾아낸다. 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 }) } }) // intersectLines 중 현재 지붕선과 가장 가까운 지붕선을 찾는다. if (intersectLines.length > 0) { intersectLines.reduce((prev, current) => { if (prev !== undefined) { const prevDistance = Big(prev.x).minus(startXPoint).pow(2).plus(Big(prev.y).minus(startYPoint).pow(2)).sqrt() const currentDistance = Big(current.x).minus(startXPoint).pow(2).plus(Big(current.y).minus(startYPoint).pow(2)).sqrt() return prevDistance > currentDistance ? current : prev } else { return current } }, undefined) } // 현재 지붕선과 마주하는 지붕선이 있는 경우 if (intersectLines.length > 0) { // 마주하는 지붕선 const intersectLine = intersectLines[0] //지붕선에서 마주하는 지붕선까지의 x,y 좌표의 차이 const diffX = Big(intersectLine.x).minus(startXPoint).round(1).abs().lt(1) ? Big(0) : Big(intersectLine.x).minus(startXPoint).round(1) const diffY = Big(intersectLine.y).minus(startYPoint).round(1).abs().lt(1) ? Big(0) : Big(intersectLine.y).minus(startYPoint).round(1) // 시작점에서 ridgeBaseLength 만큼 떨어진 지점까지 용마루 길이로 본다. endXPoint = startXPoint.plus(Big(Math.sign(diffX.toNumber())).times(diffX.abs().minus(ridgeBaseLength))) endYPoint = startYPoint.plus(Big(Math.sign(diffY.toNumber())).times(diffY.abs().minus(ridgeBaseLength))) // 시작점에서 종료점까지의 평면길이 //startXPoint.minus(endXPoint).abs().pow(2).plus(startYPoint.minus(endYPoint).abs().pow(2)).sqrt() const hypo = Big( calcLinePlaneSize({ x1: startXPoint.toNumber(), y1: startYPoint.toNumber(), x2: endXPoint.toNumber(), y2: endYPoint.toNumber(), }), ).div(10) // 현재 지붕선과 마주하는 지붕선을 잇는 선분의 교차점까지의 길이 const intersectLength = Big( calcLinePlaneSize({ x1: midX.toNumber(), y1: midY.toNumber(), x2: intersectLine.x, y2: intersectLine.y, }), ).div(10) //마주하는 지붕선까지의 길이가 현재 지붕선의 이전, 다음 지붕선의 길이보다 작을때 if (intersectLength.lt(Big(prevRoof.attributes.planeSize).div(10)) && intersectLength.lt(Big(nextRoof.attributes.planeSize).div(10))) { endXPoint = startXPoint endYPoint = startYPoint } else { if (ridgeMinLength.lt(hypo)) { endXPoint = startXPoint.plus(Big(Math.sign(diffX.toNumber())).times(ridgeMinLength)) endYPoint = startYPoint.plus(Big(Math.sign(diffY.toNumber())).times(ridgeMinLength)) } } } else { endXPoint = startXPoint.plus(Big(Math.sign(alpha.toNumber())).times(ridgeMinLength).neg()) endYPoint = startYPoint.plus(Big(Math.sign(beta.toNumber())).times(ridgeMinLength).neg()) } // 용마루 선을 그린다. const ridge = new QLine([startXPoint.toNumber(), startYPoint.toNumber(), endXPoint.toNumber(), endYPoint.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.RIDGE, textMode: textMode, attributes: { roofId: roof.id, planeSize: calcLinePlaneSize({ x1: startXPoint.toNumber(), y1: startYPoint.toNumber(), x2: endXPoint.toNumber(), y2: endYPoint.toNumber(), }), actualSize: calcLinePlaneSize({ x1: startXPoint.toNumber(), y1: startYPoint.toNumber(), x2: endXPoint.toNumber(), y2: endYPoint.toNumber(), }), }, }) // 용마루의 길이가 0보다 클때만 canvas 에 추가 한다. if (ridge.attributes.planeSize > 0) { canvas.add(ridge) roof.ridges.push(ridge) roof.innerLines.push(ridge) const distance = (x1, y1, x2, y2) => x2.minus(x1).pow(2).plus(y2.minus(y1).pow(2)).sqrt() const dist1 = distance(startXPoint, startYPoint, Big(currentRoof.x1), Big(currentRoof.y1)) const dist2 = distance(endXPoint, endYPoint, Big(currentRoof.x1), Big(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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.RIDGE, textMode: textMode, attributes: { roofId: roof.id, planeSize: calcLinePlaneSize({ x1, y1, x2, y2 }), actualSize: calcLinePlaneSize({ x1, y1, x2, y2 }), }, }) //겹치는 용마루를 제거한다. 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)) 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 * @param textMode */ const drawHips = (roof, canvas, textMode) => { 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 = Big(ridgeCoordinate.x1).minus(Big(currentRoof.x1)) const vectorY1 = Big(ridgeCoordinate.y1).minus(Big(currentRoof.y1)) //현재 지붕선의 좌표와 용마루 좌표의 각도를 구한다. const angle1 = Big(Math.atan2(vectorY1.toNumber(), vectorX1.toNumber())).times(Big(180).div(Math.PI)) // 용마루 까지의 각도가 45도 인 경우 작성. if (Big(angle1.abs().toNumber()).mod(45).eq(0)) { const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: ridgeCoordinate.x1, y2: ridgeCoordinate.y1, }), actualSize: prevDegree === currentDegree ? calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: ridgeCoordinate.x1, y2: ridgeCoordinate.y1, }, currentDegree, ) : 0, }, }) canvas.add(hip1) roof.hips.push(hip1) roof.innerLines.push(hip1) } const vectorX2 = Big(ridgeCoordinate.x1).minus(Big(currentRoof.x2)) const vectorY2 = Big(ridgeCoordinate.y1).minus(Big(currentRoof.y2)) const angle2 = Big(Math.atan2(vectorY2.toNumber(), vectorX2.toNumber())).times(Big(180).div(Math.PI)) if (Big(angle2.abs().toNumber()).mod(45).eq(0)) { const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: ridgeCoordinate.x1, y2: ridgeCoordinate.y1, }), actualSize: prevDegree === currentDegree ? calcLineActualSize( { x1: currentRoof.x2, y1: currentRoof.y2, x2: ridgeCoordinate.x1, y2: ridgeCoordinate.y1, }, currentDegree, ) : 0, }, }) canvas.add(hip2) 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 = Big(ridge.x1).minus(Big(currentRoof.x1)) const deltaY1 = Big(ridge.y1).minus(Big(currentRoof.y1)) const deltaX2 = Big(ridge.x2).minus(Big(currentRoof.x1)) const deltaY2 = Big(ridge.y2).minus(Big(currentRoof.y1)) if (deltaY1.div(deltaX1).abs().round(1).eq(1)) { ridgePoints.push({ x: ridge.x1, y: ridge.y1 }) } if (deltaY2.div(deltaX2).abs().round(1).eq(1)) { ridgePoints.push({ x: ridge.x2, y: ridge.y2 }) } }) ridgePoints = ridgePoints.reduce((prev, current) => { if (prev !== undefined) { // Math.abs(prev.x - currentRoof.x1) const deltaPrevX = Big(prev.x).minus(Big(currentRoof.x1)).abs() const deltaPrevY = Big(prev.y).minus(Big(currentRoof.y1)).abs() const deltaCurrentX = Big(current.x).minus(Big(currentRoof.x1)).abs() const deltaCurrentY = Big(current.y).minus(Big(currentRoof.y1)).abs() if (deltaPrevX.lt(deltaCurrentX) && deltaPrevY.lt(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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: ridgePoints.x, y2: ridgePoints.y, }), actualSize: prevDegree === currentDegree ? calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: ridgePoints.x, y2: ridgePoints.y, }, currentDegree, ) : 0, }, }) canvas.add(hip) roof.hips.push(hip) roof.innerLines.push(hip) } }) canvas?.renderAll() } /** * 3개 이상 이어지지 않은 라인 포인트 계산 * 모임지붕에서 point 는 3개 이상의 라인과 접해야 함. * @param polygon * @param canvas * @param textMode */ const connectLinePoint = (polygon, canvas, textMode) => { // 연결되지 않은 모든 라인의 포인트를 구한다. let missedPoints = [] const lineDegrees = [ ...new Set(polygon.lines.map((line) => (line.attributes.pitch > 0 ? getDegreeByChon(line.attributes.pitch) : line.attributes.degree))), ] //마루 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], { parentId: polygon.id, attributes: { roofId: polygon.id, planeSize: calcLinePlaneSize(p), actualSize: lineDegrees.length === 0 ? 0 : calcLineActualSize(p, lineDegrees[0]), }, fontSize: polygon.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, }) 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, planeSize: calcLinePlaneSize(p), actualSize: lineDegrees.length === 0 ? 0 : calcLineActualSize(p, lineDegrees[0]), }, fontSize: polygon.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, }) 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, textMode: textMode, attributes: { roofId: polygon.id, planeSize: calcLinePlaneSize(ridge), actualSize: lineDegrees.length > 0 ? 0 : calcLineActualSize(ridge, lineDegrees[0]), }, }) 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() } /** * 외벽선 속성에 따라서 모양을 수정한다. * @param roof * @param canvas * @param textMode */ const modifyRidge = (roof, canvas, textMode) => { 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, textMode) break case LINE_TYPE.WALLLINE.GABLE: changeGableRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.HIPANDGABLE: changeHipAndGableRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.JERKINHEAD: changeJerkInHeadRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.WALL: changeWallRoof(currentRoof, canvas, textMode) 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, textMode) break case LINE_TYPE.WALLLINE.GABLE: changeGableRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.HIPANDGABLE: changeHipAndGableRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.JERKINHEAD: changeJerkInHeadRoof(currentRoof, canvas, textMode) break case LINE_TYPE.WALLLINE.WALL: changeWallRoof(currentRoof, canvas, textMode) break } } } }) } /* 최대 생성 마루 갯수 */ const getMaxRidge = (length) => { return (length - 4) / 2 + 1 } /** * 처마지붕으로 변경 * @param currentRoof * @param canvas * @param textMode */ const changeEavesRoof = (currentRoof, canvas, textMode) => { 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 = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심 const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심 const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심 const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리 const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리 const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리 const hipX2 = midX.plus(alpha.div(hypotenuse).times(Big(currentRoof.length).div(2)).neg()) const hipY2 = midY.plus(beta.div(hypotenuse).times(Big(currentRoof.length).div(2)).neg()) const innerLines = canvas ?.getObjects() .filter( (object) => object.attributes?.roofId === roofId && object.attributes?.currentRoofId === 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.toNumber(), y1: hipY2.toNumber(), 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.toNumber(), y2: hipY2.toNumber(), }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } } //Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) } 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.toNumber(), hipY2.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: hipX2.toNumber(), y2: hipY2.toNumber(), }), actualSize: prevDegree === currentDegree ? calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: hipX2.toNumber(), y2: hipY2.toNumber(), }, currentDegree, ) : 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 - currentDegree) * Math.PI) / 180)) canvas?.add(hip1) roof.innerLines.push(hip1) const hip2 = new QLine([currentRoof.x2, currentRoof.y2, hipX2.toNumber(), hipY2.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: hipX2.toNumber(), y2: hipY2.toNumber(), }), actualSize: currentDegree === nextDegree ? calcLineActualSize( { x1: currentRoof.x2, y1: currentRoof.y2, x2: hipX2.toNumber(), y2: hipY2.toNumber(), }, currentDegree, ) : 0, }, }) canvas?.add(hip2) roof.innerLines.push(hip2) } } /** * 박공지붕으로 변경 * @param currentRoof * @param canvas * @param textMode */ const changeGableRoof = (currentRoof, canvas, textMode) => { 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 = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심 const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심 const innerLines = canvas ?.getObjects() .filter( (object) => object.attributes?.roofId === roofId && object.attributes?.currentRoofId === 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) }) canvas.renderAll() 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.toNumber(), y1: midY.toNumber(), 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.toNumber(), y2: midY.toNumber(), }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } } ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX.toNumber(), midY.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roofId, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: midX.toNumber(), y2: midY.toNumber(), }), actualSize: calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: midX.toNumber(), y2: midY.toNumber(), }, prevDegree, ), }, }) canvas?.add(hip1) // const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10 // const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180)) // hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10 // hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2))) roof.innerLines.push(hip1) let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX.toNumber(), midY.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roofId, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: midX.toNumber(), y2: midY.toNumber(), }), actualSize: calcLineActualSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: midX.toNumber(), y2: midY.toNumber() }, nextDegree), }, }) canvas?.add(hip2) // const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10 // const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180)) // hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10 // hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2))) roof.innerLines.push(hip2) canvas?.renderAll() } } } /** * 팔작지붕으로 변경 * @param currentRoof * @param canvas * @param textMode */ const changeHipAndGableRoof = (currentRoof, canvas, textMode) => { 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 = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심 const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심 const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) // 벽의 X 중심 const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심 const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리 const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리 // Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리 const xWidth = Big(Math.sign(midX.minus(midWallX).toNumber())) .times(alpha.div(hypotenuse)) .times(currentRoof.attributes.width) // 지붕의 X 너비 const yWidth = Big(Math.sign(midY.minus(midWallY))) .times(beta.div(hypotenuse)) .times(currentRoof.attributes.width) // 지붕의 Y 너비 const hipX2 = Big(Math.sign(midX.minus(midWallX).toNumber())) .times(alpha.div(hypotenuse)) .times(currentRoof.length / 2) // 추녀마루의 X 너비 const hipY2 = Big(Math.sign(midY.minus(midWallY))) .times(beta.div(hypotenuse)) .times(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))) { if ( xWidth .pow(2) .plus(yWidth.pow(2)) .sqrt() .lt(hipX2.pow(2).plus(hipY2.pow(2)).sqrt()) ) { const innerLines = canvas ?.getObjects() .filter( (object) => object.attributes?.roofId === roofId && object.attributes?.currentRoofId === 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.minus(ridge.x1).toNumber()) const signY = Math.sign(midY.minus(ridge.y1).toNumber()) ridge.set({ x1: midX.minus(xWidth.abs().times(signX)).toNumber(), y1: midY.minus(yWidth.abs().times(signY)).toNumber(), 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.minus(ridge.x2).toNumber()) const signY = Math.sign(midY.minus(ridge.y2).toNumber()) ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: midX.minus(xWidth.abs().times(signX)).toNumber(), y2: midY.minus(yWidth.abs().times(signY)).toNumber(), }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } } ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) } const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX.plus(hipX2).toNumber(), midY.plus(hipY2).toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: midX.plus(hipX2).toNumber(), y2: midY.plus(hipY2).toNumber(), }), actualSize: calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: midX.plus(hipX2).toNumber(), y2: midY.plus(hipY2).toNumber(), }, currentDegree, ), }, }) // 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.plus(hipX2).toNumber(), midY.plus(hipY2).toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: midX.plus(hipX2).toNumber(), y2: midY.plus(hipY2).toNumber(), }), actualSize: calcLineActualSize( { x1: currentRoof.x2, y1: currentRoof.y2, x2: midX.plus(hipX2).toNumber(), y2: midY.plus(hipY2).toNumber(), }, currentDegree, ), }, }) // 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.toNumber()) const singHipY = Math.sign(hip.y1 - midWallY.toNumber()) 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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, textMode: textMode, planeSize: calcLinePlaneSize({ x1: hip.x2, y1: hip.y2, x2: currentRoof.attributes.ridgeCoordinate.x1, y2: currentRoof.attributes.ridgeCoordinate.y1, }), actualSize: calcLineActualSize( { x1: hip.x2, y1: hip.y2, x2: currentRoof.attributes.ridgeCoordinate.x1, y2: currentRoof.attributes.ridgeCoordinate.y1, }, currentDegree, ), }, }) // 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 * @param textMode */ const changeJerkInHeadRoof = (currentRoof, canvas, textMode) => { 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 = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심 const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심 const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) // 벽의 X 중심 const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심 const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리 const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리 // Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리 const xWidth = Big(Math.sign(midX.minus(midWallX).toNumber())) .times(alpha.div(hypotenuse)) .times(currentRoof.attributes.width / 2) // 지붕의 X 너비 const yWidth = Big(Math.sign(midY.minus(midWallY))) .times(beta.div(hypotenuse)) .times(currentRoof.attributes.width / 2) // 지붕의 Y 너비 const addHipX2 = Big(Math.sign(midX.minus(midWallX).toNumber())) .times(alpha.div(hypotenuse)) .times(currentRoof.length / 2) // 추녀마루의 X 너비 const addHipY2 = Big(Math.sign(midY.minus(midWallY))) .times(beta.div(hypotenuse)) .times(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))) { if ( xWidth .pow(2) .plus(yWidth.pow(2)) .lt(addHipX2.pow(2).plus(addHipY2.pow(2))) ) { // reDrawPolygon(roof, canvas) const innerLines = canvas ?.getObjects() .filter( (object) => object.attributes !== undefined && object.attributes.roofId === roofId && object.attributes.currentRoofId === 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.minus(ridge.x1).toNumber()) const signY = Math.sign(midY.minus(ridge.y1).toNumber()) ridge.set({ x1: midX.minus(xWidth.abs().times(signX)).toNumber(), y1: midY.minus(yWidth.abs().times(signY)).toNumber(), 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.minus(xWidth.abs().times(signX)).toNumber(), y2: midY.minus(yWidth.abs().times(signY)).toNumber(), }) currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } hipX2 = ridge.x2 hipY2 = ridge.y2 } ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) } let hipX1 = (Math.sign(currentRoof.x1 - midX) * currentRoof.attributes.width) / 2 let hipY1 = (Math.sign(currentRoof.y1 - midY) * currentRoof.attributes.width) / 2 const gableDegree = currentRoof.attributes.degree > 0 ? currentRoof.attributes.degree : getDegreeByChon(currentRoof.attributes.pitch) const gable1 = new QLine([midX.plus(hipX1).toNumber(), midY.plus(hipY1).toNumber(), hipX2, hipY2], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: midX.plus(hipX1).toNumber(), y1: midY.plus(hipY1).toNumber(), x2: hipX2, y2: hipY2, }), actualSize: calcLineActualSize( { x1: midX.plus(hipX1).toNumber(), y1: midY.plus(hipY1).toNumber(), x2: hipX2, y2: hipY2, }, gableDegree, ), }, }) // 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.plus(hipX1).toNumber(), midY.plus(hipY1).toNumber(), hipX2, hipY2], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: midX.plus(hipX1).toNumber(), y1: midY.plus(hipY1).toNumber(), x2: hipX2, y2: hipY2, }), actualSize: calcLineActualSize( { x1: midX.plus(hipX1).toNumber(), y1: midY.plus(hipY1).toNumber(), x2: hipX2, y2: hipY2, }, gableDegree, ), }, }) // 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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.GABLE, textMode: textMode, visible: false, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: gable1.x1, y1: gable1.y1, x2: gable2.x1, y2: gable2.y1 }), actualSize: calcLinePlaneSize({ x1: gable1.x1, y1: gable1.y1, x2: gable2.x1, y2: gable2.y1 }), }, }) // 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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, visible: false, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: gable1.x1, y2: gable1.y1 }), actualSize: calcLineActualSize( { x1: currentRoof.x1, y1: currentRoof.y1, x2: gable1.x1, y2: gable1.y1, }, prevDegree, ), }, }) // 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], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, visible: false, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: gable2.x1, y2: gable2.y1 }), actualSize: calcLineActualSize( { x1: currentRoof.x2, y1: currentRoof.y2, x2: gable2.x1, y2: gable2.y1, }, nextDegree, ), }, }) // 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 * @param textMode */ const changeWallRoof = (currentRoof, canvas, textMode) => { 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 = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) const wallMidY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) const roofMidX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) const roofMidY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) const alpha = wallMidX.minus(roofMidX) const beta = wallMidY.minus(roofMidY) currentRoof.set({ x1: alpha.plus(currentRoof.x1).toNumber(), y1: beta.plus(currentRoof.y1).toNumber(), x2: alpha.plus(currentRoof.x2).toNumber(), y2: beta.plus(currentRoof.y2).toNumber(), }) prevRoof.set({ x1: prevRoof.x1, y1: prevRoof.y1, x2: alpha.plus(prevRoof.x2), y2: beta.plus(prevRoof.y2), }) nextRoof.set({ x1: alpha.plus(nextRoof.x1), y1: beta.plus(nextRoof.y1), x2: nextRoof.x2, y2: nextRoof.y2, }) const innerLines = canvas ?.getObjects() .filter( (object) => object.attributes?.roofId === roofId && object.attributes?.currentRoofId === 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 = Big(prevRoof.x2).minus(prevWidthX) const prevY2 = Big(prevRoof.y2).minus(prevWidthY) const nextX1 = Big(nextRoof.x1).plus(nextWidthX) const nextY1 = Big(nextRoof.y1).plus(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.toNumber(), y2: prevY2.toNumber(), }) nextRoof.set({ x1: nextX1.toNumber(), y1: nextY1.toNumber(), x2: nextRoof.x2, y2: nextRoof.y2, }) const addPrevWallLine1 = new QLine( [prevX2.toNumber(), prevY2.toNumber(), Big(wallLine.x1).minus(prevWidthX).toNumber(), Big(wallLine.y1).minus(prevWidthY).toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: 'roofLine', textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: prevX2.toNumber(), y1: prevY2.toNumber(), x2: Big(wallLine.x1).minus(prevWidthX).toNumber(), y2: Big(wallLine.y1).minus(prevWidthY).toNumber(), }), actualSize: calcLinePlaneSize({ x1: prevX2.toNumber(), y1: prevY2.toNumber(), x2: Big(wallLine.x1).minus(prevWidthX).toNumber(), y2: Big(wallLine.y1).minus(prevWidthY).toNumber(), }), }, }, ) const addPrevWallLine2 = new QLine( [ addPrevWallLine1.x2, addPrevWallLine1.y2, Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(), Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(), ], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: 'roofLine', textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addPrevWallLine1.x2, y1: addPrevWallLine1.y2, x2: Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(), y2: Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(), }), actualSize: calcLinePlaneSize({ x1: addPrevWallLine1.x2, y1: addPrevWallLine1.y2, x2: Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(), y2: Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(), }), }, }, ) const addNextWallLine1 = new QLine( [wallLine.x2, wallLine.y2, Big(wallLine.x2).plus(nextWidthX).toNumber(), Big(wallLine.y2).plus(nextWidthY).toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: 'roofLine', textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: wallLine.x2, y1: wallLine.y2, x2: Big(wallLine.x2).plus(nextWidthX).toNumber(), y2: Big(wallLine.y2).plus(nextWidthY).toNumber(), }), actualSize: calcLinePlaneSize({ x1: wallLine.x2, y1: wallLine.y2, }), }, }, ) const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1.toNumber(), nextY1.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: 'roofLine', attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addNextWallLine1.x2, y1: addNextWallLine1.y2, x2: nextX1.toNumber(), y2: nextY1.toNumber(), }), actualSize: calcLinePlaneSize({ x1: addNextWallLine1.x2, y1: addNextWallLine1.y2, x2: nextX1.toNumber(), y2: nextY1.toNumber(), }), }, }) 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 = Big(ridge.x1).minus(wallMidX) const diffY = Big(ridge.y1).minus(wallMidY) ridge.set({ x1: Big(ridge.x1).minus(diffX).toNumber(), y1: Big(ridge.y1).minus(diffY).toNumber(), x2: ridge.x2, y2: ridge.y2, }) } if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { const diffX = Big(ridge.x2).minus(wallMidX) const diffY = Big(ridge.y2).minus(wallMidY) ridge.set({ x1: ridge.x1, y1: ridge.y1, x2: Big(ridge.x2).minus(diffX).toNumber(), y2: Big(ridge.y2).minus(diffY).toNumber(), }) } // ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10) ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 }) let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX.toNumber(), wallMidY.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, textMode: textMode, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: wallMidX.toNumber(), y2: wallMidY.toNumber(), }), actualSize: calcLineActualSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: wallMidX.toNumber(), y2: wallMidY.toNumber() }, prevDegree), }, }) // 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.toNumber(), wallMidY.toNumber()], { parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, name: LINE_TYPE.SUBLINE.HIP, attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: wallMidX.toNumber(), y2: wallMidY.toNumber(), }), actualSize: calcLineActualSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: wallMidX.toNumber(), y2: wallMidY.toNumber() }, nextDegree), }, }) // 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.currentRoofId = 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), // ) const lineLength = calcLinePlaneSize({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }) if (line.attributes !== undefined) { line.attributes.planeSize = lineLength line.attributes.actualSize = lineLength } else { line.attributes = { roofId: newPolygon.id, planeSize: lineLength, actualSize: lineLength, } } }) canvas?.add(newPolygon) canvas?.renderAll() return newPolygon } /** * 지붕의 centerLine을 그린다. * @param roof * @param canvas * @param textMode */ const drawCenterLine = (roof, canvas, textMode) => { //현재 지붕의 centerLine을 다 지운다. canvas .getObjects() .filter((object) => object.attributes?.roofId === roof.id) .filter((line) => line.attributes?.type === 'pitchSizeLine') .forEach((line) => canvas.remove(line)) 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) { 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 if (hips.length > 1) { 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)) ) { //사각이면 마루와 현재 라인 사이에 길이를 구한다 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) } } } } } } else { if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const gables = canvas .getObjects() .filter((object) => object.attributes?.currentRoofId === currentRoof.id && object.name === LINE_TYPE.SUBLINE.GABLE) let x1, y1, x2, y2 x1 = (currentRoof.x1 + currentRoof.x2) / 2 y1 = (currentRoof.y1 + currentRoof.y2) / 2 if (currentRoof.x1 === currentRoof.x2) { const xPoints = [] gables.forEach((gable) => { if (gable.x1 !== x1) { xPoints.push(gable.x1) } else if (gable.x2 !== x1) { xPoints.push(gable.x2) } }) x2 = xPoints.reduce((sum, current) => sum + current, 0) / xPoints.length y2 = y1 } else { const yPoints = [] gables.forEach((gable) => { if (gable.y1 !== y1) { yPoints.push(gable.y1) } else if (gable.y2 !== y1) { yPoints.push(gable.y2) } }) x2 = x1 y2 = yPoints.reduce((sum, current) => sum + current, 0) / yPoints.length } points.push(x1, y1, x2, y2) } } if (points?.length > 0) { const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree const length = currentDegree !== undefined && currentDegree > 0 ? calcLineActualSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }, currentDegree) : calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }) const pitchSizeLine = new QLine(points, { parentId: roof.id, stroke: '#000000', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: roof.fontSize, textMode: textMode, attributes: { roofId: roof.id, type: 'pitchSizeLine', planeSize: length, actualSize: length, }, }) if (length > 0) { canvas.add(pitchSizeLine) canvas.renderAll() } } }) } function createRoofMarginPolygon(polygon, lines, arcSegments = 0) { const offsetEdges = [] polygon.edges.forEach((edge, i) => { const offset = lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 : 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 === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 : 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 } /** * 포인트를 기준으로 선의 길이를 구한다. 선의 길이는 10을 곱하여 사용한다. * @param points * @returns number */ export const calcLinePlaneSize = (points) => { const { x1, y1, x2, y2 } = points return Big(x1).minus(x2).abs().pow(2).plus(Big(y1).minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber() } /** * 포인트와 기울기를 기준으로 선의 길이를 구한다. * @param points * @param degree * @returns number */ export const calcLineActualSize = (points, degree) => { const planeSize = calcLinePlaneSize(points) const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180))) return Big(planeSize).div(theta).round().toNumber() } export const createLinesFromPolygon = (points) => { const lines = [] for (let i = 0; i < points.length; i++) { const nextIndex = (i + 1) % points.length const line = new fabric.Line([points[i].x, points[i].y, points[nextIndex].x, points[nextIndex].y], { stroke: 'red', strokeWidth: 2, selectable: false, evented: false, }) lines.push(line) } return lines } /** 포인트 정렬 가장왼쪽, 가장위 부터 */ const getSortedPoint = (points) => { const startPoint = points .filter((point) => point.x === Math.min(...points.map((point) => point.x))) .reduce((prev, curr) => { return prev.y < curr.y ? prev : curr }) const sortedPoints = [] sortedPoints.push(startPoint) let prevPoint = startPoint for (let i = 0; i < points.length - 1; i++) { const samePoints = [] points .filter((point) => !sortedPoints.includes(point)) .forEach((point) => { if (i % 2 === 1 && prevPoint.y === point.y) { samePoints.push({ point, size: Math.abs(point.x - prevPoint.x) }) } if (i % 2 === 0 && prevPoint.x === point.x) { samePoints.push({ point, size: Math.abs(point.y - prevPoint.y) }) } }) const samePoint = samePoints.sort((a, b) => a.size - b.size)[0].point sortedPoints.push(samePoint) prevPoint = samePoint } return sortedPoints } const reCalculateSize = (line) => { const oldPlaneSize = line.attributes.planeSize const oldActualSize = line.attributes.actualSize const theta = Big(Math.acos(Big(oldPlaneSize).div(oldActualSize))) .times(180) .div(Math.PI) console.log('theta : ', theta.toNumber()) const planeSize = calcLinePlaneSize({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, }) const actualSize = planeSize === oldActualSize ? 0 : calcLineActualSize( { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, }, theta, ) return { planeSize, actualSize } }