diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index 1a92cfe8..6ddd94c7 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -196,6 +196,14 @@ export default function CanvasMenu(props) { text: getMessage('module.delete.confirm'), type: 'confirm', confirmFn: () => { + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + roofs.forEach((roof) => { + roof.set({ + stroke: 'black', + strokeWidth: 3, + }) + }) + //해당 메뉴 이동시 배치면 삭제 setAllModuleSurfaceIsComplete(false) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index bbf87407..841bbcc2 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -319,7 +319,7 @@ const createInnerLinesFromSkeleton = (skeleton, baseLines, roof, canvas, textMod const innerLines = [] const processedInnerEdges = new Set() - const rawLines = [] + const skeletonLines = [] // 1. 기본 skeleton에서 모든 내부 선분 수집 //edge 순서와 baseLines 순서가 같을수가 없다. @@ -330,63 +330,52 @@ const createInnerLinesFromSkeleton = (skeleton, baseLines, roof, canvas, textMod //오른쪽 하단에서 시작하면 그 지점에서부터 시계 방향으로 진행합니다. //edgeIndex 대신에 실제 baseLines 선택라인을 찾아야 한다. const edgeResult = skeleton.Edges[edgeIndex] - + console.log(edgeResult) // 방향을 고려하지 않고 같은 라인인지 확인하는 함수 let edgeType = 'eaves' let baseLineIndex = 0 - processEavesEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) - // // ✅ Edge 타입별 처리 분기 - // switch (edgeType) { - // case 'eaves': - // processEavesEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) - // break - // - // case 'wall': - // processWallEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex) - // break - // - // case 'gable': - // processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) - // break - // - // default: - // console.warn(`알 수 없는 edge 타입: ${edgeType}`) - // processEavesEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) - // } + processEavesEdge(edgeResult, baseLines, skeletonLines, processedInnerEdges) + } - for (let baseLineIndex = 0; baseLineIndex < baseLines.length; baseLineIndex++) { - if (baseLines[baseLineIndex].attributes.type === 'gable') { - // 일다 그려서 rawLines를 만들어 - for (let edgeIndex = 0; edgeIndex < skeleton.Edges.length; edgeIndex++) { + for (let edgeIndex = 0; edgeIndex < skeleton.Edges.length; edgeIndex++) { - const edgeResult = skeleton.Edges[edgeIndex] - const startX = edgeResult.Edge.Begin.X - const startY = edgeResult.Edge.Begin.Y - const endX = edgeResult.Edge.End.X - const endY = edgeResult.Edge.End.Y + const edgeResult = skeleton.Edges[edgeIndex] + const startX = edgeResult.Edge.Begin.X + const startY = edgeResult.Edge.Begin.Y + const endX = edgeResult.Edge.End.X + const endY = edgeResult.Edge.End.Y - //외벽선 동일 라인이면 + + //외벽선 라인과 같은 edgeResult를 찾는다 + for (let baseLineIndex = 0; baseLineIndex < baseLines.length; baseLineIndex++) { + + if (baseLines[baseLineIndex].attributes.type === 'gable') { + // 일다 그려서 skeletonLines를 만들어 +//외벽선 동일 라인이면 if (isSameLine(startX, startY, endX, endY, baseLines[baseLineIndex])) { - processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) // + processGableEdge(edgeResult, baseLines, skeletonLines, processedInnerEdges, edgeIndex, baseLineIndex) // break // 매칭되는 라인을 찾았으므로 루프 종료 } - } + } + + + } - console.log(`처리된 rawLines: ${rawLines.length}개`) + console.log(`처리된 skeletonLines: ${skeletonLines.length}개`) // 2. 겹치는 선분 병합 - // const mergedLines = mergeCollinearLines(rawLines) + // const mergedLines = mergeCollinearLines(skeletonLines) // console.log('mergedLines', mergedLines) // 3. QLine 객체로 변환 - for (const line of rawLines) { + for (const line of skeletonLines) { const { p1, p2, attributes, lineStyle } = line const innerLine = new QLine([p1.x, p1.y, p2.x, p2.y], { parentId: roof.id, @@ -409,10 +398,13 @@ const createInnerLinesFromSkeleton = (skeleton, baseLines, roof, canvas, textMod } // ✅ EAVES (처마) 처리 - 기본 skeleton 모두 사용 -function processEavesEdge(edgeResult, baseLines, rawLines, processedInnerEdges) { - console.log(`processEavesEdge::`, rawLines) +function processEavesEdge(edgeResult, baseLines, skeletonLines, processedInnerEdges) { + console.log(`processEavesEdge::`, skeletonLines) + const begin = edgeResult.Edge.Begin + const end = edgeResult.Edge.End - // 내부 선분 수집 (스케레톤은 다각형) + + //내부 선분 수집 (스케레톤은 다각형) const polygonPoints = edgeResult.Polygon.map((p) => ({ x: p.X, y: p.Y })) for (let i = 0; i < polygonPoints.length; i++) { @@ -421,14 +413,14 @@ function processEavesEdge(edgeResult, baseLines, rawLines, processedInnerEdges) const p2 = polygonPoints[(i + 1) % polygonPoints.length] // 외벽선 제외 후 추가 - if (!isOuterEdge(p1, p2, baseLines)) { - addRawLine(rawLines, processedInnerEdges, p1, p2, 'RIDGE', '#FF0000', 3) + if(begin !== edgeResult.Polygon[i] && end !== edgeResult.Polygon[i] ) { + addRawLine(skeletonLines, processedInnerEdges, p1, p2, 'RIDGE', '#FF0000', 3) } } } // ✅ WALL (벽) 처리 - 선분 개수 최소화 -function processWallEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex) { +function processWallEdge(edgeResult, baseLines, skeletonLines, processedInnerEdges, edgeIndex) { console.log(`WALL Edge ${edgeIndex}: 내부 선분 최소화`) const polygonPoints = edgeResult.Polygon.map((p) => ({ x: p.X, y: p.Y })) @@ -444,7 +436,7 @@ function processWallEdge(edgeResult, baseLines, rawLines, processedInnerEdges, e if (lineLength > 10) { // 최소 길이 조건 - addRawLine(rawLines, processedInnerEdges, p1, p2, 'HIP', '#000000', 2) + addRawLine(skeletonLines, processedInnerEdges, p1, p2, 'HIP', '#000000', 2) } else { console.log(`WALL: 짧은 선분 제거 (길이: ${lineLength.toFixed(1)})`) } @@ -453,7 +445,7 @@ function processWallEdge(edgeResult, baseLines, rawLines, processedInnerEdges, e } // ✅ GABLE (케라바) 처리 - 직선 생성, 다른 선분 제거 -function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, edgeIndex, baseLineIndex) { +function processGableEdge(edgeResult, baseLines, skeletonLines, processedInnerEdges, edgeIndex, baseLineIndex) { console.log(`GABLE Edge ${edgeResult}: 직선 skeleton 생성`) const diagonalLine = []; //대각선 라인 @@ -463,7 +455,6 @@ function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, // 1. 기존 복잡한 skeleton 선분들 무시 // 2. GABLE edge에 수직인 직선 생성 - const sourceEdge = edgeResult.Edge const gableStart = { x: sourceEdge.Begin.X, y: sourceEdge.Begin.Y } const gableEnd = { x: sourceEdge.End.X, y: sourceEdge.End.Y } @@ -486,10 +477,10 @@ function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, // }) // } // - // // 폴리곤 중심점 (대략적) - // const centerX = polygonPoints.reduce((sum, p) => sum + p.x, 0) / polygonPoints.length - // const centerY = polygonPoints.reduce((sum, p) => sum + p.y, 0) / polygonPoints.length - // const polygonCenter = { x: centerX, y: centerY } + // 폴리곤 중심점 (대략적) + const centerX = polygonPoints.reduce((sum, p) => sum + p.x, 0) / polygonPoints.length + const centerY = polygonPoints.reduce((sum, p) => sum + p.y, 0) / polygonPoints.length + const polygonCenter = { x: centerX, y: centerY } // // // 허용 오차 // const colinearityTolerance = 0.1 @@ -530,15 +521,114 @@ function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, const selectBaseLine = baseLines[baseLineIndex]; console.log('selectBaseLine:', selectBaseLine); - console.log('rawLines:', rawLines) - //selectBaseLine 과 같은 edgeResult.ed + console.log('skeletonLines:', skeletonLines) + + // selectBaseLine의 중간 좌표 계산 + const midPoint = { + x: (selectBaseLine.x1 + selectBaseLine.x2) / 2, + y: (selectBaseLine.y1 + selectBaseLine.y2) / 2 + }; + console.log('midPoint of selectBaseLine:', midPoint); // 대각선 보정(fallback) 제거: 항상 수평/수직 내부 용마루만 생성 + const edgePoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y })); - for (let i = rawLines.length - 1; i >= 0; i--) { - const line = rawLines[i]; - console.log('line:', line) - console.log('line.attributes.type:', line.attributes.type) + //제거 + for (let i = skeletonLines.length - 1; i >= 0; i--) { + const line = skeletonLines[i]; + console.log('line:', line) + console.log('line.attributes.type:', line.attributes.type) + + const linePoints = [line.p1, line.p2]; + + // Check if both points of the line are in the edgePoints + const isEdgeLine = linePoints.every(point => + edgePoints.some(ep => + Math.abs(ep.x - point.x) < 0.001 && + Math.abs(ep.y - point.y) < 0.001 + ) + ); + + if (isEdgeLine) { + skeletonLines.splice(i, 1); + } + } + + //확장 + // Extend lines that have endpoints in edgePoints to intersect with selectBaseLine + // Find diagonal lines (not horizontal or vertical) + // Extend lines that have endpoints in edgePoints + + for (let i = 0; i < skeletonLines.length; i++) { + const line = skeletonLines[i]; + const p1 = line.p1; + const p2 = line.p2; + const lineP1 = { x: line.p1.x, y: line.p1.y }; + const lineP2 = { x: line.p2.x, y: line.p2.y }; + + let hasP1 = false; + let hasP2 = false; + console.log('edgeResult.Edge::',edgeResult.Edge) + + const lineInfo = findMatchingLinePoints(line, edgeResult.Polygon); + console.log(lineInfo); + + //대각선 + //직선(마루) + if(lineInfo.hasMatch) { + if (lineInfo.matches[0].type === 'diagonal') { + + const intersection2 = getLineIntersectionParametric(lineP1, lineP2, gableStart, gableEnd); + console.log('intersection2:', intersection2); + if (lineInfo.matches[0].linePoint === 'p1') { + + + skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: intersection2.x, y: intersection2.y }; + } else { + + skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: intersection2.x, y: intersection2.y }; + } + + } else if (lineInfo.matches[0].type === 'horizontal') { + if (lineInfo.matches[0].linePoint === 'p1') { + skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: edgeResult.Edge.Begin.X }; + } else { + skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: edgeResult.Edge.Begin.X }; + } + + } else if (lineInfo.matches[0].type === 'vertical') { + if (lineInfo.matches[0].linePoint === 'p1') { + skeletonLines[i].p1 = { ...skeletonLines[i].p1, y: edgeResult.Edge.Begin.Y }; + } else { + skeletonLines[i].p2 = { ...skeletonLines[i].p2, y: edgeResult.Edge.Begin.Y }; + } + } + } + +// for (const polyPoint of edgeResult.Polygon) { +// +// if (polyPoint.X === lineP1.x && polyPoint.Y === lineP1.y) { +// const extendedPoint1 = getExtensionIntersection(lineP2.x, lineP2.y, lineP1.x, lineP1.y, edgeResult.Edge.Begin.X, edgeResult.Edge.Begin.Y, edgeResult.Edge.End.X,edgeResult.Edge.End.Y); +// console.log('extendedPoint1:', extendedPoint1); +// +// skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: extendedPoint1.x, y: extendedPoint1.Y }; +// //skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: edgeResult.Edge.Begin.X}; +// } +// +// if (polyPoint.X === lineP2.x && polyPoint.Y === lineP2.y) { +// const extendedPoint2 = getExtensionIntersection(lineP1.x, lineP1.y,lineP2.x, lineP2.y, edgeResult.Edge.Begin.X, edgeResult.Edge.Begin.Y, edgeResult.Edge.End.X,edgeResult.Edge.End.Y); +// console.log('extendedPoint2:', extendedPoint2); +// +// skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: extendedPoint2.x, y: extendedPoint2.Y }; +// //skeletonLines[i].p2 = { ...skeletonLines[i].p2, y: edgeResult.Edge.Begin.Y }; +// } +// +// +// } + } + + + /* if (line.attributes.type === LINE_TYPE.SUBLINE.HIP || line.attributes.type === 'HIP') { // 선택한 기준선 을 중심으로 대각선 삭제 @@ -556,19 +646,32 @@ function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, const sharesEndPoint = pointsEqual(edgeEnd, lineStart) || pointsEqual(edgeEnd, lineEnd); if (sharesStartPoint || sharesEndPoint) { - rawLines.splice(i, 1); + skeletonLines.splice(i, 1); // ridge extension logic can go here + //gableMidpoint까지 확장 + + }else{ + //선택한 baseLine 연장(edgeResult.Polygon 의 좌표와 동일한 좌표를 찾아서 연장) + for (const polyPoint of edgeResult.Polygon) { + if (Math.abs(polyPoint.X - lineEnd.x) < 0.1 && Math.abs(polyPoint.Y - lineEnd.y) < 0.1) { + // 연장 로직 + } + } } - - - }else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE || line.attributes.type === 'RIDGE') { //마루일때 + const lineP1 = { x: line.p1.x, y: line.p1.y }; + const lineP2 = { x: line.p2.x, y: line.p2.y }; + const extensionLine= { + maxX:'', + minX:'', + maxY:'', + minY:'', + } if(edgeResult.Polygon.length > 3){ - const lineP1 = { x: line.p1.x, y: line.p1.y }; - const lineP2 = { x: line.p2.x, y: line.p2.y }; + let hasP1 = false; let hasP2 = false; @@ -585,18 +688,74 @@ function processGableEdge(edgeResult, baseLines, rawLines, processedInnerEdges, } if (hasP1 && hasP2) { - rawLines.splice(i, 1); + skeletonLines.splice(i, 1); + //양쪽 대각선이 있으면 서로 만난다. + for (const polyPoint of edgeResult.Polygon) { + + } + + + + //가운데 연장선을 추가 + skeletonLines.push({ + p1: {x: midPoint.x, y: midPoint.y}, + p2: {x: centerX, y: centerY}, + attributes: { + type: LINE_TYPE.SUBLINE.RIDGE, + planeSize: calcLinePlaneSize({ x1: midPoint.x, y1: midPoint.y, x2: centerX, y2: centerY }), + isRidge: true, + }, + lineStyle: { + color: '#FF0000', + width: 2 + }, + }) + } + + + + + + + }else{ + console.log("mpoint",gableMidpoint) + console.log("midPoint", midPoint) + console.log("lineP1",lineP1) + console.log("lineP2",lineP2) + //gableMidpoint까지 확장 (x or y 동일) + //가로일때 gableMidPoint.y 동일 + // Extend horizontal lines to gable midpoint + // + if (Math.abs(lineP1.y - lineP2.y) < 0.3) { // 가로 라인 + const extension = getExtensionLine(midPoint, lineP1, lineP2); + if (extension) { // null 체크 추가 + if (extension.isStartExtension) { + skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: extension.extensionPoint.x }; + } else { + skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: extension.extensionPoint.x }; + } + } + } else { // 세로 라인 + const extension = getExtensionLine(midPoint, lineP1, lineP2); + if (extension) { // null 체크 추가 + if (extension.isStartExtension) { + skeletonLines[i].p1 = { ...skeletonLines[i].p1, y: extension.extensionPoint.y }; + } else { + skeletonLines[i].p2 = { ...skeletonLines[i].p2, y: extension.extensionPoint.y }; + } + } } } } - console.log('result rawLines:', rawLines) - } + console.log('result skeletonLines:', skeletonLines) +*/ + // addRawLine( - // rawLines, + // skeletonLines, // processedInnerEdges, // gableMidpoint, // polygonCenter, @@ -626,7 +785,7 @@ function isOuterEdge(p1, p2, baseLines) { }) } -function addRawLine(rawLines, processedInnerEdges, p1, p2, lineType, color, width) { +function addRawLine(skeletonLines, processedInnerEdges, p1, p2, lineType, color, width) { const edgeKey = [`${p1.x.toFixed(1)},${p1.y.toFixed(1)}`, `${p2.x.toFixed(1)},${p2.y.toFixed(1)}`].sort().join('|') if (processedInnerEdges.has(edgeKey)) return @@ -651,7 +810,7 @@ function addRawLine(rawLines, processedInnerEdges, p1, p2, lineType, color, widt // 대각선일 때 lineType을 HIP로 지정 const normalizedType = isDiagonal ? LINE_TYPE.SUBLINE.HIP : inputNormalizedType - rawLines.push({ + skeletonLines.push({ p1: p1, p2: p2, attributes: { @@ -1617,4 +1776,334 @@ const isSameLine = (edgeStartX, edgeStartY, edgeEndX, edgeEndY, baseLine) => { Math.abs(edgeEndY - baseLine.y1) < tolerance return clockwiseMatch || counterClockwiseMatch +} + +/** + * 중간점과 선분의 끝점을 비교하여 연장선(extensionLine)을 결정합니다. + * @param {Object} midPoint - 중간점 좌표 {x, y} + * @param {Object} lineP1 - 선분의 첫 번째 끝점 {x, y} + * @param {Object} lineP2 - 선분의 두 번째 끝점 {x, y} + * @returns {Object|null} - 연장선 설정 또는 null (연장 불필요 시) + */ +function getExtensionLine(midPoint, lineP1, lineP2) { + // 선분의 방향 계산 + const isHorizontal = Math.abs(lineP1.y - lineP2.y) < 0.3; // y 좌표가 거의 같으면 수평선 + const isVertical = Math.abs(lineP1.x - lineP2.x) < 0.3; // x 좌표가 거의 같으면 수직선 + + if (isHorizontal) { + // 수평선인 경우 - y 좌표가 midPoint와 같은지 확인 + if (Math.abs(lineP1.y - midPoint.y) > 0.3) { + return null; // y 좌표가 다르면 연장하지 않음 + } + + // 중간점이 선분의 왼쪽에 있는 경우 + if (midPoint.x < Math.min(lineP1.x, lineP2.x)) { + return { + isHorizontal: true, + isStartExtension: lineP1.x < lineP2.x, + extensionPoint: { ...midPoint, y: lineP1.y } + }; + } + // 중간점이 선분의 오른쪽에 있는 경우 + else if (midPoint.x > Math.max(lineP1.x, lineP2.x)) { + return { + isHorizontal: true, + isStartExtension: lineP1.x > lineP2.x, + extensionPoint: { ...midPoint, y: lineP1.y } + }; + } + } + else if (isVertical) { + // 수직선인 경우 - x 좌표가 midPoint와 같은지 확인 + if (Math.abs(lineP1.x - midPoint.x) > 0.3) { + return null; // x 좌표가 다르면 연장하지 않음 + } + + // 중간점이 선분의 위에 있는 경우 + if (midPoint.y < Math.min(lineP1.y, lineP2.y)) { + return { + isHorizontal: false, + isStartExtension: lineP1.y < lineP2.y, + extensionPoint: { ...midPoint, x: lineP1.x } + }; + } + // 중간점이 선분의 아래에 있는 경우 + else if (midPoint.y > Math.max(lineP1.y, lineP2.y)) { + return { + isHorizontal: false, + isStartExtension: lineP1.y > lineP2.y, + extensionPoint: { ...midPoint, x: lineP1.x } + }; + } + } + + // 기본값 반환 (연장 불필요) + return null; +} +function convertToClockwise(points) { + // 1. 다각형의 면적 계산 (시계/반시계 방향 판단용) + let area = 0; + const n = points.length; + + for (let i = 0; i < n; i++) { + const j = (i + 1) % n; + area += (points[j].X - points[i].X) * (points[j].Y + points[i].Y); + } + + // 2. 반시계방향이면 배열을 뒤집어 시계방향으로 변환 + if (area < 0) { + return [...points].reverse(); + } + + // 3. 이미 시계방향이면 그대로 반환 + return [...points]; +} + +/** + * skeletonLines에서 특정 점(polyPoint)을 지나는 라인을 찾는 함수 + * @param {Array} skeletonLines - 검색할 라인 배열 + * @param {Object} polyPoint - 검색할 점 {X, Y} + * @returns {Array} - 일치하는 라인 배열 + */ +function findLinesPassingPoint(skeletonLines, polyPoint) { + return skeletonLines.filter(line => { + // 라인의 시작점이나 끝점이 polyPoint와 일치하는지 확인 + const isP1Match = (Math.abs(line.p1.x - polyPoint.X) < 0.001 && + Math.abs(line.p1.y - polyPoint.Y) < 0.001); + const isP2Match = (Math.abs(line.p2.x - polyPoint.X) < 0.001 && + Math.abs(line.p2.y - polyPoint.Y) < 0.001); + + return isP1Match || isP2Match; + }); +} + +// 두 선분의 교차점을 찾는 함수 +// 두 선분의 교차점을 찾는 함수 (개선된 버전) +function findIntersection(p1, p2, p3, p4) { + // 선분1: p1 -> p2 + // 선분2: p3 -> p4 + + // 선분 방향 벡터 + const d1x = p2.x - p1.x; + const d1y = p2.y - p1.y; + const d2x = p4.x - p3.x; + const d2y = p4.y - p3.y; + + // 분모 계산 + const denominator = d1x * d2y - d1y * d2x; + + // 평행한 경우 (또는 매우 가까운 경우) + // if (Math.abs(denominator) < 0.0001) { + // return null; + // } + + // 매개변수 t와 u 계산 + const t = ((p3.x - p1.x) * d2y - (p3.y - p1.y) * d2x) / denominator; + const u = ((p3.x - p1.x) * d1y - (p3.y - p1.y) * d1x) / denominator; + + // 두 선분이 교차하는지 확인 (0 <= t <= 1, 0 <= u <= 1) + if (t >= -0.001 && t <= 1.001 && u >= -0.001 && u <= 1.001) { + // 교차점 계산 + const x = p1.x + t * d1x; + const y = p1.y + t * d1y; + return { x, y }; + } + + // 교차하지 않는 경우 + return null; +} +/** + * edgePoints와 skeletonLines의 교차점을 찾는 함수 + * @param {Array<{x: number, y: number}>} edgePoints - 엣지 포인트 배열 + * @param {Array} skeletonLines - 원시 라인 배열 (각 라인은 p1, p2 속성을 가짐) + * @returns {Array<{x: number, y: number, line: Object}>} 교차점과 해당 라인 정보 배열 + */ +function findIntersectionsWithEdgePoints(edgePoints, skeletonLines) { + const intersections = []; + + // edgePoints를 순회하며 각 점을 지나는 라인 찾기 + for (let i = 0; i < edgePoints.length; i++) { + const point = edgePoints[i]; + const nextPoint = edgePoints[(i + 1) % edgePoints.length]; + + // 현재 엣지 선분 + const edgeLine = { + x1: point.x, y1: point.y, + x2: nextPoint.x, y2: nextPoint.y + }; + + // 모든 skeletonLines와의 교차점 검사 + for (const rawLine of skeletonLines) { + // rawLine은 p1, p2 속성을 가짐 + const rawLineObj = { + x1: rawLine.p1.x, y1: rawLine.p1.y, + x2: rawLine.p2.x, y2: rawLine.p2.y + }; + + // 선분 교차 검사 + const intersection = findIntersection( + edgeLine.x1, edgeLine.y1, edgeLine.x2, edgeLine.y2, + rawLineObj.x1, rawLineObj.y1, rawLineObj.x2, rawLineObj.y2 + ); + + if (intersection) { + intersections.push({ + x: intersection.x, + y: intersection.y, + edgeIndex: i, + line: rawLine + }); + } + } + } + + return intersections; +} + +// Helper function to extend a line to intersect with polygon edges +function extendLineToIntersections(p1, p2, polygonPoints) { + let intersections = []; + const line = { p1, p2 }; + + // Check intersection with each polygon edge + for (let i = 0; i < polygonPoints.length; i++) { + const edgeP1 = polygonPoints[i]; + const edgeP2 = polygonPoints[(i + 1) % polygonPoints.length]; + + + } + + if (intersections.length < 2) return null; + + // Sort by distance from p1 + intersections.sort((a, b) => a.distance - b.distance); + + // Return the two farthest intersection points + return { + p1: { x: intersections[0].x, y: intersections[0].y }, + p2: { + x: intersections[intersections.length - 1].x, + y: intersections[intersections.length - 1].y + } + }; +} + +function getExtensionIntersection( + startX, startY, // 대각선 시작점 + currentX, currentY, // 대각선 현재 위치 + lineStartX, lineStartY, // 연장할 선의 시작점 + lineEndX, lineEndY // 연장할 선의 끝점 +) { + // 대각선 방향 벡터 + const dx = currentX - startX; + const dy = currentY - startY; + + // 연장할 선의 기울기 + const m = (lineEndY - lineStartY) / (lineEndX - lineStartX); + + // 매개변수 t 방정식에서 t를 구하기 위한 식 전개 + // 대각선의 parametric 방정식: x = startX + t*dx, y = startY + t*dy + // 연장할 선 방정식: y = m * (x - lineStartX) + lineStartY + // 이를 대입해 t 구함 + const numerator = m * (lineStartX - startX) + startY - lineStartY; + const denominator = dy - m * dx; + + if (denominator === 0) { + // 평행하거나 일치하여 교점 없음 + return null; + } + + const t = numerator / denominator; + + const intersectX = startX + t * dx; + const intersectY = startY + t * dy; + + return { x: intersectX, y: intersectY }; +} + +function findMatchingLinePoints(Aline, APolygon, epsilon = 1e-10) { + const { p1, p2 } = Aline; + const matches = []; + + // 선의 방향 판단 + function getLineDirection(point1, point2, epsilon = 1e-10) { + const deltaX = Math.abs(point1.x - point2.x); + const deltaY = Math.abs(point1.y - point2.y); + + if (deltaX < epsilon && deltaY < epsilon) { + return { + type: 'point', + description: '점 (두 좌표가 동일)' + }; + } else if (deltaX < epsilon) { + return { + type: 'vertical', + description: '수직선 (세로)' + }; + } else if (deltaY < epsilon) { + return { + type: 'horizontal', + description: '수평선 (가로)' + }; + } else { + return { + type: 'diagonal', + description: '대각선' + }; + } + } + + // 선의 방향 정보 계산 + const lineDirection = getLineDirection(p1, p2, epsilon); + + APolygon.forEach((point, index) => { + // p1과 비교 + if (Math.abs(p1.x - point.X) < epsilon && Math.abs(p1.y - point.Y) < epsilon) { + matches.push({ + linePoint: 'p1', + polygonIndex: index, + coordinates: { x: point.X, y: point.Y }, + lineDirection: lineDirection, + type: lineDirection.type + }); + } + // p2와 비교 + if (Math.abs(p2.x - point.X) < epsilon && Math.abs(p2.y - point.Y) < epsilon) { + matches.push({ + linePoint: 'p2', + polygonIndex: index, + coordinates: { x: point.X, y: point.Y }, + lineDirection: lineDirection, + type: lineDirection.type + }); + } + }); + + return { + hasMatch: matches.length > 0, + lineDirection: lineDirection, + matches: matches + }; +} + +function getLineIntersectionParametric(p1, p2, p3, p4) { + const d1 = { x: p2.x - p1.x, y: p2.y - p1.y }; // 첫번째 직선 방향벡터 + const d2 = { x: p4.x - p3.x, y: p4.y - p3.y }; // 두번째 직선 방향벡터 + + // 평행선 체크 (외적이 0이면 평행) + const cross = d1.x * d2.y - d1.y * d2.x; + if (Math.abs(cross) < Number.EPSILON) { + return null; // 평행선 + } + + // 매개변수 t 계산 + const dx = p3.x - p1.x; + const dy = p3.y - p1.y; + const t = (dx * d2.y - dy * d2.x) / cross; + + // 교점: p1 + t * d1 + return { + x: p1.x + t * d1.x, + y: p1.y + t * d1.y + }; } \ No newline at end of file