diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 19d43c5e..4dcbd6b7 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1679,10 +1679,11 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = + /* const offset = lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 - : lines[i % lines.length].attributes.offset + : lines[i % lines.length].attributes.offset*/ + const offset = lines[i % lines.length].attributes.offset const dx = edge.outwardNormal.x * offset const dy = edge.outwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) @@ -1717,10 +1718,12 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = + /*const offset = lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0 ? 0.1 - : lines[i % lines.length].attributes.offset + : lines[i % lines.length].attributes.offset*/ + const offset = lines[i % lines.length].attributes.offset + const dx = edge.inwardNormal.x * offset const dy = edge.inwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) @@ -1769,6 +1772,8 @@ export function useMode() { }) wall.lines = afterLine.concat(beforeLine) + wall.baseLines = wall.lines + wall.colorLines = [] //외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨. const divWallLines = [] @@ -1947,6 +1952,7 @@ export function useMode() { strokeWidth: wallStrokeWidth, selectable: false, }) + wall.colorLines.push(wallLine) canvas.add(wallLine) }) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 74e4654f..5b5c1bab 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -30,7 +30,9 @@ export const calculateAngle = (point1, point2) => { .minus(point1.y ?? 0) .toNumber() const angleInRadians = Math.atan2(deltaY, deltaX) - return angleInRadians * (180 / Math.PI) + return Big(angleInRadians * (180 / Math.PI)) + .round() + .toNumber() } function inwardEdgeNormal(vertex1, vertex2) { @@ -739,15 +741,2338 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { //Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1 const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) if (hasNonParallelLines.length > 0) { - alert('대각선이 존재합니다.') + // alert('대각선이 존재합니다.') return } - drawRidge(roof, canvas, textMode) + const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] + const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] + // + // const eaves = roof.lines.filter((line) => eavesType.includes(line.attributes?.type)) + // const gables = roof.lines.filter((line) => gableType.includes(line.attributes?.type)) + + /** + * 외벽선 + */ + const baseLines = roof.wall.baseLines + const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + + console.log('지붕 마루 갯수 확인 : ', getMaxRidge(baseLines.length)) + + /** + * baseLine을 기준으로 확인용 polygon 작성 + */ + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + + /** + * 외벽선이 시계방향인지 시계반대 방향인지 확인 + * @type {boolean} + */ + let counterClockwise = true + let signedArea = 0 + + baseLinePoints.forEach((point, index) => { + const nextPoint = baseLinePoints[(index + 1) % baseLinePoints.length] + signedArea += point.x * nextPoint.y - point.y * nextPoint.x + }) + + if (signedArea > 0) { + counterClockwise = false + } + + const drawEavesFirstLines = [] + const drawEavesSecondLines = [] + /** + * 모양을 판단하여 그린다. + */ + baseLines.forEach((currentLine, index) => { + /** + * 현재 라인이 처마유형일 경우 + */ + if (eavesType.includes(currentLine.attributes?.type)) { + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + /** + * 현재 라인이 처마인 경우 + */ + if (eavesType.includes(nextLine.attributes?.type)) { + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + /** + * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. + */ + if ( + eavesType.includes(prevLine.attributes?.type) && + eavesType.includes(nextLine.attributes?.type) && + Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) + ) { + 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) + + /** + * 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다. + * @type {{x: *, y: *}} + */ + const checkPoints = { + x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), + y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), + } + + // const checkMidLine = new fabric.Line([currentMidX, currentMidY, checkPoints.x, checkPoints.y], { + // stroke: 'blue', + // strokeWidth: 2, + // }) + // canvas.add(checkMidLine) + // canvas.renderAll() + + if (checkWallPolygon.inPolygon(checkPoints)) { + drawEavesFirstLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } else { + drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } + } else { + drawEavesSecondLines.push({ currentLine, prevLine, nextLine, size: currentLine.attributes.planeSize }) + } + } + } + }) + + drawEavesFirstLines.sort((a, b) => a.size - b.size) + + /** 추녀마루 */ + let baseHipLines = [] + /** 용마루 */ + let baseRidgeLines = [] + /** ⨆ 모양 처마에 추녀마루를 그린다. */ + drawEavesFirstLines.forEach((current) => { + const { currentLine, prevLine, nextLine } = current + let { x1, x2, y1, y2 } = currentLine + let beforePrevLine, afterNextLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ + baseLines.forEach((line, index) => { + if (line === prevLine) { + beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + if (line === nextLine) { + afterNextLine = baseLines[(index + 1) % baseLines.length] + } + }) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevLine, currentLine) + let nextVector = getHalfAngleVector(currentLine, nextLine) + + let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } + let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) + + /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ + let currentSize = Big(x2) + .minus(Big(x1)) + .plus(Big(y2).minus(Big(y1))) + .abs() + + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(10)), + y: Big(y1).plus(Big(prevHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(10)), + y: Big(y2).plus(Big(nextHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } + } + + /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ + let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() + + /** + * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 + * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. + */ + if (currentAngle === beforePrevAngle && currentAngle === afterNextAngle) { + const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) + const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) + const currentMidX = Big(x1).plus(Big(x2)).div(2) + const currentMidY = Big(y1).plus(Big(y2)).div(2) + + const midLineEdge = { + vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, + vertex2: { + x: currentMidX.plus(currentSize.times(Math.sign(xVector))).toNumber(), + y: currentMidY.plus(currentSize.times(Math.sign(yVector))).toNumber(), + }, + } + + /*const checkMidLine = new fabric.Line([midLineEdge.vertex1.x, midLineEdge.vertex1.y, midLineEdge.vertex2.x, midLineEdge.vertex2.y], { + stroke: 'blue', + strokeWidth: 2, + }) + canvas.add(checkMidLine) + canvas.renderAll()*/ + + /** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/ + baseLines + .filter((line) => line !== currentLine) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(midLineEdge, lineEdge) + /** 현재라인의 길이만큼 거리가 모자라면 해당 길이 만큼을 현재라인의 길이로 판단하고 나머지 계산을 진행한다.*/ + if (intersection && !intersection.isIntersectionOutside) { + const intersectionSize = Big(intersection.x) + .minus(Big(currentMidX)) + .plus(Big(intersection.y).minus(Big(currentMidY))) + if (intersectionSize.lt(currentSize)) { + currentSize = intersectionSize + } + } + }) + + hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() + } else { + if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) { + console.log('4각 아님') + // console.log('currentLine : ', currentLine.attributes.planeSize) + const beforePrevX1 = beforePrevLine.x1, + beforePrevY1 = beforePrevLine.y1, + beforePrevX2 = beforePrevLine.x2, + beforePrevY2 = beforePrevLine.y2 + const afterNextX1 = afterNextLine.x1, + afterNextY1 = afterNextLine.y1, + afterNextX2 = afterNextLine.x2, + afterNextY2 = afterNextLine.y2 + + /** beforePrevLine 과 afterNextLine 을 연결하는 사각형의 경우 6각으로 판단 */ + const connectBAPoint = { x1: afterNextX2, y1: afterNextY2, x2: beforePrevX1, y2: beforePrevY1 } + const isConnect = + baseLines.filter( + (line) => + line.x1 === connectBAPoint.x1 && line.y1 === connectBAPoint.y1 && line.x2 === connectBAPoint.x2 && line.y2 === connectBAPoint.y2, + ).length > 0 + + /** 6각 */ + // console.log('isConnect : ', isConnect) + if (isConnect) { + const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() + const intersectBaseLine = [] + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + const prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(checkScale)), + y: Big(y1).plus(Big(prevHipVector.y).times(checkScale)), + } + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + if (intersection && !intersection.isIntersectionOutside) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + 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) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'orange', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + 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) { + /*const intersectCircle = new fabric.Circle({ + left: intersection.intersection.x - 2, + top: intersection.intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll()*/ + + hipLength = intersection.distance + } + } else { + //라인 확인용 + /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'yellow', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkCurrentLine) + canvas.renderAll()*/ + + const rightAngleLine = baseLines + .filter( + (line) => + line !== prevLine && + line !== nextLine && + (prevAngle === calculateAngle(line.startPoint, line.endPoint) || nextAngle === calculateAngle(line.startPoint, line.endPoint)), + ) + .filter((line) => { + const index = baseLines.indexOf(line) + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + switch (prevAngle) { + case 90: + return nextAngle === -90 + case -90: + return nextAngle === 90 + case 0: + return nextAngle === 180 + case 180: + return nextAngle === 0 + } + }) + + const oppositeCurrentLine = baseLines + .filter((line) => { + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + }) + .filter((line) => { + const index = baseLines.indexOf(line) + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) + const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + switch (prevAngle) { + case 90: + return nextAngle === -90 + case -90: + return nextAngle === 90 + case 0: + return nextAngle === 180 + case 180: + return nextAngle === 0 + } + }) + + let checkHipPoints + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + checkHipPoints = [ + x1, + y1, + Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), + Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), + ] + } + + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + checkHipPoints = [ + x2, + y2, + Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), + Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), + ] + } + + /* const checkHipLine = new fabric.Line(checkHipPoints, { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkHipLine) + canvas.renderAll()*/ + + if (checkHipPoints) { + const intersectPoints = [] + rightAngleLine.forEach((line) => { + /*const checkRightAngle = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkRightAngle) + canvas.renderAll()*/ + + 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) { + /*const checkRightAngleCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkRightAngleCircle) + canvas.renderAll()*/ + intersectPoints.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(checkHipPoints[0])) + .pow(2) + .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) + .sqrt(), + }) + } + }) + + oppositeCurrentLine.forEach((line) => { + /*const checkOpposite = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'blue', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkOpposite) + canvas.renderAll() +*/ + 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) { + /*const checkOppositeCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(checkOppositeCircle) + canvas.renderAll()*/ + intersectPoints.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(checkHipPoints[0])) + .pow(2) + .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) + .sqrt(), + }) + } + }) + + // console.log('intersectPoints : ', intersectPoints) + + const intersection = intersectPoints.reduce((prev, current) => (prev.distance.lt(current.distance) ? prev : current), intersectPoints[0]) + if (intersection) { + /*const checkHipCircle = new fabric.Circle({ + left: intersection.intersection.x - 2, + top: intersection.intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkHipCircle) + canvas.renderAll()*/ + hipLength = intersection.distance.div(2) + } + } + } + } + } + + let prevHipLine, nextHipLine + /** 이전라인과의 연결지점에 추녀마루를 그린다. */ + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + const prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(1), + y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(1), + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + // console.log('intersection : ', intersection, 'prevEndPoint : ', prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()) + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) + prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) + const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) + /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ + const hipEdge = { + vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, + vertex2: prevEndPoint, + } + + /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && + Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) + ) { + /* const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + const intersectSize = prevEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints', intersectPoints) + + /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ + if (intersectPoints && intersectPoints.intersection) { + prevHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) + } + } + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + const nextEndPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(1), + y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(1), + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) + const intersection = edgesIntersection( + { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + // console.log('intersection : ', intersection, 'nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) + nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) + const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + const hipEdge = { + vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, + vertex2: nextEndPoint, + } + + /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + // console.log('intersection', intersection) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && + Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection', intersection.x, intersection.y) + + const intersectSize = nextEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints : ', intersectPoints) + + if (intersectPoints && intersectPoints.intersection) { + nextHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) + } + } + + // console.log('prevHipLine : ', prevHipLine) + // console.log('nextHipLine : ', nextHipLine) + /** 두 추녀마루가 한점에서 만나는 경우 해당 점을 기점으로 마루를 작성한다.*/ + 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) + ) { + console.log('마루 작성') + const startPoint = { x: prevHipLine.x2, y: prevHipLine.y2 } + + // 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 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) + + 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))), + }, + } + + /*const checkLine = new fabric.Line([checkEdges.vertex1.x, checkEdges.vertex1.y, checkEdges.vertex2.x, checkEdges.vertex2.y], { + stroke: 'purple', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/ + const intersectBaseLine = [] + baseLines + .filter((line) => { + /** currentAngle 의 반대 각도인 라인 */ + const angle = calculateAngle(line.startPoint, line.endPoint) + switch (currentAngle) { + case 90: + return angle === -90 + case -90: + return angle === 90 + case 0: + return angle === 180 + case 180: + return angle === 0 + } + }) + .filter((line) => { + const currentMinX = Math.min(x1, x2) + const currentMaxX = Math.max(x1, x2) + const currentMinY = Math.min(y1, y2) + const currentMaxY = Math.max(y1, y2) + const lineMinX = Math.min(line.x1, line.x2) + const lineMaxX = Math.max(line.x1, line.x2) + const lineMinY = Math.min(line.y1, line.y2) + const lineMaxY = Math.max(line.y1, line.y2) + + /** currentLine 의 안쪽에 있거나 currentLine이 line의 안쪽에 있는 라인 */ + if (Big(currentLine.y1).minus(Big(currentLine.y2)).abs().lte(1)) { + return ( + (currentMinX <= lineMinX && lineMinX <= currentMaxX) || + (currentMinX <= lineMaxX && lineMaxX <= currentMaxX) || + (lineMinX <= currentMinX && currentMinX <= lineMaxX) || + (lineMinX <= currentMaxX && currentMaxX <= lineMaxX) + ) + } else { + return ( + (currentMinY <= lineMinY && lineMinY <= currentMaxY) || + (currentMinY <= lineMaxY && lineMaxY <= currentMaxY) || + (lineMinY <= currentMinY && currentMinY <= lineMaxY) || + (lineMinY <= currentMaxY && currentMaxY <= lineMaxY) + ) + } + }) + .forEach((line, index) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkEdges, lineEdge) + if (intersection) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + intersectBaseLine.push({ intersection, line }) + } + }) + + // console.log('intersectBaseLine : ', intersectBaseLine) + + /** 맞은편 라인 */ + 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]) + + // console.log('oppositeLine : ', oppositeLine) + + /** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */ + const oppositeSize = oppositeLine + ? Big(oppositeLine.intersection.x) + .minus(Big(startPoint.x)) + .pow(2) + .plus(Big(oppositeLine.intersection.y).minus(Big(startPoint.y)).pow(2)) + .sqrt() + .minus(currentSize.div(2)) + .round(1) + : Infinity + + // console.log('startPoint : ', startPoint, 'currentSize : ', currentSize.toNumber()) + + /** 이전, 다음 라인중 길이가 짧은 길이*/ + const lineMinSize = + prevLine.attributes.planeSize < nextLine.attributes.planeSize + ? Big(prevLine.attributes.planeSize).div(10) + : Big(nextLine.attributes.planeSize).div(10) + + /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ + const ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) + + // console.log('oppositeSize : ', oppositeSize, 'lineMinSize : ', lineMinSize.toNumber(), 'ridgeSize : ', ridgeSize.toNumber()) + + if (ridgeSize.gt(0)) { + const points = [ + startPoint.x, + startPoint.y, + Big(startPoint.x).minus(ridgeSize.times(Math.sign(xVector))), + Big(startPoint.y).minus(ridgeSize.times(Math.sign(yVector))), + ] + + /** 동일 라인이 있는지 확인. */ + if ( + baseRidgeLines.filter((line) => { + const ridgeMinX = Math.min(points[0], points[2]) + const ridgeMaxX = Math.max(points[0], points[2]) + const ridgeMinY = Math.min(points[1], points[3]) + const ridgeMaxY = Math.max(points[1], points[3]) + const lineMinX = Math.min(line.x1, line.x2) + const lineMaxX = Math.max(line.x1, line.x2) + const lineMinY = Math.min(line.y1, line.y2) + const lineMaxY = Math.max(line.y1, line.y2) + + return ridgeMinX === lineMinX && ridgeMaxX === lineMaxX && ridgeMinY === lineMinY && ridgeMaxY === lineMaxY + }).length > 0 + ) { + return + } + + /* const checkMidLine = new fabric.Line(points, { + stroke: 'blue', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkMidLine) + canvas.renderAll()*/ + // console.log('points : ', points) + + /** 마루 생성 */ + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + + /** 포인트 조정*/ + baseHipLines + .filter((line) => line.line === prevHipLine || line.line === nextHipLine) + .forEach((line) => { + line.x2 = points[0] + line.y2 = points[1] + }) + prevHipLine.x2 = points[0] + prevHipLine.y2 = points[1] + nextHipLine.x2 = points[0] + nextHipLine.y2 = points[1] + prevHipLine.fire('modified') + nextHipLine.fire('modified') + } + } + }) + + /** 중복제거 */ + baseRidgeLines.forEach((ridge) => { + baseRidgeLines + .filter((ridge2) => ridge !== ridge2) + .forEach((ridge2) => { + let overlap = segmentsOverlap(ridge, ridge2) + if (overlap) { + roof.ridges = roof.ridges.filter((r) => r !== ridge && r !== ridge2) + roof.innerLines = roof.innerLines.filter((l) => l !== ridge && l !== ridge2) + roof.canvas.remove(ridge) + roof.canvas.remove(ridge2) + baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2) + + 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) + roof.ridges.push(newRidge) + roof.innerLines.push(newRidge) + baseRidgeLines.push(newRidge) + } + }) + }) + + /** ㄴ 모양 처마에 추녀마루를 그린다. */ + drawEavesSecondLines.forEach((current) => { + const { currentLine, prevLine, nextLine } = current + let { x1, x2, y1, y2 } = currentLine + let beforePrevLine, afterNextLine + + /** 이전 라인의 경사 */ + const prevDegree = prevLine.attributes.pitch > 0 ? getDegreeByChon(prevLine.attributes.pitch) : prevLine.attributes.degree + /** 다음 라인의 경사 */ + const currentDegree = currentLine.attributes.pitch > 0 ? getDegreeByChon(currentLine.attributes.pitch) : currentLine.attributes.degree + + //라인 확인용 + /*const checkCurrentLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkCurrentLine) + canvas.renderAll()*/ + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevLine, currentLine) + let nextVector = getHalfAngleVector(currentLine, nextLine) + + let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } + let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } + + /** 각 라인의 흐름 방향을 확인한다. */ + const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) + /*const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) + const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint)*/ + + /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ + let hipLength = Big(x2) + .minus(Big(x1)) + .plus(Big(y2).minus(Big(y1))) + .abs() + + // console.log('currentLine : ', currentLine.attributes.planeSize) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(10)), + y: Big(y1).plus(Big(prevHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(10)), + y: Big(y2).plus(Big(nextHipVector.y).times(10)), + } + if (!checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } + } + + let prevHipLine, nextHipLine + /** 이전라인과의 연결지점에 추녀마루를 그린다. */ + // console.log('이전라인 : ', baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) + if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { + let prevEndPoint = { + x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), + y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), + } + + const prevEndEdge = { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint } + + // console.log('prevHipVector : ', prevHipVector.x.toNumber(), prevHipVector.x.s, prevHipVector.y.toNumber(), prevHipVector.y.s) + + const intersectBaseLine = [] + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .filter((line) => { + if (currentAngle === 0 || currentAngle === 180) { + return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s + } else { + return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s + } + }) + .forEach((line) => { + // console.log('line : ', line.attributes.planeSize) + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(prevEndEdge, lineEdge) + + if (intersection && Big(intersection.x - x1).s === nextHipVector.x.s && Big(intersection.y - y1).s === nextHipVector.y.s) { + /* const circle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll()*/ + const size = Big(intersection.x - x1) + .abs() + .pow(2) + .plus( + Big(intersection.y - y1) + .pow(2) + .abs(), + ) + .sqrt() + if (size.gt(0)) { + intersectBaseLine.push({ + intersection, + size, + }) + } + } + }) + + const intersectBase = intersectBaseLine.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectBaseLine[0]) + + if (intersectBase) { + prevEndPoint = { + x: Big(x1) + .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) + .round(2), + y: Big(y1) + .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) + .round(2), + } + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + const intersection = edgesIntersection( + { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + /*const checkLine = new fabric.Line([x1, y1, prevEndPoint.x, prevEndPoint.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /* if (intersection) { + const intersectCircle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(intersectCircle) + canvas.renderAll() + }*/ + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .abs() + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2).abs()) + .sqrt(), + }) + } + }) + + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + + if (intersectRidge) { + prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) + prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) + const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) + /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ + const hipEdge = { + vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, + vertex2: prevEndPoint, + } + + /*const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && + Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'cyan', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + const intersectSize = prevEndPoint.x + .minus(Big(intersection.x)) + .abs() + .pow(2) + .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2).abs()) + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints', intersectPoints) + + /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ + if (intersectPoints && intersectPoints.intersection) { + prevHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) + } + } + /** 다음라인과의 연결지점에 추녀마루를 그린다. */ + // console.log('다음라인 : ', baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) + if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { + let nextEndPoint = { + x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), + y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), + } + + /*const nextEndLine = new fabric.Line([x2, y2, nextEndPoint.x, nextEndPoint.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(nextEndLine) + canvas.renderAll()*/ + + const nextEndEdge = { + vertex1: { x: x2, y: y2 }, + vertex2: nextEndPoint, + } + + const intersectBaseLine = [] + baseLines + .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) + .filter((line) => { + if (currentAngle === 0 || currentAngle === 180) { + return Big(line.y1).minus(Big(y1)).s === nextHipVector.y.s || Big(line.y2).minus(Big(y1)).s === nextHipVector.y.s + } else { + return Big(line.x1).minus(Big(x1)).s === nextHipVector.x.s || Big(line.x2).minus(Big(x1)).s === nextHipVector.x.s + } + }) + .forEach((line) => { + // console.log('line : ', line.attributes.planeSize) + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(nextEndEdge, lineEdge) + + if (intersection && Big(intersection.x - x2).s === nextHipVector.x.s && Big(intersection.y - y2).s === nextHipVector.y.s) { + /*console.log('next intersection ============') + const circle = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll()*/ + const size = Big(intersection.x - x2) + .abs() + .pow(2) + .plus( + Big(intersection.y - y2) + .abs() + .pow(2), + ) + .sqrt() + if (size.gt(0)) { + intersectBaseLine.push({ + intersection, + size, + }) + } + } + }) + + const intersectBase = intersectBaseLine.reduce((prev, current) => { + return prev.size.lt(current.size) ? prev : current + }, intersectBaseLine[0]) + + if (intersectBase) { + nextEndPoint = { + x: Big(x2) + .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) + .round(2), + y: Big(y2) + .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) + .round(2), + } + } + + const intersectRidgeLine = [] + baseRidgeLines.forEach((line) => { + // console.log('nextEndPoint : ', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection : ', 'x : ', line.x1, 'y : ', line.y1, 'x : ', line.x2, 'y : ', line.y2) + const intersection = edgesIntersection( + { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, + { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, + ) + + if (intersection && !intersection.isIntersectionOutside) { + intersectRidgeLine.push({ + intersection, + distance: Big(intersection.x) + .minus(Big(x1)) + .pow(2) + .plus(Big(intersection.y).minus(Big(y1)).pow(2)) + .sqrt(), + }) + } + }) + const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) + // console.log('intersectRidge : ', intersectRidge) + + if (intersectRidge) { + nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) + nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) + } + + const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) + const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) + let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() + scale = scale.eq(0) ? Big(1) : scale + + const hipEdge = { + vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, + vertex2: nextEndPoint, + } + + /* const checkLine = new fabric.Line([hipEdge.vertex1.x, hipEdge.vertex1.y, hipEdge.vertex2.x, hipEdge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let intersectPoints = [] + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(hipEdge, lineEdge) + // console.log('intersection', intersection) + if ( + intersection && + !intersection.isIntersectionOutside && + Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && + Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) + ) { + /*const intersectPoint = new fabric.Circle({ + left: intersection.x - 2, + top: intersection.y - 2, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(intersectPoint) + canvas.renderAll()*/ + + // console.log('nextEndPoint', nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()) + // console.log('intersection', intersection.x, intersection.y) + + const intersectSize = nextEndPoint.x + .minus(Big(intersection.x)) + .pow(2) + .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) + .abs() + .sqrt() + .toNumber() + + intersectPoints.push({ + intersection, + size: intersectSize, + }) + } + }) + + intersectPoints = intersectPoints.reduce((prev, current) => { + return prev.size < current.size ? prev : current + }, intersectPoints[0]) + + // console.log('intersectPoints : ', intersectPoints) + + if (intersectPoints && intersectPoints.intersection) { + nextHipLine = drawHipLine( + [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], + canvas, + roof, + textMode, + null, + prevDegree, + currentDegree, + ) + baseHipLines.push({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber(), line: nextHipLine }) + } + } + }) + + // console.log('baseHipLines : ', baseHipLines) + // console.log('baseRidgeLines : ', baseRidgeLines) + /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ + /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ + let unFinishedRidge = [] + baseRidgeLines.forEach((current) => { + /*const checkLine = new fabric.Line([current.x1, current.y1, current.x2, current.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + let checkPoint = [ + { x: current.x1, y: current.y1, line: current, cnt: 0 }, + { x: current.x2, y: current.y2, line: current, cnt: 0 }, + ] + baseHipLines.forEach((line) => { + /*const checkPoint1 = new fabric.Circle({ + left: line.x1, + top: line.y1, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + const checkPoint2 = new fabric.Circle({ + left: line.x2, + top: line.y2, + radius: 4, + fill: 'blue', + }) + canvas.add(checkPoint1) + canvas.add(checkPoint2) + canvas.renderAll()*/ + // console.log('current : ', current.x1, current.y1, current.x2, current.y2) + // console.log('line : ', line.x1, line.y1, line.x2, line.y2) + + 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 + } + }) + // console.log('checkPoint : ', checkPoint) + if (checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) { + unFinishedRidge.push(checkPoint[0]) + } + if (checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) { + unFinishedRidge.push(checkPoint[1]) + } + }) + // console.log('unFinishedRidge : ', unFinishedRidge) + //포인트 확인 + /*unFinishedRidge.forEach((current) => { + const checkCircle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll() + })*/ + + /** 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) + // console.log('unFinishedPoint : ', unFinishedPoint) + + /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ + /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ + let degreeAllLine = [] + baseLines.forEach((line) => { + const pitch = line.attributes.pitch + const degree = line.attributes.degree + // console.log('pitch : ', pitch, 'degree : ', degree) + degreeAllLine.push(pitch > 0 ? getDegreeByChon(pitch) : degree) + }) + + let currentDegree, prevDegree + degreeAllLine = [...new Set(degreeAllLine)] + // console.log('degreeAllLine : ', degreeAllLine) + currentDegree = degreeAllLine[0] + if (degreeAllLine.length === 1) { + prevDegree = currentDegree + } + + /*unFinishedPoint.forEach((current) => { + const circle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll() + })*/ + + /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ + unFinishedRidge.forEach((current) => { + /*const checkLine = new fabric.Line([current.line.x1, current.line.y1, current.line.x2, current.line.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + /*const checkCircle = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'green', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll()*/ + + 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) => { + /*console.log('point : ', point.x, point.y) + const circle = new fabric.Circle({ + left: point.x, + top: point.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(circle) + canvas.renderAll() + + const checkLine = new fabric.Line([current.x, current.y, point.x, point.y], { + stroke: 'green', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + 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(), + }) + } + }) + // console.log('checkPoints : ', checkPoints) + // console.log('current ridge: ', current) + /*const checkCurrent = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'blue', + parentId: roof.id, + }) + canvas.add(checkCurrent) + canvas.renderAll()*/ + if (checkPoints.length > 0) { + /*checkPoints.forEach((point) => { + const checkPoints = new fabric.Circle({ + left: point.point.x, + top: point.point.y, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkPoints) + canvas.renderAll() + })*/ + + checkPoints.sort((a, b) => a.size - b.size) + // console.log('Big(2).minus(Big(current.cnt) : ', Big(2).minus(Big(current.cnt)).toNumber(), current.cnt) + const maxCnt = Big(2).minus(Big(current.cnt)).toNumber() + for (let i = 0; i < maxCnt; i++) { + const checkPoint = checkPoints[i] + // console.log('current : ', current.line.attributes.planeSize) + // console.log('1. checkPoint : ', checkPoint) + if (checkPoint) { + let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y] + let hipBasePoint + + baseHipLines.forEach((line) => { + const checkAngel1 = calculateAngle({ x: point[0], y: point[1] }, { x: point[2], y: point[3] }) + const checkAngel2 = calculateAngle({ x: line.line.x1, y: line.line.y1 }, { x: line.line.x2, y: line.line.y2 }) + + const isConnectLine = + ((line.line.x1 === point[0] && line.line.y1 === point[1]) || (line.line.x2 === point[0] && line.line.y2 === point[1])) && + checkAngel1 === checkAngel2 + const isOverlap = segmentsOverlap(line.line, { x1: point[0], y1: point[1], x2: point[2], y2: point[3] }) + const isSameLine = + (point[0] === line.x2 && point[1] === line.y2 && point[2] === line.x1 && point[3] === line.y1) || + (point[0] === line.x1 && point[1] === line.y1 && point[2] === line.x2 && point[3] === line.y2) + + if (isConnectLine || isOverlap || isSameLine) { + /** 겹치는 추녀마루와 하나의 선으로 변경*/ + const mergePoint = [ + { x: point[0], y: point[1] }, + { x: point[2], y: point[3] }, + { x: line.line.x1, y: line.line.y1 }, + { x: line.line.x2, y: line.line.y2 }, + ] + /** baseHipLines도 조정*/ + /*const mergeBasePoint = [ + { x: point[0], y: point[1] }, + { x: point[2], y: point[3] }, + { x: line.x1, y: line.y1 }, + { x: line.x2, y: line.y2 }, + ]*/ + mergePoint.sort((a, b) => a.x - b.x) + // mergeBasePoint.sort((a, b) => a.x - b.x) + /*const checkLine = new fabric.Line([mergeBasePoint[0].x, mergeBasePoint[0].y, mergeBasePoint[3].x, mergeBasePoint[3].y], { + stroke: 'red', + strokeWidth: 4, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } + // line.x1 = mergeBasePoint[0].x + // line.y1 = mergeBasePoint[0].y + // line.x2 = mergeBasePoint[3].x + // line.y2 = mergeBasePoint[3].y + point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] + 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) { + // console.log('no checkPoints :', current) + + 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 = 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).div(10) + } + } + + 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)) + basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize) + hipSize = Big(basePoints[0].line.attributes.planeSize).div(10) + } + + hipSize = hipSize.times(hipSize).div(2).sqrt().round().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의 각 좌표와 교차하는 경우로 한정*/ + 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) + /** 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) + } + intersectPoints.forEach((is) => { + const points = [current.x, current.y, is.x, is.y] + const hipLine = drawHipLine(points, 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) => { + 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, + ) + + noRidgeHipPoints.forEach((current) => { + const orthogonalPoints = noRidgeHipPoints.filter( + (point) => point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2)), + ) + + /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ + if (orthogonalPoints.length > 0) { + const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] + const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + /** array에서 object 삭제*/ + noRidgeHipPoints = noRidgeHipPoints.filter((point) => point !== current && point !== orthogonalPoints[0]) + } + }) + + /** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */ + const checkEdgeLines = [] + noRidgeHipPoints.forEach((current) => { + /*const checkCircle = new fabric.Circle({ + left: current.x2, + top: current.y2, + radius: 4, + fill: 'red', + parentId: roof.id, + }) + canvas.add(checkCircle) + canvas.renderAll()*/ + 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 } }, + ) + }) + }) + }) + // 라인 확인용 + /*checkEdgeLines.forEach((edge) => { + const checkLine = new fabric.Line([edge.vertex1.x, edge.vertex1.y, edge.vertex2.x, edge.vertex2.y], { + stroke: 'yellow', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll() + })*/ + + /** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */ + 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 checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll()*/ + + 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 } }) + const intersection = [] + 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) + intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) + }) + + intersectPoints.forEach((intersection) => { + const intersectPoint = intersection.intersection + noRidgeHipPoints.forEach((hipPoint) => { + const angle = calculateAngle({ x: intersectPoint.x, y: intersectPoint.y }, { x: hipPoint.x2, y: hipPoint.y2 }) + if (angle === 0 || angle === 180 || angle === 90 || angle === -90) { + const ridgeLine = drawRidgeLine([intersectPoint.x, intersectPoint.y, hipPoint.x2, hipPoint.y2], canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] + baseHipLine.x2 = intersectPoint.x + baseHipLines.y2 = intersectPoint.y + intersection.line.x2 = intersectPoint.x + intersection.line.y2 = intersectPoint.y + /** 보조선 라인 조정*/ + const hipLine = intersection.line.line + /** 평면길이 */ + const planeSize = calcLinePlaneSize({ + x1: hipLine.x1, + y1: hipLine.y1, + x2: intersectPoint.x, + y2: intersectPoint.y, + }) + /** 실제길이 */ + const actualSize = + prevDegree === currentDegree + ? calcLineActualSize( + { + x1: hipLine.x1, + y1: hipLine.y1, + x2: intersectPoint.x, + y2: intersectPoint.y, + }, + currentDegree, + ) + : 0 + hipLine.set({ x2: intersectPoint.x, y2: intersectPoint.y, attributes: { roofId: roof.id, planeSize, actualSize } }) + hipLine.fire('modified') + intersectPoints = intersectPoints.filter((isp) => isp !== intersection) + noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) + } + }) + }) + + console.log('baseRidgeLines :', baseRidgeLines) + + const ridgeAllPoints = [] + baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) + + console.log('ridgeAllPoints :', ridgeAllPoints) + + console.log('지붕 마루 갯수 확인 : ', baseRidgeLines.length, getMaxRidge(baseLines.length)) + + ridgeAllPoints.forEach((current) => { + console.log('current :', current) + + const ridgeLines = [], + hipLines = [] + + ridgeAllPoints + .filter((point) => point !== current) + .forEach((point) => { + /*canvas + .getObjects() + .filter((obj) => obj.name === 'checkPoint' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + const checkPoint = new fabric.Circle({ + left: current.x, + top: current.y, + radius: 4, + fill: 'blue', + parentId: roof.id, + name: 'checkPoint', + }) + canvas.add(checkPoint) + canvas.renderAll()*/ + let checkRidgeLine, checkHipLine + /** 직선인 경우 마루 확인*/ + if ( + baseRidgeLines.length < 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확인*/ + if ( + Big(point.x) + .minus(Big(current.x)) + .abs() + .eq(Big(point.y).minus(Big(current.y)).abs()) + ) { + 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) { + const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) + baseRidgeLines.push(ridgeLine) + } + + // checkLine1 = { x1: checkRidgeLine.x1, y1: checkRidgeLine.y1, x2: checkRidgeLine.x2, y2: checkRidgeLine.y2 } + // checkLine2 = { x1: checkRidgeLine.x2, y1: checkRidgeLine.y2, x2: checkRidgeLine.x1, y2: checkRidgeLine.y1 } + } + 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) { + 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 missingCount = Big(baseRidgeLines.length) + .minus(connectRidgePoint.length + 1) + .toNumber() + + console.log('missingCount :', missingCount)*/ + /*baseRidgeLines.forEach((current) => { + baseRidgeLines + .filter((ridge) => ridge !== current) + .forEach((ridge) => { + /!** 직선인 경우 확인 *!/ + let checkLineEdge + if (current.x1 === ridge.x1 || current.y1 === ridge.y1) { + checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x1, y: ridge.y1 } } + } else if (current.x2 === ridge.x1 || current.y2 === ridge.y1) { + checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x1, y: ridge.y1 } } + } else if (current.x1 === ridge.x2 || current.y1 === ridge.y2) { + checkLineEdge = { vertex1: { x: current.x1, y: current.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + } else if (current.x2 === ridge.x2 || current.y2 === ridge.y2) { + checkLineEdge = { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: ridge.x2, y: ridge.y2 } } + } + + console.log('checkLineEdge :', checkLineEdge) + if (checkLineEdge) { + let hasIntersectLine = false + /!** 외벽선을 통과하는지 확인*!/ + baseLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkLineEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + hasIntersectLine = true + } + }) + + /!** hipLines를 통과하는지 확인*!/ + baseHipLines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersection = edgesIntersection(checkLineEdge, lineEdge) + if (intersection && !intersection.isIntersectionOutside) { + hasIntersectLine = true + } + }) + + // if (!hasIntersectLine) { + const checkLine = new fabric.Line([checkLineEdge.vertex1.x, checkLineEdge.vertex1.y, checkLineEdge.vertex2.x, checkLineEdge.vertex2.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + }) + canvas.add(checkLine) + canvas.renderAll() + // } + } + }) + })*/ + + /*drawRidge(roof, canvas, textMode) drawHips(roof, canvas, textMode) connectLinePoint(roof, canvas, textMode) modifyRidge(roof, canvas, textMode) - drawCenterLine(roof, canvas, textMode) + drawCenterLine(roof, canvas, textMode)*/ +} + +/** + * 추녀 마루를 그린다. + * @param points + * @param canvas + * @param roof + * @param textMode + * @param currentRoof + * @param prevDegree + * @param currentDegree + */ +const drawHipLine = (points, canvas, roof, textMode, currentRoof, prevDegree, currentDegree) => { + // console.log('drawHipLine : ', points) + 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) + canvas.renderAll() + return hip +} + +/** + * 마루를 그린다. + * @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) + canvas.renderAll() + + return ridge +} + +/** + * 벡터를 정규화(Normalization)하는 함수 + * @param v + * @returns {{x: *, y: *}|{x: number, y: number}} + */ +const normalizeVector = (v) => { + /** 벡터의 크기(길이)*/ + const magnitude = Big(v.x).pow(2).plus(Big(v.y).pow(2)).sqrt() + if (magnitude.eq(0)) return { x: 0, y: 0 } // 크기가 0일 경우 (예외 처리) + return { x: Big(v.x).div(magnitude).toNumber(), y: Big(v.y).div(magnitude).toNumber() } +} + +/** + * 사잇각의 절반 방향 벡터 계산 함수 + * @param line1 + * @param line2 + * @returns {{x: *, y: *}|{x: number, y: number}} + */ +const getHalfAngleVector = (line1, line2) => { + const v1 = { x: Big(line1.x2).minus(Big(line1.x1)).toNumber(), y: Big(line1.y1).minus(Big(line1.y2)).toNumber() } + const v2 = { x: Big(line2.x2).minus(Big(line2.x1)).toNumber(), y: Big(line2.y1).minus(Big(line2.y2)).toNumber() } + + /** + * 벡터 정규화 + * @type {{x: *, y: *}|{x: number, y: number}} + */ + const unitV1 = normalizeVector(v1) // 첫 번째 벡터를 정규화 + const unitV2 = normalizeVector(v2) // 두 번째 벡터를 정규화 + + /** + * 두 벡터를 더합니다 + * @type {{x: *, y: *}} + */ + const summedVector = { x: Big(unitV1.x).plus(Big(unitV2.x)).toNumber(), y: Big(unitV1.y).plus(Big(unitV2.y)).toNumber() } + + /** 결과 벡터를 정규화하여 사잇각 벡터를 반환합니다 */ + return normalizeVector(summedVector) } /** @@ -757,7 +3082,7 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { * @param canvas * @param textMode */ -const drawRidge = (roof, canvas, textMode) => { +const drawRidges = (roof, canvas, textMode) => { console.log('roof.lines : ', roof.lines) const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인 const roofLines = roof.lines // 지붕의 라인