diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index e6d7a825..ae26d4d7 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -29,6 +29,8 @@ 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) { const canvas = useRecoilValue(canvasState) @@ -404,6 +406,56 @@ 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.name === 'eaveHelpLine' && obj.roofId === roofBase.id + ); + + const mergedEaveLines = processEaveHelpLines(roofEaveHelpLines); + // 기존 라인 제거 + roofEaveHelpLines.forEach(line => canvas.remove(line)); + + mergedEaveLines.forEach(line => { + const newLine = new QLine([line.x1, line.y1, line.x2, line.y2], { + // 필요한 속성들 유지 + parentId : roofBase.id, + fontSize : 10, + stroke : 'blue', + strokeWidth: 4, + name : 'eaveHelpLine', + lineName : 'eaveHelpLine', + selectable : true, + visible : true, + roofId : roofBase.id, + attributes : { + type: 'eaveHelpLine' + } + // 기타 속성들... + }); + canvas.add(newLine); + }); + + canvas.renderAll(); + + + if (roofBase.lines) { + // Filter out any eaveHelpLines that are already in lines to avoid duplicates + const existingEaveLineIds = new Set(roofBase.lines.map(line => line.id)); + const newEaveLines = mergedEaveLines.filter(line => !existingEaveLineIds.has(line.id)); + roofBase.lines = [...roofBase.lines, ...newEaveLines]; + } else { + roofBase.lines = [...roofEaveHelpLines]; + } + + + if (!roofBase.innerLines) { + roofBase.innerLines = []; + } + + // Add only eaveHelpLines that belong to this roofBase + // const baseEaveHelpLines = roofEaveHelpLines.filter(line => line.roofId === roofBase.id); + // roofBase.innerLines = [...new Set([...roofBase.innerLines, ...baseEaveHelpLines])]; + if (roofBase.separatePolygon.length > 0) { splitPolygonWithSeparate(roofBase.separatePolygon) } else { diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index c422e079..56984fbf 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -437,6 +437,55 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const processedInnerEdges = new Set() + const textElements = {}; + + const coordinateText = (line) => { + // Generate a stable ID for this line + const lineKey = `${line.x1},${line.y1},${line.x2},${line.y2}`; + + // Remove existing text elements for this line + if (textElements[lineKey]) { + textElements[lineKey].forEach(text => { + if (canvas.getObjects().includes(text)) { + canvas.remove(text); + } + }); + } + + // Create start point text + const startText = new fabric.Text(`(${Math.round(line.x1)}, ${Math.round(line.y1)})`, { + left: line.x1 + 5, + top: line.y1 - 20, + fontSize: 10, + fill: 'green', + fontFamily: 'Arial', + selectable: false, + hasControls: false, + hasBorders: false + }); + + // Create end point text + const endText = new fabric.Text(`(${Math.round(line.x2)}, ${Math.round(line.y2)})`, { + left: line.x2 + 5, + top: line.y2 - 20, + fontSize: 10, + fill: 'orange', + fontFamily: 'Arial', + selectable: false, + hasControls: false, + hasBorders: false + }); + + // Add to canvas + canvas.add(startText, endText); + + // Store references + textElements[lineKey] = [startText, endText]; + + // Bring lines to front + canvas.bringToFront(startText); + canvas.bringToFront(endText); + }; // 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다. skeleton.Edges.forEach((edgeResult, index) => { @@ -556,10 +605,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { lineName: (sktLine.attributes.isOuterEdge)?'roofLine': attributes.type, selectable:(!sktLine.attributes.isOuterEdge), visible: (!sktLine.attributes.isOuterEdge), - lineNo: (sktLine.attributes.isOuterEdge)? skIndex:0,//그려지는 외곽라인의 순서를 찾아서... }); - + coordinateText(skeletonLine) canvas.add(skeletonLine); skeletonLine.bringToFront(); existingLines.add(lineKey); // 추가된 라인을 추적 @@ -588,22 +636,22 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { canvas.renderAll(); }); +if(roof.moveUpDown??0 > 0) { // 같은 라인이 없으므로 새 다각형 라인 생성 - //라인 편집 - // let i = 0 - const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId) - let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId) + //라인 편집 + // let i = 0 + const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId) + let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId) + - - roofLineRects.forEach((roofLineRect) => { - canvas.remove(roofLineRect) - canvas.renderAll() - }) - - let helpLines = canvas.getObjects().filter((obj) => obj.lineName === 'helpLine' && obj.roofId === roofId) - helpLines.forEach((helpLine) => { + canvas.remove(roofLineRect) + canvas.renderAll() + }) + + let helpLines = canvas.getObjects().filter((obj) => obj.lineName === 'helpLine' && obj.roofId === roofId) + helpLines.forEach((helpLine) => { canvas.remove(helpLine) canvas.renderAll() }) @@ -637,29 +685,81 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } - // function sortCurrentRoofLines(lines) { // return [...lines].sort((a, b) => { // const aX = a.x1 ?? a.get('x1') // const aY = a.y1 ?? a.get('y1') // const bX = b.x1 ?? b.get('x1') // const bY = b.y1 ?? b.get('y1') - + // if (aX !== bX) return aX - bX // return aY - bY // }) // } - + // 각 라인 집합 정렬 + + // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정 + const alignLineDirection = (sourceLines, targetLines) => { + return sourceLines.map(sourceLine => { + // 가장 가까운 targetLine 찾기 + const nearestTarget = targetLines.reduce((nearest, targetLine) => { + const sourceCenter = { + x: (sourceLine.x1 + sourceLine.x2) / 2, + y: (sourceLine.y1 + sourceLine.y2) / 2 + }; + const targetCenter = { + x: (targetLine.x1 + targetLine.x2) / 2, + y: (targetLine.y1 + targetLine.y2) / 2 + }; + const distance = Math.hypot( + sourceCenter.x - targetCenter.x, + sourceCenter.y - targetCenter.y + ); + + return !nearest || distance < nearest.distance + ? { line: targetLine, distance } + : nearest; + }, null)?.line; + + if (!nearestTarget) return sourceLine; + + // 방향이 반대인지 확인 (벡터 내적을 사용) + const sourceVec = { + x: sourceLine.x2 - sourceLine.x1, + y: sourceLine.y2 - sourceLine.y1 + }; + const targetVec = { + x: nearestTarget.x2 - nearestTarget.x1, + y: nearestTarget.y2 - nearestTarget.y1 + }; + + const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y; + + // 내적이 음수이면 방향이 반대이므로 뒤집기 + if (dotProduct < 0) { + return { + ...sourceLine, + x1: sourceLine.x2, + y1: sourceLine.y2, + x2: sourceLine.x1, + y2: sourceLine.y1 + }; + } + + return sourceLine; + }); + }; + const sortedWallLines = sortCurrentRoofLines(wall.lines); - const sortedCurrentRoofLines = sortCurrentRoofLines(currentRoofLines); + // roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬 + const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines); + const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines); const sortedRoofLines = sortCurrentRoofLines(roofLines); const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines); - - //wall.lines 는 기본 벽 라인 //wall.baseLine은 움직인라인 const movedLines = [] @@ -672,6 +772,23 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const moveLine = sortedWallBaseLines[index] const wallBaseLine = sortedWallBaseLines[index] + console.log('=== Line Coordinates ==='); + console.table({ + 'Point' : ['X', 'Y'], + 'roofLine' : [roofLine.x1, roofLine.y1], + 'currentRoofLine': [currentRoofLine.x1, currentRoofLine.y1], + 'moveLine' : [moveLine.x1, moveLine.y1], + 'wallBaseLine' : [wallBaseLine.x1, wallBaseLine.y1] + }); + console.log('End Points:'); + console.table({ + 'Point' : ['X', 'Y'], + 'roofLine' : [roofLine.x2, roofLine.y2], + 'currentRoofLine': [currentRoofLine.x2, currentRoofLine.y2], + 'moveLine' : [moveLine.x2, moveLine.y2], + 'wallBaseLine' : [wallBaseLine.x2, wallBaseLine.y2] + }); + const origin = moveLine.attributes?.originPoint if (!origin) return @@ -698,184 +815,160 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { fontSize : roof.fontSize, stroke : stroke, strokeWidth: 4, - name : 'helpLine', - lineName : 'helpLine', + name : 'eaveHelpLine', + lineName : 'eaveHelpLine', selectable : true, visible : true, roofId : roofId, attributes : { - type: 'helpLine' + type: 'eaveHelpLine' } }); - - const coordinateText = new fabric.Text(`(${Math.round(line.x1)}, ${Math.round(line.y1)})`, { - left: line.x1 + 5, // 좌표점에서 약간 오른쪽으로 이동 - top: line.y1 - 20, // 좌표점에서 약간 위로 이동 - fontSize: 13, - fill: 'red', - fontFamily: 'Arial', - selectable: true, - lockMovementX: false, - lockMovementY: false, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - name: 'lengthText' - }) - - canvas?.add(coordinateText) - + coordinateText(line) canvas.add(line) canvas.renderAll(); return line } //두 포인트가 변경된 라인인 - if (fullyMoved) { - //반시계방향향 - newPStart = {x:roofLine.x1, y:roofLine.y1} - newPEnd = {x:roofLine.x2, y:roofLine.y2} + if (fullyMoved) { + //반시계방향향 + newPStart = { x: roofLine.x1, y: roofLine.y1 } + newPEnd = { x: roofLine.x2, y: roofLine.y2 } - console.log("moveFully:::::::::::::", wallBaseLine, newPStart, newPEnd) + console.log("moveFully:::::::::::::", wallBaseLine, newPStart, newPEnd) - if(getOrientation(roofLine) === 'vertical'){ + if (getOrientation(roofLine) === 'vertical') { - if(newPEnd.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPStart.y && newPStart.y <= wallBaseLine.y1){ - newPStart.y = wallBaseLine.y1; - getAddLine({x:newPEnd.x, y:wallBaseLine.y1}, {x: wallBaseLine.x1, y:wallBaseLine.y1 }) - } else if(wallBaseLine.y2 <= newPEnd.y && newPEnd.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPStart.y){ - newPEnd.y = wallBaseLine.y2; - getAddLine({x:newPEnd.x, y:wallBaseLine.y2}, {x: wallBaseLine.x2, y:wallBaseLine.y2 }) - } else if(newPStart.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPEnd.y && newPEnd.y <= wallBaseLine.y2){ - newPEnd.y = wallBaseLine.y2; - getAddLine({x:newPEnd.x, y:wallBaseLine.y2}, {x: wallBaseLine.x2, y:wallBaseLine.y2 }) - } else if(wallBaseLine.y1 <= newPStart.y && newPStart.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPEnd.y){ + if (newPEnd.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPStart.y && newPStart.y <= wallBaseLine.y1) { newPStart.y = wallBaseLine.y1; - getAddLine({x:newPEnd.x, y:wallBaseLine.y1}, {x: wallBaseLine.x1, y:wallBaseLine.y1 }) - } else if(wallBaseLine.y2 <= newPEnd.y && newPStart.y <= wallBaseLine.y1 ) { // 위가운데 - newPEnd.y = wallBaseLine.y2; - getAddLine({x:newPEnd.x, y:wallBaseLine.y2}, {x: wallBaseLine.x2, y:wallBaseLine.y2 }) - newPStart.y = wallBaseLine.y1; - getAddLine({x:newPEnd.x, y:wallBaseLine.y1}, {x: wallBaseLine.x1, y:wallBaseLine.y1 }) - } else if(wallBaseLine.y1 <= newPStart.y && newPEnd.y <= wallBaseLine.y2 ) { // 아래가운데 - newPEnd.y = wallBaseLine.y1; - getAddLine({x:newPEnd.x, y:wallBaseLine.y1}, {x: wallBaseLine.x1, y:wallBaseLine.y1 }) - newPStart.y = wallBaseLine.y2; - getAddLine({x:newPStart.x, y:wallBaseLine.y2}, {x: wallBaseLine.x2, y:wallBaseLine.y2 }) - } - - - }else if(getOrientation(roofLine) === 'horizontal') { + getAddLine({ x: newPEnd.x, y: wallBaseLine.y1 }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + } else if (wallBaseLine.y2 <= newPEnd.y && newPEnd.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPStart.y) { + newPEnd.y = wallBaseLine.y2; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y2 }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + } else if (newPStart.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPEnd.y && newPEnd.y <= wallBaseLine.y2) { + newPEnd.y = wallBaseLine.y2; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y2 }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + } else if (wallBaseLine.y1 <= newPStart.y && newPStart.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPEnd.y) { + newPStart.y = wallBaseLine.y1; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y1 }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + } else if (wallBaseLine.y2 <= newPEnd.y && newPStart.y <= wallBaseLine.y1) { // 위가운데 + newPEnd.y = wallBaseLine.y2; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y2 }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + newPStart.y = wallBaseLine.y1; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y1 }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + } else if (wallBaseLine.y1 <= newPStart.y && newPEnd.y <= wallBaseLine.y2) { // 아래가운데 + newPEnd.y = wallBaseLine.y1; + getAddLine({ x: newPEnd.x, y: wallBaseLine.y1 }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + newPStart.y = wallBaseLine.y2; + getAddLine({ x: newPStart.x, y: wallBaseLine.y2 }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + } - if(newPEnd.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPStart.x && newPStart.x <= wallBaseLine.x1){ //위 왼쪽 - newPStart.x = wallBaseLine.x1; - getAddLine({x:wallBaseLine.x1, y:newPEnd.y}, {x:wallBaseLine.x1, y: wallBaseLine.y1}) - } else if(wallBaseLine.x2 <= newPEnd.x && newPEnd.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPStart.x){ //아래오르쪽 - newPEnd.x = wallBaseLine.x2; - getAddLine({x:wallBaseLine.x2, y:newPEnd.y}, {x:wallBaseLine.x2, y: wallBaseLine.y2}) + } else if (getOrientation(roofLine) === 'horizontal') { - } else if(newPStart.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPEnd.x && newPEnd.x <= wallBaseLine.x2){ //위 오른쪽 - newPEnd.x = wallBaseLine.x2; - getAddLine({x:wallBaseLine.x2, y:newPEnd.y}, {x:wallBaseLine.x2, y: wallBaseLine.y2}) - } else if(wallBaseLine.x1 <= newPStart.x && newPStart.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPEnd.x){ //아래 왼쪽 + if (newPEnd.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPStart.x && newPStart.x <= wallBaseLine.x1) { //위 왼쪽 newPStart.x = wallBaseLine.x1; - getAddLine({x:wallBaseLine.x1, y:newPEnd.y}, {x:wallBaseLine.x1, y: wallBaseLine.y1}) + getAddLine({ x: wallBaseLine.x1, y: newPEnd.y }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + } else if (wallBaseLine.x2 <= newPEnd.x && newPEnd.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPStart.x) { //아래오르쪽 + newPEnd.x = wallBaseLine.x2; + getAddLine({ x: wallBaseLine.x2, y: newPEnd.y }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) - } else if(wallBaseLine.x2 <= newPEnd.x && newPEnd.x <= newPStart.x && newPStart.x <= wallBaseLine.x1 ) { // 위가운데 + } else if (newPStart.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPEnd.x && newPEnd.x <= wallBaseLine.x2) { //위 오른쪽 + newPEnd.x = wallBaseLine.x2; + getAddLine({ x: wallBaseLine.x2, y: newPEnd.y }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) - newPEnd.x = wallBaseLine.x2; - getAddLine({x:wallBaseLine.x2, y:newPEnd.y}, {x:wallBaseLine.x2, y: wallBaseLine.y2}) - newPStart.x = wallBaseLine.x1; - getAddLine({x:wallBaseLine.x1, y:newPEnd.y}, {x:wallBaseLine.x1, y: wallBaseLine.y1}) + } else if (wallBaseLine.x1 <= newPStart.x && newPStart.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPEnd.x) { //아래 왼쪽 + newPStart.x = wallBaseLine.x1; + getAddLine({ x: wallBaseLine.x1, y: newPEnd.y }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) - } else if(wallBaseLine.x1 <= newPStart.x &&newPStart.x <= newPEnd.x&&newPEnd.x <= wallBaseLine.x2 ) { // 아래가운데 - newPEnd.x = wallBaseLine.x1; - getAddLine({x:wallBaseLine.x1, y:newPEnd.y}, {x:wallBaseLine.x1, y: wallBaseLine.y1}) - newPStart.x = wallBaseLine.x2; - getAddLine({x:wallBaseLine.x2, y:newPEnd.y}, {x:wallBaseLine.x2, y: wallBaseLine.y2}) + } else if (wallBaseLine.x2 <= newPEnd.x && newPEnd.x <= newPStart.x && newPStart.x <= wallBaseLine.x1) { // 위가운데 + + newPEnd.x = wallBaseLine.x2; + getAddLine({ x: wallBaseLine.x2, y: newPEnd.y }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + newPStart.x = wallBaseLine.x1; + getAddLine({ x: wallBaseLine.x1, y: newPEnd.y }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + + } else if (wallBaseLine.x1 <= newPStart.x && newPStart.x <= newPEnd.x && newPEnd.x <= wallBaseLine.x2) { // 아래가운데 + newPEnd.x = wallBaseLine.x1; + getAddLine({ x: wallBaseLine.x1, y: newPEnd.y }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) + newPStart.x = wallBaseLine.x2; + getAddLine({ x: wallBaseLine.x2, y: newPEnd.y }, { x: wallBaseLine.x2, y: wallBaseLine.y2 }) + } } - } - getAddLine(newPStart, newPEnd) - movedLines.push({ index, newPStart, newPEnd }) - - } - - else if(movedStart) { //end 변경경 - - - newPStart = {x:roofLine.x1, y:roofLine.y1} + getAddLine(newPStart, newPEnd, 'red') + movedLines.push({ index, newPStart, newPEnd }) - if(getOrientation(roofLine) === 'vertical'){ + } else if (movedStart) { //end 변경경 - let isCross = false - if(Math.abs(currentRoofLine.x2 - roofLine.x1) < 0.1 || Math.abs(currentRoofLine.x1 - roofLine.x2) < 0.1){ + + newPStart = { x: roofLine.x1, y: roofLine.y1 } + + if (getOrientation(roofLine) === 'vertical') { + + let isCross = false + if (Math.abs(currentRoofLine.x2 - roofLine.x1) < 0.1 || Math.abs(currentRoofLine.x1 - roofLine.x2) < 0.1) { isCross = true; - } + } - newPEnd = {x:roofLine.x1, y:(isCross)? currentRoofLine.y2:origin.y1} + newPEnd = { x: roofLine.x1, y: (isCross) ? currentRoofLine.y1 : origin.y1 } - }else if(getOrientation(roofLine) === 'horizontal') { + } else if (getOrientation(roofLine) === 'horizontal') { - let isCross = false - if(Math.abs(currentRoofLine.y1 - roofLine.y2) < 0.1 || Math.abs(currentRoofLine.y2 - roofLine.y1) < 0.1){ + let isCross = false + if (Math.abs(currentRoofLine.y1 - roofLine.y2) < 0.1 || Math.abs(currentRoofLine.y2 - roofLine.y1) < 0.1) { isCross = true; + } + + newPEnd = { x: (isCross) ? currentRoofLine.x1 : origin.x1, y: roofLine.y1 } //수직라인 접점까지지 + } - newPEnd = {x:(isCross)? currentRoofLine.x2:origin.x1, y:roofLine.y1} //수직라인 접점까지지 - + movedLines.push({ index, newPStart, newPEnd }) + console.log("moveStart:::::::::::::", origin, newPStart, newPEnd) + getAddLine(newPStart, newPEnd, 'red') + + + } else if (movedEnd) { //start변경 + + //반시계방향 + newPStart = { x: roofLine.x2, y: roofLine.y2 } + + if (getOrientation(roofLine) === 'vertical') { + + let isCross = false + if (Math.abs(currentRoofLine.x2 - roofLine.x1) < 0.1 || Math.abs(currentRoofLine.x1 - roofLine.x2) < 0.1) { + isCross = true; + } + newPEnd = { x: roofLine.x2, y: (isCross) ? currentRoofLine.y2 : origin.y2 } //수직라인 접점까지지 + + } else if (getOrientation(roofLine) === 'horizontal') { + + let isCross = false + if (Math.abs(currentRoofLine.y2 - roofLine.y1) < 0.1 || Math.abs(currentRoofLine.y1 - roofLine.y2) < 0.1) { + isCross = true; + } + + newPEnd = { x: (isCross) ? currentRoofLine.x2 : origin.x2, y: roofLine.y2 } //수직라인 접점까지지 + + } + console.log("movedEnd:::::::::::::", origin, newPStart, newPEnd) + getAddLine(newPStart, newPEnd, 'orange') + movedLines.push({ index, newPStart, newPEnd }) + } - movedLines.push({ index, newPStart, newPEnd }) - console.log("moveStart:::::::::::::", origin, newPStart, newPEnd) - getAddLine(newPStart, newPEnd) + canvas.renderAll() - }else if(movedEnd) { //start변경 - - //반시계방향 - newPStart = {x:roofLine.x2, y:roofLine.y2} - - if(getOrientation(roofLine) === 'vertical'){ - - let isCross = false - if(Math.abs(currentRoofLine.x2 - roofLine.x1) < 0.1 || Math.abs(currentRoofLine.x1 - roofLine.x2) < 0.1){ - isCross = true; - } - newPEnd = {x:roofLine.x2, y:(isCross)? currentRoofLine.y1:origin.y2} //수직라인 접점까지지 - - }else if(getOrientation(roofLine) === 'horizontal') { - - let isCross = false - if(Math.abs(currentRoofLine.y2 - roofLine.y1) < 0.1 || Math.abs(currentRoofLine.y1 - roofLine.y2) < 0.1){ - isCross = true; - } - - newPEnd = {x:(isCross)? currentRoofLine.x1:origin.x2, y:roofLine.y2} //수직라인 접점까지지 - - } - console.log("movedEnd:::::::::::::", origin, newPStart, newPEnd) - getAddLine(newPStart, newPEnd) - movedLines.push({ index, newPStart, newPEnd }) - - } - - canvas.renderAll() - - - - - - - -}); + }); //polygon 만들기 -console.log("innerLines:::::", innerLines) -console.log("movedLines", movedLines) - + console.log("innerLines:::::", innerLines) + console.log("movedLines", movedLines) +} // --- 사용 예시 --- // const polygons = findConnectedLines(movedLines, innerLines, canvas, roofId, roof); // console.log("polygon", polygons); @@ -886,18 +979,18 @@ console.log("movedLines", movedLines) let p1,p2,p3, p4 let idx = 0; let isMoveLine = false; - + if (wallLine.startPoint.x !== wallLine.x1) wallLine.startPoint.x = wallLine.x1 if (wallLine.startPoint.y !== wallLine.y1) wallLine.startPoint.y = wallLine.y1 if (wallLine.endPoint.x !== wallLine.x2) wallLine.endPoint.x = wallLine.x2 if (wallLine.endPoint.y !== wallLine.y2) wallLine.endPoint.y = wallLine.y2 - + const wallLineStartPoint = {x:wallLine.x1, y:wallLine.y1} const wallLineEndPoint = {x:wallLine.x2, y:wallLine.y2} const moveLine = wall.baseLines[index] //이동한 wall 존재여부 (초기 wall line = base line) if(index === 2){ - + } // // 사용자가 라인을 드래그하기 시작할 때 @@ -910,13 +1003,13 @@ console.log("movedLines", movedLines) // .map((line, index) => ({ index, line })) // .filter(({ line }) => line.attributes.isUserMoved) - + // Then in your code: if (!moveLine?.attributes?.originPoint) return const { originPoint } = moveLine.attributes - const moved = + const moved = (Math.abs(moveLine.x1 - originPoint.x1) > 0.1 || Math.abs(moveLine.y1 - originPoint.y1) > 0.1 )||( Math.abs(moveLine.x2 - originPoint.x2) > 0.1 || @@ -938,7 +1031,7 @@ console.log("movedLines", movedLines) const getPolygonOrientation = baseLines => { if (!baseLines?.length) return 0 - + const area2 = baseLines.reduce((sum, line) => { const x1 = line.get('x1') const y1 = line.get('y1') @@ -946,10 +1039,10 @@ console.log("movedLines", movedLines) const y2 = line.get('y2') return sum + (x2 - x1) * (y2 + y1) // shoelace 변형 }, 0) - + return Math.sign(area2) // +1: CCW, -1: CW, 0: 불명 } - + const lineOrientation = (line, polygonOrientation) => { const x1 = line.get('x1') const y1 = line.get('y1') @@ -978,7 +1071,7 @@ console.log("movedLines", movedLines) }) // 기존 로직도 이어서 실행 console.log("moveLine", movedLines) - + movedLines.forEach(({ index, moveLine, wallLine }) => { console.log(`사용자가 움직인 선 index: ${index}, wallLineId: ${wallLine.id}`) @@ -996,7 +1089,7 @@ console.log("movedLines", movedLines) same(a.x2, a.y2, b.x2, b.y2) ) } - + const angleBetween = a => b => { const va = { x: a.x2 - a.x1, y: a.y2 - a.y1 } const vb = { x: b.x2 - b.x1, y: b.y2 - b.y1 } @@ -1004,7 +1097,7 @@ console.log("movedLines", movedLines) const mag = Math.hypot(va.x, va.y) * Math.hypot(vb.x, vb.y) return Math.acos(Math.min(Math.max(dot / mag, -1), 1)) * 180 / Math.PI } - + const rightAngles = [] movedLines.forEach((a, i) => { movedLines.slice(i + 1).forEach(b => { @@ -1024,14 +1117,14 @@ console.log("movedLines", movedLines) canvas.add(testLine) canvas.renderAll() } - - + + } // movedLines => [{ line: moveLineObj, index }, ...] : 이동된 것만 담김 if (moved) { - + } @@ -3987,3 +4080,49 @@ function findConnectedLines(aLines, bLines, canvas, roofId, roof) { return results; } +export const processEaveHelpLines = (lines) => { + if (!lines || lines.length === 0) return []; + + // 수직/수평 라인 분류 + const verticalLines = lines.filter(line => line.x1 === line.x2); + const horizontalLines = lines.filter(line => line.y1 === line.y2); + + // 라인 정렬 및 병합 + const mergedVertical = mergeLines(verticalLines, 'vertical'); + const mergedHorizontal = mergeLines(horizontalLines, 'horizontal'); + + return [...mergedVertical, ...mergedHorizontal]; +}; + +const mergeLines = (lines, direction) => { + if (lines.length < 2) return lines; + + // 방향에 따라 정렬 + lines.sort((a, b) => { + const aPos = direction === 'vertical' ? a.y1 : a.x1; + const bPos = direction === 'vertical' ? b.y1 : b.x1; + return aPos - bPos; + }); + + const merged = []; + let current = { ...lines[0] }; + + for (let i = 1; i < lines.length; i++) { + const line = lines[i]; + const isConnected = direction === 'vertical' + ? current.y2 >= line.y1 - 1 + : current.x2 >= line.x1 - 1; + + if (isConnected) { + // 라인 병합 + current.y2 = Math.max(current.y2, line.y2); + current.x2 = direction === 'vertical' ? current.x1 : current.x2; + } else { + merged.push(current); + current = { ...line }; + } + } + merged.push(current); + return merged; +}; +