From d25ab38f11e835271868b7099e09411d2798936a Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 31 Dec 2025 10:05:51 +0900 Subject: [PATCH 01/55] =?UTF-8?q?getAddLine(newPStart,=20newPEnd,=20'red')?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20-=20=EB=8F=99=EC=8B=9C=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 48aa68da..49cd1a7c 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -890,6 +890,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } + + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -919,6 +921,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'left_out') { console.log('left_out::::isStartEnd:::::', isStartEnd) @@ -977,6 +980,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1037,6 +1041,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } findPoints.push({ y: newPStart.y, x: newPEnd.x, position: 'left_out_end' }) + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'right_in') { if (isStartEnd.start) { @@ -1065,6 +1070,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1094,6 +1100,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'right_out') { console.log('right_out::::isStartEnd:::::', isStartEnd) @@ -1153,6 +1160,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1208,6 +1216,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } } } @@ -1258,6 +1267,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') } //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1286,6 +1296,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } //getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'top_out') { console.log('top_out isStartEnd:::::::', isStartEnd) @@ -1344,6 +1355,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() @@ -1400,6 +1412,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'bottom_in') { if (isStartEnd.start) { @@ -1427,6 +1440,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') } getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1455,6 +1469,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') } getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') + getAddLine(newPStart, newPEnd, 'red') } } else if (condition === 'bottom_out') { console.log('bottom_out isStartEnd:::::::', isStartEnd) @@ -1513,6 +1528,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } if (isStartEnd.end) { @@ -1570,12 +1586,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } + getAddLine(newPStart, newPEnd, 'red') } } } } - getAddLine(newPStart, newPEnd, 'red') + //getAddLine(newPStart, newPEnd, 'red') //canvas.remove(roofLine) } else { getAddLine(roofLine.startPoint, roofLine.endPoint) From f0e70ec2df8fe8fdf74859b0556b9a69a14e0e76 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 10:42:00 +0900 Subject: [PATCH 02/55] =?UTF-8?q?=EB=8B=A8=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B3=B5=EC=82=AC=EB=A1=9C=20=ED=91=9C=EA=B8=B0?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/modal/module/PanelEdit.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/floor-plan/modal/module/PanelEdit.jsx b/src/components/floor-plan/modal/module/PanelEdit.jsx index 97b02112..ca809f0f 100644 --- a/src/components/floor-plan/modal/module/PanelEdit.jsx +++ b/src/components/floor-plan/modal/module/PanelEdit.jsx @@ -104,14 +104,16 @@ export default function PanelEdit(props) { closePopup(id)} />
{getMessage( - [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE].includes(type) + [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE, PANEL_EDIT_TYPE, , PANEL_EDIT_TYPE.ROW_MOVE].includes(type) ? 'modal.move.setting.info' : 'modal.copy.setting.info', )} From 9e867ba53a2263234b2ace9ea23eac5675ad45af Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 10:42:46 +0900 Subject: [PATCH 03/55] =?UTF-8?q?=EB=8B=A8=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B3=B5=EC=82=AC=EB=A1=9C=20=ED=91=9C=EA=B8=B0?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/modal/module/PanelEdit.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/floor-plan/modal/module/PanelEdit.jsx b/src/components/floor-plan/modal/module/PanelEdit.jsx index ca809f0f..baf4b101 100644 --- a/src/components/floor-plan/modal/module/PanelEdit.jsx +++ b/src/components/floor-plan/modal/module/PanelEdit.jsx @@ -113,7 +113,7 @@ export default function PanelEdit(props) {
{getMessage( - [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE, PANEL_EDIT_TYPE, , PANEL_EDIT_TYPE.ROW_MOVE].includes(type) + [PANEL_EDIT_TYPE.MOVE, PANEL_EDIT_TYPE.MOVE_ALL, PANEL_EDIT_TYPE.COLUMN_MOVE, PANEL_EDIT_TYPE.ROW_MOVE].includes(type) ? 'modal.move.setting.info' : 'modal.copy.setting.info', )} From 5bbf372e47b95f8feb17352089066599f0c24c19 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Wed, 31 Dec 2025 13:19:12 +0900 Subject: [PATCH 04/55] =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=B3=B4=EC=A1=B0?= =?UTF-8?q?=EC=84=A0=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 163 +++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 58857ee2..82ad62df 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2690,13 +2690,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let proceedRidges = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이 let hipLines = [] ridgeEaves.forEach((currentLine) => { - const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine).renderAll() + /*const checkLine = createArrow(currentLine, 'blue', roofId) + canvas.add(checkLine).renderAll()*/ let prevLine, nextLine, currentI, prevI, nextI baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { @@ -2741,7 +2736,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const prevCheckPoint = [currentLine.x1, currentLine.y1, currentLine.x1 + pHipVector.x * 1000, currentLine.y1 + pHipVector.y * 1000] const nextCheckPoint = [currentLine.x2, currentLine.y2, currentLine.x2 + nHipVector.x * 1000, currentLine.y2 + nHipVector.y * 1000] - /* const checkLine1 = new fabric.Line(prevCheckPoint, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) + /*const checkLine1 = new fabric.Line(prevCheckPoint, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) const checkLine2 = new fabric.Line(nextCheckPoint, { stroke: 'green', strokeWidth: 4, parentID: roofId, name: 'check' }) canvas.add(checkLine1, checkLine2).renderAll()*/ @@ -2807,7 +2802,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let minPrevDist = Infinity let minNextDist = Infinity proceedRidges.forEach((ridge) => { - console.log('ridge : ', ridge) const ridgeEdge = { vertex1: { x: ridge.point.x1, y: ridge.point.y1 }, vertex2: { x: ridge.point.x2, y: ridge.point.y2 } } const isPrev = edgesIntersection(ridgeEdge, prevEdge) const isNext = edgesIntersection(ridgeEdge, nextEdge) @@ -3001,31 +2995,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (!alreadyRidge) { proceedRidges.push({ prev: prevI, next: nextI, point: ridgePoint }) } - - /*const checkRidgeStart = new fabric.Circle({ left: ridgePoint.x1, top: ridgePoint.y1, radius: 4, parentId: roofId, name: 'check' }) - const checkRidgeLine = new fabric.Line([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], { - stroke: 'yellow', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkRidgeLine, checkRidgeStart).renderAll()*/ } - - /*const checkLine1 = new fabric.Line([prevHipPoint.x1, prevHipPoint.y1, prevHipPoint.x2, prevHipPoint.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - const checkLine2 = new fabric.Line([nextHipPoint.x1, nextHipPoint.y1, nextHipPoint.x2, nextHipPoint.y2], { - stroke: 'blue', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine1, checkLine2).renderAll() -*/ canvas .getObjects() .filter((o) => o.name === 'check') @@ -3033,7 +3003,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { canvas.renderAll() }) - console.log('hipedEaves', hipedEaves) hipedEaves.forEach((currentLine) => { let prevLine baseLines.forEach((baseLine, index) => { @@ -3155,7 +3124,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { innerLines.push(drawHipLine([point.x1, point.y1, point.x2, point.y2], canvas, roof, textMode, currentDegree, currentDegree)) pIndexEaves.push(index) } else if (jointEaves.length === 2) { - console.log('jointEaves : ', jointEaves) const jointIndex = [index] let jointLines = [] jointEaves.forEach((e) => jointIndex.push(proceedEaves.findIndex((p) => p === e))) @@ -3172,11 +3140,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //연결된 지점에서 파생된 마루선 제거 const removeRidge = proceedRidges.filter((ridge) => almostEqual(ridge.point.x1, eaves.point.x2) && almostEqual(ridge.point.y1, eaves.point.y2)) proceedRidges = proceedRidges.filter((ridge) => !removeRidge.includes(ridge)) - console.log('pIndexEaves : ', pIndexEaves) - console.log('jointLines : ', jointLines) - console.log('jointVectors : ', jointVectors) let dneVector = jointVectors.find((v) => !jointVectors.find((v2) => v2.x === -v.x && v2.y === -v.y)) - console.log('dneVector : ', dneVector) const findRoofEdge = { vertex1: { x: eaves.point.x2, y: eaves.point.y2 }, vertex2: { x: eaves.point.x2 + dneVector.x, y: eaves.point.y2 + dneVector.y }, @@ -3201,7 +3165,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) const uniqueLine = jointLines.filter((value) => countMap.get(value) === 1) - console.log('uniqueLine : ', uniqueLine) linesAnalysis.push({ start: { x: eaves.point.x2, y: eaves.point.y2 }, end: { x: isPoint.x, y: isPoint.y }, @@ -3211,7 +3174,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { degree: currentDegree, }) } - console.log('=============') } else { linesAnalysis.push({ start: { x: point.x1, y: point.y1 }, @@ -3235,9 +3197,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }), ) - console.log('proceedEaves :', proceedEaves) - console.log('proceedRidges :', proceedRidges) - //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다. jerkinHeads.forEach((currentLine) => { let prevLine, nextLine @@ -3748,8 +3707,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { parentId: roofId, name: 'check', }) - canvas.add(checkLine, checkLine2).renderAll() -*/ + canvas.add(checkLine, checkLine2).renderAll()*/ //기준지붕선의 반대쪽선 const oppositeLine = [] @@ -3807,7 +3765,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } }) - console.log('oppositeLine', oppositeLine) + if (oppositeLine.length > 0) { const ridgePoints = [] //지붕선 출발 지점 확인을 위한 기준 포인트 @@ -4190,9 +4148,27 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2)) if (ridgeLength > EPSILON) { + //마루 포인트 중 1개만 지붕선에 붙어있을경우 해당 포인트를 시작점으로 한다. + const isPointOnRoof1 = roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined + const isPointOnRoof2 = roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined + + let startPoint, endPoint + if ((isPointOnRoof1 && isPointOnRoof2) || (!isPointOnRoof1 && !isPointOnRoof2)) { + startPoint = { x: point[0], y: point[1] } + endPoint = { x: point[2], y: point[3] } + } else { + if (isPointOnRoof1) { + startPoint = { x: point[0], y: point[1] } + endPoint = { x: point[2], y: point[3] } + } + if (isPointOnRoof2) { + startPoint = { x: point[2], y: point[3] } + endPoint = { x: point[0], y: point[1] } + } + } linesAnalysis.push({ - start: { x: point[0], y: point[1] }, - end: { x: point[2], y: point[3] }, + start: startPoint, + end: endPoint, left: baseLines.findIndex((line) => line === r.left), right: baseLines.findIndex((line) => line === r.right), type: TYPES.RIDGE, @@ -4305,13 +4281,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { iterations++ /*linesAnalysis.forEach((line) => { - const point = [line.start.x, line.start.y, line.end.x, line.end.y] - const checkLine = new fabric.Line(point, { - stroke: 'red', - strokeWidth: 2, - parentId: roofId, - name: 'check', - }) + const point = { x1: line.start.x, y1: line.start.y, x2: line.end.x, y2: line.end.y } + const checkLine = createArrow(point, 'red', roofId) canvas.add(checkLine).renderAll() // canvas.remove(checkLine).renderAll() })*/ @@ -4521,22 +4492,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine gableLine.connectCnt++ - /*const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - const checkCircle = new fabric.Circle({ - left: intersect.x, - top: intersect.y, - radius: 5, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine, checkCircle) - canvas.renderAll()*/ - const uniqueBaseLines = [...new Set(relationBaseLines)] // 연결점에서 새로운 가선분을 생성 if (uniqueBaseLines.length > 2) { @@ -4600,9 +4555,25 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let bisector console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) if (isParallel(baseLine1, baseLine2)) { + let cPoint = [cLine.start.x, cLine.start.y, cLine.end.x, cLine.end.y] + let pPoint = [pLine.start.x, pLine.start.y, pLine.end.x, pLine.end.y] + const length1 = Math.sqrt(Math.pow(intersect.x - cPoint[2], 2) + Math.pow(intersect.y - cPoint[3], 2)) + const length2 = Math.sqrt(Math.pow(intersect.x - pPoint[2], 2) + Math.pow(intersect.y - pPoint[3], 2)) + + //교점 밖으로 뻗어나가는 선의 길이가 다를 경우 이등분선 계산이 틀어져서 조정 + if (!almostEqual(length1, length2)) { + const vector1 = { x: Math.sign(clamp01(cPoint[2] - cPoint[0])), y: Math.sign(clamp01(cPoint[3] - cPoint[1])) } + const vector2 = { x: Math.sign(clamp01(pPoint[2] - pPoint[0])), y: Math.sign(clamp01(pPoint[3] - pPoint[1])) } + const addLength = 100 + cPoint[2] = intersect.x + vector1.x * addLength + cPoint[3] = intersect.y + vector1.y * addLength + pPoint[2] = intersect.x + vector2.x * addLength + pPoint[3] = intersect.y + vector2.y * addLength + } + bisector = getBisectLines( - { x1: cLine.start.x, x2: cLine.end.x, y1: cLine.start.y, y2: cLine.end.y }, - { x1: pLine.start.x, y1: pLine.start.y, x2: pLine.end.x, y2: pLine.end.y }, + { x1: cPoint[0], y1: cPoint[1], x2: cPoint[2], y2: cPoint[3] }, + { x1: pPoint[0], y1: pPoint[1], x2: pPoint[2], y2: pPoint[3] }, ) } else { bisector = getBisectBaseLines(baseLine1, baseLine2, intersect, canvas) @@ -4868,8 +4839,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y && baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && - prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE && - nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE + (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) ) { downRoofGable.push({ currLine: baseLine, currIndex: index, type: 'A' }) } @@ -4954,6 +4924,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } + if (gableLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) return + //기준점 let stdPoint = [] //반대쪽 라인을 찾기위한 vector @@ -5493,7 +5465,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - // canvas.renderAll() //추가된 하단 지붕 라인 innerLines에 추가. innerLines.push(...downRoofLines) @@ -5563,7 +5534,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - console.log('innerLines : ', innerLines) //지붕 innerLines에 추가. roof.innerLines = innerLines @@ -5574,6 +5544,43 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { canvas.renderAll() } +/** + * 라인 방향 확인 용 + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @param color + * @param roofId + * @returns {*} + */ +export const createArrow = ({ x1, y1, x2, y2 }, color, roofId) => { + const angle = Math.atan2(y2 - y1, x2 - x1) + const headLength = 15 + + const points = [ + { x: x1, y: y1 }, + { x: x2, y: y2 }, + { + x: x2 - headLength * Math.cos(angle - Math.PI / 7), + y: y2 - headLength * Math.sin(angle - Math.PI / 7), + }, + { x: x2, y: y2 }, + { + x: x2 - headLength * Math.cos(angle + Math.PI / 7), + y: y2 - headLength * Math.sin(angle + Math.PI / 7), + }, + ] + + return new fabric.Polyline(points, { + fill: 'transparent', + stroke: color, + strokeWidth: 3, + selectable: false, + parentId: roofId, + name: 'check', + }) +} /** * 선분과 선분의 교점을 구한다. * @param line1Start From 1524766b7efb2a40ebfdbe19771b07d3b7824ca4 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 13:52:50 +0900 Subject: [PATCH 05/55] =?UTF-8?q?=EC=99=B8=EB=B2=BD=EC=84=A0=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + src/hooks/roofcover/useMovementSetting.js | 206 ++++++++++-------- src/hooks/roofcover/useOuterLineWall.js | 1 + .../roofcover/useRoofShapePassivitySetting.js | 5 +- src/hooks/roofcover/useRoofShapeSetting.js | 6 + src/hooks/surface/useSurfaceShapeBatch.js | 1 + 6 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index 76632014..c0fe3701 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -222,6 +222,7 @@ export const SAVE_KEY = [ 'skeletonLines', 'skeleton', 'viewportTransform', + 'outerLineFix', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index e74a59e3..92f2e2c0 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -142,6 +142,15 @@ export function useMovementSetting(id) { } }, []) + useEffect(() => { + const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + if (roofs.length === 0) { + swalFire({ text: getMessage('roof.line.not.found') }) + closePopup(id) + return + } + }, []) + /** object 선택이 변경될 때 처리*/ useEffect(() => { if (FOLLOW_LINE_REF.current != null) { @@ -186,7 +195,6 @@ export function useMovementSetting(id) { canvas.renderAll() }, [currentObject]) - const clearRef = () => { if (type === TYPE.FLOW_LINE) { // 안전한 ref 접근 @@ -258,23 +266,25 @@ export function useMovementSetting(id) { let value = '' let direction = '' - if (Math.abs(target.y1 - target.y2) < 0.5) { // 수평 라인 + if (Math.abs(target.y1 - target.y2) < 0.5) { + // 수평 라인 value = Big(targetTop).minus(currentY).times(10).round(0) // 방향 감지 if (value.toNumber() > 0) { - direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) + direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) } else if (value.toNumber() < 0) { - direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) + direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) } - } else { // 수직 라인 + } else { + // 수직 라인 value = Big(targetLeft).minus(currentX).times(10).round(0).neg() // 방향 감지 if (value.toNumber() > 0) { - direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) + direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) } else if (value.toNumber() < 0) { - direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) + direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) } } @@ -312,37 +322,35 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - - const result = getSelectLinePosition(wall, target, { - testDistance: 5, // 테스트 거리 - debug: true // 디버깅 로그 출력 - }); + testDistance: 5, // 테스트 거리 + debug: true, // 디버깅 로그 출력 + }) //console.log("1111litarget:::::", target); //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' - let linePosition = result.position; -//console.log("1111linePosition:::::", direction, linePosition); + let linePosition = result.position + //console.log("1111linePosition:::::", direction, linePosition); - if (target.y1 === target.y2) { //수평벽 + if (target.y1 === target.y2) { + //수평벽 const setRadioStates = (isUp) => { if (UP_DOWN_REF.UP_RADIO_REF.current) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp } if (UP_DOWN_REF.DOWN_RADIO_REF.current) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp } - }; - - if (linePosition === 'top') { - setRadioStates(value.s !== -1); - } else if (linePosition === 'bottom') { - setRadioStates(value.s !== 1); } - if(direction === 'up') { + if (linePosition === 'top') { + setRadioStates(value.s !== -1) + } else if (linePosition === 'bottom') { + setRadioStates(value.s !== 1) + } + if (direction === 'up') { } /* checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } @@ -367,20 +375,19 @@ export function useMovementSetting(id) { } */ } else { - const setRadioStates = (isUp) => { if (UP_DOWN_REF.UP_RADIO_REF.current) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp } if (UP_DOWN_REF.DOWN_RADIO_REF.current) { - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp } - }; + } if (linePosition === 'left') { - setRadioStates(value.s !== 1); + setRadioStates(value.s !== 1) } else if (linePosition === 'right') { - setRadioStates(value.s !== -1); + setRadioStates(value.s !== -1) } /* checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } @@ -407,11 +414,8 @@ export function useMovementSetting(id) { */ } } - } - - const mouseDownEvent = (e) => { canvas .getObjects() @@ -460,7 +464,6 @@ export function useMovementSetting(id) { canvas.renderAll() } - const target = selectedObject.current !== null ? selectedObject.current : CONFIRM_LINE_REF.current?.target if (!target) return @@ -468,23 +471,23 @@ export function useMovementSetting(id) { const roof = canvas.getObjects().find((obj) => obj.id === roofId) // 현이동, 동이동 추가 - let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0; - let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0; - flPointValue = (flFilledValue > 0 || flFilledValue < 0) ? flFilledValue : flPointValue; + let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0 + let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0 + flPointValue = flFilledValue > 0 || flFilledValue < 0 ? flFilledValue : flPointValue const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? flPointValue : 0 - let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0; - let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0; - udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue; + let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0 + let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0 + udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue : 0 - roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; - roof.moveUpDown = parseInt(moveUpDown, 10) || 0; - roof.moveDirect = ""; + roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0 + roof.moveUpDown = parseInt(moveUpDown, 10) || 0 + roof.moveDirect = '' roof.moveSelectLine = target //console.log("target::::", target, roof.moveSelectLine) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const baseLines = wall.baseLines - let centerPoint = wall.getCenterPoint(); + let centerPoint = wall.getCenterPoint() let targetBaseLines = [] let isGableRoof if (typeRef.current === TYPE.FLOW_LINE) { @@ -522,9 +525,19 @@ export function useMovementSetting(id) { return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.y1).minus(line.y1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.y1).minus(line.y1).abs().toNumber(), + }), + ) } baseLines .filter((line) => line.y1 === line.y2 && line.y1 < target.y1) @@ -538,9 +551,19 @@ export function useMovementSetting(id) { return minX <= line.x1 && line.x1 <= maxX && minX <= line.x2 && line.x2 <= maxX }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.y1).minus(target.y1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.y1).minus(target.y1).abs().toNumber(), + }), + ) } break case 'right': @@ -551,9 +574,19 @@ export function useMovementSetting(id) { return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.x1).minus(target.x1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.x1).minus(line.x1).abs().toNumber(), + }), + ) } break case 'left': @@ -564,9 +597,19 @@ export function useMovementSetting(id) { return minY <= line.y1 && line.y1 <= maxY && minY <= line.y2 && line.y2 <= maxY }) if (isGableRoof && currentBaseLines.length > 0) { - currentBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(line.x1).minus(target.x1).abs().toNumber() })) + currentBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(line.x1).minus(target.x1).abs().toNumber(), + }), + ) } else { - checkBaseLines.forEach((line) => targetBaseLines.push({ line, distance: Big(target.x1).minus(line.x1).abs().toNumber() })) + checkBaseLines.forEach((line) => + targetBaseLines.push({ + line, + distance: Big(target.x1).minus(line.x1).abs().toNumber(), + }), + ) } break } @@ -576,18 +619,18 @@ export function useMovementSetting(id) { } // Remove duplicate lines - const uniqueLines = new Map(); - targetBaseLines = targetBaseLines.filter(item => { - const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}`; + const uniqueLines = new Map() + targetBaseLines = targetBaseLines.filter((item) => { + const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}` if (!uniqueLines.has(key)) { - uniqueLines.set(key, true); - return true; + uniqueLines.set(key, true) + return true } - return false; - }); + return false + }) // Sort by distance - targetBaseLines.sort((a, b) => a.distance - b.distance); + targetBaseLines.sort((a, b) => a.distance - b.distance) targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance) if (isGableRoof) { @@ -621,27 +664,22 @@ export function useMovementSetting(id) { let value if (typeRef.current === TYPE.FLOW_LINE) { value = (() => { - const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value; - const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value; + const filledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value + const pointerValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value if (filledValue && !isNaN(filledValue) && filledValue.trim() !== '') { - return Big(filledValue).times(2); + return Big(filledValue).times(2) } else if (pointerValue && !isNaN(pointerValue) && pointerValue.trim() !== '') { - return Big(pointerValue).times(2); + return Big(pointerValue).times(2) } - return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값 - })(); + return Big(0) // 기본값으로 0 반환 또는 다른 적절한 기본값 + })() if (Math.abs(target.y1 - target.y2) < 0.5) { value = value.neg() } } else { - console.log("error::", UP_DOWN_REF.POINTER_INPUT_REF.current.value) - value = Big( - (UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() || - UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() || - '0' - ) - ); + console.log('error::', UP_DOWN_REF.POINTER_INPUT_REF.current.value) + value = Big(UP_DOWN_REF?.FILLED_INPUT_REF?.current?.value?.trim() || UP_DOWN_REF?.POINTER_INPUT_REF?.current?.value?.trim() || '0') const midX = Big(target.x1).plus(target.x2).div(2) const midY = Big(target.y1).plus(target.y2).div(2) @@ -665,17 +703,16 @@ export function useMovementSetting(id) { // console.log("2222저장된 moveSelectLine:", roof.moveSelectLine); // console.log("222wall::::", wall.points) const result = getSelectLinePosition(wall, target, { - testDistance: 5, // 테스트 거리 - debug: true // 디버깅 로그 출력 - }); + testDistance: 5, // 테스트 거리 + debug: true, // 디버깅 로그 출력 + }) //console.log("2222linePosition:::::", result.position); //console.log("222moveDirect:::::", roof.moveDirect); + // 디버깅용 분류 결과 확인 -// 디버깅용 분류 결과 확인 - - let linePosition = result.position; + let linePosition = result.position roof.movePosition = linePosition value = value.div(10) targetBaseLines @@ -684,15 +721,14 @@ export function useMovementSetting(id) { const currentLine = target.line //console.log("linePosition::::::::::::::", linePosition) - if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked ){ + if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked) { //position확인 - if(linePosition === 'bottom' || linePosition === 'right') { + if (linePosition === 'bottom' || linePosition === 'right') { //console.log("1value::::::::::::::", value.toString()) value = value.neg() - } - }else { - if(linePosition === 'top' || linePosition === 'left') { + } else { + if (linePosition === 'top' || linePosition === 'left') { //console.log("1value::::::::::::::", value.toString()) value = value.neg() } @@ -753,7 +789,6 @@ export function useMovementSetting(id) { // javascript - return { TYPE, closePopup, @@ -765,4 +800,3 @@ export function useMovementSetting(id) { handleSave, } } - diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index ebe1d8ad..dd304865 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -251,6 +251,7 @@ export function useOuterLineWall(id, propertiesId) { removeAllDocumentEventListeners() canvas?.renderAll() setOuterLineFix(true) + canvas.outerLineFix = true closePopup(id) ccwCheck() addPopup(propertiesId, 1, ) diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index 241d2fac..ac36779c 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -7,7 +7,6 @@ import { useEvent } from '@/hooks/useEvent' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useMode } from '@/hooks/useMode' import { usePolygon } from '@/hooks/usePolygon' -import { outerLineFixState } from '@/store/outerLineAtom' import { useSwal } from '@/hooks/useSwal' import { usePopup } from '@/hooks/usePopup' import { getChonByDegree } from '@/util/canvas-util' @@ -46,11 +45,9 @@ export function useRoofShapePassivitySetting(id) { { id: 3, name: getMessage('windage'), type: TYPES.SHED }, ] - const outerLineFix = useRecoilValue(outerLineFixState) - useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - if (!outerLineFix || outerLines.length === 0) { + if (!canvas.outerLineFix || outerLines.length === 0) { swalFire({ text: getMessage('wall.line.not.found') }) closePopup(id) return diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index 9e1d00ff..f5c84f5b 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -81,6 +81,12 @@ export function useRoofShapeSetting(id) { }, [jerkinHeadPitch]) useEffect(() => { + const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') + if (!canvas.outerLineFix || outerLines.length === 0) { + swalFire({ text: getMessage('wall.line.not.found') }) + closePopup(id) + return + } return () => { if (!isFixRef.current) { return diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index b26ee195..06474da0 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -755,6 +755,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { type: 'confirm', confirmFn: () => { canvas.clear() + delete canvas.outerLineFix if (backgroundImage) { fabric.Image.fromURL(`${backgroundImage.path}`, function (img) { From 7557f61130071b317b269795622e8a894d9277c7 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 31 Dec 2025 14:17:45 +0900 Subject: [PATCH 06/55] =?UTF-8?q?=EB=B6=81=EB=A9=B4=20=EC=84=A4=EC=B9=98?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=20=EB=AA=A8=EB=93=88=EC=9D=80=20=EB=8B=A8?= =?UTF-8?q?=EB=8F=85=EC=9C=BC=EB=A1=9C=EB=A7=8C=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../step/type/PassivityCircuitAllocation.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 1ee73892..720845f5 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -122,6 +122,21 @@ export default function PassivityCircuitAllocation(props) { return } + // targetModule중 북면 설치 여부가 Y인 것과 N인 것이 혼합이면 안됨. + const targetModuleGroup = [...new Set(canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + .map((obj) => obj.moduleInfo.northModuleYn))] + + if (targetModuleGroup.length > 1) { + swalFire({ + text: getMessage('module.circuit.fix.not.same.roof.error'), + type: 'alert', + icon: 'warning', + }) + return + } + switch (pcsTpCd) { case 'INDFCS': { const originHaveThisPcsModules = canvas From fd8cbf726c13735e74129502b1014600bb1a351a Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 31 Dec 2025 15:41:46 +0900 Subject: [PATCH 07/55] =?UTF-8?q?=ED=95=A0=EB=8B=B9=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roofcover/useRoofAllocationSetting.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 4bb3d8ca..5e65cbcf 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -476,16 +476,54 @@ export function useRoofAllocationSetting(id) { const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id)) // Filter out lines from roofBase.lines that share any points with newEaveLines const linesToKeep = roofBase.lines.filter(roofLine => { - return !newEaveLines.some(eaveLine => { - // Check if any endpoint of roofLine matches any endpoint of eaveLine - return ( - // Check if any endpoint of roofLine matches any endpoint of eaveLine - (Math.abs(roofLine.x1 - eaveLine.x1) < 0.1 && Math.abs(roofLine.y1 - eaveLine.y1) < 0.1) || // p1 matches p1 - (Math.abs(roofLine.x2 - eaveLine.x2) < 0.1 && Math.abs(roofLine.y2 - eaveLine.y2) < 0.1) // p2 matches p2 - ); - }); - }); + const shouldRemove = newEaveLines.some(eaveLine => { + // 1. 기본적인 포인트 일치 확인 + const rX1 = roofLine.x1, rY1 = roofLine.y1, rX2 = roofLine.x2, rY2 = roofLine.y2; + const eX1 = eaveLine.x1, eY1 = eaveLine.y1, eX2 = eaveLine.x2, eY2 = eaveLine.y2; + const isP1Matched = (Math.abs(rX1 - eX1) < 0.1 && Math.abs(rY1 - eY1) < 0.1) || (Math.abs(rX1 - eX2) < 0.1 && Math.abs(rY1 - eY2) < 0.1); + const isP2Matched = (Math.abs(rX2 - eX1) < 0.1 && Math.abs(rY2 - eY1) < 0.1) || (Math.abs(rX2 - eX2) < 0.1 && Math.abs(rY2 - eY2) < 0.1); + + if (isP1Matched || isP2Matched) { + // 2. 일직선(평행)인지 확인 + const dx1 = rX2 - rX1; + const dy1 = rY2 - rY1; + const dx2 = eX2 - eX1; + const dy2 = eY2 - eY1; + const crossProduct = Math.abs(dx1 * dy2 - dy1 * dx2); + const mag1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); + const mag2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); + const isStraight = (mag1 * mag2) === 0 ? true : (crossProduct / (mag1 * mag2) < 0.01); + + if (isStraight) { + // 3. [핵심] 몸통이 포개지는지(Overlap) 확인 + // 한 선의 끝점이 다른 선의 "내부"에 들어와 있는지 체크 + const isPointInside = (x, y, x1, y1, x2, y2) => { + const dotProduct = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1); + if (dotProduct < 0.1) return false; // 시작점 바깥쪽 + const squaredLength = (x2 - x1) ** 2 + (y2 - y1) ** 2; + if (dotProduct > squaredLength - 0.1) return false; // 끝점 바깥쪽 + return true; // 선의 내부(몸통)에 있음 + }; + + // roofLine의 끝점 중 하나가 eaveLine의 몸통 안에 있거나, + // eaveLine의 끝점 중 하나가 roofLine의 몸통 안에 있으면 "포개짐"으로 판단 + const isOverlapping = + isPointInside(rX1, rY1, eX1, eY1, eX2, eY2) || + isPointInside(rX2, rY2, eX1, eY1, eX2, eY2) || + isPointInside(eX1, eY1, rX1, rY1, rX2, rY2) || + isPointInside(eX2, eY2, rX1, rY1, rX2, rY2); + + if (isOverlapping) { + console.log('Removing overlapping line:', roofLine); + return true; // 포개지는 경우에만 삭제 + } + } + } + return false; // 끝점만 닿아 있거나 직각인 경우는 살림 + }); + return !shouldRemove; + }); // Combine remaining lines with newEaveLines roofBase.lines = [...linesToKeep, ...newEaveLines]; } else { From a74789d8e8705f6a312e7f9c58be02ca0d07e01c Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 2 Jan 2026 14:37:08 +0900 Subject: [PATCH 08/55] =?UTF-8?q?=ED=9A=8C=EC=A0=84=20=ED=9B=84=20?= =?UTF-8?q?=EC=9E=98=EB=A6=AC=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/surface/useSurfaceShapeBatch.js | 29 ++++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 06474da0..d485b719 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -1,6 +1,6 @@ 'use client' -import { useRecoilValue, useResetRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { canvasSettingState, canvasState, @@ -50,7 +50,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { const { changeCorridorDimensionText } = useText() const currentCanvasPlan = useRecoilValue(currentCanvasPlanState) const { fetchSettings } = useCanvasSetting(false) - const currentObject = useRecoilValue(currentObjectState) + const [currentObject, setCurrentObject] = useRecoilState(currentObjectState) const [popupId, setPopupId] = useState(uuidv4()) const applySurfaceShape = (surfaceRefs, selectedType, id) => { @@ -1525,6 +1525,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // 개별 객체들을 다시 캔버스에 추가하고 처리 group.getObjects().forEach((obj) => { canvas.add(obj) + obj.dirty = true // 캐시 무효화 obj.setCoords() // currentObject인 경우 추가 처리 @@ -1535,6 +1536,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결) if (obj.type === 'QPolygon' && obj.lines) { obj.initLines() + obj.dirty = true + obj.setCoords() } obj.set({ @@ -1545,6 +1548,8 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // relatedObject인 경우에도 필요한 처리 if (obj.type === 'QPolygon' && obj.lines) { obj.initLines() + obj.dirty = true + obj.setCoords() } if (obj.type === 'group') { // 회전 후의 points를 groupPoints로 업데이트 @@ -1552,24 +1557,30 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { obj.recalculateGroupPoints() - obj._objects?.forEach((obj) => { - obj.initLines() - obj.fire('modified') + obj._objects?.forEach((innerObj) => { + innerObj.initLines() + innerObj.dirty = true + innerObj.setCoords() + innerObj.fire('modified') }) } } }) + + currentObject.dirty = true + currentObject.setCoords() currentObject.fire('modified') + currentObject.fire('polygonMoved') // 화살표와 선 다시 그리기 drawDirectionArrow(currentObject) setTimeout(() => { setPolygonLinesActualSize(currentObject) changeSurfaceLineType(currentObject) + currentObject.dirty = true + currentObject.setCoords() + canvas.requestRenderAll() + setCurrentObject(currentObject) }, 500) - - // currentObject를 다시 선택 상태로 설정 - canvas.setActiveObject(currentObject) - canvas.renderAll() } } From 8f421d08dab72ac4b24c7eca44ed38842456a4f9 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 5 Jan 2026 15:11:53 +0900 Subject: [PATCH 09/55] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=8B=9C=20=EC=9A=A9=EB=A7=88=EB=A3=A8=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=EC=A7=80=EB=B6=95=20=ED=8C=8C=EC=83=9D=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + src/components/fabric/QPolygon.js | 17 +- .../roofcover/useRoofAllocationSetting.js | 172 +- src/util/qpolygon-utils.js | 7494 +---------------- 4 files changed, 127 insertions(+), 7557 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index 76632014..0b824039 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -222,6 +222,7 @@ export const SAVE_KEY = [ 'skeletonLines', 'skeleton', 'viewportTransform', + 'adjustRoofLines', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 8d173bc6..b6c299d0 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -36,6 +36,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.separatePolygon = [] this.toFixed = options.toFixed ?? 1 this.baseLines = [] + this.adjustRoofLines = [] // this.colorLines = [] // 소수점 전부 제거 @@ -134,7 +135,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.setCoords() }) - this.on('modified', (e) => { + this.on('modified', () => { this.initLines() this.addLengthText() this.setCoords() @@ -223,7 +224,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { calculateDegree() { const degrees = [] // polygon.lines를 순회하며 각도를 구해 출력 - this.lines.forEach((line, idx) => { + this.lines.forEach((line) => { const dx = line.x2 - line.x1 const dy = line.y2 - line.y1 const rad = Math.atan2(dy, dx) @@ -258,6 +259,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { ) .forEach((obj) => this.canvas.remove(obj)) this.innerLines = [] + this.adjustRoofLines = [] this.canvas.renderAll() let textMode = 'plane' @@ -339,18 +341,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) { // 용마루 -- straight-skeleton - console.log('용마루 지붕') - ///drawRidgeRoof(this.id, this.canvas, textMode) + // console.log('용마루 지붕') drawSkeletonRidgeRoof(this.id, this.canvas, textMode) } else if (isGableRoof(types)) { // A형, B형 박공 지붕 - console.log('패턴 지붕') + // console.log('패턴 지붕') drawGableRoof(this.id, this.canvas, textMode) } else if (isShedRoof(types, this.lines)) { - console.log('한쪽흐름 지붕') + // console.log('한쪽흐름 지붕') drawShedRoof(this.id, this.canvas, textMode) } else { - console.log('변별로 설정') + // console.log('변별로 설정') drawRoofByAttribute(this.id, this.canvas, textMode) } }, @@ -404,7 +405,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber() - // Create new text object if it doesn't exist + // Create a new text object if it doesn't exist const text = new fabric.Text(length.toString(), { left: midPoint.x, top: midPoint.y, diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 4bb3d8ca..54e2b7aa 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -30,7 +30,6 @@ import { QcastContext } from '@/app/QcastProvider' import { usePlan } from '@/hooks/usePlan' import { roofsState } from '@/store/roofAtom' import { useText } from '@/hooks/useText' -import { processEaveHelpLines } from '@/util/skeleton-utils' import { QLine } from '@/components/fabric/QLine' export function useRoofAllocationSetting(id) { @@ -114,10 +113,10 @@ export function useRoofAllocationSetting(id) { */ const fetchBasicSettings = async (planNo) => { try { - const response = await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }); - - let roofsArray = []; - + const response = await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }) + + let roofsArray = [] + // API에서 데이터를 성공적으로 가져온 경우 if (response && response.length > 0) { roofsArray = response.map((item, index) => ({ @@ -133,16 +132,16 @@ export function useRoofAllocationSetting(id) { roofPitch: item.roofPitch, roofAngle: item.roofAngle, selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정 - index: index - })); - } + index: index, + })) + } // API에서 데이터가 없고 기존 roofList가 있는 경우 else if (roofList && roofList.length > 0) { roofsArray = roofList.map((roof, index) => ({ ...roof, - selected: index === 0 // 첫 번째 항목을 기본 선택으로 설정 - })); - } + selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정 + })) + } // 둘 다 없는 경우 기본값 설정 else { roofsArray = [ @@ -156,64 +155,62 @@ export function useRoofAllocationSetting(id) { roofHajebichi: 0, roofGap: 'HEI_455', roofLayout: 'P', - roofPitch: 4, - roofAngle: 21.8, - }, - ] + roofPitch: 4, + roofAngle: 21.8, + }, + ] + } + + /** + * 데이터 설정 + */ + const selectRoofs = [] + for (let i = 0; i < roofsArray.length; i++) { + roofMaterials?.map((material) => { + if (material.roofMatlCd === roofsArray[i].roofMatlCd) { + selectRoofs.push({ + ...material, + selected: roofsArray[i].roofApply, + index: roofsArray[i].roofSeq, + id: roofsArray[i].roofMatlCd, + width: roofsArray[i].roofWidth, + length: roofsArray[i].roofHeight, + hajebichi: roofsArray[i].roofHajebichi, + raft: roofsArray[i].roofGap, + layout: roofsArray[i].roofLayout, + pitch: roofsArray[i].roofPitch, + angle: roofsArray[i].roofAngle, + }) } - - - /** - * 데이터 설정 - */ - const selectRoofs = [] - for (let i = 0; i < roofsArray.length; i++) { - roofMaterials?.map((material) => { - if (material.roofMatlCd === roofsArray[i].roofMatlCd) { - selectRoofs.push({ - ...material, - selected: roofsArray[i].roofApply, - index: roofsArray[i].roofSeq, - id: roofsArray[i].roofMatlCd, - width: roofsArray[i].roofWidth, - length: roofsArray[i].roofHeight, - hajebichi: roofsArray[i].roofHajebichi, - raft: roofsArray[i].roofGap, - layout: roofsArray[i].roofLayout, - pitch: roofsArray[i].roofPitch, - angle: roofsArray[i].roofAngle, - }) - } - }) - } - - const firstRes = Array.isArray(res) && res.length > 0 ? res[0] : null - - setBasicSetting({ - ...basicSetting, - planNo: firstRes?.planNo ?? planNo, - roofSizeSet: firstRes?.roofSizeSet ?? 0, - roofAngleSet: firstRes?.roofAngleSet ?? 0, - roofsData: roofsArray, - selectedRoofMaterial: selectRoofs.find((roof) => roof.selected), }) + } - setBasicInfo({ - planNo: '' + (firstRes?.planNo ?? planNo), - roofSizeSet: '' + (firstRes?.roofSizeSet ?? 0), - roofAngleSet: '' + (firstRes?.roofAngleSet ?? 0), - }) - // 데이터 동기화: 렌더링용 필드 기본값 보정 - const normalizedRoofs = selectRoofs.map((roof) => ({ - ...roof, - width: roof.width ?? '', - length: roof.length ?? '', - hajebichi: roof.hajebichi ?? '', - pitch: roof.pitch ?? '', - angle: roof.angle ?? '', - })) - setCurrentRoofList(normalizedRoofs) + const firstRes = Array.isArray(res) && res.length > 0 ? res[0] : null + setBasicSetting({ + ...basicSetting, + planNo: firstRes?.planNo ?? planNo, + roofSizeSet: firstRes?.roofSizeSet ?? 0, + roofAngleSet: firstRes?.roofAngleSet ?? 0, + roofsData: roofsArray, + selectedRoofMaterial: selectRoofs.find((roof) => roof.selected), + }) + + setBasicInfo({ + planNo: '' + (firstRes?.planNo ?? planNo), + roofSizeSet: '' + (firstRes?.roofSizeSet ?? 0), + roofAngleSet: '' + (firstRes?.roofAngleSet ?? 0), + }) + // 데이터 동기화: 렌더링용 필드 기본값 보정 + const normalizedRoofs = selectRoofs.map((roof) => ({ + ...roof, + width: roof.width ?? '', + length: roof.length ?? '', + hajebichi: roof.hajebichi ?? '', + pitch: roof.pitch ?? '', + angle: roof.angle ?? '', + })) + setCurrentRoofList(normalizedRoofs) } catch (error) { console.error('Data fetching error:', error) } @@ -467,7 +464,6 @@ export function useRoofAllocationSetting(id) { const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { - const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id) if (roofEaveHelpLines.length > 0) { if (roofBase.lines) { @@ -475,19 +471,19 @@ export function useRoofAllocationSetting(id) { const existingEaveLineIds = new Set(roofBase.lines.map((line) => line.id)) const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id)) // Filter out lines from roofBase.lines that share any points with newEaveLines - const linesToKeep = roofBase.lines.filter(roofLine => { - return !newEaveLines.some(eaveLine => { + const linesToKeep = roofBase.lines.filter((roofLine) => { + return !newEaveLines.some((eaveLine) => { // Check if any endpoint of roofLine matches any endpoint of eaveLine return ( // Check if any endpoint of roofLine matches any endpoint of eaveLine (Math.abs(roofLine.x1 - eaveLine.x1) < 0.1 && Math.abs(roofLine.y1 - eaveLine.y1) < 0.1) || // p1 matches p1 - (Math.abs(roofLine.x2 - eaveLine.x2) < 0.1 && Math.abs(roofLine.y2 - eaveLine.y2) < 0.1) // p2 matches p2 - ); - }); - }); + (Math.abs(roofLine.x2 - eaveLine.x2) < 0.1 && Math.abs(roofLine.y2 - eaveLine.y2) < 0.1) // p2 matches p2 + ) + }) + }) -// Combine remaining lines with newEaveLines - roofBase.lines = [...linesToKeep, ...newEaveLines]; + // Combine remaining lines with newEaveLines + roofBase.lines = [...linesToKeep, ...newEaveLines] } else { roofBase.lines = [...roofEaveHelpLines] } @@ -496,6 +492,34 @@ export function useRoofAllocationSetting(id) { } } + if (roofBase.adjustRoofLines.length > 0) { + const newRoofLines = [] + let lineIndex = 1 + roofBase.lines.forEach((line, idx) => { + const adjustLines = roofBase.adjustRoofLines.filter((adjustLine) => adjustLine.roofIdx === line.idx) + if (adjustLines.length === 0) { + line.idx = lineIndex + newRoofLines.push(line) + lineIndex++ + } else { + adjustLines.forEach(({ point, roofIdx }) => { + const newLine = new QLine(point, { + idx: lineIndex, + selectable: false, + parentId: line.parentId, + parent: line.parent, + fontSize: line.fontSize, + stroke: line.stroke, + strokeWidth: line.strokeWidth, + attributes: line.attributes, + }) + newRoofLines.push(newLine) + lineIndex++ + }) + } + }) + roofBase.lines = newRoofLines + } if (roofBase.separatePolygon.length > 0) { splitPolygonWithSeparate(roofBase.separatePolygon) } else { diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 82ad62df..abcd66ce 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -3,7 +3,6 @@ import { QLine } from '@/components/fabric/QLine' import { getAdjacent, getDegreeByChon, isPointOnLine, isPointOnLineNew } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' -import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' @@ -490,14 +489,6 @@ export const isSamePoint = (a, b) => { return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2 } -/** - * 용마루 지붕 - * @param roofId - * @param canvas - * @param textMode - */ -export const drawEavesRoof = (roofId, canvas, textMode) => {} - /** * 박공지붕(A,B 패턴) * @param roofId @@ -509,7 +500,7 @@ export const drawGableRoof = (roofId, canvas, textMode) => { const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) - const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + // const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) const eavesLines = baseLines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) @@ -1492,164 +1483,6 @@ export const drawShedRoof = (roofId, canvas, textMode) => { canvas.renderAll() } -const getInwardNormal = (v1, v2, isCCW) => { - const dx = v2.x - v1.x - const dy = v2.y - v1.y - const length = Math.sqrt(dx * dx + dy * dy) - - if (length === 0) return { x: 0, y: 0 } - - if (isCCW) { - return { x: -dy / length, y: dx / length } - } else { - return { x: dy / length, y: -dx / length } - } -} - -const isCounterClockwise = (vertices) => { - let sum = 0 - for (let i = 0; i < vertices.length; i++) { - const v1 = vertices[i] - const v2 = vertices[(i + 1) % vertices.length] - sum += (v2.x - v1.x) * (v2.y + v1.y) - } - return sum < 0 -} - -const isPointInPolygon = (point, polygon) => { - let inside = false - - for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { - const xi = polygon[i].x, - yi = polygon[i].y - const xj = polygon[j].x, - yj = polygon[j].y - - const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi - - if (intersect) inside = !inside - } - - return inside -} - -const calculateAngleBisector = (prevVertex, currentVertex, nextVertex, polygonVertices) => { - const isCCW = isCounterClockwise(polygonVertices) - - // 이전 변의 내향 법선 - const norm1 = getInwardNormal(prevVertex, currentVertex, isCCW) - - // 다음 변의 내향 법선 - const norm2 = getInwardNormal(currentVertex, nextVertex, isCCW) - - // 이등분선 계산 - let bisectorX = norm1.x + norm2.x - let bisectorY = norm1.y + norm2.y - - const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY) - - if (length < 1e-10) { - // 180도인 경우 - bisectorX = norm1.x - bisectorY = norm1.y - } else { - bisectorX /= length - bisectorY /= length - } - - const testPoint = { - x: currentVertex.x + bisectorX * 0.1, - y: currentVertex.y + bisectorY * 0.1, - } - - if (isPointInPolygon(testPoint, polygonVertices)) { - // 방향이 외부를 향하면 반전 - bisectorX = -bisectorX - bisectorY = -bisectorY - } - - return { x: bisectorX, y: bisectorY } -} - -const lineSegmentIntersection = (p1, p2, p3, p4) => { - const x1 = p1.x, - y1 = p1.y - const x2 = p2.x, - y2 = p2.y - const x3 = p3.x, - y3 = p3.y - const x4 = p4.x, - y4 = p4.y - - const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) - - if (Math.abs(denom) < EPSILON) { - return null // 평행 - } - - const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom - const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom - - if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { - return { - x: Number((x1 + t * (x2 - x1)).toFixed(1)), - y: Number((y1 + t * (y2 - y1)).toFixed(1)), - } - } - - return null -} - -/** - * 두 점 사이의 거리 계산 - */ -const distanceBetweenPoints = (p1, p2) => { - const dx = p2.x - p1.x - const dy = p2.y - p1.y - return Math.sqrt(dx * dx + dy * dy) -} - -const findIntersectionPoint = (startPoint, direction, polygonVertices, divisionLines) => { - const rayEnd = { - x: startPoint.x + direction.x * 10000, - y: startPoint.y + direction.y * 10000, - } - - let closestIntersection = null - let minDistance = Infinity - - // 다각형 변과의 교점 - for (let i = 0; i < polygonVertices.length; i++) { - const v1 = polygonVertices[i] - const v2 = polygonVertices[(i + 1) % polygonVertices.length] - - const intersection = lineSegmentIntersection(startPoint, rayEnd, v1, v2) - - if (intersection) { - const dist = distanceBetweenPoints(startPoint, intersection) - if (dist > 0.1 && dist < minDistance) { - minDistance = dist - closestIntersection = intersection - } - } - } - - // 분할선분과의 교점 - for (const divLine of divisionLines) { - const intersection = lineSegmentIntersection(startPoint, rayEnd, { x: divLine.x1, y: divLine.y1 }, { x: divLine.x2, y: divLine.y2 }) - - if (intersection) { - const dist = distanceBetweenPoints(startPoint, intersection) - if (dist > 0.1 && dist < minDistance) { - minDistance = dist - closestIntersection = intersection - } - } - } - - return closestIntersection -} - /** * 변별로 설정된 지붕을 그린다. * @param roofId @@ -2340,7 +2173,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const tolerance = 1 const dx = Big(roofLine.x2).minus(Big(roofLine.x1)).toNumber() const dy = Big(roofLine.y2).minus(Big(roofLine.y1)).toNumber() - const length = Math.sqrt(dx * dx + dy * dy) const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180 let isHorizontal = false, @@ -2452,7 +2284,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) } const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 @@ -2688,10 +2519,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let proceedEaves = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이 let proceedRidges = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이 - let hipLines = [] ridgeEaves.forEach((currentLine) => { - /*const checkLine = createArrow(currentLine, 'blue', roofId) - canvas.add(checkLine).renderAll()*/ let prevLine, nextLine, currentI, prevI, nextI baseLines.forEach((baseLine, index) => { if (baseLine === currentLine) { @@ -2714,7 +2542,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) const analyze = analyzeLine(currentLine) - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) let pHipVector = getHalfAngleVector(currentLine, prevLine) let nHipVector = getHalfAngleVector(currentLine, nextLine) const pCheckPoint = { @@ -2736,10 +2563,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const prevCheckPoint = [currentLine.x1, currentLine.y1, currentLine.x1 + pHipVector.x * 1000, currentLine.y1 + pHipVector.y * 1000] const nextCheckPoint = [currentLine.x2, currentLine.y2, currentLine.x2 + nHipVector.x * 1000, currentLine.y2 + nHipVector.y * 1000] - /*const checkLine1 = new fabric.Line(prevCheckPoint, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) - const checkLine2 = new fabric.Line(nextCheckPoint, { stroke: 'green', strokeWidth: 4, parentID: roofId, name: 'check' }) - canvas.add(checkLine1, checkLine2).renderAll()*/ - const findRoofPoints = (points) => { const hipEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) } @@ -3095,7 +2918,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } if (hipPoint) { - const hipLength = Math.sqrt((hipPoint.x2 - hipPoint.x1) ** 2 + (hipPoint.y2 - hipPoint.y1) ** 2) const alreadyLine = proceedEaves.filter((line) => almostEqual(line.point.x1, hipPoint.x1) && almostEqual(line.point.y1, hipPoint.y1)) //겹쳐지는 라인이 있는경우 조정한다. if (alreadyLine.length === 0) { @@ -3435,19 +3257,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1), } - /* const checkCurrLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { - stroke: 'cyan', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkCurrLine) - canvas.renderAll()*/ - //좌우 라인이 서로 다른방향일때 if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) { const analyze = analyzeLine(currentLine) - const roofLine = analyze.roofLine let beforePrevLine, afterNextLine baseLines.forEach((baseLine, index) => { @@ -3487,7 +3299,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } - console.log('isOverlap : ', isOverlap) if (isOverlap) { const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine const cMidX = (currentLine.x1 + currentLine.x2) / 2 @@ -3601,7 +3412,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } } - let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance const stdAnalyze = analyzeLine(stdLine) let stdPoints = [] @@ -3683,11 +3493,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } } - console.log('stdAdjustVector', stdAdjustVector) } else { const stdAddPrevVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) } const stdAddNextVector = { x: Math.sign(clamp01(stdLine.x2 - stdLine.x1)), y: Math.sign(clamp01(stdLine.y2 - stdLine.y1)) } - console.log('stdAddPrevVector', stdAddPrevVector) stdPoints = [ stdLine.x1 + stdAddPrevVector.x * stdPrevLine.attributes.offset, stdLine.y1 + stdAddPrevVector.y * stdPrevLine.attributes.offset, @@ -3695,19 +3503,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { stdLine.y2 + stdAddNextVector.y * stdNextLine.attributes.offset, ] } - /*const checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - const checkLine2 = new fabric.Line(stdPoints, { - stroke: 'green', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine, checkLine2).renderAll()*/ //기준지붕선의 반대쪽선 const oppositeLine = [] @@ -3715,7 +3510,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const endX = Math.max(stdLine.x1, stdLine.x2) const startY = Math.min(stdLine.y1, stdLine.y2) const endY = Math.max(stdLine.y1, stdLine.y2) - console.log('stdFindOppVector', stdFindOppVector) baseLines .filter((line) => line !== stdLine && line !== currentLine && line.attributes.type !== LINE_TYPE.WALLLINE.SHED) .filter((line) => { @@ -3768,26 +3562,14 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (oppositeLine.length > 0) { const ridgePoints = [] - //지붕선 출발 지점 확인을 위한 기준 포인트 - let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 } oppositeLine.sort((a, b) => a.distance - b.distance) oppositeLine.forEach((opposite) => { const oppLine = opposite.line - /*const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], { - stroke: 'yellow', - strokeWidth: 6, - parentId: roofId, - name: 'check', - }) - canvas.add(checkOppLine) - canvas.renderAll()*/ - const oppIndex = baseLines.findIndex((line) => line === oppLine) //마주하는 라인의 이전 다음 라인. const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length] const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length] - const oppAnalyze = analyzeLine(oppLine) const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) } let ridgePoint @@ -3829,13 +3611,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { ridgePoint[2] = (stdLine.x2 + oppLine.x2) / 2 } - // 지붕선 출발 지점과의 거리를 통해 가까운쪽이 start point로 처리. - /*const distRidgeStandard1 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[0], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[1], 2)) - const distRidgeStandard2 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[2], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[3], 2)) - if (distRidgeStandard1 > distRidgeStandard2) { - ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] - }*/ - if ( (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) && !(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) @@ -4029,10 +3804,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } - /* const checkLine1 = new fabric.Line(stdPoints, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) - const checkLine2 = new fabric.Line(ridgePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) - canvas.add(checkLine1, checkLine2).renderAll()*/ - const stdMinX = Math.min(stdPoints[0], stdPoints[2]) const stdMaxX = Math.max(stdPoints[0], stdPoints[2]) const stdMinY = Math.min(stdPoints[1], stdPoints[3]) @@ -4183,21 +3954,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { const analyze = analyzeLine(currentLine) const roofLine = analyze.roofLine - /*const checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkRoof) - const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { - stroke: 'blue', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine) - canvas.renderAll()*/ const checkVector = { x: Math.sign(prevLine.y2 - prevLine.y1), y: Math.sign(prevLine.x1 - prevLine.x2) } const checkEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } @@ -4211,33 +3967,14 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine }) .forEach((line) => { - /*const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine).renderAll()*/ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersect = edgesIntersection(lineEdge, checkEdge) - /* if (intersect) { - const checkCircle = new fabric.Circle({ - left: intersect.x, - top: intersect.y, - radius: 5, - fill: 'blue', - parentId: roofId, - name: 'check', - }) - console.log('isPointOnLineNew(line, intersect)', isPointOnLineNew(line, intersect)) - canvas.add(checkCircle).renderAll() - }*/ + if (intersect && isPointOnLineNew(line, intersect)) { intersect.x = almostEqual(intersect.x, roofLine.x1) ? roofLine.x1 : intersect.x intersect.y = almostEqual(intersect.y, roofLine.y1) ? roofLine.y1 : intersect.y const distance = Math.sqrt(Math.pow(intersect.x - roofLine.x1, 2) + Math.pow(intersect.y - roofLine.y1, 2)) const vector = { x: Math.sign(intersect.x - roofLine.x1), y: Math.sign(intersect.y - roofLine.y1) } - console.log('vector', vector, 'checkVector', checkVector) if (distance > maxDistance && vector.x === checkVector.x && vector.y === checkVector.y) { maxDistance = distance correctPoint = intersect @@ -4274,19 +4011,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const MAX_ITERATIONS = 1000 //무한루프 방지 let iterations = 0 - console.log('baseLines', baseLines) - console.log('linesAnalysis', linesAnalysis) - while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ - /*linesAnalysis.forEach((line) => { - const point = { x1: line.start.x, y1: line.start.y, x2: line.end.x, y2: line.end.y } - const checkLine = createArrow(point, 'red', roofId) - canvas.add(checkLine).renderAll() - // canvas.remove(checkLine).renderAll() - })*/ - const intersections = [] linesAnalysis.forEach((currLine, i) => { let minDistance = Infinity @@ -4294,14 +4021,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let linePoint = null let partner = null - /*const checkCLine = new fabric.Line([currLine.start.x, currLine.start.y, currLine.end.x, currLine.end.y], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkCLine).renderAll()*/ - const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2)) //남은 길이가 0이면 무시 if (cLength < EPSILON) return @@ -4312,18 +4031,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (i === j) return if (currLine.type === TYPES.GABLE_LINE && nextLine.type === TYPES.GABLE_LINE && currLine.gableId === nextLine.gableId) return if (nextLine.type === TYPES.GABLE_LINE && nextLine.connectCnt > 1) return - /*const checkNLine = new fabric.Line([nextLine.start.x, nextLine.start.y, nextLine.end.x, nextLine.end.y], { - stroke: 'green', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkNLine).renderAll()*/ const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas) if (intersect) { - /*const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check' }) - canvas.add(checkCircle).renderAll()*/ let distance1 = Math.sqrt(Math.pow(intersect.x - currLine.start.x, 2) + Math.pow(intersect.y - currLine.start.y, 2)) let distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.start.x, 2) + Math.pow(intersect.y - nextLine.start.y, 2)) let point = [currLine.start.x, currLine.start.y, intersect.x, intersect.y] @@ -4348,20 +4058,15 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { partner = j } } - // canvas.remove(checkCircle).renderAll() } - // canvas.remove(checkNLine).renderAll() }) - // canvas.remove(checkCLine).renderAll() if (intersectPoint) { intersections.push({ index: i, intersect: intersectPoint, linePoint, partner }) } }) - console.log('intersections', intersections) const intersectPoints = intersections .map((item) => item.intersect) .filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index) - console.log('intersectPoints', intersectPoints) if (intersectPoints.length === 1 && intersections.length > 1) { intersections[0].partner = intersections[1].index @@ -4488,7 +4193,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } if (cLine.type === TYPES.GABLE_LINE || pLine.type === TYPES.GABLE_LINE) { relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right] - console.log('gableLine newAnalyze start') const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine gableLine.connectCnt++ @@ -4530,7 +4234,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } } - console.log('gableLine newAnalyze end') } else { const uniqueBaseLines = [...new Set(relationBaseLines)] // 연결점에서 새로운 가선분을 생성 @@ -4544,16 +4247,13 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { .filter(([_, count]) => count === 1) .map(([line, _]) => line) - console.log('uniqueLines', uniqueLines) if (uniqueLines.length === 2) { // 두 변의 이등분선 방향 계산 // uniqueLines.sort((a, b) => a - b) - console.log('uniqueLines : ', uniqueLines) const baseLine1 = baseLines[uniqueLines[0]] const baseLine2 = baseLines[uniqueLines[1]] let bisector - console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) if (isParallel(baseLine1, baseLine2)) { let cPoint = [cLine.start.x, cLine.start.y, cLine.end.x, cLine.end.y] let pPoint = [pLine.start.x, pLine.start.y, pLine.end.x, pLine.end.y] @@ -4601,14 +4301,12 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) intersectionsByRoof.sort((a, b) => a.distance - b.distance) - console.log('intersectionsByRoof : ', intersectionsByRoof) //기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다. const otherIs = intersections.filter( (is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y), ) - console.log('otherIs', otherIs) if (otherIs.length > 0) { const analyze = linesAnalysis[otherIs[0].index] processed.add(otherIs[0].index) @@ -4636,20 +4334,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1 const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2 - /*const checkPrevLine = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], { - stroke: 'yellow', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - const checkNextLine = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkPrevLine, checkNextLine).renderAll()*/ - if (!isDiagonal) { const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines) if (drivePoint !== null) { @@ -4659,8 +4343,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } } - // const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) - // canvas.add(checkNewLine).renderAll() newAnalysis.push({ start: { x: linePoint[0], y: linePoint[1] }, @@ -4685,7 +4367,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { // 처리된 가선분 제외 linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index))) - console.log('lineAnalysis: ', linesAnalysis) canvas .getObjects() @@ -4795,7 +4476,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { .forEach((line) => { const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start)) const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end)) - console.log('startOnLine, endOnLine: ', startOnLine, endOnLine) const allLinesPoints = [] innerLines.forEach((innerLine) => { allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 }) @@ -4823,7 +4503,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //케라바에서 파생된 하단 지붕 라인처리 const downRoofGable = [] //처마에서 파생된 하단 지붕 라인 처리 - let downRoofEaves = [] + const downRoofEaves = [] + //roof lines에 조정해야하는 라인 처리 + // const adjustRoofLines = [] baseLines.forEach((baseLine, index) => { const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] @@ -4992,7 +4674,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //1. 현재 라인을 기준으로 지붕선 추가. //1-1 stdPoint을 현재라인의 지붕 출폭 만큼 조정 const currOffset = currLine.attributes.offset - const noGableLine = gableLine === prevLine ? nextLine : prevLine let roofLinePoint = stdPoint if (currVector.x === 0) { @@ -5273,9 +4954,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const vector = Math.sign(currLine.y1 - originPoint.y1) isFlowInside = isTopIn ? vector < 0 : vector > 0 } - console.log('isFlowInside', isFlowInside) const roofCheckPoint = { x: roofLine.x2 + roofVector.x, y: roofLine.y2 + roofVector.y } - console.log('roofCheck : ', roof.inPolygon(roofCheckPoint)) let otherLine = roof.inPolygon(roofCheckPoint) ? baseLines[nextIndex] : baseLines[prevIndex] //상단 지붕 @@ -5300,7 +4979,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) }) - const upDegree = getDegreeByChon(upLine.attributes.pitch) const upAnalyze = analyzeLine(upLine) const upRoofLine = upAnalyze.roofLine const addUpOffset = flowDistance //상단 지붕선의 추가길이 @@ -5340,10 +5018,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { downHipStartPoint = { x: downRoofPoint[0], y: downRoofPoint[1] } } - const checkUpLine = new fabric.Line(upRoofPoint, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' }) - const checkDownLine = new fabric.Line(downRoofPoint, { stroke: 'yellow', strokeWidth: 4, parentId: roofId, name: 'check' }) - canvas.add(checkUpLine, checkDownLine).renderAll() - //상단지붕선과 만나는 innerLines 조정 const upRoofEdge = { vertex1: { x: upRoofPoint[0], y: upRoofPoint[1] }, vertex2: { x: upRoofPoint[2], y: upRoofPoint[3] } } @@ -5429,11 +5103,16 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } downRoofLines.push(drawHipLine(upHipPoint, canvas, roof, textMode, hipDegree, hipDegree)) //각도 있는 처마지붕선 downRoofLines.push(drawRoofLine([connectPoint.x, connectPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode)) //각도 없는 처마지붕선 + + roof.adjustRoofLines.push({ point: upHipPoint, roofIdx: upRoofLine.idx }) + roof.adjustRoofLines.push({ point: [connectPoint.x, connectPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx }) } else { downRoofLines.push(drawRoofLine([upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode)) + roof.adjustRoofLines.push({ point: [upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx }) } } else { downRoofLines.push(drawRoofLine([upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode)) + roof.adjustRoofLines.push({ point: [upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx }) } //하단지붕선 추가. @@ -5461,6 +5140,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { downRoofLines.push( drawHipLine([intersectDownJoin.x, intersectDownJoin.y, joinEndPoint.x, joinEndPoint.y], canvas, roof, textMode, downDegree, downDegree), ) + roof.adjustRoofLines.push({ point: downRoofPoint, roofIdx: downRoofLine.idx }) } } }) @@ -5632,24 +5312,6 @@ const clamp01 = (t) => { return t } -/** - * 실제 각도를 계산한다 대각선인 경우 각도 조정 - * @param points - * @param degree - */ -const getRealDegree = (points, degree) => { - const deltaX = Math.abs(points[2] - points[0]) - const deltaY = Math.abs(points[3] - points[1]) - if (deltaX < 1 || deltaY < 1) { - return degree - } - - const hypotenuse = Math.sqrt(deltaX ** 2 + deltaY ** 2) - const adjacent = Math.sqrt(Math.pow(hypotenuse, 2) / 2) - const height = adjacent * Math.tan((degree * Math.PI) / 180) - return Math.atan2(height, hypotenuse) * (180 / Math.PI) -} - /** * 두 라인이 평행한지 확인한다. * @param line1 @@ -5807,7050 +5469,6 @@ const alreadyPoints = (lines, points) => { return has } -/** - * 마루가 있는 지붕을 그린다. - * @param roofId - * @param canvas - * @param textMode - */ -export const drawRidgeRoof = (roofId, canvas, textMode) => { - let roof = canvas?.getObjects().find((object) => object.id === roofId) - const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) - - const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) - if (hasNonParallelLines.length > 0) { - return - } - - const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] - const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] - - /** 외벽선 */ - const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) - const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - - /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ - baseLines - .filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.WALL && line.attributes.offset > 0) - .forEach((currentLine) => { - const prevLine = baseLines.find((line) => line.x2 === currentLine.x1 && line.y2 === currentLine.y1) - const nextLine = baseLines.find((line) => line.x1 === currentLine.x2 && line.y1 === currentLine.y2) - - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber() - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber() - const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) - const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) - - /** 현재 라인의 지붕 라인을 찾는다. */ - const intersectionRoofs = [] - let currentRoof - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY }, - vertex2: { x: currentMidX, y: currentMidY }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX, y: prevLine.y1 }, - vertex2: { x: currentMidX, y: currentMidY }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - if (currentRoof) { - const prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) - const nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) - - const prevOffset = prevLine.attributes.offset - const nextOffset = nextLine.attributes.offset - - currentRoof.set({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 }) - - if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && prevOffset > 0) { - const addPoint1 = [] - const addPoint2 = [] - if (Math.sign(prevLine.y2 - prevLine.y1) === 0) { - addPoint1.push(prevRoof.x2, prevRoof.y2, prevRoof.x2, currentRoof.y1) - addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1) - } else { - addPoint1.push(prevRoof.x2, prevRoof.y2, currentRoof.x1, prevRoof.y2) - addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1) - } - const addRoofLine1 = new QLine(addPoint1, { - name: 'addRoofLine', - parentId: roof.id, - fontSize: roof.fontSize, - stroke: '#1083E3', - strokeWidth: 2, - textMode: textMode, - attributes: { - roofId: roofId, - type: LINE_TYPE.WALLLINE.ETC, - planeSize: calcLinePlaneSize({ - x1: addPoint1[0], - y1: addPoint1[1], - x2: addPoint1[2], - y2: addPoint1[3], - }), - actualSize: calcLinePlaneSize({ - x1: addPoint1[0], - y1: addPoint1[1], - x2: addPoint1[2], - y2: addPoint1[3], - }), - }, - }) - - const addRoofLine2 = new QLine(addPoint2, { - name: 'addRoofLine', - parentId: roof.id, - fontSize: roof.fontSize, - stroke: '#1083E3', - strokeWidth: 2, - textMode: textMode, - attributes: { - roofId: roofId, - type: LINE_TYPE.WALLLINE.ETC, - planeSize: calcLinePlaneSize({ - x1: addPoint2[0], - y1: addPoint2[1], - x2: addPoint2[2], - y2: addPoint2[3], - }), - actualSize: calcLinePlaneSize({ - x1: addPoint2[0], - y1: addPoint2[1], - x2: addPoint2[2], - y2: addPoint2[3], - }), - }, - }) - canvas.add(addRoofLine1, addRoofLine2) - canvas.renderAll() - - const prevIndex = roof.lines.indexOf(prevRoof) - if (prevIndex === roof.lines.length - 1) { - roof.lines.unshift(addRoofLine1, addRoofLine2) - } else { - roof.lines.splice(prevIndex + 1, 0, addRoofLine1, addRoofLine2) - } - } else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) { - prevRoof.set({ x2: currentLine.x1, y2: currentLine.y1 }) - } - if (nextLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && nextOffset > 0) { - const addPoint1 = [] - const addPoint2 = [] - if (Math.sign(nextLine.y2 - nextLine.y1) === 0) { - addPoint1.push(currentRoof.x2, currentRoof.y2, nextRoof.x1, currentRoof.y2) - addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1) - } else { - addPoint1.push(currentRoof.x2, currentRoof.y2, currentRoof.x2, nextRoof.y1) - addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1) - } - - const addRoofLine1 = new QLine(addPoint1, { - name: 'addRoofLine', - parentId: roof.id, - fontSize: roof.fontSize, - stroke: '#1083E3', - strokeWidth: 2, - textMode: textMode, - attributes: { - roofId: roofId, - type: LINE_TYPE.WALLLINE.ETC, - planeSize: calcLinePlaneSize({ - x1: addPoint1[0], - y1: addPoint1[1], - x2: addPoint1[2], - y2: addPoint1[3], - }), - actualSize: calcLinePlaneSize({ - x1: addPoint1[0], - y1: addPoint1[1], - x2: addPoint1[2], - y2: addPoint1[3], - }), - }, - }) - - const addRoofLine2 = new QLine(addPoint2, { - name: 'addRoofLine', - parentId: roof.id, - fontSize: roof.fontSize, - stroke: '#1083E3', - strokeWidth: 2, - textMode: textMode, - attributes: { - roofId: roofId, - type: LINE_TYPE.WALLLINE.ETC, - planeSize: calcLinePlaneSize({ - x1: addPoint2[0], - y1: addPoint2[1], - x2: addPoint2[2], - y2: addPoint2[3], - }), - actualSize: calcLinePlaneSize({ - x1: addPoint2[0], - y1: addPoint2[1], - x2: addPoint2[2], - y2: addPoint2[3], - }), - }, - }) - canvas.add(addRoofLine1, addRoofLine2) - canvas.renderAll() - - const nextIndex = roof.lines.indexOf(nextRoof) - if (nextIndex === 0) { - roof.lines.push(addRoofLine1, addRoofLine2) - } else { - roof.lines.splice(nextIndex, 0, addRoofLine1, addRoofLine2) - } - } else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL) { - if (Math.sign(nextLine.y2 - nextLine.y1) === 0) { - nextRoof.set({ x1: currentLine.x2, y1: nextRoof.y1 }) - } else { - nextRoof.set({ x1: nextRoof.x1, y1: currentLine.y2 }) - } - currentRoof.set({ x2: nextRoof.x1, y2: nextRoof.y1 }) - } else if (nextOffset === 0) { - nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 }) - } - - roof = reDrawPolygon(roof, canvas) - } - }) - - /** 모양 판단을 위한 라인 처리. - * 평행한 라인이 나누어져 있는 경우 하나의 선으로 판단 한다. - */ - const drawBaseLines = [] - baseLines.forEach((currentLine, index) => { - let nextLine = baseLines[(index + 1) % baseLines.length] - let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - - let { x1, y1, x2, y2 } = currentLine - - if (currentAngle !== prevAngle || (currentAngle === prevAngle && currentLine.attributes.type !== prevLine.attributes.type)) { - if (currentAngle === nextAngle && currentLine.attributes.type === nextLine.attributes.type) { - let nextIndex = baseLines.findIndex((line) => line === nextLine) - while (nextIndex !== index) { - const checkNextLine = baseLines[(nextIndex + 1 + baseLines.length) % baseLines.length] - const checkAngle = calculateAngle(checkNextLine.startPoint, checkNextLine.endPoint) - if (currentAngle !== checkAngle) { - x2 = checkNextLine.x1 - y2 = checkNextLine.y1 - break - } else { - nextIndex = nextIndex + 1 - } - } - } - drawBaseLines.push({ x1, y1, x2, y2, line: currentLine, size: calcLinePlaneSize({ x1, y1, x2, y2 }) }) - } - }) - - /** baseLine을 기준으로 확인용 polygon 작성 */ - const checkWallPolygon = new QPolygon(baseLinePoints, {}) - - const drawEavesFirstLines = [] - const drawEavesSecondLines = [] - const drawGableRidgeFirst = [] - const drawGableRidgeSecond = [] - const drawGablePolygonFirst = [] - const drawGablePolygonSecond = [] - const drawHipAndGableFirst = [] - const drawWallRidgeLine = [] - - /** 모양을 판단한다. */ - drawBaseLines.forEach((currentBaseLine, index) => { - let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - - // const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { - // stroke: 'red', - // strokeWidth: 4, - // parentId: roofId, - // name: 'checkLine', - // }) - // canvas.add(checkLine) - // canvas.renderAll() - - const checkScale = Big(10) - const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) - const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - - const checkPoints = { - x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(), - y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(), - } - - /** 현재 라인이 처마유형일 경우 */ - if (currentLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { - if (nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { - /** - * 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다. - */ - if (prevLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { - if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { - /** - * 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다. - * @type {{x: *, y: *}} - */ - if (checkWallPolygon.inPolygon(checkPoints)) { - drawEavesFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } else { - drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } else { - drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } else if (eavesType.includes(nextLine.attributes?.type)) { - drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) { - if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { - if (checkWallPolygon.inPolygon(checkPoints)) { - drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } else { - drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } else { - if (currentAngle !== prevAngle && currentAngle !== nextAngle) { - drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } - } - } - - if (gableType.includes(currentLine.attributes?.type)) { - if ( - eavesType.includes(prevLine.attributes?.type) && - eavesType.includes(nextLine.attributes?.type) && - Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) - ) { - if (checkWallPolygon.inPolygon(checkPoints)) { - drawGableRidgeFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } else { - drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } - } - - if ( - LINE_TYPE.WALLLINE.HIPANDGABLE === currentLine.attributes?.type && - eavesType.includes(nextLine.attributes?.type) && - eavesType.includes(prevLine.attributes?.type) - ) { - drawHipAndGableFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - - if ( - LINE_TYPE.WALLLINE.WALL === currentLine.attributes?.type && - eavesType.includes(nextLine.attributes?.type) && - eavesType.includes(prevLine.attributes?.type) - ) { - if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) && checkWallPolygon.inPolygon(checkPoints)) { - drawWallRidgeLine.push({ currentBaseLine, prevBaseLine, nextBaseLine }) - } - } - }) - - drawEavesFirstLines.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) - drawGableRidgeFirst.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) - drawGableRidgeSecond.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) - drawWallRidgeLine.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size) - - /** 추녀마루 */ - let baseHipLines = [] - /** 용마루 */ - let baseRidgeLines = [] - /** 박공지붕 마루*/ - let baseGableRidgeLines = [] - - /** 박공지붕 라인*/ - let baseGableLines = [] - /** 용마루의 갯수*/ - let baseRidgeCount = 0 - - // console.log('drawEavesFirstLines :', drawEavesFirstLines) - // console.log('drawEavesSecondLines :', drawEavesSecondLines) - console.log('drawGableRidgeFirst: ', drawGableRidgeFirst) - console.log('drawGableRidgeSecond:', drawGableRidgeSecond) - console.log('drawGablePolygonFirst :', drawGablePolygonFirst) - console.log('drawGablePolygonSecond :', drawGablePolygonSecond) - // console.log('drawHipAndGableFirst :', drawHipAndGableFirst) - // console.log('drawWallLines :', drawWallRidgeLine) - - /** 박공지붕에서 파생되는 마루를 그린다. ㄷ 형태*/ - drawGableRidgeFirst.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - - if (prevBaseLine.size !== prevLine.attributes.planeSize) { - prevLine.x1 = prevBaseLine.x1 - prevLine.y1 = prevBaseLine.y1 - prevLine.x2 = prevBaseLine.x2 - prevLine.y2 = prevBaseLine.y2 - prevLine.setCoords() - } - if (nextBaseLine.size !== nextLine.attributes.planeSize) { - nextLine.x1 = nextBaseLine.x1 - nextLine.y1 = nextBaseLine.y1 - nextLine.x2 = nextBaseLine.x2 - nextLine.y2 = nextBaseLine.y2 - nextLine.setCoords() - } - - let { x1, x2, y1, y2, size } = currentBaseLine - let beforePrevBaseLine, afterNextBaseLine - - /** 이전 라인의 경사 */ - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - /** 다음 라인의 경사 */ - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - - /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ - drawBaseLines.forEach((line, index) => { - if (line === prevBaseLine) { - beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - } - if (line === nextBaseLine) { - afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] - } - }) - - const beforePrevLine = beforePrevBaseLine?.line - const afterNextLine = afterNextBaseLine?.line - - /** 각 라인의 흐름 방향을 확인한다. */ - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint) - const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint) - - /** 현재라인의 vector*/ - const currentVectorX = Math.sign(Big(x2).minus(Big(x1)).toNumber()) - const currentVectorY = Math.sign(Big(y2).minus(Big(y1)).toNumber()) - - /** 이전라인의 vector*/ - const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1))) - const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1))) - - /** 다음라인의 vector*/ - const nextVectorX = Math.sign(Big(nextLine.x2).minus(Big(nextLine.x1))) - const nextVectorY = Math.sign(Big(nextLine.y2).minus(Big(nextLine.y1))) - - /** 현재라인의 기준점*/ - let currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset)) - let currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset)) - - /** 마루 반대 좌표*/ - let oppositeMidX = currentMidX, - oppositeMidY = currentMidY - - /** 현재 라인의 지붕 라인을 찾는다. */ - const intersectionRoofs = [] - let currentRoof - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - - /** 현재 라인의 지붕선에서 이전 지붕선, 다음 지붕선으로 향하는 vector*/ - const prevRoofVectorX = Math.sign(currentRoof.x2 - currentRoof.x1) - const prevRoofVectorY = Math.sign(currentRoof.y2 - currentRoof.y1) - const nextRoofVectorX = Math.sign(currentRoof.x1 - currentRoof.x2) - const nextRoofVectorY = Math.sign(currentRoof.y1 - currentRoof.y2) - - /** 한개의 지붕선을 둘로 나누어서 처리 하는 경우 */ - if (prevAngle === beforePrevAngle || nextAngle === afterNextAngle) { - if (currentVectorX === 0) { - const addLength = Big(currentLine.y1).minus(Big(currentLine.y2)).abs().div(2) - const ridgeVector = Math.sign(prevLine.x1 - currentLine.x1) - oppositeMidX = Big(prevLine.x1).plus(Big(addLength).times(ridgeVector)) - - const ridgeEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - - const ridgeVectorX = Math.sign(ridgeEdge.vertex1.x - ridgeEdge.vertex2.x) - roof.lines - .filter((line) => line.x1 === line.x2) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(ridgeEdge, lineEdge) - if (is) { - const isVectorX = Math.sign(ridgeEdge.vertex1.x - is.x) - if ( - isVectorX === ridgeVectorX && - ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) - ) { - currentMidX = Big(is.x) - currentMidY = Big(is.y) - } - } - }) - } else { - const addLength = Big(currentLine.x1).minus(Big(currentLine.x2)).abs().div(2) - const ridgeVector = Math.sign(prevLine.y1 - currentLine.y1) - oppositeMidY = Big(prevLine.y1).plus(addLength.times(ridgeVector)) - - const ridgeEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - const ridgeVectorY = Math.sign(ridgeEdge.vertex1.y - ridgeEdge.vertex2.y) - roof.lines - .filter((line) => line.y1 === line.y2) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(ridgeEdge, lineEdge) - if (is) { - const isVectorY = Math.sign(ridgeEdge.vertex1.y - is.y) - if ( - isVectorY === ridgeVectorY && - ((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX)) - ) { - currentMidX = Big(is.x) - currentMidY = Big(is.y) - } - } - }) - } - - const prevHipEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: prevLine.x1, y: prevLine.y1 }, - } - const prevHipVectorX = Math.sign(prevHipEdge.vertex1.x - prevHipEdge.vertex2.x) - const prevHipVectorY = Math.sign(prevHipEdge.vertex1.y - prevHipEdge.vertex2.y) - const prevIsPoints = [] - roof.lines - .filter((line) => (Math.sign(prevLine.x1 - prevLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(prevHipEdge, lineEdge) - if (is) { - const isVectorX = Math.sign(prevHipEdge.vertex1.x - is.x) - const isVectorY = Math.sign(prevHipEdge.vertex1.y - is.y) - if (isVectorX === prevHipVectorX && isVectorY === prevHipVectorY) { - const size = Big(prevHipEdge.vertex1.x) - .minus(Big(is.x)) - .abs() - .pow(2) - .plus(Big(prevHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) - .sqrt() - .toNumber() - prevIsPoints.push({ is, size }) - } - } - }) - - if (prevIsPoints.length > 0) { - const prevIs = prevIsPoints.sort((a, b) => a.size - b.size)[0].is - const prevHipLine = drawHipLine( - [prevIs.x, prevIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - prevDegree, - ) - baseHipLines.push({ - x1: prevLine.x1, - y1: prevLine.y1, - x2: oppositeMidX.toNumber(), - y2: oppositeMidY.toNumber(), - line: prevHipLine, - }) - } - - const nextHipEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: nextLine.x2, y: nextLine.y2 }, - } - const nextHipVectorX = Math.sign(nextHipEdge.vertex1.x - nextHipEdge.vertex2.x) - const nextHipVectorY = Math.sign(nextHipEdge.vertex1.y - nextHipEdge.vertex2.y) - const nextIsPoints = [] - - roof.lines - .filter((line) => (Math.sign(nextLine.x1 - nextLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(nextHipEdge, lineEdge) - if (is) { - const isVectorX = Math.sign(nextHipEdge.vertex1.x - is.x) - const isVectorY = Math.sign(nextHipEdge.vertex1.y - is.y) - if (isVectorX === nextHipVectorX && isVectorY === nextHipVectorY) { - const size = Big(nextHipEdge.vertex1.x) - .minus(Big(is.x)) - .abs() - .pow(2) - .plus(Big(nextHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2)) - .sqrt() - .toNumber() - nextIsPoints.push({ is, size }) - } - } - }) - if (nextIsPoints.length > 0) { - const nextIs = nextIsPoints.sort((a, b) => a.size - b.size)[0].is - const nextHipLine = drawHipLine( - [nextIs.x, nextIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - - nextDegree, - nextDegree, - ) - baseHipLines.push({ - x1: nextLine.x2, - y1: nextLine.y2, - x2: oppositeMidX.toNumber(), - y2: oppositeMidY.toNumber(), - line: nextHipLine, - }) - } - const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) - const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) - - /** 반철처 인 경우 처리 */ - if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const width = Big(currentLine.attributes.width).div(2) - const degree = getDegreeByChon(currentLine.attributes.pitch) - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - if (vectorOppositeY === 0) { - currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) - } else { - currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) - } - - /** 현재 라인에서 반철처 부분을 그린다.*/ - let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint - if (vectorOppositeY === 0) { - firstHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } else { - firstHipPoint = [ - currentMidX.plus(Big(width).times(prevRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.plus(Big(width).times(nextRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } - connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] - firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] - secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, degree, degree) - const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, degree, degree) - const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, nextDegree, nextDegree) - const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) - baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) - } else { - const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] - const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, nextDegree, nextDegree) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - } - - if (baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridgeLine = drawRidgeLine( - [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - ) - baseGableRidgeLines.push(ridgeLine) - baseRidgeCount++ - } - } else { - if (beforePrevBaseLine === afterNextBaseLine) { - const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2) - const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2) - const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX)) - const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY)) - - let oppositeMidX, oppositeMidY - if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - const checkSize = currentMidX - .minus(afterNextMidX) - .pow(2) - .plus(currentMidY.minus(afterNextMidY).pow(2)) - .sqrt() - .minus(Big(afterNextLine.attributes.planeSize).div(20)) - .round(1) - oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg()) - oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg()) - - const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber()) - const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber()) - const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber()) - const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber()) - - let addOppositeX1 = 0, - addOppositeY1 = 0, - addOppositeX2 = 0, - addOppositeY2 = 0 - - if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { - const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt() - addOppositeX1 = checkScale.times(xVector1).toNumber() - addOppositeY1 = checkScale.times(yVector1).toNumber() - addOppositeX2 = checkScale.times(xVector2).toNumber() - addOppositeY2 = checkScale.times(yVector2).toNumber() - } - - let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() - scale1 = scale1.eq(0) ? Big(1) : scale1 - let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() - scale2 = scale2.eq(0) ? Big(1) : scale2 - - const checkHip1 = { - x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(), - y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(), - x2: oppositeMidX.plus(addOppositeX1).toNumber(), - y2: oppositeMidY.plus(addOppositeY1).toNumber(), - } - - const checkHip2 = { - x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(), - y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(), - x2: oppositeMidX.plus(addOppositeX2).toNumber(), - y2: oppositeMidY.plus(addOppositeY2).toNumber(), - } - - const intersection1 = findRoofIntersection(roof, checkHip1, { - x: oppositeMidX.plus(addOppositeX1), - y: oppositeMidY.plus(addOppositeY1), - }) - const intersection2 = findRoofIntersection(roof, checkHip2, { - x: oppositeMidX.plus(addOppositeX2), - y: oppositeMidY.plus(addOppositeY2), - }) - - const afterNextDegree = getDegreeByChon(afterNextLine.attributes.pitch) - - if (intersection1) { - const hipLine = drawHipLine( - [intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)], - canvas, - roof, - textMode, - - nextDegree, - afterNextDegree, - ) - baseHipLines.push({ - x1: afterNextLine.x1, - y1: afterNextLine.y1, - x2: oppositeMidX.plus(addOppositeX1).toNumber(), - y2: oppositeMidY.plus(addOppositeY1).toNumber(), - line: hipLine, - }) - } - if (intersection2) { - const hipLine = drawHipLine( - [intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)], - canvas, - roof, - textMode, - - prevDegree, - afterNextDegree, - ) - baseHipLines.push({ - x1: afterNextLine.x2, - y1: afterNextLine.y2, - x2: oppositeMidX.plus(addOppositeX2).toNumber(), - y2: oppositeMidY.plus(addOppositeY2).toNumber(), - line: hipLine, - }) - } - } else { - oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset)) - oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset)) - } - - const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) - const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) - - if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY) { - if (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - const intersectionPoints = [] - - roof.lines - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) - ) { - const size = Big(intersection.x) - .minus(currentMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - currentMidX = Big(intersection.x) - currentMidY = Big(intersection.y) - } - } - - if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { - const checkEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - const intersectionPoints = [] - - roof.lines - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) - ) { - const size = Big(intersection.x) - .minus(oppositeMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - oppositeMidX = Big(intersection.x) - oppositeMidY = Big(intersection.y) - } - } - - if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { - const width = afterNextLine.attributes.width - if (vectorOppositeY === 0) { - oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX)) - } else { - oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY)) - } - } - - if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const width = Big(afterNextLine.attributes.width).div(2).toNumber() - if (vectorOppositeY === 0) { - oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX)) - } else { - oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY)) - } - } - - /** 반철처 인 경우 처리 */ - if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const width = Big(currentLine.attributes.width).div(2) - const degree = getDegreeByChon(currentLine.attributes.pitch) - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - if (vectorMidY === 0) { - currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) - } else { - currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) - } - - /** 현재 라인에서 반철처 부분을 그린다.*/ - let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint - if (vectorMidY === 0) { - firstHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } else { - firstHipPoint = [ - currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } - connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] - firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] - secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, degree, degree) - const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, degree, degree) - const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, nextDegree, nextDegree) - const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) - baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) - } else { - const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] - const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, nextDegree, nextDegree) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - } - - if (baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridge = drawRidgeLine( - [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - ) - baseGableRidgeLines.push(ridge) - baseRidgeCount++ - } - } - } else { - const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1)) - const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1)) - - let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY - const beforePrevOffset = - currentAngle === beforePrevAngle - ? Big(beforePrevLine.attributes.offset) - : Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset) - const afterNextOffset = - currentAngle === afterNextAngle - ? Big(afterNextLine.attributes.offset) - : Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset) - const prevSize = Big(calcLinePlaneSize(prevLine)).div(10) - const nextSize = Big(calcLinePlaneSize(nextLine)).div(10) - - let prevHipCoords, nextHipCoords - - /** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ - if (eavesType.includes(afterNextLine.attributes?.type)) { - /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ - let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() - - const nextHalfVector = getHalfAngleVector(nextLine, afterNextLine) - let nextHipVector = { x: nextHalfVector.x, y: nextHalfVector.y } - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(nextLine.x2).plus(Big(nextHalfVector.x).times(10)), - y: Big(nextLine.y2).plus(Big(nextHalfVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() } - } - - const nextEndPoint = { - x: Big(nextLine.x2).plus(Big(nextHipVector.x).times(hipLength)), - y: Big(nextLine.y2).plus(Big(nextHipVector.y).times(hipLength)), - } - - let ridgeEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentMidX.plus(Big(nextVectorX).times(nextBaseLine.size)).toNumber(), - y: currentMidY.plus(Big(nextVectorY).times(nextBaseLine.size)).toNumber(), - }, - } - let hipEdge = { - vertex1: { x: nextLine.x2, y: nextLine.y2 }, - vertex2: { x: nextEndPoint.x, y: nextEndPoint.y }, - } - let intersection = edgesIntersection(ridgeEdge, hipEdge) - if (intersection) { - nextHipCoords = { x1: nextLine.x2, y1: nextLine.y2, x2: intersection.x, y2: intersection.y } - nextOppositeMidY = Big(intersection.y) - nextOppositeMidX = Big(intersection.x) - } - } else { - if (vectorMidX === 0) { - nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY)) - nextOppositeMidX = currentMidX - } else { - nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX)) - nextOppositeMidY = currentMidY - } - } - - /** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/ - if (eavesType.includes(beforePrevLine.attributes?.type)) { - /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ - let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt() - - const prevHalfVector = getHalfAngleVector(prevLine, beforePrevLine) - let prevHipVector = { x: prevHalfVector.x, y: prevHalfVector.y } - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(prevLine.x1).plus(Big(prevHalfVector.x).times(10)), - y: Big(prevLine.y1).plus(Big(prevHalfVector.y).times(10)), - } - - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() } - } - - const prevEndPoint = { - x: Big(prevLine.x1).plus(Big(prevHipVector.x).times(hipLength)), - y: Big(prevLine.y1).plus(Big(prevHipVector.y).times(hipLength)), - } - - let ridgeEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentMidX.plus(Big(prevVectorX).times(prevBaseLine.size)).toNumber(), - y: currentMidY.plus(Big(prevVectorY).times(prevBaseLine.size)).toNumber(), - }, - } - let hipEdge = { - vertex1: { x: prevLine.x1, y: prevLine.y1 }, - vertex2: { x: prevEndPoint.x, y: prevEndPoint.y }, - } - let intersection = edgesIntersection(ridgeEdge, hipEdge) - if (intersection) { - prevHipCoords = { x1: prevLine.x1, y1: prevLine.y1, x2: intersection.x, y2: intersection.y } - prevOppositeMidY = Big(intersection.y) - prevOppositeMidX = Big(intersection.x) - } - } else { - if (vectorMidX === 0) { - prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY)) - prevOppositeMidX = currentMidX - } else { - prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX)) - prevOppositeMidY = currentMidY - } - } - - const currentMidEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentVectorX === 0 ? nextLine.x2 : currentMidX.toNumber(), - y: currentVectorX === 0 ? currentMidY.toNumber() : nextLine.y2, - }, - } - - let oppositeLines = [] - drawBaseLines - .filter((line, index) => { - const currentLine = line.line - const nextLine = drawBaseLines[(index + 1) % drawBaseLines.length].line - const prevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length].line - const angle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - if (angle === prevAngle || angle === nextAngle) { - const sameAngleLine = angle === prevAngle ? prevLine : nextLine - if (gableType.includes(currentLine.attributes.type) && !gableType.includes(sameAngleLine.attributes.type)) { - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - } - } - } - return false - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(currentMidEdge, lineEdge) - if (intersection) { - if (line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) { - oppositeLines.push({ - line, - intersection, - size: Big(intersection.x) - .minus(currentMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) - .sqrt() - .toNumber(), - }) - } - } - }) - - if (oppositeLines.length > 0) { - const oppositePoint = oppositeLines.sort((a, b) => a.size - b.size)[0].intersection - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: oppositePoint.x, y: oppositePoint.y }, - } - const oppositeRoofPoints = [] - roof.lines - .filter((line) => { - const angle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - } - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) - ) { - oppositeRoofPoints.push({ - line, - intersection, - size: Big(intersection.x) - .minus(currentMidX.toNumber()) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) - .sqrt() - .toNumber(), - }) - } - }) - const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection - oppositeMidX = Big(oppositeRoofPoint.x) - oppositeMidY = Big(oppositeRoofPoint.y) - - const currentRoofPoints = [] - roof.lines - .filter((line) => { - const angle = calculateAngle(line.startPoint, line.endPoint) - return currentAngle === angle - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y)) - ) { - currentRoofPoints.push({ - line, - intersection, - size: Big(intersection.x) - .minus(currentMidX.toNumber()) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2)) - .sqrt() - .toNumber(), - }) - } - }) - const currentRoofPoint = currentRoofPoints.sort((a, b) => a.size - b.size)[0].intersection - currentMidX = Big(currentRoofPoint.x) - currentMidY = Big(currentRoofPoint.y) - } else { - const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt() - const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt() - - /** 두 포인트 중에 current와 가까운 포인트를 사용*/ - if (checkPrevSize.gt(checkNextSize)) { - if (nextHipCoords) { - let intersectPoints = [] - const hipEdge = { - vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, - vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 }, - } - - /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) && - Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y) - ) { - const intersectEdge = { - vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 }, - vertex2: { x: intersection.x, y: intersection.y }, - } - const is = edgesIntersection(intersectEdge, lineEdge) - if (!is.isIntersectionOutside) { - const intersectSize = Big(nextHipCoords.x2) - .minus(Big(intersection.x)) - .pow(2) - .plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - line, - }) - } - } - }) - const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] - if (intersect) { - const degree = getDegreeByChon(intersect.line.attributes.pitch) - const hipLine = drawHipLine( - [intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()], - canvas, - roof, - textMode, - - degree, - degree, - ) - baseHipLines.push({ - x1: nextHipCoords.x1, - y1: nextHipCoords.y1, - x2: nextHipCoords.x2, - y2: nextHipCoords.y2, - line: hipLine, - }) - } - } - oppositeMidY = nextOppositeMidY - oppositeMidX = nextOppositeMidX - } else { - if (prevHipCoords) { - let intersectPoints = [] - const hipEdge = { - vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, - vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 }, - } - - /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) && - Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y) - ) { - const intersectEdge = { - vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 }, - vertex2: { x: intersection.x, y: intersection.y }, - } - const is = edgesIntersection(intersectEdge, lineEdge) - if (!is.isIntersectionOutside) { - const intersectSize = Big(prevHipCoords.x2) - .minus(Big(intersection.x)) - .pow(2) - .plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - line, - }) - } - } - }) - const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0] - - if (intersect) { - const degree = getDegreeByChon(intersect.line.attributes.pitch) - const hipLine = drawHipLine( - [intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()], - canvas, - roof, - textMode, - - degree, - degree, - ) - baseHipLines.push({ - x1: prevHipCoords.x1, - y1: prevHipCoords.y1, - x2: prevHipCoords.x2, - y2: prevHipCoords.y2, - line: hipLine, - }) - } - } - oppositeMidY = prevOppositeMidY - oppositeMidX = prevOppositeMidX - } - } - - /** 포인트가 지붕 밖에 있는 경우 조정 */ - if (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - const intersectionPoints = [] - - roof.lines - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) - ) { - const size = Big(intersection.x) - .minus(currentMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - currentMidX = Big(intersection.x) - currentMidY = Big(intersection.y) - } - } - - if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) { - const checkEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - const intersectionPoints = [] - - roof.lines - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) - ) { - const size = Big(intersection.x) - .minus(oppositeMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - oppositeMidX = Big(intersection.x) - oppositeMidY = Big(intersection.y) - } - } - - const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX)) - const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY)) - - /** 반철처 인 경우 처리 */ - if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const width = Big(currentLine.attributes.width).div(2) - const degree = getDegreeByChon(currentLine.attributes.pitch) - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - if (vectorOppositeY === 0) { - currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX)) - } else { - currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY)) - } - - /** 현재 라인에서 반철처 부분을 그린다.*/ - let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint - if (vectorOppositeY === 0) { - firstHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(), - currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } else { - firstHipPoint = [ - currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - - secondHipPoint = [ - currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(), - currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(), - currentMidX.toNumber(), - currentMidY.toNumber(), - ] - } - connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]] - firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]] - secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, degree, degree) - const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, degree, degree) - const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, nextDegree, nextDegree) - const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine }) - baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine }) - } else { - const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()] - const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()] - const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, prevDegree, prevDegree) - const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, nextDegree, nextDegree) - baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine }) - baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine }) - } - - /** 마루가 맞은편 외벽선에 닿는 경우 해당 부분까지로 한정한다. */ - const ridgeEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - const ridgeVectorX = Math.sign(currentMidX.minus(oppositeMidX).toNumber()) - const ridgeVectorY = Math.sign(currentMidY.minus(oppositeMidY).toNumber()) - - roof.lines - .filter((line) => { - const lineVectorX = Math.sign(Big(line.x2).minus(Big(line.x1)).toNumber()) - const lineVectorY = Math.sign(Big(line.y2).minus(Big(line.y1)).toNumber()) - return ( - (lineVectorX === currentVectorX && lineVectorY !== currentVectorY) || (lineVectorX !== currentVectorX && lineVectorY === currentVectorY) - ) - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(ridgeEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(currentMidX).minus(intersection.x).toNumber()) - const isVectorY = Math.sign(Big(currentMidY).minus(intersection.y).toNumber()) - if (isVectorX === ridgeVectorX && isVectorY === ridgeVectorY) { - oppositeMidX = Big(intersection.x) - oppositeMidY = Big(intersection.y) - } - } - }) - if (baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridgeLine = drawRidgeLine( - [currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()], - canvas, - roof, - textMode, - ) - baseGableRidgeLines.push(ridgeLine) - baseRidgeCount++ - } - } - } - }) - - /** 박공지붕에서 파생되는 마루를 그린다. 첫번째에서 처리 하지 못한 라인이 있는 경우 */ - drawGableRidgeSecond.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - - /** 이전 라인의 경사 */ - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - /** 다음 라인의 경사 */ - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const currentVectorX = Big(currentLine.x2).minus(currentLine.x1) - const currentVectorY = Big(currentLine.y2).minus(currentLine.y1) - const checkVectorX = Big(nextLine.x2).minus(Big(nextLine.x1)) - const checkVectorY = Big(nextLine.y2).minus(Big(nextLine.y1)) - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - const checkSize = Big(10) - - /** 현재 라인의 지붕선을 찾는다. */ - const intersectionRoofs = [] - if (currentVectorX.eq(0)) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter( - (line) => - Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) && - Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()), - ) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter( - (line) => - Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) && - Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()), - ) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber(), - }) - } - } - }) - } - let currentRoof - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - - if (currentLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || currentLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { - const currentMidEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(), - y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(), - }, - } - let oppositeLines = [] - baseLines - .filter((line) => { - if (eavesType.includes(line.attributes.type)) { - const angle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - default: - return false - } - } else { - return false - } - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(currentMidEdge, lineEdge) - if (intersection) { - oppositeLines.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - }) - - if (oppositeLines.length === 0) { - return - } - const oppositeLine = oppositeLines.sort((a, b) => b.size - a.size)[0].line - - let ridgePoint - if (currentVectorY.eq(0)) { - const ridgeY = Big(currentLine.y1).plus(Big(oppositeLine.y1)).div(2).round() - ridgePoint = [currentRoof.x1, ridgeY.toNumber(), currentRoof.x2, ridgeY.toNumber()] - } else { - const ridgeX = Big(currentLine.x1).plus(Big(oppositeLine.x1)).div(2).round() - ridgePoint = [ridgeX.toNumber(), currentRoof.y1, ridgeX.toNumber(), currentRoof.y2] - } - const isAlreadyRidge = baseGableRidgeLines.find( - (line) => - (line.x1 === ridgePoint[0] && line.y1 === ridgePoint[1] && line.x2 === ridgePoint[2] && line.y2 === ridgePoint[3]) || - (line.x1 === ridgePoint[2] && line.y1 === ridgePoint[3] && line.x2 === ridgePoint[0] && line.y2 === ridgePoint[1]) || - segmentsOverlap(line, { x1: ridgePoint[0], y1: ridgePoint[1], x2: ridgePoint[2], y2: ridgePoint[3] }), - ) - if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) { - const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode) - baseGableRidgeLines.push(ridgeLine) - baseRidgeCount++ - } - } else { - const checkPoints = { - x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.toNumber()))).toNumber(), - y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.toNumber()))).toNumber(), - } - if (!checkWallPolygon.inPolygon(checkPoints)) { - const currentMidEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(), - y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(), - }, - } - - let oppositeLines = [] - baseLines - .filter((line, index) => { - let nextLine = baseLines[(index + 1) % baseLines.length] - let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - if ( - (gableType.includes(nextLine.attributes.type) && gableType.includes(prevLine.attributes.type)) || - (eavesType.includes(nextLine.attributes.type) && eavesType.includes(prevLine.attributes.type)) - ) { - const angle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - } - } - return false - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(currentMidEdge, lineEdge) - if (intersection) { - oppositeLines.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - }) - - if (oppositeLines.length === 0) { - return - } - const oppositeLine = oppositeLines.sort((a, b) => a.size - b.size)[0] - - let points = [] - if (eavesType.includes(oppositeLine.line.attributes.type)) { - const oppositeCurrentLine = oppositeLine.line - let oppositePrevLine, oppositeNextLine - baseLines.forEach((line, index) => { - if (line === oppositeCurrentLine) { - oppositePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - oppositeNextLine = baseLines[(index + 1) % baseLines.length] - } - }) - if (gableType.includes(oppositeNextLine.attributes.type) && gableType.includes(oppositePrevLine.attributes.type)) { - if (currentVectorX.eq(0)) { - const centerX = currentMidX.plus(oppositeLine.intersection.x).div(2).toNumber() - points = [centerX, currentLine.y1, centerX, currentLine.y2] - } else { - const centerY = currentMidY.plus(oppositeLine.intersection.y).div(2).toNumber() - points = [currentLine.x1, centerY, currentLine.x2, centerY] - } - } - if (eavesType.includes(oppositeNextLine.attributes.type) && eavesType.includes(oppositePrevLine.attributes.type)) { - /** 이전, 다음라인의 사잇각의 vector를 구한다. */ - let prevVector = getHalfAngleVector(oppositePrevLine, oppositeCurrentLine) - let nextVector = getHalfAngleVector(oppositeCurrentLine, oppositeNextLine) - - let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) } - let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) } - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(oppositeCurrentLine.x1).plus(Big(prevHipVector.x).times(10)), - y: Big(oppositeCurrentLine.y1).plus(Big(prevHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() } - } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(oppositeCurrentLine.x2).plus(Big(nextHipVector.x).times(10)), - y: Big(oppositeCurrentLine.y2).plus(Big(nextHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() } - } - - /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ - let hipLength = Big(oppositeCurrentLine.attributes.planeSize) - .div(2) - .pow(2) - .plus(Big(oppositeCurrentLine.attributes.planeSize).div(2).pow(2)) - .sqrt() - .div(10) - .round(2) - - const ridgeEndPoint = { - x: Big(oppositeCurrentLine.x1).plus(hipLength.times(prevHipVector.x)).round(1), - y: Big(oppositeCurrentLine.y1).plus(hipLength.times(prevHipVector.y)).round(1), - } - - const prevHypotenuse = Big(oppositePrevLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() - const prevHipPoints = { - x1: Big(oppositeCurrentLine.x1).plus(prevHypotenuse.times(prevHipVector.x.neg())).round(1).toNumber(), - y1: Big(oppositeCurrentLine.y1).plus(prevHypotenuse.times(prevHipVector.y.neg())).round(1).toNumber(), - x2: ridgeEndPoint.x.toNumber(), - y2: ridgeEndPoint.y.toNumber(), - } - - const nextHypotenuse = Big(oppositeNextLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt() - const nextHipPoints = { - x1: Big(oppositeCurrentLine.x2).plus(nextHypotenuse.times(nextHipVector.x.neg())).round(1).toNumber(), - y1: Big(oppositeCurrentLine.y2).plus(nextHypotenuse.times(nextHipVector.y.neg())).round(1).toNumber(), - x2: ridgeEndPoint.x.toNumber(), - y2: ridgeEndPoint.y.toNumber(), - } - - const prevIntersection = findRoofIntersection(roof, prevHipPoints, ridgeEndPoint) - const nextIntersection = findRoofIntersection(roof, nextHipPoints, ridgeEndPoint) - - if (prevIntersection) { - const prevHip = drawHipLine( - [prevIntersection.intersection.x, prevIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - prevDegree, - ) - baseHipLines.push({ - x1: oppositeCurrentLine.x1, - y1: oppositeCurrentLine.y1, - x2: ridgeEndPoint.x, - y2: ridgeEndPoint.y, - line: prevHip, - }) - } - if (nextIntersection) { - const nextHip = drawHipLine( - [nextIntersection.intersection.x, nextIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - nextDegree, - nextDegree, - ) - baseHipLines.push({ - x1: ridgeEndPoint.x, - y1: ridgeEndPoint.y, - x2: oppositeCurrentLine.x2, - y2: oppositeCurrentLine.y2, - line: nextHip, - }) - } - - const ridgeVectorX = Math.sign(currentMidX.minus(ridgeEndPoint.x).toNumber()) - const ridgeVectorY = Math.sign(currentMidY.minus(ridgeEndPoint.y).toNumber()) - const ridgePoints = { - x1: currentMidX.plus(Big(currentLine.attributes.offset).times(ridgeVectorX)).toNumber(), - y1: currentMidY.plus(Big(currentLine.attributes.offset).times(ridgeVectorY)).toNumber(), - x2: ridgeEndPoint.x.toNumber(), - y2: ridgeEndPoint.y.toNumber(), - } - const ridgeIntersection = findRoofIntersection(roof, ridgePoints, { - x: Big(ridgePoints.x2), - y: Big(ridgePoints.y2), - }) - if (ridgeIntersection) { - points = [ridgeIntersection.intersection.x, ridgeIntersection.intersection.y, ridgeEndPoint.x, ridgeEndPoint.y] - } - } - } else { - if (currentVectorX.eq(0)) { - points = [oppositeLine.intersection.x, currentLine.y1, oppositeLine.intersection.x, currentLine.y2] - } else { - points = [currentLine.x1, oppositeLine.intersection.y, currentLine.x2, oppositeLine.intersection.y] - } - } - - const isAlreadyRidge = baseGableRidgeLines.find( - (line) => - (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || - (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), - ) - if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) { - const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) - baseGableRidgeLines.push(ridgeLine) - baseRidgeCount++ - } - } else { - const oppositeLines = baseLines.filter((line) => { - const lineAngle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return lineAngle === -90 - case -90: - return lineAngle === 90 - case 0: - return lineAngle === 180 - case 180: - return lineAngle === 0 - } - }) - - if (oppositeLines.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { - let ridgePoints = [] - const oppositeLine = oppositeLines.sort((a, b) => { - let diffCurrentA, diffCurrentB - if (Math.sign(currentVectorX) === 0) { - diffCurrentA = currentMidY.minus(a.y1).abs() - diffCurrentB = currentMidY.minus(b.y1).abs() - } else { - diffCurrentA = currentMidX.minus(a.x1).abs() - diffCurrentB = currentMidX.minus(b.x1).abs() - } - return diffCurrentA.minus(diffCurrentB).toNumber() - })[0] - - const prevOffset = prevLine.attributes.offset - const nextOffset = nextLine.attributes.offset - if (Math.sign(currentVectorX) === 0) { - const prevY = Big(currentLine.y1) - .plus(Big(Math.sign(currentVectorY)).neg().times(prevOffset)) - .toNumber() - const nextY = Big(currentLine.y2) - .plus(Big(Math.sign(currentVectorY)).times(nextOffset)) - .toNumber() - const midX = Big(currentLine.x1).plus(oppositeLine.x1).div(2).toNumber() - ridgePoints = [midX, prevY, midX, nextY] - } else { - const prevX = Big(currentLine.x1) - .plus(Big(Math.sign(currentVectorX)).neg().times(prevOffset)) - .toNumber() - const nextX = Big(currentLine.x2) - .plus(Big(Math.sign(currentVectorX)).times(nextOffset)) - .toNumber() - const midY = Big(currentLine.y1).plus(oppositeLine.y1).div(2).toNumber() - ridgePoints = [prevX, midY, nextX, midY] - } - const isAlreadyRidge = baseGableRidgeLines.find( - (line) => - (line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) || - (line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]), - ) - if (!isAlreadyRidge) { - const ridge = drawRidgeLine(ridgePoints, canvas, roof, textMode) - baseGableRidgeLines.push(ridge) - baseRidgeCount++ - } - } - } - } - }) - - const uniqueRidgeLines = [] - /** 중복제거 */ - baseGableRidgeLines.forEach((currentLine, index) => { - if (index === 0) { - uniqueRidgeLines.push(currentLine) - } else { - const duplicateLines = uniqueRidgeLines.filter( - (line) => - (currentLine.x1 === line.x1 && currentLine.y1 === line.y1 && currentLine.x2 === line.x2 && currentLine.y2 === line.y2) || - (currentLine.x1 === line.x2 && currentLine.y1 === line.y2 && currentLine.x2 === line.x1 && currentLine.y2 === line.y1), - ) - if (duplicateLines.length === 0) { - uniqueRidgeLines.push(currentLine) - } - } - }) - - baseGableRidgeLines = uniqueRidgeLines - - /** 박공지붕 polygon 생성 */ - drawGablePolygonFirst.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - - const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) - const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) - const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) - const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - const intersectionRoofs = [] - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - let currentRoof, prevRoof, nextRoof - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - - if (currentRoof) { - prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) - nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) - - const prevRoofEdge = { - vertex1: { x: prevRoof.x2, y: prevRoof.y2 }, - vertex2: { x: prevRoof.x1, y: prevRoof.y1 }, - } - const nextRoofEdge = { - vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, - vertex2: { x: nextRoof.x2, y: nextRoof.y2 }, - } - - let polygonPoints = [ - { x: currentRoof.x1, y: currentRoof.y1 }, - { x: currentRoof.x2, y: currentRoof.y2 }, - ] - const prevHipLines = [] - const nextHipLines = [] - let prevLineRidge, nextLineRidge - - baseHipLines.forEach((current) => { - const { line } = current - if ( - (Math.abs(line.x1 - currentRoof.x1) <= 1 && - Math.abs(line.y1 - currentRoof.y1) <= 1 && - isPointOnLine(prevRoof, { x: line.x2, y: line.y2 })) || - (Math.abs(line.x2 - currentRoof.x1) <= 1 && - Math.abs(line.y2 - currentRoof.y1) <= 1 && - isPointOnLine(prevRoof, { - x: line.x1, - y: line.y1, - })) - ) { - prevHipLines.push(current) - } - if ( - (Math.abs(line.x1 - currentRoof.x2) <= 1 && - Math.abs(line.y1 - currentRoof.y2) <= 1 && - isPointOnLine(nextRoof, { x: line.x2, y: line.y2 })) || - (Math.abs(line.x2 - currentRoof.x2) <= 1 && - Math.abs(line.y2 - currentRoof.y2) <= 1 && - isPointOnLine(nextRoof, { - x: line.x1, - y: line.y1, - })) - ) { - nextHipLines.push(current) - } - }) - prevHipLines.forEach((current) => { - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - let findPoint - if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) { - findPoint = { x: current.x2, y: current.y2 } - } else { - findPoint = { x: current.x1, y: current.y1 } - } - baseHipLines - .filter( - (line) => - ((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) || - (Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) && - line.x1 !== line.x2 && - line.y1 !== line.y2, - ) - .forEach((line) => { - polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) - let ridgePoint - if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) { - ridgePoint = { x: line.x2, y: line.y2 } - } else { - ridgePoint = { x: line.x1, y: line.y1 } - } - prevLineRidge = baseGableRidgeLines.find( - (ridge) => - (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || - (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), - ) - }) - } else { - let ridgePoint - if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) { - ridgePoint = { x: current.x2, y: current.y2 } - } else { - ridgePoint = { x: current.x1, y: current.y1 } - } - - prevLineRidge = baseGableRidgeLines.find((ridge) => { - return ( - (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || - (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1) - ) - }) - } - }) - nextHipLines.forEach((current) => { - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - let findPoint - if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) { - findPoint = { x: current.x2, y: current.y2 } - } else { - findPoint = { x: current.x1, y: current.y1 } - } - - baseHipLines - .filter( - (line) => - ((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) || - (Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) && - line.x1 !== line.x2 && - line.y1 !== line.y2, - ) - .forEach((line) => { - polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }) - let ridgePoint - if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) { - ridgePoint = { x: line.x2, y: line.y2 } - } else { - ridgePoint = { x: line.x1, y: line.y1 } - } - nextLineRidge = baseGableRidgeLines.find( - (ridge) => - (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || - (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), - ) - }) - } else { - let ridgePoint - if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) { - ridgePoint = { x: current.x2, y: current.y2 } - } else { - ridgePoint = { x: current.x1, y: current.y1 } - } - - nextLineRidge = baseGableRidgeLines.find( - (ridge) => - (Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) || - (Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1), - ) - } - }) - - if (!prevLineRidge) { - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - } else { - const isRidgePoints = [] - baseGableRidgeLines.forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const intersection = edgesIntersection(prevRoofEdge, ridgeEdge) - if ( - intersection && - ((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) || - (ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1)) - ) { - const size = Big(intersection.x) - .minus(Big(currentRoof.x1)) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(Big(currentRoof.y1)).abs().pow(2)) - .sqrt() - isRidgePoints.push({ intersection, ridge, size }) - } - }) - if (isRidgePoints.length > 0) { - const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size) - prevLineRidge = sortedRidgePoints[0].ridge - } - } - } - if (!nextLineRidge) { - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - } else { - const isRidgePoints = [] - baseGableRidgeLines.forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const intersection = edgesIntersection(nextRoofEdge, ridgeEdge) - if ( - intersection && - ((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) || - (ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1)) - ) { - const size = Big(intersection.x) - .minus(Big(currentRoof.x2)) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(Big(currentRoof.y2)).abs().pow(2)) - .sqrt() - isRidgePoints.push({ intersection, ridge, size }) - } - }) - if (isRidgePoints.length > 0) { - const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size) - nextLineRidge = sortedRidgePoints[0].ridge - } - } - } - const ridgeLine = prevLineRidge === undefined ? nextLineRidge : prevLineRidge - - if (prevLineRidge !== undefined && nextLineRidge !== undefined) { - /** 4각*/ - if (prevLineRidge === nextLineRidge) { - polygonPoints.push({ x: ridgeLine.x1, y: ridgeLine.y1 }, { x: ridgeLine.x2, y: ridgeLine.y2 }) - /** 포인트가 직각 사각형인지 확인하여 아닌경우 직각인 다각형 포인트로 변경한다.*/ - const checkPoints = getSortedPoint(polygonPoints, baseHipLines) - let hasDiagonal = false - if (checkPoints < 4) { - hasDiagonal = true - } else { - checkPoints.forEach((point, index) => { - const nextPoint = checkPoints[(index + 1) % checkPoints.length] - if (point.x !== nextPoint.x && point.y !== nextPoint.y) { - hasDiagonal = true - } - }) - } - if (hasDiagonal) { - const vectorX = Math.sign(currentRoof.x1 - ridgeLine.x1) - const vectorY = Math.sign(currentRoof.y1 - ridgeLine.y1) - const ridgeMinX = Math.min(ridgeLine.x1, ridgeLine.x2) - const ridgeMaxX = Math.max(ridgeLine.x1, ridgeLine.x2) - const ridgeMinY = Math.min(ridgeLine.y1, ridgeLine.y2) - const ridgeMaxY = Math.max(ridgeLine.y1, ridgeLine.y2) - if ( - (!isPointOnLineNew(prevRoof, { x: ridgeLine.x1, y: ridgeLine.y1 }) && - !isPointOnLineNew(nextRoof, { x: ridgeLine.x1, y: ridgeLine.y1 })) || - (!isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) && !isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.y2 })) - ) { - roof.lines - .filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof) - .filter((line) => - ridgeLine.y1 === ridgeLine.y2 - ? vectorY === Math.sign(line.y1 - ridgeLine.y1) && - ridgeMinX <= line.x1 && - line.x1 <= ridgeMaxX && - ridgeMinX <= line.x2 && - line.x2 <= ridgeMaxX - : vectorX === Math.sign(line.x1 - ridgeLine.x1) && - ridgeMinY <= line.y1 && - line.y1 <= ridgeMaxY && - ridgeMinY <= line.y2 && - line.y2 <= ridgeMaxY, - ) - .forEach((line) => { - if (ridgeLine.y1 === ridgeLine.y2) { - if (vectorY === Math.sign(line.y1 - ridgeLine.y1)) { - polygonPoints.push({ x: line.x1, y: line.y1 }) - } - if (vectorY === Math.sign(line.y2 - ridgeLine.y1)) { - polygonPoints.push({ x: line.x2, y: line.y2 }) - } - } else { - if (vectorX === Math.sign(line.x1 - ridgeLine.x1)) { - polygonPoints.push({ x: line.x1, y: line.y1 }) - } - if (vectorX === Math.sign(line.x2 - ridgeLine.x1)) { - polygonPoints.push({ x: line.x2, y: line.y2 }) - } - } - }) - } - if ( - !isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) && - !isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) - ) { - } - } - } else { - /** 6각이상*/ - let isOverLap = - currentVectorX === 0 - ? (prevLineRidge.y1 <= nextLineRidge.y1 && prevLineRidge.y2 >= nextLineRidge.y1) || - (prevLineRidge.y1 >= nextLineRidge.y1 && prevLineRidge.y2 <= nextLineRidge.y1) || - (prevLineRidge.y1 <= nextLineRidge.y2 && prevLineRidge.y2 >= nextLineRidge.y2) || - (prevLineRidge.y1 >= nextLineRidge.y2 && prevLineRidge.y2 <= nextLineRidge.y2) - : (prevLineRidge.x1 <= nextLineRidge.x1 && prevLineRidge.x2 >= nextLineRidge.x1) || - (prevLineRidge.x1 >= nextLineRidge.x1 && prevLineRidge.x2 <= nextLineRidge.x1) || - (prevLineRidge.x1 <= nextLineRidge.x2 && prevLineRidge.x2 >= nextLineRidge.x2) || - (prevLineRidge.x1 >= nextLineRidge.x2 && prevLineRidge.x2 <= nextLineRidge.x2) - if (isOverLap) { - const prevDistance = currentVectorX === 0 ? Math.abs(prevLineRidge.x1 - currentRoof.x1) : Math.abs(prevLineRidge.y1 - currentRoof.y1) - const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - currentRoof.x1) : Math.abs(nextLineRidge.y1 - currentRoof.y1) - - /** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */ - if (Math.abs(prevDistance - nextDistance) < 1) { - const minX = Math.min(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2) - const maxX = Math.max(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2) - const minY = Math.min(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2) - const maxY = Math.max(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2) - if (currentVectorX === 0) { - polygonPoints.push({ x: prevLineRidge.x1, y: minY }, { x: prevLineRidge.x1, y: maxY }) - } else { - polygonPoints.push({ x: minX, y: prevLineRidge.y1 }, { x: maxX, y: prevLineRidge.y1 }) - } - } else if (prevDistance < nextDistance) { - polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 }) - - /** 이전라인과 교차한 마루의 포인트*/ - let prevRidgePoint1 - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - prevRidgePoint1 = polygonPoints.find( - (point) => - (point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1) || (point.x === prevLineRidge.x2 && point.y === prevLineRidge.y2), - ) - } else { - prevRidgePoint1 = - currentVectorX === 0 - ? currentRoof.y1 === prevLineRidge.y1 - ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } - : { x: prevLineRidge.x2, y: prevLineRidge.y2 } - : currentRoof.x1 === prevLineRidge.x1 - ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } - : { x: prevLineRidge.x2, y: prevLineRidge.y2 } - - polygonPoints.push(prevRidgePoint1) - } - - /** 다음 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ - let checkRidgePoint - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const ridgePoint1 = polygonPoints.filter((point) => point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1) - checkRidgePoint = ridgePoint1.length > 0 ? { x: nextLineRidge.x2, y: nextLineRidge.y2 } : { x: nextLineRidge.x1, y: nextLineRidge.y1 } - } else { - checkRidgePoint = - currentVectorX === 0 - ? currentRoof.y2 !== nextLineRidge.y1 - ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } - : { x: nextLineRidge.x2, y: nextLineRidge.y2 } - : currentRoof.x2 !== nextLineRidge.x1 - ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } - : { x: nextLineRidge.x2, y: nextLineRidge.y2 } - } - - const prevRidgePoint2 = - currentVectorX === 0 ? { x: prevRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: prevRidgePoint1.y } - polygonPoints.push(prevRidgePoint2) - } else { - polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 }) - - /** 다음라인과 교차한 마루의 포인트*/ - let nextRidgePoint1 - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - nextRidgePoint1 = polygonPoints.find( - (point) => - (point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1) || (point.x === nextLineRidge.x2 && point.y === nextLineRidge.y2), - ) - } else { - nextRidgePoint1 = - currentVectorX === 0 - ? currentRoof.y2 === nextLineRidge.y1 - ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } - : { x: nextLineRidge.x2, y: nextLineRidge.y2 } - : currentRoof.x2 === nextLineRidge.x1 - ? { x: nextLineRidge.x1, y: nextLineRidge.y1 } - : { x: nextLineRidge.x2, y: nextLineRidge.y2 } - polygonPoints.push(nextRidgePoint1) - } - - /** 이전 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/ - let checkRidgePoint - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const ridgePoint1 = polygonPoints.filter((point) => point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1) - checkRidgePoint = ridgePoint1.length > 0 ? { x: prevLineRidge.x2, y: prevLineRidge.y2 } : { x: prevLineRidge.x1, y: prevLineRidge.y1 } - } else { - checkRidgePoint = - currentVectorX === 0 - ? currentRoof.y1 !== prevLineRidge.y1 - ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } - : { x: prevLineRidge.x2, y: prevLineRidge.y2 } - : currentRoof.x1 !== prevLineRidge.x1 - ? { x: prevLineRidge.x1, y: prevLineRidge.y1 } - : { x: prevLineRidge.x2, y: prevLineRidge.y2 } - } - - const nextRidgePoint2 = - currentVectorX === 0 ? { x: nextRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: nextRidgePoint1.y } - polygonPoints.push(nextRidgePoint2) - } - } else { - /** 마루가 겹치지 않을때 */ - const otherRidgeLines = [] - - baseGableRidgeLines - .filter((ridge) => ridge !== prevLineRidge && ridge !== nextLineRidge) - .filter((ridge) => (currentVectorX === 0 ? ridge.x1 === ridge.x2 : ridge.y1 === ridge.y2)) - .filter((ridge) => - currentVectorX === 0 ? nextVectorX === Math.sign(nextLine.x1 - ridge.x1) : nextVectorY === Math.sign(nextLine.y1 - ridge.y1), - ) - .forEach((ridge) => { - const size = currentVectorX === 0 ? Math.abs(nextLine.x1 - ridge.x1) : Math.abs(nextLine.y1 - ridge.y1) - otherRidgeLines.push({ ridge, size }) - }) - if (otherRidgeLines.length > 0) { - const otherRidge = otherRidgeLines.sort((a, b) => a.size - b.size)[0].ridge - /** - * otherRidge이 prevRidgeLine, nextRidgeLine 과 currentLine의 사이에 있는지 확인해서 분할하여 작업 - * 지붕의 덮힘이 다르기 때문 - */ - const isInside = - currentVectorX === 0 - ? Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - prevLineRidge.x1) && - Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - nextLineRidge.x1) - : Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - prevLineRidge.y1) && - Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - nextLineRidge.y1) - - if (isInside) { - polygonPoints.push( - { x: prevLineRidge.x1, y: prevLineRidge.y1 }, - { x: prevLineRidge.x2, y: prevLineRidge.y2 }, - { x: nextLineRidge.x1, y: nextLineRidge.y1 }, - { x: nextLineRidge.x2, y: nextLineRidge.y2 }, - ) - - let ridgeAllPoints = [ - { x: prevLineRidge.x1, y: prevLineRidge.y1 }, - { x: prevLineRidge.x2, y: prevLineRidge.y2 }, - { x: nextLineRidge.x1, y: nextLineRidge.y1 }, - { x: nextLineRidge.x2, y: nextLineRidge.y2 }, - ] - let ridgePoints = [] - ridgeAllPoints.forEach((point) => { - let isOnLine = false - roof.lines.forEach((line) => { - if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { - isOnLine = true - } - }) - if (!isOnLine) { - ridgePoints.push(point) - } - }) - if (ridgePoints.length === 2) { - if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { - polygonPoints.push( - { x: otherRidge.x1, y: ridgePoints[0].y }, - { - x: otherRidge.x1, - y: ridgePoints[1].y, - }, - ) - } else { - polygonPoints.push( - { x: ridgePoints[0].x, y: otherRidge.y1 }, - { - x: ridgePoints[1].x, - y: otherRidge.y1, - }, - ) - } - } - } else { - polygonPoints.push({ x: otherRidge.x1, y: otherRidge.y1 }, { x: otherRidge.x2, y: otherRidge.y2 }) - - let ridgePoints = [ - { x: prevLineRidge.x1, y: prevLineRidge.y1 }, - { x: prevLineRidge.x2, y: prevLineRidge.y2 }, - { x: nextLineRidge.x1, y: nextLineRidge.y1 }, - { x: nextLineRidge.x2, y: nextLineRidge.y2 }, - ] - - ridgePoints.forEach((point) => { - let isOnLine = false - roof.lines.forEach((line) => { - if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) { - isOnLine = true - } - }) - if (isOnLine) { - polygonPoints.push(point) - } - }) - - if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) { - const prevY = - (prevLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= prevLineRidge.y2) || - (prevLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= prevLineRidge.y2) - ? otherRidge.y1 - : otherRidge.y2 - const nextY = - (nextLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= nextLineRidge.y2) || - (nextLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= nextLineRidge.y2) - ? otherRidge.y1 - : otherRidge.y2 - polygonPoints.push({ x: prevLineRidge.x1, y: prevY }, { x: nextLineRidge.x1, y: nextY }) - } else { - const prevX = - (prevLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= prevLineRidge.x2) || - (prevLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= prevLineRidge.x2) - ? otherRidge.x1 - : otherRidge.x2 - const nextX = - (nextLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= nextLineRidge.x2) || - (nextLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= nextLineRidge.x2) - ? otherRidge.x1 - : otherRidge.x2 - polygonPoints.push({ x: prevX, y: prevLineRidge.y1 }, { x: nextX, y: nextLineRidge.y1 }) - } - } - } - } - } - } else { - if (ridgeLine) { - const ridgeEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } } - const prevRoofEdge = { vertex1: { x: prevRoof.x1, y: prevRoof.y1 }, vertex2: { x: prevRoof.x2, y: prevRoof.y2 } } - const nextRoofEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } } - const isPrevRoof = edgesIntersection(prevRoofEdge, ridgeEdge) - const isNextRoof = edgesIntersection(nextRoofEdge, ridgeEdge) - if (isPrevRoof && isPointOnLine(ridgeLine, isPrevRoof)) { - polygonPoints.push({ x: isPrevRoof.x, y: isPrevRoof.y }) - } else { - polygonPoints.push({ x: prevRoof.x1, y: prevRoof.y1 }) - } - if (isNextRoof && isPointOnLine(ridgeLine, isNextRoof)) { - polygonPoints.push({ x: isNextRoof.x, y: isNextRoof.y }) - } else { - polygonPoints.push({ x: nextRoof.x2, y: nextRoof.y2 }) - } - - roof.lines - .filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(ridgeEdge, lineEdge) - if (intersection && isPointOnLine(ridgeLine, intersection)) { - const size1 = Math.sqrt(Math.pow(line.x1 - intersection.x, 2) + Math.pow(line.y1 - intersection.y, 2)) - const size2 = Math.sqrt(Math.pow(line.x2 - intersection.x, 2) + Math.pow(line.y2 - intersection.y, 2)) - if (size1 < size2) { - polygonPoints.push({ x: line.x2, y: line.y2 }) - } else { - polygonPoints.push({ x: line.x1, y: line.y1 }) - } - polygonPoints.push({ x: intersection.x, y: intersection.y }) - } - }) - } - } - /** 중복되는 포인트 제거 */ - const uniquePoints = [] - polygonPoints.forEach((point) => { - const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y) - if (!isAlready) { - uniquePoints.push(point) - } - }) - polygonPoints = getSortedPoint(uniquePoints, baseHipLines) - - /*polygonPoints.forEach((point) => { - const checkCircle = new fabric.Circle({ - left: point.x, - top: point.y, - radius: 4, - fill: 'red', - parentId: roofId, - name: 'checkCircle', - }) - canvas.add(checkCircle) - canvas.renderAll() - - /!** 확인용 라인 제거 *!/ - canvas - .getObjects() - .filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() - })*/ - - polygonPoints.forEach((currentPoint, index) => { - const nextPoint = polygonPoints[(index + 1) % polygonPoints.length] - const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y] - const isParallel = ridgeLine.x1 === ridgeLine.x2 ? currentPoint.x === nextPoint.x : currentPoint.y === nextPoint.y - const ridgeLines = baseGableRidgeLines.filter( - (line) => - (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || - (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), - ) - const hipLine = baseHipLines.filter( - (line) => - (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || - (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), - ) - /** 이미 존재하는 라인이면 넘긴다.*/ - if (ridgeLines.length > 0 || hipLine.length > 0) { - return - } - - let line - if (isParallel) { - line = drawRoofLine(points, canvas, roof, textMode) - baseGableLines.push(line) - } else { - line = drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree) - baseHipLines.push({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, line }) - } - }) - } - }) - - drawGablePolygonSecond.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let { x1, x2, y1, y2 } = currentBaseLine - - /*const checkLine = new fabric.Line([x1, y1, x2, y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'checkLine', - }) - canvas.add(checkLine) - canvas.renderAll()*/ - - const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) - const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - const intersectionRoofs = [] - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), - }) - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), - }) - } - }) - } - let currentRoof, prevRoof, nextRoof - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) - nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) - } - - let polygonPoints = [] - if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) { - const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) - const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - - const checkPoints = { - x: currentMidX.plus(Big(10).times(Math.sign(xVector.toNumber()))).toNumber(), - y: currentMidY.plus(Big(10).times(Math.sign(yVector.toNumber()))).toNumber(), - } - - if (checkWallPolygon.inPolygon(checkPoints)) { - const currentRidge = baseGableRidgeLines.find((line) => - currentVectorX === 0 - ? (line.y1 === y1 && line.y2 === y2) || (line.y1 === y2 && line.y2 === y1) - : (line.x1 === x1 && line.x2 === x2) || (line.x1 === x2 && line.x2 === x1), - ) - if (currentRidge) { - const ridgeVectorX = Math.sign(currentRidge.x1 - currentRidge.x2) - const ridgeVectorY = Math.sign(currentRidge.y1 - currentRidge.y2) - - let checkEdge - if (currentVectorX === 0) { - checkEdge = { - vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, - vertex2: { x: currentLine.x1, y: currentRidge.y1 }, - } - } else { - checkEdge = { - vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, - vertex2: { x: currentRidge.x1, y: currentLine.y1 }, - } - } - const isRoofLines = [] - roof.lines - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(checkEdge, lineEdge) - if (is) { - const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) - const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) - const isVectorX = Math.sign(checkEdge.vertex1.x - is.x) - const isVectorY = Math.sign(checkEdge.vertex1.y - is.y) - if ((ridgeVectorX === 0 && checkVectorX === isVectorX) || (ridgeVectorY === 0 && checkVectorY === isVectorY)) { - const size = ridgeVectorX === 0 ? Math.abs(checkEdge.vertex1.x - is.x) : Math.abs(checkEdge.vertex1.y - is.y) - isRoofLines.push({ line, size }) - } - } - }) - isRoofLines.sort((a, b) => a.size - b.size) - const roofLine = isRoofLines[0].line - polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) - if (ridgeVectorX === 0) { - polygonPoints.push({ x: roofLine.x1, y: currentRidge.y1 }, { x: roofLine.x1, y: currentRidge.y2 }) - } else { - polygonPoints.push({ x: currentRidge.x1, y: roofLine.y1 }, { x: currentRidge.x2, y: roofLine.y1 }) - } - } - } else { - const currentRidge = baseGableRidgeLines.find((line) => - currentVectorX === 0 - ? (Math.abs(line.y1 - currentRoof.y1) < 1 && Math.abs(line.y2 - currentRoof.y2) < 1) || - (Math.abs(line.y1 - currentRoof.y2) < 1 && Math.abs(line.y2 - currentRoof.y1) < 1) - : (Math.abs(line.x1 - currentRoof.x1) < 1 && Math.abs(line.x2 - currentRoof.x2) < 1) || - (Math.abs(line.x1 - currentRoof.x2) < 1 && Math.abs(line.x2 - currentRoof.x1) < 1), - ) - if (currentRidge) { - if (currentVectorX === 0) { - polygonPoints.push({ x: currentRoof.x1, y: currentLine.y1 }, { x: currentRoof.x2, y: currentLine.y2 }) - polygonPoints.push({ x: currentRidge.x1, y: currentLine.y1 }, { x: currentRidge.x1, y: currentLine.y2 }) - } else { - polygonPoints.push({ x: currentLine.x1, y: currentRoof.y1 }, { x: currentLine.x2, y: currentRoof.y2 }) - polygonPoints.push({ x: currentLine.x1, y: currentRidge.y1 }, { x: currentLine.x2, y: currentRidge.y1 }) - } - } - } - } else { - const prevEdge = { vertex1: { x: prevRoof.x2, y: prevRoof.y2 }, vertex2: { x: prevRoof.x1, y: prevRoof.y1 } } - const prevVectorX = Math.sign(prevRoof.x1 - prevRoof.x2) - const prevVectorY = Math.sign(prevRoof.y1 - prevRoof.y2) - - let prevRidge, nextRidge - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const hipLines = [] - baseHipLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(prevEdge, lineEdge) - if (is) { - const size = Big(is.x).minus(prevRoof.x2).abs().pow(2).plus(Big(is.y).minus(prevRoof.y2).abs().pow(2)).sqrt().toNumber() - hipLines.push({ line, is, size }) - } - }) - if (hipLines.length > 0) { - hipLines.sort((a, b) => a.size - b.size) - const hipLine = hipLines[0] - polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y }) - const ridgePoint = - hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1 - ? { x: hipLine.line.x2, y: hipLine.line.y2 } - : { x: hipLine.line.x1, y: hipLine.line.y1 } - prevRidge = baseGableRidgeLines.find( - (line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y), - ) - } - } else { - const prevRidges = [] - baseGableRidgeLines.forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const is = edgesIntersection(prevEdge, ridgeEdge) - if (is) { - const isVectorX = Math.sign(prevRoof.x1 - is.x) - const isVectorY = Math.sign(prevRoof.y1 - is.y) - if ( - isVectorX === prevVectorX && - isVectorY === prevVectorY && - ((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1)) - ) { - const size = Big(prevRoof.x1).minus(is.x).abs().pow(2).plus(Big(prevRoof.y1).minus(is.y).abs().pow(2)).sqrt().toNumber() - prevRidges.push({ ridge, size }) - } - } - }) - if (prevRidges.length > 0) { - prevRidges.sort((a, b) => a.size - b.size) - prevRidge = prevRidges[0].ridge - } - } - - const nextEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } } - const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2) - const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2) - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - const hipLines = [] - baseHipLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(nextEdge, lineEdge) - if (is) { - const size = Big(is.x).minus(nextRoof.x1).abs().pow(2).plus(Big(is.y).minus(nextRoof.y1).abs().pow(2)).sqrt().toNumber() - hipLines.push({ line, is, size }) - } - }) - if (hipLines.length > 0) { - hipLines.sort((a, b) => a.size - b.size) - const hipLine = hipLines[0] - polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y }) - const ridgePoint = - hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1 - ? { x: hipLine.line.x2, y: hipLine.line.y2 } - : { x: hipLine.line.x1, y: hipLine.line.y1 } - nextRidge = baseGableRidgeLines.find( - (line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y), - ) - } - } else { - const nextRidges = [] - baseGableRidgeLines.forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const is = edgesIntersection(nextEdge, ridgeEdge) - if (is) { - const isVectorX = Math.sign(nextRoof.x1 - is.x) - const isVectorY = Math.sign(nextRoof.y1 - is.y) - if ( - isVectorX === nextVectorX && - isVectorY === nextVectorY && - ((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1)) - ) { - const size = Big(nextLine.x1).minus(is.x).abs().pow(2).plus(Big(nextLine.y1).minus(is.y).abs().pow(2)).sqrt().toNumber() - nextRidges.push({ ridge, size }) - } - } - }) - if (nextRidges.length > 0) { - nextRidges.sort((a, b) => a.size - b.size) - nextRidge = nextRidges[0].ridge - } - } - - let currentRidge - - if (prevRidge) { - if (currentVectorX === 0) { - const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) - const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) - if (minY <= prevRidge.y1 && maxY >= prevRidge.y1 && minY <= prevRidge.y2 && maxY >= prevRidge.y2) { - currentRidge = prevRidge - } - } - if (currentVectorY === 0) { - const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) - const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) - - if (minX <= prevRidge.x1 && maxX >= prevRidge.x1 && minX <= prevRidge.x2 && maxX >= prevRidge.x2) { - currentRidge = prevRidge - } - } - } - if (nextRidge) { - if (currentVectorX === 0) { - const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) - const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2) - if (minY <= nextRidge.y1 && maxY >= nextRidge.y1 && minY <= nextRidge.y2 && maxY >= nextRidge.y2) { - currentRidge = nextRidge - } - } - if (currentVectorY === 0) { - const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) - const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2) - - if (minX <= nextRidge.x1 && maxX >= nextRidge.x1 && minX <= nextRidge.x2 && maxX >= nextRidge.x2) { - currentRidge = nextRidge - } - } - } - - if (currentRidge) { - polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 }) - - /** 기준점이 될 포인트를 찾는다. 기준점 = 지붕선이나 hip 등에 붙지 않은 포인트 */ - const checkRidgePoints = [ - { x: currentRidge.x1, y: currentRidge.y1, checkPoint: true }, - { x: currentRidge.x2, y: currentRidge.y2, checkPoint: true }, - ] - const ridgeEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x2, y: currentRidge.y2 } } - /** 포인트가 지붕선과 붙은 경우 */ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(ridgeEdge, lineEdge) - if (is) { - if (Math.abs(is.x - currentRidge.x1) < 1 && Math.abs(is.y - currentRidge.y1) < 1) { - checkRidgePoints[0].checkPoint = false - } - if (Math.abs(is.x - currentRidge.x2) < 1 && Math.abs(is.y - currentRidge.y2) < 1) { - checkRidgePoints[1].checkPoint = false - } - } - }) - /** 포인트가 hip과 붙은 경우 */ - baseHipLines - .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) - .forEach((line) => { - if ( - (Math.abs(line.x1 - currentRidge.x1) < 1 && Math.abs(line.y1 - currentRidge.y1) < 1) || - (Math.abs(line.x2 - currentRidge.x1) < 1 && Math.abs(line.y2 - currentRidge.y1) < 1) - ) { - checkRidgePoints[0].checkPoint = false - } - if ( - (Math.abs(line.x1 - currentRidge.x2) < 1 && Math.abs(line.y1 - currentRidge.y2) < 1) || - (Math.abs(line.x2 - currentRidge.x2) < 1 && Math.abs(line.y2 - currentRidge.y2) < 1) - ) { - checkRidgePoints[1].checkPoint = false - } - }) - - const checkRidgePoint = checkRidgePoints.find((point) => point.checkPoint) - - if (!checkRidgePoint) return - /** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/ - let checkEdge - if (currentVectorX === 0) { - checkEdge = { - vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y }, - vertex2: { x: currentRoof.x1, y: checkRidgePoint.y }, - } - } else { - checkEdge = { - vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y }, - vertex2: { x: checkRidgePoint.x, y: currentRoof.y1 }, - } - } - const currentRoofEdge = { vertex1: { x: currentRoof.x1, y: currentRoof.y1 }, vertex2: { x: currentRoof.x2, y: currentRoof.y2 } } - - const isRoofPoint = edgesIntersection(checkEdge, currentRoofEdge) - if (isRoofPoint) { - if (currentVectorX === 0) { - const minY = Math.min(currentRoof.y1, currentRoof.y2, isRoofPoint.y) - const maxY = Math.max(currentRoof.y1, currentRoof.y2, isRoofPoint.y) - polygonPoints.push({ x: isRoofPoint.x, y: minY }, { x: isRoofPoint.x, y: maxY }) - } else { - const minX = Math.min(currentRoof.x1, currentRoof.x2, isRoofPoint.x) - const maxX = Math.max(currentRoof.x1, currentRoof.x2, isRoofPoint.x) - polygonPoints.push({ x: minX, y: isRoofPoint.y }, { x: maxX, y: isRoofPoint.y }) - } - } - } - } - - const uniquePoints = [] - polygonPoints.forEach((point) => { - const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y) - if (!isAlready) { - uniquePoints.push(point) - } - }) - - polygonPoints = getSortedPoint(uniquePoints, baseHipLines) - polygonPoints.forEach((currentPoint, index) => { - const nextPoint = polygonPoints[(index + 1) % polygonPoints.length] - const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y] - - const ridgeLines = baseGableRidgeLines.filter( - (line) => - (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || - (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), - ) - const hipLines = baseHipLines.filter( - (line) => - (line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) || - (line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]), - ) - /** 이미 존재하는 라인이면 넘긴다.*/ - if (ridgeLines.length > 0 || hipLines.length > 0) { - return - } - - const hipLine = drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree) - if (currentVectorX === 0) { - if (Math.sign(currentPoint.x - nextPoint.x) === 0) { - hipLine.attributes.actualSize = hipLine.attributes.planeSize - } - } else { - if (Math.sign(currentPoint.y - nextPoint.y) === 0) { - hipLine.attributes.actualSize = hipLine.attributes.planeSize - } - } - baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine }) - }) - }) - - /** 케라바 지붕에 연결된 마루 중 처마라인이 그려지지 않은 경우 확*/ - baseGableRidgeLines.forEach((ridge) => { - const ridgeVectorX = Math.sign(ridge.x1 - ridge.x2) - const ridgeVectorY = Math.sign(ridge.y1 - ridge.y2) - - const firstGableLines = [] - const secondGableLines = [] - - baseGableLines - .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) - .filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1)) - .forEach((line) => firstGableLines.push(line)) - baseGableLines - .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) - .filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) - .forEach((line) => secondGableLines.push(line)) - .filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1)) - .forEach((line) => firstGableLines.push(line)) - baseHipLines - .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2)) - .filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) - .forEach((line) => secondGableLines.push(line)) - - let degree1, degree2 - if (firstGableLines.length < 2 || secondGableLines.length < 2) { - drawBaseLines.forEach((currentBaseLine, index) => { - let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length] - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - - if ( - gableType.includes(currentLine.attributes?.type) && - eavesType.includes(prevLine.attributes?.type) && - eavesType.includes(nextLine.attributes?.type) && - Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) - ) { - if ( - ridgeVectorX === 0 && - currentLine.x1 !== currentLine.x2 && - ((currentLine.x1 <= ridge.x1 && ridge.x1 <= currentLine.x2) || (currentLine.x2 <= ridge.x1 && ridge.x1 <= currentLine.x1)) - ) { - degree1 = getDegreeByChon(prevLine.attributes.pitch) - degree2 = getDegreeByChon(nextLine.attributes.pitch) - } - - if ( - ridgeVectorY === 0 && - currentLine.y1 !== currentLine.y2 && - ((currentLine.y1 <= ridge.y1 && ridge.y1 <= currentLine.y2) || (currentLine.y2 <= ridge.y1 && ridge.y1 <= currentLine.y1)) - ) { - degree1 = getDegreeByChon(prevLine.attributes.pitch) - degree2 = getDegreeByChon(nextLine.attributes.pitch) - } - } - }) - } - - if (firstGableLines.length < 2) { - const connectRoof = roof.lines.find( - (line) => - (line.x1 <= ridge.x1 && line.x2 >= ridge.x1 && line.y1 <= ridge.y1 && line.y2 >= ridge.y1) || - (line.x2 <= ridge.x1 && line.x1 >= ridge.x1 && line.y2 <= ridge.y1 && line.y1 >= ridge.y1), - ) - if (connectRoof) { - let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x1, ridge.y1] - let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x1, ridge.y1] - let intersectPoints1 - let intersectPoints2 - baseHipLines - .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) - .forEach((hip) => { - const line = hip.line - if ( - (hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) || - (hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1]) - ) { - intersectPoints1 = { x: line.x1, y: line.y1 } - } - if ( - (hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) || - (hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1]) - ) { - intersectPoints1 = { x: line.x2, y: line.y2 } - } - if ( - (hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) || - (hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1]) - ) { - intersectPoints2 = { x: line.x1, y: line.y1 } - } - if ( - (hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) || - (hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1]) - ) { - intersectPoints2 = { x: line.x2, y: line.y2 } - } - }) - if (intersectPoints1) { - hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x1, ridge.y1] - } - if (intersectPoints2) { - hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x1, ridge.y1] - } - - if (hipPoint1) { - const hipLines = baseHipLines.filter( - (line) => - (line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) || - (line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]), - ) - - const gableLines = baseGableLines.filter( - (line) => - (line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) || - (line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]), - ) - - if (gableLines.length === 0 && hipLines.length === 0) { - const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, degree1, degree1) - baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 }) - } - } - if (hipPoint2) { - const hipLines = baseHipLines.filter( - (line) => - (line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) || - (line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]), - ) - const gableLines = baseGableLines.filter( - (line) => - (line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) || - (line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]), - ) - if (hipLines.length === 0 && gableLines.length === 0) { - const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, degree2, degree2) - baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 }) - } - } - } - } - if (secondGableLines.length < 2) { - const connectRoof = roof.lines.find( - (line) => - (line.x1 <= ridge.x2 && line.x2 >= ridge.x2 && line.y1 <= ridge.y2 && line.y2 >= ridge.y2) || - (line.x2 <= ridge.x2 && line.x1 >= ridge.x2 && line.y2 <= ridge.y2 && line.y1 >= ridge.y2), - ) - if (connectRoof) { - let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x2, ridge.y2] - let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x2, ridge.y2] - let intersectPoints1 - let intersectPoints2 - - baseHipLines - .filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) - .forEach((hip) => { - const line = hip.line - if ( - (hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) || - (hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1]) - ) { - intersectPoints1 = { x: line.x1, y: line.y1 } - } - if ( - (hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) || - (hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1]) - ) { - intersectPoints1 = { x: line.x2, y: line.y2 } - } - if ( - (hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) || - (hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1]) - ) { - intersectPoints2 = { x: line.x1, y: line.y1 } - } - if ( - (hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) || - (hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1]) - ) { - intersectPoints2 = { x: line.x2, y: line.y2 } - } - }) - - if (intersectPoints1) { - hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x2, ridge.y2] - } - if (intersectPoints2) { - hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x2, ridge.y2] - } - - if (hipPoint1) { - const hipLines = baseHipLines.filter( - (line) => - (line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) || - (line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]), - ) - const gableLines = baseGableLines.filter( - (line) => - (line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) || - (line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]), - ) - - if (hipLines.length === 0 && gableLines.length === 0) { - const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, degree1, degree1) - baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 }) - } - } - if (hipPoint2) { - const hipLines = baseHipLines.filter( - (line) => - (line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) || - (line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]), - ) - const gableLines = baseGableLines.filter( - (line) => - (line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) || - (line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]), - ) - if (hipLines.length === 0 && gableLines.length === 0) { - const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, degree2, degree2) - baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 }) - } - } - } - } - }) - - /** 팔작지붕 */ - drawHipAndGableFirst.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let { x1, x2, y1, y2 } = currentBaseLine - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - - let beforePrevLine, afterNextLine - drawBaseLines.forEach((line, index) => { - if (line === prevBaseLine) { - beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - } - if (line === nextBaseLine) { - afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] - } - }) - /** 팔작지붕 두께*/ - const lineWidth = - currentLine.attributes.width < Big(currentLine.attributes.planeSize).div(20).toNumber() - ? currentLine.attributes.width - : Big(currentLine.attributes.planeSize).div(20).toNumber() - - const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) - const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) - - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2) - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2) - const intersectionRoofs = [] - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY.toNumber() }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 }, - vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - - let currentRoof, prevRoof, nextRoof - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - - roof.lines.forEach((line, index) => { - if (line === currentRoof) { - prevRoof = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] - nextRoof = roof.lines[(index + 1) % roof.lines.length] - } - }) - - // /** 이전, 다음라인의 사잇각의 vector를 구한다. */ - let prevVector = getHalfAngleVector(prevRoof, currentRoof) - let nextVector = getHalfAngleVector(currentRoof, nextRoof) - - let prevHipVector = { x: Math.sign(prevVector.x), y: Math.sign(prevVector.y) } - let nextHipVector = { x: Math.sign(nextVector.x), y: Math.sign(nextVector.y) } - - /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ - let hipSize = Big(lineWidth).pow(2).plus(Big(lineWidth).pow(2)).sqrt() - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)), - y: Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)), - } - if (!roof.inPolygon(prevCheckPoint)) { - prevHipVector = { x: Math.sign(prevHipVector.x * -1), y: Math.sign(prevHipVector.y * -1) } - } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)), - y: Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)), - } - if (!roof.inPolygon(nextCheckPoint)) { - nextHipVector = { x: Math.sign(nextHipVector.x * -1), y: Math.sign(nextHipVector.y * -1) } - } - - /** 처마끝에서 올라오는 라인*/ - const prevPoint = [ - currentRoof.x1, - currentRoof.y1, - Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)).toNumber(), - Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)).toNumber(), - ] - const nextPoint = [ - currentRoof.x2, - currentRoof.y2, - Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)).toNumber(), - Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)).toNumber(), - ] - const prevHipLine = drawHipLine(prevPoint, canvas, roof, textMode, prevDegree, currentDegree) - const nextHipLine = drawHipLine(nextPoint, canvas, roof, textMode, currentDegree, nextDegree) - baseHipLines.push({ x1, y1, x2: prevHipLine.x1, y2: prevHipLine.y1, line: prevHipLine }) - baseHipLines.push({ x1: x2, y1: y2, x2: nextHipLine.x1, y2: nextHipLine.y1, line: nextHipLine }) - - /** 처마끝에서 올라오는 라인에서 가운데로 모이는 라인*/ - let midX, midY - if (currentVectorX === 0) { - midX = prevHipLine.x2 - midY = currentMidY.toNumber() - } else { - midX = currentMidX.toNumber() - midY = prevHipLine.y2 - } - const prevGablePoint = [prevHipLine.x2, prevHipLine.y2, midX, midY] - const nextGablePoint = [nextHipLine.x2, nextHipLine.y2, midX, midY] - - const prevGableLine = drawHipLine(prevGablePoint, canvas, roof, textMode, prevDegree, currentDegree) - const nextGableLine = drawHipLine(nextGablePoint, canvas, roof, textMode, currentDegree, nextDegree) - baseHipLines.push({ x1: prevGableLine.x1, y1: prevGableLine.y1, x2: prevGableLine.x2, y2: prevGableLine.y2, line: prevGableLine }) - baseHipLines.push({ x1: nextGableLine.x1, y1: nextGableLine.y1, x2: nextGableLine.x2, y2: nextGableLine.y2, line: nextGableLine }) - - let oppositeLine - baseLines - .filter((line) => line !== currentLine) - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((oppCurrLine) => { - let checkEdge - if (currentVectorX === 0) { - checkEdge = { - vertex1: { x: midX, y: midY }, - vertex2: { x: oppCurrLine.x1, y: midY }, - } - } else { - checkEdge = { - vertex1: { x: midX, y: midY }, - vertex2: { x: midX, y: oppCurrLine.y1 }, - } - } - const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(oppCurrLine, intersection)) { - const size = - calcLinePlaneSize({ - x1: checkEdge.vertex1.x, - y1: checkEdge.vertex1.y, - x2: intersection.x, - y2: intersection.y, - }) / 10 - oppositeLine = { intersection, size, oppCurrLine } - } - }) - - if (oppositeLine) { - const { intersection, size, oppCurrLine } = oppositeLine - let oppPrevLine, oppNextLine - let oppRoofLine - - let ridgePoints = [] - let ridgeSize - - const ridgeEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: intersection.x, y: intersection.y }, - } - - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine) - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const beforePrevCheckPoint = { - x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)), - y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(beforePrevCheckPoint)) { - beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y } - } - - const checkBeforeEdge = { - vertex1: { x: prevLine.x1, y: prevLine.y1 }, - vertex2: { - x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)).toNumber(), - y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)).toNumber(), - }, - } - - const isBefore = edgesIntersection(checkBeforeEdge, ridgeEdge) - if (isBefore) { - const size = Big(isBefore.x).minus(midX).abs().pow(2).plus(Big(isBefore.y).minus(midY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isBefore.x, y: isBefore.y, size }) - } - } - - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let afterNextVector = getHalfAngleVector(nextLine, afterNextLine) - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const afterNextCheckPoint = { - x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)), - y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(afterNextCheckPoint)) { - afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y } - } - - const checkAfterEdge = { - vertex1: { x: nextLine.x2, y: nextLine.y2 }, - vertex2: { - x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)).toNumber(), - y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)).toNumber(), - }, - } - const isAfter = edgesIntersection(checkAfterEdge, ridgeEdge) - if (isAfter) { - const size = Big(isAfter.x).minus(midX).abs().pow(2).plus(Big(isAfter.y).minus(midY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isAfter.x, y: isAfter.y, size }) - } - } - - baseLines.forEach((line, index) => { - if (line === oppCurrLine) { - oppPrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - oppNextLine = baseLines[(index + 1) % baseLines.length] - } - }) - - const oppositeRoofs = [] - const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2) - const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2) - const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1) - const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1) - - if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) { - const checkEdge = { - vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - oppositeRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - oppositeRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - if (oppositeRoofs.length > 0) { - oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line - } - - if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - if (oppRoofLine) { - const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20) - const ridgeEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { x: intersection.x, y: intersection.y }, - } - - if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine) - oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y } - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)), - y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)), - } - - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y } - } - - const oppPrevHipEdge = { - vertex1: { - x: oppRoofLine.x1, - y: oppRoofLine.y1, - }, - vertex2: { - x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(), - y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(), - }, - } - - const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge) - - if ( - isOppPrev && - isPointOnLine( - { - x1: oppPrevHipEdge.vertex1.x, - y1: oppPrevHipEdge.vertex1.y, - x2: oppPrevHipEdge.vertex2.x, - y2: oppPrevHipEdge.vertex2.y, - }, - isOppPrev, - ) - ) { - const size = Big(isOppPrev.x).minus(midX).abs().pow(2).plus(Big(isOppPrev.y).minus(midY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size }) - } - } - if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine) - oppNextVector = { x: oppNextVector.x, y: oppNextVector.y } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)), - y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)), - } - - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y } - } - - const oppNextHipEdge = { - vertex1: { - x: oppRoofLine.x2, - y: oppRoofLine.y2, - }, - vertex2: { - x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(), - y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(), - }, - } - const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge) - if ( - isOppNext && - isPointOnLine( - { - x1: oppNextHipEdge.vertex1.x, - y1: oppNextHipEdge.vertex1.y, - x2: oppNextHipEdge.vertex2.x, - y2: oppNextHipEdge.vertex2.y, - }, - isOppNext, - ) - ) { - const size = Big(isOppNext.x).minus(midX).abs().pow(2).plus(Big(isOppNext.y).minus(midY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size }) - } - } - } - } else if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { - ridgeSize = Big(size).minus(Big(oppCurrLine.attributes.width)).plus(Big(oppCurrLine.attributes.offset)).toNumber() - } - - if (ridgePoints.length > 0) { - ridgeSize = ridgePoints.sort((a, b) => a.size - b.size)[0].size - } - - if (ridgeSize > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { - const ridgeVectorX = Math.sign(midX - intersection.x) - const ridgeVectorY = Math.sign(midY - intersection.y) - const ridgePoint = [ - midX, - midY, - Big(midX).minus(Big(ridgeSize).times(ridgeVectorX)).toNumber(), - Big(midY).minus(Big(ridgeSize).times(ridgeVectorY)).toNumber(), - ] - const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode) - baseRidgeLines.push(ridgeLine) - baseRidgeCount++ - } else { - let ridges = [] - baseGableRidgeLines - .filter( - (ridge) => - (Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1), - ) - .forEach((ridge) => ridges.push(ridge)) - baseRidgeLines - .filter( - (ridge) => - (Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1), - ) - .forEach((ridge) => ridges.push(ridge)) - if (ridges.length > 0) { - return - } - - // canvas.remove(prevGableLine) - // canvas.remove(nextGableLine) - baseHipLines = baseHipLines.filter((base) => base.line !== prevGableLine && base.line !== nextGableLine) - - const points = [prevGableLine.x1, prevGableLine.y1, nextGableLine.x1, nextGableLine.y1] - const gableLine = drawRoofLine(points, canvas, roof, textMode) - baseHipLines.push({ x1: gableLine.x1, y1: gableLine.y1, x2: gableLine.x2, y2: gableLine.y2, line: gableLine }) - } - } - }) - - /** 벽과 이어진 마루를 그린다. */ - drawWallRidgeLine.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let beforePrevLine, afterNextLine - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber() - const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber() - const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) - const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) - - /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ - drawBaseLines.forEach((line, index) => { - if (line === prevBaseLine) { - beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - } - if (line === nextBaseLine) { - afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] - } - }) - - let oppositeLine - baseLines - .filter((line) => line !== currentLine) - .filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2)) - .forEach((oppCurrLine) => { - let checkEdge - if (currentVectorX === 0) { - checkEdge = { - vertex1: { x: currentMidX, y: currentMidY }, - vertex2: { x: oppCurrLine.x1, y: currentMidY }, - } - } else { - checkEdge = { - vertex1: { x: currentMidX, y: currentMidY }, - vertex2: { x: currentMidX, y: oppCurrLine.y1 }, - } - } - const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(oppCurrLine, intersection)) { - const size = - calcLinePlaneSize({ - x1: checkEdge.vertex1.x, - y1: checkEdge.vertex1.y, - x2: intersection.x, - y2: intersection.y, - }) / 10 - oppositeLine = { intersection, size, oppCurrLine } - } - }) - - const ridgePoints = [] - if (oppositeLine) { - const { intersection, size, oppCurrLine } = oppositeLine - ridgePoints.push({ x: intersection.x, y: intersection.y, size }) - const oppPrevLine = baseLines.find((line) => line.x2 === oppCurrLine.x1 && line.y2 === oppCurrLine.y1) - const oppNextLine = baseLines.find((line) => line.x1 === oppCurrLine.x2 && line.y1 === oppCurrLine.y2) - - const oppositeRoofs = [] - const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2) - const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2) - const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1) - const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1) - - let oppRoofLine - - if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) { - const checkEdge = { - vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - oppositeRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 }, - vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - oppositeRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - if (oppositeRoofs.length > 0) { - oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line - } - - if (oppRoofLine) { - const ridgeEdge = { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: intersection.x, y: intersection.y } } - - if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20) - - if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine) - oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y } - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)), - y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)), - } - - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y } - } - - const oppPrevHipEdge = { - vertex1: { - x: oppRoofLine.x1, - y: oppRoofLine.y1, - }, - vertex2: { - x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(), - y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(), - }, - } - - const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge) - - if ( - isOppPrev && - isPointOnLine( - { - x1: oppPrevHipEdge.vertex1.x, - y1: oppPrevHipEdge.vertex1.y, - x2: oppPrevHipEdge.vertex2.x, - y2: oppPrevHipEdge.vertex2.y, - }, - isOppPrev, - ) - ) { - const size = Big(isOppPrev.x).minus(currentMidX).abs().pow(2).plus(Big(isOppPrev.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size }) - } - } - if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine) - oppNextVector = { x: oppNextVector.x, y: oppNextVector.y } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)), - y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)), - } - - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y } - } - - const oppNextHipEdge = { - vertex1: { - x: oppRoofLine.x2, - y: oppRoofLine.y2, - }, - vertex2: { - x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(), - y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(), - }, - } - - const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge) - if ( - isOppNext && - isPointOnLine( - { - x1: oppNextHipEdge.vertex1.x, - y1: oppNextHipEdge.vertex1.y, - x2: oppNextHipEdge.vertex2.x, - y2: oppNextHipEdge.vertex2.y, - }, - isOppNext, - ) - ) { - const size = Big(isOppNext.x).minus(currentMidX).abs().pow(2).plus(Big(isOppNext.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber() - ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size }) - } - } - } else { - baseHipLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(ridgeEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - const size = Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt() - ridgePoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - }) - } - - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine) - const checkPoint = { - x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)), - y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(checkPoint)) { - beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y } - } - - const hipEdge = { - vertex1: { x: prevLine.x1, y: prevLine.y1 }, - vertex2: { - x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(prevLine.attributes.planeSize)).toNumber(), - y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(prevLine.attributes.planeSize)).toNumber(), - }, - } - - const isLines = [] - drawBaseLines - .filter((base) => base !== currentBaseLine && base !== prevBaseLine && base !== beforePrevLine) - .forEach((base) => { - const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if (intersection && isPointOnLine(base.line, intersection)) { - const size = Big(intersection.x) - .minus(prevLine.x1) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(prevLine.y1).abs().pow(2)) - .sqrt() - .toNumber() - isLines.push({ intersection, size }) - } - }) - - if (isLines.length > 0) { - const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2) - const hipPoint = [ - prevLine.x1, - prevLine.y1, - Big(prevLine.x1).plus(Big(beforePrevVector.x).times(hipSize)).toNumber(), - Big(prevLine.y1).plus(Big(beforePrevVector.y).times(hipSize)).toNumber(), - ] - - const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } - const intersection = edgesIntersection(ridgeEdge, hipEdge) - if ( - intersection && - (isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) || - (Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) || - (Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1)) - ) { - const size = Big(intersection.x) - .minus(currentMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) - .sqrt() - .toNumber() - ridgePoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - } - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let afterNextVector = getHalfAngleVector(nextLine, afterNextLine) - const checkPoint = { - x: Big(nextLine.x1).plus(Big(afterNextVector.x).times(10)), - y: Big(nextLine.y1).plus(Big(afterNextVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(checkPoint)) { - afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y } - } - - const hipEdge = { - vertex1: { x: nextLine.x2, y: nextLine.y2 }, - vertex2: { - x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(nextLine.attributes.planeSize)).toNumber(), - y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(nextLine.attributes.planeSize)).toNumber(), - }, - } - - const isLines = [] - drawBaseLines - .filter((base) => base !== currentBaseLine && base !== nextBaseLine && base !== afterNextLine) - .forEach((base) => { - const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if (intersection && isPointOnLine(base.line, intersection)) { - const size = Big(intersection.x) - .minus(nextLine.x2) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(nextLine.y2).abs().pow(2)) - .sqrt() - .toNumber() - isLines.push({ intersection, size }) - } - }) - if (isLines.length > 0) { - const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2) - const hipPoint = [ - nextLine.x2, - nextLine.y2, - Big(nextLine.x2).plus(Big(afterNextVector.x).times(hipSize)).toNumber(), - Big(nextLine.y2).plus(Big(afterNextVector.y).times(hipSize)).toNumber(), - ] - const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } - const intersection = edgesIntersection(ridgeEdge, hipEdge) - - if ( - intersection && - (isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) || - (Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) || - (Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1)) - ) { - const size = Big(intersection.x) - .minus(currentMidX) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(currentMidY).abs().pow(2)) - .sqrt() - .toNumber() - ridgePoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - } - } - } - - if (ridgePoints.length === 0 || baseRidgeCount >= getMaxRidge(baseLines.length)) return - const ridgeEndPoint = ridgePoints.sort((a, b) => a.size - b.size)[0] - const ridgePoint = { x1: currentMidX, y1: currentMidY, x2: ridgeEndPoint.x, y2: ridgeEndPoint.y } - - /** 포인트가 지붕밖에 있는 경우 조정*/ - if (!roof.inPolygon({ x: ridgePoint.x1, y: ridgePoint.y1 })) { - const checkEdge = { vertex1: { x: ridgePoint.x2, y: ridgePoint.y2 }, vertex2: { x: ridgePoint.x1, y: ridgePoint.y1 } } - const isPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(line, intersection)) { - if ( - Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) && - Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y) - ) { - const size = Big(intersection.x) - .minus(ridgePoint.x2) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(ridgePoint.y2).abs().pow(2)) - .sqrt() - .toNumber() - isPoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - }) - if (isPoints.length > 0) { - const newPoint = isPoints.sort((a, b) => a.size - b.size)[0] - ridgePoint.x1 = newPoint.x - ridgePoint.y1 = newPoint.y - } - } - - if (!roof.inPolygon({ x: ridgePoint.x2, y: ridgePoint.y2 })) { - const checkEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } } - const isPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && isPointOnLine(line, intersection)) { - if ( - Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) && - Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y) - ) { - const size = Big(intersection.x) - .minus(ridgePoint.x1) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(ridgePoint.y1).abs().pow(2)) - .sqrt() - .toNumber() - isPoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - }) - if (isPoints.length > 0) { - const newPoint = isPoints.sort((a, b) => a.size - b.size)[0] - ridgePoint.x2 = newPoint.x - ridgePoint.y2 = newPoint.y - } - } - - const ridge = drawRidgeLine([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], canvas, roof, textMode) - baseRidgeLines.push(ridge) - baseRidgeCount++ - - /** 현재 라인의 지붕 라인을 찾는다. */ - const intersectionRoofs = [] - let currentRoof - if (currentVectorX === 0) { - const checkEdge = { - vertex1: { x: prevLine.x1, y: currentMidY }, - vertex2: { x: currentMidX, y: currentMidY }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } else { - const checkEdge = { - vertex1: { x: currentMidX, y: prevLine.y1 }, - vertex2: { x: currentMidX, y: currentMidY }, - } - roof.lines - .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { - if (isPointOnLine(line, intersection)) { - intersectionRoofs.push({ - line, - intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), - }) - } - } - }) - } - if (intersectionRoofs.length > 0) { - currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line - } - if (currentRoof) { - const prevHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX, currentMidY] - const nextHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX, currentMidY] - - const prevHipLine = drawHipLine(prevHipPoint, canvas, roof, textMode, prevDegree, prevDegree) - const nextHipLine = drawHipLine(nextHipPoint, canvas, roof, textMode, nextDegree, nextDegree) - baseHipLines.push({ x1: prevHipLine.x1, y1: prevHipLine.y1, x2: prevHipLine.x2, y2: prevHipLine.y2, line: prevHipLine }) - baseHipLines.push({ x1: nextHipLine.x1, y1: nextHipLine.y1, x2: nextHipLine.x2, y2: nextHipLine.y2, line: nextHipLine }) - } - }) - - /** ⨆ 모양 처마에 추녀마루를 그린다. */ - drawEavesFirstLines.forEach((current) => { - // 확인용 라인, 포인트 제거 - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let { x1, x2, y1, y2, size } = currentBaseLine - let beforePrevLine, afterNextLine - - /** 이전 라인의 경사 */ - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - /** 다음 라인의 경사 */ - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - - /** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */ - drawBaseLines.forEach((line, index) => { - if (line === prevBaseLine) { - beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length] - } - if (line === nextBaseLine) { - afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length] - } - }) - - /** 이전, 다음라인의 사잇각의 vector를 구한다. */ - let prevVector = getHalfAngleVector(prevLine, currentLine) - let nextVector = getHalfAngleVector(currentLine, nextLine) - - let prevHipVector = { x: prevVector.x, y: prevVector.y } - let nextHipVector = { x: nextVector.x, y: nextVector.y } - - /** 각 라인의 흐름 방향을 확인한다. */ - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - const beforePrevAngle = calculateAngle(beforePrevLine.line.startPoint, beforePrevLine.line.endPoint) - const afterNextAngle = calculateAngle(afterNextLine.line.startPoint, afterNextLine.line.endPoint) - - /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ - let currentSize = Big(size).div(10) - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(x1).plus(Big(prevHipVector.x).times(10)), - y: Big(y1).plus(Big(prevHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - prevHipVector = { x: -prevHipVector.x, y: -prevHipVector.y } - } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(x2).plus(Big(nextHipVector.x).times(10)), - y: Big(y2).plus(Big(nextHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - nextHipVector = { x: -nextHipVector.x, y: -nextHipVector.y } - } - - /** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/ - let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() - - /** - * 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고 - * 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다. - */ - if (beforePrevLine === afterNextLine || (currentAngle === beforePrevAngle && currentAngle === afterNextAngle)) { - console.log('4각 ::::::::: ') - const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) - const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) - const currentMidX = Big(x1).plus(Big(x2)).div(2) - const currentMidY = Big(y1).plus(Big(y2)).div(2) - - const midLineEdge = { - vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() }, - vertex2: { - x: currentMidX.plus(currentSize.times(Math.sign(xVector))).toNumber(), - y: currentMidY.plus(currentSize.times(Math.sign(yVector))).toNumber(), - }, - } - - /** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/ - baseLines - .filter((line) => line !== currentLine) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(midLineEdge, lineEdge) - /** 현재라인의 길이만큼 거리가 모자라면 해당 길이 만큼을 현재라인의 길이로 판단하고 나머지 계산을 진행한다.*/ - if (intersection && !intersection.isIntersectionOutside) { - const intersectionSize = Big(intersection.x) - .minus(Big(currentMidX)) - .plus(Big(intersection.y).minus(Big(currentMidY))) - .abs() - if (intersectionSize.lt(currentSize)) { - currentSize = intersectionSize - } - } - }) - - hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt() - } else { - if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) { - const beforePrevX1 = beforePrevLine.x1, - beforePrevY1 = beforePrevLine.y1 - const afterNextX2 = afterNextLine.x2, - afterNextY2 = afterNextLine.y2 - - /** beforePrevLine 과 afterNextLine 을 연결하는 사각형의 경우 6각으로 판단 */ - const connectBAPoint = { x1: afterNextX2, y1: afterNextY2, x2: beforePrevX1, y2: beforePrevY1 } - const isConnect = - baseLines.filter( - (line) => - line.x1 === connectBAPoint.x1 && line.y1 === connectBAPoint.y1 && line.x2 === connectBAPoint.x2 && line.y2 === connectBAPoint.y2, - ).length > 0 - - /** 6각 */ - if (isConnect) { - const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt() - const intersectBaseLine = [] - if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { - const prevEndPoint = { - x: Big(x1).plus(Big(prevHipVector.x).times(checkScale)), - y: Big(y1).plus(Big(prevHipVector.y).times(checkScale)), - } - baseLines - .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) - .forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection && !intersection.isIntersectionOutside) { - intersectBaseLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x1)) - .pow(2) - .plus(Big(intersection.y).minus(Big(y1)).pow(2)) - .sqrt(), - }) - } - }) - } - if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { - const nextEndPoint = { - x: Big(x2).plus(Big(nextHipVector.x).times(checkScale)), - y: Big(y2).plus(Big(nextHipVector.y).times(checkScale)), - } - baseLines - .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) - .forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection && !intersection.isIntersectionOutside) { - intersectBaseLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x2)) - .pow(2) - .plus(Big(intersection.y).minus(Big(y2)).pow(2)) - .sqrt(), - }) - } - }) - } - const intersection = intersectBaseLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectBaseLine[0]) - if (intersection) { - hipLength = intersection.distance - } - } else { - const rightAngleLine = baseLines - .filter( - (line) => - line !== currentLine && - line !== prevLine && - line !== nextLine && - (prevAngle === calculateAngle(line.startPoint, line.endPoint) || nextAngle === calculateAngle(line.startPoint, line.endPoint)), - ) - .filter((line) => { - const index = baseLines.indexOf(line) - const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - const nextLine = baseLines[(index + 1) % baseLines.length] - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - switch (prevAngle) { - case 90: - return nextAngle === -90 - case -90: - return nextAngle === 90 - case 0: - return nextAngle === 180 - case 180: - return nextAngle === 0 - } - }) - - const oppositeCurrentLine = baseLines - .filter((line) => { - const angle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - } - }) - .filter((line) => { - const index = baseLines.indexOf(line) - const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - const nextLine = baseLines[(index + 1) % baseLines.length] - const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) - const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) - switch (prevAngle) { - case 90: - return nextAngle === -90 - case -90: - return nextAngle === 90 - case 0: - return nextAngle === 180 - case 180: - return nextAngle === 0 - } - }) - - let checkHipPoints - if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { - checkHipPoints = [ - x1, - y1, - Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2).toNumber(), - Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2).toNumber(), - ] - } - - if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { - checkHipPoints = [ - x2, - y2, - Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2).toNumber(), - Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2).toNumber(), - ] - } - - if (checkHipPoints) { - const intersectPoints = [] - rightAngleLine.forEach((line) => { - const intersection = edgesIntersection( - { - vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, - vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] }, - }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection) { - intersectPoints.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(checkHipPoints[0])) - .pow(2) - .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) - .sqrt() - .toNumber(), - }) - } - }) - - oppositeCurrentLine.forEach((line) => { - const intersection = edgesIntersection( - { - vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] }, - vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] }, - }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection) { - intersectPoints.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(checkHipPoints[0])) - .pow(2) - .plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2)) - .sqrt() - .toNumber(), - }) - } - }) - - if (intersectPoints.length > 0) { - const intersection = intersectPoints.sort((a, b) => a.distance - b.distance)[0] - hipLength = getAdjacent(intersection.distance) / 2 - } - } - } - } - } - let prevHipLine, nextHipLine - /** 이전라인과의 연결지점에 추녀마루를 그린다. */ - if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) { - const prevEndPoint = { - x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(1), - y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(1), - } - - if (!roof.inPolygon({ x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() })) { - const checkEdge = { vertex1: { x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() }, vertex2: { x: x1, y: y1 } } - const intersectionPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const size = Big(intersection.x) - .minus(Big(prevEndPoint.x)) - .pow(2) - .plus(Big(intersection.y).minus(Big(prevEndPoint.y)).pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - prevEndPoint.x = Big(intersection.x) - prevEndPoint.y = Big(intersection.y) - } - } - const overlapLine = baseHipLines.find( - (line) => - isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) || - isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }), - ) - - if (overlapLine) { - let size1, size2 - if ( - isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 }) - ) { - size1 = Math.sqrt(Math.pow(x1 - overlapLine.x1, 2) + Math.pow(y1 - overlapLine.y1, 2)) - } - if ( - isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 }) - ) { - size2 = Math.sqrt(Math.pow(x1 - overlapLine.x2, 2) + Math.pow(y1 - overlapLine.y2, 2)) - } - if (size1 && size2) { - if (size1 < size2) { - prevEndPoint.x = Big(overlapLine.x1) - prevEndPoint.y = Big(overlapLine.y1) - } else { - prevEndPoint.x = Big(overlapLine.x2) - prevEndPoint.y = Big(overlapLine.y2) - } - } else { - if (size1) { - prevEndPoint.x = Big(overlapLine.x1) - prevEndPoint.y = Big(overlapLine.y1) - } - if (size2) { - prevEndPoint.x = Big(overlapLine.x2) - prevEndPoint.y = Big(overlapLine.y2) - } - } - } - const intersectRidgeLine = [] - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection && !intersection.isIntersectionOutside) { - intersectRidgeLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x1)) - .pow(2) - .plus(Big(intersection.y).minus(Big(y1)).pow(2)) - .sqrt(), - }) - } - }) - const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - - if (intersectRidge) { - prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) - prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) - } - - const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) - const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) - /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ - let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() - scale = scale.eq(0) ? Big(1) : scale - - /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ - const hipEdge = { - vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, - vertex2: prevEndPoint, - } - - let intersectPoints = [] - /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) && - Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && - Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) - ) { - const intersectSize = prevEndPoint.x - .minus(Big(intersection.x)) - .pow(2) - .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - }) - } - }) - - intersectPoints = intersectPoints.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectPoints[0]) - - /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ - if (intersectPoints && intersectPoints.intersection) { - prevHipLine = drawHipLine( - [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - currentDegree, - ) - baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) - } - } - if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) { - const nextEndPoint = { - x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(1), - y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(1), - } - - if (!roof.inPolygon({ x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() })) { - const checkEdge = { vertex1: { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() }, vertex2: { x: x2, y: y2 } } - const intersectionPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const size = Big(intersection.x) - .minus(Big(nextEndPoint.x)) - .pow(2) - .plus(Big(intersection.y).minus(Big(nextEndPoint.y)).pow(2)) - .sqrt() - .toNumber() - intersectionPoints.push({ intersection, size }) - } - }) - if (intersectionPoints.length > 0) { - const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection - nextEndPoint.x = Big(intersection.x) - nextEndPoint.y = Big(intersection.y) - } - } - - // const overlapLine = baseHipLines.find((line) => isPointOnLine(line, { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() })) - const overlapLine = baseHipLines.find( - (line) => - isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) || - isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }), - ) - if (overlapLine) { - let size1, size2 - if ( - isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 }) - ) { - size1 = Math.sqrt(Math.pow(x2 - overlapLine.x1, 2) + Math.pow(y2 - overlapLine.y1, 2)) - } - if ( - isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 }) - ) { - size2 = Math.sqrt(Math.pow(x2 - overlapLine.x2, 2) + Math.pow(y2 - overlapLine.y2, 2)) - } - if (size1 && size2) { - if (size1 < size2) { - nextEndPoint.x = Big(overlapLine.x1) - nextEndPoint.y = Big(overlapLine.y1) - } else { - nextEndPoint.x = Big(overlapLine.x2) - nextEndPoint.y = Big(overlapLine.y2) - } - } else { - if (size1) { - nextEndPoint.x = Big(overlapLine.x1) - nextEndPoint.y = Big(overlapLine.y1) - } - if (size2) { - nextEndPoint.x = Big(overlapLine.x2) - nextEndPoint.y = Big(overlapLine.y2) - } - } - } - - const intersectRidgeLine = [] - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - - if (intersection && !intersection.isIntersectionOutside) { - intersectRidgeLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x1)) - .pow(2) - .plus(Big(intersection.y).minus(Big(y1)).pow(2)) - .sqrt(), - }) - } - }) - const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - - if (intersectRidge) { - nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) - nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) - } - - const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) - const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) - let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() - scale = scale.eq(0) ? Big(1) : scale - - const hipEdge = { - vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, - vertex2: nextEndPoint, - } - - let intersectPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) && - Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && - Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) - ) { - const intersectSize = nextEndPoint.x - .minus(Big(intersection.x)) - .pow(2) - .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - }) - } - }) - - intersectPoints = intersectPoints.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectPoints[0]) - - if (intersectPoints && intersectPoints.intersection) { - nextHipLine = drawHipLine( - [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - currentDegree, - ) - baseHipLines.push({ - x1: x2, - y1: y2, - x2: nextEndPoint.x.toNumber(), - y2: nextEndPoint.y.toNumber(), - line: nextHipLine, - }) - } - } - - /** 두 선이 교차하면 해당 포인트까지로 선 조정*/ - if (prevHipLine !== undefined && nextHipLine !== undefined) { - const prevEdge = { - vertex1: { x: prevHipLine.x1, y: prevHipLine.y1 }, - vertex2: { x: prevHipLine.x2, y: prevHipLine.y2 }, - } - const nextEdge = { - vertex1: { x: nextHipLine.x1, y: nextHipLine.y1 }, - vertex2: { x: nextHipLine.x2, y: nextHipLine.y2 }, - } - const intersection = edgesIntersection(prevEdge, nextEdge) - if (intersection) { - /** 포인트 조정*/ - baseHipLines - .filter((line) => line.line === prevHipLine || line.line === nextHipLine) - .forEach((line) => { - line.x2 = intersection.x - line.y2 = intersection.y - line.line.set({ x2: intersection.x, y2: intersection.y }) - }) - prevHipLine.x2 = intersection.x - prevHipLine.y2 = intersection.y - const prevSize = reCalculateSize(prevHipLine) - - prevHipLine.attributes.planeSize = prevSize.planeSize - prevHipLine.attributes.actualSize = prevSize.actualSize - prevHipLine.fire('modified') - - nextHipLine.x2 = intersection.x - nextHipLine.y2 = intersection.y - const nextSize = reCalculateSize(nextHipLine) - - nextHipLine.attributes.planeSize = nextSize.planeSize - nextHipLine.attributes.actualSize = nextSize.actualSize - nextHipLine.fire('modified') - canvas.renderAll() - } - } - - /** 두 추녀마루가 한점에서 만나는 경우 해당 점을 기점으로 마루를 작성한다.*/ - if ( - prevHipLine !== undefined && - nextHipLine !== undefined && - Big(prevHipLine.x2).minus(Big(nextHipLine.x2)).abs().lte(1) && - Big(prevHipLine.y2).minus(Big(nextHipLine.y2)).abs().lte(1) - ) { - const startPoint = { x: prevHipLine.x2, y: prevHipLine.y2 } - let ridgeSize = 0 - - const currentMidX = Big(currentLine.x2).plus(Big(currentLine.x1)).div(2) - const currentMidY = Big(currentLine.y2).plus(Big(currentLine.y1)).div(2) - - const xVector = Big(currentMidX).minus(Big(startPoint.x)).round(0, Big.roundDown) - const yVector = Big(currentMidY).minus(Big(startPoint.y)).round(0, Big.roundDown) - if (beforePrevLine === afterNextLine) { - console.log('4각 :::::::: ') - const oppositeMidX = Big(beforePrevLine.x2).plus(Big(beforePrevLine.x1)).div(2) - const oppositeMidY = Big(beforePrevLine.y2).plus(Big(beforePrevLine.y1)).div(2) - if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - ridgeSize = oppositeMidX - .minus(Big(startPoint.x)) - .abs() - .pow(2) - .plus(oppositeMidY.minus(Big(startPoint.y)).abs().pow(2)) - .sqrt() - .minus(Big(beforePrevLine.line.attributes.planeSize).div(20)) - } else { - let width = 0 - if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { - width = beforePrevLine.line.attributes.width / 2 - } else if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { - width = beforePrevLine.line.attributes.width - } - const checkEdge = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() } } - const vectorX = Math.sign(startPoint.x - oppositeMidX.toNumber()) - const vectorY = Math.sign(startPoint.y - oppositeMidY.toNumber()) - const oppositeRoofPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection && Math.sign(startPoint.x - intersection.x) === vectorX && Math.sign(startPoint.y - intersection.y) === vectorY) { - const size = Big(intersection.x) - .minus(Big(startPoint.x)) - .pow(2) - .plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2)) - .sqrt() - .toNumber() - oppositeRoofPoints.push({ intersection, size }) - } - }) - if (oppositeRoofPoints.length > 0) { - const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection - ridgeSize = Big(oppositeRoofPoint.x) - .minus(Big(startPoint.x)) - .abs() - .pow(2) - .plus(Big(oppositeRoofPoint.y).minus(Big(startPoint.y)).abs().pow(2)) - .minus(width) - .sqrt() - } - } - } else { - if (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) { - const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line - const oppositeAngle = calculateAngle(oppositeLine.startPoint, oppositeLine.endPoint) - let checkEdge - if (Math.sign(oppositeLine.x1 - oppositeLine.x2) === 0) { - checkEdge = { vertex1: startPoint, vertex2: { x: oppositeLine.x1, y: startPoint.y } } - } else { - checkEdge = { vertex1: startPoint, vertex2: { x: startPoint.x, y: oppositeLine.y1 } } - } - if (currentAngle === oppositeAngle) { - const oppositeEdge = { - vertex1: { x: oppositeLine.x1, y: oppositeLine.y1 }, - vertex2: { x: oppositeLine.x2, y: oppositeLine.y2 }, - } - const intersection = edgesIntersection(oppositeEdge, checkEdge) - if (intersection) { - ridgeSize = Big(intersection.x) - .minus(Big(startPoint.x)) - .pow(2) - .plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2)) - .sqrt() - } - } else { - const intersectPoints = [] - roof.lines - .filter( - (line) => - Math.sign(oppositeLine.x1 - oppositeLine.x2) === Math.sign(line.x1 - line.x2) && - Math.sign(oppositeLine.y1 - oppositeLine.y2) === Math.sign(line.y1 - line.y2), - ) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(lineEdge, checkEdge) - if (intersection) { - const size = Big(startPoint.x) - .minus(Big(intersection.x)) - .pow(2) - .plus(Big(startPoint.y).minus(Big(intersection.y)).pow(2)) - .sqrt() - .toNumber() - intersectPoints.push({ intersection, size }) - } - }) - intersectPoints.sort((a, b) => a.size - b.size) - if (intersectPoints.length > 0) { - ridgeSize = Big(intersectPoints[0].size) - } - } - } else { - /** baseLines 에서 가장 작은 x1과 가장 큰 x1, 가장 작은 y1과 가장 큰 y1을 계산*/ - let minX = Infinity - let maxX = -Infinity - let minY = Infinity - let maxY = -Infinity - - baseLines.forEach((line) => { - if (line.x1 < minX) { - minX = line.x1 - } - if (line.x1 > maxX) { - maxX = line.x1 - } - if (line.y1 < minY) { - minY = line.y1 - } - if (line.y1 > maxY) { - maxY = line.y1 - } - }) - const checkLength = Big(maxX) - .minus(Big(minX)) - .pow(2) - .plus(Big(maxY).minus(Big(minY)).pow(2)) - .sqrt() - - const checkEdges = { - vertex1: { x: startPoint.x, y: startPoint.y }, - vertex2: { - x: Big(startPoint.x).minus(checkLength.times(Math.sign(xVector))), - y: Big(startPoint.y).minus(checkLength.times(Math.sign(yVector))), - }, - } - - /** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/ - const intersectBaseLine = [] - baseLines - .filter((line) => { - /** currentAngle 의 반대 각도인 라인 */ - const angle = calculateAngle(line.startPoint, line.endPoint) - switch (currentAngle) { - case 90: - return angle === -90 - case -90: - return angle === 90 - case 0: - return angle === 180 - case 180: - return angle === 0 - } - }) - .filter((line) => { - const currentMinX = Math.min(x1, x2) - const currentMaxX = Math.max(x1, x2) - const currentMinY = Math.min(y1, y2) - const currentMaxY = Math.max(y1, y2) - const lineMinX = Math.min(line.x1, line.x2) - const lineMaxX = Math.max(line.x1, line.x2) - const lineMinY = Math.min(line.y1, line.y2) - const lineMaxY = Math.max(line.y1, line.y2) - - /** currentLine 의 안쪽에 있거나 currentLine이 line의 안쪽에 있는 라인 */ - if (Big(currentLine.y1).minus(Big(currentLine.y2)).abs().lte(1)) { - return ( - (currentMinX <= lineMinX && lineMinX <= currentMaxX) || - (currentMinX <= lineMaxX && lineMaxX <= currentMaxX) || - (lineMinX <= currentMinX && currentMinX <= lineMaxX) || - (lineMinX <= currentMaxX && currentMaxX <= lineMaxX) - ) - } else { - return ( - (currentMinY <= lineMinY && lineMinY <= currentMaxY) || - (currentMinY <= lineMaxY && lineMaxY <= currentMaxY) || - (lineMinY <= currentMinY && currentMinY <= lineMaxY) || - (lineMinY <= currentMaxY && currentMaxY <= lineMaxY) - ) - } - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(checkEdges, lineEdge) - if (intersection) { - intersectBaseLine.push({ intersection, line }) - } - }) - - /** 맞은편 라인 */ - const oppositeLine = intersectBaseLine.reduce((prev, current) => { - const prevDistance = Big(prev.intersection.x) - .minus(Big(startPoint.x)) - .pow(2) - .plus(Big(prev.intersection.y).minus(Big(startPoint.y)).pow(2)) - .sqrt() - const currentDistance = Big(current.intersection.x) - .minus(Big(startPoint.x)) - .pow(2) - .plus(Big(current.intersection.y).minus(Big(startPoint.y)).pow(2)) - .sqrt() - return prevDistance < currentDistance ? prev : current - }, intersectBaseLine[0]) - - /** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */ - const oppositeSize = oppositeLine - ? Big(oppositeLine.intersection.x) - .minus(Big(startPoint.x)) - .pow(2) - .plus(Big(oppositeLine.intersection.y).minus(Big(startPoint.y)).pow(2)) - .sqrt() - .minus(currentSize.div(2)) - .round(1) - .toNumber() - : Infinity - - /** 이전, 다음 라인중 길이가 짧은 길이*/ - const lineMinSize = - prevBaseLine.size < nextBaseLine.size ? Big(prevBaseLine.size).div(10).toNumber() : Big(nextBaseLine.size).div(10).toNumber() - /** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */ - ridgeSize = Big(Math.min(oppositeSize, lineMinSize)) - } - } - - if (ridgeSize.gt(0) && baseRidgeCount < getMaxRidge(baseLines.length)) { - const points = [ - startPoint.x, - startPoint.y, - Big(startPoint.x) - .minus(ridgeSize.times(Math.sign(xVector))) - .toNumber(), - Big(startPoint.y) - .minus(ridgeSize.times(Math.sign(yVector))) - .toNumber(), - ] - - const oppositeHipPoints = [] - const ridgeEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let hipVector = getHalfAngleVector(prevLine, beforePrevLine.line) - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const checkPoint = { - x: Big(prevLine.x1).plus(Big(hipVector.x).times(10)), - y: Big(prevLine.y1).plus(Big(hipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(checkPoint)) { - hipVector = { x: -hipVector.x, y: -hipVector.y } - } - - const hipEdge = { - vertex1: { x: prevLine.x1, y: prevLine.y1 }, - vertex2: { - x: Big(prevLine.x1).plus(Big(hipVector.x).times(prevLine.attributes.planeSize)).toNumber(), - y: Big(prevLine.y1).plus(Big(hipVector.y).times(prevLine.attributes.planeSize)).toNumber(), - }, - } - const intersection = edgesIntersection(ridgeEdge, hipEdge) - if (intersection) { - const size = Big(points[0] - intersection.x) - .abs() - .pow(2) - .plus( - Big(points[1] - intersection.y) - .abs() - .pow(2), - ) - .sqrt() - .toNumber() - oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let hipVector = getHalfAngleVector(nextLine, afterNextLine.line) - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const checkPoint = { - x: Big(nextLine.x1).plus(Big(hipVector.x).times(10)), - y: Big(nextLine.y1).plus(Big(hipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(checkPoint)) { - hipVector = { x: -hipVector.x, y: -hipVector.y } - } - - const hipEdge = { - vertex1: { x: nextLine.x2, y: nextLine.y2 }, - vertex2: { - x: Big(nextLine.x2).plus(Big(hipVector.x).times(nextLine.attributes.planeSize)).toNumber(), - y: Big(nextLine.y2).plus(Big(hipVector.y).times(nextLine.attributes.planeSize)).toNumber(), - }, - } - const intersection = edgesIntersection(ridgeEdge, hipEdge) - if (intersection) { - const size = Big(points[0] - intersection.x) - .abs() - .pow(2) - .plus( - Big(points[1] - intersection.y) - .abs() - .pow(2), - ) - .sqrt() - .toNumber() - oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size }) - } - } - if (oppositeHipPoints.length > 0) { - const oppositeHipPoint = oppositeHipPoints.sort((a, b) => a.size - b.size)[0] - points[2] = oppositeHipPoint.x - points[3] = oppositeHipPoint.y - } - - /** 동일 라인이 있는지 확인. */ - if ( - baseRidgeLines.filter((line) => { - const ridgeMinX = Math.min(points[0], points[2]) - const ridgeMaxX = Math.max(points[0], points[2]) - const ridgeMinY = Math.min(points[1], points[3]) - const ridgeMaxY = Math.max(points[1], points[3]) - const lineMinX = Math.min(line.x1, line.x2) - const lineMaxX = Math.max(line.x1, line.x2) - const lineMinY = Math.min(line.y1, line.y2) - const lineMaxY = Math.max(line.y1, line.y2) - - return ridgeMinX === lineMinX && ridgeMaxX === lineMaxX && ridgeMinY === lineMinY && ridgeMaxY === lineMaxY - }).length > 0 - ) { - return - } - - const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) - if ( - oppositeHipPoints.length === 0 && - (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) - ) { - baseGableRidgeLines.push(ridgeLine) - } else { - baseRidgeLines.push(ridgeLine) - } - baseRidgeCount = baseRidgeCount + 1 - - /** 포인트 조정*/ - baseHipLines - .filter((line) => line.line === prevHipLine || line.line === nextHipLine) - .forEach((line) => { - line.x2 = points[0] - line.y2 = points[1] - line.line.set({ x2: points[0], y2: points[1] }) - // line.line.fire('modified') - }) - prevHipLine.x2 = points[0] - prevHipLine.y2 = points[1] - const prevSize = reCalculateSize(prevHipLine) - prevHipLine.attributes.planeSize = prevSize.planeSize - prevHipLine.attributes.actualSize = prevSize.actualSize - prevHipLine.fire('modified') - nextHipLine.x2 = points[0] - nextHipLine.y2 = points[1] - const nextSize = reCalculateSize(nextHipLine) - nextHipLine.attributes.planeSize = nextSize.planeSize - nextHipLine.attributes.actualSize = nextSize.actualSize - nextHipLine.fire('modified') - canvas.renderAll() - - if ( - oppositeHipPoints.length === 0 && - (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) - ) { - const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line - if (Math.sign(ridgeLine.x1 - ridgeLine.x2) === 0) { - const gableVector = Math.sign(ridgeLine.x1 - oppositeLine.x1) - const prevVector = ridgeLine.x1 === prevHipLine.x1 ? Math.sign(ridgeLine.x1 - prevHipLine.x2) : Math.sign(ridgeLine.x2 - prevHipLine.x1) - - const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine - const firstDegree = - gableVector === Math.sign(ridgeLine.x1 - prevLine.x1) - ? getDegreeByChon(prevLine.attributes.pitch) - : getDegreeByChon(nextLine.attributes.pitch) - - const oppositeRoofPoints = [ - ridgeLine.x2, - ridgeLine.y2, - ridgeLine.x1 === firstHipLine.x1 ? firstHipLine.x2 : firstHipLine.x1, - ridgeLine.y2, - ] - const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, firstDegree, firstDegree) - baseHipLines.push({ - x1: oppositeRoofLine.x1, - y1: oppositeRoofLine.y1, - x2: oppositeRoofLine.x2, - y2: oppositeRoofLine.y2, - line: oppositeRoofLine, - }) - - const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] - if (ridgeLine.x1 === firstHipLine.x1) { - connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2) - } else { - connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1) - } - const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) - baseHipLines.push({ - x1: connectRoofLine.x1, - y1: connectRoofLine.y1, - x2: connectRoofLine.x2, - y2: connectRoofLine.y2, - line: connectRoofLine, - }) - - /** 다른 방향의 추녀마루 */ - const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine - const secondDegree = - gableVector === Math.sign(ridgeLine.x1 - prevLine.x1) - ? getDegreeByChon(nextLine.attributes.pitch) - : getDegreeByChon(prevLine.attributes.pitch) - - const intersections = [] - const checkEdge = { - vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 }, - vertex2: { x: ridgeLine.x1 === secondHipLine.x1 ? secondHipLine.x2 : secondHipLine.x1, y: ridgeLine.y2 }, - } - - baseGableRidgeLines - .filter((ridge) => ridge !== ridgeLine) - .forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const intersection = edgesIntersection(ridgeEdge, checkEdge) - if (intersection && !intersections.includes(intersection)) { - const size = Big(intersection.x) - .minus(Big(ridgeLine.x2)) - .pow(2) - .plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2)) - .sqrt() - .toNumber() - intersections.push({ intersection, size, ridge }) - } - }) - intersections.sort((a, b) => a.size - b.size) - - if (intersections.length > 0) { - const intersection = intersections[0].intersection - const intersectRidge = intersections[0].ridge - const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y] - const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, secondDegree, secondDegree) - baseHipLines.push({ - x1: oppositeLine.x1, - y1: oppositeLine.y1, - x2: oppositeLine.x2, - y2: oppositeLine.y2, - line: oppositeRoofLine, - }) - - const ridgeVector = Math.sign(ridgeLine.y1 - ridgeLine.y2) - const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] - if (ridgeVector === Math.sign(oppositeRoofLine.y1 - intersectRidge.y2)) { - connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2) - } else { - connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1) - } - - const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) - baseHipLines.push({ - x1: connectRoofLine.x1, - y1: connectRoofLine.y1, - x2: connectRoofLine.x2, - y2: connectRoofLine.y2, - line: connectRoofLine, - }) - } - } else { - const gableVector = Math.sign(ridgeLine.y1 - oppositeLine.y1) - const prevVector = ridgeLine.y1 === prevHipLine.y1 ? Math.sign(ridgeLine.y1 - prevHipLine.y2) : Math.sign(ridgeLine.y1 - prevHipLine.y1) - - /** 마루와 박공지붕을 연결하기위한 추녀마루 라인 */ - const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine - const firstDegree = - gableVector === Math.sign(ridgeLine.y1 - prevLine.y1) - ? getDegreeByChon(prevLine.attributes.pitch) - : getDegreeByChon(nextLine.attributes.pitch) - - const oppositeRoofPoints = [ - ridgeLine.x2, - ridgeLine.y2, - ridgeLine.x2, - ridgeLine.y1 === firstHipLine.y1 ? firstHipLine.y2 : firstHipLine.y1, - ] - const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, firstDegree, firstDegree) - baseHipLines.push({ - x1: oppositeRoofLine.x1, - y1: oppositeRoofLine.y1, - x2: oppositeRoofLine.x2, - y2: oppositeRoofLine.y2, - line: oppositeRoofLine, - }) - - const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] - if (ridgeLine.y1 === firstHipLine.y1) { - connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2) - } else { - connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1) - } - - const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) - baseHipLines.push({ - x1: connectRoofLine.x1, - y1: connectRoofLine.y1, - x2: connectRoofLine.x2, - y2: connectRoofLine.y2, - line: connectRoofLine, - }) - - /** 다른 방향의 추녀마루 */ - const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine - const secondDegree = - gableVector === Math.sign(ridgeLine.y1 - prevLine.y1) - ? getDegreeByChon(nextLine.attributes.pitch) - : getDegreeByChon(prevLine.attributes.pitch) - - const intersections = [] - const checkEdge = { - vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 }, - vertex2: { x: ridgeLine.x2, y: ridgeLine.y1 === secondHipLine.y1 ? secondHipLine.y2 : secondHipLine.y1 }, - } - baseGableRidgeLines - .filter((ridge) => ridge !== ridgeLine) - .forEach((ridge) => { - const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } } - const intersection = edgesIntersection(ridgeEdge, checkEdge) - if (intersection && !intersections.includes(intersection)) { - const size = Big(intersection.x) - .minus(Big(ridgeLine.x2)) - .pow(2) - .plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2)) - .sqrt() - .toNumber() - intersections.push({ intersection, size, ridge }) - } - }) - intersections.sort((a, b) => a.size - b.size) - if (intersections.length > 0) { - const intersection = intersections[0].intersection - const intersectRidge = intersections[0].ridge - const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y] - const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, secondDegree, secondDegree) - baseHipLines.push({ - x1: oppositeLine.x1, - y1: oppositeLine.y1, - x2: oppositeLine.x2, - y2: oppositeLine.y2, - line: oppositeRoofLine, - }) - - const ridgeVector = Math.sign(ridgeLine.x1 - ridgeLine.x2) - const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2] - if (ridgeVector === Math.sign(oppositeRoofLine.x1 - intersectRidge.x2)) { - connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2) - } else { - connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1) - } - - const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode) - baseHipLines.push({ - x1: connectRoofLine.x1, - y1: connectRoofLine.y1, - x2: connectRoofLine.x2, - y2: connectRoofLine.y2, - line: connectRoofLine, - }) - } - } - } - } - } - }) - - /** 중복제거 */ - baseRidgeLines.forEach((ridge) => { - baseRidgeLines - .filter((ridge2) => ridge !== ridge2) - .forEach((ridge2) => { - let overlap = segmentsOverlap(ridge, ridge2) - if (overlap) { - roof.canvas.remove(ridge) - roof.canvas.remove(ridge2) - baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2) - - baseRidgeCount = baseRidgeCount - 2 - - let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) - let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2) - let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) - let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) - - const newRidge = drawRidgeLine([x1, y1, x2, y2], canvas, roof, textMode) - baseRidgeLines.push(newRidge) - baseRidgeCount = baseRidgeCount + 1 - } - }) - }) - - /** ㄴ 모양 처마에 추녀마루를 그린다. */ - drawEavesSecondLines.forEach((current) => { - const { currentBaseLine, prevBaseLine, nextBaseLine } = current - const currentLine = currentBaseLine.line - const prevLine = prevBaseLine.line - const nextLine = nextBaseLine.line - let { x1, x2, y1, y2 } = currentBaseLine - - /** 이전 라인의 경사 */ - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - /** 다음 라인의 경사 */ - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - - /** 이전, 다음라인의 사잇각의 vector를 구한다. */ - let prevVector = getHalfAngleVector(prevLine, currentLine) - let nextVector = getHalfAngleVector(currentLine, nextLine) - - let prevHipVector = { x: prevVector.x, y: prevVector.y } - let nextHipVector = { x: nextVector.x, y: nextVector.y } - - /** 각 라인의 흐름 방향을 확인한다. */ - const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint) - - /** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */ - let hipLength = Big(x2) - .minus(Big(x1)) - .plus(Big(y2).minus(Big(y1))) - .abs() - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: Big(x1).plus(Big(prevHipVector.x).times(10)), - y: Big(y1).plus(Big(prevHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(prevCheckPoint)) { - prevHipVector = { x: -prevHipVector.x, y: -prevHipVector.y } - } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: Big(x2).plus(Big(nextHipVector.x).times(10)), - y: Big(y2).plus(Big(nextHipVector.y).times(10)), - } - if (!checkWallPolygon.inPolygon(nextCheckPoint)) { - nextHipVector = { x: -nextHipVector.x, y: -nextHipVector.y } - } - - let prevHipLine, nextHipLine - /** 이전라인과의 연결지점에 추녀마루를 그린다. */ - if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0 && prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let prevEndPoint = { - x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2), - y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2), - } - - const prevEndEdge = { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint } - - const intersectBaseLine = [] - baseLines - .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) - .filter((line) => { - if (currentAngle === 0 || currentAngle === 180) { - return Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y - } else { - return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x - } - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(prevEndEdge, lineEdge) - - if (intersection && Math.sign(intersection.x - x1) === nextHipVector.x && Math.sign(intersection.y - y1) === nextHipVector.y) { - const size = Big(intersection.x - x1) - .abs() - .pow(2) - .plus( - Big(intersection.y - y1) - .pow(2) - .abs(), - ) - .sqrt() - if (size.gt(0)) { - intersectBaseLine.push({ - intersection, - size, - }) - } - } - }) - - const intersectBase = intersectBaseLine.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectBaseLine[0]) - - if (intersectBase) { - prevEndPoint = { - x: Big(x1) - .plus(Big(nextHipVector.x).times(intersectBase.size.div(2))) - .round(2), - y: Big(y1) - .plus(Big(nextHipVector.y).times(intersectBase.size.div(2))) - .round(2), - } - } - - const intersectRidgeLine = [] - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if (intersection && !intersection.isIntersectionOutside) { - intersectRidgeLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x1)) - .abs() - .pow(2) - .plus(Big(intersection.y).minus(Big(y1)).pow(2).abs()) - .sqrt(), - }) - } - }) - - const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - - if (intersectRidge) { - prevEndPoint.x = Big(intersectRidge.intersection.x).round(1) - prevEndPoint.y = Big(intersectRidge.intersection.y).round(1) - } - - const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber()) - const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber()) - /** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/ - let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt() - scale = scale.eq(0) ? Big(1) : scale - - /** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */ - const hipEdge = { - vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() }, - vertex2: prevEndPoint, - } - - let intersectPoints = [] - /** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - !intersection.isIntersectionOutside && - Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) && - Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y) - ) { - const intersectSize = prevEndPoint.x - .minus(Big(intersection.x)) - .abs() - .pow(2) - .plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2).abs()) - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - }) - } - }) - - intersectPoints = intersectPoints.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectPoints[0]) - - /** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/ - if (intersectPoints && intersectPoints.intersection) { - prevHipLine = drawHipLine( - [intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - currentDegree, - ) - baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine }) - } - } - /** 다음라인과의 연결지점에 추녀마루를 그린다. */ - if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0 && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - let nextEndPoint = { - x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2), - y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2), - } - - const nextEndEdge = { - vertex1: { x: x2, y: y2 }, - vertex2: nextEndPoint, - } - - const intersectBaseLine = [] - baseLines - .filter((line) => line !== currentLine && line !== prevLine && line !== nextLine) - .filter((line) => { - if (currentAngle === 0 || currentAngle === 180) { - return Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y - } else { - return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x - } - }) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(nextEndEdge, lineEdge) - - if (intersection && Math.sign(intersection.x - x2) === nextHipVector.x && Math.sign(intersection.y - y2) === nextHipVector.y) { - const size = Big(intersection.x - x2) - .abs() - .pow(2) - .plus( - Big(intersection.y - y2) - .abs() - .pow(2), - ) - .sqrt() - if (size.gt(0)) { - intersectBaseLine.push({ - intersection, - size, - }) - } - } - }) - - const intersectBase = intersectBaseLine.reduce((prev, current) => { - return prev.size.lt(current.size) ? prev : current - }, intersectBaseLine[0]) - - if (intersectBase) { - const size = Big(getAdjacent(intersectBase.size)) - nextEndPoint = { - x: Big(x2) - .plus(Big(nextHipVector.x).times(size.div(2))) - .round(2), - y: Big(y2) - .plus(Big(nextHipVector.y).times(size.div(2))) - .round(2), - } - } - - const intersectRidgeLine = [] - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection( - { vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint }, - { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, - ) - if ( - intersection && - ((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) || - (line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y)) - ) { - intersectRidgeLine.push({ - intersection, - distance: Big(intersection.x) - .minus(Big(x1)) - .pow(2) - .plus(Big(intersection.y).minus(Big(y1)).pow(2)) - .sqrt(), - }) - } - }) - - const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0]) - - if (intersectRidge) { - nextEndPoint.x = Big(intersectRidge.intersection.x).round(1) - nextEndPoint.y = Big(intersectRidge.intersection.y).round(1) - } - - const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber()) - const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber()) - let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt() - scale = scale.eq(0) ? Big(1) : scale - - const hipEdge = { - vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() }, - vertex2: nextEndPoint, - } - - let intersectPoints = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(hipEdge, lineEdge) - if ( - intersection && - !intersection.isIntersectionOutside && - Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) && - Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y) - ) { - const intersectSize = nextEndPoint.x - .minus(Big(intersection.x)) - .pow(2) - .plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - }) - } - }) - - intersectPoints = intersectPoints.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectPoints[0]) - - if (intersectPoints && intersectPoints.intersection) { - nextHipLine = drawHipLine( - [intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()], - canvas, - roof, - textMode, - - prevDegree, - currentDegree, - ) - baseHipLines.push({ - x1: x2, - y1: y2, - x2: nextEndPoint.x.toNumber(), - y2: nextEndPoint.y.toNumber(), - line: nextHipLine, - }) - } - } - }) - - /** baseHipLine 이 ridge에 붙지 않은 경우 확인 */ - baseHipLines.forEach((hipLine) => { - const ridgeCount = baseRidgeLines.filter( - (ridgeLine) => (hipLine.x2 === ridgeLine.x1 && hipLine.y2 === ridgeLine.y1) || (hipLine.x2 === ridgeLine.x2 && hipLine.y2 === ridgeLine.y2), - ).length - if (ridgeCount === 0) { - const hipXVector = Big(hipLine.x2).minus(hipLine.x1) - const hipYVector = Big(hipLine.y2).minus(hipLine.y1) - const hipSize = hipXVector.abs().pow(2).plus(hipYVector.abs().pow(2)).sqrt() - - const intersectRidgePoints = [] - - const hipLineEdge = { vertex1: { x: hipLine.x1, y: hipLine.y1 }, vertex2: { x: hipLine.x2, y: hipLine.y2 } } - baseRidgeLines.forEach((ridgeLine) => { - const ridgeLineEdge = { - vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, - vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 }, - } - const intersection = edgesIntersection(hipLineEdge, ridgeLineEdge) - - if (intersection) { - const intersectXVector = Big(intersection.x).minus(Big(hipLine.x1)) - const intersectYVector = Big(intersection.y).minus(Big(hipLine.y1)) - const intersectSize = intersectXVector.pow(2).plus(intersectYVector.pow(2)).sqrt() - - if (!intersection.isIntersectionOutside) { - intersectRidgePoints.push({ - x: intersection.x, - y: intersection.y, - size: intersectSize, - }) - } else if ( - ((intersection.x === ridgeLine.x1 && intersection.y === ridgeLine.y1) || - (intersection.x === ridgeLine.x2 && intersection.y === ridgeLine.y2)) && - Math.sign(hipXVector.toNumber()) === Math.sign(intersectXVector.toNumber()) && - Math.sign(hipYVector.toNumber()) === Math.sign(intersectYVector.toNumber()) && - intersectSize.gt(0) && - intersectSize.lt(hipSize) - ) { - intersectRidgePoints.push({ - x: intersection.x, - y: intersection.y, - size: intersectSize, - }) - } - } - }) - intersectRidgePoints.sort((prev, current) => prev.size.minus(current.size).toNumber()) - if (intersectRidgePoints.length > 0) { - hipLine.x2 = intersectRidgePoints[0].x - hipLine.y2 = intersectRidgePoints[0].y - hipLine.line.set({ x2: intersectRidgePoints[0].x, y2: intersectRidgePoints[0].y }) - const hipSize = reCalculateSize(hipLine.line) - hipLine.line.attributes.planeSize = hipSize.planeSize - hipLine.line.attributes.actualSize = hipSize.actualSize - hipLine.line.fire('modified') - } - } - }) - - /** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/ - /** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/ - let unFinishedRidge = [] - baseRidgeLines.forEach((current) => { - let checkPoint = [ - { x: current.x1, y: current.y1, line: current, cnt: 0, onRoofLine: false }, - { x: current.x2, y: current.y2, line: current, cnt: 0, onRoofLine: false }, - ] - baseHipLines.forEach((line) => { - if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { - checkPoint[0].cnt = checkPoint[0].cnt + 1 - } - if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { - checkPoint[1].cnt = checkPoint[1].cnt + 1 - } - }) - - /** 마루의 포인트가 지붕선 위에 있는경우는 제외 (케라바 등)*/ - roof.lines.forEach((line) => { - if ( - line.x1 === line.x2 && - checkPoint[0].x === line.x1 && - ((line.y1 <= checkPoint[0].y && line.y2 >= checkPoint[0].y) || (line.y1 >= checkPoint[0].y && line.y2 <= checkPoint[0].y)) - ) { - checkPoint[0].onRoofLine = true - } - if ( - line.y1 === line.y2 && - checkPoint[0].y === line.y1 && - ((line.x1 <= checkPoint[0].x && line.x2 >= checkPoint[0].x) || (line.x1 >= checkPoint[0].x && line.x2 <= checkPoint[0].x)) - ) { - checkPoint[0].onRoofLine = true - } - if ( - line.x1 === line.x2 && - checkPoint[1].x === line.x1 && - ((line.y1 <= checkPoint[1].y && line.y2 >= checkPoint[1].y) || (line.y1 >= checkPoint[1].y && line.y2 <= checkPoint[1].y)) - ) { - checkPoint[1].onRoofLine = true - } - if ( - line.y1 === line.y2 && - checkPoint[1].y === line.y1 && - ((line.x1 <= checkPoint[1].x && line.x2 >= checkPoint[1].x) || (line.x1 >= checkPoint[1].x && line.x2 <= checkPoint[1].x)) - ) { - checkPoint[1].onRoofLine = true - } - }) - if ((checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) && !checkPoint[0].onRoofLine) { - unFinishedRidge.push(checkPoint[0]) - } - if ((checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) && !checkPoint[1].onRoofLine) { - unFinishedRidge.push(checkPoint[1]) - } - }) - - /** 2. 그려진 추녀마루 중 완성되지 않은 것을 찾는다. 완성되지 않았다는 것은 연결된 포인트가 홀수인 경우로 판단한다.*/ - const findUnFinishedPoints = (baseHipLines) => { - let unFinishedPoint = [] - baseHipLines.forEach((current) => { - let checkPoint = [ - { x: current.x1, y: current.y1, checked: true, line: current.line }, - { x: current.x2, y: current.y2, checked: true, line: current.line }, - ] - baseLines.forEach((line) => { - if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) { - checkPoint[0].checked = false - } - if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) { - checkPoint[1].checked = false - } - }) - - const samePoints = [] - checkPoint - .filter((point) => point.checked) - .forEach((point) => { - baseHipLines.forEach((line) => { - if (line.x1 === point.x && line.y1 === point.y) { - samePoints.push({ x: point.x, y: point.y, line: line }) - } - if (line.x2 === point.x && line.y2 === point.y) { - samePoints.push({ x: point.x, y: point.y, line: line }) - } - }) - }) - if (samePoints.length > 0 && samePoints.length % 2 !== 0) { - unFinishedPoint.push(samePoints[0]) - } - }) - return unFinishedPoint - } - - let unFinishedPoint = findUnFinishedPoints(baseHipLines) - - /**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/ - /**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */ - let degreeAllLine = [] - baseLines - .filter((line) => eavesType.includes(line.attributes.type)) - .forEach((line) => { - const pitch = line.attributes.pitch - degreeAllLine.push(getDegreeByChon(pitch)) - }) - - let currentDegree, prevDegree - degreeAllLine = [...new Set(degreeAllLine)] - currentDegree = degreeAllLine[0] - if (degreeAllLine.length === 1) { - prevDegree = currentDegree - } else { - prevDegree = degreeAllLine[1] - } - - /** 라인이 부재한 마루에 라인을 찾아 그린다.*/ - unFinishedRidge.forEach((current) => { - let checkPoints = [] - - unFinishedPoint - .filter( - (point) => - point.x !== current.x && - point.y !== current.y && - Big(point.x) - .minus(Big(current.x)) - .abs() - .minus(Big(point.y).minus(Big(current.y)).abs()) - .abs() - .lt(1), - ) - .forEach((point) => { - const pointEdge = { vertex1: { x: point.x, y: point.y }, vertex2: { x: current.x, y: current.y } } - let isIntersection = false - baseLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(pointEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - isIntersection = true - } - }) - if (!isIntersection) { - checkPoints.push({ - point, - size: Big(point.x) - .minus(Big(current.x)) - .abs() - .pow(2) - .plus(Big(point.y).minus(Big(current.y)).abs().pow(2)) - .sqrt(), - }) - } - }) - if (checkPoints.length > 0) { - checkPoints.sort((a, b) => a.size - b.size) - const maxCnt = Big(2).minus(Big(current.cnt)).toNumber() - for (let i = 0; i < maxCnt; i++) { - const checkPoint = checkPoints[i] - if (checkPoint) { - let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y] - let hipBasePoint - - baseHipLines.forEach((line) => { - const checkAngel1 = calculateAngle({ x: point[0], y: point[1] }, { x: point[2], y: point[3] }) - const checkAngel2 = calculateAngle( - { x: line.line.x1, y: line.line.y1 }, - { - x: line.line.x2, - y: line.line.y2, - }, - ) - - const checkPointCnt = checkPoints.filter((point) => point.point.x === checkPoint.point.x && point.point.y === checkPoint.point.y).length - const isConnectLine = - ((line.line.x1 === point[0] && line.line.y1 === point[1]) || (line.line.x2 === point[0] && line.line.y2 === point[1])) && - checkAngel1 === checkAngel2 - const isOverlap = segmentsOverlap(line.line, { x1: point[0], y1: point[1], x2: point[2], y2: point[3] }) - const isSameLine = - (point[0] === line.x2 && point[1] === line.y2 && point[2] === line.x1 && point[3] === line.y1) || - (point[0] === line.x1 && point[1] === line.y1 && point[2] === line.x2 && point[3] === line.y2) - - if (checkPointCnt === 1 && (isConnectLine || isOverlap || isSameLine)) { - /** 겹치는 추녀마루와 하나의 선으로 변경*/ - const mergePoint = [ - { x: point[0], y: point[1] }, - { x: point[2], y: point[3] }, - { x: line.line.x1, y: line.line.y1 }, - { x: line.line.x2, y: line.line.y2 }, - ] - /** baseHipLines도 조정*/ - mergePoint.sort((a, b) => a.x - b.x) - - hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 } - point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y] - const theta = Big( - Math.acos( - Big(line.line.attributes.planeSize).div( - line.line.attributes.actualSize === 0 || line.line.attributes.actualSize === '' || line.line.attributes.actualSize === undefined - ? line.line.attributes.planeSize - : line.line.attributes.actualSize, - ), - ), - ) - .times(180) - .div(Math.PI) - .round(1) - prevDegree = theta.toNumber() - currentDegree = theta.toNumber() - canvas.remove(line.line) - baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line) - } - }) - - const hipLine = drawHipLine(point, canvas, roof, textMode, prevDegree, currentDegree) - if (hipBasePoint) { - baseHipLines.push({ x1: hipBasePoint.x1, y1: hipBasePoint.y1, x2: point[2], y2: point[3], line: hipLine }) - } else { - baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: hipLine }) - } - current.cnt = current.cnt + 1 - } - } - } - - /** 라인이 다 그려지지 않은 경우 */ - if (current.cnt % 2 !== 0) { - let basePoints = baseLinePoints - .filter((point) => - Big(point.x) - .minus(Big(current.x)) - .abs() - .minus(Big(point.y).minus(Big(current.y)).abs()) - .abs() - .lt(1), - ) - .filter((point) => { - const pointEdge = { vertex1: { x: current.x, y: current.y }, vertex2: { x: point.x, y: point.y } } - - const intersectPoints = [] - baseLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(pointEdge, lineEdge) - if (intersection && !intersection.isIntersectionOutside) { - intersectPoints.push(intersection) - } - }) - return ( - !intersectPoints.filter( - (intersect) => !(Big(intersect.x).minus(Big(point.x)).abs().lt(1) && Big(intersect.y).minus(Big(point.y)).abs().lt(1)), - ).length > 0 - ) - }) - - /** hip을 그리기 위한 기본 길이*/ - let hipSize = Big(0) - if (basePoints.length > 0) { - basePoints.sort((a, b) => { - const aSize = Big(a.x) - .minus(Big(current.x)) - .abs() - .pow(2) - .plus(Big(a.y).minus(Big(current.y)).abs().pow(2)) - .sqrt() - const bSize = Big(b.x) - .minus(Big(current.x)) - .abs() - .pow(2) - .plus(Big(b.y).minus(Big(current.y)).abs().pow(2)) - .sqrt() - return aSize - bSize - }) - const baseHips = baseHipLines.filter((line) => line.x1 === basePoints[0].x && line.y1 === basePoints[0].y) - if (baseHips.length > 0) { - hipSize = Big(baseHips[0].line.attributes.planeSize) - } - } - - if (hipSize.eq(0)) { - const ridge = current.line - basePoints = baseHipLines - .filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2)) - .filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0) - basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize) - if (basePoints.length > 0 && basePoints[0].line) { - hipSize = Big(basePoints[0].line.attributes.planeSize) - } else { - hipSize = Big(0) // 또는 기본값 설정 - } - } - hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber() - - /** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */ - const checkEdge45 = { - vertex1: { x: current.x, y: current.y }, - vertex2: { x: current.x + hipSize, y: current.y - hipSize }, - } - const checkEdge135 = { - vertex1: { x: current.x, y: current.y }, - vertex2: { x: current.x + hipSize, y: current.y + hipSize }, - } - const checkEdge225 = { - vertex1: { x: current.x, y: current.y }, - vertex2: { x: current.x - hipSize, y: current.y + hipSize }, - } - const checkEdge315 = { - vertex1: { x: current.x, y: current.y }, - vertex2: { x: current.x - hipSize, y: current.y - hipSize }, - } - - let intersectPoints = [] - let notIntersect45 = true, - notIntersect135 = true, - notIntersect225 = true, - notIntersect315 = true - baseLines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection45 = edgesIntersection(checkEdge45, lineEdge) - const intersection135 = edgesIntersection(checkEdge135, lineEdge) - const intersection225 = edgesIntersection(checkEdge225, lineEdge) - const intersection315 = edgesIntersection(checkEdge315, lineEdge) - - if (intersection45 && !intersection45.isIntersectionOutside) { - intersectPoints.push(intersection45) - notIntersect45 = false - } - if (intersection135 && !intersection135.isIntersectionOutside) { - intersectPoints.push(intersection135) - notIntersect135 = false - } - if (intersection225 && !intersection225.isIntersectionOutside) { - intersectPoints.push(intersection225) - notIntersect225 = false - } - if (intersection315 && !intersection315.isIntersectionOutside) { - intersectPoints.push(intersection315) - notIntersect315 = false - } - }) - /** baseLine과 교차하지 않는 포인트를 추가한다.*/ - if (notIntersect45) { - intersectPoints.push(checkEdge45.vertex2) - } - if (notIntersect135) { - intersectPoints.push(checkEdge135.vertex2) - } - if (notIntersect225) { - intersectPoints.push(checkEdge225.vertex2) - } - if (notIntersect315) { - intersectPoints.push(checkEdge315.vertex2) - } - /** baseLine의 각 좌표와 교차하는 경우로 한정*/ - intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0) - /** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */ - intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0) - /** 중복제거 */ - intersectPoints = intersectPoints.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) - - intersectPoints.forEach((is) => { - const points = [current.x, current.y, is.x, is.y] - /** 추녀마루의 연결점이 처마라인이 아닌경우 return */ - let hasGable = false - baseLines - .filter((line) => (line.x1 === points[2] && line.y1 === points[3]) || (line.x2 === points[2] && line.y2 === points[3])) - .forEach((line) => { - if (!eavesType.includes(line.attributes.type)) { - hasGable = true - } - }) - if (hasGable) return - const pointEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } } - const vectorX = Math.sign(Big(points[2]).minus(Big(points[0])).toNumber()) - const vectorY = Math.sign(Big(points[3]).minus(Big(points[1])).toNumber()) - const roofIntersections = [] - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(pointEdge, lineEdge) - if (intersection) { - const vectorIntersectionX = Math.sign(Big(intersection.x).minus(Big(points[0])).toNumber()) - const vectorIntersectionY = Math.sign(Big(intersection.y).minus(Big(points[1])).toNumber()) - if (vectorIntersectionX === vectorX && vectorIntersectionY === vectorY) { - roofIntersections.push({ - x: intersection.x, - y: intersection.y, - size: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: intersection.x, y2: intersection.y }), - }) - } - } - }) - roofIntersections.sort((a, b) => a.size - b.size) - - const hipLine = drawHipLine( - [points[0], points[1], roofIntersections[0].x, roofIntersections[0].y], - canvas, - roof, - textMode, - - prevDegree, - currentDegree, - ) - baseHipLines.push({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], line: hipLine }) - current.cnt = current.cnt + 1 - }) - } - }) - - /** hip이 짝수개가 맞다아있는데 마루와 연결되지 않는 포인트를 찾는다. 그려지지 않은 마루를 찾기 위함.*/ - let noRidgeHipPoints = baseHipLines - .filter((current) => current.x1 !== current.x2 && current.y1 !== current.y2) - .filter((current) => { - const lines = baseHipLines - .filter((line) => line !== current) - .filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) - - return lines.length !== 0 && lines.length % 2 !== 0 - }) - .filter( - (current) => - baseRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) - .length === 0 && - baseGableRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) - .length === 0, - ) - - noRidgeHipPoints.forEach((current) => { - const orthogonalPoints = noRidgeHipPoints.filter((point) => { - if (point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2))) { - return true - } - }) - - /** 직교 하는 포인트가 존재 할때 마루를 그린다. */ - if (orthogonalPoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { - baseRidgeCount = baseRidgeCount + 1 - const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2] - const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) - baseRidgeLines.push(ridgeLine) - } - }) - - /** 중복제거*/ - baseRidgeLines.forEach((current) => { - const sameRidge = baseRidgeLines.filter( - (line) => - line !== current && - ((line.x1 === current.x1 && line.y1 === current.y1 && line.x2 === current.x2 && line.y2 === current.y2) || - (line.x1 === current.x2 && line.y1 === current.y2 && line.x2 === current.x1 && line.y2 === current.y1)), - ) - if (sameRidge.length > 0) { - sameRidge.forEach((duplicateLine) => { - const index = baseRidgeLines.indexOf(duplicateLine) - if (index !== -1) { - baseRidgeLines.splice(index, 1) - } - canvas.remove(duplicateLine) - }) - } - }) - - /** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */ - const checkEdgeLines = [] - noRidgeHipPoints.forEach((current) => { - noRidgeHipPoints - .filter((point) => point !== current) - .forEach((point) => { - checkEdgeLines.push( - { vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: current.x2, y: point.y2 } }, - { vertex1: { x: current.x2, y: point.y2 }, vertex2: { x: point.x2, y: point.y2 } }, - { vertex1: { x: point.x2, y: point.y2 }, vertex2: { x: point.x2, y: current.y2 } }, - { vertex1: { x: point.x2, y: current.y2 }, vertex2: { x: current.x2, y: current.y2 } }, - ) - }) - }) - - /** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */ - let unFinishedPoints = [] - let unFinishedLines = [] - let intersectPoints = [] - baseHipLines.forEach((line) => { - if (baseLinePoints.filter((point) => point.x === line.x1 && point.y === line.y1).length === 0) { - unFinishedPoints.push({ x: line.x1, y: line.y1 }) - } - if (baseLinePoints.filter((point) => point.x === line.x2 && point.y === line.y2).length === 0) { - unFinishedPoints.push({ x: line.x2, y: line.y2 }) - } - }) - unFinishedPoints - .filter((point) => unFinishedPoints.filter((p) => p !== point && p.x === point.x && p.y === point.y).length === 0) - .forEach((point) => { - baseHipLines - .filter((line) => (line.x1 === point.x && line.y1 === point.y) || (line.x2 === point.x && line.y2 === point.y)) - .forEach((line) => unFinishedLines.push(line)) - }) - - unFinishedLines.forEach((line) => { - const xVector = Math.sign(Big(line.x2).minus(Big(line.x1))) - const yVector = Math.sign(Big(line.y2).minus(Big(line.y1))) - let lineIntersectPoints = [] - checkEdgeLines.forEach((edge) => { - const intersectEdge = edgesIntersection(edge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersectEdge) { - const isXVector = Math.sign(Big(intersectEdge.x).minus(Big(line.x1))) === xVector - const isYVector = Math.sign(Big(intersectEdge.y).minus(Big(line.y1))) === yVector - if (isXVector && isYVector) { - lineIntersectPoints.push({ - intersection: intersectEdge, - size: Big(intersectEdge.x) - .minus(Big(line.x1)) - .abs() - .pow(2) - .plus(Big(intersectEdge.y).minus(Big(line.y1)).abs().pow(2)) - .sqrt() - .round(1) - .toNumber(), - }) - } - } - }) - - lineIntersectPoints = lineIntersectPoints.filter( - (point, index, self) => index === self.findIndex((p) => p.intersection.x === point.intersection.x && p.intersection.y === point.intersection.y), - ) - lineIntersectPoints.sort((a, b) => a.size - b.size) - if (lineIntersectPoints.length > 0) { - intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line }) - } - }) - - /** 마루를 그릴수 있는지 찾는다. */ - noRidgeHipPoints.forEach((hipPoint) => { - const ridgePoints = [] - intersectPoints - .filter( - (intersectPoint) => - (intersectPoint.intersection.x !== hipPoint.x2 && intersectPoint.intersection.y === hipPoint.y2) || - (intersectPoint.intersection.x === hipPoint.x2 && intersectPoint.intersection.y !== hipPoint.y2), - ) - .forEach((intersectPoint) => { - ridgePoints.push({ - intersection: intersectPoint, - distance: Big(intersectPoint.intersection.x) - .minus(Big(hipPoint.x2)) - .abs() - .pow(2) - .plus(Big(intersectPoint.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) - .sqrt() - .round(1) - .toNumber(), - }) - }) - ridgePoints.sort((a, b) => a.distance - b.distance) - - if (ridgePoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) { - const intersection = ridgePoints[0].intersection - const isPoint = intersection.intersection - const points = [hipPoint.x2, hipPoint.y2, isPoint.x, isPoint.y] - const ridgeLine = drawRidgeLine(points, canvas, roof, textMode) - baseRidgeCount = baseRidgeCount + 1 - baseRidgeLines.push(ridgeLine) - - let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0] - baseHipLine.x2 = isPoint.x - baseHipLines.y2 = isPoint.y - /** 보조선 라인 조정*/ - const hipLine = intersection.line.line - /** 평면길이 */ - const planeSize = calcLinePlaneSize({ - x1: hipLine.x1, - y1: hipLine.y1, - x2: isPoint.x, - y2: isPoint.y, - }) - /** 실제길이 */ - const actualSize = - prevDegree === currentDegree - ? calcLineActualSize( - { - x1: hipLine.x1, - y1: hipLine.y1, - x2: isPoint.x, - y2: isPoint.y, - }, - currentDegree, - ) - : 0 - hipLine.set({ - x2: isPoint.x, - y2: isPoint.y, - attributes: { roofId: roof.id, planeSize, actualSize }, - }) - hipLine.fire('modified') - intersectPoints = intersectPoints.filter((isp) => isp !== intersection) - noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) - } else { - const linePoints = [] - intersectPoints.forEach((intersectPoint) => { - const intersection = intersectPoint.intersection - const xVector = Math.sign(Big(intersection.x).minus(Big(hipPoint.x2))) - const yVector = Math.sign(Big(intersection.y).minus(Big(hipPoint.y2))) - - const checkEdge = { - vertex1: { x: intersection.x, y: intersection.y }, - vertex2: { - x: Big(intersection.x).plus(Big(xVector).times(10)).toNumber(), - y: Big(intersection.y).plus(Big(yVector).times(10)).toNumber(), - }, - } - const intersectX = edgesIntersection( - { - vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, - vertex2: { x: Big(hipPoint.x2).plus(Big(xVector).neg().times(10)).toNumber(), y: hipPoint.y2 }, - }, - checkEdge, - ) - const intersectY = edgesIntersection( - { - vertex1: { x: hipPoint.x2, y: hipPoint.y2 }, - vertex2: { x: hipPoint.x2, y: Big(hipPoint.y2).plus(Big(yVector).neg().times(10)).toNumber() }, - }, - checkEdge, - ) - - let distanceX = Infinity, - distanceY = Infinity - - if (intersectX) { - distanceX = Big(intersectX.x) - .minus(Big(intersection.x)) - .abs() - .pow(2) - .plus(Big(intersectX.y).minus(Big(intersection.y)).abs().pow(2)) - .sqrt() - .round(1) - .toNumber() - } - if (intersectY) { - distanceY = Big(intersectY.x) - .minus(Big(intersection.x)) - .abs() - .pow(2) - .plus(Big(intersectY.y).minus(Big(intersection.y)).abs().pow(2)) - .sqrt() - .round(1) - .toNumber() - } - - if (distanceX < distanceY) { - linePoints.push({ intersection: intersectX, intersectPoint }) - } - if (distanceX > distanceY) { - linePoints.push({ intersection: intersectY, intersectPoint }) - } - }) - - const linePoint = linePoints.reduce((prev, current) => { - const prevDistance = Big(prev.intersection.x) - .minus(Big(hipPoint.x2)) - .abs() - .pow(2) - .plus(Big(prev.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) - .sqrt() - const currentDistance = Big(current.intersection.x) - .minus(Big(hipPoint.x2)) - .abs() - .pow(2) - .plus(Big(current.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2)) - .sqrt() - if (prevDistance < currentDistance) { - return prev - } else { - return current - } - }, linePoints[0]) - - if (!linePoint) return - const hipStartPoint = [hipPoint.x2, hipPoint.y2, linePoint.intersection.x, linePoint.intersection.y] - /** 직선인 경우 마루를 그린다.*/ - if ( - ((hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) || - (hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3])) && - baseRidgeCount < getMaxRidge(baseLines.length) - ) { - const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode) - baseRidgeCount = baseRidgeCount + 1 - baseRidgeLines.push(ridgeLine) - noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) - } - /** 대각선인경우 hip을 그린다. */ - if ( - Big(hipStartPoint[0]) - .minus(Big(hipStartPoint[2])) - .abs() - .minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs()) - .abs() - .lt(1) - ) { - // console.log('힙1') - const hipLine = drawHipLine(hipStartPoint, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ - x1: hipStartPoint[0], - y1: hipStartPoint[1], - x2: hipStartPoint[2], - y2: hipStartPoint[3], - line: hipLine, - }) - noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint) - } - - const isStartPoint = [ - linePoint.intersection.x, - linePoint.intersection.y, - linePoint.intersectPoint.intersection.x, - linePoint.intersectPoint.intersection.y, - ] - if ( - ((isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) || - (isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3])) && - baseRidgeCount < getMaxRidge(baseLines.length) - ) { - const ridgeLine = drawRidgeLine(isStartPoint, canvas, roof, textMode) - baseRidgeCount = baseRidgeCount + 1 - baseRidgeLines.push(ridgeLine) - } - if ( - Big(isStartPoint[0]) - .minus(Big(isStartPoint[2])) - .abs() - .minus(Big(isStartPoint[1]).minus(Big(isStartPoint[3])).abs()) - .abs() - .lt(1) - ) { - const hipLine = drawHipLine(isStartPoint, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ - x1: isStartPoint[0], - y1: isStartPoint[1], - x2: isStartPoint[2], - y2: isStartPoint[3], - line: hipLine, - }) - } - } - }) - - const ridgeAllPoints = [] - baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) - baseGableRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })) - - /** hip 중에 지붕의 라인과 만나지 않은 선을 확인.*/ - baseHipLines - .filter( - (hip) => baseLinePoints.filter((point) => (point.x === hip.x1 && point.y === hip.y1) || (point.x === hip.x2 && point.y === hip.y2)).length > 0, - ) - .filter((hip) => { - const hipEdge = { vertex1: { x: hip.line.x1, y: hip.line.y1 }, vertex2: { x: hip.line.x2, y: hip.line.y2 } } - const hipVectorX = Math.sign(Big(hip.x1).minus(Big(hip.x2))) - const hipVectorY = Math.sign(Big(hip.y1).minus(Big(hip.y2))) - let isIntersect = false - roof.lines.forEach((line) => { - const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(edge, hipEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(intersection.x).minus(Big(hip.x2))) - const isVectorY = Math.sign(Big(intersection.y).minus(Big(hip.y2))) - if (isVectorX === hipVectorX && isVectorY === hipVectorY) { - isIntersect = true - } - } - }) - return !isIntersect - }) - .forEach((hip) => { - const hipLine = hip.line - if (hipLine) { - const hipVectorX = Big(hipLine.x2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.x1).minus(Big(hipLine.x2)).neg().s)) - const hipVectorY = Big(hipLine.y2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.y1).minus(Big(hipLine.y2)).neg().s)) - const overlapLineX = roof.lines - .filter((roofLine) => roofLine.x1 !== roofLine.x2 && roofLine.y1 === hipLine.y2 && roofLine.y2 === hipLine.y2) - .filter((roofLine) => { - const minX = Math.min(roofLine.x1, roofLine.x2, hipLine.x2) - const maxX = Math.max(roofLine.x1, roofLine.x2, hipLine.x2) - const checkLineEdge = { vertex1: { x: minX, y: hipLine.y2 }, vertex2: { x: maxX, y: hipLine.y2 } } - let isIntersect = false - baseHipLines.forEach((baseHipLine) => { - const edge = { - vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, - vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 }, - } - const intersection = edgesIntersection(edge, checkLineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(intersection.x).minus(Big(baseHipLine.x2))) - const isVectorY = Math.sign(Big(intersection.y).minus(Big(baseHipLine.y2))) - if (isVectorX === hipVectorX && isVectorY === hipVectorY) { - isIntersect = true - } - } - }) - baseRidgeLines.forEach((baseRidgeLine) => { - const edge = { - vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, - vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 }, - } - const intersection = edgesIntersection(edge, checkLineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) - const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) - if (isVectorX === hipVectorX && isVectorY === hipVectorY) { - isIntersect = true - } - } - }) - return !isIntersect - }) - const overlapLineY = roof.lines - .filter((roofLine) => roofLine.y1 !== roofLine.y2 && roofLine.x1 === hipLine.x2 && roofLine.x2 === hipLine.x2) - .filter((roofLine) => { - const minY = Math.min(roofLine.y1, roofLine.y2, hipLine.y2) - const maxY = Math.max(roofLine.y1, roofLine.y2, hipLine.y2) - const checkLineEdge = { vertex1: { x: hipLine.x2, y: minY }, vertex2: { x: hipLine.x2, y: maxY } } - let isIntersect = false - baseHipLines.forEach((baseHipLine) => { - const edge = { - vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 }, - vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 }, - } - const intersection = edgesIntersection(edge, checkLineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) - const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) - if (isVectorX === hipVectorX && isVectorY === hipVectorY) { - isIntersect = true - } - } - }) - baseRidgeLines.forEach((baseRidgeLine) => { - const edge = { - vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 }, - vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 }, - } - const intersection = edgesIntersection(edge, checkLineEdge) - if (intersection && !intersection.isIntersectionOutside) { - const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2))) - const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2))) - if (isVectorX === hipVectorX && isVectorY === hipVectorY) { - isIntersect = true - } - } - }) - return !isIntersect - }) - - overlapLineX.reduce((prev, current) => { - const prevDistance = Big(prev.x1) - .minus(Big(hipLine.x2)) - .abs() - .lt(Big(prev.x2).minus(Big(hipLine.x2)).abs()) - ? Big(prev.x1).minus(Big(hipLine.x2)).abs() - : Big(prev.x2).minus(Big(hipLine.x2)).abs() - const currentDistance = Big(current.x1) - .minus(Big(hipLine.x2)) - .abs() - .lt(Big(current.x2).minus(Big(hipLine.x2)).abs()) - ? Big(current.x1).minus(Big(hipLine.x2)).abs() - : Big(current.x2).minus(Big(hipLine.x2)).abs() - - return prevDistance.lt(currentDistance) ? prev : current - }, overlapLineX[0]) - - overlapLineY.reduce((prev, current) => { - const prevDistance = Big(prev.y1) - .minus(Big(hipLine.y2)) - .abs() - .lt(Big(prev.y2).minus(Big(hipLine.y2)).abs()) - ? Big(prev.y1).minus(Big(hipLine.y2)).abs() - : Big(prev.y2).minus(Big(hipLine.y2)).abs() - const currentDistance = Big(current.y1) - .minus(Big(hipLine.y2)) - .abs() - .lt(Big(current.y2).minus(Big(hipLine.y2)).abs()) - ? Big(current.y1).minus(Big(hipLine.y2)).abs() - : Big(current.y2).minus(Big(hipLine.y2)).abs() - return prevDistance.lt(currentDistance) ? prev : current - }, overlapLineY[0]) - - if (overlapLineX.length > 0) { - const overlapLine = overlapLineX[0] - const maxX = Math.max(overlapLine.x1, overlapLine.x2) - const point = [hipLine.x2, hipLine.y2, maxX, hipLine.y2] - const addLine = drawHipLine(point, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) - } - if (overlapLineY.length > 0) { - const overlapLine = overlapLineY[0] - const maxY = Math.max(overlapLine.y1, overlapLine.y2) - const point = [hipLine.x2, hipLine.y2, hipLine.x2, maxY] - const addLine = drawHipLine(point, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine }) - } - } - - const modifiedBaseLine = baseLines.filter((line) => (hip.x2 === line.x1 && hip.y2 === line.y1) || (hip.x2 === line.x2 && hip.y2 === line.y2)) - if (modifiedBaseLine.length === 0) return - const verticalLine = modifiedBaseLine.find( - (line) => - (hip.x2 === line.attributes.originPoint.x1 && hip.x2 === line.attributes.originPoint.x2) || - (hip.y2 === line.attributes.originPoint.y1 && hip.y2 === line.attributes.originPoint.y2), - ) - const horizonLine = modifiedBaseLine.find((line) => line !== verticalLine) - - const horizonRoof = roof.lines.find((line) => { - const originPoint = horizonLine.attributes.originPoint - if (originPoint.y1 === originPoint.y2) { - return ( - line.y1 === line.y2 && - Math.sign(originPoint.x1 - originPoint.x2) === Math.sign(line.x1 - line.x2) && - Big(originPoint.y1).minus(Big(line.y1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) - ) - } else { - return ( - line.x1 === line.x2 && - Math.sign(originPoint.y1 - originPoint.y2) === Math.sign(line.y1 - line.y2) && - Big(originPoint.x1).minus(Big(line.x1)).abs().minus(horizonLine.attributes.offset).abs().lt(1) - ) - } - }) - - if (horizonRoof) { - let horizonPoint - if (horizonRoof.y1 === horizonRoof.y2) { - const minX = Math.min(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) - const maxX = Math.max(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2) - horizonPoint = [minX, horizonRoof.y1, maxX, horizonRoof.y1] - } else { - const minY = Math.min(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) - const maxY = Math.max(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2) - horizonPoint = [horizonRoof.x1, minY, horizonRoof.x1, maxY] - } - let addLine - const alreadyHorizonLines = baseHipLines.find( - (hipLine) => - hipLine.x1 === horizonPoint[0] && hipLine.y1 === horizonPoint[1] && hipLine.x2 === horizonPoint[2] && hipLine.y2 === horizonPoint[3], - ) - if (!alreadyHorizonLines) { - addLine = drawHipLine(horizonPoint, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ - x1: horizonPoint[0], - y1: horizonPoint[1], - x2: horizonPoint[2], - y2: horizonPoint[3], - line: addLine, - }) - } else { - addLine = alreadyHorizonLines - } - - let verticalPoint - if (addLine.y1 === addLine.y2) { - verticalPoint = [hip.x2, hip.y2, hip.x2, addLine.y1] - } else { - verticalPoint = [hip.x2, hip.y2, addLine.x1, hip.y2] - } - const alreadyVerticalLine = baseHipLines.find( - (hipLine) => - hipLine.x1 === verticalPoint[0] && hipLine.y1 === verticalPoint[1] && hipLine.x2 === verticalPoint[2] && hipLine.y2 === verticalPoint[3], - ) - if (!alreadyVerticalLine) { - addLine = drawHipLine(verticalPoint, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ - x1: verticalPoint[0], - y1: verticalPoint[1], - x2: verticalPoint[2], - y2: verticalPoint[3], - line: addLine, - }) - } - } - }) - - ridgeAllPoints.forEach((current) => { - ridgeAllPoints - .filter((point) => point !== current) - .forEach((point) => { - let checkRidgeLine, checkHipLine - /** 직선인 경우 마루 확인*/ - if ( - baseRidgeCount < getMaxRidge(baseLines.length) && - ((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y)) - ) { - checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } - } - /** 대각선인 경우 hip 확인*/ - const hipX = Big(current.x).minus(Big(point.x)).abs() - const hipY = Big(current.y).minus(Big(point.y)).abs() - if (hipX.eq(hipY) && hipX.gt(0) && hipY.gt(0)) { - checkHipLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y } - } - - if (checkRidgeLine) { - const ridgePoints = [checkRidgeLine.x1, checkRidgeLine.y1, checkRidgeLine.x2, checkRidgeLine.y2] - - let baseIntersection = false - const ridgeInterSection = [] - const ridgeEdge = { - vertex1: { x: ridgePoints[0], y: ridgePoints[1] }, - vertex2: { x: ridgePoints[2], y: ridgePoints[3] }, - } - baseLines.forEach((line) => { - const intersection = edgesIntersection(ridgeEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside) { - ridgeInterSection.push(intersection) - } - }) - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection(ridgeEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside) { - ridgeInterSection.push(intersection) - } - }) - baseHipLines.forEach((line) => { - const intersection = edgesIntersection(ridgeEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside) { - ridgeInterSection.push(intersection) - } - }) - const otherRidgeInterSection = ridgeInterSection.filter( - (intersection) => - !( - (intersection.x === ridgePoints[0] && intersection.y === ridgePoints[1]) || - (intersection.x === ridgePoints[2] && intersection.y === ridgePoints[3]) - ), - ) - const alreadyRidges = baseRidgeLines.filter( - (line) => - (line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) || - (line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]), - ) - - if ( - !baseIntersection && - alreadyRidges.length === 0 && - otherRidgeInterSection.length === 0 && - baseRidgeCount < getMaxRidge(baseLines.length) - ) { - baseRidgeCount = baseRidgeCount + 1 - const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode) - baseRidgeLines.push(ridgeLine) - } - } - if (checkHipLine) { - const hipPoints = [checkHipLine.x1, checkHipLine.y1, checkHipLine.x2, checkHipLine.y2] - - let baseIntersection = false - let hipInterSection = [] - const hipEdge = { - vertex1: { x: hipPoints[0], y: hipPoints[1] }, - vertex2: { x: hipPoints[2], y: hipPoints[3] }, - } - baseLines.forEach((line) => { - const intersection = edgesIntersection(hipEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside && eavesType.includes(line.attributes.type)) { - baseIntersection = true - } - }) - baseRidgeLines.forEach((line) => { - const intersection = edgesIntersection(hipEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside) { - hipInterSection.push(intersection) - } - }) - baseHipLines.forEach((line) => { - const intersection = edgesIntersection(hipEdge, { - vertex1: { x: line.x1, y: line.y1 }, - vertex2: { x: line.x2, y: line.y2 }, - }) - if (intersection && !intersection.isIntersectionOutside) { - hipInterSection.push(intersection) - } - }) - const otherHipInterSection = hipInterSection.filter( - (intersection) => - !( - (intersection.x === hipPoints[0] && intersection.y === hipPoints[1]) || - (intersection.x === hipPoints[2] && intersection.y === hipPoints[3]) - ), - ) - const alreadyHips = baseHipLines.filter( - (line) => - (line.x1 === hipPoints[0] && line.y1 === hipPoints[1] && line.x2 === hipPoints[2] && line.y2 === hipPoints[3]) || - (line.x1 === hipPoints[2] && line.y1 === hipPoints[3] && line.x2 === hipPoints[0] && line.y2 === hipPoints[1]), - ) - - if (!baseIntersection && alreadyHips.length === 0 && otherHipInterSection.length === 0) { - const hipLine = drawHipLine(hipPoints, canvas, roof, textMode, prevDegree, currentDegree) - baseHipLines.push({ x1: hipPoints[0], y1: hipPoints[1], x2: hipPoints[2], y2: hipPoints[3], line: hipLine }) - } - } - }) - }) - - const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseGableLines, ...baseHipLines.map((line) => line.line)] - const uniqueInnerLines = [] - - innerLines.forEach((currentLine) => { - if (currentLine.length === 0) { - canvas.remove(currentLine) - } else { - const sameLines = uniqueInnerLines.filter( - (line) => - line !== currentLine && - ((line.x1 === currentLine.x1 && line.y1 === currentLine.y1 && line.x2 === currentLine.x2 && line.y2 === currentLine.y2) || - (line.x1 === currentLine.x2 && line.y1 === currentLine.y2 && line.x2 === currentLine.x1 && line.y2 === currentLine.y1)), - ) - - if (sameLines.length === 0) { - uniqueInnerLines.push(currentLine) - } else { - canvas.remove(currentLine) - } - } - }) - canvas.renderAll() - roof.innerLines = uniqueInnerLines - - roof.innerLines.forEach((line) => { - line.bringToFront() - }) - /** 확인용 라인 제거 */ - canvas - .getObjects() - .filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() - - /* - drawCenterLine(roof, canvas, textMode) - */ -} /** * 추녀 마루를 그린다. @@ -12887,7 +5505,6 @@ const drawHipLine = (points, canvas, roof, textMode, prevDegree, currentDegree) textMode: textMode, attributes: { roofId: roof.id, - // currentRoofId: currentRoof.id, planeSize: calcLinePlaneSize({ x1: points[0], y1: points[1], @@ -12915,52 +5532,6 @@ const drawHipLine = (points, canvas, roof, textMode, prevDegree, currentDegree) return hip } -/** - * 라인의 흐름 방향에서 마주치는 지붕선의 포인트를 찾는다. - * @param roof - * @param baseLine - * @param endPoint - * @returns {*} - */ -const findRoofIntersection = (roof, baseLine, endPoint) => { - let intersectPoints = [] - const { x1, y1, x2, y2 } = baseLine - - const baseEdge = { - vertex1: { x: x1, y: y1 }, - vertex2: { x: x2, y: y2 }, - } - - /** 외벽선에서 라인 겹치는 경우에 대한 확인*/ - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersection = edgesIntersection(baseEdge, lineEdge) - if ( - intersection && - !intersection.isIntersectionOutside && - Math.sign(endPoint.x - baseLine.x1) === Math.sign(endPoint.x - intersection.x) && - Math.sign(endPoint.y - baseLine.y1) === Math.sign(endPoint.y - intersection.y) - ) { - const intersectSize = endPoint.x - .minus(Big(intersection.x)) - .pow(2) - .plus(endPoint.y.minus(Big(intersection.y)).pow(2)) - .abs() - .sqrt() - .toNumber() - - intersectPoints.push({ - intersection, - size: intersectSize, - }) - } - }) - - return intersectPoints.reduce((prev, current) => { - return prev.size < current.size ? prev : current - }, intersectPoints[0]) -} - /** * 마루를 그린다. * @param points @@ -13081,35 +5652,6 @@ const getHalfAngleVector = (line1, line2) => { return normalizeVector(summedVector) } -/** - * 두 선분이 겹치는지 확인 - * @param line1 - * @param line2 - * @returns {boolean} - */ -export const segmentsOverlap = (line1, line2) => { - if (line1.y1 === line1.y2 && line2.y1 === line2.y2 && line1.y1 === line2.y1) { - if ((line1.x1 <= line2.x1 && line1.x2 >= line2.x1) || (line1.x1 <= line2.x2 && line1.x2 >= line2.x2)) { - return true - } - } - if (line1.x1 === line1.x2 && line2.x1 === line2.x2 && line1.x1 === line2.x1) { - if ((line1.y1 <= line2.y1 && line1.y2 >= line2.y1) || (line1.y1 <= line2.y2 && line1.y2 >= line2.y2)) { - return true - } - } - return false -} - -/** - * 최대 생성 마루 갯수 - * @param length - * @returns {number} - */ -const getMaxRidge = (length) => { - return (length - 4) / 2 + 1 -} - /** * 지붕 모양 을 변경한다. * @param polygon @@ -13169,7 +5711,7 @@ const reDrawPolygon = (polygon, canvas) => { * @param canvas * @param textMode */ -const drawCenterLine = (roof, canvas, textMode) => { +/*const drawCenterLine = (roof, canvas, textMode) => { //현재 지붕의 centerLine을 다 지운다. canvas .getObjects() @@ -13390,7 +5932,7 @@ const drawCenterLine = (roof, canvas, textMode) => { } } }) -} +}*/ function arePointsEqual(point1, point2) { return Math.abs(point1.x - point2.x) <= 1 && Math.abs(point1.y - point2.y) <= 1 @@ -13406,7 +5948,7 @@ export const toGeoJSON = (pointsArray) => { return coordinates } -export const inPolygon = (polygonPoints, rectPoints) => { +/*export const inPolygon = (polygonPoints, rectPoints) => { const polygonCoordinates = toGeoJSON(polygonPoints) const rectCoordinates = toGeoJSON(rectPoints) @@ -13426,7 +5968,7 @@ export const inPolygon = (polygonPoints, rectPoints) => { }) return allPointsInsidePolygon && noPolygonPointsInsideRect -} +}*/ /** * 포인트를 기준으로 선의 길이를 구한다. 선의 길이는 10을 곱하여 사용한다. @@ -13466,7 +6008,7 @@ export const createLinesFromPolygon = (points) => { } /** 포인트 정렬 가장왼쪽, 가장위 부터 */ -const getSortedPoint = (points, lines) => { +/*const getSortedPoint = (points, lines) => { const startPoint = points .filter((point) => point.x === Math.min(...points.map((point) => point.x))) .reduce((prev, curr) => { @@ -13541,8 +6083,9 @@ const getSortedPoint = (points, lines) => { } } return sortedPoints -} +}*/ +/* const reCalculateSize = (line) => { const oldPlaneSize = line.attributes.planeSize const oldActualSize = line.attributes.actualSize @@ -13571,6 +6114,7 @@ const reCalculateSize = (line) => { ) return { planeSize, actualSize } } +*/ /** * 직교 다각형(축에 평행한 변들로만 구성된 다각형)의 점들을 시계방향으로 정렬 From faa619d12459a9f72b022f9513d96ed09d359874 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 5 Jan 2026 18:59:43 +0900 Subject: [PATCH 10/55] =?UTF-8?q?=EB=A9=94=EC=9D=B8=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8=20chagePasswordPopOpen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Main.jsx | 37 ++++++++++++++++++----- src/components/main/ChangePasswordPop.jsx | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/components/Main.jsx b/src/components/Main.jsx index 9afadca7..ad7dd3d3 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -18,8 +18,9 @@ import Config from '@/config/config.export' export default function MainPage() { const [sessionState, setSessionState] = useRecoilState(sessionStore) - const [chagePasswordPopOpen, setChagePasswordPopOpen] = useState(false) - + const [changePasswordPopOpen, setChangePasswordPopOpen] = useState(false) + // 데이터 확인 완료 여부 상태 추가 + const [isSessionLoaded, setIsSessionLoaded] = useState(false) const router = useRouter() const { getMessage } = useMessage() @@ -52,6 +53,14 @@ export default function MainPage() { } } + useEffect(() => { + if (isObjectNotEmpty(sessionState)) { + if (sessionState?.pwdInitYn !== 'Y') { + setChangePasswordPopOpen(true) + } + } + }, [sessionState]) + // 라디오 변경 이벤트 const handleOnChangeRadio = (e) => { setSearchRadioType(e.target.value) @@ -77,7 +86,7 @@ export default function MainPage() { useEffect(() => { if (isObjectNotEmpty(sessionState)) { if (sessionState?.pwdInitYn !== 'Y') { - setChagePasswordPopOpen(true) + setChangePasswordPopOpen(true) } } }, [sessionState]) @@ -86,10 +95,25 @@ export default function MainPage() { const [open, setOpen] = useState(false) const [modalNoticeNo, setModalNoticeNo] = useState('') + useEffect(() => { + if (isObjectNotEmpty(sessionState)) { + if (sessionState?.pwdInitYn !== 'Y') { + setChangePasswordPopOpen(true) + } else { + // pwdInitYn이 'Y'라면 팝업을 닫음 (false) + setChangePasswordPopOpen(false) + } + } + }, [sessionState]) + + //if (!isSessionLoaded) return null + return ( <> {open && } - {(!chagePasswordPopOpen && ( + {changePasswordPopOpen ? ( + + ) : ( <>
@@ -131,11 +155,8 @@ export default function MainPage() {
- )) || ( - <> - - )} + ) } diff --git a/src/components/main/ChangePasswordPop.jsx b/src/components/main/ChangePasswordPop.jsx index 54075153..efae3bb5 100644 --- a/src/components/main/ChangePasswordPop.jsx +++ b/src/components/main/ChangePasswordPop.jsx @@ -114,7 +114,7 @@ export default function ChangePasswordPop(props) { const result = { ...sessionState, pwdInitYn: 'Y' } setSession(result) setSessionState(result) - props.setChagePasswordPopOpen(false) + props.setChangePasswordPopOpen(false) await login() }, }) From a128b0b5bb0973cdc096d3a2992d309eb9e3b794 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 6 Jan 2026 12:22:57 +0900 Subject: [PATCH 11/55] =?UTF-8?q?=EB=B0=A9=EC=9C=84=EA=B0=81=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20(-180=20~=20180)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 6 ++++-- .../floor-plan/modal/basic/step/Orientation.jsx | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index 5cb2bb42..b17f6667 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect, forwardRef } from 'react' +import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react' import { createCalculator } from '@/util/calc-utils' import '@/styles/calc.scss' @@ -294,9 +294,11 @@ export const CalculatorInput = forwardRef( } else { calculator.currentOperand = filteredValue setHasOperation(false) + // 연산자가 없는 순수 숫자일 때만 부모 컴포넌트의 onChange 호출 + onChange(filteredValue) } - onChange(filteredValue) + //onChange(filteredValue) } } diff --git a/src/components/floor-plan/modal/basic/step/Orientation.jsx b/src/components/floor-plan/modal/basic/step/Orientation.jsx index 4feeb41b..fe00c93b 100644 --- a/src/components/floor-plan/modal/basic/step/Orientation.jsx +++ b/src/components/floor-plan/modal/basic/step/Orientation.jsx @@ -452,7 +452,16 @@ export const Orientation = forwardRef((props, ref) => { className="input-origin block" value={inputCompasDeg} readOnly={!hasAnglePassivity} - onChange={(value) => setInputCompasDeg(value)} + onChange={(value) => { + // Convert to number and ensure it's within -180 to 180 range + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + const clampedValue = Math.min(180, Math.max(-180, numValue)); + setInputCompasDeg(String(clampedValue)); + } else { + setInputCompasDeg(value); + } + }} options={{ allowNegative: true, allowDecimal: false From 72e4aded51b084d23b65642beec5f6e01c9847df Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 6 Jan 2026 13:46:14 +0900 Subject: [PATCH 12/55] =?UTF-8?q?=EA=B3=84=EC=82=B0=EA=B8=B0=ED=8C=A8?= =?UTF-8?q?=EB=93=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/lineTypes/OuterLineWall.jsx | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx b/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx index ffbfa9f4..ced6dbef 100644 --- a/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx +++ b/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx @@ -11,9 +11,44 @@ export default function OuterLineWall({ props }) { const { length1, setLength1, length1Ref, arrow1, setArrow1 } = props + const resetCalculatorValue = () => { + // 1. 즉시 상태를 0으로 변경 + setLength1(0); + + if (length1Ref.current) { + // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 + length1Ref.current.focus(); + length1Ref.current.value = ''; + + // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 + setTimeout(() => { + const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || + Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); + + if (acButton) { + acButton.click(); + } else { + // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 + length1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); + } + // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) + length1Ref.current.focus(); + }, 10); + } + } + // 키보드 입력 처리 useEffect(() => { const handleKeyDown = (e) => { + + if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { + // 계산기 값이 확정된 후 초기화하기 위해 약간의 지연을 줌 + setTimeout(() => { + resetCalculatorValue(); + }, 0); + return; + } + // 계산기 키패드가 보이는지 확인 const keypadVisible = document.querySelector('.keypad-container') @@ -88,37 +123,58 @@ export default function OuterLineWall({ props }) { }} />
- +
{getMessage('modal.cover.outline.arrow')}
From 2b29a62616d5c3a2a6e994cd7e12a6b46cced3a8 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 6 Jan 2026 14:39:18 +0900 Subject: [PATCH 13/55] =?UTF-8?q?=EB=B0=A9=EC=9C=84=EA=B0=81=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=20(-180=20~=20180)=20:=20string=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useTrestle.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 0f8c04d3..9e4415bd 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -819,7 +819,12 @@ export const useTrestle = () => { // 발전 시뮬레이션 용 각도 재계산 const getAzimuth = (parent) => { - const { moduleCompass, surfaceCompass, direction } = parent + if (typeof parent === 'string') { + console.warn('getAzimuth: parent is string, expected object', parent); + return 0; // 또는 적절한 기본값 + } + + const { moduleCompass, surfaceCompass, direction } = parent || {}; if (surfaceCompass) { return -surfaceCompass From c8d0dd30e557fd5040e1c9d64c0a69442cb0f9d3 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 6 Jan 2026 16:50:48 +0900 Subject: [PATCH 14/55] =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useFont.js | 109 ++++++++++++++++++++---------------- src/hooks/useLine.js | 6 +- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/hooks/common/useFont.js b/src/hooks/common/useFont.js index 9498b036..3db63427 100644 --- a/src/hooks/common/useFont.js +++ b/src/hooks/common/useFont.js @@ -12,6 +12,7 @@ export function useFont() { const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText')) useEffect(() => { + setTimeout(() => { if (canvas && commonText.fontWeight.value) { const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'commonText') textObjs.forEach((obj) => { @@ -24,71 +25,81 @@ export function useFont() { }) }) canvas.renderAll() - } + }}, 200) }, [commonText]) useEffect(() => { - if (canvas && dimensionLineText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'dimensionLineText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: dimensionLineText.fontFamily.value, - fontWeight: dimensionLineText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: dimensionLineText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: dimensionLineText.fontSize.value, - fill: dimensionLineText.fontColor.value, + setTimeout(() => { + if (canvas && dimensionLineText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'dimensionLineText') + textObjs.forEach((obj) => { + obj.set({ + fontFamily: dimensionLineText.fontFamily.value, + fontWeight: dimensionLineText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: dimensionLineText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: dimensionLineText.fontSize.value, + fill: dimensionLineText.fontColor.value, + }) }) - }) - canvas.renderAll() - } + canvas.renderAll() + } + }, 200) + }, [dimensionLineText]) useEffect(() => { - if (canvas && flowText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'flowText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: flowText.fontFamily.value, - fontWeight: flowText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: flowText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: flowText.fontSize.value, - fill: flowText.fontColor.value, + setTimeout(() => { + if (canvas && flowText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'flowText') + textObjs.forEach((obj) => { + obj.set({ + fontFamily: flowText.fontFamily.value, + fontWeight: flowText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: flowText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: flowText.fontSize.value, + fill: flowText.fontColor.value, + }) }) - }) - canvas.renderAll() - } + canvas.renderAll() + } + }, 200) + }, [flowText]) useEffect(() => { - if (canvas && lengthText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: lengthText.fontFamily.value, - fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: lengthText.fontSize.value, - fill: lengthText.fontColor.value, + setTimeout(() => { + if (canvas && lengthText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') + textObjs.forEach((obj) => { + obj.set({ + fontFamily: lengthText.fontFamily.value, + fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: lengthText.fontSize.value, + fill: lengthText.fontColor.value, + }) }) - }) - canvas.renderAll() - } + canvas.renderAll() + } + }, 200) }, [lengthText]) useEffect(() => { - if (canvas && circuitNumberText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'circuitNumber') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: circuitNumberText.fontFamily.value, - fontWeight: circuitNumberText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: circuitNumberText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: circuitNumberText.fontSize.value, - fill: circuitNumberText.fontColor.value, + setTimeout(() => { + if (canvas && circuitNumberText.fontWeight.value) { + const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'circuitNumber') + textObjs.forEach((obj) => { + obj.set({ + fontFamily: circuitNumberText.fontFamily.value, + fontWeight: circuitNumberText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: circuitNumberText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: circuitNumberText.fontSize.value, + fill: circuitNumberText.fontColor.value, + }) }) - }) - canvas.renderAll() - } + canvas.renderAll() + } + }, 200) }, [circuitNumberText]) return {} diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index 58f87e7d..66dff769 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -13,6 +13,7 @@ import { basicSettingState } from '@/store/settingAtom' import { calcLineActualSize } from '@/util/qpolygon-utils' import { getDegreeByChon } from '@/util/canvas-util' import { useText } from '@/hooks/useText' +import { fontSelector } from '@/store/fontAtom' export const useLine = () => { const canvas = useRecoilValue(canvasState) @@ -23,14 +24,15 @@ export const useLine = () => { const angleUnit = useRecoilValue(showAngleUnitSelector) const roofSizeSet = useRecoilValue(basicSettingState).roofSizeSet const globalPitch = useRecoilValue(globalPitchState) + const lengthText = useRecoilValue(fontSelector('lengthText')) const { changeCorridorDimensionText } = useText() const addLine = (points = [], options) => { const line = new QLine(points, { ...options, - fontSize: fontSize, - fontFamily: fontFamily, + fontSize: lengthText.fontSize.value, + fontFamily: lengthText.fontFamily.value, }) if (line.length < 1) { From 47078a22058e48a9783475a403656c7a1814d860 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 6 Jan 2026 17:47:30 +0900 Subject: [PATCH 15/55] =?UTF-8?q?maxHeight=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/select/QSelectBox.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/select/QSelectBox.jsx b/src/components/common/select/QSelectBox.jsx index dbb3c285..0e77ccca 100644 --- a/src/components/common/select/QSelectBox.jsx +++ b/src/components/common/select/QSelectBox.jsx @@ -96,7 +96,7 @@ export default function QSelectBox({ title={tagTitle} >

{selected}

-
    +
      {options?.length > 0 && options?.map((option, index) => (
    • handleClickSelectOption(option)}> From bff666914c8d74f8ab90e7ada80eb37c06bde775 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 Jan 2026 10:07:31 +0900 Subject: [PATCH 16/55] =?UTF-8?q?font=20=EC=84=A4=EC=A0=95=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useFont.js | 170 +++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/src/hooks/common/useFont.js b/src/hooks/common/useFont.js index 3db63427..45ca8200 100644 --- a/src/hooks/common/useFont.js +++ b/src/hooks/common/useFont.js @@ -1,7 +1,19 @@ import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' import { fontSelector } from '@/store/fontAtom' -import { useEffect } from 'react' +import { useCallback, useEffect } from 'react' + +/** 폰트 타입별 캔버스 오브젝트 이름 매핑 */ +const FONT_TYPE_TO_OBJ_NAME = { + commonText: 'commonText', + dimensionLineText: 'dimensionLineText', + flowText: 'flowText', + lengthText: 'lengthText', + circuitNumberText: 'circuitNumber', +} + +/** 캔버스 오브젝트 이름 → 폰트 타입 역매핑 */ +const OBJ_NAME_TO_FONT_TYPE = Object.fromEntries(Object.entries(FONT_TYPE_TO_OBJ_NAME).map(([k, v]) => [v, k])) export function useFont() { const canvas = useRecoilValue(canvasState) @@ -11,96 +23,98 @@ export function useFont() { const lengthText = useRecoilValue(fontSelector('lengthText')) const circuitNumberText = useRecoilValue(fontSelector('circuitNumberText')) + /** 폰트 타입별 설정 매핑 */ + const fontSettings = { + commonText, + dimensionLineText, + flowText, + lengthText, + circuitNumberText, + } + + /** + * 타입별 폰트 설정을 캔버스 오브젝트에 적용하는 공통 함수 + * @param {string} type - 폰트 타입 (commonText, dimensionLineText, flowText, lengthText, circuitNumberText) + * @param {number} delay - 적용 지연 시간 (ms), 기본값 200 + */ + const changeFontByType = useCallback( + (type, delay = 200) => { + const fontSetting = fontSettings[type] + const objName = FONT_TYPE_TO_OBJ_NAME[type] + + if (!fontSetting || !objName) { + console.warn(`Invalid font type: ${type}`) + return + } + + setTimeout(() => { + if (canvas && fontSetting.fontWeight?.value) { + const textObjs = canvas.getObjects().filter((obj) => obj.name === objName) + textObjs.forEach((obj) => { + obj.set({ + fontFamily: fontSetting.fontFamily.value, + fontWeight: fontSetting.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', + fontStyle: fontSetting.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', + fontSize: fontSetting.fontSize.value, + fill: fontSetting.fontColor.value, + }) + }) + canvas.renderAll() + } + }, delay) + }, + [canvas, fontSettings], + ) + + const changeAllFonts = () => { + changeFontByType('commonText') + changeFontByType('dimensionLineText') + changeFontByType('flowText') + changeFontByType('lengthText') + changeFontByType('circuitNumberText') + } + + /** 각 폰트 타입별 useEffect */ useEffect(() => { - setTimeout(() => { - if (canvas && commonText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'commonText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: commonText.fontFamily.value, - fontWeight: commonText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: commonText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: commonText.fontSize.value, - fill: commonText.fontColor.value, - }) - }) - canvas.renderAll() - }}, 200) + changeFontByType('commonText') }, [commonText]) useEffect(() => { - setTimeout(() => { - if (canvas && dimensionLineText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'dimensionLineText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: dimensionLineText.fontFamily.value, - fontWeight: dimensionLineText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: dimensionLineText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: dimensionLineText.fontSize.value, - fill: dimensionLineText.fontColor.value, - }) - }) - canvas.renderAll() - } - }, 200) - + changeFontByType('dimensionLineText') }, [dimensionLineText]) useEffect(() => { - setTimeout(() => { - if (canvas && flowText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'flowText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: flowText.fontFamily.value, - fontWeight: flowText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: flowText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: flowText.fontSize.value, - fill: flowText.fontColor.value, - }) - }) - canvas.renderAll() - } - }, 200) - + changeFontByType('flowText') }, [flowText]) useEffect(() => { - setTimeout(() => { - if (canvas && lengthText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'lengthText') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: lengthText.fontFamily.value, - fontWeight: lengthText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: lengthText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: lengthText.fontSize.value, - fill: lengthText.fontColor.value, - }) - }) - canvas.renderAll() - } - }, 200) + changeFontByType('lengthText') }, [lengthText]) useEffect(() => { - setTimeout(() => { - if (canvas && circuitNumberText.fontWeight.value) { - const textObjs = canvas?.getObjects().filter((obj) => obj.name === 'circuitNumber') - textObjs.forEach((obj) => { - obj.set({ - fontFamily: circuitNumberText.fontFamily.value, - fontWeight: circuitNumberText.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', - fontStyle: circuitNumberText.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', - fontSize: circuitNumberText.fontSize.value, - fill: circuitNumberText.fontColor.value, - }) - }) - canvas.renderAll() - } - }, 200) + changeFontByType('circuitNumberText') }, [circuitNumberText]) - return {} + /** 캔버스에 텍스트 오브젝트 추가 시 자동으로 폰트 적용 */ + useEffect(() => { + if (!canvas) return + + const handleObjectAdded = (e) => { + const obj = e.target + if (!obj?.name) return + + const fontType = OBJ_NAME_TO_FONT_TYPE[obj.name] + if (fontType) { + changeFontByType(fontType, 0) + } + } + + canvas.on('object:added', handleObjectAdded) + + return () => { + canvas.off('object:added', handleObjectAdded) + } + }, [canvas, changeFontByType]) + + return { changeFontByType, changeAllFonts } } From 6872c6bb16fe1d78002f1754b55992ad41f8c5bb Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 Jan 2026 10:12:25 +0900 Subject: [PATCH 17/55] =?UTF-8?q?=EB=B0=B0=EC=B9=98=EB=A9=B4=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0,=20=EC=99=B8=EB=B2=BD=EC=84=A0=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=20=EA=B0=81=EB=8F=84=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useOuterLineWall.js | 2 +- src/hooks/surface/usePlacementShapeDrawing.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index dd304865..850a761a 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -812,7 +812,7 @@ export function useOuterLineWall(id, propertiesId) { if (points.length === 0) { return } - enterCheck(e) + // enterCheck(e) const key = e.key switch (key) { case 'Enter': { diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js index 629aae6e..3dfea465 100644 --- a/src/hooks/surface/usePlacementShapeDrawing.js +++ b/src/hooks/surface/usePlacementShapeDrawing.js @@ -815,7 +815,7 @@ export function usePlacementShapeDrawing(id) { if (points.length === 0) { return } - enterCheck(e) + // enterCheck(e) const key = e.key switch (key) { case 'Enter': { From 5ac023d72d5ae3f08dfe91724f8ee22b05336cc6 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 Jan 2026 10:35:24 +0900 Subject: [PATCH 18/55] =?UTF-8?q?[HANASYS=20DESIGN]=20=ED=9A=8C=EC=A0=84?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EC=9D=B4=EC=9A=A9=ED=96=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=EC=9D=98=20=EB=B6=88=EB=9F=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QPolygon.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index b6c299d0..dbe6a9e3 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -181,8 +181,27 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { return fabric.util.transformPoint(p, matrix) }) this.points = transformedPoints - const { left, top } = this.calcOriginCoords() - this.set('pathOffset', { x: left, y: top }) + + // 바운딩 박스 재계산 (width, height 업데이트 - fill 영역 수정) + const calcDim = this._calcDimensions({}) + this.width = calcDim.width + this.height = calcDim.height + + const newPathOffset = { + x: calcDim.left + this.width / 2, + y: calcDim.top + this.height / 2, + } + this.set('pathOffset', newPathOffset) + + // 변환을 points에 적용했으므로 left, top, angle, scale 모두 리셋 (이중 변환 방지) + this.set({ + left: newPathOffset.x, + top: newPathOffset.y, + angle: 0, + scaleX: 1, + scaleY: 1, + }) + this.setCoords() this.initLines() }) From c499653e798f0d02fda912c2e8273306cf4c282d Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 7 Jan 2026 16:59:30 +0900 Subject: [PATCH 19/55] =?UTF-8?q?=EB=B6=81=EB=A9=B4=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EC=84=A4=EC=B9=98=20=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?,=20=EB=B6=81=EB=A9=B4=20=EB=AA=A8=EB=93=88=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=EC=8B=9C=20=EB=8F=99=EB=A9=B4=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + .../step/type/PassivityCircuitAllocation.jsx | 2 ++ src/hooks/useCirCuitTrestle.js | 25 ++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index fd961063..6ecc08fc 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -224,6 +224,7 @@ export const SAVE_KEY = [ 'viewportTransform', 'outerLineFix', 'adjustRoofLines', + 'northModuleYn', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 720845f5..a25a1a37 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -234,6 +234,7 @@ export default function PassivityCircuitAllocation(props) { setSelectedPcs(tempSelectedPcs) canvas.add(moduleCircuitText) }) + const roofSurfaceList = canvas .getObjects() @@ -244,6 +245,7 @@ export default function PassivityCircuitAllocation(props) { roofSurface: surface.direction, roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch, roofSurfaceNorthYn: surface.direction === 'north' ? 'Y' : 'N', + roofSurfaceNorthModuleYn: surface.northModuleYn, moduleList: surface.modules.map((module) => { return { itemId: module.moduleInfo.itemId, diff --git a/src/hooks/useCirCuitTrestle.js b/src/hooks/useCirCuitTrestle.js index 0c332ec8..5bfab3ce 100644 --- a/src/hooks/useCirCuitTrestle.js +++ b/src/hooks/useCirCuitTrestle.js @@ -99,6 +99,12 @@ export function useCircuitTrestle(executeEffect = false) { // 지붕면 목록 const getRoofSurfaceList = () => { const roofSurfaceList = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) + + roofSurfaceList.forEach((roofSurface) => { + const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.surfaceId === roofSurface.id) + roofSurface.northModuleYn = modules.every((module) => module.moduleInfo.northModuleYn === 'Y') ? 'Y' : 'N' + }) + roofSurfaceList.sort((a, b) => a.left - b.left || b.top - a.top) const result = roofSurfaceList @@ -119,6 +125,7 @@ export function useCircuitTrestle(executeEffect = false) { } }), roofSurfaceNorthYn: obj.direction === 'north' ? 'Y' : 'N', + roofSurfaceNorthModuleYn: obj.northModuleYn, } }) .filter((surface) => surface.moduleList.length > 0) @@ -139,11 +146,14 @@ export function useCircuitTrestle(executeEffect = false) { let remaining = [...arr] while (remaining.length > 0) { - const { roofSurface, roofSurfaceIncl } = remaining[0] - const key = `${roofSurface}|${roofSurfaceIncl}` + const { roofSurface, roofSurfaceIncl, roofSurfaceNorthModuleYn } = remaining[0] + const key = `${roofSurface}|${roofSurfaceIncl}|${roofSurfaceNorthModuleYn}` // 해당 그룹 추출 - const group = remaining.filter((item) => item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl) + const group = remaining.filter( + (item) => + item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl && item.roofSurfaceNorthModuleYn === roofSurfaceNorthModuleYn, + ) // 이미 처리했는지 체크 후 저장 if (!seen.has(key)) { @@ -152,7 +162,14 @@ export function useCircuitTrestle(executeEffect = false) { } // remaining에서 제거 - remaining = remaining.filter((item) => !(item.roofSurface === roofSurface && item.roofSurfaceIncl === roofSurfaceIncl)) + remaining = remaining.filter( + (item) => + !( + item.roofSurface === roofSurface && + item.roofSurfaceIncl === roofSurfaceIncl && + item.roofSurfaceNorthModuleYn === roofSurfaceNorthModuleYn + ), + ) } return result From 47de1ef61d7d2b7b75a41ed5196c83dbe6441e54 Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 7 Jan 2026 17:49:37 +0900 Subject: [PATCH 20/55] =?UTF-8?q?azimuth:=20string=20->=20int=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useTrestle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 9e4415bd..658c8c22 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -830,7 +830,7 @@ export const useTrestle = () => { return -surfaceCompass } - let resultAzimuth = moduleCompass + let resultAzimuth = parseInt(moduleCompass, 10) switch (direction) { case 'south': { From a5dc5caaf34660eb2c143d69d556751787c1a799 Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 7 Jan 2026 17:51:11 +0900 Subject: [PATCH 21/55] =?UTF-8?q?=EA=B0=81=EB=8F=84=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/lineTypes/Angle.jsx | 128 +++++++++++++++++- 1 file changed, 122 insertions(+), 6 deletions(-) diff --git a/src/components/floor-plan/modal/lineTypes/Angle.jsx b/src/components/floor-plan/modal/lineTypes/Angle.jsx index 0faad2a4..1b05993f 100644 --- a/src/components/floor-plan/modal/lineTypes/Angle.jsx +++ b/src/components/floor-plan/modal/lineTypes/Angle.jsx @@ -2,11 +2,110 @@ import Image from 'next/image' import { useMessage } from '@/hooks/useMessage' import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils' import { CalculatorInput } from '@/components/common/input/CalcInput' +import { useEffect } from 'react' export default function Angle({ props }) { const { getMessage } = useMessage() const { angle1, setAngle1, angle1Ref, length1, setLength1, length1Ref } = props + const resetCalculatorValue = () => { + // 1. 즉시 상태를 0으로 변경 + setLength1(0); + + if (length1Ref.current) { + // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 + length1Ref.current.focus(); + length1Ref.current.value = ''; + + // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 + setTimeout(() => { + const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || + Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); + + if (acButton) { + acButton.click(); + } else { + // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 + length1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); + } + // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) + length1Ref.current.focus(); + }, 10); + } + } + + const resetCalculatorValue2 = () => { + // 1. 즉시 상태를 0으로 변경 + setAngle1(0); + + if (angle1Ref.current) { + // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 + angle1Ref.current.focus(); + angle1Ref.current.value = ''; + + // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 + setTimeout(() => { + const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || + Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); + + if (acButton) { + acButton.click(); + } else { + // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 + angle1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); + } + // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) + angle1Ref.current.focus(); + }, 10); + } + } + + // 키보드 입력 처리 + useEffect(() => { + const handleKeyDown = (e) => { + + if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { + // 계산기 값이 확정된 후 초기화하기 위해 약간의 지연을 줌 + setTimeout(() => { + resetCalculatorValue(); + }, 0); + return; + } + + // 계산기 키패드가 보이는지 확인 + const keypadVisible = document.querySelector('.keypad-container') + + // 계산기 키패드가 보이면 계산기가 처리하도록 함 + if (keypadVisible) { + return + } + + // 이미 계산기 input에 포커스가 있으면 계산기가 처리하도록 함 + if (document.activeElement && document.activeElement.classList.contains('calculator-input')) { + return + } + + // 엔터키는 계산기가 숨겨진 상태에서만 페이지가 처리 + if (e.key === 'Enter') { + // 엔터키를 페이지 레벨로 전달 (useOuterLineWall.js에서 처리) + return + } + + // 숫자 키가 입력되면 계산기 input에 포커스만 주기 + if (/^[0-9+\-*\/=.]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete') { + const calcInput = document.querySelector('.calculator-input') + if (calcInput) { + // 포커스만 주고 이벤트는 preventDefault 하지 않음 + calcInput.focus() + calcInput.click() + } + } + } + // capture: true로 설정하여 다른 핸들러보다 먼저 실행 + document.addEventListener('keydown', handleKeyDown, { capture: true }) + return () => document.removeEventListener('keydown', handleKeyDown, { capture: true }) + }, []) + return ( <>
      @@ -31,19 +130,33 @@ export default function Angle({ props }) { className="input-origin block" value={angle1} ref={angle1Ref} - onChange={(value) => setAngle1(value)} + onChange={(value) => {setAngle1(value) + + // Convert to number and ensure it's within -180 to 180 range + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + const clampedValue = Math.min(180, Math.max(-180, numValue)); + setAngle1(String(clampedValue)); + } else { + setAngle1(value); + } + } + } placeholder="45" onFocus={() => (angle1Ref.current.value = '')} options={{ - allowNegative: false, - allowDecimal: true + allowNegative: true, + allowDecimal: false }} />
@@ -77,8 +190,11 @@ export default function Angle({ props }) { From 4651ebc36584903b03501a0417edb9d7f8eb8378 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 8 Jan 2026 10:15:44 +0900 Subject: [PATCH 22/55] =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 17 ++- .../floor-plan/modal/lineTypes/Angle.jsx | 133 ++---------------- .../modal/lineTypes/OuterLineWall.jsx | 66 +-------- 3 files changed, 35 insertions(+), 181 deletions(-) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index b17f6667..37bd00ef 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -23,8 +23,23 @@ export const CalculatorInput = forwardRef( }, [ref]) // Sync displayValue with value prop + // useEffect(() => { + // setDisplayValue(value || '0') + // }, [value]) + useEffect(() => { - setDisplayValue(value || '0') + const newValue = value || '0' + setDisplayValue(newValue) + + // 외부에서 value가 변경될 때 계산기 내부 상태도 동기화 + const calculator = calculatorRef.current + if (calculator) { + // 연산 중이 아닐 때 외부에서 값이 들어오면 현재 피연산자로 설정 + calculator.currentOperand = newValue.toString() + calculator.previousOperand = '' + calculator.operation = undefined + setHasOperation(false) + } }, [value]) // 클릭 외부 감지 diff --git a/src/components/floor-plan/modal/lineTypes/Angle.jsx b/src/components/floor-plan/modal/lineTypes/Angle.jsx index 1b05993f..97bbe416 100644 --- a/src/components/floor-plan/modal/lineTypes/Angle.jsx +++ b/src/components/floor-plan/modal/lineTypes/Angle.jsx @@ -8,104 +8,6 @@ export default function Angle({ props }) { const { getMessage } = useMessage() const { angle1, setAngle1, angle1Ref, length1, setLength1, length1Ref } = props - const resetCalculatorValue = () => { - // 1. 즉시 상태를 0으로 변경 - setLength1(0); - - if (length1Ref.current) { - // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 - length1Ref.current.focus(); - length1Ref.current.value = ''; - - // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 - setTimeout(() => { - const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || - Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); - - if (acButton) { - acButton.click(); - } else { - // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 - length1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); - } - // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) - length1Ref.current.focus(); - }, 10); - } - } - - const resetCalculatorValue2 = () => { - // 1. 즉시 상태를 0으로 변경 - setAngle1(0); - - if (angle1Ref.current) { - // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 - angle1Ref.current.focus(); - angle1Ref.current.value = ''; - - // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 - setTimeout(() => { - const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || - Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); - - if (acButton) { - acButton.click(); - } else { - // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 - angle1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); - } - // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) - angle1Ref.current.focus(); - }, 10); - } - } - - // 키보드 입력 처리 - useEffect(() => { - const handleKeyDown = (e) => { - - if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { - // 계산기 값이 확정된 후 초기화하기 위해 약간의 지연을 줌 - setTimeout(() => { - resetCalculatorValue(); - }, 0); - return; - } - - // 계산기 키패드가 보이는지 확인 - const keypadVisible = document.querySelector('.keypad-container') - - // 계산기 키패드가 보이면 계산기가 처리하도록 함 - if (keypadVisible) { - return - } - - // 이미 계산기 input에 포커스가 있으면 계산기가 처리하도록 함 - if (document.activeElement && document.activeElement.classList.contains('calculator-input')) { - return - } - - // 엔터키는 계산기가 숨겨진 상태에서만 페이지가 처리 - if (e.key === 'Enter') { - // 엔터키를 페이지 레벨로 전달 (useOuterLineWall.js에서 처리) - return - } - - // 숫자 키가 입력되면 계산기 input에 포커스만 주기 - if (/^[0-9+\-*\/=.]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete') { - const calcInput = document.querySelector('.calculator-input') - if (calcInput) { - // 포커스만 주고 이벤트는 preventDefault 하지 않음 - calcInput.focus() - calcInput.click() - } - } - } - // capture: true로 설정하여 다른 핸들러보다 먼저 실행 - document.addEventListener('keydown', handleKeyDown, { capture: true }) - return () => document.removeEventListener('keydown', handleKeyDown, { capture: true }) - }, []) - return ( <>
@@ -130,33 +32,29 @@ export default function Angle({ props }) { className="input-origin block" value={angle1} ref={angle1Ref} - onChange={(value) => {setAngle1(value) - - // Convert to number and ensure it's within -180 to 180 range - const numValue = parseInt(value, 10); - if (!isNaN(numValue)) { - const clampedValue = Math.min(180, Math.max(-180, numValue)); - setAngle1(String(clampedValue)); - } else { - setAngle1(value); - } + onChange={(value) => { + // Calculate the final value first + let finalValue = value; + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + const clampedValue = Math.min(180, Math.max(-180, numValue)); + finalValue = String(clampedValue); } - } + // Set state once with the final value + setAngle1(finalValue); + }} placeholder="45" onFocus={() => (angle1Ref.current.value = '')} options={{ allowNegative: true, - allowDecimal: false + allowDecimal: true }} />
@@ -190,11 +88,8 @@ export default function Angle({ props }) { diff --git a/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx b/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx index ced6dbef..ffbfa9f4 100644 --- a/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx +++ b/src/components/floor-plan/modal/lineTypes/OuterLineWall.jsx @@ -11,44 +11,9 @@ export default function OuterLineWall({ props }) { const { length1, setLength1, length1Ref, arrow1, setArrow1 } = props - const resetCalculatorValue = () => { - // 1. 즉시 상태를 0으로 변경 - setLength1(0); - - if (length1Ref.current) { - // 2. input에 포커스를 주어 계산기 UI가 떠있는 상태를 유지하게 함 - length1Ref.current.focus(); - length1Ref.current.value = ''; - - // 3. 약간의 시간차를 두어 계산기 UI 내부의 버튼을 찾음 - setTimeout(() => { - const acButton = document.querySelector('.keypad-btn.ac, .btn-ac') || - Array.from(document.querySelectorAll('button')).find(el => el.textContent === 'AC'); - - if (acButton) { - acButton.click(); - } else { - // 버튼을 못 찾으면 강제로 input 이벤트와 Enter/Escape 등을 시뮬레이션 - length1Ref.current.dispatchEvent(new Event('input', { bubbles: true })); - } - // 다시 포커스를 주어 키패드가 유지되도록 함 (필요시) - length1Ref.current.focus(); - }, 10); - } - } - // 키보드 입력 처리 useEffect(() => { const handleKeyDown = (e) => { - - if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) { - // 계산기 값이 확정된 후 초기화하기 위해 약간의 지연을 줌 - setTimeout(() => { - resetCalculatorValue(); - }, 0); - return; - } - // 계산기 키패드가 보이는지 확인 const keypadVisible = document.querySelector('.keypad-container') @@ -123,58 +88,37 @@ export default function OuterLineWall({ props }) { }} /> - +
{getMessage('modal.cover.outline.arrow')}
From 3a3afe9b3bf978b7e3baf478a0ba6820ee84f78c Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 Jan 2026 10:26:24 +0900 Subject: [PATCH 23/55] =?UTF-8?q?=EC=8B=A4=EB=82=B4=EC=A7=91=EC=A4=91?= =?UTF-8?q?=ED=98=95=20=EB=B6=81=EB=A9=B4=EB=AA=A8=EB=93=88=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../step/type/PassivityCircuitAllocation.jsx | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index a25a1a37..b3c38c8e 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -123,10 +123,14 @@ export default function PassivityCircuitAllocation(props) { } // targetModule중 북면 설치 여부가 Y인 것과 N인 것이 혼합이면 안됨. - const targetModuleGroup = [...new Set(canvas - .getObjects() - .filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) - .map((obj) => obj.moduleInfo.northModuleYn))] + const targetModuleGroup = [ + ...new Set( + canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + .map((obj) => obj.moduleInfo.northModuleYn), + ), + ] if (targetModuleGroup.length > 1) { swalFire({ @@ -142,16 +146,12 @@ export default function PassivityCircuitAllocation(props) { const originHaveThisPcsModules = canvas .getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.pcs && obj.pcs.id === selectedPcs.id) - // 이미 해당 pcs로 설치된 모듈의 surface의 방향을 구한다. - const originSurfaceList = canvas - .getObjects() - .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && originHaveThisPcsModules.map((obj) => obj.surfaceId).includes(obj.id)) + // 1. 북면모듈, 북면외모듈 혼합 여부 체크 + const targetModuleInfos = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + debugger + const newTargetModuleGroup = [...new Set(targetModuleInfos.concat(originHaveThisPcsModules).map((obj) => obj.moduleInfo.northModuleYn))] - originSurfaceList.concat(originSurfaceList).forEach((surface) => { - surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface - }) - - if (Object.keys(surfaceType).length > 1) { + if (newTargetModuleGroup.length > 1) { swalFire({ text: getMessage('module.circuit.fix.not.same.roof.error'), type: 'alert', @@ -234,7 +234,6 @@ export default function PassivityCircuitAllocation(props) { setSelectedPcs(tempSelectedPcs) canvas.add(moduleCircuitText) }) - const roofSurfaceList = canvas .getObjects() From ddd3eaf82a65199ebb1de83a6616b73014270e75 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 Jan 2026 12:54:00 +0900 Subject: [PATCH 24/55] =?UTF-8?q?=EC=B9=98=EC=88=98=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=20=EC=84=A0=201=EC=9D=98=20=EC=9E=90=EB=A6=AC=200=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=82=98=EC=98=A4=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/canvas-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index b2ee57db..5c51560b 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -80,7 +80,7 @@ export const getCenterPoint = (point1, point2) => { * @returns */ export const getDistance = (x1, y1, x2, y2) => { - return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)).toFixed(0) + return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)).toFixed(1) } // polygon의 각 변에 해당 점과 점 사이의 거리를 나타내는 IText를 추가하는 함수 From a258f610931b12486ae758364cfce98fa39a293f Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 8 Jan 2026 14:46:21 +0900 Subject: [PATCH 25/55] =?UTF-8?q?=ED=83=AD=ED=82=A4,=20=EB=B0=A9=ED=96=A5?= =?UTF-8?q?=ED=82=A4=20=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index 37bd00ef..df41043f 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -340,12 +340,18 @@ export const CalculatorInput = forwardRef( // Tab 키는 계산기 숨기고 기본 동작 허용 if (e.key === 'Tab') { + if (hasOperation) { + handleCompute(true) // 계산 수행 + } setShowKeypad(false) return } // 모든 방향키는 기본 동작 허용 if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') { + if (hasOperation) { + handleCompute(true) // 계산 수행 + } setShowKeypad(true) return } @@ -360,6 +366,12 @@ export const CalculatorInput = forwardRef( return } + // --- 여기서부터는 브라우저의 기본 입력을 막고 계산기 로직만 적용함 --- + if (e.key !== 'Process') { // 한글 입력 등 특수 상황 방지 (필요시) + // e.preventDefault() 호출 위치를 확인하세요. + } + + e.preventDefault() const calculator = calculatorRef.current const { allowDecimal } = options From 49253b766846709b0ba3ce4427b777227a2ee8df Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 Jan 2026 15:04:00 +0900 Subject: [PATCH 26/55] =?UTF-8?q?1390=20=E3=80=90HANASYS=20DESIGN=E3=80=91?= =?UTF-8?q?=EB=B3=B5=EB=8F=84=EC=97=90=EC=84=9C=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=9C=20=EA=B2=BD=EC=9A=B0=20=EC=B9=98=EC=88=98=20=EC=B7=A8?= =?UTF-8?q?=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QLine.js | 14 ++++++---- src/hooks/useLine.js | 6 ++-- src/hooks/usePolygon.js | 51 ++++++++++++++++++++++++++++++++-- src/util/qpolygon-utils.js | 11 ++++++++ 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 02448cd7..d1e1ada3 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -72,13 +72,17 @@ export const QLine = fabric.util.createClass(fabric.Line, { setLength() { // Ensure all required properties are valid numbers - const { x1, y1, x2, y2 } = this; + const { x1, y1, x2, y2 } = this if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) { - logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 }); - this.length = 0; - return; + logger.error('Invalid coordinates in QLine:', { x1, y1, x2, y2 }) + this.length = 0 + return } - this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10; + this.length = calcLinePlaneSize({ x1, y1, x2, y2 }) / 10 + }, + + setLengthByValue(length) { + this.length = length / 10 }, addLengthText() { diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index 66dff769..c9de0194 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -10,7 +10,7 @@ import { } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' import { basicSettingState } from '@/store/settingAtom' -import { calcLineActualSize } from '@/util/qpolygon-utils' +import { calcLineActualSizeByLineLength } from '@/util/qpolygon-utils' import { getDegreeByChon } from '@/util/canvas-util' import { useText } from '@/hooks/useText' import { fontSelector } from '@/store/fontAtom' @@ -181,7 +181,7 @@ export const useLine = () => { if (isVertical) { line.attributes = { ...line.attributes, - actualSize: calcLineActualSize(line, getDegreeByChon(pitch)), + actualSize: calcLineActualSizeByLineLength(lineLength, getDegreeByChon(pitch)), } } else if (isDiagonal) { const yLength = Math.abs(y2 - y1) * 10 @@ -195,7 +195,7 @@ export const useLine = () => { if (isHorizontal) { line.attributes = { ...line.attributes, - actualSize: calcLineActualSize(line, getDegreeByChon(pitch)), + actualSize: calcLineActualSizeByLineLength(lineLength, getDegreeByChon(pitch)), } } else if (isDiagonal) { const xLength = Math.abs(x2 - x1) * 10 diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 011b8852..8822fd5f 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,19 @@ -import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, globalPitchState, pitchTextSelector } from '@/store/canvasAtom' +import { + ANGLE_TYPE, + canvasState, + currentAngleTypeSelector, + globalPitchState, + pitchTextSelector, +} from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' +import { + calculateIntersection, + findAndRemoveClosestPoint, + getDegreeByChon, + getDegreeInOrientation, + isPointOnLine, +} from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { basicSettingState, flowDisplaySelector } from '@/store/settingAtom' @@ -1958,6 +1970,38 @@ export const usePolygon = () => { canvas.renderAll() } + /** + * 폴리곤의 라인 길이가 10 이하로 차이나는 경우 작은 값으로 통일 + * @param polygon + */ + const unifyLineLengths = (polygon) => { + if (!polygon.lines || polygon.lines.length === 0) { + return + } + + const lines = polygon.lines + + for (let i = 0; i < lines.length; i++) { + for (let j = i + 1; j < lines.length; j++) { + const length1 = lines[i].getLength() + const length2 = lines[j].getLength() + const diff = Math.abs(length1 - length2) + + if (diff > 0 && diff <= 10) { + const minLength = Math.min(length1, length2) + if (length1 > length2) { + lines[i].setLengthByValue(minLength) + } else { + lines[j].setLengthByValue(minLength) + } + } + } + } + + addLengthText(polygon) + canvas.renderAll() + } + /** * 폴리곤의 라인 속성을 복도치수, 실제치수에 따라 actualSize 설정 * @param polygon @@ -1966,7 +2010,7 @@ export const usePolygon = () => { if (!polygon.lines || polygon.lines.length === 0 || !polygon.roofMaterial) { return } - + unifyLineLengths(polygon) polygon.lines.forEach((line) => { setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch) }) @@ -1983,5 +2027,6 @@ export const usePolygon = () => { splitPolygonWithLines, splitPolygonWithSeparate, setPolygonLinesActualSize, + unifyLineLengths, } } diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index abcd66ce..e1ff8575 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -5992,6 +5992,17 @@ export const calcLineActualSize = (points, degree = 0) => { return Big(planeSize).div(theta).round().toNumber() } +/** + * 포인트와 기울기를 기준으로 선의 길이를 구한다. + * @param lineLength + * @param degree + * @returns number + */ +export const calcLineActualSizeByLineLength = (lineLength, degree = 0) => { + const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180))) + return Big(lineLength).div(theta).round().toNumber() +} + export const createLinesFromPolygon = (points) => { const lines = [] for (let i = 0; i < points.length; i++) { From 9683b38cf0d60c170cfa05bf90f7260fe53214b2 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 8 Jan 2026 15:15:17 +0900 Subject: [PATCH 27/55] =?UTF-8?q?=EB=B0=A9=ED=96=A5=ED=82=A4=EC=97=90=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=ED=8C=A8=EB=93=9C=20=EC=88=A8=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index df41043f..9cfddfd4 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -352,7 +352,7 @@ export const CalculatorInput = forwardRef( if (hasOperation) { handleCompute(true) // 계산 수행 } - setShowKeypad(true) + setShowKeypad(false) return } From 12f442b3edeec26620ae269a21878a01dc453b0a Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 8 Jan 2026 15:17:45 +0900 Subject: [PATCH 28/55] =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/lineTypes/DoublePitch.jsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx b/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx index 12ed66be..4919462b 100644 --- a/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx +++ b/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx @@ -26,17 +26,15 @@ export default function DoublePitch({ props }) { arrow2Ref, } = props - const getLength2 = () => { - const angle1Value = angle1Ref.current.value - const angle2Value = angle2Ref.current.value - const length1Value = length1Ref.current.value + const getLength2 = (angle1, angle2, length1) => { + const angle1Value = angle1 !== undefined ? angle1 : angle1Ref.current?.value + const angle2Value = angle2 !== undefined ? angle2 : angle2Ref.current?.value + const length1Value = length1 !== undefined ? length1 : length1Ref.current?.value const arrow1Value = arrow1Ref.current - const arrow2Value = arrow2Ref.current - if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '') { + if (!isNaN(Number(angle1Value)) && !isNaN(Number(length1Value)) && !isNaN(Number(angle2Value)) && arrow1Value) { const radian1 = (getDegreeByChon(angle1Value) * Math.PI) / 180 - const radian2 = (getDegreeByChon(angle2Value) * Math.PI) / 180 return Math.floor((Math.tan(radian1) * length1Value) / Math.tan(radian2)) } @@ -178,7 +176,7 @@ export default function DoublePitch({ props }) { ref={angle2Ref} onChange={(value) => { setAngle2(value) - setLength2(getLength2()) + setLength2(getLength2(angle1Ref.current?.value, value, length1Ref.current?.value)) }} placeholder="45" onFocus={() => (angle2Ref.current.value = '')} From 8d5d36fce6576f78d9856cbd6852242288898279 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 Jan 2026 16:41:11 +0900 Subject: [PATCH 29/55] =?UTF-8?q?=EC=8B=A4=EC=B9=98=EC=88=98=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EC=8B=9C=20=EC=86=8C=EC=88=98=EC=A0=90=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EB=AC=B8=EC=A0=9C=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=93=A0=20roof=20lines=EB=A5=BC?= =?UTF-8?q?=20=ED=99=95=EC=9D=B8=ED=95=98=EC=97=AC=20length=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 78 +++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 8822fd5f..bc63c50c 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,19 +1,7 @@ -import { - ANGLE_TYPE, - canvasState, - currentAngleTypeSelector, - globalPitchState, - pitchTextSelector, -} from '@/store/canvasAtom' +import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, globalPitchState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { - calculateIntersection, - findAndRemoveClosestPoint, - getDegreeByChon, - getDegreeInOrientation, - isPointOnLine, -} from '@/util/canvas-util' +import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { basicSettingState, flowDisplaySelector } from '@/store/settingAtom' @@ -1392,6 +1380,8 @@ export const usePolygon = () => { // 나눠서 중복 제거된 roof return let newRoofs = getSplitRoofsPoints(allLines) + const createdRoofs = [] + newRoofs = newRoofs.filter((roof) => roof.length !== 0) newRoofs.forEach((roofPoint, index) => { let defense, pitch @@ -1634,8 +1624,8 @@ export const usePolygon = () => { }) }) - canvas.add(roof) - addLengthText(roof) + // canvas.add(roof) + createdRoofs.push(roof) canvas.remove(polygon) canvas.renderAll() }) @@ -1645,6 +1635,11 @@ export const usePolygon = () => { auxiliaryLines.forEach((line) => { canvas.remove(line) }) + + createdRoofs.forEach((roof) => { + canvas.add(roof) + }) + canvas.renderAll() canvas.discardActiveObject() } @@ -1970,38 +1965,6 @@ export const usePolygon = () => { canvas.renderAll() } - /** - * 폴리곤의 라인 길이가 10 이하로 차이나는 경우 작은 값으로 통일 - * @param polygon - */ - const unifyLineLengths = (polygon) => { - if (!polygon.lines || polygon.lines.length === 0) { - return - } - - const lines = polygon.lines - - for (let i = 0; i < lines.length; i++) { - for (let j = i + 1; j < lines.length; j++) { - const length1 = lines[i].getLength() - const length2 = lines[j].getLength() - const diff = Math.abs(length1 - length2) - - if (diff > 0 && diff <= 10) { - const minLength = Math.min(length1, length2) - if (length1 > length2) { - lines[i].setLengthByValue(minLength) - } else { - lines[j].setLengthByValue(minLength) - } - } - } - } - - addLengthText(polygon) - canvas.renderAll() - } - /** * 폴리곤의 라인 속성을 복도치수, 실제치수에 따라 actualSize 설정 * @param polygon @@ -2010,7 +1973,23 @@ export const usePolygon = () => { if (!polygon.lines || polygon.lines.length === 0 || !polygon.roofMaterial) { return } - unifyLineLengths(polygon) + + // createdRoofs들의 모든 lines를 확인해서 length값이 1이하인 차이가 있으면 통일 시킨다. + const allRoofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + const allRoofLines = allRoofs.flatMap((roof) => roof.lines) + for (let i = 0; i < allRoofLines.length; i++) { + for (let j = i + 1; j < allRoofLines.length; j++) { + const line1 = allRoofLines[i] + const line2 = allRoofLines[j] + const diff = Math.abs(line1.length - line2.length) + if (diff > 0 && diff <= 1) { + const minLength = Math.min(line1.length, line2.length) + line1.setLengthByValue(minLength * 10) + line2.setLengthByValue(minLength * 10) + } + } + } + polygon.lines.forEach((line) => { setActualSize(line, polygon.direction, +polygon.roofMaterial?.pitch) }) @@ -2027,6 +2006,5 @@ export const usePolygon = () => { splitPolygonWithLines, splitPolygonWithSeparate, setPolygonLinesActualSize, - unifyLineLengths, } } From fbb1f352e9f8f7b686fa8a5f87d6c145ac4c18e9 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 8 Jan 2026 17:03:14 +0900 Subject: [PATCH 30/55] =?UTF-8?q?=EB=B2=BD=20=EB=9D=BC=EC=9D=B8=20wallLine?= =?UTF-8?q?=20=EC=86=8D=EC=84=B1=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roofcover/useRoofAllocationSetting.js | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 69479eef..70874765 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -323,42 +323,19 @@ export function useRoofAllocationSetting(id) { } const drawOriginRoofLine = () => { - // outerLinePoints 배열을 이용하여 빨간색 Line 객체들 생성 - if (outerLinePoints && outerLinePoints.length > 1) { - // 연속된 점들을 연결하여 라인 생성 - for (let i = 0; i < outerLinePoints.length - 1; i++) { - const point1 = outerLinePoints[i] - const point2 = outerLinePoints[i + 1] - - const line = new fabric.Line([point1.x, point1.y, point2.x, point2.y], { - stroke: 'black', - strokeDashArray: [5, 2], - strokeWidth: 1, - selectable: false, - name: 'originRoofOuterLine', - visible: outlineDisplay, - }) - - canvas.add(line) - } - - // 마지막 점과 첫 점을 연결하여 폐곡선 만들기 - if (outerLinePoints.length > 2) { - const lastPoint = outerLinePoints[outerLinePoints.length - 1] - const firstPoint = outerLinePoints[0] - - const closingLine = new fabric.Line([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], { - stroke: 'red', - strokeWidth: 2, - selectable: false, - name: 'originRoofOuterLine', - }) - - canvas.add(closingLine) - } - - canvas.renderAll() - } + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) + /** 벽면 삭제 */ + wallLines.forEach((wallLine) => { + wallLine.set({ + stroke: 'black', + strokeDashArray: [5, 2], + strokeWidth: 1, + selectable: false, + name: 'originRoofOuterLine', + visible: outlineDisplay, + }) + }) + canvas.renderAll() } /** From de707796a36ab3cc909632de934c8748b7384a57 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 8 Jan 2026 18:12:10 +0900 Subject: [PATCH 31/55] =?UTF-8?q?=EB=8D=94=EB=B8=94=EA=B3=84=EC=82=B0?= =?UTF-8?q?=ED=8C=A8=EB=93=9C=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/lineTypes/RightAngle.jsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/floor-plan/modal/lineTypes/RightAngle.jsx b/src/components/floor-plan/modal/lineTypes/RightAngle.jsx index ef2f00e2..a4356b4a 100644 --- a/src/components/floor-plan/modal/lineTypes/RightAngle.jsx +++ b/src/components/floor-plan/modal/lineTypes/RightAngle.jsx @@ -61,28 +61,40 @@ export default function RightAngle({ props }) {
Date: Fri, 9 Jan 2026 15:33:04 +0900 Subject: [PATCH 35/55] =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EC=A0=84=20quotatio?= =?UTF-8?q?nParam=20null=20=EC=84=B8=ED=8C=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useTrestle.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 658c8c22..0899afcb 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -44,6 +44,9 @@ export const useTrestle = () => { // exposedBottomPoints는 노출 최하면 들의 centerPoint 배열. const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) + surfaces.forEach((surface) => { + surface.set({ quotationParam: null }) + }) // 기존 eaveBar를 삭제 canvas.getObjects().forEach((obj) => { if (obj.name === 'eaveBar' || obj.name === 'rack' || obj.name === 'halfEaveBar' || obj.name === 'smartRack') { @@ -744,18 +747,17 @@ export const useTrestle = () => { return } //itemList = data -// itemList에 northModuleYn 추가 - itemList = data.map(item => { - if (item.itemTpCd === "MODULE") { - const matchedModule = modules.find(module => module.moduleItemId === item.itemId); + // itemList에 northModuleYn 추가 + itemList = data.map((item) => { + if (item.itemTpCd === 'MODULE') { + const matchedModule = modules.find((module) => module.moduleItemId === item.itemId) return { ...item, - northModuleYn: matchedModule?.northModuleYn || 'N' - }; + northModuleYn: matchedModule?.northModuleYn || 'N', + } } - return item; - }); - + return item + }) //northArrangement 북면 설치 여부 const northArrangement = getNorthArrangement() @@ -820,11 +822,11 @@ export const useTrestle = () => { // 발전 시뮬레이션 용 각도 재계산 const getAzimuth = (parent) => { if (typeof parent === 'string') { - console.warn('getAzimuth: parent is string, expected object', parent); - return 0; // 또는 적절한 기본값 + console.warn('getAzimuth: parent is string, expected object', parent) + return 0 // 또는 적절한 기본값 } - const { moduleCompass, surfaceCompass, direction } = parent || {}; + const { moduleCompass, surfaceCompass, direction } = parent || {} if (surfaceCompass) { return -surfaceCompass @@ -2603,7 +2605,7 @@ export const useTrestle = () => { return { moduleTpCd: module.moduleInfo.itemTp, moduleItemId: module.moduleInfo.itemId, - northModuleYn: module?.moduleInfo?.northModuleYn || 'N' // 기본값 'N' + northModuleYn: module?.moduleInfo?.northModuleYn || 'N', // 기본값 'N' } }) @@ -2615,7 +2617,7 @@ export const useTrestle = () => { moduleTpCd: cur.moduleTpCd, moduleItemId: cur.moduleItemId, cnt: 0, - northModuleYn: cur.northModuleYn + northModuleYn: cur.northModuleYn, } } acc[key].cnt++ @@ -2628,7 +2630,7 @@ export const useTrestle = () => { moduleTpCd: groupedParam.moduleTpCd, moduleItemId: groupedParam.moduleItemId, moduleCnt: groupedParam.cnt, - northModuleYn: groupedParam.northModuleYn + northModuleYn: groupedParam.northModuleYn, // northModuleYn: params.find(p => // p.moduleTpCd === groupedParam.moduleTpCd && // p.moduleItemId === groupedParam.moduleItemId From 9369c1c952a0001033861bfa5470c312fb4e0a02 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Fri, 9 Jan 2026 17:15:18 +0900 Subject: [PATCH 36/55] =?UTF-8?q?qline=20=EC=8B=A4=EC=B9=98=EC=88=98=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B6=80=EB=B6=84=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/QLine.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 02448cd7..4608bd58 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -84,9 +84,12 @@ export const QLine = fabric.util.createClass(fabric.Line, { addLengthText() { const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id) - if (this.textMode === 'none') { - if (thisText) { - this.canvas.remove(thisText) + if (thisText) { + if (this.attributes?.actualSize) { + thisText.set({ actualSize: this.attributes.actualSize }) + } + if (this.attributes?.planeSize) { + thisText.set({ planeSize: this.attributes.planeSize }) } } else { this.setLength() @@ -97,11 +100,6 @@ export const QLine = fabric.util.createClass(fabric.Line, { const x2 = this.left + this.width * scaleX const y2 = this.top + this.height * scaleY - if (thisText) { - thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 }) - this.text = thisText - return - } let left, top if (this.direction === 'left' || this.direction === 'right') { left = (x1 + x2) / 2 @@ -118,6 +116,8 @@ export const QLine = fabric.util.createClass(fabric.Line, { const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI const text = new fabric.Textbox(this.getLength().toString(), { + actualSize: this.attributes?.actualSize, + planeSize: this.attributes?.planeSize, left: left, top: top, fontSize: this.fontSize, From c4b03c6745dfedd25f350352637d0797affa6c87 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 9 Jan 2026 17:28:02 +0900 Subject: [PATCH 37/55] =?UTF-8?q?diff=202=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index bc63c50c..e846760b 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1982,7 +1982,7 @@ export const usePolygon = () => { const line1 = allRoofLines[i] const line2 = allRoofLines[j] const diff = Math.abs(line1.length - line2.length) - if (diff > 0 && diff <= 1) { + if (diff > 0 && diff <= 2) { const minLength = Math.min(line1.length, line2.length) line1.setLengthByValue(minLength * 10) line2.setLengthByValue(minLength * 10) From e61913355e539a42fd19abe694d5e853a981be7d Mon Sep 17 00:00:00 2001 From: yscha Date: Sun, 11 Jan 2026 15:24:43 +0900 Subject: [PATCH 38/55] =?UTF-8?q?1=EC=B0=A8=EC=A0=90=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A0=20=EA=B2=AC=EC=A0=81=EC=84=9C=202=EC=B0=A8?= =?UTF-8?q?=EC=A0=90=20=EA=B0=80=EA=B2=A9=20=EB=AF=B8=EB=85=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/estimate/popup/DocDownOptionPop.jsx | 5 +++-- src/components/management/StuffDetail.jsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/estimate/popup/DocDownOptionPop.jsx b/src/components/estimate/popup/DocDownOptionPop.jsx index f5a967f1..9a48999f 100644 --- a/src/components/estimate/popup/DocDownOptionPop.jsx +++ b/src/components/estimate/popup/DocDownOptionPop.jsx @@ -8,7 +8,7 @@ import { usePathname, useSearchParams } from 'next/navigation' import { QcastContext } from '@/app/QcastProvider' import { sessionStore } from '@/store/commonAtom' -export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDownPopLockFlg }) { +export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDownPopLockFlg, saleStoreId = '' }) { const { setIsGlobalLoading } = useContext(QcastContext) const { getMessage } = useMessage() @@ -69,7 +69,8 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown schDrawingFlg: defaultSchDrawingFlg, pwrGnrSimType: 'D', //default 화면에 안보여줌 userId: sessionState.userId ? sessionState.userId : "", - saleStoreId: sessionState.storeId ? sessionState.storeId : "", + saleStoreId: saleStoreId ? saleStoreId : (sessionState.saleStoreId ? sessionState.saleStoreId : ""), + storeLvl: sessionState.storeLvl } const options = { responseType: 'blob' } diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index cc9d71ea..611ba72a 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -2936,7 +2936,7 @@ export default function StuffDetail() { )} - {estimatePopupOpen && } + {estimatePopupOpen && } ) } From bdd7a475d51c3f126496cd7f89c3a08eb6a03215 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 12 Jan 2026 11:08:49 +0900 Subject: [PATCH 39/55] =?UTF-8?q?1=EC=B0=A8=EC=A0=90=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=202=EC=B0=A8=EC=A0=90=20=EA=B0=80=EA=B2=A9=20=EB=AF=B8?= =?UTF-8?q?=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/estimate/popup/DocDownOptionPop.jsx | 7 ++++--- src/components/management/StuffDetail.jsx | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/estimate/popup/DocDownOptionPop.jsx b/src/components/estimate/popup/DocDownOptionPop.jsx index 9a48999f..2f250646 100644 --- a/src/components/estimate/popup/DocDownOptionPop.jsx +++ b/src/components/estimate/popup/DocDownOptionPop.jsx @@ -8,7 +8,7 @@ import { usePathname, useSearchParams } from 'next/navigation' import { QcastContext } from '@/app/QcastProvider' import { sessionStore } from '@/store/commonAtom' -export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDownPopLockFlg, saleStoreId = '' }) { +export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDownPopLockFlg, createStoreId = '' }) { const { setIsGlobalLoading } = useContext(QcastContext) const { getMessage } = useMessage() @@ -69,8 +69,9 @@ export default function DocDownOptionPop({ planNo, setEstimatePopupOpen, docDown schDrawingFlg: defaultSchDrawingFlg, pwrGnrSimType: 'D', //default 화면에 안보여줌 userId: sessionState.userId ? sessionState.userId : "", - saleStoreId: saleStoreId ? saleStoreId : (sessionState.saleStoreId ? sessionState.saleStoreId : ""), - storeLvl: sessionState.storeLvl + saleStoreId: sessionState.storeId ? sessionState.storeId : "", + storeLvl: sessionState.storeLvl, + createStoreId: createStoreId ? createStoreId : '', } const options = { responseType: 'blob' } diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index 611ba72a..75db2623 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -54,6 +54,8 @@ export default function StuffDetail() { const globalLocaleState = useRecoilValue(globalLocaleStore) const ref = useRef() const { get, promiseGet, del, promisePost, promisePut } = useAxios(globalLocaleState) + const [createSaleStoreId, setCreateSaleStoreId] = useState('') + //form const formInitValue = { // 물건번호 T...(임시) S...(진짜) @@ -350,6 +352,9 @@ export default function StuffDetail() { promiseGet({ url: `/api/object/${objectNo}/detail` }).then((res) => { setIsGlobalLoading(false) if (res.status === 200) { + + setCreateSaleStoreId(res?.data?.createSaleStoreId); + if (res?.data?.createSaleStoreId === 'T01') { if (session?.storeId !== 'T01') { setShowButton('none') @@ -2936,7 +2941,7 @@ export default function StuffDetail() { )} - {estimatePopupOpen && } + {estimatePopupOpen && } ) } From aa837d8dbf3e6344f9ee59b2aca4c037d8ccdfe1 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 12 Jan 2026 11:12:29 +0900 Subject: [PATCH 40/55] =?UTF-8?q?=EB=B8=94=EB=9F=AD=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/input/CalcInput.jsx | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index 9cfddfd4..e8a97b93 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -63,6 +63,33 @@ export const CalculatorInput = forwardRef( const calculator = calculatorRef.current let newDisplayValue = '' + // 블록 지정(Selection) 확인 및 처리 + if (inputRef.current) { + const { selectionStart, selectionEnd } = inputRef.current + // 텍스트 전체 또는 일부가 블록 지정된 경우 + if (selectionStart !== null && selectionEnd !== null && selectionStart !== selectionEnd) { + // 연산 중이 아닐 때만 전체 초기화 후 입력 처리 (계산기 모드 유지를 위해) + if (!hasOperation) { + calculator.currentOperand = num.toString() + calculator.previousOperand = '' + calculator.operation = undefined + calculator.shouldResetDisplay = false + + newDisplayValue = calculator.currentOperand + setDisplayValue(newDisplayValue) + onChange(newDisplayValue) + + requestAnimationFrame(() => { + if (inputRef.current) { + inputRef.current.focus() + inputRef.current.setSelectionRange(newDisplayValue.length, newDisplayValue.length) + } + }) + return // 블록 처리 로직 완료 후 종료 + } + } + } + // maxLength 체크 if (maxLength > 0) { const currentLength = (calculator.currentOperand || '').length + (calculator.previousOperand || '').length + (calculator.operation || '').length From 3dd96d043fc7055af4c298549d94c13b1c53fa4e Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 12 Jan 2026 15:38:21 +0900 Subject: [PATCH 41/55] =?UTF-8?q?[1383]=EA=B2=AC=EC=A0=81=EC=84=9C=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=9D=B8=EC=BD=94=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/estimate/Estimate.jsx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index ee2fab4b..9107dde0 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -2039,7 +2039,11 @@ export default function Estimate({}) { } }} menuPlacement={'auto'} - getOptionLabel={(x) => x.itemName + ' (' + x.itemNo + ')'} + getOptionLabel={(x) => { + // 메뉴 리스트에 보이는 텍스트 디코딩 + const doc = new DOMParser().parseFromString(x.itemName, 'text/html'); + return (doc.documentElement.textContent || x.itemName) + ' (' + x.itemNo + ')'; + }} getOptionValue={(x) => x.itemNo} components={{ SingleValue: ({ children, ...props }) => { @@ -2048,13 +2052,21 @@ export default function Estimate({}) { }} isClearable={false} isDisabled={!!item?.paDispOrder} - value={displayItemList.filter(function (option) { - if (item.itemNo === '') { - return false - } else { - return option.itemId === item.itemId + value={(() => { + const selectedOption = displayItemList.find((option) => { + return item.itemNo !== '' && option.itemId === item.itemId; + }); + + if (selectedOption) { + // 현재 선택된 값의 itemName을 실시간으로 디코딩하여 전달 + const doc = new DOMParser().parseFromString(selectedOption.itemName, 'text/html'); + return { + ...selectedOption, + itemName: doc.documentElement.textContent || selectedOption.itemName + }; } - })} + return null; + })()} /> ) : ( { - handleChangeApplyParalQty(idx, serQtyIdx, e.target.value) - }} - > - {item.paralQty === 0 && ( - - )} - {Array.from( - { - length: originPcsVoltageStepUpList[index] - ? originPcsVoltageStepUpList[index]?.pcsItemList[idx].serQtyList[serQtyIdx].paralQty - : item.paralQty, - }, - (_, i) => i + 1, - ).map((num) => ( - - ))} - + stepUp?.pcsItemList.length !== 1 ? ( + + ) : ( + <>{item.paralQty} + ) ) : ( <>{item.paralQty} )} From 0f481e2933b49bfd35ce69baae7c0e5eea896a41 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 13 Jan 2026 13:32:45 +0900 Subject: [PATCH 43/55] =?UTF-8?q?=EC=84=A0=EC=9D=B4=20=EA=B0=80=EC=A7=80?= =?UTF-8?q?=EA=B3=A0=EC=9E=88=EB=8A=94=20planeSize=EB=A1=9C=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index c9de0194..f61b977b 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -172,9 +172,9 @@ export const useLine = () => { const isHorizontal = y1 === y2 const isVertical = x1 === x2 const isDiagonal = !isHorizontal && !isVertical - const lineLength = line.getLength() + const lineLength = line.attributes.planeSize ?? line.getLength() - line.attributes = { ...line.attributes, planeSize: line.getLength(), actualSize: line.getLength() } + // line.attributes = { ...line.attributes, planeSize: line.getLength(), actualSize: line.getLength() } if (+roofSizeSet === 1) { if (direction === 'south' || direction === 'north') { From 4fdd7d55d0e327c2784d1e7afbd519539663ea40 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 13 Jan 2026 13:44:29 +0900 Subject: [PATCH 44/55] =?UTF-8?q?=ED=98=95=EC=98=AC=EB=A6=BC=EB=82=B4?= =?UTF-8?q?=EB=A6=BC=20=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index f61b977b..30cc9ba9 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -174,7 +174,7 @@ export const useLine = () => { const isDiagonal = !isHorizontal && !isVertical const lineLength = line.attributes.planeSize ?? line.getLength() - // line.attributes = { ...line.attributes, planeSize: line.getLength(), actualSize: line.getLength() } + line.attributes = { ...line.attributes, planeSize: lineLength, actualSize: lineLength } if (+roofSizeSet === 1) { if (direction === 'south' || direction === 'north') { From c501896f590cab2c61306b64e62da8261d6fc30b Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 15 Jan 2026 09:55:52 +0900 Subject: [PATCH 45/55] =?UTF-8?q?=EC=84=A0=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B8=B0=EB=B3=B8=20attributes.planeSize=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useLine.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/useLine.js b/src/hooks/useLine.js index 30cc9ba9..3e1d72e6 100644 --- a/src/hooks/useLine.js +++ b/src/hooks/useLine.js @@ -10,7 +10,7 @@ import { } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' import { basicSettingState } from '@/store/settingAtom' -import { calcLineActualSizeByLineLength } from '@/util/qpolygon-utils' +import { calcLineActualSizeByLineLength, calcLinePlaneSize } from '@/util/qpolygon-utils' import { getDegreeByChon } from '@/util/canvas-util' import { useText } from '@/hooks/useText' import { fontSelector } from '@/store/fontAtom' @@ -31,6 +31,7 @@ export const useLine = () => { const addLine = (points = [], options) => { const line = new QLine(points, { ...options, + attributes: {}, fontSize: lengthText.fontSize.value, fontFamily: lengthText.fontFamily.value, }) @@ -38,7 +39,7 @@ export const useLine = () => { if (line.length < 1) { return null } - + line.attributes.planeSize = calcLinePlaneSize(line) canvas?.add(line) return line } From 8475dc4b1f973cd19aa063d061c5d2abc83308a9 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 15 Jan 2026 10:24:50 +0900 Subject: [PATCH 46/55] =?UTF-8?q?=ED=8C=A8=EC=8A=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=ED=8C=9D=EC=97=85=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=95=88=ED=95=A8=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=A9=88?= =?UTF-8?q?=EC=B6=A4=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main/ChangePasswordPop.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/main/ChangePasswordPop.jsx b/src/components/main/ChangePasswordPop.jsx index efae3bb5..554f9b29 100644 --- a/src/components/main/ChangePasswordPop.jsx +++ b/src/components/main/ChangePasswordPop.jsx @@ -2,12 +2,13 @@ import { useContext } from 'react' import { useMessage } from '@/hooks/useMessage' import { useForm } from 'react-hook-form' import { sessionStore } from '@/store/commonAtom' -import { useRecoilValue, useRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue } from 'recoil' import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' -import { logout, setSession, login } from '@/lib/authActions' +import { login, logout, setSession } from '@/lib/authActions' import { useSwal } from '@/hooks/useSwal' import { QcastContext } from '@/app/QcastProvider' +import { useRouter } from 'next/navigation' export default function ChangePasswordPop(props) { const globalLocaleState = useRecoilValue(globalLocaleStore) @@ -18,6 +19,7 @@ export default function ChangePasswordPop(props) { const { patch } = useAxios(globalLocaleState) const { getMessage } = useMessage() const [sessionState, setSessionState] = useRecoilState(sessionStore) + const router = useRouter() const formInitValue = { password1: '', password2: '', @@ -207,6 +209,7 @@ export default function ChangePasswordPop(props) { className="btn-origin grey" onClick={() => { logout() + router.replace('/login', undefined, { shallow: true }) }} > {getMessage('main.popup.login.btn2')} From 6c232403454cf3702504bf3a57d28e1598edd57c Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 15 Jan 2026 10:26:39 +0900 Subject: [PATCH 47/55] =?UTF-8?q?logout=20=ED=98=B8=EC=B6=9C=20=EC=8B=9C?= =?UTF-8?q?=20=EC=A0=84=EB=B6=80=20login=20=EC=9C=BC=EB=A1=9C=20=ED=8A=95?= =?UTF-8?q?=EA=B8=B0=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/main/ChangePasswordPop.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/main/ChangePasswordPop.jsx b/src/components/main/ChangePasswordPop.jsx index 554f9b29..28dc537c 100644 --- a/src/components/main/ChangePasswordPop.jsx +++ b/src/components/main/ChangePasswordPop.jsx @@ -130,12 +130,14 @@ export default function ChangePasswordPop(props) { } else { setIsGlobalLoading(false) logout() + router.replace('/login', undefined, { shallow: true }) console.log('code not 200 error') } }) .catch((error) => { setIsGlobalLoading(false) logout() + router.replace('/login', undefined, { shallow: true }) console.log('catch::::::::', error) }) } From d95dcf608d6c45c3b7e4dd9a1c911b81a1695a9d Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 15 Jan 2026 14:36:26 +0900 Subject: [PATCH 48/55] =?UTF-8?q?=EB=8F=99=EC=AA=BD=20=EC=B9=98=EB=8F=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=B0=EC=B9=98=EC=8B=9C=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useTrestle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 0899afcb..88980cad 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -3094,7 +3094,7 @@ export const useTrestle = () => { ) } if (!halfBottomRightModule) { - halfBottomRightPoint = { x: x + w, y: y - height / 2 } + halfBottomRightPoint = { x: x + w, y: y + height / 2 } halfBottomRightModule = centerPoints.find( (centerPoint) => Math.abs(centerPoint.x - halfBottomRightPoint.x) < maxX && Math.abs(centerPoint.y - halfBottomRightPoint.y) < maxY, ) From 310068bf6ad3b3a0c5b41b94628647c08a629a9b Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 Jan 2026 09:51:32 +0900 Subject: [PATCH 49/55] =?UTF-8?q?circuitTestle=20=ED=8F=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/circuitTrestle/CircuitTrestleSetting.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index 32e20ad4..b2c4495c 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -344,9 +344,9 @@ export default function CircuitTrestleSetting({ id }) { canvas.zoomToPoint(new fabric.Point(x, y), 0.4) - changeFontSize('lengthText', '28') - changeFontSize('circuitNumber', '28') - changeFontSize('flowText', '28') + // changeFontSize('lengthText', '28') + // changeFontSize('circuitNumber', '28') + // changeFontSize('flowText', '28') canvas.renderAll() } From 76e8e581dbe0483ddd11076831a954d5f418b90f Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 16 Jan 2026 17:23:42 +0900 Subject: [PATCH 50/55] =?UTF-8?q?[1410]=20:=20[HANASYS=20DESIGN]=20[?= =?UTF-8?q?=EB=B6=81=EB=A9=B4]=EC=9C=BC=EB=A1=9C=20=EC=B7=A8=EA=B8=89?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=B2=94=EC=9C=84=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=20=EB=8A=98=EC=96=B4=EB=82=A8=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EA=B3=84=EC=82=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/usePolygon.js | 248 +++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 143 deletions(-) diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index e846760b..b47de91a 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -1,7 +1,7 @@ import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, globalPitchState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' -import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util' +import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { basicSettingState, flowDisplaySelector } from '@/store/settingAtom' @@ -344,6 +344,7 @@ export const usePolygon = () => { } //arrow의 compass 값으로 방향 글자 설정 필요 + // moduleCompass 각도와 direction(지붕면 방향)에 따라 한자 방위 텍스트 매핑 const drawDirectionStringToArrow2 = (polygon, showDirectionText) => { let { direction, surfaceCompass, moduleCompass, arrow } = polygon if (moduleCompass === null || moduleCompass === undefined) { @@ -371,153 +372,114 @@ export const usePolygon = () => { let text = '' - let compassType = (375 + getDegreeInOrientation(moduleCompass)) / 15 + // moduleCompass 각도와 direction에 따른 한자 방위 매핑 + // direction: south(↓), west(←), north(↑), east(→) + // 각도 범위별 매핑 테이블 (사진 기준) + const getDirectionText = (angle, dir) => { + // 각도를 정규화 (-180 ~ 180 범위로) + let normalizedAngle = Number(angle) + while (normalizedAngle > 180) normalizedAngle -= 360 + while (normalizedAngle < -180) normalizedAngle += 360 - moduleCompass = -1 * moduleCompass + // 매핑 테이블: { south(↓), west(←), north(↑), east(→) } + // 각도 0: 남, 서, 북, 동 + // 각도 45: 남서, 북서, 북동, 남동 + // 각도 90: 서, 북, 동, 남 + // 각도 135: 북서, 북동, 남동, 남서 + // 각도 180: 북, 동, 남, 서 + // 각도 -45: 남동, 남서, 북서, 북동 + // 각도 -90: 동, 남, 서, 북 + // 각도 -135: 북동, 남동, 남서, 북서 - if (moduleCompass === 0 || (moduleCompass < 0 && moduleCompass >= -6)) { - compassType = 1 - } else if (moduleCompass < 0 && moduleCompass >= -21) { - compassType = 2 - } else if (moduleCompass < 0 && moduleCompass >= -36) { - compassType = 3 - } else if (moduleCompass < 0 && moduleCompass >= -51) { - compassType = 4 - } else if (moduleCompass < 0 && moduleCompass >= -66) { - compassType = 5 - } else if (moduleCompass < 0 && moduleCompass >= -81) { - compassType = 6 - } else if (moduleCompass < 0 && moduleCompass >= -96) { - compassType = 7 - } else if (moduleCompass < 0 && moduleCompass >= -111) { - compassType = 8 - } else if (moduleCompass < 0 && moduleCompass >= -126) { - compassType = 9 - } else if (moduleCompass < 0 && moduleCompass >= -141) { - compassType = 10 - } else if (moduleCompass < 0 && moduleCompass >= -156) { - compassType = 11 - } else if (moduleCompass < 0 && moduleCompass >= -171) { - compassType = 12 - } else if (Math.abs(moduleCompass) === 180) { - compassType = 13 + let mapping + // 정확한 각도 먼저 체크 + if (normalizedAngle === 0) { + mapping = { south: '南', west: '西', north: '北', east: '東' } + } else if (normalizedAngle === 45) { + mapping = { south: '南西', west: '北西', north: '北東', east: '南東' } + } else if (normalizedAngle === 90) { + mapping = { south: '西', west: '北', north: '東', east: '南' } + } else if (normalizedAngle === 135) { + mapping = { south: '北西', west: '北東', north: '南東', east: '南西' } + } else if (normalizedAngle === 180 || normalizedAngle === -180) { + mapping = { south: '北', west: '東', north: '南', east: '西' } + } else if (normalizedAngle === -45) { + mapping = { south: '南東', west: '南西', north: '北西', east: '北東' } + } else if (normalizedAngle === -90) { + mapping = { south: '東', west: '南', north: '西', east: '北' } + } else if (normalizedAngle === -135) { + mapping = { south: '北東', west: '南東', north: '南西', east: '北西' } + } + // 범위 각도 체크 + else if (normalizedAngle >= 1 && normalizedAngle <= 44) { + // 1~44: 남남서, 서북서, 북북동, 동남동 + mapping = { south: '南南西', west: '西北西', north: '北北東', east: '東南東' } + } else if (normalizedAngle >= 46 && normalizedAngle <= 89) { + // 46~89: 서남서, 북북서, 동북동, 남남동 + mapping = { south: '西南西', west: '北北西', north: '東北東', east: '南南東' } + } else if (normalizedAngle >= 91 && normalizedAngle <= 134) { + // 91~134: 서북서, 북북동, 동남동, 남남서 + mapping = { south: '西北西', west: '北北東', north: '東南東', east: '南南西' } + } else if (normalizedAngle >= 136 && normalizedAngle <= 179) { + // 136~179: 북북서, 동북동, 남남동, 서남서 + mapping = { south: '北北西', west: '東北東', north: '南南東', east: '西南西' } + } else if (normalizedAngle >= -44 && normalizedAngle <= -1) { + // -1~-44: 남남동, 서남서, 북북서, 동북동 + mapping = { south: '南南東', west: '西南西', north: '北北西', east: '東北東' } + } else if (normalizedAngle >= -89 && normalizedAngle <= -46) { + // -46~-89: 동남동, 남남서, 서북서, 북북동 + mapping = { south: '東南東', west: '南南西', north: '西北西', east: '北北東' } + } else if (normalizedAngle >= -134 && normalizedAngle <= -91) { + // -91~-134: 동북동, 남남동, 서남서, 북북서 + mapping = { south: '東北東', west: '南南東', north: '西南西', east: '北北西' } + } else if (normalizedAngle >= -179 && normalizedAngle <= -136) { + // -136~-179: 북북동, 동남동, 남남서, 서북서 + mapping = { south: '北北東', west: '東南東', north: '南南西', east: '西北西' } + } else { + // 기본값: 0도 + mapping = { south: '南', west: '西', north: '北', east: '東' } + } + + return mapping[dir] || '南' } - if ([1, 25].includes(compassType)) { - direction === 'north' ? (text = '北') : direction === 'south' ? (text = '南') : direction === 'west' ? (text = '西') : (text = '東') - } else if ([2, 3].includes(compassType)) { - direction === 'north' - ? (text = '北北東') - : direction === 'south' - ? (text = '南南西') - : direction === 'west' - ? (text = '西北西') - : (text = '東南東') - } else if ([4].includes(compassType)) { - direction === 'north' ? (text = '北東') : direction === 'south' ? (text = '南西') : direction === 'west' ? (text = '北西') : (text = '南東') - } else if ([5, 6].includes(compassType)) { - direction === 'north' - ? (text = '東北東') - : direction === 'south' - ? (text = '西南西') - : direction === 'west' - ? (text = '北北西') - : (text = '南南東') - } else if ([7].includes(compassType)) { - direction === 'north' ? (text = '東') : direction === 'south' ? (text = '西') : direction === 'west' ? (text = '北') : (text = '南') - } else if ([8, 9].includes(compassType)) { - direction === 'north' - ? (text = '東南東') - : direction === 'south' - ? (text = '西北西') - : direction === 'west' - ? (text = '北北東') - : (text = '南南西') - } else if ([10].includes(compassType)) { - direction === 'north' ? (text = '南東') : direction === 'south' ? (text = '北西') : direction === 'west' ? (text = '北東') : (text = '南西') - } else if ([11, 12].includes(compassType)) { - direction === 'north' - ? (text = '南南東') - : direction === 'south' - ? (text = '北北西') - : direction === 'west' - ? (text = '東北東') - : (text = '西南西') - } else if ([13].includes(compassType)) { - direction === 'north' ? (text = '南') : direction === 'south' ? (text = '北') : direction === 'west' ? (text = '東') : (text = '西') - } else if ([14, 15].includes(compassType)) { - direction === 'north' - ? (text = '南南西') - : direction === 'south' - ? (text = '北北東') - : direction === 'west' - ? (text = '東南東') - : (text = '西北西') - } else if ([16].includes(compassType)) { - direction === 'north' ? (text = '南西') : direction === 'south' ? (text = '北東') : direction === 'west' ? (text = '南東') : (text = '北西') - } else if ([17, 18].includes(compassType)) { - direction === 'north' - ? (text = '西南西') - : direction === 'south' - ? (text = '東北東') - : direction === 'west' - ? (text = '南南東') - : (text = '北北西') - } else if ([19].includes(compassType)) { - direction === 'north' ? (text = '西') : direction === 'south' ? (text = '東') : direction === 'west' ? (text = '南') : (text = '北') - } else if ([20, 21].includes(compassType)) { - direction === 'north' - ? (text = '西北西') - : direction === 'south' - ? (text = '東南東') - : direction === 'west' - ? (text = '南南西') - : (text = '北北東') - } else if ([22].includes(compassType)) { - direction === 'north' ? (text = '北西') : direction === 'south' ? (text = '南東') : direction === 'west' ? (text = '南西') : (text = '北東') - } else if ([23, 24].includes(compassType)) { - direction === 'north' - ? (text = '北北西') - : direction === 'south' - ? (text = '南南東') - : direction === 'west' - ? (text = '西南西') - : (text = '東北東') - } + text = getDirectionText(moduleCompass, direction) - // 東,西,南,北 - if ([0].includes(surfaceCompass)) { - text = '南' - } else if ([15, 30].includes(surfaceCompass)) { - text = '南南東' - } else if ([45].includes(surfaceCompass)) { - text = '南東' - } else if ([60, 75].includes(surfaceCompass)) { - text = '東南東' - } else if ([90].includes(surfaceCompass)) { - text = '東' - } else if ([105, 120].includes(surfaceCompass)) { - text = '東北東' - } else if ([135].includes(surfaceCompass)) { - text = '北東' - } else if ([150, 165].includes(surfaceCompass)) { - text = '北北東' - } else if ([180].includes(surfaceCompass)) { - text = '北' - } else if ([-165, -150].includes(surfaceCompass)) { - text = '北北西' - } else if ([-135].includes(surfaceCompass)) { - text = '北西' - } else if ([-120, -105].includes(surfaceCompass)) { - text = '西北西' - } else if ([-90].includes(surfaceCompass)) { - text = '西' - } else if ([-75, -60].includes(surfaceCompass)) { - text = '西南西' - } else if ([-45].includes(surfaceCompass)) { - text = '西南' - } else if ([-30, -15].includes(surfaceCompass)) { - text = '西西南' + // surfaceCompass가 있으면 text를 덮어쓰기 (기존 로직 유지) + if (surfaceCompass !== null && surfaceCompass !== undefined) { + if ([0].includes(surfaceCompass)) { + text = '南' + } else if ([15, 30].includes(surfaceCompass)) { + text = '南南東' + } else if ([45].includes(surfaceCompass)) { + text = '南東' + } else if ([60, 75].includes(surfaceCompass)) { + text = '東南東' + } else if ([90].includes(surfaceCompass)) { + text = '東' + } else if ([105, 120].includes(surfaceCompass)) { + text = '東北東' + } else if ([135].includes(surfaceCompass)) { + text = '北東' + } else if ([150, 165].includes(surfaceCompass)) { + text = '北北東' + } else if ([180].includes(surfaceCompass)) { + text = '北' + } else if ([-165, -150].includes(surfaceCompass)) { + text = '北北西' + } else if ([-135].includes(surfaceCompass)) { + text = '北西' + } else if ([-120, -105].includes(surfaceCompass)) { + text = '西北西' + } else if ([-90].includes(surfaceCompass)) { + text = '西' + } else if ([-75, -60].includes(surfaceCompass)) { + text = '西南西' + } else if ([-45].includes(surfaceCompass)) { + text = '南西' + } else if ([-30, -15].includes(surfaceCompass)) { + text = '南南西' + } } const sameDirectionCnt = canvas.getObjects().filter((obj) => { From b361ccf4b7342bbd62f043783cfdc748d05766eb Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 19 Jan 2026 10:23:12 +0900 Subject: [PATCH 51/55] =?UTF-8?q?=EC=84=B8=EB=A1=9C=EB=A1=9C=20=EA=B8=B4?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=9E=98?= =?UTF-8?q?=EB=A6=AC=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/image/canvas/route.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/app/api/image/canvas/route.js b/src/app/api/image/canvas/route.js index 9f4e3f52..b96e079f 100644 --- a/src/app/api/image/canvas/route.js +++ b/src/app/api/image/canvas/route.js @@ -95,16 +95,11 @@ const resizeImage = async (image) => { const scaleY = targetImageHeight / image.bitmap.height let scale = Math.min(scaleX, scaleY) // 비율 유지하면서 최대한 크게 - // scale 저장 (나중에 전체 확대에 사용) - const originalScale = scale - let finalWidth = Math.round(image.bitmap.width * scale) let finalHeight = Math.round(image.bitmap.height * scale) - if (scale >= 0.6) { - // 실제 리사이즈 실행 - image.resize({ w: finalWidth, h: finalHeight }) - } + // 항상 리사이즈 실행 (scale >= 0.6 조건 제거) + image.resize({ w: finalWidth, h: finalHeight }) //배경 이미지를 생성 const mixedImage = new Jimp({ width: convertStandardWidth, height: convertStandardHeight, color: 0xffffffff }) @@ -119,14 +114,7 @@ const resizeImage = async (image) => { opacityDest: 1, }) - // scale이 0.8 이하인 경우 완성된 이미지를 전체적으로 확대 - if (originalScale <= 0.8) { - const enlargeRatio = 1.5 // 50% 확대 - const newWidth = Math.round(mixedImage.bitmap.width * enlargeRatio) - const newHeight = Math.round(mixedImage.bitmap.height * enlargeRatio) - - mixedImage.resize({ w: newWidth, h: newHeight }) - } + // 1.5x 확대 로직 제거 - 이미지가 템플릿 크기를 초과하지 않도록 함 return mixedImage } From a687be997f5b32005da78851a928efa4f78ea5c3 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 19 Jan 2026 13:39:11 +0900 Subject: [PATCH 52/55] =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=2030=EC=9E=90?= =?UTF-8?q?=20->=2050=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/auth/Join.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index 4c080f00..9fcc09fd 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -381,7 +381,7 @@ export default function Join() {
- +
From bb47f14d953adc672521f74e40e51c35e31821d2 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 19 Jan 2026 15:37:39 +0900 Subject: [PATCH 53/55] =?UTF-8?q?[1413]=20=EC=A7=80=EB=B6=95=EC=9E=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20api=20=ED=98=B8=EC=B6=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=88=98=20=3D>=20=ED=95=9C=EB=B2=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useMasterController.js | 26 ++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/hooks/common/useMasterController.js b/src/hooks/common/useMasterController.js index f13d3e74..b46adc81 100644 --- a/src/hooks/common/useMasterController.js +++ b/src/hooks/common/useMasterController.js @@ -3,8 +3,11 @@ import { useMessage } from '@/hooks/useMessage' import { useSwal } from '@/hooks/useSwal' import { sessionStore } from '@/store/commonAtom' import { getQueryString } from '@/util/common-utils' -import { useRecoilValue } from 'recoil' +import { atom, useRecoilState, useRecoilValue } from 'recoil' + +// API 요청을 저장할 모듈 레벨 변수 (Hook 외부) +let roofMaterialPromise = null; /** * 마스터 컨트롤러 훅 * @returns @@ -20,10 +23,23 @@ export function useMasterController() { * @returns */ const getRoofMaterialList = async () => { - return await get({ url: '/api/v1/master/getRoofMaterialList' }).then((res) => { - // console.log('🚀🚀 ~ getRoofMaterialList ~ res:', res) - return res - }) + // 1. 이미 진행 중이거나 완료된 Promise가 있으면 그것을 반환 + if (roofMaterialPromise) { + return roofMaterialPromise; + } + + // 2. 처음 호출될 때 Promise를 생성하여 변수에 할당 + roofMaterialPromise = get({ url: '/api/v1/master/getRoofMaterialList' }) + .then((res) => { + return res; + }) + .catch((error) => { + // 에러 발생 시 다음 호출을 위해 초기화 + roofMaterialPromise = null; + throw error; + }); + + return roofMaterialPromise; } /** From 2316e92cfe3bc13698adafb4e529204438f8aa4e Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 20 Jan 2026 15:03:03 +0900 Subject: [PATCH 54/55] =?UTF-8?q?=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=20-?= =?UTF-8?q?=20=ED=8F=AC=ED=95=A8,=20=ED=8C=A9=EC=8A=A4=ED=95=84=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/auth/Join.jsx | 61 +++++++++++++++++++++++++++++++++--- src/locales/ja.json | 4 +-- src/locales/ko.json | 2 +- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index 9fcc09fd..f414cd62 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -33,6 +33,11 @@ export default function Join() { // 가입 신청 유효성 검사 const joinValidation = (formData) => { + + // 전화번호/FAX 정규식 (일본 형식: 0으로 시작, 하이픈 포함) + const telRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/ + + // 판매대리점 정보 - 판매대리점명 const storeQcastNm = formData.get('storeQcastNm') if (!isObjectNotEmpty(storeQcastNm)) { @@ -65,12 +70,34 @@ export default function Join() { return false } + // 판매대리점 정보 - 전화번호 const telNo = formData.get('telNo') if (!isObjectNotEmpty(telNo)) { alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) telNoRef.current.focus() return false + } else if (!telRegex.test(telNo)) { + alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) + telNoRef.current.focus() + return false + } + + // + // // 판매대리점 정보 - 전화번호 + // const telNo = formData.get('telNo') + // if (!isObjectNotEmpty(telNo)) { + // alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) + // telNoRef.current.focus() + // return false + // } + + // 판매대리점 정보 - FAX 번호 + const fax = formData.get('fax') + if (!isObjectNotEmpty(fax)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub1.fax')])) + faxRef.current.focus() + return false } const bizNo = formData.get('bizNo') @@ -122,16 +149,38 @@ export default function Join() { } // 담당자 정보 - 전화번호 + // const userTelNo = formData.get('userTelNo') + // if (!isObjectNotEmpty(userTelNo)) { + // alert(getMessage('common.message.required.data', [getMessage('join.sub2.telNo')])) + // userTelNoRef.current.focus() + // return false + // } + + const userTelNo = formData.get('userTelNo') if (!isObjectNotEmpty(userTelNo)) { - alert(getMessage('common.message.required.data', [getMessage('join.sub2.telNo')])) + alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) userTelNoRef.current.focus() return false + } else if (!telRegex.test(userTelNo)) { + alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) + userTelNoRef.current.focus() + return false + } + + // 담당자 정보 - FAX 번호 + const userFax = formData.get('userFax') + if (!isObjectNotEmpty(userFax)) { + alert(getMessage('common.message.required.data', [getMessage('join.sub2.fax')])) + userFaxRef.current.focus() + return false } return true } + + // 가입 신청 const joinProcess = async (e) => { e.preventDefault() @@ -288,7 +337,8 @@ export default function Join() { name="telNo" className="input-light" maxLength={15} - onChange={inputNumberCheck} + placeholder={getMessage('join.sub1.telNo_placeholder')} + onChange={inputTelNumberCheck} ref={telNoRef} />
@@ -296,7 +346,7 @@ export default function Join() { {/* FAX 번호 */} - {getMessage('join.sub1.fax')} + {getMessage('join.sub1.fax')}*
@@ -398,7 +448,8 @@ export default function Join() { name="userTelNo" className="input-light" maxLength={15} - onChange={inputNumberCheck} + placeholder={getMessage('join.sub1.telNo_placeholder')} + onChange={inputTelNumberCheck} ref={userTelNoRef} />
@@ -406,7 +457,7 @@ export default function Join() { {/* FAX 번호 */} - {getMessage('join.sub2.fax')} + {getMessage('join.sub2.fax')}*
Date: Thu, 22 Jan 2026 17:36:48 +0900 Subject: [PATCH 55/55] =?UTF-8?q?eaveHelpLine=20actualSize=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 49cd1a7c..7bd773f6 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -802,6 +802,10 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const getAddLine = (p1, p2, stroke = '') => { movedLines.push({ index, p1, p2 }) + const dx = Math.abs(p2.x - p1.x); + const dy = Math.abs(p2.y - p1.y); + const isDiagonal = dx > 0.5 && dy > 0.5; // x, y 변화가 모두 있으면 대각선 + //console.log("mergeLines:::::::", mergeLines); const line = new QLine([p1.x, p1.y, p2.x, p2.y], { parentId: roof.id, @@ -818,6 +822,15 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { type: 'eaveHelpLine', isStart: true, pitch: wallLine.attributes.pitch, + actualSize: (isDiagonal) ? calcLineActualSize( + { + x1: p1.x, + y1: p1.y, + x2: p2.x, + y2: p2.y + }, + getDegreeByChon(wallLine.attributes.pitch) + ) : calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }), }, })