import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' import { getAdjacent, getDegreeByChon, isPointOnLine, isPointOnLineNew } 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 Big from 'big.js' const TWO_PI = Math.PI * 2 export const defineQPolygon = () => { 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, hasAuxiliaryLine = false) { 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 !checkPolygonSelfIntersection(polygon) }) // uniquePolygons = uniquePolygons.filter((polygon) => { // return isValidPoints(polygon) // }) console.log('uniquePolygons2', uniquePolygons) return uniquePolygons } /** * 두 선분이 교차하는지 확인하는 함수 * @param {Object} p1 첫 번째 선분의 시작점 {x, y} * @param {Object} q1 첫 번째 선분의 끝점 {x, y} * @param {Object} p2 두 번째 선분의 시작점 {x, y} * @param {Object} q2 두 번째 선분의 끝점 {x, y} * @returns {boolean} 교차하면 true, 아니면 false */ function doSegmentsIntersect(p1, q1, p2, q2) { // CCW (Counter-Clockwise) 방향 확인 함수 function orientation(p, q, r) { const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) if (val === 0) return 0 // 일직선 return val > 0 ? 1 : 2 // 시계방향 또는 반시계방향 } // 점 q가 선분 pr 위에 있는지 확인 function onSegment(p, q, r) { return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y) } // 같은 끝점을 공유하는 경우는 교차로 보지 않음 if ((p1.x === p2.x && p1.y === p2.y) || (p1.x === q2.x && p1.y === q2.y) || (q1.x === p2.x && q1.y === p2.y) || (q1.x === q2.x && q1.y === q2.y)) { return false } const o1 = orientation(p1, q1, p2) const o2 = orientation(p1, q1, q2) const o3 = orientation(p2, q2, p1) const o4 = orientation(p2, q2, q1) // 일반적인 교차 경우 if (o1 !== o2 && o3 !== o4) return true // 특별한 경우들 (한 점이 다른 선분 위에 있는 경우) if (o1 === 0 && onSegment(p1, p2, q1)) return true if (o2 === 0 && onSegment(p1, q2, q1)) return true if (o3 === 0 && onSegment(p2, p1, q2)) return true if (o4 === 0 && onSegment(p2, q1, q2)) return true return false } /** * 다각형의 자기 교차를 검사하는 메인 함수 * @param {Array} coordinates 다각형의 좌표 배열 [{x, y}, ...] * @returns {Object} 검사 결과 {hasSelfIntersection: boolean, intersections: Array} */ function checkPolygonSelfIntersection(coordinates) { if (coordinates.length < 3) { return { hasSelfIntersection: false, intersections: [], error: '다각형은 최소 3개의 점이 필요합니다.', } } const intersections = [] const edges = [] // 모든 변(edge) 생성 for (let i = 0; i < coordinates.length; i++) { const start = coordinates[i] const end = coordinates[(i + 1) % coordinates.length] edges.push({ start: start, end: end, index: i, }) } // 모든 변 쌍에 대해 교차 검사 for (let i = 0; i < edges.length; i++) { for (let j = i + 1; j < edges.length; j++) { // 인접한 변들은 제외 (끝점을 공유하므로) if (Math.abs(i - j) === 1 || (i === 0 && j === edges.length - 1)) { continue } const edge1 = edges[i] const edge2 = edges[j] if (doSegmentsIntersect(edge1.start, edge1.end, edge2.start, edge2.end)) { intersections.push({ edge1Index: i, edge2Index: j, edge1: { from: edge1.start, to: edge1.end, }, edge2: { from: edge2.start, to: edge2.end, }, }) } } } return intersections.length > 0 } // 같은 직선상에 있는지 확인 같은 직선이라면 polygon을 생성할 수 없으므로 false const isValidPoints = (points) => { // 연속된 3개 이상의 점이 같은 x 또는 y 값을 가지는지 확인 (원형 배열로 처리) for (let i = 0; i < points.length; i++) { const point1 = points[i] const point2 = points[(i + 1) % points.length] const point3 = points[(i + 2) % points.length] // x값이 같은 연속된 3개 점 확인 if (point1.x === point2.x && point2.x === point3.x) { return false } // y값이 같은 연속된 3개 점 확인 if (point1.y === point2.y && point2.y === point3.y) { return false } } 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 } /** * 한쪽흐름 지붕 * @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) const shedDegree = getDegreeByChon(sheds[0].attributes.pitch) 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 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) => { let roof = canvas?.getObjects().find((object) => object.id === roofId) const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) 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) { return } const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] /** 외벽선 */ const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ baseLines .filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.WALL && line.attributes.offset > 0) .forEach((currentLine) => { const prevLine = baseLines.find((line) => line.x2 === currentLine.x1 && line.y2 === currentLine.y1) const nextLine = baseLines.find((line) => line.x1 === currentLine.x2 && line.y1 === currentLine.y2) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber() const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber() const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) /** 현재 라인의 지붕 라인을 찾는다. */ const intersectionRoofs = [] let currentRoof if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY }, vertex2: { x: currentMidX, y: currentMidY }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX, y: prevLine.y1 }, vertex2: { x: currentMidX, y: currentMidY }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } if (currentRoof) { const prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) const nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) const prevOffset = prevLine.attributes.offset const nextOffset = nextLine.attributes.offset currentRoof.set({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 }) if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && prevOffset > 0) { const addPoint1 = [] const addPoint2 = [] if (Math.sign(prevLine.y2 - prevLine.y1) === 0) { addPoint1.push(prevRoof.x2, prevRoof.y2, prevRoof.x2, currentRoof.y1) addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1) } else { addPoint1.push(prevRoof.x2, prevRoof.y2, currentRoof.x1, prevRoof.y2) addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1) } const addRoofLine1 = new QLine(addPoint1, { name: 'addRoofLine', parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addPoint1[0], y1: addPoint1[1], x2: addPoint1[2], y2: addPoint1[3], }), actualSize: calcLinePlaneSize({ x1: addPoint1[0], y1: addPoint1[1], x2: addPoint1[2], y2: addPoint1[3], }), }, }) const addRoofLine2 = new QLine(addPoint2, { name: 'addRoofLine', parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addPoint2[0], y1: addPoint2[1], x2: addPoint2[2], y2: addPoint2[3], }), actualSize: calcLinePlaneSize({ x1: addPoint2[0], y1: addPoint2[1], x2: addPoint2[2], y2: addPoint2[3], }), }, }) canvas.add(addRoofLine1, addRoofLine2) canvas.renderAll() const prevIndex = roof.lines.indexOf(prevRoof) if (prevIndex === roof.lines.length - 1) { roof.lines.unshift(addRoofLine1, addRoofLine2) } else { roof.lines.splice(prevIndex + 1, 0, addRoofLine1, addRoofLine2) } } else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) { prevRoof.set({ x2: currentLine.x1, y2: currentLine.y1 }) } if (nextLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && nextOffset > 0) { const addPoint1 = [] const addPoint2 = [] if (Math.sign(nextLine.y2 - nextLine.y1) === 0) { addPoint1.push(currentRoof.x2, currentRoof.y2, nextRoof.x1, currentRoof.y2) addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1) } else { addPoint1.push(currentRoof.x2, currentRoof.y2, currentRoof.x2, nextRoof.y1) addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1) } const addRoofLine1 = new QLine(addPoint1, { name: 'addRoofLine', parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addPoint1[0], y1: addPoint1[1], x2: addPoint1[2], y2: addPoint1[3], }), actualSize: calcLinePlaneSize({ x1: addPoint1[0], y1: addPoint1[1], x2: addPoint1[2], y2: addPoint1[3], }), }, }) const addRoofLine2 = new QLine(addPoint2, { name: 'addRoofLine', parentId: roof.id, fontSize: roof.fontSize, stroke: '#1083E3', strokeWidth: 2, textMode: textMode, attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC, planeSize: calcLinePlaneSize({ x1: addPoint2[0], y1: addPoint2[1], x2: addPoint2[2], y2: addPoint2[3], }), actualSize: calcLinePlaneSize({ x1: addPoint2[0], y1: addPoint2[1], x2: addPoint2[2], y2: addPoint2[3], }), }, }) canvas.add(addRoofLine1, addRoofLine2) canvas.renderAll() const nextIndex = roof.lines.indexOf(nextRoof) if (nextIndex === 0) { roof.lines.push(addRoofLine1, addRoofLine2) } else { roof.lines.splice(nextIndex, 0, addRoofLine1, addRoofLine2) } } else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL) { if (Math.sign(nextLine.y2 - nextLine.y1) === 0) { nextRoof.set({ x1: currentLine.x2, y1: nextRoof.y1 }) } else { nextRoof.set({ x1: nextRoof.x1, y1: currentLine.y2 }) } currentRoof.set({ x2: nextRoof.x1, y2: nextRoof.y1 }) } else if (nextOffset === 0) { nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 }) } roof = reDrawPolygon(roof, canvas) } }) /** 모양 판단을 위한 라인 처리. * 평행한 라인이 나누어져 있는 경우 하나의 선으로 판단 한다. */ 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 }) }) } }) /** 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 drawGablePolygonFirst = [] const drawGablePolygonSecond = [] const drawHipAndGableFirst = [] const drawWallRidgeLine = [] /** 모양을 판단한다. */ drawBaseLines.forEach((currentBaseLine, index) => { let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) // const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { // stroke: 'red', // strokeWidth: 4, // parentId: roofId, // name: 'checkLine', // }) // canvas.add(checkLine) // canvas.renderAll() 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(), } /** 현재 라인이 처마유형일 경우 */ if (currentLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { if (nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { /** * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. */ if (prevLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { if (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 (eavesType.includes(nextLine.attributes?.type)) { drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) { if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { if (checkWallPolygon.inPolygon(checkPoints)) { drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } else { if (currentAngle !== prevAngle && currentAngle !== nextAngle) { drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } } } if (gableType.includes(currentLine.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 }) } else { drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } } } if ( LINE_TYPE.WALLLINE.HIPANDGABLE === currentLine.attributes?.type && eavesType.includes(nextLine.attributes?.type) && eavesType.includes(prevLine.attributes?.type) ) { drawHipAndGableFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) } if ( LINE_TYPE.WALLLINE.WALL === currentLine.attributes?.type && eavesType.includes(nextLine.attributes?.type) && eavesType.includes(prevLine.attributes?.type) ) { if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) && checkWallPolygon.inPolygon(checkPoints)) { drawWallRidgeLine.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) drawWallRidgeLine.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) /** 추녀마루 */ let baseHipLines = [] /** 용마루 */ let baseRidgeLines = [] /** 박공지붕 마루*/ let baseGableRidgeLines = [] /** 박공지붕 라인*/ let baseGableLines = [] /** 용마루의 갯수*/ let baseRidgeCount = 0 // console.log('drawEavesFirstLines :', drawEavesFirstLines) // console.log('drawEavesSecondLines :', drawEavesSecondLines) // console.log('drawGableRidgeFirst: ', drawGableRidgeFirst) // console.log('drawGableRidgeSecond:', drawGableRidgeSecond) // console.log('drawGablePolygonFirst :', drawGablePolygonFirst) // console.log('drawGablePolygonSecond :', drawGablePolygonSecond) // console.log('drawHipAndGableFirst :', drawHipAndGableFirst) // console.log('drawWallLines :', drawWallRidgeLine) /** 박공지붕에서 파생되는 마루를 그린다. ㄷ 형태*/ 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 = getDegreeByChon(prevLine.attributes.pitch) /** 다음 라인의 경사 */ const nextDegree = getDegreeByChon(nextLine.attributes.pitch) /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ 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))) /** 현재라인의 기준점*/ let currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) let currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) /** 마루 반대 좌표*/ let oppositeMidX = currentMidX, oppositeMidY = currentMidY /** 현재 라인의 지붕 라인을 찾는다. */ const intersectionRoofs = [] let currentRoof if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } /** 현재 라인의 지붕선에서 이전 지붕선, 다음 지붕선으로 향하는 vector*/ const prevRoofVectorX = Math.sign(currentRoof.x2 - currentRoof.x1) const prevRoofVectorY = Math.sign(currentRoof.y2 - currentRoof.y1) const nextRoofVectorX = Math.sign(currentRoof.x1 - currentRoof.x2) const nextRoofVectorY = Math.sign(currentRoof.y1 - currentRoof.y2) /** 한개의 지붕선을 둘로 나누어서 처리 하는 경우 */ if (prevAngle === beforePrevAngle || nextAngle === afterNextAngle) { if (currentVectorX === 0) { const addLength = Big(currentLine.y1).minus(Big(currentLine.y2)).abs().div(2) const ridgeVector = Math.sign(prevLine.x1 - currentLine.x1) oppositeMidX = Big(prevLine.x1).plus(Big(addLength).times(ridgeVector)) const ridgeEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } const ridgeVectorX = Math.sign(ridgeEdge.vertex1.x - ridgeEdge.vertex2.x) roof.lines .filter((line) => line.x1 === line.x2) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(ridgeEdge, lineEdge) if (is) { const isVectorX = Math.sign(ridgeEdge.vertex1.x - is.x) if ( isVectorX === ridgeVectorX && ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) ) { currentMidX = Big(is.x) currentMidY = Big(is.y) } } }) } else { const addLength = Big(currentLine.x1).minus(Big(currentLine.x2)).abs().div(2) const ridgeVector = Math.sign(prevLine.y1 - currentLine.y1) oppositeMidY = Big(prevLine.y1).plus(addLength.times(ridgeVector)) const ridgeEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } const ridgeVectorY = Math.sign(ridgeEdge.vertex1.y - ridgeEdge.vertex2.y) roof.lines .filter((line) => 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(ridgeEdge, lineEdge) if (is) { const isVectorY = Math.sign(ridgeEdge.vertex1.y - is.y) if ( isVectorY === ridgeVectorY && ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) ) { currentMidX = Big(is.x) currentMidY = Big(is.y) } } }) } const prevHipEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: prevLine.x1, y: prevLine.y1 }, } const prevHipVectorX = Math.sign(prevHipEdge.vertex1.x - prevHipEdge.vertex2.x) const prevHipVectorY = Math.sign(prevHipEdge.vertex1.y - prevHipEdge.vertex2.y) const prevIsPoints = [] roof.lines .filter((line) => (Math.sign(prevLine.x1 - prevLine.x2) === 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(prevHipEdge, lineEdge) if (is) { const isVectorX = Math.sign(prevHipEdge.vertex1.x - is.x) const isVectorY = Math.sign(prevHipEdge.vertex1.y - is.y) if (isVectorX === prevHipVectorX && isVectorY === prevHipVectorY) { const size = Big(prevHipEdge.vertex1.x) .minus(Big(is.x)) .abs() .pow(2) .plus(Big(prevHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) .sqrt() .toNumber() prevIsPoints.push({ is, size }) } } }) if (prevIsPoints.length > 0) { const prevIs = prevIsPoints.sort((a, b) => a.size - b.size)[0].is const prevHipLine = drawHipLine( [prevIs.x, prevIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], canvas, roof, textMode, null, prevDegree, prevDegree, ) baseHipLines.push({ x1: prevLine.x1, y1: prevLine.y1, x2: oppositeMidX.toNumber(), y2: oppositeMidY.toNumber(), line: prevHipLine, }) } const nextHipEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: nextLine.x2, y: nextLine.y2 }, } const nextHipVectorX = Math.sign(nextHipEdge.vertex1.x - nextHipEdge.vertex2.x) const nextHipVectorY = Math.sign(nextHipEdge.vertex1.y - nextHipEdge.vertex2.y) const nextIsPoints = [] roof.lines .filter((line) => (Math.sign(nextLine.x1 - nextLine.x2) === 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(nextHipEdge, lineEdge) if (is) { const isVectorX = Math.sign(nextHipEdge.vertex1.x - is.x) const isVectorY = Math.sign(nextHipEdge.vertex1.y - is.y) if (isVectorX === nextHipVectorX && isVectorY === nextHipVectorY) { const size = Big(nextHipEdge.vertex1.x) .minus(Big(is.x)) .abs() .pow(2) .plus(Big(nextHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) .sqrt() .toNumber() nextIsPoints.push({ is, size }) } } }) if (nextIsPoints.length > 0) { const nextIs = nextIsPoints.sort((a, b) => a.size - b.size)[0].is const nextHipLine = drawHipLine( [nextIs.x, nextIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], canvas, roof, textMode, null, nextDegree, nextDegree, ) baseHipLines.push({ x1: nextLine.x2, y1: nextLine.y2, x2: oppositeMidX.toNumber(), y2: oppositeMidY.toNumber(), line: nextHipLine, }) } const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) /** 반철처 인 경우 처리 */ if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const width = Big(currentLine.attributes.width).div(2) const degree = getDegreeByChon(currentLine.attributes.pitch) const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) if (vectorOppositeY === 0) { currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) } else { currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) } /** 현재 라인에서 반철처 부분을 그린다.*/ let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint if (vectorOppositeY === 0) { firstHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } else { firstHipPoint = [ currentMidX.plus(Big(width).times(prevRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.plus(Big(width).times(nextRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree) const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree) const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree) const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) } else { const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) } if (baseRidgeCount < getMaxRidge(baseLines.length)) { const ridgeLine = drawRidgeLine( [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], canvas, roof, textMode, ) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ } } 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 (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { 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 = getDegreeByChon(afterNextLine.attributes.pitch) 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) { if (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } const intersectionPoints = [] 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 intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) ) { const size = Big(intersection.x) .minus(currentMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection currentMidX = Big(intersection.x) currentMidY = Big(intersection.y) } } if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { const checkEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } const intersectionPoints = [] 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 intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) ) { const size = Big(intersection.x) .minus(oppositeMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection oppositeMidX = Big(intersection.x) oppositeMidY = Big(intersection.y) } } if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { const width = afterNextLine.attributes.width if (vectorOppositeY === 0) { oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX)) } else { oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY)) } } if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const width = Big(afterNextLine.attributes.width).div(2).toNumber() if (vectorOppositeY === 0) { oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX)) } else { oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY)) } } /** 반철처 인 경우 처리 */ if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const width = Big(currentLine.attributes.width).div(2) const degree = getDegreeByChon(currentLine.attributes.pitch) const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) if (vectorMidY === 0) { currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) } else { currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) } /** 현재 라인에서 반철처 부분을 그린다.*/ let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint if (vectorMidY === 0) { firstHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } else { firstHipPoint = [ currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree) const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree) const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree) const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) } else { const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) } if (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 currentMidEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: currentVectorX === 0 ? nextLine.x2 : currentMidX.toNumber(), y: currentVectorX === 0 ? currentMidY.toNumber() : nextLine.y2, }, } let oppositeLines = [] drawBaseLines .filter((line, index) => { const currentLine = line.line const nextLine = drawBaseLines[(index + 1) % drawBaseLines.length].line const prevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length].line const angle = calculateAngle(currentLine.startPoint, currentLine.endPoint) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) if (angle === prevAngle || angle === nextAngle) { const sameAngleLine = angle === prevAngle ? prevLine : nextLine if (gableType.includes(currentLine.attributes.type) && !gableType.includes(sameAngleLine.attributes.type)) { 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) { if (line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) { oppositeLines.push({ line, intersection, size: Big(intersection.x) .minus(currentMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) .sqrt() .toNumber(), }) } } }) if (oppositeLines.length > 0) { const oppositePoint = oppositeLines.sort((a, b) => a.size - b.size)[0].intersection const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: oppositePoint.x, y: oppositePoint.y }, } const oppositeRoofPoints = [] roof.lines .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 } }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) ) { oppositeRoofPoints.push({ line, intersection, size: Big(intersection.x) .minus(currentMidX.toNumber()) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) .sqrt() .toNumber(), }) } }) const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection oppositeMidX = Big(oppositeRoofPoint.x) oppositeMidY = Big(oppositeRoofPoint.y) const currentRoofPoints = [] roof.lines .filter((line) => { const angle = calculateAngle(line.startPoint, line.endPoint) return currentAngle === angle }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) ) { currentRoofPoints.push({ line, intersection, size: Big(intersection.x) .minus(currentMidX.toNumber()) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) .sqrt() .toNumber(), }) } }) const currentRoofPoint = currentRoofPoints.sort((a, b) => a.size - b.size)[0].intersection currentMidX = Big(currentRoofPoint.x) currentMidY = Big(currentRoofPoint.y) } else { 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 = getDegreeByChon(intersect.line.attributes.pitch) 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 = getDegreeByChon(intersect.line.attributes.pitch) 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 (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } const intersectionPoints = [] 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 intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) ) { const size = Big(intersection.x) .minus(currentMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection currentMidX = Big(intersection.x) currentMidY = Big(intersection.y) } } if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { const checkEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } const intersectionPoints = [] 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 intersection = edgesIntersection(checkEdge, lineEdge) if ( intersection && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) ) { const size = Big(intersection.x) .minus(oppositeMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection oppositeMidX = Big(intersection.x) oppositeMidY = Big(intersection.y) } } const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) /** 반철처 인 경우 처리 */ if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const width = Big(currentLine.attributes.width).div(2) const degree = getDegreeByChon(currentLine.attributes.pitch) const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) if (vectorOppositeY === 0) { currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) } else { currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) } /** 현재 라인에서 반철처 부분을 그린다.*/ let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint if (vectorOppositeY === 0) { firstHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } else { firstHipPoint = [ currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] secondHipPoint = [ currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(), currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), currentMidX.toNumber(), currentMidY.toNumber(), ] } connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree) const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree) const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree) const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) } else { const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree) baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) } /** 마루가 맞은편 외벽선에 닿는 경우 해당 부분까지로 한정한다. */ 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) } } }) if (baseRidgeCount < getMaxRidge(baseLines.length)) { const ridgeLine = drawRidgeLine( [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], 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 /** 이전 라인의 경사 */ const prevDegree = getDegreeByChon(prevLine.attributes.pitch) /** 다음 라인의 경사 */ const nextDegree = getDegreeByChon(nextLine.attributes.pitch) 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 intersectionRoofs = [] if (currentVectorX.eq(0)) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter( (line) => Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) && Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()), ) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter( (line) => Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) && Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()), ) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber(), }) } } }) } let currentRoof if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } if (currentLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || currentLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { 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) => { if (eavesType.includes(line.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 default: return false } } else { 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) => b.size - a.size)[0].line let ridgePoint if (currentVectorY.eq(0)) { const ridgeY = Big(currentLine.y1).plus(Big(oppositeLine.y1)).div(2).round() ridgePoint = [currentRoof.x1, ridgeY.toNumber(), currentRoof.x2, ridgeY.toNumber()] } else { const ridgeX = Big(currentLine.x1).plus(Big(oppositeLine.x1)).div(2).round() ridgePoint = [ridgeX.toNumber(), currentRoof.y1, ridgeX.toNumber(), currentRoof.y2] } const isAlreadyRidge = baseGableRidgeLines.find( (line) => (line.x1 === ridgePoint[0] && line.y1 === ridgePoint[1] && line.x2 === ridgePoint[2] && line.y2 === ridgePoint[3]) || (line.x1 === ridgePoint[2] && line.y1 === ridgePoint[3] && line.x2 === ridgePoint[0] && line.y2 === ridgePoint[1]) || segmentsOverlap(line, { x1: ridgePoint[0], y1: ridgePoint[1], x2: ridgePoint[2], y2: ridgePoint[3] }), ) if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) { const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ } } else { 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] } } const isAlreadyRidge = baseGableRidgeLines.find( (line) => (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), ) if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) { const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) baseGableRidgeLines.push(ridgeLine) baseRidgeCount++ } } 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 isAlreadyRidge = baseGableRidgeLines.find( (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 (!isAlreadyRidge) { const ridge = drawRidgeLine(ridgePoints, canvas, roof, textMode) baseGableRidgeLines.push(ridge) baseRidgeCount++ } } } } }) 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 /** 박공지붕 polygon 생성 */ drawGablePolygonFirst.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) const intersectionRoofs = [] if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } let currentRoof, prevRoof, nextRoof if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } if (currentRoof) { prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) const prevRoofEdge = { vertex1: { x: prevRoof.x2, y: prevRoof.y2 }, vertex2: { x: prevRoof.x1, y: prevRoof.y1 }, } const nextRoofEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 }, } let polygonPoints = [ { x: currentRoof.x1, y: currentRoof.y1 }, { x: currentRoof.x2, y: currentRoof.y2 }, ] const prevHipLines = [] const nextHipLines = [] let prevLineRidge, nextLineRidge baseHipLines.forEach((current) => { const { line } = current if ( (Math.abs(line.x1 - currentRoof.x1) <= 1 && Math.abs(line.y1 - currentRoof.y1) <= 1 && isPointOnLine(prevRoof, { x: line.x2, y: line.y2 })) || (Math.abs(line.x2 - currentRoof.x1) <= 1 && Math.abs(line.y2 - currentRoof.y1) <= 1 && isPointOnLine(prevRoof, { x: line.x1, y: line.y1, })) ) { prevHipLines.push(current) } if ( (Math.abs(line.x1 - currentRoof.x2) <= 1 && Math.abs(line.y1 - currentRoof.y2) <= 1 && isPointOnLine(nextRoof, { x: line.x2, y: line.y2 })) || (Math.abs(line.x2 - currentRoof.x2) <= 1 && Math.abs(line.y2 - currentRoof.y2) <= 1 && isPointOnLine(nextRoof, { x: line.x1, y: line.y1, })) ) { nextHipLines.push(current) } }) prevHipLines.forEach((current) => { if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { let findPoint if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) { findPoint = { x: current.x2, y: current.y2 } } else { findPoint = { x: current.x1, y: current.y1 } } baseHipLines .filter( (line) => ((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) || (Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) && line.x1 !== line.x2 && line.y1 !== line.y2, ) .forEach((line) => { polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) let ridgePoint if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) { ridgePoint = { x: line.x2, y: line.y2 } } else { ridgePoint = { x: line.x1, y: line.y1 } } prevLineRidge = baseGableRidgeLines.find( (ridge) => (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), ) }) } else { let ridgePoint if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) { ridgePoint = { x: current.x2, y: current.y2 } } else { ridgePoint = { x: current.x1, y: current.y1 } } prevLineRidge = baseGableRidgeLines.find((ridge) => { return ( (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1) ) }) } }) nextHipLines.forEach((current) => { if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { let findPoint if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) { findPoint = { x: current.x2, y: current.y2 } } else { findPoint = { x: current.x1, y: current.y1 } } baseHipLines .filter( (line) => ((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) || (Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) && line.x1 !== line.x2 && line.y1 !== line.y2, ) .forEach((line) => { polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) let ridgePoint if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) { ridgePoint = { x: line.x2, y: line.y2 } } else { ridgePoint = { x: line.x1, y: line.y1 } } nextLineRidge = baseGableRidgeLines.find( (ridge) => (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), ) }) } else { let ridgePoint if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) { ridgePoint = { x: current.x2, y: current.y2 } } else { ridgePoint = { x: current.x1, y: current.y1 } } nextLineRidge = baseGableRidgeLines.find( (ridge) => (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), ) } }) if (!prevLineRidge) { if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { } else { const isRidgePoints = [] baseGableRidgeLines.forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const intersection = edgesIntersection(prevRoofEdge, ridgeEdge) if ( intersection && ((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) || (ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1)) ) { const size = Big(intersection.x) .minus(Big(currentRoof.x1)) .abs() .pow(2) .plus(Big(intersection.y).minus(Big(currentRoof.y1)).abs().pow(2)) .sqrt() isRidgePoints.push({ intersection, ridge, size }) } }) if (isRidgePoints.length > 0) { const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size) prevLineRidge = sortedRidgePoints[0].ridge } } } if (!nextLineRidge) { if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { } else { const isRidgePoints = [] baseGableRidgeLines.forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const intersection = edgesIntersection(nextRoofEdge, ridgeEdge) if ( intersection && ((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) || (ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1)) ) { const size = Big(intersection.x) .minus(Big(currentRoof.x2)) .abs() .pow(2) .plus(Big(intersection.y).minus(Big(currentRoof.y2)).abs().pow(2)) .sqrt() isRidgePoints.push({ intersection, ridge, size }) } }) if (isRidgePoints.length > 0) { const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size) nextLineRidge = sortedRidgePoints[0].ridge } } } const ridgeLine = prevLineRidge === undefined ? nextLineRidge : prevLineRidge if (prevLineRidge !== undefined && nextLineRidge !== undefined) { /** 4각*/ if (prevLineRidge === nextLineRidge) { polygonPoints.push({ x: ridgeLine.x1, y: ridgeLine.y1 }, { x: ridgeLine.x2, y: ridgeLine.y2 }) /** 포인트가 직각 사각형인지 확인하여 아닌경우 직각인 다각형 포인트로 변경한다.*/ const checkPoints = getSortedPoint(polygonPoints, baseHipLines) let hasDiagonal = false if (checkPoints < 4) { hasDiagonal = true } else { checkPoints.forEach((point, index) => { const nextPoint = checkPoints[(index + 1) % checkPoints.length] if (point.x !== nextPoint.x && point.y !== nextPoint.y) { hasDiagonal = true } }) } if (hasDiagonal) { const vectorX = Math.sign(currentRoof.x1 - ridgeLine.x1) const vectorY = Math.sign(currentRoof.y1 - ridgeLine.y1) const ridgeMinX = Math.min(ridgeLine.x1, ridgeLine.x2) const ridgeMaxX = Math.max(ridgeLine.x1, ridgeLine.x2) const ridgeMinY = Math.min(ridgeLine.y1, ridgeLine.y2) const ridgeMaxY = Math.max(ridgeLine.y1, ridgeLine.y2) if ( (!isPointOnLineNew(prevRoof, { x: ridgeLine.x1, y: ridgeLine.y1 }) && !isPointOnLineNew(nextRoof, { x: ridgeLine.x1, y: ridgeLine.y1 })) || (!isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) && !isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.y2 })) ) { roof.lines .filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof) .filter((line) => ridgeLine.y1 === ridgeLine.y2 ? vectorY === Math.sign(line.y1 - ridgeLine.y1) && ridgeMinX <= line.x1 && line.x1 <= ridgeMaxX && ridgeMinX <= line.x2 && line.x2 <= ridgeMaxX : vectorX === Math.sign(line.x1 - ridgeLine.x1) && ridgeMinY <= line.y1 && line.y1 <= ridgeMaxY && ridgeMinY <= line.y2 && line.y2 <= ridgeMaxY, ) .forEach((line) => { if (ridgeLine.y1 === ridgeLine.y2) { if (vectorY === Math.sign(line.y1 - ridgeLine.y1)) { polygonPoints.push({ x: line.x1, y: line.y1 }) } if (vectorY === Math.sign(line.y2 - ridgeLine.y1)) { polygonPoints.push({ x: line.x2, y: line.y2 }) } } else { if (vectorX === Math.sign(line.x1 - ridgeLine.x1)) { polygonPoints.push({ x: line.x1, y: line.y1 }) } if (vectorX === Math.sign(line.x2 - ridgeLine.x1)) { polygonPoints.push({ x: line.x2, y: line.y2 }) } } }) } if ( !isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) && !isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.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 - currentRoof.x1) : Math.abs(prevLineRidge.y1 - currentRoof.y1) const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - currentRoof.x1) : Math.abs(nextLineRidge.y1 - currentRoof.y1) /** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */ if (Math.abs(prevDistance - nextDistance) < 1) { const minX = Math.min(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2) const maxX = Math.max(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2) const minY = Math.min(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2) const maxY = Math.max(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2) if (currentVectorX === 0) { polygonPoints.push({ x: prevLineRidge.x1, y: minY }, { x: prevLineRidge.x1, y: maxY }) } else { polygonPoints.push({ x: minX, y: prevLineRidge.y1 }, { x: maxX, y: prevLineRidge.y1 }) } } else if (prevDistance < nextDistance) { polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }) /** 이전라인과 교차한 마루의 포인트*/ let prevRidgePoint1 if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { prevRidgePoint1 = polygonPoints.find( (point) => (point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1) || (point.x === prevLineRidge.x2 && point.y === prevLineRidge.y2), ) } else { prevRidgePoint1 = currentVectorX === 0 ? currentRoof.y1 === prevLineRidge.y1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } : currentRoof.x1 === prevLineRidge.x1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } polygonPoints.push(prevRidgePoint1) } /** 다음 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ let checkRidgePoint if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const ridgePoint1 = polygonPoints.filter((point) => point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1) checkRidgePoint = ridgePoint1.length > 0 ? { x: nextLineRidge.x2, y: nextLineRidge.y2 } : { x: nextLineRidge.x1, y: nextLineRidge.y1 } } else { checkRidgePoint = currentVectorX === 0 ? currentRoof.y2 !== nextLineRidge.y1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } : currentRoof.x2 !== 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 }) /** 다음라인과 교차한 마루의 포인트*/ let nextRidgePoint1 if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { nextRidgePoint1 = polygonPoints.find( (point) => (point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1) || (point.x === nextLineRidge.x2 && point.y === nextLineRidge.y2), ) } else { nextRidgePoint1 = currentVectorX === 0 ? currentRoof.y2 === nextLineRidge.y1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } : currentRoof.x2 === nextLineRidge.x1 ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } : { x: nextLineRidge.x2, y: nextLineRidge.y2 } polygonPoints.push(nextRidgePoint1) } /** 이전 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ let checkRidgePoint if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const ridgePoint1 = polygonPoints.filter((point) => point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1) checkRidgePoint = ridgePoint1.length > 0 ? { x: prevLineRidge.x2, y: prevLineRidge.y2 } : { x: prevLineRidge.x1, y: prevLineRidge.y1 } } else { checkRidgePoint = currentVectorX === 0 ? currentRoof.y1 !== prevLineRidge.y1 ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } : { x: prevLineRidge.x2, y: prevLineRidge.y2 } : currentRoof.x1 !== 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 }) } } } } } } else { if (ridgeLine) { const ridgeEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } const prevRoofEdge = { vertex1: { x: prevRoof.x1, y: prevRoof.y1 }, vertex2: { x: prevRoof.x2, y: prevRoof.y2 } } const nextRoofEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } } const isPrevRoof = edgesIntersection(prevRoofEdge, ridgeEdge) const isNextRoof = edgesIntersection(nextRoofEdge, ridgeEdge) if (isPrevRoof && isPointOnLine(ridgeLine, isPrevRoof)) { polygonPoints.push({ x: isPrevRoof.x, y: isPrevRoof.y }) } else { polygonPoints.push({ x: prevRoof.x1, y: prevRoof.y1 }) } if (isNextRoof && isPointOnLine(ridgeLine, isNextRoof)) { polygonPoints.push({ x: isNextRoof.x, y: isNextRoof.y }) } else { polygonPoints.push({ x: nextRoof.x2, y: nextRoof.y2 }) } roof.lines .filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof) .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 && isPointOnLine(ridgeLine, intersection)) { const size1 = Math.sqrt(Math.pow(line.x1 - intersection.x, 2) + Math.pow(line.y1 - intersection.y, 2)) const size2 = Math.sqrt(Math.pow(line.x2 - intersection.x, 2) + Math.pow(line.y2 - intersection.y, 2)) if (size1 < size2) { polygonPoints.push({ x: line.x2, y: line.y2 }) } else { polygonPoints.push({ x: line.x1, y: line.y1 }) } polygonPoints.push({ x: intersection.x, y: intersection.y }) } }) } } /** 중복되는 포인트 제거 */ const uniquePoints = [] polygonPoints.forEach((point) => { const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y) if (!isAlready) { uniquePoints.push(point) } }) polygonPoints = getSortedPoint(uniquePoints, baseHipLines) /*polygonPoints.forEach((point) => { const checkCircle = new fabric.Circle({ left: point.x, top: point.y, radius: 4, fill: 'red', parentId: roofId, name: 'checkCircle', }) canvas.add(checkCircle) canvas.renderAll() /!** 확인용 라인 제거 *!/ canvas .getObjects() .filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() })*/ polygonPoints.forEach((currentPoint, index) => { const nextPoint = polygonPoints[(index + 1) % polygonPoints.length] const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y] const isParallel = ridgeLine.x1 === ridgeLine.x2 ? currentPoint.x === nextPoint.x : currentPoint.y === nextPoint.y const ridgeLines = baseGableRidgeLines.filter( (line) => (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), ) const hipLine = baseHipLines.filter( (line) => (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), ) /** 이미 존재하는 라인이면 넘긴다.*/ if (ridgeLines.length > 0 || hipLine.length > 0) { return } let line if (isParallel) { line = drawRoofLine(points, canvas, roof, textMode) baseGableLines.push(line) } else { line = drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree) baseHipLines.push({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, line }) } }) } }) 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 /*const checkLine = new fabric.Line([x1, y1, x2, y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'checkLine', }) canvas.add(checkLine) canvas.renderAll()*/ const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) const intersectionRoofs = [] if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), }) } }) } else { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), }) } }) } let currentRoof, prevRoof, nextRoof if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) } let polygonPoints = [] if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { 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(Big(10).times(Math.sign(xVector.toNumber()))).toNumber(), y: currentMidY.plus(Big(10).times(Math.sign(yVector.toNumber()))).toNumber(), } if (checkWallPolygon.inPolygon(checkPoints)) { 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 currentRidge = baseGableRidgeLines.find((line) => currentVectorX === 0 ? (Math.abs(line.y1 - currentRoof.y1) < 1 && Math.abs(line.y2 - currentRoof.y2) < 1) || (Math.abs(line.y1 - currentRoof.y2) < 1 && Math.abs(line.y2 - currentRoof.y1) < 1) : (Math.abs(line.x1 - currentRoof.x1) < 1 && Math.abs(line.x2 - currentRoof.x2) < 1) || (Math.abs(line.x1 - currentRoof.x2) < 1 && Math.abs(line.x2 - currentRoof.x1) < 1), ) if (currentRidge) { if (currentVectorX === 0) { polygonPoints.push({ x: currentRoof.x1, y: currentLine.y1 }, { x: currentRoof.x2, y: currentLine.y2 }) polygonPoints.push({ x: currentRidge.x1, y: currentLine.y1 }, { x: currentRidge.x1, y: currentLine.y2 }) } else { polygonPoints.push({ x: currentLine.x1, y: currentRoof.y1 }, { x: currentLine.x2, y: currentRoof.y2 }) polygonPoints.push({ x: currentLine.x1, y: currentRidge.y1 }, { x: currentLine.x2, y: currentRidge.y1 }) } } } } else { const prevEdge = { vertex1: { x: prevRoof.x2, y: prevRoof.y2 }, vertex2: { x: prevRoof.x1, y: prevRoof.y1 } } const prevVectorX = Math.sign(prevRoof.x1 - prevRoof.x2) const prevVectorY = Math.sign(prevRoof.y1 - prevRoof.y2) let prevRidge, nextRidge if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const hipLines = [] baseHipLines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(prevEdge, lineEdge) if (is) { const size = Big(is.x).minus(prevRoof.x2).abs().pow(2).plus(Big(is.y).minus(prevRoof.y2).abs().pow(2)).sqrt().toNumber() hipLines.push({ line, is, size }) } }) if (hipLines.length > 0) { hipLines.sort((a, b) => a.size - b.size) const hipLine = hipLines[0] polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y }) const ridgePoint = hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1 ? { x: hipLine.line.x2, y: hipLine.line.y2 } : { x: hipLine.line.x1, y: hipLine.line.y1 } prevRidge = baseGableRidgeLines.find( (line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y), ) } } else { const prevRidges = [] baseGableRidgeLines.forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const is = edgesIntersection(prevEdge, ridgeEdge) if (is) { const isVectorX = Math.sign(prevRoof.x1 - is.x) const isVectorY = Math.sign(prevRoof.y1 - is.y) if ( isVectorX === prevVectorX && isVectorY === prevVectorY && ((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1)) ) { const size = Big(prevRoof.x1).minus(is.x).abs().pow(2).plus(Big(prevRoof.y1).minus(is.y).abs().pow(2)).sqrt().toNumber() prevRidges.push({ ridge, size }) } } }) if (prevRidges.length > 0) { prevRidges.sort((a, b) => a.size - b.size) prevRidge = prevRidges[0].ridge } } const nextEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } } const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { const hipLines = [] baseHipLines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(nextEdge, lineEdge) if (is) { const size = Big(is.x).minus(nextRoof.x1).abs().pow(2).plus(Big(is.y).minus(nextRoof.y1).abs().pow(2)).sqrt().toNumber() hipLines.push({ line, is, size }) } }) if (hipLines.length > 0) { hipLines.sort((a, b) => a.size - b.size) const hipLine = hipLines[0] polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y }) const ridgePoint = hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1 ? { x: hipLine.line.x2, y: hipLine.line.y2 } : { x: hipLine.line.x1, y: hipLine.line.y1 } nextRidge = baseGableRidgeLines.find( (line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y), ) } } else { const nextRidges = [] baseGableRidgeLines.forEach((ridge) => { const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } const is = edgesIntersection(nextEdge, ridgeEdge) if (is) { const isVectorX = Math.sign(nextRoof.x1 - is.x) const isVectorY = Math.sign(nextRoof.y1 - is.y) if ( isVectorX === nextVectorX && isVectorY === nextVectorY && ((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1)) ) { const size = Big(nextLine.x1).minus(is.x).abs().pow(2).plus(Big(nextLine.y1).minus(is.y).abs().pow(2)).sqrt().toNumber() nextRidges.push({ ridge, size }) } } }) if (nextRidges.length > 0) { nextRidges.sort((a, b) => a.size - b.size) nextRidge = nextRidges[0].ridge } } let currentRidge if (prevRidge) { if (currentVectorX === 0) { const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) if (minY <= prevRidge.y1 && maxY >= prevRidge.y1 && minY <= prevRidge.y2 && maxY >= prevRidge.y2) { currentRidge = prevRidge } } if (currentVectorY === 0) { const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) if (minX <= prevRidge.x1 && maxX >= prevRidge.x1 && minX <= prevRidge.x2 && maxX >= prevRidge.x2) { currentRidge = prevRidge } } } if (nextRidge) { if (currentVectorX === 0) { const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) if (minY <= nextRidge.y1 && maxY >= nextRidge.y1 && minY <= nextRidge.y2 && maxY >= nextRidge.y2) { currentRidge = nextRidge } } if (currentVectorY === 0) { const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) if (minX <= nextRidge.x1 && maxX >= nextRidge.x1 && minX <= nextRidge.x2 && maxX >= nextRidge.x2) { currentRidge = nextRidge } } } if (currentRidge) { polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) /** 기준점이 될 포인트를 찾는다. 기준점 = 지붕선이나 hip 등에 붙지 않은 포인트 */ const checkRidgePoints = [ { x: currentRidge.x1, y: currentRidge.y1, checkPoint: true }, { x: currentRidge.x2, y: currentRidge.y2, checkPoint: true }, ] const ridgeEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x2, y: currentRidge.y2 } } /** 포인트가 지붕선과 붙은 경우 */ roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(ridgeEdge, lineEdge) if (is) { if (Math.abs(is.x - currentRidge.x1) < 1 && Math.abs(is.y - currentRidge.y1) < 1) { checkRidgePoints[0].checkPoint = false } if (Math.abs(is.x - currentRidge.x2) < 1 && Math.abs(is.y - currentRidge.y2) < 1) { checkRidgePoints[1].checkPoint = false } } }) /** 포인트가 hip과 붙은 경우 */ baseHipLines .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) .forEach((line) => { if ( (Math.abs(line.x1 - currentRidge.x1) < 1 && Math.abs(line.y1 - currentRidge.y1) < 1) || (Math.abs(line.x2 - currentRidge.x1) < 1 && Math.abs(line.y2 - currentRidge.y1) < 1) ) { checkRidgePoints[0].checkPoint = false } if ( (Math.abs(line.x1 - currentRidge.x2) < 1 && Math.abs(line.y1 - currentRidge.y2) < 1) || (Math.abs(line.x2 - currentRidge.x2) < 1 && Math.abs(line.y2 - currentRidge.y2) < 1) ) { checkRidgePoints[1].checkPoint = false } }) const checkRidgePoint = checkRidgePoints.find((point) => point.checkPoint) if (!checkRidgePoint) return /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y }, vertex2: { x: currentRoof.x1, y: checkRidgePoint.y }, } } else { checkEdge = { vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y }, vertex2: { x: checkRidgePoint.x, y: currentRoof.y1 }, } } const currentRoofEdge = { vertex1: { x: currentRoof.x1, y: currentRoof.y1 }, vertex2: { x: currentRoof.x2, y: currentRoof.y2 } } const isRoofPoint = edgesIntersection(checkEdge, currentRoofEdge) if (isRoofPoint) { if (currentVectorX === 0) { const minY = Math.min(currentRoof.y1, currentRoof.y2, isRoofPoint.y) const maxY = Math.max(currentRoof.y1, currentRoof.y2, isRoofPoint.y) polygonPoints.push({ x: isRoofPoint.x, y: minY }, { x: isRoofPoint.x, y: maxY }) } else { const minX = Math.min(currentRoof.x1, currentRoof.x2, isRoofPoint.x) const maxX = Math.max(currentRoof.x1, currentRoof.x2, isRoofPoint.x) polygonPoints.push({ x: minX, y: isRoofPoint.y }, { x: maxX, y: isRoofPoint.y }) } } } } const uniquePoints = [] polygonPoints.forEach((point) => { const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y) if (!isAlready) { uniquePoints.push(point) } }) polygonPoints = getSortedPoint(uniquePoints, baseHipLines) polygonPoints.forEach((currentPoint, index) => { const nextPoint = polygonPoints[(index + 1) % polygonPoints.length] const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y] const ridgeLines = baseGableRidgeLines.filter( (line) => (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), ) const hipLines = baseHipLines.filter( (line) => (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), ) /** 이미 존재하는 라인이면 넘긴다.*/ if (ridgeLines.length > 0 || hipLines.length > 0) { return } const hipLine = drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree) if (currentVectorX === 0) { if (Math.sign(currentPoint.x - nextPoint.x) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } else { if (Math.sign(currentPoint.y - nextPoint.y) === 0) { hipLine.attributes.actualSize = hipLine.attributes.planeSize } } baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) }) }) /** 케라바 지붕에 연결된 마루 중 처마라인이 그려지지 않은 경우 확*/ baseGableRidgeLines.forEach((ridge) => { const ridgeVectorX = Math.sign(ridge.x1 - ridge.x2) const ridgeVectorY = Math.sign(ridge.y1 - ridge.y2) const firstGableLines = [] const secondGableLines = [] baseGableLines .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) .filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1)) .forEach((line) => firstGableLines.push(line)) baseGableLines .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) .filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) .forEach((line) => secondGableLines.push(line)) baseHipLines .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) .filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1)) .forEach((line) => firstGableLines.push(line)) baseHipLines .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) .filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) .forEach((line) => secondGableLines.push(line)) let degree1, degree2 if (firstGableLines.length < 2 || secondGableLines.length < 2) { drawBaseLines.forEach((currentBaseLine, index) => { let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] 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) if ( gableType.includes(currentLine.attributes?.type) && eavesType.includes(prevLine.attributes?.type) && eavesType.includes(nextLine.attributes?.type) && Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) ) { if ( ridgeVectorX === 0 && currentLine.x1 !== currentLine.x2 && ((currentLine.x1 <= ridge.x1 && ridge.x1 <= currentLine.x2) || (currentLine.x2 <= ridge.x1 && ridge.x1 <= currentLine.x1)) ) { degree1 = getDegreeByChon(prevLine.attributes.pitch) degree2 = getDegreeByChon(nextLine.attributes.pitch) } if ( ridgeVectorY === 0 && currentLine.y1 !== currentLine.y2 && ((currentLine.y1 <= ridge.y1 && ridge.y1 <= currentLine.y2) || (currentLine.y2 <= ridge.y1 && ridge.y1 <= currentLine.y1)) ) { degree1 = getDegreeByChon(prevLine.attributes.pitch) degree2 = getDegreeByChon(nextLine.attributes.pitch) } } }) } if (firstGableLines.length < 2) { const connectRoof = roof.lines.find( (line) => (line.x1 <= ridge.x1 && line.x2 >= ridge.x1 && line.y1 <= ridge.y1 && line.y2 >= ridge.y1) || (line.x2 <= ridge.x1 && line.x1 >= ridge.x1 && line.y2 <= ridge.y1 && line.y1 >= ridge.y1), ) if (connectRoof) { let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x1, ridge.y1] let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x1, ridge.y1] let intersectPoints1 let intersectPoints2 baseHipLines .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) .forEach((hip) => { const line = hip.line if ( (hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) || (hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1]) ) { intersectPoints1 = { x: line.x1, y: line.y1 } } if ( (hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) || (hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1]) ) { intersectPoints1 = { x: line.x2, y: line.y2 } } if ( (hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) || (hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1]) ) { intersectPoints2 = { x: line.x1, y: line.y1 } } if ( (hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) || (hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1]) ) { intersectPoints2 = { x: line.x2, y: line.y2 } } }) if (intersectPoints1) { hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x1, ridge.y1] } if (intersectPoints2) { hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x1, ridge.y1] } if (hipPoint1) { const hipLines = baseHipLines.filter( (line) => (line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) || (line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]), ) const gableLines = baseGableLines.filter( (line) => (line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) || (line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]), ) if (gableLines.length === 0 && hipLines.length === 0) { const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, null, degree1, degree1) baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 }) } } if (hipPoint2) { const hipLines = baseHipLines.filter( (line) => (line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) || (line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]), ) const gableLines = baseGableLines.filter( (line) => (line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) || (line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]), ) if (hipLines.length === 0 && gableLines.length === 0) { const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, null, degree2, degree2) baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 }) } } } } if (secondGableLines.length < 2) { const connectRoof = roof.lines.find( (line) => (line.x1 <= ridge.x2 && line.x2 >= ridge.x2 && line.y1 <= ridge.y2 && line.y2 >= ridge.y2) || (line.x2 <= ridge.x2 && line.x1 >= ridge.x2 && line.y2 <= ridge.y2 && line.y1 >= ridge.y2), ) if (connectRoof) { let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x2, ridge.y2] let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x2, ridge.y2] let intersectPoints1 let intersectPoints2 baseHipLines .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) .forEach((hip) => { const line = hip.line if ( (hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) || (hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1]) ) { intersectPoints1 = { x: line.x1, y: line.y1 } } if ( (hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) || (hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1]) ) { intersectPoints1 = { x: line.x2, y: line.y2 } } if ( (hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) || (hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1]) ) { intersectPoints2 = { x: line.x1, y: line.y1 } } if ( (hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) || (hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1]) ) { intersectPoints2 = { x: line.x2, y: line.y2 } } }) if (intersectPoints1) { hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x2, ridge.y2] } if (intersectPoints2) { hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x2, ridge.y2] } if (hipPoint1) { const hipLines = baseHipLines.filter( (line) => (line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) || (line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]), ) const gableLines = baseGableLines.filter( (line) => (line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) || (line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]), ) if (hipLines.length === 0 && gableLines.length === 0) { const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, null, degree1, degree1) baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 }) } } if (hipPoint2) { const hipLines = baseHipLines.filter( (line) => (line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) || (line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]), ) const gableLines = baseGableLines.filter( (line) => (line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) || (line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]), ) if (hipLines.length === 0 && gableLines.length === 0) { const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, null, degree2, degree2) baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 }) } } } } }) /** 팔작지붕 */ drawHipAndGableFirst.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 const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) let beforePrevLine, afterNextLine drawBaseLines.forEach((line, index) => { if (line === prevBaseLine) { beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] } if (line === nextBaseLine) { afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] } }) /** 팔작지붕 두께*/ const lineWidth = currentLine.attributes.width < Big(currentLine.attributes.planeSize).div(20).toNumber() ? currentLine.attributes.width : Big(currentLine.attributes.planeSize).div(20).toNumber() const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) const intersectionRoofs = [] if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } let currentRoof, prevRoof, nextRoof if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } roof.lines.forEach((line, index) => { if (line === currentRoof) { prevRoof = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] nextRoof = roof.lines[(index + 1) % roof.lines.length] } }) // /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevRoof, currentRoof) let nextVector = getHalfAngleVector(currentRoof, nextRoof) let prevHipVector = { x: Math.sign(prevVector.x), y: Math.sign(prevVector.y) } let nextHipVector = { x: Math.sign(nextVector.x), y: Math.sign(nextVector.y) } /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ let hipSize = Big(lineWidth).pow(2).plus(Big(lineWidth).pow(2)).sqrt() /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)), y: Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)), } if (!roof.inPolygon(prevCheckPoint)) { prevHipVector = { x: Math.sign(prevHipVector.x * -1), y: Math.sign(prevHipVector.y * -1) } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)), y: Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)), } if (!roof.inPolygon(nextCheckPoint)) { nextHipVector = { x: Math.sign(nextHipVector.x * -1), y: Math.sign(nextHipVector.y * -1) } } /** 처마끝에서 올라오는 라인*/ const prevPoint = [ currentRoof.x1, currentRoof.y1, Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)).toNumber(), Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)).toNumber(), ] const nextPoint = [ currentRoof.x2, currentRoof.y2, Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)).toNumber(), Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)).toNumber(), ] const prevHipLine = drawHipLine(prevPoint, canvas, roof, textMode, null, prevDegree, currentDegree) const nextHipLine = drawHipLine(nextPoint, canvas, roof, textMode, null, currentDegree, nextDegree) baseHipLines.push({ x1, y1, x2: prevHipLine.x1, y2: prevHipLine.y1, line: prevHipLine }) baseHipLines.push({ x1: x2, y1: y2, x2: nextHipLine.x1, y2: nextHipLine.y1, line: nextHipLine }) /** 처마끝에서 올라오는 라인에서 가운데로 모이는 라인*/ let midX, midY if (currentVectorX === 0) { midX = prevHipLine.x2 midY = currentMidY.toNumber() } else { midX = currentMidX.toNumber() midY = prevHipLine.y2 } const prevGablePoint = [prevHipLine.x2, prevHipLine.y2, midX, midY] const nextGablePoint = [nextHipLine.x2, nextHipLine.y2, midX, midY] const prevGableLine = drawHipLine(prevGablePoint, canvas, roof, textMode, null, prevDegree, currentDegree) const nextGableLine = drawHipLine(nextGablePoint, canvas, roof, textMode, null, currentDegree, nextDegree) baseHipLines.push({ x1: prevGableLine.x1, y1: prevGableLine.y1, x2: prevGableLine.x2, y2: prevGableLine.y2, line: prevGableLine }) baseHipLines.push({ x1: nextGableLine.x1, y1: nextGableLine.y1, x2: nextGableLine.x2, y2: nextGableLine.y2, line: nextGableLine }) let oppositeLine baseLines .filter((line) => line !== currentLine) .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) .forEach((oppCurrLine) => { let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: oppCurrLine.x1, y: midY }, } } else { checkEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX, y: oppCurrLine.y1 }, } } const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(oppCurrLine, intersection)) { const size = calcLinePlaneSize({ x1: checkEdge.vertex1.x, y1: checkEdge.vertex1.y, x2: intersection.x, y2: intersection.y, }) / 10 oppositeLine = { intersection, size, oppCurrLine } } }) if (oppositeLine) { const { intersection, size, oppCurrLine } = oppositeLine let oppPrevLine, oppNextLine let oppRoofLine let ridgePoints = [] let ridgeSize const ridgeEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: intersection.x, y: intersection.y }, } if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const beforePrevCheckPoint = { x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)), y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)), } if (!checkWallPolygon.inPolygon(beforePrevCheckPoint)) { beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y } } const checkBeforeEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)).toNumber(), y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)).toNumber(), }, } const isBefore = edgesIntersection(checkBeforeEdge, ridgeEdge) if (isBefore) { const size = Big(isBefore.x).minus(midX).abs().pow(2).plus(Big(isBefore.y).minus(midY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isBefore.x, y: isBefore.y, size }) } } if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let afterNextVector = getHalfAngleVector(nextLine, afterNextLine) /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const afterNextCheckPoint = { x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)), y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)), } if (!checkWallPolygon.inPolygon(afterNextCheckPoint)) { afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y } } const checkAfterEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)).toNumber(), y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)).toNumber(), }, } const isAfter = edgesIntersection(checkAfterEdge, ridgeEdge) if (isAfter) { const size = Big(isAfter.x).minus(midX).abs().pow(2).plus(Big(isAfter.y).minus(midY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isAfter.x, y: isAfter.y, size }) } } baseLines.forEach((line, index) => { if (line === oppCurrLine) { oppPrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] oppNextLine = baseLines[(index + 1) % baseLines.length] } }) const oppositeRoofs = [] const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2) const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2) const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1) const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1) if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) { const checkEdge = { vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { oppositeRoofs.push({ line, intersection, size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { oppositeRoofs.push({ line, intersection, size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), }) } } }) } if (oppositeRoofs.length > 0) { oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line } if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { if (oppRoofLine) { const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20) const ridgeEdge = { vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, vertex2: { x: intersection.x, y: intersection.y }, } if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine) oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y } /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)), y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y } } const oppPrevHipEdge = { vertex1: { x: oppRoofLine.x1, y: oppRoofLine.y1, }, vertex2: { x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(), y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(), }, } const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge) if ( isOppPrev && isPointOnLine( { x1: oppPrevHipEdge.vertex1.x, y1: oppPrevHipEdge.vertex1.y, x2: oppPrevHipEdge.vertex2.x, y2: oppPrevHipEdge.vertex2.y, }, isOppPrev, ) ) { const size = Big(isOppPrev.x).minus(midX).abs().pow(2).plus(Big(isOppPrev.y).minus(midY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size }) } } if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine) oppNextVector = { x: oppNextVector.x, y: oppNextVector.y } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)), y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y } } const oppNextHipEdge = { vertex1: { x: oppRoofLine.x2, y: oppRoofLine.y2, }, vertex2: { x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(), y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(), }, } const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge) if ( isOppNext && isPointOnLine( { x1: oppNextHipEdge.vertex1.x, y1: oppNextHipEdge.vertex1.y, x2: oppNextHipEdge.vertex2.x, y2: oppNextHipEdge.vertex2.y, }, isOppNext, ) ) { const size = Big(isOppNext.x).minus(midX).abs().pow(2).plus(Big(isOppNext.y).minus(midY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size }) } } } } else if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { ridgeSize = Big(size).minus(Big(oppCurrLine.attributes.width)).plus(Big(oppCurrLine.attributes.offset)).toNumber() } if (ridgePoints.length > 0) { ridgeSize = ridgePoints.sort((a, b) => a.size - b.size)[0].size } if (ridgeSize > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { const ridgeVectorX = Math.sign(midX - intersection.x) const ridgeVectorY = Math.sign(midY - intersection.y) const ridgePoint = [ midX, midY, Big(midX).minus(Big(ridgeSize).times(ridgeVectorX)).toNumber(), Big(midY).minus(Big(ridgeSize).times(ridgeVectorY)).toNumber(), ] const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode) baseRidgeLines.push(ridgeLine) baseRidgeCount++ } else { let ridges = [] baseGableRidgeLines .filter( (ridge) => (Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1), ) .forEach((ridge) => ridges.push(ridge)) baseRidgeLines .filter( (ridge) => (Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1), ) .forEach((ridge) => ridges.push(ridge)) if (ridges.length > 0) { return } canvas.remove(prevGableLine) canvas.remove(nextGableLine) baseHipLines = baseHipLines.filter((base) => base.line !== prevGableLine && base.line !== nextGableLine) const points = [prevGableLine.x1, prevGableLine.y1, nextGableLine.x1, nextGableLine.y1] const gableLine = drawRoofLine(points, canvas, roof, textMode) baseHipLines.push({ x1: gableLine.x1, y1: gableLine.y1, x2: gableLine.x2, y2: gableLine.y2, line: gableLine }) } } }) /** 벽과 이어진 마루를 그린다. */ drawWallRidgeLine.forEach((current) => { const { currentBaseLine, prevBaseLine, nextBaseLine } = current const currentLine = currentBaseLine.line const prevLine = prevBaseLine.line const nextLine = nextBaseLine.line let beforePrevLine, afterNextLine const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber() const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber() const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ drawBaseLines.forEach((line, index) => { if (line === prevBaseLine) { beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] } if (line === nextBaseLine) { afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] } }) let oppositeLine baseLines .filter((line) => line !== currentLine) .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) .forEach((oppCurrLine) => { let checkEdge if (currentVectorX === 0) { checkEdge = { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: oppCurrLine.x1, y: currentMidY }, } } else { checkEdge = { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: currentMidX, y: oppCurrLine.y1 }, } } const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(oppCurrLine, intersection)) { const size = calcLinePlaneSize({ x1: checkEdge.vertex1.x, y1: checkEdge.vertex1.y, x2: intersection.x, y2: intersection.y, }) / 10 oppositeLine = { intersection, size, oppCurrLine } } }) const ridgePoints = [] if (oppositeLine) { const { intersection, size, oppCurrLine } = oppositeLine ridgePoints.push({ x: intersection.x, y: intersection.y, size }) const oppPrevLine = baseLines.find((line) => line.x2 === oppCurrLine.x1 && line.y2 === oppCurrLine.y1) const oppNextLine = baseLines.find((line) => line.x1 === oppCurrLine.x2 && line.y1 === oppCurrLine.y2) const oppositeRoofs = [] const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2) const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2) const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1) const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1) let oppRoofLine if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) { const checkEdge = { vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { oppositeRoofs.push({ line, intersection, size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { oppositeRoofs.push({ line, intersection, size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), }) } } }) } if (oppositeRoofs.length > 0) { oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line } if (oppRoofLine) { const ridgeEdge = { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: intersection.x, y: intersection.y } } if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20) if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine) oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y } /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)), y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)), } if (!checkWallPolygon.inPolygon(prevCheckPoint)) { oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y } } const oppPrevHipEdge = { vertex1: { x: oppRoofLine.x1, y: oppRoofLine.y1, }, vertex2: { x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(), y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(), }, } const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge) if ( isOppPrev && isPointOnLine( { x1: oppPrevHipEdge.vertex1.x, y1: oppPrevHipEdge.vertex1.y, x2: oppPrevHipEdge.vertex2.x, y2: oppPrevHipEdge.vertex2.y, }, isOppPrev, ) ) { const size = Big(isOppPrev.x).minus(currentMidX).abs().pow(2).plus(Big(isOppPrev.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size }) } } if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine) oppNextVector = { x: oppNextVector.x, y: oppNextVector.y } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)), y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)), } if (!checkWallPolygon.inPolygon(nextCheckPoint)) { oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y } } const oppNextHipEdge = { vertex1: { x: oppRoofLine.x2, y: oppRoofLine.y2, }, vertex2: { x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(), y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(), }, } const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge) if ( isOppNext && isPointOnLine( { x1: oppNextHipEdge.vertex1.x, y1: oppNextHipEdge.vertex1.y, x2: oppNextHipEdge.vertex2.x, y2: oppNextHipEdge.vertex2.y, }, isOppNext, ) ) { const size = Big(isOppNext.x).minus(currentMidX).abs().pow(2).plus(Big(isOppNext.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber() ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size }) } } } else { baseHipLines.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) { if (isPointOnLine(line, intersection)) { const size = Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt() ridgePoints.push({ x: intersection.x, y: intersection.y, size }) } } }) } if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine) const checkPoint = { x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)), y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)), } if (!checkWallPolygon.inPolygon(checkPoint)) { beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y } } const hipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(prevLine.attributes.planeSize)).toNumber(), y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(prevLine.attributes.planeSize)).toNumber(), }, } const isLines = [] drawBaseLines .filter((base) => base !== currentBaseLine && base !== prevBaseLine && base !== beforePrevLine) .forEach((base) => { const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if (intersection && isPointOnLine(base.line, intersection)) { const size = Big(intersection.x) .minus(prevLine.x1) .abs() .pow(2) .plus(Big(intersection.y).minus(prevLine.y1).abs().pow(2)) .sqrt() .toNumber() isLines.push({ intersection, size }) } }) if (isLines.length > 0) { const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2) const hipPoint = [ prevLine.x1, prevLine.y1, Big(prevLine.x1).plus(Big(beforePrevVector.x).times(hipSize)).toNumber(), Big(prevLine.y1).plus(Big(beforePrevVector.y).times(hipSize)).toNumber(), ] const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } const intersection = edgesIntersection(ridgeEdge, hipEdge) if ( intersection && (isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) || (Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) || (Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1)) ) { const size = Big(intersection.x) .minus(currentMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) .sqrt() .toNumber() ridgePoints.push({ x: intersection.x, y: intersection.y, size }) } } } if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let afterNextVector = getHalfAngleVector(nextLine, afterNextLine) const checkPoint = { x: Big(nextLine.x1).plus(Big(afterNextVector.x).times(10)), y: Big(nextLine.y1).plus(Big(afterNextVector.y).times(10)), } if (!checkWallPolygon.inPolygon(checkPoint)) { afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y } } const hipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(nextLine.attributes.planeSize)).toNumber(), y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(nextLine.attributes.planeSize)).toNumber(), }, } const isLines = [] drawBaseLines .filter((base) => base !== currentBaseLine && base !== nextBaseLine && base !== afterNextLine) .forEach((base) => { const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } } const intersection = edgesIntersection(hipEdge, lineEdge) if (intersection && isPointOnLine(base.line, intersection)) { const size = Big(intersection.x) .minus(nextLine.x2) .abs() .pow(2) .plus(Big(intersection.y).minus(nextLine.y2).abs().pow(2)) .sqrt() .toNumber() isLines.push({ intersection, size }) } }) if (isLines.length > 0) { const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2) const hipPoint = [ nextLine.x2, nextLine.y2, Big(nextLine.x2).plus(Big(afterNextVector.x).times(hipSize)).toNumber(), Big(nextLine.y2).plus(Big(afterNextVector.y).times(hipSize)).toNumber(), ] const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } const intersection = edgesIntersection(ridgeEdge, hipEdge) if ( intersection && (isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) || (Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) || (Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1)) ) { const size = Big(intersection.x) .minus(currentMidX) .abs() .pow(2) .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) .sqrt() .toNumber() ridgePoints.push({ x: intersection.x, y: intersection.y, size }) } } } } } if (ridgePoints.length === 0 || baseRidgeCount >= getMaxRidge(baseLines.length)) return const ridgeEndPoint = ridgePoints.sort((a, b) => a.size - b.size)[0] const ridgePoint = { x1: currentMidX, y1: currentMidY, x2: ridgeEndPoint.x, y2: ridgeEndPoint.y } /** 포인트가 지붕밖에 있는 경우 조정*/ if (!roof.inPolygon({ x: ridgePoint.x1, y: ridgePoint.y1 })) { const checkEdge = { vertex1: { x: ridgePoint.x2, y: ridgePoint.y2 }, vertex2: { x: ridgePoint.x1, y: ridgePoint.y1 } } const isPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(line, intersection)) { if ( Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) && Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y) ) { const size = Big(intersection.x) .minus(ridgePoint.x2) .abs() .pow(2) .plus(Big(intersection.y).minus(ridgePoint.y2).abs().pow(2)) .sqrt() .toNumber() isPoints.push({ x: intersection.x, y: intersection.y, size }) } } }) if (isPoints.length > 0) { const newPoint = isPoints.sort((a, b) => a.size - b.size)[0] ridgePoint.x1 = newPoint.x ridgePoint.y1 = newPoint.y } } if (!roof.inPolygon({ x: ridgePoint.x2, y: ridgePoint.y2 })) { const checkEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } } const isPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && isPointOnLine(line, intersection)) { if ( Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) && Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y) ) { const size = Big(intersection.x) .minus(ridgePoint.x1) .abs() .pow(2) .plus(Big(intersection.y).minus(ridgePoint.y1).abs().pow(2)) .sqrt() .toNumber() isPoints.push({ x: intersection.x, y: intersection.y, size }) } } }) if (isPoints.length > 0) { const newPoint = isPoints.sort((a, b) => a.size - b.size)[0] ridgePoint.x2 = newPoint.x ridgePoint.y2 = newPoint.y } } const ridge = drawRidgeLine([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], canvas, roof, textMode) baseRidgeLines.push(ridge) baseRidgeCount++ /** 현재 라인의 지붕 라인을 찾는다. */ const intersectionRoofs = [] let currentRoof if (currentVectorX === 0) { const checkEdge = { vertex1: { x: prevLine.x1, y: currentMidY }, vertex2: { x: currentMidX, y: currentMidY }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } else { const checkEdge = { vertex1: { x: currentMidX, y: prevLine.y1 }, vertex2: { x: currentMidX, y: currentMidY }, } roof.lines .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection) { if (isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), }) } } }) } if (intersectionRoofs.length > 0) { currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line } if (currentRoof) { const prevHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX, currentMidY] const nextHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX, currentMidY] const prevHipLine = drawHipLine(prevHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree) const nextHipLine = drawHipLine(nextHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree) baseHipLines.push({ x1: prevHipLine.x1, y1: prevHipLine.y1, x2: prevHipLine.x2, y2: prevHipLine.y2, line: prevHipLine }) baseHipLines.push({ x1: nextHipLine.x1, y1: nextHipLine.y1, x2: nextHipLine.x2, y2: nextHipLine.y2, line: nextHipLine }) } }) /** ⨆ 모양 처마에 추녀마루를 그린다. */ 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 = getDegreeByChon(prevLine.attributes.pitch) /** 다음 라인의 경사 */ const currentDegree = getDegreeByChon(currentLine.attributes.pitch) /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ 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: prevVector.x, y: prevVector.y } let nextHipVector = { x: nextVector.x, y: 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: -prevHipVector.x, y: -prevHipVector.y } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ 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: -nextHipVector.x, y: -nextHipVector.y } } /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() /** * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. */ if (beforePrevLine === afterNextLine || (currentAngle === beforePrevAngle && currentAngle === afterNextAngle)) { console.log('4각 ::::::::: ') 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))) .abs() 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) { const beforePrevX1 = beforePrevLine.x1, beforePrevY1 = beforePrevLine.y1 const 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각 */ 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 !== currentLine && 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).toNumber(), Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2).toNumber(), ] } 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).toNumber(), Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2).toNumber(), ] } 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() .toNumber(), }) } }) 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() .toNumber(), }) } }) if (intersectPoints.length > 0) { const intersection = intersectPoints.sort((a, b) => a.distance - b.distance)[0] hipLength = getAdjacent(intersection.distance) / 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), } if (!roof.inPolygon({ x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() })) { const checkEdge = { vertex1: { x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() }, vertex2: { x: x1, y: y1 } } const intersectionPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && !intersection.isIntersectionOutside) { const size = Big(intersection.x) .minus(Big(prevEndPoint.x)) .pow(2) .plus(Big(intersection.y).minus(Big(prevEndPoint.y)).pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection prevEndPoint.x = Big(intersection.x) prevEndPoint.y = Big(intersection.y) } } const overlapLine = baseHipLines.find( (line) => isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) || isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }), ) if (overlapLine) { let size1, size2 if ( isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 }) ) { size1 = Math.sqrt(Math.pow(x1 - overlapLine.x1, 2) + Math.pow(y1 - overlapLine.y1, 2)) } if ( isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 }) ) { size2 = Math.sqrt(Math.pow(x1 - overlapLine.x2, 2) + Math.pow(y1 - overlapLine.y2, 2)) } if (size1 && size2) { if (size1 < size2) { prevEndPoint.x = Big(overlapLine.x1) prevEndPoint.y = Big(overlapLine.y1) } else { prevEndPoint.x = Big(overlapLine.x2) prevEndPoint.y = Big(overlapLine.y2) } } else { if (size1) { prevEndPoint.x = Big(overlapLine.x1) prevEndPoint.y = Big(overlapLine.y1) } if (size2) { prevEndPoint.x = Big(overlapLine.x2) prevEndPoint.y = Big(overlapLine.y2) } } } 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 && isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) && 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]) /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ 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 }) } } 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), } if (!roof.inPolygon({ x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() })) { const checkEdge = { vertex1: { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() }, vertex2: { x: x2, y: y2 } } const intersectionPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && !intersection.isIntersectionOutside) { const size = Big(intersection.x) .minus(Big(nextEndPoint.x)) .pow(2) .plus(Big(intersection.y).minus(Big(nextEndPoint.y)).pow(2)) .sqrt() .toNumber() intersectionPoints.push({ intersection, size }) } }) if (intersectionPoints.length > 0) { const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection nextEndPoint.x = Big(intersection.x) nextEndPoint.y = Big(intersection.y) } } // const overlapLine = baseHipLines.find((line) => isPointOnLine(line, { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() })) const overlapLine = baseHipLines.find( (line) => isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) || isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }), ) if (overlapLine) { let size1, size2 if ( isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 }) ) { size1 = Math.sqrt(Math.pow(x2 - overlapLine.x1, 2) + Math.pow(y2 - overlapLine.y1, 2)) } if ( isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 }) ) { size2 = Math.sqrt(Math.pow(x2 - overlapLine.x2, 2) + Math.pow(y2 - overlapLine.y2, 2)) } if (size1 && size2) { if (size1 < size2) { nextEndPoint.x = Big(overlapLine.x1) nextEndPoint.y = Big(overlapLine.y1) } else { nextEndPoint.x = Big(overlapLine.x2) nextEndPoint.y = Big(overlapLine.y2) } } else { if (size1) { nextEndPoint.x = Big(overlapLine.x1) nextEndPoint.y = Big(overlapLine.y1) } if (size2) { nextEndPoint.x = Big(overlapLine.x2) nextEndPoint.y = Big(overlapLine.y2) } } } 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 && isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) && 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, }) } } /** 두 선이 교차하면 해당 포인트까지로 선 조정*/ 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) if (intersection) { /** 포인트 조정*/ 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) if (beforePrevLine === afterNextLine) { console.log('4각 :::::::: ') const oppositeMidX = Big(beforePrevLine.x2).plus(Big(beforePrevLine.x1)).div(2) const oppositeMidY = Big(beforePrevLine.y2).plus(Big(beforePrevLine.y1)).div(2) if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { ridgeSize = oppositeMidX .minus(Big(startPoint.x)) .abs() .pow(2) .plus(oppositeMidY.minus(Big(startPoint.y)).abs().pow(2)) .sqrt() .minus(Big(beforePrevLine.line.attributes.planeSize).div(20)) } else { let width = 0 if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { width = beforePrevLine.line.attributes.width / 2 } else if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { width = beforePrevLine.line.attributes.width } const checkEdge = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() } } const vectorX = Math.sign(startPoint.x - oppositeMidX.toNumber()) const vectorY = Math.sign(startPoint.y - oppositeMidY.toNumber()) const oppositeRoofPoints = [] roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) if (intersection && Math.sign(startPoint.x - intersection.x) === vectorX && Math.sign(startPoint.y - intersection.y) === vectorY) { const size = Big(intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() .toNumber() oppositeRoofPoints.push({ intersection, size }) } }) if (oppositeRoofPoints.length > 0) { const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection ridgeSize = Big(oppositeRoofPoint.x) .minus(Big(startPoint.x)) .abs() .pow(2) .plus(Big(oppositeRoofPoint.y).minus(Big(startPoint.y)).abs().pow(2)) .minus(width) .sqrt() } } } else { 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) 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) if (intersection) { ridgeSize = Big(intersection.x) .minus(Big(startPoint.x)) .pow(2) .plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2)) .sqrt() } } 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) if (intersection) { 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) if (intersectPoints.length > 0) { ridgeSize = Big(intersectPoints[0].size) } } } else { /** 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) => { 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) .toNumber() : Infinity /** 이전, 다음 라인중 길이가 짧은 길이*/ const lineMinSize = prevBaseLine.size < nextBaseLine.size ? Big(prevBaseLine.size).div(10).toNumber() : Big(nextBaseLine.size).div(10).toNumber() /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) } } if (ridgeSize.gt(0) && baseRidgeCount < getMaxRidge(baseLines.length)) { const points = [ startPoint.x, startPoint.y, Big(startPoint.x) .minus(ridgeSize.times(Math.sign(xVector))) .toNumber(), Big(startPoint.y) .minus(ridgeSize.times(Math.sign(yVector))) .toNumber(), ] const oppositeHipPoints = [] const ridgeEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let hipVector = getHalfAngleVector(prevLine, beforePrevLine.line) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const checkPoint = { x: Big(prevLine.x1).plus(Big(hipVector.x).times(10)), y: Big(prevLine.y1).plus(Big(hipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(checkPoint)) { hipVector = { x: -hipVector.x, y: -hipVector.y } } const hipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: Big(prevLine.x1).plus(Big(hipVector.x).times(prevLine.attributes.planeSize)).toNumber(), y: Big(prevLine.y1).plus(Big(hipVector.y).times(prevLine.attributes.planeSize)).toNumber(), }, } const intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { const size = Big(points[0] - intersection.x) .abs() .pow(2) .plus( Big(points[1] - intersection.y) .abs() .pow(2), ) .sqrt() .toNumber() oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size }) } } if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let hipVector = getHalfAngleVector(nextLine, afterNextLine.line) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const checkPoint = { x: Big(nextLine.x1).plus(Big(hipVector.x).times(10)), y: Big(nextLine.y1).plus(Big(hipVector.y).times(10)), } if (!checkWallPolygon.inPolygon(checkPoint)) { hipVector = { x: -hipVector.x, y: -hipVector.y } } const hipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: Big(nextLine.x2).plus(Big(hipVector.x).times(nextLine.attributes.planeSize)).toNumber(), y: Big(nextLine.y2).plus(Big(hipVector.y).times(nextLine.attributes.planeSize)).toNumber(), }, } const intersection = edgesIntersection(ridgeEdge, hipEdge) if (intersection) { const size = Big(points[0] - intersection.x) .abs() .pow(2) .plus( Big(points[1] - intersection.y) .abs() .pow(2), ) .sqrt() .toNumber() oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size }) } } if (oppositeHipPoints.length > 0) { const oppositeHipPoint = oppositeHipPoints.sort((a, b) => a.size - b.size)[0] points[2] = oppositeHipPoint.x points[3] = oppositeHipPoint.y } /** 동일 라인이 있는지 확인. */ 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 ( oppositeHipPoints.length === 0 && (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 ( oppositeHipPoints.length === 0 && (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) ) { const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line 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 firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine const firstDegree = gableVector === Math.sign(ridgeLine.x1 - prevLine.x1) ? getDegreeByChon(prevLine.attributes.pitch) : getDegreeByChon(nextLine.attributes.pitch) 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) ? getDegreeByChon(nextLine.attributes.pitch) : getDegreeByChon(prevLine.attributes.pitch) 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 firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine const firstDegree = gableVector === Math.sign(ridgeLine.y1 - prevLine.y1) ? getDegreeByChon(prevLine.attributes.pitch) : getDegreeByChon(nextLine.attributes.pitch) 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) ? getDegreeByChon(nextLine.attributes.pitch) : getDegreeByChon(prevLine.attributes.pitch) 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, }) } } } } } }) /** 중복제거 */ 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 } }) }) /** ㄴ 모양 처마에 추녀마루를 그린다. */ 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 } = currentBaseLine /** 이전 라인의 경사 */ const prevDegree = getDegreeByChon(prevLine.attributes.pitch) /** 다음 라인의 경사 */ const currentDegree = getDegreeByChon(currentLine.attributes.pitch) /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevLine, currentLine) let nextVector = getHalfAngleVector(currentLine, nextLine) let prevHipVector = { x: prevVector.x, y: prevVector.y } let nextHipVector = { x: nextVector.x, y: nextVector.y } /** 각 라인의 흐름 방향을 확인한다. */ const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ let hipLength = Big(x2) .minus(Big(x1)) .plus(Big(y2).minus(Big(y1))) .abs() /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ 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: -prevHipVector.x, y: -prevHipVector.y } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ 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: -nextHipVector.x, y: -nextHipVector.y } } let prevHipLine, nextHipLine /** 이전라인과의 연결지점에 추녀마루를 그린다. */ if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0 && prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { 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 } const intersectBaseLine = [] baseLines .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) .filter((line) => { if (currentAngle === 0 || currentAngle === 180) { return Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y } else { return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x } }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(prevEndEdge, lineEdge) if (intersection && Math.sign(intersection.x - x1) === nextHipVector.x && Math.sign(intersection.y - y1) === nextHipVector.y) { 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 } }, ) 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, } 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)) .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]) /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ 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 }) } } /** 다음라인과의 연결지점에 추녀마루를 그린다. */ if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0 && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { 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 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 Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y } else { return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x } }) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(nextEndEdge, lineEdge) if (intersection && Math.sign(intersection.x - x2) === nextHipVector.x && Math.sign(intersection.y - y2) === nextHipVector.y) { 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) { const size = Big(getAdjacent(intersectBase.size)) nextEndPoint = { x: Big(x2) .plus(Big(nextHipVector.x).times(size.div(2))) .round(2), y: Big(y2) .plus(Big(nextHipVector.y).times(size.div(2))) .round(2), } } 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 && ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) ) { 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, }) } } }) /** 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) { hipLine.x2 = intersectRidgePoints[0].x hipLine.y2 = intersectRidgePoints[0].y hipLine.line.set({ x2: intersectRidgePoints[0].x, y2: intersectRidgePoints[0].y }) const hipSize = reCalculateSize(hipLine.line) hipLine.line.attributes.planeSize = hipSize.planeSize hipLine.line.attributes.actualSize = hipSize.actualSize hipLine.line.fire('modified') } } }) /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ /** 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 } }) 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 degreeAllLine.push(getDegreeByChon(pitch)) }) let currentDegree, prevDegree degreeAllLine = [...new Set(degreeAllLine)] currentDegree = degreeAllLine[0] if (degreeAllLine.length === 1) { prevDegree = currentDegree } else { prevDegree = degreeAllLine[1] } /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ 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 checkPointCnt = checkPoints.filter((point) => point.point.x === checkPoint.point.x && point.point.y === checkPoint.point.y).length 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 (checkPointCnt === 1 && (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) 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() /** 현재 라인을 기준으로 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 } }) /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ 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 .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 }) } }) /** 마루를 그릴수 있는지 찾는다. */ noRidgeHipPoints.forEach((hipPoint) => { 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 }) } }) 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] /** 직선인 경우 마루를 그린다.*/ if ( ((hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3])) && baseRidgeCount < getMaxRidge(baseLines.length) ) { const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) baseRidgeCount = baseRidgeCount + 1 baseRidgeLines.push(ridgeLine) noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) } /** 대각선인경우 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) } 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 })) baseGableRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) /** 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) => { 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)) 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) 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], ) 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 }) } } }) }) const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseGableLines, ...baseHipLines.map((line) => line.line)] const uniqueInnerLines = [] innerLines.forEach((currentLine) => { if (currentLine.length === 0) { canvas.remove(currentLine) } else { 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 { canvas.remove(currentLine) } } }) canvas.renderAll() roof.innerLines = uniqueInnerLines roof.innerLines.forEach((line) => { line.bringToFront() }) /** 확인용 라인 제거 */ canvas .getObjects() .filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() /* 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 baseX = Big(points[0]).minus(Big(points[2])).abs() const baseY = Big(points[1]).minus(Big(points[3])).abs() if (baseX.gt(1) && baseY.gt(1)) { const hypotenuse = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }) const base = getAdjacent(hypotenuse) const heightX = base * Math.tan((currentDegree * Math.PI) / 180) const heightY = base * Math.tan((prevDegree * Math.PI) / 180) const degreeX = Math.atan2(heightX, hypotenuse) * (180 / Math.PI) const degreeY = Math.atan2(heightY, hypotenuse) * (180 / Math.PI) if (Math.abs(degreeX - degreeY) < 1) { currentDegree = degreeX prevDegree = degreeY } } 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: Math.sign(Big(v.x).div(magnitude).toNumber()), y: Math.sign(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 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 length * @returns {number} */ const getMaxRidge = (length) => { return (length - 4) / 2 + 1 } /** * 지붕 모양 을 변경한다. * @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, originX: polygon.originX, originY: polygon.originY, }) 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 = 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 = getDegreeByChon(currentRoof.attributes.pitch) 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 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 = 0) => { 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, lines) => { 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 let prevDirection for (let i = 0; i < points.length - 1; i++) { const samePoint = [] if (i === 0) { points.forEach((point) => { if (point.x === prevPoint.x && point.y > prevPoint.y) { samePoint.push({ point, direction: 'bottom', size: Math.abs(prevPoint.y - point.y) }) } }) if (samePoint.length > 0) { samePoint.sort((a, b) => a.size - b.size) sortedPoints.push(samePoint[0].point) prevDirection = samePoint[0].direction prevPoint = samePoint[0].point } else { points.forEach((point) => { if (point.y === prevPoint.y && point.x > prevPoint.x) { samePoint.push({ point, direction: 'right', size: Math.abs(prevPoint.x - point.x) }) } }) if (samePoint.length > 0) { samePoint.sort((a, b) => a.size - b.size) sortedPoints.push(samePoint[0].point) prevDirection = samePoint[0].direction prevPoint = samePoint[0].point } } } else { points .filter((point) => !sortedPoints.includes(point)) .forEach((point) => { if ((prevDirection === 'top' || prevDirection === 'bottom') && point.y === prevPoint.y) { const direction = point.x > prevPoint.x ? 'right' : 'left' const size = Math.abs(point.x - prevPoint.x) samePoint.push({ point, direction, size }) } if ((prevDirection === 'left' || prevDirection === 'right') && point.x === prevPoint.x) { const direction = point.y > prevPoint.y ? 'bottom' : 'top' const size = Math.abs(point.y - prevPoint.y) samePoint.push({ point, direction, size }) } if (Math.round(Math.abs(point.x - prevPoint.x)) === Math.round(Math.abs(point.y - prevPoint.y))) { const isLinePoint = lines.filter( (line) => (line.x1 === prevPoint.x && line.y1 === prevPoint.y && line.x2 === point.x && line.y2 === point.y) || (line.x2 === prevPoint.x && line.y2 === prevPoint.y && line.x1 === point.x && line.y1 === point.y), ).length > 0 if (isLinePoint) { const direction = prevDirection const size = Big(point.x).minus(prevPoint.x).abs().pow(2).plus(Big(point.y).minus(prevPoint.y).abs().pow(2)).sqrt().round().toNumber() samePoint.push({ point, direction, size }) } } }) if (samePoint.length > 0) { samePoint.sort((a, b) => a.size - b.size) sortedPoints.push(samePoint[0].point) prevDirection = samePoint[0].direction prevPoint = samePoint[0].point } } } 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) 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 } }