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 const EPSILON = 1e-10 //좌표계산 시 최소 차이값 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) // }) 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개의 점이 필요합니다.', } } // 모든 점이 같은 직선상에 있는지 검사 (degenerate polygon) const allSameX = coordinates.every((point) => point.x === coordinates[0].x) const allSameY = coordinates.every((point) => point.y === coordinates[0].y) if (allSameX || allSameY) { return true // 직선상의 점들은 유효하지 않은 다각형이므로 true 반환 } 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 drawEavesRoof = (roofId, canvas, textMode) => {} /** * 박공지붕(A,B 패턴) * @param roofId * @param canvas * @param textMode */ export const drawGableRoof = (roofId, canvas, textMode) => { const roof = canvas?.getObjects().find((object) => object.id === roofId) const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) const eavesLines = baseLines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) const ridgeLines = [] const innerLines = [] /** * 처마 라인 속성 판단 * @param line * @returns {{startPoint: {x, y}, endPoint: {x, y}, length: number, angleDegree: number, normalizedAngle: number, isHorizontal: boolean, isVertical: boolean, isDiagonal: boolean, directionVector: {x: number, y: number}, roofLine}} */ const analyzeEavesLine = (line) => { const tolerance = 1 const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const length = Math.sqrt(dx * dx + dy * dy) const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 const directionVector = { x: dx / length, y: dy / length } let isHorizontal = false, isVertical = false, isDiagonal = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } else { isDiagonal = true } const originPoint = line.attributes.originPoint const midX = (originPoint.x1 + originPoint.x2) / 2 const midY = (originPoint.y1 + originPoint.y2) / 2 const offset = line.attributes.offset const checkRoofLines = roof.lines.filter((roof) => { const roofDx = Big(roof.x2).minus(Big(roof.x1)).toNumber() const roofDy = Big(roof.y2).minus(Big(roof.y1)).toNumber() const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) const roofVector = { x: roofDx / roofLength, y: roofDy / roofLength } return directionVector.x === roofVector.x && directionVector.y === roofVector.y }) let roofVector = { x: 0, y: 0 } if (isHorizontal) { const checkPoint = { x: midX, y: midY + offset } if (wall.inPolygon(checkPoint)) { roofVector = { x: 0, y: -1 } } else { roofVector = { x: 0, y: 1 } } } if (isVertical) { const checkPoint = { x: midX + offset, y: midY } if (wall.inPolygon(checkPoint)) { roofVector = { x: -1, y: 0 } } else { roofVector = { x: 1, y: 0 } } } const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofVector.x * offset, y: midY + roofVector.y * offset } } const edgeDx = Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber() const edgeDy = Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).toNumber() const edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy) const edgeVector = { x: edgeDx / edgeLength, y: edgeDy / edgeLength } const intersectRoofLines = [] checkRoofLines.forEach((roofLine) => { const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } const intersect = edgesIntersection(lineEdge, findEdge) if (intersect) { const intersectDx = Big(intersect.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(intersect.x).minus(Big(findEdge.vertex1.x)).toNumber() const intersectDy = Big(intersect.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(intersect.y).minus(Big(findEdge.vertex1.y)).toNumber() const intersectLength = Math.sqrt(intersectDx * intersectDx + intersectDy * intersectDy) const intersectVector = { x: intersectDx / intersectLength, y: intersectDy / intersectLength } if (edgeVector.x === intersectVector.x && edgeVector.y === intersectVector.y) { intersectRoofLines.push({ roofLine, intersect, length: intersectLength }) } } }) intersectRoofLines.sort((a, b) => a.length - b.length) let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect) && roof.length - offset < 0.1) if (!currentRoof) { currentRoof = intersectRoofLines[0] } let startPoint, endPoint if (isHorizontal) { startPoint = { x: Math.min(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y1 } endPoint = { x: Math.max(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y2 } } if (isVertical) { startPoint = { x: line.x1, y: Math.min(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) } endPoint = { x: line.x2, y: Math.max(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) } } if (isDiagonal) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } return { startPoint, endPoint, length, angleDegree, normalizedAngle, isHorizontal, isVertical, isDiagonal, directionVector: { x: dx / length, y: dy / length }, roofLine: currentRoof.roofLine, roofVector, } } const isOverlapLine = (currAnalyze, checkAnalyze) => { // 허용 오차 const tolerance = 1 // 같은 방향인지 확인 if ( currAnalyze.isHorizontal !== checkAnalyze.isHorizontal || currAnalyze.isVertical !== checkAnalyze.isVertical || currAnalyze.isDiagonal !== checkAnalyze.isDiagonal ) { return false } if (currAnalyze.isHorizontal && !(Math.abs(currAnalyze.startPoint.y - checkAnalyze.startPoint.y) < tolerance)) { // 수평선: y좌표가 다른경우 false return false } else if (currAnalyze.isVertical && !(Math.abs(currAnalyze.startPoint.x - checkAnalyze.startPoint.x) < tolerance)) { // 수직선: x좌표가 다른경우 false return false } // 3. 선분 구간 겹침 확인 let range1, range2 if (currAnalyze.isHorizontal) { // 수평선: x 범위로 비교 range1 = { min: Math.min(currAnalyze.startPoint.x, currAnalyze.endPoint.x), max: Math.max(currAnalyze.startPoint.x, currAnalyze.endPoint.x), } range2 = { min: Math.min(checkAnalyze.startPoint.x, checkAnalyze.endPoint.x), max: Math.max(checkAnalyze.startPoint.x, checkAnalyze.endPoint.x), } } else { // 수직선: y 범위로 비교 range1 = { min: Math.min(currAnalyze.startPoint.y, currAnalyze.endPoint.y), max: Math.max(currAnalyze.startPoint.y, currAnalyze.endPoint.y), } range2 = { min: Math.min(checkAnalyze.startPoint.y, checkAnalyze.endPoint.y), max: Math.max(checkAnalyze.startPoint.y, checkAnalyze.endPoint.y), } } // 구간 겹침 확인 const overlapStart = Math.max(range1.min, range2.min) const overlapEnd = Math.min(range1.max, range2.max) return Math.max(0, overlapEnd - overlapStart) > 0 } /** * 전체 처마 라인의 속성 확인 및 정리 * @param lines * @returns {{forwardLines: Array, backwardLines: Array}} */ const analyzeAllEavesLines = (lines) => { let forwardLines = [] let backwardLines = [] lines.forEach((line) => { const analyze = analyzeEavesLine(line) if (analyze.isHorizontal) { if (analyze.directionVector.x > 0) { const overlapLines = forwardLines.filter((forwardLine) => forwardLine !== line && isOverlapLine(analyze, forwardLine.analyze)) if (overlapLines.length > 0) { overlapLines.forEach((overlap) => { const overlapAnalyze = overlap.analyze const startPoint = { x: Math.min(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x), y: analyze.startPoint.y, } const endPoint = { x: Math.max(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x), y: analyze.endPoint.y, } analyze.startPoint = startPoint analyze.endPoint = endPoint forwardLines = forwardLines.filter((forwardLine) => forwardLine !== overlap) }) } forwardLines.push({ eaves: line, analyze }) } if (analyze.directionVector.x < 0) { const overlapLines = backwardLines.filter((backwardLine) => backwardLine !== line && isOverlapLine(analyze, backwardLine.analyze)) if (overlapLines.length > 0) { overlapLines.forEach((overlap) => { const overlapAnalyze = overlap.analyze const startPoint = { x: Math.min(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x), y: analyze.startPoint.y, } const endPoint = { x: Math.max(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x), y: analyze.endPoint.y, } analyze.startPoint = startPoint analyze.endPoint = endPoint backwardLines = backwardLines.filter((backwardLine) => backwardLine !== overlap) }) } backwardLines.push({ eaves: line, analyze }) } } if (analyze.isVertical) { if (analyze.directionVector.y > 0) { const overlapLines = forwardLines.filter((forwardLine) => forwardLine !== line && isOverlapLine(analyze, forwardLine.analyze)) if (overlapLines.length > 0) { overlapLines.forEach((overlap) => { const overlapAnalyze = overlap.analyze const startPoint = { x: analyze.startPoint.x, y: Math.min(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y), } const endPoint = { x: analyze.endPoint.x, y: Math.max(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y), } analyze.startPoint = startPoint analyze.endPoint = endPoint forwardLines = forwardLines.filter((forwardLine) => forwardLine !== overlap) }) } forwardLines.push({ eaves: line, analyze }) } if (analyze.directionVector.y < 0) { const overlapLines = backwardLines.filter((backwardLine) => backwardLine !== line && isOverlapLine(analyze, backwardLine.analyze)) if (overlapLines.length > 0) { overlapLines.forEach((overlap) => { const overlapAnalyze = overlap.analyze const startPoint = { x: analyze.startPoint.x, y: Math.min(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y), } const endPoint = { x: analyze.endPoint.x, y: Math.max(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y), } analyze.startPoint = startPoint analyze.endPoint = endPoint backwardLines = backwardLines.filter((backwardLine) => backwardLine !== overlap) }) } backwardLines.push({ eaves: line, analyze }) } } }) forwardLines.sort((a, b) => { if (a.analyze.isHorizontal && b.analyze.isHorizontal) { return a.analyze.startPoint.x - b.analyze.startPoint.x } else if (a.analyze.isVertical && b.analyze.isVertical) { return a.analyze.startPoint.y - b.analyze.startPoint.y } }) backwardLines.sort((a, b) => { if (a.analyze.isHorizontal && b.analyze.isHorizontal) { return a.analyze.startPoint.x - b.analyze.startPoint.x } else if (a.analyze.isVertical && b.analyze.isVertical) { return a.analyze.startPoint.y - b.analyze.startPoint.y } }) return { forwardLines, backwardLines } } /** * 지점 사이의 길이와 지점별 각도에 따라서 교점까지의 길이를 구한다. * @param distance * @param angleA * @param angleB * @returns {{a: number, b: number}} */ const calculateIntersection = (distance, angleA, angleB) => { // A에서 B방향으로의 각도 (0도가 B방향) const tanA = Math.tan((angleA * Math.PI) / 180) // B에서 A방향으로의 각도 (180도가 A방향, 실제 계산에서는 180-angleB) const tanB = Math.tan(((180 - angleB) * Math.PI) / 180) const a = Math.round(((distance * tanB) / (tanB - tanA)) * 10) / 10 const b = Math.round(Math.abs(distance - a) * 10) / 10 return { a, b } } /** * polygon에 line이 겹쳐지는 구간을 확인. * @param polygon * @param linePoints * @returns */ const findPloygonLineOverlap = (polygon, linePoints) => { const polygonPoints = polygon.points const checkLine = { x1: linePoints[0], y1: linePoints[1], x2: linePoints[2], y2: linePoints[3], } let startPoint = { x: checkLine.x1, y: checkLine.y1 } let endPoint = { x: checkLine.x2, y: checkLine.y2 } const lineEdge = { vertex1: startPoint, vertex2: endPoint } const checkPolygon = new QPolygon(polygonPoints, {}) const isStartInside = checkPolygon.inPolygon(startPoint) const isEndInside = checkPolygon.inPolygon(endPoint) const intersections = [] checkPolygon.lines.forEach((line) => { const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(edge, lineEdge) if ( intersect && isPointOnLineNew(line, intersect) && isPointOnLineNew(checkLine, intersect) && !(Math.abs(startPoint.x - intersect.x) < 1 && Math.abs(startPoint.y - intersect.y) < 1) && !(Math.abs(endPoint.x - intersect.x) < 1 && Math.abs(endPoint.y - intersect.y) < 1) ) { intersections.push({ intersect, dist: Math.sqrt(Math.pow(startPoint.x - intersect.x, 2) + Math.pow(startPoint.y - intersect.y, 2)) }) } }) if (intersections.length > 0) { intersections.sort((a, b) => a.dist - b.dist) let i = 0 if (!isStartInside) { startPoint = { x: intersections[0].intersect.x, y: intersections[0].intersect.y } i++ } endPoint = { x: intersections[i].intersect.x, y: intersections[i].intersect.y } } return [startPoint.x, startPoint.y, endPoint.x, endPoint.y] } const { forwardLines, backwardLines } = analyzeAllEavesLines(eavesLines) forwardLines.forEach((current) => { const currentLine = current.eaves const analyze = current.analyze const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const currentX1 = Math.min(currentLine.x1, currentLine.x2) const currentX2 = Math.max(currentLine.x1, currentLine.x2) const currentY1 = Math.min(currentLine.y1, currentLine.y2) const currentY2 = Math.max(currentLine.y1, currentLine.y2) const x1 = analyze.startPoint.x const x2 = analyze.endPoint.x const y1 = analyze.startPoint.y const y2 = analyze.endPoint.y const lineVector = { x: 0, y: 0 } if (analyze.isHorizontal) { lineVector.x = Math.sign(analyze.roofLine.y1 - currentLine.attributes.originPoint.y1) } if (analyze.isVertical) { lineVector.y = Math.sign(analyze.roofLine.x1 - currentLine.attributes.originPoint.x1) } const overlapLines = [] const findBackWardLines = backwardLines.filter((backward) => { const backwardVector = { x: 0, y: 0 } if (analyze.isHorizontal) { backwardVector.x = Math.sign(analyze.roofLine.y1 - backward.analyze.startPoint.y) } if (analyze.isVertical) { backwardVector.y = Math.sign(analyze.roofLine.x1 - backward.analyze.startPoint.x) } return backwardVector.x === lineVector.x && backwardVector.y === lineVector.y }) const backWardLine = findBackWardLines.find((backward) => { const backX1 = Math.min(backward.eaves.x1, backward.eaves.x2) const backX2 = Math.max(backward.eaves.x1, backward.eaves.x2) const backY1 = Math.min(backward.eaves.y1, backward.eaves.y2) const backY2 = Math.max(backward.eaves.y1, backward.eaves.y2) return ( (analyze.isHorizontal && Math.abs(currentX1 - backX1) < 0.1 && Math.abs(currentX2 - backX2) < 0.1) || (analyze.isVertical && Math.abs(currentY1 - backY1) < 0.1 && Math.abs(currentY2 - backY2) < 0.1) ) }) if (backWardLine) { overlapLines.push(backWardLine) } else { findBackWardLines.forEach((backward) => { const backX1 = Math.min(backward.eaves.x1, backward.eaves.x2) const backX2 = Math.max(backward.eaves.x1, backward.eaves.x2) const backY1 = Math.min(backward.eaves.y1, backward.eaves.y2) const backY2 = Math.max(backward.eaves.y1, backward.eaves.y2) if ( analyze.isHorizontal && ((currentX1 <= backX1 && currentX2 >= backX1) || (currentX1 <= backX2 && currentX2 >= backX2) || (backX1 < currentX1 && backX1 < currentX2 && backX2 > currentX1 && backX2 > currentX2)) ) { overlapLines.push(backward) } if ( analyze.isVertical && ((currentY1 <= backY1 && currentY2 >= backY1) || (currentY1 <= backY2 && currentY2 >= backY2) || (backY1 < currentY1 && backY1 < currentY2 && backY2 > currentY1 && backY2 > currentY2)) ) { overlapLines.push(backward) } }) } overlapLines.forEach((overlap) => { const overlapAnalyze = overlap.analyze const overlapDegree = getDegreeByChon(overlap.eaves.attributes.pitch) const ridgePoint = [] if (analyze.isHorizontal) { const overlapX1 = Math.min(overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x) const overlapX2 = Math.max(overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x) // 각 라인 사이의 길이를 구해서 각도에 대한 중간 지점으로 마루를 생성. const currentMidY = (currentLine.y1 + currentLine.y2) / 2 const overlapMidY = (overlap.eaves.y1 + overlap.eaves.y2) / 2 const distance = calculateIntersection(Math.abs(currentMidY - overlapMidY), currentDegree, overlapDegree) const vectorToOverlap = Math.sign(overlap.eaves.y1 - currentLine.y1) const pointY = currentLine.y1 + vectorToOverlap * distance.a if (x1 <= overlapX1 && overlapX1 <= x2) { ridgePoint.push({ x: overlapX1, y: pointY }) } else { ridgePoint.push({ x: x1, y: pointY }) } if (x1 <= overlapX2 && overlapX2 <= x2) { ridgePoint.push({ x: overlapX2, y: pointY }) } else { ridgePoint.push({ x: x2, y: pointY }) } } if (analyze.isVertical) { const overlapY1 = Math.min(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y) const overlapY2 = Math.max(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y) // 각 라인 사이의 길이를 구해서 각도에 대한 중간 지점으로 마루를 생성. const currentMidX = (currentLine.x1 + currentLine.x2) / 2 const overlapMidX = (overlap.eaves.x1 + overlap.eaves.x2) / 2 const distance = calculateIntersection(Math.abs(currentMidX - overlapMidX), currentDegree, overlapDegree) const vectorToOverlap = Math.sign(overlap.eaves.x1 - currentLine.x1) const pointX = currentLine.x1 + vectorToOverlap * distance.a if (y1 <= overlapY1 && overlapY1 <= y2) { ridgePoint.push({ x: pointX, y: overlapY1 }) } else { ridgePoint.push({ x: pointX, y: y1 }) } if (y1 <= overlapY2 && overlapY2 <= y2) { ridgePoint.push({ x: pointX, y: overlapY2 }) } else { ridgePoint.push({ x: pointX, y: y2 }) } } const points = findPloygonLineOverlap(roof, [ridgePoint[0].x, ridgePoint[0].y, ridgePoint[1].x, ridgePoint[1].y], canvas, roofId) ridgeLines.push(drawRidgeLine(points, canvas, roof, textMode)) }) }) /** * currentLine {x1,y1}, {x2,y2} 좌표 안쪽에 있는 마루를 찾는다. * @param currentLine * @param roofVector * @param lines * @param tolerance * @returns {*[]} */ const findInnerRidge = (currentLine, roofVector, lines, tolerance = 1) => { const x1 = Math.min(currentLine.x1, currentLine.x2) const y1 = Math.min(currentLine.y1, currentLine.y2) const x2 = Math.max(currentLine.x1, currentLine.x2) const y2 = Math.max(currentLine.y1, currentLine.y2) const dx = Big(currentLine.x2).minus(Big(currentLine.x1)).toNumber() const dy = Big(currentLine.y2).minus(Big(currentLine.y1)).toNumber() const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 let isHorizontal = false, isVertical = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } let innerRidgeLines = [] if (isHorizontal) { lines .filter((line) => { const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 return normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance }) .filter((line) => { const minX = Math.min(line.x1, line.x2) const maxX = Math.max(line.x1, line.x2) return x1 <= minX && maxX <= x2 && roofVector.y * -1 === Math.sign(line.y1 - y1) }) .forEach((line) => innerRidgeLines.push({ line, dist: Math.abs(currentLine.y1 - line.y1) })) } if (isVertical) { lines .filter((line) => { const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 return Math.abs(normalizedAngle - 90) <= tolerance }) .filter((line) => { const minY = Math.min(line.y1, line.y2) const maxY = Math.max(line.y1, line.y2) return y1 <= minY && maxY <= y2 && roofVector.x * -1 === Math.sign(line.x1 - x1) }) .forEach((line) => innerRidgeLines.push({ line, dist: Math.abs(currentLine.x1 - line.x1) })) } innerRidgeLines.sort((a, b) => a.dist - b.dist) const ridge1 = innerRidgeLines.find((ridge) => { if (isHorizontal) { const minX = Math.min(ridge.line.x1, ridge.line.x2) return Math.abs(x1 - minX) <= 1 } if (isVertical) { const minY = Math.min(ridge.line.y1, ridge.line.y2) return Math.abs(y1 - minY) <= 1 } }) const ridge2 = innerRidgeLines.find((ridge) => { if (isHorizontal) { const maxX = Math.max(ridge.line.x1, ridge.line.x2) return Math.abs(x2 - maxX) <= 1 } if (isVertical) { const maxY = Math.max(ridge.line.y1, ridge.line.y2) return Math.abs(y2 - maxY) <= 1 } }) if (ridge1 === ridge2) { innerRidgeLines = [ridge1] } else { if (ridge1 && ridge2) { let range1, range2 if (isHorizontal) { // 수평선: x 범위로 비교 range1 = { min: Math.min(ridge1.line.x1, ridge1.line.x2), max: Math.max(ridge1.line.x1, ridge1.line.x2), } range2 = { min: Math.min(ridge2.line.x1, ridge2.line.x2), max: Math.max(ridge2.line.x1, ridge2.line.x2), } } else { // 수직선: y 범위로 비교 range1 = { min: Math.min(ridge1.line.y1, ridge1.line.y2), max: Math.max(ridge1.line.y1, ridge1.line.y2), } range2 = { min: Math.min(ridge2.line.y1, ridge2.line.y2), max: Math.max(ridge2.line.y1, ridge2.line.y2), } } // 구간 겹침 확인 const overlapStart = Math.max(range1.min, range2.min) const overlapEnd = Math.min(range1.max, range2.max) if (Math.max(0, overlapEnd - overlapStart) > 0) { innerRidgeLines = [ridge1, ridge2] } } } return innerRidgeLines } /** * 지붕면을 그린다. * @param currentLine */ const drawRoofPlane = (currentLine) => { const currentDegree = getDegreeByChon(currentLine.eaves.attributes.pitch) const analyze = currentLine.analyze // 현재라인 안쪽의 마루를 filter하여 계산에 사용. const innerRidgeLines = findInnerRidge( { x1: analyze.startPoint.x, y1: analyze.startPoint.y, x2: analyze.endPoint.x, y2: analyze.endPoint.y }, analyze.roofVector, ridgeLines, 1, ) // 안쪽의 마루가 있는 경우 마루의 연결 포인트를 설정 if (innerRidgeLines.length > 0) { const innerRidgePoints = innerRidgeLines .map((innerRidgeLine) => [ { x: innerRidgeLine.line.x1, y: innerRidgeLine.line.y1 }, { x: innerRidgeLine.line.x2, y: innerRidgeLine.line.y2 }, ]) .flat() const ridgeMinPoint = innerRidgePoints.reduce( (min, curr) => (analyze.isHorizontal ? (curr.x < min.x ? curr : min) : curr.y < min.y ? curr : min), innerRidgePoints[0], ) const ridgeMaxPoint = innerRidgePoints.reduce( (max, curr) => (analyze.isHorizontal ? (curr.x > max.x ? curr : max) : curr.y > max.y ? curr : max), innerRidgePoints[0], ) const roofPlanePoint = [] innerRidgeLines .sort((a, b) => { const line1 = a.line const line2 = b.line if (analyze.isHorizontal) { return line1.x1 - line2.x1 } if (analyze.isVertical) { return line1.y1 - line2.y1 } }) .forEach((ridge, index) => { const isFirst = index === 0 const isLast = index === innerRidgeLines.length - 1 const line = ridge.line if (isFirst) { roofPlanePoint.push({ x: line.x1, y: line.y1 }) } const nextRidge = innerRidgeLines[index + 1] if (nextRidge) { const nextLine = nextRidge.line let range1, range2 if (analyze.isHorizontal) { // 수평선: x 범위로 비교 range1 = { min: Math.min(line.x1, line.x2), max: Math.max(line.x1, line.x2), } range2 = { min: Math.min(nextLine.x1, nextLine.x2), max: Math.max(nextLine.x1, nextLine.x2), } // 겹쳐지는 구간 const overlapX = [Math.max(range1.min, range2.min), Math.min(range1.max, range2.max)] let linePoints = [ { x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, { x: nextLine.x1, y: nextLine.y1 }, { x: nextLine.x2, y: nextLine.y2 }, ].filter((point) => overlapX.includes(point.x)) const firstPoint = ridge.dist > nextRidge.dist ? linePoints.find((point) => point.x === line.x1 || point.x === line.x2) : linePoints.find((point) => point.x === nextLine.x1 || point.x === nextLine.x2) const lastPoint = linePoints.find((point) => point !== firstPoint) roofPlanePoint.push({ x: firstPoint.x, y: firstPoint.y }, { x: firstPoint.x, y: lastPoint.y }) } else { // 수직선: y 범위로 비교 range1 = { min: Math.min(line.y1, line.y2), max: Math.max(line.y1, line.y2), } range2 = { min: Math.min(nextLine.y1, nextLine.y2), max: Math.max(nextLine.y1, nextLine.y2), } //겹쳐지는 구간 const overlapY = [Math.max(range1.min, range2.min), Math.min(range1.max, range2.max)] let linePoints = [ { x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, { x: nextLine.x1, y: nextLine.y1 }, { x: nextLine.x2, y: nextLine.y2 }, ].filter((point) => overlapY.includes(point.y)) const firstPoint = ridge.dist > nextRidge.dist ? linePoints.find((point) => point.y === line.y1 || point.y === line.y2) : linePoints.find((point) => point.y === nextLine.y1 || point.y === nextLine.y2) const lastPoint = linePoints.find((point) => point !== firstPoint) roofPlanePoint.push({ x: firstPoint.x, y: firstPoint.y }, { x: lastPoint.x, y: firstPoint.y }) } } if (isLast) { roofPlanePoint.push({ x: line.x2, y: line.y2 }) } }) const maxDistRidge = innerRidgeLines.reduce((max, curr) => (curr.dist > max.dist ? curr : max), innerRidgeLines[0]) // 지붕선에 맞닫는 포인트를 찾아서 지붕선의 모양에 따라 추가 한다. let innerRoofLines = roof.lines .filter((line) => { //1.지붕선이 현재 라인의 안쪽에 있는지 판단 const tolerance = 1 const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 let isHorizontal = false, isVertical = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } const minX = Math.min(line.x1, line.x2) const maxX = Math.max(line.x1, line.x2) const minY = Math.min(line.y1, line.y2) const maxY = Math.max(line.y1, line.y2) return ( (analyze.isHorizontal && isHorizontal && analyze.startPoint.x <= minX && maxX <= analyze.endPoint.x) || (analyze.isVertical && isVertical && analyze.startPoint.y <= minY && maxY <= analyze.endPoint.y) ) }) .filter((line) => { //2.지붕선이 현재 라인의 바깥에 있는지 확인. const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const length = Math.sqrt(dx * dx + dy * dy) const lineVector = { x: 0, y: 0 } if (analyze.isHorizontal) { // lineVector.y = Math.sign(line.y1 - currentLine.eaves.attributes.originPoint.y1) lineVector.y = Math.sign(line.y1 - maxDistRidge.line.y1) } else if (analyze.isVertical) { // lineVector.x = Math.sign(line.x1 - currentLine.eaves.attributes.originPoint.x1) lineVector.x = Math.sign(line.x1 - maxDistRidge.line.x1) } return ( analyze.roofVector.x === lineVector.x && analyze.roofVector.y === lineVector.y && analyze.directionVector.x === dx / length && analyze.directionVector.y === dy / length ) }) // 패턴 방향에 따라 최소지점, 최대지점의 포인트에서 지붕선 방향에 만나는 포인트를 찾기위한 vector const checkMinVector = { vertex1: { x: ridgeMinPoint.x, y: ridgeMinPoint.y }, vertex2: { x: ridgeMinPoint.x + analyze.roofVector.x, y: ridgeMinPoint.y + analyze.roofVector.y }, } const checkMaxVector = { vertex1: { x: ridgeMaxPoint.x, y: ridgeMaxPoint.y }, vertex2: { x: ridgeMaxPoint.x + analyze.roofVector.x, y: ridgeMaxPoint.y + analyze.roofVector.y }, } // 최소, 최대 지점에 만나는 포인트들에 대한 정보 const roofMinPoint = [], roofMaxPoint = [] innerRoofLines.forEach((line) => { const lineVector = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const minIntersect = edgesIntersection(checkMinVector, lineVector) const maxIntersect = edgesIntersection(checkMaxVector, lineVector) if (minIntersect) { const distance = Math.abs( analyze.isHorizontal ? ridgeMinPoint.x - Math.min(line.x1, line.x2) : ridgeMinPoint.y - Math.min(line.y1, line.y2), ) roofMinPoint.push({ x: minIntersect.x, y: minIntersect.y, isPointOnLine: isPointOnLineNew(line, minIntersect), distance, line }) } if (maxIntersect) { const distance = Math.abs( analyze.isHorizontal ? ridgeMaxPoint.x - Math.max(line.x1, line.x2) : ridgeMaxPoint.y - Math.max(line.y1, line.y2), ) roofMaxPoint.push({ x: maxIntersect.x, y: maxIntersect.y, isPointOnLine: isPointOnLineNew(line, maxIntersect), distance, line }) } }) // 최소지점, 최대지점에 연결되는 지붕선 let minRoof = roofMinPoint.find((point) => point.isPointOnLine) let maxRoof = roofMaxPoint.find((point) => point.isPointOnLine) if (!minRoof) { minRoof = roofMinPoint.sort((a, b) => a.distance - b.distance)[0] } if (!maxRoof) { maxRoof = roofMaxPoint.sort((a, b) => a.distance - b.distance)[0] } if (minRoof && maxRoof) { // 1. 연결되는 지점의 포인트를 사용한다. roofPlanePoint.push({ x: minRoof.x, y: minRoof.y }, { x: maxRoof.x, y: maxRoof.y }) // 2. 최소지점, 최대지점에 연결되는 지붕선이 하나가 아닐경우 연결되는 지점외에 지붕선의 다른 포인트를 추가 해야 한다. if (minRoof.line !== maxRoof.line) { if (analyze.isHorizontal) { Math.abs(minRoof.x - minRoof.line.x1) < Math.abs(minRoof.x - minRoof.line.x2) ? roofPlanePoint.push({ x: minRoof.line.x2, y: minRoof.line.y2 }) : roofPlanePoint.push({ x: minRoof.line.x1, y: minRoof.line.y1 }) Math.abs(maxRoof.x - maxRoof.line.x1) < Math.abs(maxRoof.x - maxRoof.line.x2) ? roofPlanePoint.push({ x: maxRoof.line.x2, y: maxRoof.line.y2 }) : roofPlanePoint.push({ x: maxRoof.line.x1, y: maxRoof.line.y1 }) } if (analyze.isVertical) { Math.abs(minRoof.y - minRoof.line.y1) < Math.abs(minRoof.y - minRoof.line.y2) ? roofPlanePoint.push({ x: minRoof.line.x2, y: minRoof.line.y2 }) : roofPlanePoint.push({ x: minRoof.line.x1, y: minRoof.line.y1 }) Math.abs(maxRoof.y - maxRoof.line.y1) < Math.abs(maxRoof.y - maxRoof.line.y2) ? roofPlanePoint.push({ x: maxRoof.line.x2, y: maxRoof.line.y2 }) : roofPlanePoint.push({ x: maxRoof.line.x1, y: maxRoof.line.y1 }) } // 3.지붕선이 세개 이상일 경우 최소, 최대 연결지점의 지붕선을 제외한 나머지 지붕선은 모든 포인트를 사용한다. const otherRoof = innerRoofLines.filter((line) => line !== minRoof.line && line !== maxRoof.line) otherRoof.forEach((line) => { roofPlanePoint.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) }) } } //각 포인트들을 직교하도록 정렬 const sortedPoints = getSortedOrthogonalPoints(roofPlanePoint) sortedPoints.forEach((currPoint, index) => { const nextPoint = sortedPoints[(index + 1) % sortedPoints.length] const points = [currPoint.x, currPoint.y, nextPoint.x, nextPoint.y] const isAlready = ridgeLines.find( (ridge) => (Math.abs(ridge.x1 - points[0]) < 1 && Math.abs(ridge.y1 - points[1]) < 1 && Math.abs(ridge.x2 - points[2]) < 1 && Math.abs(ridge.y2 - points[3]) < 1) || (Math.abs(ridge.x1 - points[2]) < 1 && Math.abs(ridge.y1 - points[3]) < 1 && Math.abs(ridge.x2 - points[0]) < 1 && Math.abs(ridge.y2 - points[1]) < 1), ) if (isAlready) { return true } const tolerance = 1 const dx = Big(points[2]).minus(Big(points[0])).toNumber() const dy = Big(points[3]).minus(Big(points[1])).toNumber() const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 let isHorizontal = false, isVertical = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } if (analyze.isHorizontal) { //현재라인이 수평선일때 if (isHorizontal) { //같은방향 처리 innerLines.push(drawRoofLine(points, canvas, roof, textMode)) } else { //다른방향 처리 innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } } else if (analyze.isVertical) { //현재라인이 수직선일때 if (isVertical) { //같은방향 처리 innerLines.push(drawRoofLine(points, canvas, roof, textMode)) } else { //다른방향 처리 innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } } }) } } forwardLines.forEach((forward) => { drawRoofPlane(forward) }) backwardLines.forEach((backward) => { drawRoofPlane(backward) }) roof.innerLines.push(...ridgeLines, ...innerLines) canvas .getObjects() .filter((obj) => obj.name === 'check') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() } /** * 한쪽흐름 지붕 * @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() } const getInwardNormal = (v1, v2, isCCW) => { const dx = v2.x - v1.x const dy = v2.y - v1.y const length = Math.sqrt(dx * dx + dy * dy) if (length === 0) return { x: 0, y: 0 } if (isCCW) { return { x: -dy / length, y: dx / length } } else { return { x: dy / length, y: -dx / length } } } const isCounterClockwise = (vertices) => { let sum = 0 for (let i = 0; i < vertices.length; i++) { const v1 = vertices[i] const v2 = vertices[(i + 1) % vertices.length] sum += (v2.x - v1.x) * (v2.y + v1.y) } return sum < 0 } const isPointInPolygon = (point, polygon) => { let inside = false for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { const xi = polygon[i].x, yi = polygon[i].y const xj = polygon[j].x, yj = polygon[j].y const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi if (intersect) inside = !inside } return inside } const calculateAngleBisector = (prevVertex, currentVertex, nextVertex, polygonVertices) => { const isCCW = isCounterClockwise(polygonVertices) // 이전 변의 내향 법선 const norm1 = getInwardNormal(prevVertex, currentVertex, isCCW) // 다음 변의 내향 법선 const norm2 = getInwardNormal(currentVertex, nextVertex, isCCW) // 이등분선 계산 let bisectorX = norm1.x + norm2.x let bisectorY = norm1.y + norm2.y const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY) if (length < 1e-10) { // 180도인 경우 bisectorX = norm1.x bisectorY = norm1.y } else { bisectorX /= length bisectorY /= length } const testPoint = { x: currentVertex.x + bisectorX * 0.1, y: currentVertex.y + bisectorY * 0.1, } if (isPointInPolygon(testPoint, polygonVertices)) { // 방향이 외부를 향하면 반전 bisectorX = -bisectorX bisectorY = -bisectorY } return { x: bisectorX, y: bisectorY } } const lineSegmentIntersection = (p1, p2, p3, p4) => { const x1 = p1.x, y1 = p1.y const x2 = p2.x, y2 = p2.y const x3 = p3.x, y3 = p3.y const x4 = p4.x, y4 = p4.y const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) if (Math.abs(denom) < EPSILON) { return null // 평행 } const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { return { x: Number((x1 + t * (x2 - x1)).toFixed(1)), y: Number((y1 + t * (y2 - y1)).toFixed(1)), } } return null } /** * 두 점 사이의 거리 계산 */ const distanceBetweenPoints = (p1, p2) => { const dx = p2.x - p1.x const dy = p2.y - p1.y return Math.sqrt(dx * dx + dy * dy) } const findIntersectionPoint = (startPoint, direction, polygonVertices, divisionLines) => { const rayEnd = { x: startPoint.x + direction.x * 10000, y: startPoint.y + direction.y * 10000, } let closestIntersection = null let minDistance = Infinity // 다각형 변과의 교점 for (let i = 0; i < polygonVertices.length; i++) { const v1 = polygonVertices[i] const v2 = polygonVertices[(i + 1) % polygonVertices.length] const intersection = lineSegmentIntersection(startPoint, rayEnd, v1, v2) if (intersection) { const dist = distanceBetweenPoints(startPoint, intersection) if (dist > 0.1 && dist < minDistance) { minDistance = dist closestIntersection = intersection } } } // 분할선분과의 교점 for (const divLine of divisionLines) { const intersection = lineSegmentIntersection(startPoint, rayEnd, { x: divLine.x1, y: divLine.y1 }, { x: divLine.x2, y: divLine.y2 }) if (intersection) { const dist = distanceBetweenPoints(startPoint, intersection) if (dist > 0.1 && dist < minDistance) { minDistance = dist closestIntersection = intersection } } } return closestIntersection } /** * 변별로 설정된 지붕을 그린다. * @param roofId * @param canvas * @param textMode */ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const TYPES = { HIP: 'hip', RIDGE: 'ridge', GABLE_LINE: 'gableLine', NEW: 'new' } 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 zeroLines = [] wall.baseLines.forEach((line, index) => (line.attributes.planeSize < EPSILON ? zeroLines.push(index) : null)) let baseLines = [] if (zeroLines.length > 0) { //형이동으로 인해 뭉쳐지는 라인을 찾는다. const mergedLines = [] zeroLines.forEach((zeroIndex) => { const prevIndex = (zeroIndex - 1 + wall.baseLines.length) % wall.baseLines.length const nextIndex = (zeroIndex + 1) % wall.baseLines.length mergedLines.push({ prevIndex, nextIndex }) }) const proceedPair = [] wall.baseLines.forEach((line, index) => { //뭉쳐지는 라인이면 하나로 합치고 baseLines에 추가. 아니면 baseLines에 바로 추가. if (!zeroLines.includes(index) && !mergedLines.find((mergedLine) => mergedLine.prevIndex === index || mergedLine.nextIndex === index)) { baseLines.push(line) } else { if (zeroLines.includes(index)) { mergedLines.forEach((indexPair) => { //이미 처리된 라인이편 패스 if (proceedPair.includes(indexPair.prevIndex) || proceedPair.includes(indexPair.nextIndex)) return let prevLine = wall.baseLines[indexPair.prevIndex] const lineVector = { x: Math.sign(prevLine.x2 - prevLine.x1), y: Math.sign(prevLine.y2 - prevLine.y1) } //중복되는 지붕선을 병합한다. 라인 vector가 같아야한다. const indexList = [indexPair.prevIndex, indexPair.nextIndex] mergedLines .filter((pair) => pair !== indexPair) .forEach((pair) => { const pLine = wall.baseLines[pair.prevIndex] const nLine = wall.baseLines[pair.nextIndex] const pVector = { x: Math.sign(pLine.x2 - pLine.x1), y: Math.sign(pLine.y2 - pLine.y1) } const nVector = { x: Math.sign(nLine.x2 - nLine.x1), y: Math.sign(nLine.y2 - nLine.y1) } if ( pVector.x === lineVector.x && pVector.y === lineVector.y && nVector.x === lineVector.x && nVector.y === lineVector.y && (indexList.includes(pair.prevIndex) || indexList.includes(pair.nextIndex)) ) { indexList.push(pair.prevIndex, pair.nextIndex) } }) const startLine = wall.baseLines[Math.min(...indexList)] const endLine = wall.baseLines[Math.max(...indexList)] const points = [startLine.x1, startLine.y1, endLine.x2, endLine.y2] const size = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }) //라인 좌표 조정. startLine.set({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], startPoint: { x: points[0], y: points[1] }, endPoint: { x: points[2], y: points[3] }, }) startLine.setCoords() startLine.attributes.planeSize = size startLine.attributes.actualSize = size startLine.fire('setLength') //처리된 index 추가. proceedPair.push(...indexList) //조정된라인 baseLine에 추가. baseLines.push(startLine) }) } } }) } else { baseLines = wall.baseLines } const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) const checkWallPolygon = new QPolygon(baseLinePoints, {}) let innerLines = [] /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ 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) { if (Math.sign(prevLine.y2 - prevLine.y1) === 0) { prevRoof.set({ x2: currentLine.x1, y2: prevRoof.y1 }) } else { prevRoof.set({ x2: prevRoof.x1, y2: currentLine.y1 }) } currentRoof.set({ x1: prevRoof.x2, y1: prevRoof.y2 }) } 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 && nextOffset > 0) { 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 (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) { nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 }) } roof = reDrawPolygon(roof, canvas) } }) /** * 라인의 속성을 분석한다. * @param line * @returns {{startPoint: {x: number, y}, endPoint: {x: number, y}, length: number, angleDegree: number, normalizedAngle: number, isHorizontal: boolean, isVertical: boolean, isDiagonal: boolean, directionVector: {x: number, y: number}, roofLine: *, roofVector: {x: number, y: number}}} */ const analyzeLine = (line) => { const tolerance = 1 const dx = Big(line.x2).minus(Big(line.x1)).toNumber() const dy = Big(line.y2).minus(Big(line.y1)).toNumber() const length = Math.sqrt(dx * dx + dy * dy) const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 const directionVector = { x: dx / length, y: dy / length } let isHorizontal = false, isVertical = false, isDiagonal = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } else { isDiagonal = true } const originPoint = line.attributes.originPoint const midX = (originPoint.x1 + originPoint.x2) / 2 const midY = (originPoint.y1 + originPoint.y2) / 2 const offset = line.attributes.offset const checkRoofLines = roof.lines.filter((roof) => { const roofDx = Big(roof.x2).minus(Big(roof.x1)).toNumber() const roofDy = Big(roof.y2).minus(Big(roof.y1)).toNumber() const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) const roofVector = { x: roofDx / roofLength, y: roofDy / roofLength } return directionVector.x === roofVector.x && directionVector.y === roofVector.y }) let roofVector = { x: 0, y: 0 } if (isHorizontal) { const checkPoint = { x: midX, y: midY + offset } if (wall.inPolygon(checkPoint)) { roofVector = { x: 0, y: -1 } } else { roofVector = { x: 0, y: 1 } } } if (isVertical) { const checkPoint = { x: midX + offset, y: midY } if (wall.inPolygon(checkPoint)) { roofVector = { x: -1, y: 0 } } else { roofVector = { x: 1, y: 0 } } } const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofVector.x * offset, y: midY + roofVector.y * offset } } const edgeDx = Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber() const edgeDy = Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).toNumber() const edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy) const edgeVector = { x: edgeDx / edgeLength, y: edgeDy / edgeLength } const intersectRoofLines = [] checkRoofLines.forEach((roofLine) => { const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } const intersect = edgesIntersection(lineEdge, findEdge) if (intersect) { const intersectDx = Big(intersect.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(intersect.x).minus(Big(findEdge.vertex1.x)).toNumber() const intersectDy = Big(intersect.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(intersect.y).minus(Big(findEdge.vertex1.y)).toNumber() const intersectLength = Math.sqrt(intersectDx * intersectDx + intersectDy * intersectDy) const intersectVector = { x: intersectDx / intersectLength, y: intersectDy / intersectLength } if (edgeVector.x === intersectVector.x && edgeVector.y === intersectVector.y) { intersectRoofLines.push({ roofLine, intersect, length: intersectLength }) } } }) intersectRoofLines.sort((a, b) => a.length - b.length) let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect) && roof.length - offset < 0.1) if (!currentRoof) { currentRoof = intersectRoofLines[0] } let startPoint, endPoint if (isHorizontal) { startPoint = { x: Math.min(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y1 } endPoint = { x: Math.max(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y2 } } if (isVertical) { startPoint = { x: line.x1, y: Math.min(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) } endPoint = { x: line.x2, y: Math.max(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) } } if (isDiagonal) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } return { startPoint, endPoint, length, angleDegree, normalizedAngle, isHorizontal, isVertical, isDiagonal, directionVector: { x: dx / length, y: dy / length }, roofLine: currentRoof.roofLine, roofVector, } } /** * * @param testPoint * @param prevLine * @param nextLine * @param baseLines */ const getRidgeDrivePoint = (testPoint = [], prevLine, nextLine, baseLines) => { if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null let prevPoint = null, nextPoint = null const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) const checkWallPolygon = new QPolygon(baseLinePoints, {}) const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } } let prevHasGable = false, nextHasGable = false if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const index = baseLines.findIndex((line) => line === prevLine) const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] if (!beforePrevLine) { prevPoint = null } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { const analyze = analyzeLine(beforePrevLine) const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } } const intersection = edgesIntersection(checkEdge, roofEdge) if (intersection) { prevHasGable = true const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) prevPoint = { intersection, distance } } } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let prevVector = getHalfAngleVector(beforePrevLine, prevLine) const prevCheckPoint = { x: prevLine.x1 + prevVector.x * 10, y: prevLine.y1 + prevVector.y * 10, } let prevHipVector if (checkWallPolygon.inPolygon(prevCheckPoint)) { prevHipVector = { x: prevVector.x, y: prevVector.y } } else { prevHipVector = { x: -prevVector.x, y: -prevVector.y } } const prevHipEdge = { vertex1: { x: prevLine.x1, y: prevLine.y1 }, vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 }, } const intersection = edgesIntersection(prevHipEdge, checkEdge) if (intersection) { const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) } const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) } if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) prevPoint = { intersection, distance } } if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) { prevPoint = { intersection, distance: 0 } } } } } if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const index = baseLines.findIndex((line) => line === nextLine) const afterNextLine = baseLines[(index + 1) % baseLines.length] if (!afterNextLine) { nextPoint = null } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { const analyze = analyzeLine(afterNextLine) const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } } const intersection = edgesIntersection(checkEdge, roofEdge) if (intersection) { nextHasGable = true const distance = Math.sqrt((intersection.x - testPoint[2]) ** 2 + (intersection.y - testPoint[3]) ** 2) nextPoint = { intersection, distance } } } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { let nextVector = getHalfAngleVector(nextLine, afterNextLine) const nextCheckPoint = { x: nextLine.x2 + nextVector.x * 10, y: nextLine.y2 + nextVector.y * 10, } let nextHipVector if (checkWallPolygon.inPolygon(nextCheckPoint)) { nextHipVector = { x: nextVector.x, y: nextVector.y } } else { nextHipVector = { x: -nextVector.x, y: -nextVector.y } } const nextHipEdge = { vertex1: { x: nextLine.x2, y: nextLine.y2 }, vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 }, } const intersection = edgesIntersection(nextHipEdge, checkEdge) if (intersection) { const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) } const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) } if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) nextPoint = { intersection, distance } } if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) { nextPoint = { intersection, distance: 0 } } } } } if (prevHasGable || nextHasGable) { const prevLength = Math.sqrt((prevLine.x1 - prevLine.x2) ** 2 + (prevLine.y1 - prevLine.y2) ** 2) const nextLength = Math.sqrt((nextLine.x1 - nextLine.x2) ** 2 + (nextLine.y1 - nextLine.y2) ** 2) if (prevPoint && prevHasGable && prevLength <= nextLength) { return prevPoint.intersection } if (nextPoint && nextHasGable && prevLength > nextLength) { return nextPoint.intersection } } if (prevPoint && nextPoint) { return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection } else if (prevPoint) { return prevPoint.intersection } else if (nextPoint) { return nextPoint.intersection } else { return null } } /** * 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다. */ const sheds = [] const hipAndGables = [] const eaves = [] const jerkinHeads = [] const gables = [] baseLines.forEach((baseLine) => { switch (baseLine.attributes.type) { case LINE_TYPE.WALLLINE.SHED: sheds.push(baseLine) break case LINE_TYPE.WALLLINE.HIPANDGABLE: hipAndGables.push(baseLine) break case LINE_TYPE.WALLLINE.EAVES: eaves.push(baseLine) break case LINE_TYPE.WALLLINE.JERKINHEAD: jerkinHeads.push(baseLine) break case LINE_TYPE.WALLLINE.GABLE: gables.push(baseLine) break default: break } }) //지붕선 내부에 보조선을 그리기 위한 analysis let linesAnalysis = [] //1. 한쪽흐름(단면경사) 판단, 단면경사는 양옆이 케라바(일반)여야 한다. 맞은편 지붕이 처마여야 한다. sheds.forEach((currentLine) => { let prevLine, nextLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { nextLine = baseLines[(index + 1) % baseLines.length] prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) const analyze = analyzeLine(currentLine) //양옆이 케라바가 아닐때 제외 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { return } const eavesLines = [] // 맞은편 처마 지붕을 찾는다. 흐름 각도를 확인하기 위함. baseLines .filter((baseLine) => baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) .forEach((baseLine) => { const lineAnalyze = analyzeLine(baseLine) //방향이 맞지 않으면 패스 if ( analyze.isHorizontal !== lineAnalyze.isHorizontal || analyze.isVertical !== lineAnalyze.isVertical || analyze.isDiagonal || lineAnalyze.isDiagonal ) { return false } // 수평 일때 if ( analyze.isHorizontal && Math.min(baseLine.x1, baseLine.x2) <= Math.min(currentLine.x1, currentLine.x2) && Math.max(baseLine.x1, baseLine.x2) >= Math.max(currentLine.x1, currentLine.x2) ) { eavesLines.push(baseLine) } //수직 일때 if ( analyze.isVertical && Math.min(baseLine.y1, baseLine.y2) <= Math.min(currentLine.y1, currentLine.y2) && Math.max(baseLine.y1, baseLine.y2) >= Math.max(currentLine.y1, currentLine.y2) ) { eavesLines.push(baseLine) } }) let shedDegree = getDegreeByChon(4) if (eavesLines.length > 0) { shedDegree = getDegreeByChon(eavesLines[0].attributes.pitch) } //지붕좌표1에서 지붕 안쪽으로의 확인 방향 const checkRoofVector1 = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x1 + analyze.roofVector.x * -1, y: analyze.roofLine.y1 + analyze.roofVector.y * -1 }, } //지붕좌표2에서 지붕 안쪽으로의 확인 방향 const checkRoofVector2 = { vertex1: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 }, vertex2: { x: analyze.roofLine.x2 + analyze.roofVector.x * -1, y: analyze.roofLine.y2 + analyze.roofVector.y * -1 }, } //좌표1에서의 교차점 const intersectPoints1 = [] //좌료2에서의 교차점 const intersectPoints2 = [] roof.lines .filter((roofLine) => roofLine !== analyze.roofLine) // 같은 지붕선 제외 .filter((roofLine) => { const tolerance = 1 const dx = Big(roofLine.x2).minus(Big(roofLine.x1)).toNumber() const dy = Big(roofLine.y2).minus(Big(roofLine.y1)).toNumber() const length = Math.sqrt(dx * dx + dy * dy) const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 let isHorizontal = false, isVertical = false, isDiagonal = false if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) { isHorizontal = true } else if (Math.abs(normalizedAngle - 90) <= tolerance) { isVertical = true } else { isDiagonal = true } let otherSide = false // 수평 일때 반대쪽 라인이 현재 라인을 포함하는지 확인. if ( analyze.isHorizontal && ((Math.min(roofLine.x1, roofLine.x2) <= Math.min(analyze.roofLine.x1, analyze.roofLine.x2) && Math.max(roofLine.x1, roofLine.x2) >= Math.max(analyze.roofLine.x1, analyze.roofLine.x2)) || (Math.min(analyze.roofLine.x1, analyze.roofLine.x2) <= Math.min(roofLine.x1, roofLine.x2) && Math.max(analyze.roofLine.x1, analyze.roofLine.x2) >= Math.max(roofLine.x1, roofLine.x2))) && angleDegree !== analyze.angleDegree ) { otherSide = true } //수직 일때 반대쪽 라인이 현재 라인을 포함하는지 확인. if ( analyze.isVertical && ((Math.min(roofLine.y1, roofLine.y2) <= Math.min(analyze.roofLine.y1, analyze.roofLine.y2) && Math.max(roofLine.y1, roofLine.y2) >= Math.max(analyze.roofLine.y1, analyze.roofLine.y2)) || (Math.min(analyze.roofLine.y1, analyze.roofLine.y2) <= Math.min(roofLine.y1, roofLine.y2) && Math.max(analyze.roofLine.y1, analyze.roofLine.y2) >= Math.max(roofLine.y1, roofLine.y2))) && angleDegree !== analyze.angleDegree ) { otherSide = true } return analyze.isHorizontal === isHorizontal && analyze.isVertical === isVertical && analyze.isDiagonal === isDiagonal && otherSide }) .forEach((roofLine) => { const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } const intersect1 = edgesIntersection(lineEdge, checkRoofVector1) const intersect2 = edgesIntersection(lineEdge, checkRoofVector2) if (intersect1 && isPointOnLineNew(roofLine, intersect1)) { const size = Math.sqrt(Math.pow(analyze.roofLine.x1 - intersect1.x, 2) + Math.pow(analyze.roofLine.y1 - intersect1.y, 2)) intersectPoints1.push({ intersect: intersect1, size }) } if (intersect2 && isPointOnLineNew(roofLine, intersect2)) { const size = Math.sqrt(Math.pow(analyze.roofLine.x2 - intersect2.x, 2) + Math.pow(analyze.roofLine.y2 - intersect2.y, 2)) intersectPoints2.push({ intersect: intersect2, size }) } }) intersectPoints1.sort((a, b) => b.size - a.size) intersectPoints2.sort((a, b) => b.size - a.size) const points1 = [analyze.roofLine.x1, analyze.roofLine.y1, intersectPoints1[0].intersect.x, intersectPoints1[0].intersect.y] const points2 = [analyze.roofLine.x2, analyze.roofLine.y2, intersectPoints2[0].intersect.x, intersectPoints2[0].intersect.y] const prevIndex = baseLines.findIndex((baseLine) => baseLine === prevLine) const beforePrevIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(prevIndex - 1 + baseLines.length) % baseLines.length]) const nextIndex = baseLines.findIndex((baseLine) => baseLine === nextLine) const afterNextIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(nextIndex + 1) % baseLines.length]) linesAnalysis.push( { start: { x: points1[0], y: points1[1] }, end: { x: points1[2], y: points1[3] }, left: beforePrevIndex, right: prevIndex, type: TYPES.HIP, degree: shedDegree, }, { start: { x: points2[0], y: points2[1] }, end: { x: points2[2], y: points2[3] }, left: nextIndex, right: afterNextIndex, type: TYPES.HIP, degree: shedDegree, }, ) }) //2. 팔작지붕(이리모야) 판단, 팔작지붕은 양옆이 처마여야만 한다. hipAndGables.forEach((currentLine) => { let prevLine, nextLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { nextLine = baseLines[(index + 1) % baseLines.length] prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) const analyze = analyzeLine(currentLine) //양옆이 처마가 아닐때 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return } let beforePrevLine, afterNextLine baseLines.forEach((baseLine, index) => { if (baseLine === prevLine) { beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } if (baseLine === nextLine) { afterNextLine = baseLines[(index + 1) % baseLines.length] } }) const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) } const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { return } const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) const currentRoofLine = analyze.roofLine let prevRoofLine, nextRoofLine roof.lines.forEach((roofLine, index) => { if (roofLine === currentRoofLine) { nextRoofLine = roof.lines[(index + 1) % roof.lines.length] prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] } }) /** 팔작지붕 두께*/ const roofDx = currentRoofLine.x2 - currentRoofLine.x1 const roofDy = currentRoofLine.y2 - currentRoofLine.y1 const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) const lineWidth = currentLine.attributes.width <= roofLength / 2 ? currentLine.attributes.width : roofLength / 2 /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine) let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: currentRoofLine.x1 + prevVector.x * lineWidth, y: currentRoofLine.y1 + prevVector.y * lineWidth, } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: currentRoofLine.x2 + nextVector.x * lineWidth, y: currentRoofLine.y2 + nextVector.y * lineWidth, } let prevHipVector, nextHipVector if (roof.inPolygon(prevCheckPoint)) { prevHipVector = { x: prevVector.x, y: prevVector.y } } else { prevHipVector = { x: -prevVector.x, y: -prevVector.y } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ if (roof.inPolygon(nextCheckPoint)) { nextHipVector = { x: nextVector.x, y: nextVector.y } } else { nextHipVector = { x: -nextVector.x, y: -nextVector.y } } const prevHipPoint = [ currentRoofLine.x1, currentRoofLine.y1, currentRoofLine.x1 + prevHipVector.x * lineWidth, currentRoofLine.y1 + prevHipVector.y * lineWidth, ] const nextHipPoint = [ currentRoofLine.x2, currentRoofLine.y2, currentRoofLine.x2 + nextHipVector.x * lineWidth, currentRoofLine.y2 + nextHipVector.y * lineWidth, ] const gablePoint = [prevHipPoint[2], prevHipPoint[3], nextHipPoint[2], nextHipPoint[3]] innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree)) innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree)) // innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode)) //양옆이 처마일경우 두개의 선, 아닐때 한개의 선, 좌우가 처마가 아닐때 안그려져야하는데 기존에 그려지는 경우가 있음 이유를 알 수 없음. if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const midPoint = { x: (prevHipPoint[2] + nextHipPoint[2]) / 2, y: (prevHipPoint[3] + nextHipPoint[3]) / 2 } const prevGablePoint = [gablePoint[0], gablePoint[1], midPoint.x, midPoint.y] const nextGablePoint = [gablePoint[2], gablePoint[3], midPoint.x, midPoint.y] const prevDx = prevGablePoint[0] - prevGablePoint[2] const prevDy = prevGablePoint[1] - prevGablePoint[3] const prevGableLength = Math.sqrt(prevDx * prevDx + prevDy * prevDy) const nextDx = nextGablePoint[0] - nextGablePoint[2] const nextDy = nextGablePoint[1] - nextGablePoint[3] const nextGableLength = Math.sqrt(nextDx * nextDx + nextDy * nextDy) if (prevGableLength >= 1) { innerLines.push(drawHipLine(prevGablePoint, canvas, roof, textMode, null, prevDegree, prevDegree)) } if (nextGableLength >= 1) { innerLines.push(drawHipLine(nextGablePoint, canvas, roof, textMode, null, nextDegree, nextDegree)) } const checkEdge = { vertex1: { x: midPoint.x, y: midPoint.y }, vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, } const intersections = [] roof.lines .filter((line) => line !== currentRoofLine) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) if (intersect && isPointOnLineNew(line, intersect)) { const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) intersections.push({ intersect, distance }) } }) intersections.sort((a, b) => a.distance - b.distance) if (intersections.length > 0) { const intersect = intersections[0].intersect const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) const isOverlapBefore = analyze.isHorizontal ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2)) const isOverlapAfter = analyze.isHorizontal ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2)) if (isOverlapBefore || isOverlapAfter) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const otherGable = baseLines.find( (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE, ) const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) } if (!otherGable) { let offset = 0 switch (oppLine.attributes.type) { case LINE_TYPE.WALLLINE.HIPANDGABLE: offset = oppLine.attributes.width break case LINE_TYPE.WALLLINE.JERKINHEAD: offset = oppLine.attributes.width / 2 break case LINE_TYPE.WALLLINE.WALL: offset = 0 break default: break } point[2] += pointVector.x * offset point[3] += pointVector.y * offset } else { if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } } } else if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } linesAnalysis.push({ start: { x: point[0], y: point[1] }, end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: TYPES.RIDGE, degree: 0, }) } } else { const gableDx = gablePoint[0] - gablePoint[2] const gableDy = gablePoint[1] - gablePoint[3] const gableLength = Math.sqrt(gableDx * gableDx + gableDy * gableDy) if (gableLength >= 1) { innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode)) } else { const midPoint = { x: gablePoint[2], y: gablePoint[3] } const checkEdge = { vertex1: { x: midPoint.x, y: midPoint.y }, vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, } const intersections = [] roof.lines .filter((line) => line !== currentRoofLine) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) if (intersect && isPointOnLineNew(line, intersect)) { const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) intersections.push({ intersect, distance }) } }) intersections.sort((a, b) => a.distance - b.distance) if (intersections.length > 0) { const intersect = intersections[0].intersect linesAnalysis.push({ start: { x: midPoint.x, y: midPoint.y }, end: { x: intersect.x, y: intersect.y }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: TYPES.RIDGE, degree: 0, }) } } } }) eaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize) //3. 처마지붕 판단, 처마는 옆이 처마여야한다. const ridgeEaves = eaves.filter((currentLine) => { let prevLine, nextLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { nextLine = baseLines[(index + 1) % baseLines.length] prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } const inPolygonPoint = { x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1), y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1), } //좌우 라인이 서로 다른방향이고 지붕 안쪽으로 들어가지 않을때 const isAbleShape = (prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint) const isAbleAttribute = prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES return isAbleAttribute && isAbleShape }) const hipedEaves = eaves.filter((line) => !ridgeEaves.includes(line)) ridgeEaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize) let proceedEaves = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이 let proceedRidges = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이 let hipLines = [] ridgeEaves.forEach((currentLine) => { /*const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine).renderAll()*/ let prevLine, nextLine, currentI, prevI, nextI baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { currentI = index prevI = (index - 1 + baseLines.length) % baseLines.length nextI = (index + 1) % baseLines.length } }) prevLine = baseLines[prevI] nextLine = baseLines[nextI] let beforePrevLine, afterNextLine baseLines.forEach((baseLine, index) => { if (baseLine === prevLine) { beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } if (baseLine === nextLine) { afterNextLine = baseLines[(index + 1) % baseLines.length] } }) const analyze = analyzeLine(currentLine) const currentDegree = getDegreeByChon(currentLine.attributes.pitch) let pHipVector = getHalfAngleVector(currentLine, prevLine) let nHipVector = getHalfAngleVector(currentLine, nextLine) const pCheckPoint = { x: currentLine.x1 + (pHipVector.x * 10) / 2, y: currentLine.y1 + (pHipVector.y * 10) / 2, } const nCheckPoint = { x: currentLine.x2 + (nHipVector.x * 10) / 2, y: currentLine.y2 + (nHipVector.y * 10) / 2, } if (!checkWallPolygon.inPolygon(pCheckPoint)) { pHipVector = { x: -pHipVector.x, y: -pHipVector.y } } if (!checkWallPolygon.inPolygon(nCheckPoint)) { nHipVector = { x: -nHipVector.x, y: -nHipVector.y } } const prevCheckPoint = [currentLine.x1, currentLine.y1, currentLine.x1 + pHipVector.x * 1000, currentLine.y1 + pHipVector.y * 1000] const nextCheckPoint = [currentLine.x2, currentLine.y2, currentLine.x2 + nHipVector.x * 1000, currentLine.y2 + nHipVector.y * 1000] const findRoofPoints = (points) => { const hipEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) } const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y } const isForwardPoints = [] const isBackwardPoints = [] roof.lines.forEach((roofLine) => { const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } const intersect = edgesIntersection(lineEdge, hipEdge) if (intersect && isPointOnLineNew(roofLine, intersect)) { const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) } if ( (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) || (intersectVector.x === 0 && intersectVector.y === 0) ) { const dx = hipEdge.vertex1.x - intersect.x const dy = hipEdge.vertex1.y - intersect.y const length = Math.sqrt(dx * dx + dy * dy) isForwardPoints.push({ intersect, length }) } if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) { const dx = hipEdge.vertex2.x - intersect.x const dy = hipEdge.vertex2.y - intersect.y const length = Math.sqrt(dx * dx + dy * dy) isBackwardPoints.push({ intersect, length }) } } }) isForwardPoints.sort((a, b) => a.length - b.length) isBackwardPoints.sort((a, b) => a.length - b.length) return { forward: isForwardPoints[0].intersect, backward: isBackwardPoints[0].intersect } } const pRoofPoints = findRoofPoints(prevCheckPoint) const nRoofPoints = findRoofPoints(nextCheckPoint) let prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y } let nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y } const prevEdge = { vertex1: { x: prevHipPoint.x1, y: prevHipPoint.y1 }, vertex2: { x: prevHipPoint.x2, y: prevHipPoint.y2 } } const nextEdge = { vertex1: { x: nextHipPoint.x1, y: nextHipPoint.y1 }, vertex2: { x: nextHipPoint.x2, y: nextHipPoint.y2 } } const intersect = edgesIntersection(prevEdge, nextEdge) if (intersect && isPointOnLineNew(prevHipPoint, intersect) && isPointOnLineNew(nextHipPoint, intersect)) { prevHipPoint.x2 = intersect.x prevHipPoint.y2 = intersect.y nextHipPoint.x2 = intersect.x nextHipPoint.y2 = intersect.y } let isRidgePrev, isRidgeNext let minPrevDist = Infinity let minNextDist = Infinity proceedRidges.forEach((ridge) => { console.log('ridge : ', ridge) const ridgeEdge = { vertex1: { x: ridge.point.x1, y: ridge.point.y1 }, vertex2: { x: ridge.point.x2, y: ridge.point.y2 } } const isPrev = edgesIntersection(ridgeEdge, prevEdge) const isNext = edgesIntersection(ridgeEdge, nextEdge) if ( isPrev && isPointOnLineNew(prevHipPoint, isPrev) && (ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI) ) { const distance = Math.sqrt((isPrev.x - prevHipPoint.x1) ** 2 + (isPrev.y - prevHipPoint.y1) ** 2) if (distance < minPrevDist) { minPrevDist = distance isRidgePrev = isPrev } } if ( isNext && isPointOnLineNew(nextHipPoint, isNext) && (ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI) ) { const distance = Math.sqrt((isNext.x - nextHipPoint.x1) ** 2 + (isNext.y - nextHipPoint.y1) ** 2) if (distance < minNextDist) { minNextDist = distance isRidgeNext = isNext } } }) //접하는 라인에서 파생된 마루선이 겹칠경우 라인보정을 종료 if (isRidgePrev) { prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y } } if (isRidgeNext) { nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y } } let prevHipLength = Math.sqrt((prevHipPoint.x2 - prevHipPoint.x1) ** 2 + (prevHipPoint.y2 - prevHipPoint.y1) ** 2) let nextHipLength = Math.sqrt((nextHipPoint.x2 - nextHipPoint.x1) ** 2 + (nextHipPoint.y2 - nextHipPoint.y1) ** 2) const alreadyPrev = proceedEaves.filter((line) => almostEqual(line.point.x1, prevHipPoint.x1) && almostEqual(line.point.y1, prevHipPoint.y1)) const alreadyNext = proceedEaves.filter((line) => almostEqual(line.point.x1, nextHipPoint.x1) && almostEqual(line.point.y1, nextHipPoint.y1)) if (alreadyPrev.length) { alreadyPrev.sort((a, b) => a.length - b.length) const alrPrev = alreadyPrev[0] if (isRidgePrev) { alrPrev.prev = prevI alrPrev.current = currentI alrPrev.point = prevHipPoint alrPrev.length = prevHipLength } else { if (prevHipLength < alrPrev.length) { //겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가. alrPrev.prev = prevI alrPrev.current = currentI alrPrev.point = prevHipPoint alrPrev.length = prevHipLength } else { prevHipPoint = alrPrev.point } } } else { proceedEaves.push({ prev: prevI, current: currentI, point: prevHipPoint, length: prevHipLength }) } if (alreadyNext.length) { alreadyNext.sort((a, b) => a.length - b.length) const alrNext = alreadyNext[0] if (isRidgeNext) { alrNext.prev = currentI alrNext.current = nextI alrNext.point = nextHipPoint alrNext.length = nextHipLength } else { if (nextHipLength < alrNext.length) { //겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가. alrNext.prev = currentI alrNext.current = nextI alrNext.point = nextHipPoint alrNext.length = nextHipLength } else { nextHipPoint = alrNext.point } } } else { proceedEaves.push({ prev: currentI, current: nextI, point: nextHipPoint, length: nextHipLength }) } let ridgePoint if (almostEqual(prevHipPoint.x2, nextHipPoint.x2) && almostEqual(prevHipPoint.y2, nextHipPoint.y2)) { const ridgeStartPoint = { x: prevHipPoint.x2, y: prevHipPoint.y2 } let ridgeVector = { x: Math.sign(clamp01(nextLine.x2 - nextLine.x1)), y: Math.sign(clamp01(nextLine.y2 - nextLine.y1)) } const midX = (currentLine.x1 + currentLine.x2) / 2 const midY = (currentLine.y1 + currentLine.y2) / 2 let checkPoint = { x: midX + ridgeVector.x, y: midY + ridgeVector.y } if (!checkWallPolygon.inPolygon(checkPoint)) { ridgeVector = { x: -ridgeVector.x, y: -ridgeVector.y } } ridgePoint = { x1: ridgeStartPoint.x, y1: ridgeStartPoint.y, x2: ridgeStartPoint.x + ridgeVector.x, y2: ridgeStartPoint.y + ridgeVector.y } const ridgeEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } } let roofIs let minDistance = Infinity roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, ridgeEdge) if (intersect && isPointOnLineNew(line, intersect)) { const isVector = { x: Math.sign(clamp01(intersect.x - ridgeStartPoint.x)), y: Math.sign(clamp01(intersect.y - ridgeStartPoint.y)) } const distance = Math.sqrt(Math.pow(ridgeStartPoint.x - intersect.x, 2) + Math.pow(ridgeStartPoint.y - intersect.y, 2)) if (distance < minDistance && isVector.x === ridgeVector.x && isVector.y === ridgeVector.y) { minDistance = distance roofIs = intersect } } }) if (roofIs) { ridgePoint.x2 = roofIs.x ridgePoint.y2 = roofIs.y } const drivePoint = getRidgeDrivePoint([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], prevLine, nextLine, baseLines) const isOverlapBefore = analyze.isHorizontal ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2)) const isOverlapAfter = analyze.isHorizontal ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2)) if (isOverlapBefore || isOverlapAfter) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const otherGable = baseLines.find( (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE, ) const pointVector = { x: Math.sign(clamp01(ridgePoint.x1 - ridgePoint.x2)), y: Math.sign(clamp01(ridgePoint.y1 - ridgePoint.y2)) } let offset = 0 switch (oppLine.attributes.type) { case LINE_TYPE.WALLLINE.HIPANDGABLE: offset = oppLine.attributes.width break case LINE_TYPE.WALLLINE.JERKINHEAD: offset = oppLine.attributes.width / 2 break case LINE_TYPE.WALLLINE.WALL: offset = 0 break default: break } if (!otherGable) { if (oppLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const oppIndex = baseLines.findIndex((l) => l === oppLine) const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length] const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length] if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const currentLength = Math.sqrt(Math.pow(currentLine.x1 - currentLine.x2, 2) + Math.pow(currentLine.y1 - currentLine.y2, 2)) const oppLength = Math.sqrt(Math.pow(oppLine.x1 - oppLine.x2, 2) + Math.pow(oppLine.y1 - oppLine.y2, 2)) if (almostEqual(currentLength, oppLength)) { if (drivePoint) { ridgePoint.x2 = drivePoint.x ridgePoint.y2 = drivePoint.y } } else { ridgePoint.x2 += pointVector.x * offset ridgePoint.y2 += pointVector.y * offset } } } else { ridgePoint.x2 += pointVector.x * offset ridgePoint.y2 += pointVector.y * offset } // } } else if (drivePoint) { ridgePoint.x2 = drivePoint.x ridgePoint.y2 = drivePoint.y } } else if (drivePoint) { ridgePoint.x2 = drivePoint.x ridgePoint.y2 = drivePoint.y } } if (ridgePoint) { const alreadyRidge = proceedRidges.find( (line) => almostEqual(line.point.x1, ridgePoint.x1) && almostEqual(line.point.y1, ridgePoint.y1) && almostEqual(line.point.x2, ridgePoint.x2) && almostEqual(line.point.y2, ridgePoint.y2), ) if (!alreadyRidge) { proceedRidges.push({ prev: prevI, next: nextI, point: ridgePoint }) } /*const checkRidgeStart = new fabric.Circle({ left: ridgePoint.x1, top: ridgePoint.y1, radius: 4, parentId: roofId, name: 'check' }) const checkRidgeLine = new fabric.Line([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], { stroke: 'yellow', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkRidgeLine, checkRidgeStart).renderAll()*/ } /*const checkLine1 = new fabric.Line([prevHipPoint.x1, prevHipPoint.y1, prevHipPoint.x2, prevHipPoint.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkLine2 = new fabric.Line([nextHipPoint.x1, nextHipPoint.y1, nextHipPoint.x2, nextHipPoint.y2], { stroke: 'blue', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine1, checkLine2).renderAll() */ canvas .getObjects() .filter((o) => o.name === 'check') .forEach((o) => canvas.remove(o)) canvas.renderAll() }) console.log('hipedEaves', hipedEaves) hipedEaves.forEach((currentLine) => { let prevLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) const analyze = analyzeLine(currentLine) // 옆이 처마가 아니면 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return } const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const checkVector = getHalfAngleVector(currentLine, prevLine) const checkPoint = { x: currentLine.x1 + (checkVector.x * 10) / 2, y: currentLine.y1 + (checkVector.y * 10) / 2, } let hipVector if (checkWallPolygon.inPolygon(checkPoint)) { hipVector = { x: checkVector.x, y: checkVector.y } } else { hipVector = { x: -checkVector.x, y: -checkVector.y } } const hipEdge = { vertex1: { x: currentLine.x1, y: currentLine.y1 }, vertex2: { x: currentLine.x1 + hipVector.x * analyze.length, y: currentLine.y1 + hipVector.y * analyze.length }, } const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) } const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y } const isForwardPoints = [] const isBackwardPoints = [] roof.lines.forEach((roofLine) => { const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } const intersect = edgesIntersection(lineEdge, hipEdge) if (intersect && isPointOnLineNew(roofLine, intersect)) { const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) } if ( (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) || (intersectVector.x === 0 && intersectVector.y === 0) ) { const dx = hipEdge.vertex1.x - intersect.x const dy = hipEdge.vertex1.y - intersect.y const length = Math.sqrt(dx * dx + dy * dy) isForwardPoints.push({ intersect, length }) } if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) { const dx = hipEdge.vertex2.x - intersect.x const dy = hipEdge.vertex2.y - intersect.y const length = Math.sqrt(dx * dx + dy * dy) isBackwardPoints.push({ intersect, length }) } } }) isForwardPoints.sort((a, b) => a.length - b.length) isBackwardPoints.sort((a, b) => a.length - b.length) let hipPoint if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) { hipPoint = { x1: isBackwardPoints[0].intersect.x, y1: isBackwardPoints[0].intersect.y, x2: isForwardPoints[0].intersect.x, y2: isForwardPoints[0].intersect.y, } } else { if (isBackwardPoints.length === 0 && isForwardPoints.length > 1) { hipPoint = { x1: isForwardPoints[0].intersect.x, y1: isForwardPoints[0].intersect.y, x2: isForwardPoints[1].intersect.x, y2: isForwardPoints[1].intersect.y, } } if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) { hipPoint = { x1: isBackwardPoints[0].intersect.x, y1: isBackwardPoints[0].intersect.y, x2: isBackwardPoints[1].intersect.x, y2: isBackwardPoints[1].intersect.y, } } } if (hipPoint) { const hipLength = Math.sqrt((hipPoint.x2 - hipPoint.x1) ** 2 + (hipPoint.y2 - hipPoint.y1) ** 2) const alreadyLine = proceedEaves.filter((line) => almostEqual(line.point.x1, hipPoint.x1) && almostEqual(line.point.y1, hipPoint.y1)) //겹쳐지는 라인이 있는경우 조정한다. if (alreadyLine.length === 0) { linesAnalysis.push({ start: { x: hipPoint.x1, y: hipPoint.y1 }, end: { x: hipPoint.x2, y: hipPoint.y2 }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === currentLine), type: TYPES.HIP, degree: currentDegree, }) } } }) //작성된 라인을 analyze에 추가한다. const pIndexEaves = [] proceedEaves.forEach((eaves, index) => { if (pIndexEaves.includes(index)) return const { prev, current, point } = eaves const currentDegree = getDegreeByChon(baseLines[current].attributes.pitch) const jointEaves = proceedEaves .filter((e) => e !== eaves) .filter((e) => almostEqual(e.point.x2, eaves.point.x2) && almostEqual(e.point.y2, eaves.point.y2)) if (jointEaves.length === 1 && ridgeEaves.length > 1) { innerLines.push(drawHipLine([point.x1, point.y1, point.x2, point.y2], canvas, roof, textMode, null, currentDegree, currentDegree)) pIndexEaves.push(index) } else if (jointEaves.length === 2) { console.log('jointEaves : ', jointEaves) const jointIndex = [index] let jointLines = [] jointEaves.forEach((e) => jointIndex.push(proceedEaves.findIndex((p) => p === e))) const jointVectors = [] //연결된 라인 생성 jointIndex.forEach((i) => { const ev = proceedEaves[i] const degree = getDegreeByChon(baseLines[ev.current].attributes.pitch) innerLines.push(drawHipLine([ev.point.x1, ev.point.y1, ev.point.x2, ev.point.y2], canvas, roof, textMode, null, degree, degree)) pIndexEaves.push(i) jointLines.push(ev.prev, ev.current) jointVectors.push({ x: Math.sign(ev.point.x2 - ev.point.x1), y: Math.sign(ev.point.y2 - ev.point.y1) }) }) //연결된 지점에서 파생된 마루선 제거 const removeRidge = proceedRidges.filter((ridge) => almostEqual(ridge.point.x1, eaves.point.x2) && almostEqual(ridge.point.y1, eaves.point.y2)) proceedRidges = proceedRidges.filter((ridge) => !removeRidge.includes(ridge)) console.log('pIndexEaves : ', pIndexEaves) console.log('jointLines : ', jointLines) console.log('jointVectors : ', jointVectors) let dneVector = jointVectors.find((v) => !jointVectors.find((v2) => v2.x === -v.x && v2.y === -v.y)) console.log('dneVector : ', dneVector) const findRoofEdge = { vertex1: { x: eaves.point.x2, y: eaves.point.y2 }, vertex2: { x: eaves.point.x2 + dneVector.x, y: eaves.point.y2 + dneVector.y }, } let minDistance = Infinity let isPoint roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, findRoofEdge) if (intersect && isPointOnLineNew(line, intersect)) { const distance = Math.sqrt(Math.pow(intersect.x - eaves.point.x2, 2) + Math.pow(intersect.y - eaves.point.y2, 2)) if (distance < minDistance) { minDistance = distance isPoint = intersect } } }) if (isPoint) { const countMap = new Map() jointLines.forEach((value) => { countMap.set(value, (countMap.get(value) || 0) + 1) }) const uniqueLine = jointLines.filter((value) => countMap.get(value) === 1) console.log('uniqueLine : ', uniqueLine) linesAnalysis.push({ start: { x: eaves.point.x2, y: eaves.point.y2 }, end: { x: isPoint.x, y: isPoint.y }, left: uniqueLine[0], right: uniqueLine[1], type: TYPES.HIP, degree: currentDegree, }) } console.log('=============') } else { linesAnalysis.push({ start: { x: point.x1, y: point.y1 }, end: { x: point.x2, y: point.y2 }, left: prev, right: current, type: TYPES.HIP, degree: currentDegree, }) pIndexEaves.push(index) } }) proceedRidges.forEach(({ prev, next, point }) => linesAnalysis.push({ start: { x: point.x1, y: point.y1 }, end: { x: point.x2, y: point.y2 }, left: prev, right: next, type: TYPES.RIDGE, degree: 0, }), ) console.log('proceedEaves :', proceedEaves) console.log('proceedRidges :', proceedRidges) //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다. jerkinHeads.forEach((currentLine) => { let prevLine, nextLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { nextLine = baseLines[(index + 1) % baseLines.length] prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) //양옆이 처마가 아닐때 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return } let beforePrevLine, afterNextLine baseLines.forEach((baseLine, index) => { if (baseLine === prevLine) { beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } if (baseLine === nextLine) { afterNextLine = baseLines[(index + 1) % baseLines.length] } }) const analyze = analyzeLine(currentLine) const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 // innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) return } const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) const gableWidth = currentLine.attributes.width const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) } const roofDx = roofPoints[0] - roofPoints[2] const roofDy = roofPoints[1] - roofPoints[3] const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) const lineLength = (roofLength - gableWidth) / 2 const jerkinPoints = [] jerkinPoints.push( { x: roofPoints[0], y: roofPoints[1] }, { x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength }, ) jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth }) jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength }) if (gableWidth >= roofLength) { innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) } else if (jerkinPoints.length === 4) { const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y] const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y] const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y] const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2)) const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2)) const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2)) if (gableLength1 >= 1) { innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, null, prevDegree, prevDegree)) } if (gableLength2 >= 1) { innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode)) } if (gableLength3 >= 1) { innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, nextDegree, nextDegree)) } const currentRoofLine = analyze.roofLine let prevRoofLine, nextRoofLine roof.lines.forEach((roofLine, index) => { if (roofLine === currentRoofLine) { nextRoofLine = roof.lines[(index + 1) % roof.lines.length] prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] } }) /** 이전, 다음라인의 사잇각의 vector를 구한다. */ let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine) let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine) /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const prevCheckPoint = { x: currentRoofLine.x1 + prevVector.x * 10, y: currentRoofLine.y1 + prevVector.y * 10, } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ const nextCheckPoint = { x: currentRoofLine.x2 + nextVector.x * 10, y: currentRoofLine.y2 + nextVector.y * 10, } let prevHipVector, nextHipVector if (roof.inPolygon(prevCheckPoint)) { prevHipVector = { x: prevVector.x, y: prevVector.y } } else { prevHipVector = { x: -prevVector.x, y: -prevVector.y } } /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ if (roof.inPolygon(nextCheckPoint)) { nextHipVector = { x: nextVector.x, y: nextVector.y } } else { nextHipVector = { x: -nextVector.x, y: -nextVector.y } } const prevHipEdge = { vertex1: { x: gablePoint2[0], y: gablePoint2[1] }, vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth }, } const nextHipEdge = { vertex1: { x: gablePoint2[2], y: gablePoint2[3] }, vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth }, } const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge) if (hipIntersection) { const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) const midPoint = { x: hipIntersection.x, y: hipIntersection.y } const checkEdge = { vertex1: { x: midPoint.x, y: midPoint.y }, vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, } const intersections = [] roof.lines .filter((line) => line !== currentRoofLine) .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) if (intersect && isPointOnLineNew(line, intersect)) { const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) intersections.push({ intersect, distance }) } }) intersections.sort((a, b) => a.distance - b.distance) if (intersections.length > 0) { const intersect = intersections[0].intersect const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) const isOverlapBefore = analyze.isHorizontal ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2)) const isOverlapAfter = analyze.isHorizontal ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2)) if (isOverlapBefore || isOverlapAfter) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const otherGable = baseLines.find( (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE, ) if (!otherGable) { const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) } let offset = 0 switch (oppLine.attributes.type) { case LINE_TYPE.WALLLINE.HIPANDGABLE: offset = oppLine.attributes.width break case LINE_TYPE.WALLLINE.JERKINHEAD: offset = oppLine.attributes.width / 2 break case LINE_TYPE.WALLLINE.WALL: offset = 0 break default: break } if (oppLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } else { point[2] += pointVector.x * offset point[3] += pointVector.y * offset } } else { point[2] += pointVector.x * offset point[3] += pointVector.y * offset } } else if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } } else if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } linesAnalysis.push({ start: { x: point[0], y: point[1] }, end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: TYPES.RIDGE, degree: 0, }) } } } }) //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. gables.forEach((currentLine) => { let prevLine, nextLine baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { nextLine = baseLines[(index + 1) % baseLines.length] prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } }) //양옆이 처마가 아닐때 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return } const prevLineVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) } const nextLineVector = { x: Math.sign(clamp01(nextLine.x1 - nextLine.x2)), y: Math.sign(clamp01(nextLine.y1 - nextLine.y2)) } const inPolygonPoint = { x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1), y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1), } /* const checkCurrLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkCurrLine) canvas.renderAll()*/ //좌우 라인이 서로 다른방향일때 if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) { const analyze = analyzeLine(currentLine) const roofLine = analyze.roofLine let beforePrevLine, afterNextLine baseLines.forEach((baseLine, index) => { if (baseLine === prevLine) { beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] } if (baseLine === nextLine) { afterNextLine = baseLines[(index + 1) % baseLines.length] } }) const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) } // 마루 진행 최대 길이 let prevDistance = 0, nextDistance = 0 // 반대쪽 라인이 특정조건일때 마루선을 반대쪽까지로 처리한다.. const isOverlapBefore = analyze.isHorizontal ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2)) const isOverlapAfter = analyze.isHorizontal ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) && almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2)) : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) && almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2)) let isOverlap = false if (isOverlapBefore || isOverlapAfter) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const otherGable = baseLines.find( (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE, ) if (!otherGable) { isOverlap = true } } console.log('isOverlap : ', isOverlap) if (isOverlap) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const cMidX = (currentLine.x1 + currentLine.x2) / 2 const cMidY = (currentLine.y1 + currentLine.y2) / 2 const oMidX = (oppLine.x1 + oppLine.x2) / 2 const oMidY = (oppLine.y1 + oppLine.y2) / 2 const cOffset = currentLine.attributes.offset const oOffset = oppLine.attributes.type === LINE_TYPE.WALLLINE.WALL ? 0 : oppLine.attributes.offset const ridgeVector = { x: Math.sign(clamp01(cMidX - oMidX)), y: Math.sign(clamp01(cMidY - oMidY)) } const ridgePoint = [ cMidX + ridgeVector.x * cOffset, cMidY + ridgeVector.y * cOffset, oMidX + -ridgeVector.x * oOffset, oMidY + -ridgeVector.y * oOffset, ] let offset = 0 switch (oppLine.attributes.type) { case LINE_TYPE.WALLLINE.HIPANDGABLE: offset = oppLine.attributes.width break case LINE_TYPE.WALLLINE.JERKINHEAD: offset = oppLine.attributes.width / 2 break case LINE_TYPE.WALLLINE.WALL: offset = 0 break default: break } ridgePoint[2] += ridgeVector.x * offset ridgePoint[3] += ridgeVector.y * offset linesAnalysis.push({ start: { x: ridgePoint[0], y: ridgePoint[1] }, end: { x: ridgePoint[2], y: ridgePoint[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: TYPES.RIDGE, degree: 0, }) } else { if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { const beforeVector = { x: Math.sign(clamp01(beforePrevLine.x1 - beforePrevLine.x2)), y: Math.sign(clamp01(beforePrevLine.y1 - beforePrevLine.y2)), } prevDistance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset if (beforeVector.x === currentVector.x && beforeVector.y === currentVector.y) { prevDistance = prevDistance - beforePrevLine.attributes.offset } else { prevDistance = prevDistance + beforePrevLine.attributes.offset } } else { prevDistance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset + beforePrevLine.attributes.offset } if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { const afterVector = { x: Math.sign(clamp01(afterNextLine.x1 - afterNextLine.x2)), y: Math.sign(clamp01(afterNextLine.y1 - afterNextLine.y2)), } nextDistance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset if (afterVector.x === currentVector.x && afterVector.y === currentVector.y) { nextDistance = nextDistance - afterNextLine.attributes.offset } else { nextDistance = nextDistance + afterNextLine.attributes.offset } } else { nextDistance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset + afterNextLine.attributes.offset } //좌우 선분 의 이전 다음 선이 케라바인경우 둘 중 긴 쪽이 기준선이 된다. 아닌경우 짧은쪽 let stdLine let stdFindOppVector if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE && afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { if (nextDistance <= prevDistance) { stdLine = prevLine if (prevLineVector.x === 0) { stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 } } else { stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) } } } else { stdLine = nextLine if (nextLineVector.x === 0) { stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 } } else { stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) } } } } else { if (nextDistance <= prevDistance) { stdLine = nextLine if (nextLineVector.x === 0) { stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 } } else { stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) } } } else { stdLine = prevLine if (prevLineVector.x === 0) { stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 } } else { stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) } } } } let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance const stdAnalyze = analyzeLine(stdLine) let stdPoints = [] const stdPrevLine = baseLines[(baseLines.indexOf(stdLine) - 1 + baseLines.length) % baseLines.length] const stdNextLine = baseLines[(baseLines.indexOf(stdLine) + 1) % baseLines.length] const stdVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) } const stdPrevVector = { x: Math.sign(clamp01(stdPrevLine.x1 - stdPrevLine.x2)), y: Math.sign(clamp01(stdPrevLine.y1 - stdPrevLine.y2)) } const stdNextVector = { x: Math.sign(clamp01(stdNextLine.x1 - stdNextLine.x2)), y: Math.sign(clamp01(stdNextLine.y1 - stdNextLine.y2)) } let stdAdjustVector = { x: 0, y: 0 } if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) { if (stdAnalyze.isHorizontal) { if (stdVector.x === 1) { if (stdPrevLine.y1 > stdNextLine.y1) { stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1) if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2) } else { stdPoints.push(stdLine.x2, stdLine.y2) } } else { if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1) } else { stdPoints.push(stdLine.x1, stdLine.y1) } stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2) } } else { if (stdPrevLine.y1 < stdNextLine.y1) { stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1) if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2) } else { stdPoints.push(stdLine.x2, stdLine.y2) } } else { if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1) } else { stdPoints.push(stdLine.x1, stdLine.y1) } stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2) } } } if (stdAnalyze.isVertical) { if (stdVector.y === 1) { if (stdPrevLine.x1 > stdNextLine.x1) { if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset) } else { stdPoints.push(stdLine.x1, stdLine.y1) } stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset) } else { stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset) if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset) } else { stdPoints.push(stdLine.x2, stdLine.y2) } } } else { if (stdPrevLine.x1 < stdNextLine.x1) { if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset) } else { stdPoints.push(stdLine.x1, stdLine.y1) } stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset) } else { stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset) if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset) } else { stdPoints.push(stdLine.x2, stdLine.y2) } } } } console.log('stdAdjustVector', stdAdjustVector) } else { const stdAddPrevVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) } const stdAddNextVector = { x: Math.sign(clamp01(stdLine.x2 - stdLine.x1)), y: Math.sign(clamp01(stdLine.y2 - stdLine.y1)) } console.log('stdAddPrevVector', stdAddPrevVector) stdPoints = [ stdLine.x1 + stdAddPrevVector.x * stdPrevLine.attributes.offset, stdLine.y1 + stdAddPrevVector.y * stdPrevLine.attributes.offset, stdLine.x2 + stdAddNextVector.x * stdNextLine.attributes.offset, stdLine.y2 + stdAddNextVector.y * stdNextLine.attributes.offset, ] } /*const checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkLine2 = new fabric.Line(stdPoints, { stroke: 'green', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine, checkLine2).renderAll() */ //기준지붕선의 반대쪽선 const oppositeLine = [] const startX = Math.min(stdLine.x1, stdLine.x2) const endX = Math.max(stdLine.x1, stdLine.x2) const startY = Math.min(stdLine.y1, stdLine.y2) const endY = Math.max(stdLine.y1, stdLine.y2) console.log('stdFindOppVector', stdFindOppVector) baseLines .filter((line) => line !== stdLine && line !== currentLine && line.attributes.type !== LINE_TYPE.WALLLINE.SHED) .filter((line) => { const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) } let oppVector = { x: 0, y: 0 } if (stdVector.x === 0) { oppVector = { x: Math.sign(clamp01(stdLine.x1 - line.x1)), y: 0 } } if (stdVector.y === 0) { oppVector = { x: 0, y: Math.sign(clamp01(stdLine.y1 - line.y1)) } } const rightDirection = (stdVector.x === lineVector.x && stdVector.y !== lineVector.y) || (stdVector.x !== lineVector.x && stdVector.y === lineVector.y) const rightOpp = stdFindOppVector.x === oppVector.x && stdFindOppVector.y === oppVector.y return rightDirection && rightOpp }) .forEach((line) => { const lineStartX = Math.min(line.x1, line.x2) const lineEndX = Math.max(line.x1, line.x2) const lineStartY = Math.min(line.y1, line.y2) const lineEndY = Math.max(line.y1, line.y2) if (stdAnalyze.isHorizontal) { //full overlap if (lineStartX <= startX && endX <= lineEndX) { oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) } else if ( (startX < lineStartX && lineStartX < endX) || (startX < lineStartX && lineEndX < endX) || (lineStartX === startX && lineEndX <= endX) || (lineEndX === endX && lineStartX <= startX) ) { oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) } } if (stdAnalyze.isVertical) { //full overlap if (lineStartY <= startY && endY <= lineEndY) { oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) } else if ( (startY < lineStartY && lineStartY < endY) || (startY < lineEndY && lineEndY < endY) || (lineStartY === startY && lineEndY <= endY) || (lineEndY === endY && lineStartY <= startY) ) { oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) } } }) console.log('oppositeLine', oppositeLine) if (oppositeLine.length > 0) { const ridgePoints = [] //지붕선 출발 지점 확인을 위한 기준 포인트 let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 } oppositeLine.sort((a, b) => a.distance - b.distance) oppositeLine.forEach((opposite) => { const oppLine = opposite.line /*const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], { stroke: 'yellow', strokeWidth: 6, parentId: roofId, name: 'check', }) canvas.add(checkOppLine) canvas.renderAll()*/ const oppIndex = baseLines.findIndex((line) => line === oppLine) //마주하는 라인의 이전 다음 라인. const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length] const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length] const oppAnalyze = analyzeLine(oppLine) const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) } let ridgePoint const oppPrevVector = { x: Math.sign(oppPrevLine.x1 - oppPrevLine.x2), y: Math.sign(oppPrevLine.y1 - oppPrevLine.y2) } const oppNextVector = { x: Math.sign(oppNextLine.x1 - oppNextLine.x2), y: Math.sign(oppNextLine.y1 - oppNextLine.y2) } if (oppPrevVector.x === oppNextVector.x && oppPrevVector.y === oppNextVector.y) { const addOffsetX1 = oppPrevVector.y * oppPrevLine.attributes.offset const addOffsetY1 = -oppPrevVector.x * oppPrevLine.attributes.offset const addOffsetX2 = oppPrevVector.y * oppNextLine.attributes.offset const addOffsetY2 = -oppPrevVector.x * oppNextLine.attributes.offset ridgePoint = [oppLine.x1 + addOffsetX1, oppLine.y1 + addOffsetY1, oppLine.x2 + addOffsetX2, oppLine.y2 + addOffsetY2] } else { const inPolygonPoint = { x: (oppLine.x1 + oppLine.x2) / 2 + Math.sign(oppNextLine.x2 - oppNextLine.x1), y: (oppLine.y1 + oppLine.y2) / 2 + Math.sign(oppNextLine.y2 - oppNextLine.y1), } if (checkWallPolygon.inPolygon(inPolygonPoint)) { ridgePoint = [ oppLine.x1 + -oppVector.x * oppPrevLine.attributes.offset, oppLine.y1 + -oppVector.y * oppPrevLine.attributes.offset, oppLine.x2 + oppVector.x * oppNextLine.attributes.offset, oppLine.y2 + oppVector.y * oppNextLine.attributes.offset, ] } else { ridgePoint = [ oppLine.x1 + oppVector.x * oppPrevLine.attributes.offset, oppLine.y1 + oppVector.y * oppPrevLine.attributes.offset, oppLine.x2 + -oppVector.x * oppNextLine.attributes.offset, oppLine.y2 + -oppVector.y * oppNextLine.attributes.offset, ] } } if (stdAnalyze.isHorizontal) { ridgePoint[1] = (stdLine.y1 + oppLine.y1) / 2 ridgePoint[3] = (stdLine.y2 + oppLine.y2) / 2 } if (stdAnalyze.isVertical) { ridgePoint[0] = (stdLine.x1 + oppLine.x1) / 2 ridgePoint[2] = (stdLine.x2 + oppLine.x2) / 2 } // 지붕선 출발 지점과의 거리를 통해 가까운쪽이 start point로 처리. /*const distRidgeStandard1 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[0], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[1], 2)) const distRidgeStandard2 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[2], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[3], 2)) if (distRidgeStandard1 > distRidgeStandard2) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] }*/ if ( (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) && !(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) ) { const oppLineLength = Math.sqrt(Math.pow(oppLine.x2 - oppLine.x1, 2) + Math.pow(oppLine.y2 - oppLine.y1, 2)) const stdLineLength = Math.sqrt(Math.pow(stdLine.x2 - stdLine.x1, 2) + Math.pow(stdLine.y2 - stdLine.y1, 2)) //기준선이 반대선보다 길이가 짧을때는 추녀마루에 대한 교점 처리 패스 if (stdLineLength >= oppLineLength) { if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경 const oppNextAnalyze = analyzeLine(oppNextLine) if (oppNextAnalyze.isHorizontal) { const dist1 = Math.abs(oppNextLine.y1 - ridgePoint[1]) const dist2 = Math.abs(oppNextLine.y1 - ridgePoint[3]) if (dist1 > dist2) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } if (oppNextAnalyze.isVertical) { const dist1 = Math.abs(oppNextLine.x1 - ridgePoint[0]) const dist2 = Math.abs(oppNextLine.x1 - ridgePoint[2]) if (dist1 > dist2) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } const checkVector = getHalfAngleVector(oppLine, oppPrevLine) const checkPoint = { x: oppLine.x1 + (checkVector.x * 10) / 2, y: oppLine.y1 + (checkVector.y * 10) / 2, } let hipVector if (checkWallPolygon.inPolygon(checkPoint)) { hipVector = { x: checkVector.x, y: checkVector.y } } else { hipVector = { x: -checkVector.x, y: -checkVector.y } } const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) } const hipEdge = { vertex1: { x: oppLine.x1, y: oppLine.y1 }, vertex2: { x: oppLine.x1 + hipVector.x, y: oppLine.y1 + hipVector.y } } const intersect = edgesIntersection(hipEdge, checkEdge) if (intersect) { const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) } if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) { ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] } else { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]] } } } if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경 const oppPrevAnalyze = analyzeLine(oppPrevLine) if (oppPrevAnalyze.isHorizontal) { const dist1 = Math.abs(oppPrevLine.y1 - ridgePoint[1]) const dist2 = Math.abs(oppPrevLine.y1 - ridgePoint[3]) if (dist1 > dist2) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } if (oppPrevAnalyze.isVertical) { const dist1 = Math.abs(oppPrevLine.x1 - ridgePoint[0]) const dist2 = Math.abs(oppPrevLine.x1 - ridgePoint[2]) if (dist1 > dist2) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } const checkVector = getHalfAngleVector(oppLine, oppNextLine) const checkPoint = { x: oppLine.x2 + (checkVector.x * 10) / 2, y: oppLine.y2 + (checkVector.y * 10) / 2 } let hipVector if (checkWallPolygon.inPolygon(checkPoint)) { hipVector = { x: checkVector.x, y: checkVector.y } } else { hipVector = { x: -checkVector.x, y: -checkVector.y } } const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) } const hipEdge = { vertex1: { x: oppLine.x2, y: oppLine.y2 }, vertex2: { x: oppLine.x2 + hipVector.x, y: oppLine.y2 + hipVector.y } } const intersect = edgesIntersection(hipEdge, checkEdge) if (intersect) { const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) } if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) { ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] } else { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]] } } } } else { if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine let driveVector = getHalfAngleVector(vectorLine, stdLine) const allPoints = [ { x: stdLine.x1, y: stdLine.y1 }, { x: stdLine.x2, y: stdLine.y2 }, { x: vectorLine.x1, y: vectorLine.y1 }, { x: vectorLine.x2, y: vectorLine.y2 }, ] let startPoint for (let i = 0; i < allPoints.length; i++) { const point = allPoints[i] for (let j = i + 1; j < allPoints.length; j++) { if (i === j) continue const point2 = allPoints[j] if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) { startPoint = point2 break } } if (startPoint) break } const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 } if (!checkWallPolygon.inPolygon(checkDrivePoint)) { driveVector = { x: -driveVector.x, y: -driveVector.y } } const driveEdge = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y }, } const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } const intersect = edgesIntersection(driveEdge, ridgeEdge) const rLength = Math.sqrt(Math.pow(ridgePoint[2] - ridgePoint[0], 2) + Math.pow(ridgePoint[3] - ridgePoint[1], 2)) if (intersect) { const length1 = Math.sqrt(Math.pow(intersect.x - ridgePoint[0], 2) + Math.pow(intersect.y - ridgePoint[1], 2)) const length2 = Math.sqrt(Math.pow(intersect.x - ridgePoint[2], 2) + Math.pow(intersect.y - ridgePoint[3], 2)) if (rLength < length1 && rLength < length2) { if (length1 >= length2) { ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] } else { ridgePoint = [ridgePoint[2], ridgePoint[3], intersect.x, intersect.y] } } } } } } if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine let driveVector = getHalfAngleVector(vectorLine, stdLine) const allPoints = [ { x: stdLine.x1, y: stdLine.y1 }, { x: stdLine.x2, y: stdLine.y2 }, { x: vectorLine.x1, y: vectorLine.y1 }, { x: vectorLine.x2, y: vectorLine.y2 }, ] let startPoint for (let i = 0; i < allPoints.length; i++) { const point = allPoints[i] for (let j = i + 1; j < allPoints.length; j++) { if (i === j) continue const point2 = allPoints[j] if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) { startPoint = point2 break } } if (startPoint) break } const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 } if (!checkWallPolygon.inPolygon(checkDrivePoint)) { driveVector = { x: -driveVector.x, y: -driveVector.y } } const driveEdge = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y }, } const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } const intersect = edgesIntersection(driveEdge, ridgeEdge) if (intersect) { if (almostEqual(stdLine.x1, startPoint.x) && stdAnalyze.isHorizontal) { stdPoints[0] = intersect.x } if (almostEqual(stdLine.y1, startPoint.y) && stdAnalyze.isVertical) { stdPoints[1] = intersect.y } if (almostEqual(stdLine.x2, startPoint.x) && stdAnalyze.isHorizontal) { stdPoints[2] = intersect.x } if (almostEqual(stdLine.y2, startPoint.y) && stdAnalyze.isVertical) { stdPoints[3] = intersect.y } } } /* const checkLine1 = new fabric.Line(stdPoints, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) const checkLine2 = new fabric.Line(ridgePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) canvas.add(checkLine1, checkLine2).renderAll()*/ const stdMinX = Math.min(stdPoints[0], stdPoints[2]) const stdMaxX = Math.max(stdPoints[0], stdPoints[2]) const stdMinY = Math.min(stdPoints[1], stdPoints[3]) const stdMaxY = Math.max(stdPoints[1], stdPoints[3]) const rMinX = Math.min(ridgePoint[0], ridgePoint[2]) const rMaxX = Math.max(ridgePoint[0], ridgePoint[2]) const rMinY = Math.min(ridgePoint[1], ridgePoint[3]) const rMaxY = Math.max(ridgePoint[1], ridgePoint[3]) const overlapMinX = Math.max(stdMinX, rMinX) const overlapMaxX = Math.min(stdMaxX, rMaxX) const overlapMinY = Math.max(stdMinY, rMinY) const overlapMaxY = Math.min(stdMaxY, rMaxY) if (stdAnalyze.isHorizontal) { if (overlapMinX > overlapMaxX) { return } const dist1 = Math.abs(ridgePoint[0] - overlapMinX) const dist2 = Math.abs(ridgePoint[0] - overlapMaxX) if (dist1 < dist2) { ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]] } else { ridgePoint = [overlapMaxX, ridgePoint[1], overlapMinX, ridgePoint[3]] } const ridgeVector = Math.sign(clamp01(ridgePoint[0] - ridgePoint[2])) if (stdVector.x !== ridgeVector) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } if (stdAnalyze.isVertical) { if (overlapMinY > overlapMaxY) { return } const dist1 = Math.abs(ridgePoint[1] - overlapMinY) const dist2 = Math.abs(ridgePoint[1] - overlapMaxY) if (dist1 < dist2) { ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY] } else { ridgePoint = [ridgePoint[0], overlapMaxY, ridgePoint[2], overlapMinY] } const ridgeVector = Math.sign(clamp01(ridgePoint[1] - ridgePoint[3])) if (stdVector.y !== ridgeVector) { ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] } } ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine }) canvas .getObjects() .filter((obj) => obj.name === 'check') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() }) ridgePoints.forEach((r) => { let point = r.point const inPolygon1 = roof.inPolygon({ x: point[0], y: point[1] }) || roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined const inPolygon2 = roof.inPolygon({ x: point[2], y: point[3] }) || roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined //시작점이 지붕 밖에 있을때 vector내의 가까운 지붕선으로 변경 if (!inPolygon1) { const checkVector = { x: Math.sign(point[0] - point[2]), y: Math.sign(point[1] - point[3]) } const checkEdge = { vertex1: { x: point[0], y: point[1] }, vertex2: { x: point[2], y: point[3] } } let minDistance = Infinity let correctPoint roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) if (intersect) { const distance = Math.sqrt(Math.pow(intersect.x - point[0], 2) + Math.pow(intersect.y - point[1], 2)) const intersectVector = { x: Math.sign(point[0] - intersect.x), y: Math.sign(point[1] - intersect.y) } if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) { minDistance = distance correctPoint = intersect } } }) if (correctPoint) { point = [correctPoint.x, correctPoint.y, point[2], point[3]] } } //종료점이 지붕밖에 있을때 vector내의 가까운 지붕선으로 변경 if (!inPolygon2) { const checkVector = { x: Math.sign(point[2] - point[0]), y: Math.sign(point[3] - point[1]) } const checkEdge = { vertex1: { x: point[2], y: point[3] }, vertex2: { x: point[0], y: point[1] } } let minDistance = Infinity let correctPoint roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) if (intersect) { const distance = Math.sqrt(Math.pow(intersect.x - point[2], 2) + Math.pow(intersect.y - point[3], 2)) const intersectVector = { x: Math.sign(point[2] - intersect.x), y: Math.sign(point[3] - intersect.y) } if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) { minDistance = distance correctPoint = intersect } } }) if (correctPoint) { point = [point[0], point[1], correctPoint.x, correctPoint.y] } } const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2)) if (ridgeLength > EPSILON) { linesAnalysis.push({ start: { x: point[0], y: point[1] }, end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === r.left), right: baseLines.findIndex((line) => line === r.right), type: TYPES.RIDGE, degree: 0, }) } }) } } } //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { const analyze = analyzeLine(currentLine) const roofLine = analyze.roofLine /*const checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkRoof) const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { stroke: 'blue', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine) canvas.renderAll()*/ const checkVector = { x: Math.sign(prevLine.y2 - prevLine.y1), y: Math.sign(prevLine.x1 - prevLine.x2) } const checkEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } let maxDistance = 0 let correctPoint roof.lines .filter((line) => { const roofIndex = roof.lines.indexOf(roofLine) const prevRoofLine = roof.lines[(roofIndex - 1 + roof.lines.length) % roof.lines.length] const nextRoofLine = roof.lines[(roofIndex + 1) % roof.lines.length] return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine }) .forEach((line) => { /*const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine).renderAll()*/ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) /* if (intersect) { const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check', }) console.log('isPointOnLineNew(line, intersect)', isPointOnLineNew(line, intersect)) canvas.add(checkCircle).renderAll() }*/ if (intersect && isPointOnLineNew(line, intersect)) { intersect.x = almostEqual(intersect.x, roofLine.x1) ? roofLine.x1 : intersect.x intersect.y = almostEqual(intersect.y, roofLine.y1) ? roofLine.y1 : intersect.y const distance = Math.sqrt(Math.pow(intersect.x - roofLine.x1, 2) + Math.pow(intersect.y - roofLine.y1, 2)) const vector = { x: Math.sign(intersect.x - roofLine.x1), y: Math.sign(intersect.y - roofLine.y1) } console.log('vector', vector, 'checkVector', checkVector) if (distance > maxDistance && vector.x === checkVector.x && vector.y === checkVector.y) { maxDistance = distance correctPoint = intersect } } }) if (correctPoint) { const startPoint = Math.sqrt(Math.pow(correctPoint.x - roofLine.x1, 2) + Math.pow(correctPoint.y - roofLine.y1, 2)) > Math.sqrt(Math.pow(correctPoint.x - roofLine.x2, 2) + Math.pow(correctPoint.y - roofLine.y2, 2)) ? { x: roofLine.x1, y: roofLine.y1 } : { x: roofLine.x2, y: roofLine.y2 } linesAnalysis.push({ start: startPoint, end: { x: correctPoint.x, y: correctPoint.y }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: TYPES.GABLE_LINE, degree: getDegreeByChon(prevLine.attributes.pitch), gableId: baseLines.findIndex((line) => line === currentLine), connectCnt: 0, }) } } canvas .getObjects() .filter((obj) => obj.name === 'check') .forEach((obj) => canvas.remove(obj)) canvas.renderAll() }) //각 선분의 교차점을 찾아 선을 그린다. const MAX_ITERATIONS = 1000 //무한루프 방지 let iterations = 0 console.log('baseLines', baseLines) console.log('linesAnalysis', linesAnalysis) while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ /*linesAnalysis.forEach((line) => { const point = [line.start.x, line.start.y, line.end.x, line.end.y] const checkLine = new fabric.Line(point, { stroke: 'red', strokeWidth: 2, parentId: roofId, name: 'check', }) canvas.add(checkLine).renderAll() // canvas.remove(checkLine).renderAll() })*/ const intersections = [] linesAnalysis.forEach((currLine, i) => { let minDistance = Infinity let intersectPoint = null let linePoint = null let partner = null /*const checkCLine = new fabric.Line([currLine.start.x, currLine.start.y, currLine.end.x, currLine.end.y], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkCLine).renderAll()*/ const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2)) //남은 길이가 0이면 무시 if (cLength < EPSILON) return // 하단레벨 케라바 라인인데 연결점이 2개 이상이면 무시 if (currLine.type === TYPES.GABLE_LINE && currLine.connectCnt > 1) return linesAnalysis.forEach((nextLine, j) => { if (i === j) return if (currLine.type === TYPES.GABLE_LINE && nextLine.type === TYPES.GABLE_LINE && currLine.gableId === nextLine.gableId) return if (nextLine.type === TYPES.GABLE_LINE && nextLine.connectCnt > 1) return /*const checkNLine = new fabric.Line([nextLine.start.x, nextLine.start.y, nextLine.end.x, nextLine.end.y], { stroke: 'green', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkNLine).renderAll()*/ const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas) if (intersect) { /*const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check' }) canvas.add(checkCircle).renderAll()*/ let distance1 = Math.sqrt(Math.pow(intersect.x - currLine.start.x, 2) + Math.pow(intersect.y - currLine.start.y, 2)) let distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.start.x, 2) + Math.pow(intersect.y - nextLine.start.y, 2)) let point = [currLine.start.x, currLine.start.y, intersect.x, intersect.y] if (distance1 < EPSILON) { distance1 = Math.sqrt(Math.pow(intersect.x - currLine.end.x, 2) + Math.pow(intersect.y - currLine.end.y, 2)) distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.end.x, 2) + Math.pow(intersect.y - nextLine.end.y, 2)) point = [currLine.end.x, currLine.end.y, intersect.x, intersect.y] } // 하단레벨 케라바 라인인데 남은 길이가 없는 경우 무시 if (currLine.type === TYPES.GABLE_LINE && distance1 < EPSILON) return //교점까지의 길이가 최소 길이보다 작을 경우, 최소 길이와 교점을 교체 if (distance1 < minDistance && !almostEqual(distance1, minDistance) && !(distance1 < EPSILON && distance2 < EPSILON)) { minDistance = distance1 intersectPoint = intersect linePoint = point partner = j } else if (almostEqual(distance1, minDistance)) { const pLine = linesAnalysis[partner] const isSameLine = currLine.left === pLine.left || currLine.left === pLine.right || currLine.right === pLine.left || currLine.right === pLine.right if (!isSameLine) { partner = j } } // canvas.remove(checkCircle).renderAll() } // canvas.remove(checkNLine).renderAll() }) // canvas.remove(checkCLine).renderAll() if (intersectPoint) { intersections.push({ index: i, intersect: intersectPoint, linePoint, partner }) } }) console.log('intersections', intersections) const intersectPoints = intersections .map((item) => item.intersect) .filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index) console.log('intersectPoints', intersectPoints) if (intersectPoints.length === 1 && intersections.length > 1) { intersections[0].partner = intersections[1].index intersections[1].partner = intersections[0].index } let newAnalysis = [] //신규 발생 선 const processed = new Set() //처리된 선 for (const { index, intersect, linePoint, partner } of intersections) { const cLine = linesAnalysis[index] const pLine = linesAnalysis[partner] //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다. if (!intersect || processed.has(index)) continue //교점이 없거나, 교점 선분이 없으면 처리하지 않는다. const pIntersection = intersections.find((p) => p.index === partner) if (!pIntersection || !pIntersection.intersect) continue //상호 최단 교점이 아닐경우 처리하지 않는다. if (pIntersection.partner !== index) continue //공통선분이 없으면 처리하지 않는다. const isSameLine = cLine.left === pLine.left || cLine.left === pLine.right || cLine.right === pLine.left || cLine.right === pLine.right if (!isSameLine) continue //처리 된 라인으로 설정 processed.add(index).add(partner) let point1 = linePoint let point2 = pIntersection.linePoint let length1 = Math.sqrt(Math.pow(point1[2] - point1[0], 2) + Math.pow(point1[3] - point1[1], 2)) let length2 = Math.sqrt(Math.pow(point2[2] - point2[0], 2) + Math.pow(point2[3] - point2[1], 2)) //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. if (cLine.type === TYPES.GABLE_LINE && length2 < EPSILON) { point2 = [pLine.end.x, pLine.end.y, intersect.x, intersect.y] length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) } if (pLine.type === TYPES.GABLE_LINE && length1 < EPSILON) { point1 = [cLine.end.x, cLine.end.y, intersect.x, intersect.y] length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) } if (length1 > 0 && !alreadyPoints(innerLines, point1)) { if (cLine.type === TYPES.HIP) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) } else if (cLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } else if (cLine.type === TYPES.NEW) { const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1 if (isDiagonal) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } } else if (cLine.type === TYPES.GABLE_LINE) { if (cLine.degree > 0) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } } } if (length2 > 0 && !alreadyPoints(innerLines, point2)) { if (pLine.type === TYPES.HIP) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) } else if (pLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } else if (pLine.type === TYPES.NEW) { const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1 if (isDiagonal) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } } else if (pLine.type === TYPES.GABLE_LINE) { if (pLine.degree > 0) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } } } const otherIs = intersections .filter((is) => is.index !== index && is.index !== partner) .filter((is) => almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y)) let relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right] if (otherIs.length > 0 && cLine.type !== TYPES.GABLE_LINE && pLine.type !== TYPES.GABLE_LINE) { for (let i = 0; i < otherIs.length; i++) { const oLine = linesAnalysis[otherIs[i].index] if (oLine.type === TYPES.RIDGE && linesAnalysis.length > 3) continue const isSameLine = relationBaseLines.includes(oLine.left) || relationBaseLines.includes(oLine.right) if (isSameLine) { let oPoint = otherIs[i].linePoint let oLength = Math.sqrt(Math.pow(oPoint[2] - oPoint[0], 2) + Math.pow(oPoint[3] - oPoint[1], 2)) if (oLength > 0 && !alreadyPoints(innerLines, oPoint)) { if (oLine.type === TYPES.HIP) { innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) } else if (oLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) } else if (oLine.type === TYPES.NEW) { const isDiagonal = Math.abs(oPoint[0] - oPoint[2]) >= 1 && Math.abs(oPoint[1] - oPoint[3]) >= 1 if (isDiagonal) { innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) } else { innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) } } else if (oLine.type === TYPES.GABLE_LINE) { if (oLine.degree > 0) { innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) } else { innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) } } } processed.add(otherIs[i].index) relationBaseLines.push(oLine.left, oLine.right) } } } if (cLine.type === TYPES.GABLE_LINE || pLine.type === TYPES.GABLE_LINE) { relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right] console.log('gableLine newAnalyze start') const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine gableLine.connectCnt++ /*const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, parentId: roofId, name: 'check', }) canvas.add(checkLine, checkCircle) canvas.renderAll()*/ const uniqueBaseLines = [...new Set(relationBaseLines)] // 연결점에서 새로운 가선분을 생성 if (uniqueBaseLines.length > 2) { const linesCounts = new Map() relationBaseLines.forEach((e) => { linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) const uniqueLines = Array.from(linesCounts.entries()) .filter(([_, count]) => count === 1) .map(([line, _]) => line) if (uniqueLines.length === 2) { if (gableLine.connectCnt < 2) { newAnalysis.push({ start: { x: intersect.x, y: intersect.y }, end: { x: gableLine.end.x, y: gableLine.end.y }, left: gableLine.left, right: gableLine.right, type: TYPES.GABLE_LINE, degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch), gableId: gableLine.gableId, connectCnt: gableLine.connectCnt, }) } if (gableLine.connectCnt >= 2) { //가선분 발생만 시키는 더미 생성. newAnalysis.push({ start: { x: intersect.x, y: intersect.y }, end: { x: intersect.x, y: intersect.y }, left: gableLine.gableId, right: gableLine.gableId, type: TYPES.HIP, degree: 0, }) } } } console.log('gableLine newAnalyze end') } else { const uniqueBaseLines = [...new Set(relationBaseLines)] // 연결점에서 새로운 가선분을 생성 if (uniqueBaseLines.length > 2) { const linesCounts = new Map() relationBaseLines.forEach((e) => { linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) const uniqueLines = Array.from(linesCounts.entries()) .filter(([_, count]) => count === 1) .map(([line, _]) => line) console.log('uniqueLines', uniqueLines) if (uniqueLines.length === 2) { // 두 변의 이등분선 방향 계산 // uniqueLines.sort((a, b) => a - b) console.log('uniqueLines : ', uniqueLines) const baseLine1 = baseLines[uniqueLines[0]] const baseLine2 = baseLines[uniqueLines[1]] let bisector console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) if (isParallel(baseLine1, baseLine2)) { bisector = getBisectLines( { x1: cLine.start.x, x2: cLine.end.x, y1: cLine.start.y, y2: cLine.end.y }, { x1: pLine.start.x, y1: pLine.start.y, x2: pLine.end.x, y2: pLine.end.y }, ) } else { bisector = getBisectBaseLines(baseLine1, baseLine2, intersect, canvas) } //마주하는 지붕선을 찾는다. const intersectionsByRoof = [] const checkEdge = { vertex1: { x: intersect.x, y: intersect.y }, vertex2: { x: intersect.x + bisector.x, y: intersect.y + bisector.y }, } const checkVector = { x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x), y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y), } roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(lineEdge, checkEdge) if (is && isPointOnLineNew(line, is)) { const distance = Math.sqrt((is.x - intersect.x) ** 2 + (is.y - intersect.y) ** 2) const isVector = { x: Math.sign(intersect.x - is.x), y: Math.sign(intersect.y - is.y) } if (isVector.x === checkVector.x && isVector.y === checkVector.y) { intersectionsByRoof.push({ is, distance }) } } }) intersectionsByRoof.sort((a, b) => a.distance - b.distance) console.log('intersectionsByRoof : ', intersectionsByRoof) //기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다. const otherIs = intersections.filter( (is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y), ) console.log('otherIs', otherIs) if (otherIs.length > 0) { const analyze = linesAnalysis[otherIs[0].index] processed.add(otherIs[0].index) newAnalysis.push({ start: { x: intersect.x, y: intersect.y }, end: { x: analyze.end.x, y: analyze.end.y }, left: analyze.left, right: analyze.right, type: analyze.type, degree: analyze.degree, gableId: analyze.gableId, connectCnt: analyze.connectCnt, }) } else if (intersectionsByRoof.length > 0) { //지붕선과 교점이 발생할때 let is = intersectionsByRoof[0].is let linePoint = [intersect.x, intersect.y, is.x, is.y] const isDiagonal = Math.abs(is.x - intersect.x) >= 1 && Math.abs(is.y - intersect.y) >= 1 const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2) const line1 = baseLines[uniqueLines[0]] const line2 = baseLines[uniqueLines[1]] const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) } const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1 const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2 /*const checkPrevLine = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], { stroke: 'yellow', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkNextLine = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkPrevLine, checkNextLine).renderAll()*/ if (!isDiagonal) { const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines) if (drivePoint !== null) { const driveLength = Math.sqrt((drivePoint.x - intersect.x) ** 2 + (drivePoint.y - intersect.y) ** 2) if (driveLength < length) { linePoint = [intersect.x, intersect.y, drivePoint.x, drivePoint.y] } } } const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) canvas.add(checkNewLine).renderAll() newAnalysis.push({ start: { x: linePoint[0], y: linePoint[1] }, end: { x: linePoint[2], y: linePoint[3] }, left: uniqueLines[0], right: uniqueLines[1], type: TYPES.NEW, degree: isDiagonal ? cLine.degree : 0, }) } } } } canvas .getObjects() .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') .forEach((object) => canvas.remove(object)) canvas.renderAll() if (newAnalysis.length > 0) break } // 처리된 가선분 제외 linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index))) console.log('lineAnalysis: ', linesAnalysis) canvas .getObjects() .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') .forEach((object) => canvas.remove(object)) canvas.renderAll() if (newAnalysis.length === 0) break } // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. const proceedAnalysis = [] linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .forEach((currentLine) => { if (proceedAnalysis.find((p) => p === currentLine)) return //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .filter( (partnerLine) => partnerLine !== currentLine && !proceedAnalysis.find((p) => p === partnerLine) && (currentLine.left === partnerLine.left || currentLine.left === partnerLine.right || partnerLine.left === currentLine.left || partnerLine.left === currentLine.right), ) .forEach((partnerLine) => { const dx1 = currentLine.end.x - currentLine.start.x const dy1 = currentLine.end.y - currentLine.start.y const dx2 = partnerLine.end.x - partnerLine.start.x const dy2 = partnerLine.end.y - partnerLine.start.y const denominator = dy2 * dx1 - dx2 * dy1 const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 //평행하지 않으면 제외 if (Math.abs(denominator) > EPSILON) return const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) if (isOpposite) { const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) if (length > 0) { if (currentLine.type === TYPES.HIP) { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } else if (currentLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) } else if (currentLine.type === TYPES.NEW) { const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } if (!isDiagonal) { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) } } proceedAnalysis.push(currentLine, partnerLine) } } else { const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end] let points = [] allPoints.forEach((point) => { let count = 0 allPoints.forEach((p) => { if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++ }) if (count === 1) points.push(point) }) if (points.length === 2) { const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) if (length < EPSILON) return const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 if (isDiagonal) { innerLines.push( drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), ) } else { innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) } proceedAnalysis.push(currentLine, partnerLine) } } }) }) linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line)) // 최종 라인중 벽에서 시작해서 벽에서 끝나는 라인이 남을 경우(벽취함) if (linesAnalysis.length > 0) { linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .forEach((line) => { const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start)) const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end)) console.log('startOnLine, endOnLine: ', startOnLine, endOnLine) const allLinesPoints = [] innerLines.forEach((innerLine) => { allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 }) }) if ( allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 && allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0 ) { if (line.type === TYPES.RIDGE && baseLines.length === 4 && (startOnLine || endOnLine)) { innerLines.push(drawRidgeLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode)) } else { if (startOnLine && endOnLine) { if (line.degree === 0) { innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode)) } else { innerLines.push( drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree), ) } } } } }) } //하단 지붕 라인처리 const downRoofLines = [] baseLines.forEach((baseLine, index) => { const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. if ( prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y && (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) ) { downRoofLines.push(index) } }) //지붕선에 따라 라인추가 작업 처리. const innerLinesPoints = [] innerLines.forEach((line) => { const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1) const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2) if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 }) if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 }) }) roof.lines.forEach((currentLine, index) => { const prevLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] const nextLine = roof.lines[(index + 1) % roof.lines.length] const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) console.log('prevDegree, nextDegree: ', prevDegree, nextDegree) const splitPoint = [] let hasOverlapLine = false const minX = Math.min(currentLine.x1, currentLine.x2) const maxX = Math.max(currentLine.x1, currentLine.x2) const minY = Math.min(currentLine.y1, currentLine.y2) const maxY = Math.max(currentLine.y1, currentLine.y2) innerLines.forEach((innerLine) => { const innerLineMinX = Math.min(innerLine.x1, innerLine.x2) const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2) const innerLineMinY = Math.min(innerLine.y1, innerLine.y2) const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2) if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) { hasOverlapLine = true } if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) { hasOverlapLine = true } }) if (hasOverlapLine) return innerLinesPoints.forEach((point) => { if ( isPointOnLineNew(currentLine, point) && !(almostEqual(currentLine.x1, point.x) && almostEqual(currentLine.y1, point.y)) && !(almostEqual(currentLine.x2, point.x) && almostEqual(currentLine.y2, point.y)) ) { const distance = Math.sqrt((point.x - currentLine.x1) ** 2 + (point.y - currentLine.y1) ** 2) splitPoint.push({ point, distance }) } }) if (splitPoint.length > 0) { splitPoint.sort((a, b) => a[1] - b[1]) let startPoint = { x: currentLine.x1, y: currentLine.y1 } for (let i = 0; i < splitPoint.length; i++) { const point = splitPoint[i].point if (i === 0) { innerLines.push(drawHipLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode, null, prevDegree, prevDegree)) } else { innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode)) } startPoint = point } innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, null, nextDegree, nextDegree)) } else { innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode)) } }) //지붕 innerLines에 추가. roof.innerLines = innerLines canvas .getObjects() .filter((object) => object.name === 'check') .forEach((object) => canvas.remove(object)) canvas.renderAll() //return /*while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ linesAnalysis.forEach((line) => { const point = [line.start.x, line.start.y, line.end.x, line.end.y] const checkLine = new fabric.Line(point, { stroke: 'red', strokeWidth: 2, parentId: roofId, name: 'check', }) canvas.add(checkLine) canvas.renderAll() }) // 각 가선분의 최단 교점 찾기 const intersections = [] for (let i = 0; i < linesAnalysis.length; i++) { let minDistance = Infinity //최단거리 let intersectPoint = null //교점 좌표 let partners = new Set() //교점 선분의 index const lineI = linesAnalysis[i] const lengthI = Math.sqrt(Math.pow(lineI.end.x - lineI.start.x, 2) + Math.pow(lineI.end.y - lineI.start.y, 2)) console.log('lengthI', lengthI) const checkLineI = new fabric.Line([lineI.start.x, lineI.start.y, lineI.end.x, lineI.end.y], { stroke: 'blue', strokeWidth: 2, parentId: roofId, name: 'check', }) canvas.add(checkLineI).renderAll() if (lineI.type === TYPES.GABLE_LINE && lineI.connectCnt > 1) continue if (lengthI > EPSILON) { const otherLines = linesAnalysis.filter((j) => j !== lineI) const zeroLines = linesAnalysis.filter((j) => Math.sqrt(Math.pow(j.end.x - j.start.x, 2) + Math.pow(j.end.y - j.start.y, 2)) < EPSILON) if (otherLines.length === zeroLines.length) { zeroLines.forEach((j) => { const jIndex = linesAnalysis.indexOf(j) if (isPointOnLineNew({ x1: lineI.start.x, y1: lineI.start.y, x2: lineI.end.x, y2: lineI.end.y }, { x: j.start.x, y: j.start.y })) { const distance = Math.sqrt(Math.pow(j.start.x - lineI.start.x, 2) + Math.pow(j.start.y - lineI.start.y, 2)) if (distance < minDistance) { minDistance = distance intersectPoint = { x: j.start.x, y: j.start.y } partners = new Set([jIndex]) } else if (almostEqual(distance, minDistance)) { partners.add(jIndex) } } }) if (intersectPoint) { intersections.push({ index: i, intersectPoint, partners, distance: minDistance }) partners.forEach((j) => { const p = new Set([i]) intersections.push({ index: j, intersectPoint, partners: p, distance: 0 }) }) continue } } } for (let j = 0; j < linesAnalysis.length; j++) { const lineJ = linesAnalysis[j] if (lineI === lineJ) continue if (lineI.type === TYPES.GABLE_LINE && lineJ.type === TYPES.GABLE_LINE && i.gableId === lineJ.gableId) continue if (lineJ.type === TYPES.GABLE_LINE && lineJ.connectCnt > 1) continue const intersection = lineIntersection(lineI.start, lineI.end, lineJ.start, lineJ.end, canvas) if (intersection) { const distance = Math.sqrt((intersection.x - lineI.start.x) ** 2 + (intersection.y - lineI.start.y) ** 2) const distance2 = Math.sqrt((intersection.x - lineJ.start.x) ** 2 + (intersection.y - lineJ.start.y) ** 2) if (lineI.type === TYPES.GABLE_LINE && distance < EPSILON) continue if (distance < minDistance && !almostEqual(distance, minDistance) && !(distance < EPSILON && distance2 < EPSILON)) { minDistance = distance intersectPoint = intersection partners = new Set([j]) } else if (almostEqual(distance, minDistance)) { partners.add(j) } } } if (intersectPoint) { intersections.push({ index: i, intersectPoint, partners, distance: minDistance }) } canvas.remove(checkLineI).renderAll() } // 제일 가까운 교점부터 처리 intersections.sort((a, b) => a.distance - b.distance) console.log('intersections', intersections) // 교점에 대한 적합 여부 판단 및 처리. let newAnalysis = [] //신규 발생 선 const processed = new Set() //처리된 선 for (const { index, intersectPoint, partners } of intersections) { canvas .getObjects() .filter((object) => object.name === 'check') .forEach((object) => canvas.remove(object)) canvas.renderAll() let isProceed = false for (const pIndex of partners) { console.log('pIndex : ', pIndex) const check1 = linesAnalysis[index] const checkLine1 = new fabric.Line([check1.start.x, check1.start.y, check1.end.x, check1.end.y], { stroke: 'red', strokeWidth: 2, parentId: roofId, name: 'check', }) const checkCircle1 = new fabric.Circle({ left: check1.start.x, top: check1.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' }) const checkCircle2 = new fabric.Circle({ left: check1.end.x, top: check1.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' }) canvas.add(checkLine1, checkCircle1, checkCircle2) canvas.renderAll() const check2 = linesAnalysis[pIndex] const checkLine2 = new fabric.Line([check2.start.x, check2.start.y, check2.end.x, check2.end.y], { stroke: 'green', strokeWidth: 2, parentId: roofId, name: 'check', }) const checkCircle3 = new fabric.Circle({ left: check2.start.x, top: check2.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' }) const checkCircle4 = new fabric.Circle({ left: check2.end.x, top: check2.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' }) canvas.add(checkLine2, checkCircle3, checkCircle4) canvas.renderAll() console.log('!intersectPoint || processed.has(index)', !intersectPoint, processed.has(index)) //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다. if (!intersectPoint || processed.has(index)) continue const partner = intersections.find((p) => p.index === pIndex) //교점이 없거나, 교점 선분이 없으면 처리하지 않는다. if (!partner || !partner.intersectPoint) continue //상호 최단 교점 여부 확인. if (partner.partners.has(index)) { const line1 = linesAnalysis[index] const line2 = linesAnalysis[pIndex] //좌,우 선 중 공통 선 존재 확인. const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right if (isSameLine) { // 현재 선이 처리 되었음을 표기 let point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] let point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] let length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) let length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) if (length1 < EPSILON && length2 < EPSILON) continue isProceed = true //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. if (line1.type === TYPES.GABLE_LINE && length2 < EPSILON) { point2 = [line2.end.x, line2.end.y, intersectPoint.x, intersectPoint.y] length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) } if (line2.type === TYPES.GABLE_LINE && length1 < EPSILON) { point1 = [line1.end.x, line1.end.y, intersectPoint.x, intersectPoint.y] length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) } if (length1 > 0 && !alreadyPoints(innerLines, point1)) { if (line1.type === TYPES.HIP) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) } else if (line1.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } else if (line1.type === TYPES.NEW) { const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1 if (isDiagonal) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } } else if (line1.type === TYPES.GABLE_LINE) { if (line1.degree > 0) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } } } if (length2 > 0 && !alreadyPoints(innerLines, point2)) { if (line2.type === TYPES.HIP) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) } else if (line2.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } else if (line2.type === TYPES.NEW) { const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1 if (isDiagonal) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } } else if (line2.type === TYPES.GABLE_LINE) { if (line2.degree > 0) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } } } if (line1.type === TYPES.GABLE_LINE || line2.type === TYPES.GABLE_LINE) { console.log('gableLine newAnalyze start') const gableLine = line1.type === TYPES.GABLE_LINE ? line1 : line2 gableLine.connectCnt++ const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkCircle = new fabric.Circle({ left: intersectPoint.x, top: intersectPoint.y, radius: 5, parentId: roofId, name: 'check', }) canvas.add(checkLine, checkCircle) canvas.renderAll() const relationBaseLines = [line1.left, line1.right, line2.left, line2.right] const uniqueBaseLines = [...new Set(relationBaseLines)] if (uniqueBaseLines.length === 3) { const linesCounts = new Map() relationBaseLines.forEach((e) => { linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) const uniqueLines = Array.from(linesCounts.entries()) .filter(([_, count]) => count === 1) .map(([line, _]) => line) if (uniqueLines.length === 2) { if (gableLine.connectCnt < 2) { newAnalysis.push({ start: { x: intersectPoint.x, y: intersectPoint.y }, end: { x: gableLine.end.x, y: gableLine.end.y }, left: gableLine.left, right: gableLine.right, type: TYPES.GABLE_LINE, degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch), gableId: gableLine.gableId, connectCnt: gableLine.connectCnt, }) } if (gableLine.connectCnt >= 2) { //가선분 발생만 시키는 더미 생성. newAnalysis.push({ start: { x: intersectPoint.x, y: intersectPoint.y }, end: { x: intersectPoint.x, y: intersectPoint.y }, left: gableLine.gableId, right: gableLine.gableId, type: TYPES.HIP, degree: 0, }) } } } console.log('gableLine newAnalyze end') } else { // 연결점에서 새로운 가선분을 생성 const relationBaseLines = [line1.left, line1.right, line2.left, line2.right] const uniqueBaseLines = [...new Set(relationBaseLines)] if (uniqueBaseLines.length === 3) { const linesCounts = new Map() relationBaseLines.forEach((e) => { linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) const uniqueLines = Array.from(linesCounts.entries()) .filter(([_, count]) => count === 1) .map(([line, _]) => line) if (uniqueLines.length === 2) { // 두 변의 이등분선 방향 계산 // uniqueLines.sort((a, b) => a - b) console.log('uniqueLines : ', uniqueLines) const baseLine1 = baseLines[uniqueLines[0]] const baseLine2 = baseLines[uniqueLines[1]] const checkLine1 = new fabric.Line([baseLine1.x1, baseLine1.y1, baseLine1.x2, baseLine1.y2], { stroke: 'yellow', strokeWidth: 4, parentId: roofId, name: 'check', }) const checkLine2 = new fabric.Line([baseLine2.x1, baseLine2.y1, baseLine2.x2, baseLine2.y2], { stroke: 'blue', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine1, checkLine2) canvas.renderAll() let bisector console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) if (isParallel(baseLine1, baseLine2)) { bisector = getBisectLines( { x1: line1.start.x, x2: line1.end.x, y1: line1.start.y, y2: line1.end.y }, { x1: line2.start.x, y1: line2.start.y, x2: line2.end.x, y2: line2.end.y }, ) } else { //이등분선 bisector = getBisectBaseLines(baseLine1, baseLine2, intersectPoint, canvas) } //마주하는 지붕선을 찾는다. const intersectionsByRoof = [] const checkEdge = { vertex1: { x: intersectPoint.x, y: intersectPoint.y }, vertex2: { x: intersectPoint.x + bisector.x, y: intersectPoint.y + bisector.y }, } const checkVector = { x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x), y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y), } roof.lines.forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const is = edgesIntersection(lineEdge, checkEdge) if (is && isPointOnLineNew(line, is)) { const distance = Math.sqrt((is.x - intersectPoint.x) ** 2 + (is.y - intersectPoint.y) ** 2) const isVector = { x: Math.sign(intersectPoint.x - is.x), y: Math.sign(intersectPoint.y - is.y) } if (isVector.x === checkVector.x && isVector.y === checkVector.y) { intersectionsByRoof.push({ is, distance }) } } }) intersectionsByRoof.sort((a, b) => a.distance - b.distance) //지붕 선과의 교점이 존재 할때 if (intersectionsByRoof.length > 0) { let is = intersectionsByRoof[0].is let linePoint = [intersectPoint.x, intersectPoint.y, is.x, is.y] const isDiagonal = Math.abs(is.x - intersectPoint.x) >= 1 && Math.abs(is.y - intersectPoint.y) >= 1 const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2) if (!isDiagonal) { const line1 = baseLines[uniqueLines[0]] const line2 = baseLines[uniqueLines[1]] const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) } const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1 const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2 const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines) if (drivePoint !== null) { const driveLength = Math.sqrt((drivePoint.x - intersectPoint.x) ** 2 + (drivePoint.y - intersectPoint.y) ** 2) if (driveLength < length) { linePoint = [intersectPoint.x, intersectPoint.y, drivePoint.x, drivePoint.y] } } } const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) canvas.add(checkNewLine).renderAll() newAnalysis.push({ start: { x: linePoint[0], y: linePoint[1] }, end: { x: linePoint[2], y: linePoint[3] }, left: uniqueLines[0], right: uniqueLines[1], type: TYPES.NEW, degree: isDiagonal ? line1.degree : 0, }) } } canvas .getObjects() .filter((object) => object.name === 'check') .forEach((object) => canvas.remove(object)) canvas.renderAll() } } processed.add(pIndex) } } canvas .getObjects() .filter((object) => object.name === 'check') .forEach((object) => canvas.remove(object)) canvas.renderAll() } if (isProceed) { processed.add(index) break } } // 처리된 가선분 제외 linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index))) console.log('lineAnalysis: ', linesAnalysis) canvas .getObjects() .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') .forEach((object) => canvas.remove(object)) canvas.renderAll() // 새로운 가선분이 없을때 종료 console.log('newAnalysis.length : ', newAnalysis.length) if (newAnalysis.length === 0) break } console.log('lineAnalysis: end ', linesAnalysis)*/ // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. /* const proceedAnalysis = [] linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .forEach((currentLine) => { if (proceedAnalysis.find((p) => p === currentLine)) return //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .filter( (partnerLine) => partnerLine !== currentLine && !proceedAnalysis.find((p) => p === partnerLine) && (currentLine.left === partnerLine.left || currentLine.left === partnerLine.right || partnerLine.left === currentLine.left || partnerLine.left === currentLine.right), ) .forEach((partnerLine) => { const dx1 = currentLine.end.x - currentLine.start.x const dy1 = currentLine.end.y - currentLine.start.y const dx2 = partnerLine.end.x - partnerLine.start.x const dy2 = partnerLine.end.y - partnerLine.start.y const denominator = dy2 * dx1 - dx2 * dy1 const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 //평행하지 않으면 제외 if (Math.abs(denominator) > EPSILON) return const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) if (isOpposite) { const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) if (length > 0) { if (currentLine.type === TYPES.HIP) { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } else if (currentLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) } else if (currentLine.type === TYPES.NEW) { const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) } if (!isDiagonal) { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) } } proceedAnalysis.push(currentLine, partnerLine) } } else { const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end] let points = [] allPoints.forEach((point) => { let count = 0 allPoints.forEach((p) => { if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++ }) if (count === 1) points.push(point) }) if (points.length === 2) { const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) if (length < EPSILON) return const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 if (isDiagonal) { innerLines.push( drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), ) } else { innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) } proceedAnalysis.push(currentLine, partnerLine) } } }) }) linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line))*/ //하단 지붕 라인처리 /* const downRoofLines = [] baseLines.forEach((baseLine, index) => { const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. if ( prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y && (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) ) { downRoofLines.push(index) } })*/ /*if (downRoofLines.length > 0) { downRoofLines.forEach((index) => { const currentLine = baseLines[index] // const nextLine = baseLines[(index + 1) % baseLines.length] // const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] const analyze = analyzeLine(currentLine) if (analyze.isDiagonal) { return } const roofLine = analyze.roofLine let roofPoint = [analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y] if (analyze.isVertical) { roofPoint[0] = roofLine.x1 roofPoint[2] = roofLine.x2 } if (analyze.isHorizontal) { roofPoint[1] = roofLine.y1 roofPoint[3] = roofLine.y2 } console.log('analyze: ', analyze) const findRidgeVector = { x: 0, y: 0 } if (analyze.isVertical) { // noinspection JSSuspiciousNameCombination findRidgeVector.x = analyze.directionVector.y } if (analyze.isHorizontal) { // noinspection JSSuspiciousNameCombination findRidgeVector.y = analyze.directionVector.x } console.log('findRidgeVector: ', findRidgeVector) innerLines .filter((line) => { if (line.name === LINE_TYPE.SUBLINE.RIDGE) { if (analyze.isVertical) { const signX = Math.sign(currentLine.x1 - line.x1) console.log('signX: ', signX) return signX === findRidgeVector.x } if (analyze.isHorizontal) { const signY = Math.sign(currentLine.y1 - line.y1) console.log('signY: ', signY) return signY === findRidgeVector.y } return false } return false }) .forEach((line) => { if (line.name === LINE_TYPE.SUBLINE.RIDGE) { const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine) canvas.renderAll() } }) /!*const oppositeLines = [] baseLines .filter((line) => { //평행한 반대방향 라인인지 확인. const lineAnalyze = analyzeLine(line) return ( (analyze.isHorizontal && lineAnalyze.directionVector.x !== analyze.directionVector.x && lineAnalyze.directionVector.y === analyze.directionVector.y) || (analyze.isVertical && lineAnalyze.directionVector.x === analyze.directionVector.x && lineAnalyze.directionVector.y !== analyze.directionVector.y) ) }) .filter((line) => { //라인이 현재라인에 overlap 되거나, 현재 라인을 full overlap하는지 확인. if (analyze.isHorizontal) { const currentMinX = Math.min(currentLine.x1, currentLine.x2) const currentMaxX = Math.max(currentLine.x1, currentLine.x2) const minX = Math.min(line.x1, line.x2) const maxX = Math.max(line.x1, line.x2) //full overlap if (minX <= currentMinX && maxX >= currentMaxX) { return true } //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단. if ((currentMinX <= minX && minX <= currentMaxX) || (currentMinX <= maxX && maxX <= currentMaxX)) { return true } } if (analyze.isVertical) { const currentMinY = Math.min(currentLine.y1, currentLine.y2) const currentMaxY = Math.max(currentLine.y1, currentLine.y2) const minY = Math.min(line.y1, line.y2) const maxY = Math.max(line.y1, line.y2) //full overlap if (minY <= currentMinY && maxY >= currentMaxY) { return true } //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단. if ((currentMinY <= minY && minY <= currentMaxY) || (currentMinY <= maxY && maxY <= currentMaxY)) { return true } } return false }) .forEach((line, i) => { const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { stroke: 'green', strokeWidth: 4, parentId: roofId, name: 'check', }) canvas.add(checkLine) canvas.renderAll() })*!/ // innerLines.push(drawRoofLine(roofPoint, canvas, roof, textMode)) }) }*/ /*if (linesAnalysis.length > 0) { linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x const dy = line.end.y - line.start.y const length = Math.sqrt(dx ** 2 + dy ** 2) return length > 0 }) .forEach((line) => { const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start)) const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end)) console.log('startOnLine, endOnLine: ', startOnLine, endOnLine) const allLinesPoints = [] innerLines.forEach((innerLine) => { allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 }) }) if ( allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 && allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0 ) { if (startOnLine || endOnLine) { if (line.degree === 0) { innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode)) } else { innerLines.push( drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree), ) } } } }) }*/ //지붕선에 따라 라인추가 작업 처리. /*const innerLinesPoints = [] innerLines.forEach((line) => { const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1) const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2) if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 }) if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 }) }) roof.lines.forEach((line) => { const splitPoint = [] let hasOverlapLine = false const minX = Math.min(line.x1, line.x2) const maxX = Math.max(line.x1, line.x2) const minY = Math.min(line.y1, line.y2) const maxY = Math.max(line.y1, line.y2) innerLines.forEach((innerLine) => { const innerLineMinX = Math.min(innerLine.x1, innerLine.x2) const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2) const innerLineMinY = Math.min(innerLine.y1, innerLine.y2) const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2) if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) { hasOverlapLine = true } if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) { hasOverlapLine = true } }) if (hasOverlapLine) return innerLinesPoints.forEach((point) => { if ( isPointOnLineNew(line, point) && !(almostEqual(line.x1, point.x) && almostEqual(line.y1, point.y)) && !(almostEqual(line.x2, point.x) && almostEqual(line.y2, point.y)) ) { const distance = Math.sqrt((point.x - line.x1) ** 2 + (point.y - line.y1) ** 2) splitPoint.push({ point, distance }) } }) if (splitPoint.length > 0) { splitPoint.sort((a, b) => a[1] - b[1]) let startPoint = { x: line.x1, y: line.y1 } for (let i = 0; i < splitPoint.length; i++) { const point = splitPoint[i].point innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode)) startPoint = point } innerLines.push(drawRoofLine([startPoint.x, startPoint.y, line.x2, line.y2], canvas, roof, textMode)) } else { innerLines.push(drawRoofLine([line.x1, line.y1, line.x2, line.y2], canvas, roof, textMode)) } })*/ } /** * 선분과 선분의 교점을 구한다. * @param line1Start * @param line1End * @param line2Start * @param line2End */ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { const dx1 = line1End.x - line1Start.x const dy1 = line1End.y - line1Start.y const dx2 = line2End.x - line2Start.x const dy2 = line2End.y - line2Start.y // const denominator = (line2End.y - line2Start.y) * (line1End.x - line1Start.x) - (line2End.x - line2Start.x) * (line1End.y - line1Start.y) const denominator = dy2 * dx1 - dx2 * dy1 if (Math.abs(denominator) < EPSILON) { const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 const isOverlap = isPointOnLineNew({ x1: line1Start.x, y1: line1Start.y, x2: line1End.x, y2: line1End.y }, { x: line2Start.x, y: line2Start.y }) if (isOpposite && isOverlap) { return { x: line2Start.x, y: line2Start.y } } return null } // 평행 let ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator let ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator ua = clamp01(ua) ub = clamp01(ub) if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return { x: line1Start.x + ua * dx1, y: line1Start.y + ua * dy1, } } return null } /** * 경계값 보정용 헬퍼 * @param t * @returns {*|number} */ const clamp01 = (t) => { if (almostEqual(t, 0, EPSILON)) return 0 if (almostEqual(t, 1, EPSILON)) return 1 return t } /** * 실제 각도를 계산한다 대각선인 경우 각도 조정 * @param points * @param degree */ const getRealDegree = (points, degree) => { const deltaX = Math.abs(points[2] - points[0]) const deltaY = Math.abs(points[3] - points[1]) if (deltaX < 1 || deltaY < 1) { return degree } const hypotenuse = Math.sqrt(deltaX ** 2 + deltaY ** 2) const adjacent = Math.sqrt(Math.pow(hypotenuse, 2) / 2) const height = adjacent * Math.tan((degree * Math.PI) / 180) return Math.atan2(height, hypotenuse) * (180 / Math.PI) } /** * 두 라인이 평행한지 확인한다. * @param line1 * @param line2 * @returns {boolean} */ const isParallel = (line1, line2) => { // 벡터 계산 const v1x = line1.x2 - line1.x1 const v1y = line1.y2 - line1.y1 const v2x = line2.x2 - line2.x1 const v2y = line2.y2 - line2.y1 // 벡터의 크기 계산 const length1 = Math.sqrt(v1x ** 2 + v1y ** 2) const length2 = Math.sqrt(v2x ** 2 + v2y ** 2) // 벡터가 너무 작으면 평행 판단 불가 if (length1 < EPSILON || length2 < EPSILON) { return false } // 정규화된 벡터 const norm1x = v1x / length1 const norm1y = v1y / length1 const norm2x = v2x / length2 const norm2y = v2y / length2 // 외적(cross product)을 이용한 평행 판단 // 외적의 크기가 0에 가까우면 평행 const crossProduct = Math.abs(norm1x * norm2y - norm1y * norm2x) const isParallel = crossProduct < EPSILON // 또는 내적(dot product)을 이용한 방법 // 내적의 절대값이 1에 가까우면 평행 또는 반평행 const dotProduct = norm1x * norm2x + norm1y * norm2y const isParallelOrAntiParallel = Math.abs(Math.abs(dotProduct) - 1) < EPSILON return isParallel || isParallelOrAntiParallel } /** * 두 라인의 방향에 따라 이등분선의 vector를 구한다. * @param line1 * @param line2 */ const getBisectLines = (line1, line2) => { const bisector = { x: 0, y: 0 } // 벡터 계산 const v1x = line1.x2 - line1.x1 const v1y = line1.y2 - line1.y1 const v2x = line2.x2 - line2.x1 const v2y = line2.y2 - line2.y1 // 벡터의 크기 계산 const length1 = Math.sqrt(v1x ** 2 + v1y ** 2) const length2 = Math.sqrt(v2x ** 2 + v2y ** 2) // 벡터가 너무 작으면 평행 판단 불가 if (length1 < EPSILON || length2 < EPSILON) { return bisector } const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } } const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } } const is = edgesIntersection(lineEdge1, lineEdge2) if (is) { const dir1 = getDirection(line1, is) const dir2 = getDirection(line2, is) // 이등분선 계산 const bisectorX = dir1.x + dir2.x const bisectorY = dir1.y + dir2.y const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY) bisector.x = bisectorX / length bisector.y = bisectorY / length } return bisector } /** * BaseLine의 교차상태에서의 bisect를 구한다. * @param line1 * @param line2 * @param intersection */ const getBisectBaseLines = (line1, line2, intersection) => { let bisector = { x: 0, y: 0 } if (isParallel(line1, line2)) return bisector const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } } const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } } const is = edgesIntersection(lineEdge1, lineEdge2) if (is) { bisector = { x: Math.sign(intersection.x - is.x), y: Math.sign(intersection.y - is.y) } } return bisector } /** * a 와 b 가 epsilon내 오차인지 확인한다. * @param a * @param b * @param epsilon * @returns {boolean} */ const almostEqual = (a, b, epsilon = 1e-10) => { if (a === Infinity || b === Infinity) return false return Math.abs(a - b) <= epsilon } /** * segment가 fromPoint에서 떨어진 방향을 구한다. * @param segment * @param fromPoint * @returns {{x, y}} */ const getDirection = (segment, fromPoint) => { const target = { x: segment.x2, y: segment.y2 } const dx = target.x - fromPoint.x const dy = target.y - fromPoint.y const length = Math.sqrt(dx * dx + dy * dy) return { x: dx / length, y: dy / length } } /** * lines중 points가 이미 존재하는지 확인한다. * @param lines * @param points * @returns {boolean} */ const alreadyPoints = (lines, points) => { let has = false lines.forEach((line) => { const linePoints = [line.x1, line.y1, line.x2, line.y2] if ( (almostEqual(linePoints[0], points[0], 1) && almostEqual(linePoints[1], points[1], 1) && almostEqual(linePoints[2], points[2], 1) && almostEqual(linePoints[3], points[3], 1)) || (almostEqual(linePoints[0], points[2], 1) && almostEqual(linePoints[1], points[3], 1) && almostEqual(linePoints[2], points[0], 1) && almostEqual(linePoints[3], points[1], 1)) ) { has = true } }) return has } /** * 마루가 있는 지붕을 그린다. * @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, {}) 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 if (prevBaseLine.size !== prevLine.attributes.planeSize) { prevLine.x1 = prevBaseLine.x1 prevLine.y1 = prevBaseLine.y1 prevLine.x2 = prevBaseLine.x2 prevLine.y2 = prevBaseLine.y2 prevLine.setCoords() } if (nextBaseLine.size !== nextLine.attributes.planeSize) { nextLine.x1 = nextBaseLine.x1 nextLine.y1 = nextBaseLine.y1 nextLine.x2 = nextBaseLine.x2 nextLine.y2 = nextBaseLine.y2 nextLine.setCoords() } 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(calcLinePlaneSize(prevLine)).div(10) const nextSize = Big(calcLinePlaneSize(nextLine)).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)) .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 === 0 || line.line.attributes.actualSize === '' || line.line.attributes.actualSize === undefined ? line.line.attributes.planeSize : 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) if (basePoints.length > 0 && basePoints[0].line) { hipSize = Big(basePoints[0].line.attributes.planeSize) } else { hipSize = Big(0) // 또는 기본값 설정 } } 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 === 0 || oldActualSize === '' || oldActualSize === undefined ? oldPlaneSize : 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 } } /** * 직교 다각형(축에 평행한 변들로만 구성된 다각형)의 점들을 시계방향으로 정렬 * @param points 정렬할 점들의 배열 * @returns {[sortedPoints]} 정렬된 점들의 배열 */ const getSortedOrthogonalPoints = (points) => { if (points.length < 3) return points /** * @param currentDirection 현재 방향 * @param nextDirection 다음 방향 * @param isClockWise 흐름 방향 * @returns {boolean} 유효한 방향 전환인지 여부 */ const isValidNextDirection = (currentDirection, nextDirection, isClockWise = false) => { if (!currentDirection) return true let validTransitions if (!isClockWise) { // 반 시계방향 진행 규칙 validTransitions = { right: ['up'], down: ['right'], left: ['down'], up: ['left'], } } else { // 시계방향 진행 규칙 validTransitions = { right: ['down'], down: ['left'], left: ['up'], up: ['right'], } } return !validTransitions[currentDirection] || validTransitions[currentDirection].includes(nextDirection) } // 시작점: 왼쪽 위 점 (x가 최소이면서 y도 최소인 점) const startPoint = points.reduce((min, point) => { if (point.x < min.x || (point.x === min.x && point.y < min.y)) { return point } return min }) const sortedPoints = [startPoint] const remainingPoints = points.filter((p) => p !== startPoint) let currentPoint = startPoint let currentDirection = null while (remainingPoints.length > 0) { let nextPoint = null let nextDirection = null let minDistance = Infinity // 현재 방향을 고려하여 다음 점 찾기 for (const point of remainingPoints) { const dx = point.x - currentPoint.x const dy = point.y - currentPoint.y // 직교 다각형이므로 수직 또는 수평 방향만 고려 if (Math.abs(dx) < 1e-10 && Math.abs(dy) > 1e-10) { // 수직 이동 const direction = dy > 0 ? 'down' : 'up' const distance = Math.abs(dy) if (isValidNextDirection(currentDirection, direction) && distance < minDistance) { nextPoint = point nextDirection = direction minDistance = distance } } else if (Math.abs(dy) < 1e-10 && Math.abs(dx) > 1e-10) { // 수평 이동 const direction = dx > 0 ? 'right' : 'left' const distance = Math.abs(dx) if (isValidNextDirection(currentDirection, direction) && distance < minDistance) { nextPoint = point nextDirection = direction minDistance = distance } } } if (!nextPoint) { for (const point of remainingPoints) { const dx = point.x - currentPoint.x const dy = point.y - currentPoint.y // 직교 다각형이므로 수직 또는 수평 방향만 고려 if (Math.abs(dx) < 1e-10 && Math.abs(dy) > 1e-10) { // 수직 이동 const direction = dy > 0 ? 'down' : 'up' const distance = Math.abs(dy) if (isValidNextDirection(currentDirection, direction, true) && distance < minDistance) { nextPoint = point nextDirection = direction minDistance = distance } } else if (Math.abs(dy) < 1e-10 && Math.abs(dx) > 1e-10) { // 수평 이동 const direction = dx > 0 ? 'right' : 'left' const distance = Math.abs(dx) if (isValidNextDirection(currentDirection, direction, true) && distance < minDistance) { nextPoint = point nextDirection = direction minDistance = distance } } } } if (nextPoint) { sortedPoints.push(nextPoint) remainingPoints.splice(remainingPoints.indexOf(nextPoint), 1) currentPoint = nextPoint currentDirection = nextDirection } else { // 다음 점을 찾을 수 없는 경우, 가장 가까운 점 선택 const nearestPoint = remainingPoints.reduce((nearest, point) => { const currentDist = Math.sqrt(Math.pow(point.x - currentPoint.x, 2) + Math.pow(point.y - currentPoint.y, 2)) const nearestDist = Math.sqrt(Math.pow(nearest.x - currentPoint.x, 2) + Math.pow(nearest.y - currentPoint.y, 2)) return currentDist < nearestDist ? point : nearest }) sortedPoints.push(nearestPoint) remainingPoints.splice(remainingPoints.indexOf(nearestPoint), 1) currentPoint = nearestPoint currentDirection = null } } return sortedPoints }