From 3663636b5dd8c2a40eeef18868e92fd386d1a117 Mon Sep 17 00:00:00 2001 From: yscha Date: Thu, 4 Dec 2025 00:59:24 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A2=8C=EC=9A=B0=EC=83=81=ED=95=98=EC=A7=81?= =?UTF-8?q?=EC=84=A0=20=EA=B5=AC=EB=B6=84=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 159 +++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 6 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 0e4f06d5..df920a88 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -952,7 +952,8 @@ if((roof.moveUpDown??0 > 0) ) { // newPStart.y = wallBaseLine.y1; // getAddLine({ x: newPEnd.x, y: wallBaseLine.y1 }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) - } else if (wallBaseLine.y1 <= newPStart.y && newPStart.y <= newPEnd.y && newPEnd.y <= wallBaseLine.y2) { // 아래가운데 + } else if (wallBaseLine.y1 <= newPStart.y && newPStart.y <= newPEnd.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; @@ -969,17 +970,33 @@ if((roof.moveUpDown??0 > 0) ) { ? 'in' : 'out'; const condition = `${mLine.position}_${positionType}`; + let isStartEnd = findInteriorPoint(wallBaseLine, sortedWallBaseLines) switch (condition) { case 'top_in': - newPStart.x = wallBaseLine.x1; - //추가 수직 - getAddLine({ x: wallBaseLine.x1, y: newPEnd.y }, { x: wallBaseLine.x1, y: wallBaseLine.y1 }) - //추가 라인? - findPoints.push({ x: wallBaseLine.x1, y: wallBaseLine.y1 }); + + console.log("findInteriorPoint result:::::::", isStartEnd); + + let sPoint, ePoint; + if (isStartEnd.start ) { + sPoint = {x: wallBaseLine.x1, y: wallBaseLine.y1}; + newPStart.x = wallBaseLine.x1; + getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }) + findPoints.push({ x: sPoint.x, y: sPoint.y }); + } + + if(isStartEnd.end){ + ePoint = {x: wallBaseLine.x2, y: wallBaseLine.y2}; + newPEnd.x = wallBaseLine.x2; + getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }) + findPoints.push({ x: ePoint.x, y: ePoint.y }); + } break; case 'top_out': + + console.log("findInteriorPoint result:::::::", isStartEnd); + const moveY = Math.abs(wallLine.y1 - wallBaseLine.y1) const dist = Math.abs(roofLine.y1 - wallLine.y1) const aStartX = Math.abs(newPStart.x + moveY) @@ -4232,3 +4249,133 @@ function PointBasedOnBaseLength(x1, y1, x2, y2) { { x: thirdX2, y: thirdY2 } ]; } + +/** + * 선분의 시작점과 끝점 중 어느 쪽이 내부를 향하는지 반환 + * @param {Object} line - {x1, y1, x2, y2} 형태의 선분 + * @param {Array} polygonLines - 폴리곤을 구성하는 선분들의 배열 + * @returns {Object} - { start: boolean, end: boolean } 시작점과 끝점의 내부 여부 + */ +function findInteriorPoint(line, polygonLines) { + const { x1, y1, x2, y2 } = line; + + // 선분의 방향 벡터 + const dx = x2 - x1; + const dy = y2 - y1; + + // 수직 벡터 (왼쪽으로 90도 회전) + const perpX = -dy; + const perpY = dx; + + // 정규화 + const length = Math.sqrt(perpX * perpX + perpY * perpY); + const nx = perpX / length; + const ny = perpY / length; + + // 오프셋 계산 (선분 길이의 1% 또는 최소 0.1) + const lineLength = Math.sqrt(dx*dx + dy*dy); + const offset = Math.max(0.1, lineLength * 0.01); + + // 시작점에서 수직 방향으로 약간 떨어진 점 + const testPoint = { + x: x1 + nx * offset, + y: y1 + ny * offset + }; + + // 이 점이 폴리곤 내부에 있는지 확인 + const isInside = isPointInPolygon(testPoint, polygonLines); + + // 시작점이 내부를 향하는지 여부 + return { + start: isInside, + // 끝점 방향은 시작점과 반대 + end: !isInside + }; +} +// 점이 선분의 어느 쪽에 있는지 확인 +function isPointInDirection(line, point) { + const {x1, y1, x2, y2} = line; + const {x, y} = point; + return ((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)) > 0; +} + +/** + * 점이 폴리곤 내부에 있는지 확인 (Ray Casting 알고리즘) + * @param {Object} point - {x, y} 형태의 점 + * @param {Array} polygonLines - 폴리곤을 구성하는 선분들의 배열 + * @returns {boolean} - 점이 폴리곤 내부에 있으면 true, 아니면 false + */ +function isPointInPolygon(point, polygonLines) { + if (!polygonLines || polygonLines.length === 0) { + console.log("경고: 폴리곤 선분이 비어있습니다."); + return false; + } + + const x = point.x; + const y = point.y; + let inside = false; + let intersections = 0; + + // 폴리곤의 모든 선분에 대해 반복 + for (let i = 0; i < polygonLines.length; i++) { + const line = polygonLines[i]; + const x1 = line.x1; + const y1 = line.y1; + const x2 = line.x2; + const y2 = line.y2; + + // 점이 선분 위에 있는지 확인 + if (isPointOnLineSegment2(point, {x: x1, y: y1}, {x: x2, y: y2}, 0.1)) { + console.log(`점 (${x}, ${y})은 선분 [(${x1}, ${y1}), (${x2}, ${y2})] 위에 있습니다.`); + return true; // 경계선 위의 점은 내부로 간주 + } + + // Ray casting 알고리즘 + // 점의 y 좌표가 선분의 y 범위 내에 있는지 확인 + const yInRange = (y1 > y) !== (y2 > y); + + if (yInRange) { + // x 교차점 계산 + const xIntersect = (y - y1) * (x2 - x1) / (y2 - y1) + x1; + + // 교차점이 점의 오른쪽에 있는지 확인 + if (x <= xIntersect) { + inside = !inside; + intersections++; + } + } + } + + console.log(`폴리곤 검사 결과: 점 (${x}, ${y})은 ${inside ? '내부' : '외부'}에 있습니다. (교차점 수: ${intersections})`); + return inside; +} + +/** + * 점이 선분 위에 있는지 확인 + * @param {Object} point - 확인할 점 {x, y} + * @param {Object} lineStart - 선분의 시작점 {x, y} + * @param {Object} lineEnd - 선분의 끝점 {x, y} + * @param {number} tolerance - 오차 허용 범위 + * @returns {boolean} - 점이 선분 위에 있으면 true, 아니면 false + */ +function isPointOnLineSegment2(point, lineStart, lineEnd, tolerance = 0.1) { + const { x: px, y: py } = point; + const { x: x1, y: y1 } = lineStart; + const { x: x2, y: y2 } = lineEnd; + + // 선분의 길이 + const lineLength = Math.hypot(x2 - x1, y2 - y1); + + // 점에서 선분의 양 끝점까지의 거리 + const dist1 = Math.hypot(px - x1, py - y1); + const dist2 = Math.hypot(px - x2, py - y2); + + // 점이 선분 위에 있는지 확인 (오차 허용 범위 내에서) + const isOnSegment = Math.abs((dist1 + dist2) - lineLength) <= tolerance; + + if (isOnSegment) { + console.log(`점 (${px}, ${py})은 선분 [(${x1}, ${y1}), (${x2}, ${y2})] 위에 있습니다.`); + } + + return isOnSegment; +} \ No newline at end of file