diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 8b1f7dc8..51c16c1a 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -370,7 +370,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla }} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: true //(index !== 0), }} /> diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 7f7ffcd7..95c5cb7b 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -376,8 +376,6 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { roof.innerLines = roof.innerLines || []; roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode) - - // 캔버스에 스켈레톤 상태 저장 if (!canvas.skeletonStates) { canvas.skeletonStates = {} @@ -387,7 +385,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { canvas.skeletonLines = []; canvas.skeletonLines.push(...roof.innerLines) roof.skeletonLines = canvas.skeletonLines; - canvas.renderAll() + const cleanSkeleton = { Edges: skeleton.Edges.map(edge => ({ X1: edge.Edge.Begin.X, @@ -436,10 +434,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { skeleton.Edges.forEach((edgeResult, index) => { - canvas.skeletonLines = []; + processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines); - - }); // 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다. @@ -504,8 +500,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const innerLines = []; const existingLines = new Set(); // 이미 추가된 라인을 추적하기 위한 Set - console.log("length::::::::", skeletonLines.length); - skeletonLines.forEach((line, index) => { + + skeletonLines.forEach(line => { const { p1, p2, attributes, lineStyle } = line; // 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교) @@ -515,11 +511,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { ].sort().join('|'); // 이미 추가된 라인인지 확인 - // if (existingLines.has(lineKey)) { - // return; // 이미 있는 라인이면 스킵 - // } - - + if (existingLines.has(lineKey)) { + return; // 이미 있는 라인이면 스킵 + } const direction = getLineDirection( { x: line.p1.x, y: line.p1.y }, @@ -540,22 +534,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { roofId: roofId, }); - console.log('Processing line:', { - p1: {x: p1.x, y: p1.y}, - p2: {x: p2.x, y: p2.y}, - attributes, - lineName: (line.attributes.isOuterEdge) ? 'outerLine' : attributes.type - }); - - console.log('innerLines', innerLine.lineName); - console.log("index::::",index) //skeleton 라인에서 처마선은 삭제 - if(innerLine.lineName !== 'outerLine'){ canvas.add(innerLine); innerLine.bringToFront(); - //existingLines.add(lineKey); // 추가된 라인을 추적 - + existingLines.add(lineKey); // 추가된 라인을 추적 }else{ @@ -692,7 +675,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { fontSize: 13, fill: 'red', fontFamily: 'Arial', - textBaseline: 'alphabetic', // 올바른 값으로 설정 selectable: true, lockMovementX: false, lockMovementY: false, @@ -701,7 +683,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { lockScalingY: true, name: 'lengthText' }) -//좌표점(임시) + canvas?.add(coordinateText) } innerLines.push(innerLine) @@ -729,10 +711,10 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line) ); - // if(!outerLine) { - // outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points); - // console.log('Has matching line:', outerLine); - // } + if(!outerLine) { + outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points); + console.log('Has matching line:', outerLine); + } let pitch = outerLine?.attributes?.pitch??0 @@ -756,10 +738,6 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { } let eavesLines = [] - // 이 다각형이 roof.lines와 일치하는 변을 가지고 있는지 확인 - - const isolatedPolygons = []; - for (let i = 0; i < polygonPoints.length; i++) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; @@ -767,28 +745,11 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { // 지붕 경계선과 교차 확인 및 클리핑 const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine); - + console.log('clipped line', clippedLine.p1, clippedLine.p2); const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge]) - - //현이동에 의해 스켈레톤 라인이 내부에서 끝난경우 roof.lines까지 수평은 수직, 수직은 수평되게 설정 - - // 다각형이 roof.lines와 일치하는 변이 하나도 없는 경우에만 확장 - // 이 다각형이 roof.lines와 일치하는 변을 가지고 있는지 확인 - // 다각형의 모든 변이 roof.lines와 일치하지 않는 경우에만 확장 - //const extendLine = extendLineToRoofBoundary(clippedLine, roof, convertedPolygon) - - - const isIsolated = !convertedPolygon.some(pp => - roof.points.some(rp => - Math.abs(pp.x - rp.x) < 0.5 && Math.abs(pp.y - rp.y) < 0.5 - )); - //console.log("skeletonLines::::",skeletonLines); addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', 'red', 5, pitch, isOuterLine); - + // } } - - //그려진 - } @@ -899,53 +860,6 @@ function isOuterEdge(p1, p2, edges) { }); } -function isOuterRoofLine(p1, p2, lines) { - const tolerance = 0.5; - let foundLine = null; - let isForward = false; - let isBackward = false; - - for (const line of lines) { - console.log("lines of line::::", line.startPoint, line.endPoint); - const lineStart = { x: line.startPoint.x, y: line.startPoint.y }; - const lineEnd = { x: line.endPoint.x, y: line.endPoint.y }; - - - let p1X = Math.abs(lineStart.x - p1.x) < tolerance && Math.abs(lineEnd.x - p2.x) < tolerance - let p2X = Math.abs(lineEnd.x - p1.x) < tolerance && Math.abs(lineStart.x - p2.x) < tolerance - let p1Y = Math.abs(lineStart.y - p1.y) < tolerance && Math.abs(lineEnd.y - p2.y) < tolerance - let p2Y = Math.abs(lineEnd.y - p2.y) < tolerance && Math.abs(lineStart.y - p2.y) < tolerance - - if (p1X || p2X) { - foundLine = line; - p1.y = p1X? lineStart.y : lineEnd.y - p2.y = p2X? lineEnd.y : lineStart.y - break; // 매칭되는 라인을 찾으면 루프 종료 - }else if(p1Y || p2Y) { - foundLine = line; - p1.x = p1Y? lineStart.x : lineEnd.x - p2.x = p2Y? lineEnd.x : lineStart.x - break; // 매칭되는 라인을 찾으면 루프 종료 - } - } - - if (foundLine) { - return { - result: true, - line: foundLine, - p1: p1, - p2: p2 - }; - } - - return { - result: false, - line: null, - pX: false, - p2: false, - }; -} - /** * 스켈레톤 라인 배열에 새로운 라인을 추가합니다. (중복 방지) * @param id @@ -1708,27 +1622,23 @@ function calculateSlope(p1, p2) { return (p2.y - p1.y) / (p2.x - p1.x); } -// Helper function to calculate slope of a line -function getSlope(line) { - const dx = line.endPoint.x - line.startPoint.x; - // Avoid division by zero for vertical lines - return dx === 0 ? Infinity : (line.endPoint.y - line.startPoint.y) / dx; -} - -// Check if two lines are parallel -function areLinesParallel(line1, line2) { - const slope1 = getSlope(line1); - const slope2 = getSlope(line2); - - // Both lines are vertical - if (slope1 === Infinity && slope2 === Infinity) return true; - - // Check if slopes are approximately equal - const epsilon = 0.0001; - return Math.abs(slope1 - slope2) < epsilon; -} - +// 두 직선이 평행한지 확인 +// function areLinesParallel(slope1, slope2) { +// // 두 직선 모두 수직선인 경우 +// if (slope1 === Infinity && slope2 === Infinity) return true; +// +// // 기울기의 차이가 매우 작으면 평행한 것으로 간주 +// const epsilon = 0.0001; +// return Math.abs(slope1 - slope2) < epsilon; +// } +// 두 선분이 동일한지 확인 +// function areSameLine(p1, p2, p3, p4) { +// return ( +// (isSamePoint(p1, p3) && isSamePoint(p2, p4)) || +// (isSamePoint(p1, p4) && isSamePoint(p2, p3)) +// ); +// } /** * Helper function to find the polygon containing the given line */ @@ -1881,59 +1791,70 @@ function isPointInsidePolygon2(point, roofLines) { return inside; } - -/** - * 점이 다각형 내부에 있는지 확인합니다 (Ray Casting 알고리즘 사용). - * @param {Object} point - 확인할 점 {x, y} - * @param {Array} polygonLines - 다각형을 구성하는 선분들의 배열 - * @returns {boolean} 점이 다각형 내부에 있으면 true - */ -function isPointInsidePolygon(point, polygonLines) { - if (!polygonLines || polygonLines.length < 3) { - return false; +function isPointInsidePolygon(point, roofLines) { + // 1. 먼저 경계선 위에 있는지 확인 (방향 무관) + if (isOnBoundaryDirectionIndependent(point, roofLines)) { + return true; } - let inside = false; + // 2. 내부/외부 판단 (기존 알고리즘) + let winding = 0; const x = point.x; const y = point.y; - const n = polygonLines.length; - // 경계 박스(bounding box) 체크로 빠르게 필터링 - let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; - for (const line of polygonLines) { - minX = Math.min(minX, line.x1, line.x2); - maxX = Math.max(maxX, line.x1, line.x2); - minY = Math.min(minY, line.y1, line.y2); - maxY = Math.max(maxY, line.y1, line.y2); + for (let i = 0; i < roofLines.length; i++) { + const line = roofLines[i]; + const x1 = line.x1, y1 = line.y1; + const x2 = line.x2, y2 = line.y2; + + if (y1 <= y) { + if (y2 > y) { + const orientation = (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1); + if (orientation > 0) winding++; + } + } else { + if (y2 <= y) { + const orientation = (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1); + if (orientation < 0) winding--; + } + } } - // 점이 경계 박스 밖에 있으면 바로 false 반환 - if (x < minX || x > maxX || y < minY || y > maxY) { + return winding !== 0; +} + +// 방향에 무관한 경계선 검사 +function isOnBoundaryDirectionIndependent(point, roofLines) { + const tolerance = 1e-10; + + for (const line of roofLines) { + if (isPointOnLineSegmentDirectionIndependent(point, line, tolerance)) { + return true; + } + } + return false; +} + +// 핵심: 방향에 무관한 선분 위 점 검사 +function isPointOnLineSegmentDirectionIndependent(point, line, tolerance) { + const x = point.x, y = point.y; + const x1 = line.x1, y1 = line.y1; + const x2 = line.x2, y2 = line.y2; + + // 방향에 무관하게 경계 상자 체크 + const minX = Math.min(x1, x2); + const maxX = Math.max(x1, x2); + const minY = Math.min(y1, y2); + const maxY = Math.max(y1, y2); + + if (x < minX - tolerance || x > maxX + tolerance || + y < minY - tolerance || y > maxY + tolerance) { return false; } - // Ray Casting 알고리즘 - for (let i = 0, j = n - 1; i < n; j = i++) { - const xi = polygonLines[i].x1; - const yi = polygonLines[i].y1; - const xj = polygonLines[j].x1; - const yj = polygonLines[j].y1; - - // 점이 정점 위에 있는 경우 - if ((xi === x && yi === y) || (xj === x && yj === y)) { - return true; - } - - // 수평선과 교차하는지 확인 - const intersect = ((yi > y) !== (yj > y)) && - (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) { - inside = !inside; - } - } - - return inside; + // 외적을 이용한 직선 위 판단 (방향 무관) + const cross = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1); + return Math.abs(cross) < tolerance; } /** @@ -2648,454 +2569,4 @@ function hasIntersectionWithOtherLines(point, skeletonLines, currentLine, tolera // 1개 이상의 다른 라인과 연결되어 있으면 교점으로 간주 return connectionCount >= 1; -} - -/** - * 대각선의 양 끝점을 roof.lines의 가장 가까운 접점까지 확장합니다. - * @param {Object} p1 - 대각선의 시작점 {x, y} - * @param {Object} p2 - 대각선의 끝점 {x, y} - * @param {Array} roofLines - 지붕 경계선 배열 - * @returns {Object|null} 확장된 라인 {p1: {x, y}, p2: {x, y}} 또는 null - */ -function extendDiagonalToRoofBoundary(p1, p2, roofLines) { - if (!roofLines || roofLines.length === 0) { - return null; - } - - const tolerance = 0.5; - const extendedLine = { - p1: { ...p1 }, - p2: { ...p2 } - }; - - // p1이 roof.lines 위에 있는지 확인 - const p1OnBoundary = roofLines.some(line => { - return isPointOnLineSegment(p1, - { x: line.x1, y: line.y1 }, - { x: line.x2, y: line.y2 }, - tolerance - ); - }); - - // p2가 roof.lines 위에 있는지 확인 - const p2OnBoundary = roofLines.some(line => { - return isPointOnLineSegment(p2, - { x: line.x1, y: line.y1 }, - { x: line.x2, y: line.y2 }, - tolerance - ); - }); - - // p1을 확장해야 하는 경우 - if (!p1OnBoundary) { - const extendedP1 = findClosestBoundaryPoint(p1, p2, roofLines, 'backward'); - if (extendedP1) { - extendedLine.p1 = extendedP1; - } - } - - // p2를 확장해야 하는 경우 - if (!p2OnBoundary) { - const extendedP2 = findClosestBoundaryPoint(p2, p1, roofLines, 'forward'); - if (extendedP2) { - extendedLine.p2 = extendedP2; - } - } - - return extendedLine; -} - -/** - * 점이 선분 위에 있는지 확인합니다 (허용 오차 포함). - * @param {Object} point - 확인할 점 {x, y} - * @param {Object} lineStart - 선분 시작점 {x, y} - * @param {Object} lineEnd - 선분 끝점 {x, y} - * @param {number} tolerance - 허용 오차 - * @returns {boolean} 선분 위에 있으면 true - */ -function isPointOnLineSegment(point, lineStart, lineEnd, tolerance = 0.5) { - const dist = Math.sqrt( - Math.pow(lineEnd.x - lineStart.x, 2) + - Math.pow(lineEnd.y - lineStart.y, 2) - ); - - const dist1 = Math.sqrt( - Math.pow(point.x - lineStart.x, 2) + - Math.pow(point.y - lineStart.y, 2) - ); - - const dist2 = Math.sqrt( - Math.pow(point.x - lineEnd.x, 2) + - Math.pow(point.y - lineEnd.y, 2) - ); - - return Math.abs(dist - (dist1 + dist2)) < tolerance; -} - -/** - * 한 점에서 다른 점 방향으로 연장하여 가장 가까운 roof.lines 교차점을 찾습니다. - * @param {Object} fromPoint - 연장할 시작점 - * @param {Object} directionPoint - 방향을 결정하는 점 - * @param {Array} roofLines - 지붕 경계선 배열 - * @param {string} direction - 'forward' 또는 'backward' - * @returns {Object|null} 교차점 {x, y} 또는 null - */ -function findClosestBoundaryPoint(fromPoint, directionPoint, roofLines, direction = 'forward') { - // 방향 벡터 계산 - const dx = directionPoint.x - fromPoint.x; - const dy = directionPoint.y - fromPoint.y; - const length = Math.sqrt(dx * dx + dy * dy); - - if (length === 0) return null; - - // 정규화된 방향 벡터 - const dirVec = { - x: dx / length, - y: dy / length - }; - - // backward 방향인 경우 벡터를 반대로 - if (direction === 'backward') { - dirVec.x = -dirVec.x; - dirVec.y = -dirVec.y; - } - - let closestIntersection = null; - let minDistance = Infinity; - - // 모든 roof.lines와의 교차점 찾기 - for (const line of roofLines) { - const lineP1 = { x: line.x1, y: line.y1 }; - const lineP2 = { x: line.x2, y: line.y2 }; - - // 무한 직선과의 교차점 계산 - const intersection = getRayIntersectionWithSegment( - fromPoint, - dirVec, - lineP1, - lineP2 - ); - - if (intersection && intersection.t > 0.1) { // 약간의 여유를 둠 - const distance = Math.sqrt( - Math.pow(intersection.point.x - fromPoint.x, 2) + - Math.pow(intersection.point.y - fromPoint.y, 2) - ); - - if (distance < minDistance) { - minDistance = distance; - closestIntersection = intersection.point; - } - } - } - - return closestIntersection; -} - - -/** - * 다각형이 roof.lines와 일치하는 변(edge)을 하나 이상 가지고 있는지 확인합니다. - * @param {Array} polygonPoints - 다각형의 점들 배열 [{x, y}, ...] - * @param {Array} roofLines - 지붕 경계선 배열 - * @returns {boolean} 일치하는 변이 하나라도 있으면 true, 하나도 없으면 false - */ -function polygonHasMatchingRoofLine(polygonPoints, roofLines) { - if (!polygonPoints || polygonPoints.length < 2) return false; - if (!roofLines || roofLines.length === 0) return false; - - const tolerance = 0.5; - - // 다각형의 각 변을 순회 - for (let i = 0; i < polygonPoints.length; i++) { - const p1 = polygonPoints[i]; - const p2 = polygonPoints[(i + 1) % polygonPoints.length]; - - // 이 변이 roof.lines 중 하나와 일치하는지 확인 - for (const roofLine of roofLines) { - const rp1 = { x: roofLine.x1, y: roofLine.y1 }; - const rp2 = { x: roofLine.x2, y: roofLine.y2 }; - - // 정방향 또는 역방향으로 일치하는지 확인 - const forwardMatch = - isSamePoint(p1, rp1, tolerance) && isSamePoint(p2, rp2, tolerance); - const backwardMatch = - isSamePoint(p1, rp2, tolerance) && isSamePoint(p2, rp1, tolerance); - - if (forwardMatch || backwardMatch) { - console.log('Found matching edge - this polygon touches roof.lines:', { - polygonEdge: { p1, p2 }, - roofLine: { p1: rp1, p2: rp2 } - }); - return true; // 하나라도 일치하면 즉시 true 반환 - } - } - } - - console.log('No matching edges - this polygon is isolated from roof.lines'); - return false; // 모든 변을 확인했는데 일치하는 게 없음 -} - - -function extendLineToRoofBoundary(clippedLine, roof, convertedPolygon) { - - const isIsolated = !convertedPolygon.some(pp => - roof.points.some(rp => - Math.abs(pp.x - rp.x) < 0.5 && Math.abs(pp.y - rp.y) < 0.5 - )); - - // 대각선 라인 선택해서 가장 가까운 경계선 까지 확장 - const dx = Math.abs(clippedLine.p2.x - clippedLine.p1.x); - const dy = Math.abs(clippedLine.p2.y - clippedLine.p1.y); - const isDiagonal = dx > 0.5 && dy > 0.5; - - if (isIsolated && isDiagonal) { - // moveSelectLine의 방향 벡터 계산 - const selectDx = roof.moveSelectLine.endPoint.x - roof.moveSelectLine.startPoint.x; - const selectDy = roof.moveSelectLine.endPoint.y - roof.moveSelectLine.startPoint.y; - const selectLength = Math.sqrt(selectDx * selectDx + selectDy * selectDy); - const selectDir = { x: selectDx / selectLength, y: selectDy / selectLength }; - - // moveSelectLine의 중점 - const selectMidX = (roof.moveSelectLine.startPoint.x + roof.moveSelectLine.endPoint.x) / 2; - const selectMidY = (roof.moveSelectLine.startPoint.y + roof.moveSelectLine.endPoint.y) / 2; - - // 이동 방향 확인 - const moveDirection = roof.moveDirect; // 'up', 'down', 'left', 'right', 'in', 'out' - const movePosiotn = roof.movePosition - // moveSelectLine과 평행한 경계선 찾기 - let closestParallelLine = null; - let minDistToLine = Infinity; - - // 대각선의 중점 - const midX = (clippedLine.p1.x + clippedLine.p2.x) / 2; - const midY = (clippedLine.p1.y + clippedLine.p2.y) / 2; - - for (const roofLine of roof.lines) { - const lineP1 = { x: roofLine.x1, y: roofLine.y1 }; - const lineP2 = { x: roofLine.x2, y: roofLine.y2 }; - - // 경계선의 방향 벡터 계산 - const lineDx = lineP2.x - lineP1.x; - const lineDy = lineP2.y - lineP1.y; - const lineLength = Math.sqrt(lineDx * lineDx + lineDy * lineDy); - const lineDir = { x: lineDx / lineLength, y: lineDy / lineLength }; - - // moveSelectLine과 평행한지 확인 - const dotProduct = Math.abs(selectDir.x * lineDir.x + selectDir.y * lineDir.y); - const isParallel = dotProduct > 0.95; - - if (!isParallel) continue; - - // 대각선 중점에서 경계선까지의 거리 - const lineMidX = (lineP1.x + lineP2.x) / 2; - const lineMidY = (lineP1.y + lineP2.y) / 2; - const dist = Math.sqrt( - Math.pow(midX - lineMidX, 2) + - Math.pow(midY - lineMidY, 2) - ); - - if (dist < minDistToLine) { - minDistToLine = dist; - closestParallelLine = roofLine; - } - } - - if (closestParallelLine) { - const lineP1 = { x: closestParallelLine.x1, y: closestParallelLine.y1 }; - const lineP2 = { x: closestParallelLine.x2, y: closestParallelLine.y2 }; - const lineMidX = (lineP1.x + lineP2.x) / 2; - const lineMidY = (lineP1.y + lineP2.y) / 2; - - // 이동 방향에 따라 확장할 끝점 결정 - let shouldExtendP1 = false; - - switch (movePosiotn) { - case 'down': - // moveSelectLine이 아래/밖으로 이동 -> 아래/밖에 있는 점 확장 - //shouldExtendP1 = (lineMidY > selectMidY) ? (clippedLine.p1.y > clippedLine.p2.y) : (clippedLine.p1.y < clippedLine.p2.y); - break; - case 'up': - // moveSelectLine이 위/안으로 이동 -> 위/안에 있는 점 확장 - //shouldExtendP1 = (lineMidY < selectMidY) ? (clippedLine.p1.y < clippedLine.p2.y) : (clippedLine.p1.y > clippedLine.p2.y); - break; - case 'left': - // moveSelectLine이 왼쪽으로 이동 -> 왼쪽에 있는 점 확장 - //shouldExtendP1 = (lineMidX < selectMidX) ? (clippedLine.p1.x < clippedLine.p2.x) : (clippedLine.p1.x > clippedLine.p2.x); - break; - case 'right': - // moveSelectLine이 오른쪽으로 이동 -> 오른쪽에 있는 점 확장 - //shouldExtendP1 = (lineMidX > selectMidX) ? (clippedLine.p1.x > clippedLine.p2.x) : (clippedLine.p1.x < clippedLine.p2.x); - break; - } - - const endPoint = shouldExtendP1 ? clippedLine.p1 : clippedLine.p2; - const startPoint = shouldExtendP1 ? clippedLine.p2 : clippedLine.p1; - - // 대각선 방향 벡터 - const dirX = endPoint.x - startPoint.x; - const dirY = endPoint.y - startPoint.y; - - // endPoint에서 방향으로 연장 - const farEnd = { - x: endPoint.x + dirX * 10000, - y: endPoint.y + dirY * 10000 - }; - - const intersection = getLineIntersection(startPoint, farEnd, lineP1, lineP2); - - if (intersection) { - // endPoint를 교차점으로 확장 - if (shouldExtendP1) { - clippedLine.p1 = intersection; - } else { - clippedLine.p2 = intersection; - } - console.log('고립된 다각형 대각선 확장됨 (방향 고려):', clippedLine); - return clippedLine - - } - } - return null; - } - - - return undefined -} - -function getIntersectionDetails(p1, p2, line) { - const { startPoint: lineStart, endPoint: lineEnd } = line; - const lineIsVertical = Math.abs(lineEnd.x - lineStart.x) < 0.0001; - const lineIsHorizontal = Math.abs(lineEnd.y - lineStart.y) < 0.0001; - - // Calculate intersection point - const d1 = (p2.x - p1.x) * (lineStart.y - p1.y) - (p2.y - p1.y) * (lineStart.x - p1.x); - const d2 = (p2.x - p1.x) * (lineEnd.y - p1.y) - (p2.y - p1.y) * (lineEnd.x - p1.x); - const d3 = (lineEnd.x - lineStart.x) * (p1.y - lineStart.y) - (lineEnd.y - lineStart.y) * (p1.x - lineStart.x); - const d4 = (lineEnd.x - lineStart.x) * (p2.y - lineStart.y) - (lineEnd.y - lineStart.y) * (p2.x - lineStart.x); - - if ((d1 * d2 < 0) && (d3 * d4 < 0)) { - // Calculate intersection point - const t = d3 / (d3 - d4); - const ix = p1.x + t * (p2.x - p1.x); - const iy = p1.y + t * (p2.y - p1.y); - - // Calculate distances to determine which point is closer - const distToP1 = Math.hypot(ix - p1.x, iy - p1.y); - const distToP2 = Math.hypot(ix - p2.x, iy - p2.y); - - // Determine if intersection is closer to start or end of the line segment - const distToLineStart = Math.hypot(ix - lineStart.x, iy - lineStart.y); - const distToLineEnd = Math.hypot(ix - lineEnd.x, iy - lineEnd.y); - - return { - intersects: true, - point: { x: ix, y: iy }, - // Which point of the segment (p1 or p2) is closer to intersection - segmentPoint: distToP1 < distToP2 ? 'p1' : 'p2', - // Which point of the moveSelectLine is closer to intersection - linePoint: distToLineStart < distToLineEnd ? 'start' : 'end', - // Line orientation - lineOrientation: lineIsVertical ? 'vertical' : lineIsHorizontal ? 'horizontal' : 'diagonal' - }; - } - - return { intersects: false }; -} - -function findClosestParallelLine(intersection, roofLines, orientation) { - if (!intersection || !roofLines?.length) return null; - - let closest = null; - let minDist = Infinity; - - for (const line of roofLines) { - const p1 = line.x1 !== undefined ? {x: line.x1, y: line.y1} : line.startPoint; - const p2 = line.x2 !== undefined ? {x: line.x2, y: line.y2} : line.endPoint; - - // Check line orientation - const dx = Math.abs(p2.x - p1.x); - const dy = Math.abs(p2.y - p1.y); - const lineOrientation = dx < 0.0001 ? 'vertical' : - dy < 0.0001 ? 'horizontal' : 'diagonal'; - - if (lineOrientation !== orientation) continue; - - // Calculate distance to line - const dist = Math.min( - Math.hypot(p1.x - intersection.x, p1.y - intersection.y), - Math.hypot(p2.x - intersection.x, p2.y - intersection.y) - ); - - if (dist < minDist) { - minDist = dist; - closest = { startPoint: p1, endPoint: p2 }; - } - } - - return closest; -} - -function findClosestRoofLine(point, roofLines) { - let closestLine = null; - let minDistance = Infinity; - let roofLineIndex = 0; - let interPoint = null; - - roofLines.forEach((roofLine, index) => { - const lineP1 = roofLine.startPoint; - const lineP2 = roofLine.endPoint; - - // 점에서 선분까지의 최단 거리 계산 - const distance = pointToLineDistance(point, lineP1, lineP2); - - // 점에서 수직으로 내린 교점 계산 - const intersection = getProjectionPoint(point, { - x1: lineP1.x, - y1: lineP1.y, - x2: lineP2.x, - y2: lineP2.y - }); - - if (distance < minDistance) { - minDistance = distance; - closestLine = roofLine; - roofLineIndex = index - interPoint = intersection; - } - }); - - return { line: closestLine, distance: minDistance, index: roofLineIndex, intersectionPoint: interPoint }; -} - -// 점에서 선분까지의 최단 거리를 계산하는 도우미 함수 -function pointToLineDistance(point, lineP1, lineP2) { - const A = point.x - lineP1.x; - const B = point.y - lineP1.y; - const C = lineP2.x - lineP1.x; - const D = lineP2.y - lineP1.y; - - const dot = A * C + B * D; - const lenSq = C * C + D * D; - let param = -1; - - if (lenSq !== 0) { - param = dot / lenSq; - } - - let xx, yy; - - if (param < 0) { - xx = lineP1.x; - yy = lineP1.y; - } else if (param > 1) { - xx = lineP2.x; - yy = lineP2.y; - } else { - xx = lineP1.x + param * C; - yy = lineP1.y + param * D; - } - - const dx = point.x - xx; - const dy = point.y - yy; - return Math.sqrt(dx * dx + dy * dy); } \ No newline at end of file