From e35cacf5200f3a1db7ead244ef0c602ebc0d6cbb Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Wed, 27 Aug 2025 10:02:53 +0900 Subject: [PATCH] =?UTF-8?q?a,b=20=ED=8C=A8=ED=84=B4=20=EC=A7=80=EB=B6=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 용마루 생성 끝, 지붕면 생성 중 --- src/components/fabric/QPolygon.js | 3 +- src/util/qpolygon-utils.js | 575 +++++++++++++++++++++++++++++- 2 files changed, 574 insertions(+), 4 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index c7628088..c049a541 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,7 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' -import { calculateAngle, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' +import { calculateAngle, drawGableRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' @@ -330,6 +330,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { } else if (isGableRoof(types)) { // A형, B형 박공 지붕 console.log('패턴 지붕') + drawGableRoof(this.id, this.canvas, textMode) } else if (isShedRoof(types, this.lines)) { console.log('한쪽흐름 지붕') drawShedRoof(this.id, this.canvas, textMode) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ba451009..816f4447 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -495,9 +495,7 @@ export const isSamePoint = (a, b) => { * @param canvas * @param textMode */ -export const drawEavesRoof = (roofId, canvas, textMode) => { - -} +export const drawEavesRoof = (roofId, canvas, textMode) => {} /** * 박공지붕(A,B 패턴) @@ -506,7 +504,578 @@ export const drawEavesRoof = (roofId, canvas, textMode) => { * @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 })) + + /** baseLine을 기준으로 확인용 polygon 작성 */ + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + + const eavesLines = baseLines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) + const gableLines = baseLines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.GABLE) + + 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)).toNumber() + const edgeDy = 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)).toNumber() + const intersectDy = 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 }) + } + } + }) + + let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect)) + if (!currentRoof) { + currentRoof = intersectRoofLines.sort((a, b) => a.length - b.length)[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, + } + } + + 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 currentLine + * @returns {*} + */ + const findCurrentRoof = (currentLine) => { + const analyze = analyzeEavesLine(currentLine) + const originPoint = currentLine.attributes.originPoint + const midX = (originPoint.x1 + originPoint.x2) / 2 + const midY = (originPoint.y1 + originPoint.y2) / 2 + const offset = currentLine.attributes.offset + + let currentRoof + let roofFindVector = { x: 0, y: 0 } + const checkRoofLines = roof.lines.filter((roof) => { + const dx = Big(roof.x2).minus(Big(roof.x1)).toNumber() + const dy = Big(roof.y2).minus(Big(roof.y1)).toNumber() + const length = Math.sqrt(dx * dx + dy * dy) + const directionVector = { x: dx / length, y: dy / length } + return analyze.directionVector.x === directionVector.x && analyze.directionVector.y === directionVector.y + }) + + if (analyze.isHorizontal) { + const checkPoint = { x: midX, y: midY + offset } + if (wall.inPolygon(checkPoint)) { + roofFindVector = { x: 0, y: -1 } + } else { + roofFindVector = { x: 0, y: 1 } + } + } + if (analyze.isVertical) { + const checkPoint = { x: midX + offset, y: midY } + if (wall.inPolygon(checkPoint)) { + roofFindVector = { x: -1, y: 0 } + } else { + roofFindVector = { x: 1, y: 0 } + } + } + const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofFindVector.x * offset, y: midY + roofFindVector.y * offset } } + const edgeDx = Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber() + const edgeDy = 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)).toNumber() + const intersectDy = 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 }) + } + } + }) + + currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect)) + if (!currentRoof) { + currentRoof = intersectRoofLines.sort((a, b) => a.length - b.length)[0] + } + + return currentRoof + } + + /** + * 지점 사이의 길이와 지점별 각도에 따라서 교점까지의 길이를 구한다. + * @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 } + } + + 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 checkLine = new fabric.Line([analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + const overlapLines = [] + backwardLines.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 = (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 }) + } + } + ridgeLines.push(drawRidgeLine([ridgePoint[0].x, ridgePoint[0].y, ridgePoint[1].x, ridgePoint[1].y], canvas, roof, textMode)) + }) + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + }) + + /** + * 지붕면을 그린다. + * @param currentLine + */ + const drawRoofPlane = (currentLine) => { + const analyze = currentLine.analyze + const checkLine = new fabric.Line([analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + const innerRidgeLines = [] + if (analyze.isHorizontal) { + ridgeLines + .filter((ridgeLine) => { + const tolerance = 1 + const dx = Big(ridgeLine.x2).minus(Big(ridgeLine.x1)).toNumber() + const dy = Big(ridgeLine.y2).minus(Big(ridgeLine.y1)).toNumber() + const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 + return normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance + }) + .filter((ridgeLine) => { + const minX = Math.min(analyze.startPoint.x, analyze.endPoint.x) + const maxX = Math.max(analyze.startPoint.x, analyze.endPoint.x) + const ridgeLineX = (ridgeLine.x1 + ridgeLine.x2) / 2 + return ridgeLineX >= minX && ridgeLineX <= maxX + }) + .forEach((ridgeLine) => innerRidgeLines.push(ridgeLine)) + } + if (analyze.isVertical) { + ridgeLines + .filter((ridgeLine) => { + const tolerance = 1 + const dx = Big(ridgeLine.x2).minus(Big(ridgeLine.x1)).toNumber() + const dy = Big(ridgeLine.y2).minus(Big(ridgeLine.y1)).toNumber() + const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 + return Math.abs(normalizedAngle - 90) <= tolerance + }) + .filter((ridgeLine) => { + const minY = Math.min(analyze.startPoint.y, analyze.endPoint.y) + const maxY = Math.max(analyze.startPoint.y, analyze.endPoint.y) + const ridgeLineY = (ridgeLine.y1 + ridgeLine.y2) / 2 + return ridgeLineY >= minY && ridgeLineY <= maxY + }) + .forEach((ridgeLine) => innerRidgeLines.push(ridgeLine)) + } + + innerRidgeLines.forEach((ridgeLine) => { + const checkLine = new fabric.Line([ridgeLine.x1, ridgeLine.y1, ridgeLine.x2, ridgeLine.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + }) + + if (innerRidgeLines.length === 1) { + const innerRidgeLine = innerRidgeLines[0] + if (analyze.isHorizontal) { + } + if (analyze.isVertical) { + } + } + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + } + + forwardLines.forEach((forward) => { + const analyze = forward.analyze + drawRoofPlane(forward) + }) + backwardLines.forEach((backward) => { + const analyze = backward.analyze + drawRoofPlane(backward) + }) + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() } /**