From 77c0fe7edc6c9ce848526f7081f7fec6c2108216 Mon Sep 17 00:00:00 2001 From: Cha Date: Mon, 24 Nov 2025 00:45:08 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=91=EC=B6=94=EA=B0=80=EC=9E=91=EC=97=856?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 308 +++++++++++++++++++++++++++++++++---- 1 file changed, 276 insertions(+), 32 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index edc5ffea..25e1b9c3 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -610,12 +610,18 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { 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(helpLine) + canvas.renderAll() + }) function sortCurrentRoofLines(lines) { return [...lines].sort((a, b) => { @@ -666,70 +672,187 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { //wall.lines 는 기본 벽 라인 //wall.baseLine은 움직인라인 const movedLines = [] + sortedWallLines.forEach((wallLine, index) => { + const roofLine = sortedRoofLines[index]; const currentRoofLine = sortedCurrentRoofLines[index]; const moveLine = sortedWallBaseLines[index] - + const wallBaseLine = sortedWallBaseLines[index] + const origin = moveLine.attributes?.originPoint if (!origin) return - const movedStart = - Math.abs(moveLine.x1 - origin.x1) > EPSILON || - Math.abs(moveLine.y1 - origin.y1) > EPSILON + if(isSamePoint(moveLine, wallLine)) { + return false + } -const movedEnd = - Math.abs(moveLine.x2 - origin.x2) > EPSILON || - Math.abs(moveLine.y2 - origin.y2) > EPSILON + const movedStart = Math.abs(moveLine.x1 - origin.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON + const movedEnd = Math.abs(moveLine.x2 - origin.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON -const fullyMoved = movedStart && movedEnd + const fullyMoved = movedStart && movedEnd + + +//반시계 방향 +let newPStart //= {x:roofLine.x1, y:roofLine.y1} +let newPEnd //= {x:movedLines.x2, y:movedLines.y2} + +//현재 roof는 무조건 시계방향 + +//두 포인트가 변경된 라인인 if (fullyMoved) { - movedLines.push({ index, moveLine }) + //반시계방향향 + newPStart = {x:roofLine.x1, y:roofLine.y1} + newPEnd = {x:roofLine.x2, y:roofLine.y2} + - const p1roofLine = findClosestRoofLine(roofLine.startPoint,sortedRoofLines) - const p2roofLine = findClosestRoofLine(roofLine.endPoint,sortedRoofLines) - const p1currentRoofLine = findClosestRoofLine(currentRoofLine.startPoint,sortedCurrentRoofLines) - const p2currentRoofLine = findClosestRoofLine(currentRoofLine.endPoint,sortedCurrentRoofLines) - const p1moveLine = findClosestRoofLine(moveLine.startPoint,sortedWallBaseLines) - const p2moveLine = findClosestRoofLine(moveLine.endPoint,sortedWallBaseLines) + console.log("moveFully:::::::::::::", wallBaseLine, newPStart, newPEnd) - console.log('::::::',p1roofLine, p2roofLine, p1currentRoofLine, p2currentRoofLine, p1moveLine, p2moveLine) + if(getOrientation(roofLine) === 'vertical'){ - let testLine = new QLine([moveLine.x1, moveLine.y1, moveLine.x2, moveLine.y2], { + if(newPEnd.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPStart.y && newPStart.y <= wallBaseLine.y1){ + newPStart.y = wallBaseLine.y1; + } else if(wallBaseLine.y2 <= newPEnd.y && newPEnd.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPStart.y){ + newPEnd.y = wallBaseLine.y2; + } else if(newPStart.y <= wallBaseLine.y1 && wallBaseLine.y1 <= newPEnd.y && newPEnd.y <= wallBaseLine.y2){ + newPEnd.y = wallBaseLine.y2; + } else if(wallBaseLine.y1 <= newPStart.y && newPStart.y <= wallBaseLine.y2 && wallBaseLine.y2 <= newPEnd.y){ + newPStart.y = wallBaseLine.y1; + } + + + }else if(getOrientation(roofLine) === 'horizontal') { + + + if(newPEnd.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPStart.x && newPStart.x <= wallBaseLine.x1){ + newPStart.x = wallBaseLine.x1; + } else if(wallBaseLine.x2 <= newPEnd.x && newPEnd.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPStart.x){ + newPEnd.x = wallBaseLine.x2; + } else if(newPStart.x <= wallBaseLine.x1 && wallBaseLine.x1 <= newPEnd.x && newPEnd.x <= wallBaseLine.x2){ + newPEnd.x = wallBaseLine.x2; + } else if(wallBaseLine.x1 <= newPStart.x && newPStart.x <= wallBaseLine.x2 && wallBaseLine.x2 <= newPEnd.x){ + newPStart.x = wallBaseLine.x1; + } + + + } + + movedLines.push({ index, newPStart, newPEnd }) + let testLine = new QLine([newPStart.x, newPStart.y, newPEnd.x, newPEnd.y], { + stroke: 'red', + strokeWidth: 10, + property: 'normal', + fontSize: 14, + lineName: 'helpLine', + roofId: roofId, + parentId: roof.id, + }); + canvas.add(testLine) + + } + + else if(movedStart) { //end 변경경 + + + 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} + + }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){ + isCross = true; + } + + newPEnd = {x:(isCross)? currentRoofLine.x2:origin.x1, y:roofLine.y1} //수직라인 접점까지지 + + } + + movedLines.push({ index, newPStart, newPEnd }) + console.log("moveStart:::::::::::::", origin, newPStart, newPEnd) + + let testLine = new QLine([newPStart.x, newPStart.y, newPEnd.x, newPEnd.y], { stroke: 'yellow', strokeWidth: 10, property: 'normal', fontSize: 14, + lineName: 'helpLine', + roofId: roofId, + parentId: roof.id, }); - let testLine2 = new QLine([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], { - stroke: 'red', - strokeWidth: 10, - property: 'normal', - fontSize: 14, - }); - let testLine3 = new QLine([currentRoofLine.x1, currentRoofLine.y1, currentRoofLine.x2, currentRoofLine.y2], { + canvas.add(testLine) + + + }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) + let testLine = new QLine([newPStart.x, newPStart.y, newPEnd.x, newPEnd.y], { stroke: 'orange', strokeWidth: 10, property: 'normal', fontSize: 14, + lineName: 'helpLine', + roofId:roofId, + parentId: roof.id, + }); canvas.add(testLine) - canvas.add(testLine2) - canvas.add(testLine3) + movedLines.push({ index, newPStart, newPEnd }) - canvas.renderAll() } - - - + + canvas.renderAll() + + + + }); + +//polygon 만들기 +console.log("innerLines:::::", innerLines) +console.log("movedLines", movedLines) + +// --- 사용 예시 --- +const polygons = findConnectedLines(movedLines, innerLines, canvas, roofId, roof); +console.log("polygon", polygons); +canvas.renderAll return innerLines; } /* @@ -799,6 +922,7 @@ const fullyMoved = movedStart && movedEnd return Math.sign(area2) // +1: CCW, -1: CW, 0: 불명 } + const lineOrientation = (line, polygonOrientation) => { const x1 = line.get('x1') const y1 = line.get('y1') @@ -3716,3 +3840,123 @@ const getOrientation = (line, eps = 0.1) => { if (dx < eps && dy < eps) return 'point' return 'diagonal' } + +/** + * 두 선분이 교차하는지 확인하는 헬퍼 함수 + * (끝점이 닿아있는 경우도 교차로 간주) + */ +function checkIntersection(p1, p2, p3, p4) { + // CCW (Counter Clockwise) 알고리즘을 이용한 교차 판별 + function ccw(a, b, c) { + const val = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + if (val < 0) return -1; + if (val > 0) return 1; + return 0; + } + + const abc = ccw(p1, p2, p3); + const abd = ccw(p1, p2, p4); + const cda = ccw(p3, p4, p1); + const cdb = ccw(p3, p4, p2); + + // 두 선분이 일직선 상에 있을 때 (겹치는지 확인) + if (abc === 0 && abd === 0) { + // x축, y축 순서대로 정렬하여 겹침 여부 확인 + if (p1.x > p2.x || (p1.x === p2.x && p1.y > p2.y)) [p1, p2] = [p2, p1]; + if (p3.x > p4.x || (p3.x === p4.x && p3.y > p4.y)) [p3, p4] = [p4, p3]; + return p2.x >= p3.x && p2.y >= p3.y && p4.x >= p1.x && p4.y >= p1.y; + } + + return abc * abd <= 0 && cda * cdb <= 0; +} + +/** +* aLine의 좌표를 추출하는 함수 +*/ +function getACoords(line) { + return { + start: { x: line.newPStart.x, y: line.newPStart.y }, + end: { x: line.newPEnd.x, y: line.newPEnd.y } + }; +} + +/** +* bLine의 좌표를 추출하는 함수 +* (left, top을 시작점으로 보고 width, height를 더해 끝점을 계산) +*/ +function getBCoords(line) { + // QLine 데이터 구조상 left/top이 시작점, width/height가 델타값으로 가정 + return { + start: { x: line.left, y: line.top }, + end: { x: line.left + line.width, y: line.top + line.height } + }; +} + +/** +* 메인 로직 함수 +* 1. aLines 순회 +* 2. aLine과 교차하는 bLines 찾기 (Level 1) +* 3. 찾은 bLine과 교차하는 또 다른 bLines 찾기 (Level 2) +*/ +function findConnectedLines(aLines, bLines, canvas, roofId, roof) { + const results = []; + + aLines.forEach(aLine => { + const aCoords = getACoords(aLine); + const intersections = []; + + // 1단계: aLine과 교차하는 bLines 찾기 + bLines.forEach(bLine1 => { + const bCoords1 = getBCoords(bLine1); + + if (checkIntersection(aCoords.start, aCoords.end, bCoords1.start, bCoords1.end)) { + + // 2단계: 위에서 찾은 bLine1과 교차하는 다른 bLines 찾기 + const connectedToB1 = []; + bLines.forEach(bLine2 => { + // 자기 자신은 제외 + if (bLine1 === bLine2) return; + + const bCoords2 = getBCoords(bLine2); + if (checkIntersection(bCoords1.start, bCoords1.end, bCoords2.start, bCoords2.end)) { + connectedToB1.push(bLine2); + + let testLine = new QLine([bLine2.x1, bLine2.y1, bLine2.x2, bLine2.y2], { + stroke: 'orange', + strokeWidth: 10, + property: 'normal', + fontSize: 14, + lineName: 'helpLine', + roofId:roofId, + parentId: roof.id, + + }); + canvas.add(testLine) + } + + + }); + + intersections.push({ + targetBLine: bLine1, // aLine과 만난 녀석 + connectedBLines: connectedToB1 // 그 녀석과 만난 다른 bLines + }); + + + } + }); + + // 결과가 있는 경우에만 저장 (필요에 따라 조건 제거 가능) + if (intersections.length > 0) { + results.push({ + sourceALine: aLine, + intersections: intersections + }); + + + } + }); + + return results; +} +