diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 54cb5ba2..34d8153e 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -4,6 +4,7 @@ import { calcLineActualSize, calcLinePlaneSize, toGeoJSON } from '@/util/qpolygo import { QLine } from '@/components/fabric/QLine' import { getDegreeByChon } from '@/util/canvas-util' import Big from 'big.js' +import { line } from 'framer-motion/m' /** * 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다. @@ -15,33 +16,144 @@ import Big from 'big.js' export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => { - let roof = canvas?.getObjects().find((object) => object.id === roofId) - const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) - - const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) - if (hasNonParallelLines.length > 0) { - return - } - - const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] - const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] - - /** 외벽선 */ - const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) - - - - //const skeletonLines = []; - // 1. 지붕 폴리곤 좌표 전처리 - const coordinates = preprocessPolygonCoordinates(roof.points); - if (coordinates.length < 3) { - console.warn("Polygon has less than 3 unique points. Cannot generate skeleton."); - return; - } - // 2. 스켈레톤 생성 및 그리기 - skeletonBuilder(roofId, canvas, textMode, roof, baseLines) + skeletonBuilder(roofId, canvas, textMode) +} + + +const movingRidgeFromSkeleton = (roofId, canvas) => { + + let roof = canvas?.getObjects().find((object) => object.id === roofId) + let moveDirection = roof.moveDirect; + let moveFlowLine = roof.moveFlowLine??0; + const selectLine = roof.moveSelectLine; + + const startPoint = selectLine.startPoint + const endPoint = selectLine.endPoint + const oldPoints = canvas?.movePoints?.points ?? roof.points + const oppositeLine = findOppositeLine(canvas.skeleton.Edges, startPoint, endPoint, oldPoints); + + if (oppositeLine) { + console.log('Opposite line found:', oppositeLine); + } else { + console.log('No opposite line found'); + } + + return oldPoints.map((point) => { + const newPoint = { ...point }; + const absMove = Big(moveFlowLine).abs().times(2).div(10); + //console.log('absMove:', absMove); + + const skeletonLines = canvas.skeletonLines; + + console.log('skeleton line:', canvas.skeletonLines); + const changeSkeletonLine = (canvas, oldPoint, newPoint, str) => { + for (const line of canvas.skeletonLines) { + if (str === 'start' && isSamePoint(line.startPoint, oldPoint)) { + // Fabric.js 객체의 set 메서드로 속성 업데이트 + line.set({ + x1: newPoint.x, + y1: newPoint.y, + x2: line.x2 || line.endPoint?.x, + y2: line.y2 || line.endPoint?.y + }); + line.startPoint = newPoint; // 참조 업데이트 + } + else if (str === 'end' && isSamePoint(line.endPoint, oldPoint)) { + line.set({ + x1: line.x1 || line.startPoint?.x, + y1: line.y1 || line.startPoint?.y, + x2: newPoint.x, + y2: newPoint.y + }); + line.endPoint = newPoint; // 참조 업데이트 + } + } + canvas.requestRenderAll(); + console.log('skeleton line:', canvas.skeletonLines); + } + + + + if(moveFlowLine > 0) { + if(moveDirection === 'down'){ + moveDirection = 'up'; + }else if(moveDirection === 'left'){ + moveDirection = 'right'; + } + } + + console.log('skeletonBuilder moveDirection:', moveDirection); + + switch (moveDirection) { + case 'left': + // Move left: decrease X + for (const line of oppositeLine) { + if (line.position === 'left') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).minus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.start, newPoint, 'start') + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).minus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.end, newPoint, 'end') + } + break + } + } + + break; + case 'right': + for (const line of oppositeLine) { + if (line.position === 'right') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).plus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.start, newPoint, 'start') + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).plus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.end, newPoint, 'end') + } + break + } + } + + break; + case 'up': + // Move up: decrease Y (toward top of screen) + for (const line of oppositeLine) { + if (line.position === 'top') { + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.start, newPoint, 'start') + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.end, newPoint, 'end') + } + break + } + } + + break; + case 'down': + // Move down: increase Y (toward bottom of screen) + for (const line of oppositeLine) { + if (line.position === 'bottom') { + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).plus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.start, newPoint, 'start') + + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).plus(absMove).toNumber(); + //changeSkeletonLine(canvas, line.end, newPoint, 'end') + } + break + } + } + break; + } + + return newPoint; + }) } /** @@ -52,18 +164,65 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => { * @param {fabric.Object} roof - 지붕 객체 * @param baseLines */ -export const skeletonBuilder = (roofId, canvas, textMode, roof, baseLines) => { - const geoJSONPolygon = toGeoJSON(roof.points) +export const skeletonBuilder = (roofId, canvas, textMode) => { + + //처마 + let roof = canvas?.getObjects().find((object) => object.id === roofId) + //벽 + const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) + + // const hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1)) + // if (hasNonParallelLines.length > 0) { + // return + // } + + const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] + const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] + + /** 외벽선 */ + const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) + + //const skeletonLines = []; + // 1. 지붕 폴리곤 좌표 전처리 + const coordinates = preprocessPolygonCoordinates(roof.points); + if (coordinates.length < 3) { + console.warn("Polygon has less than 3 unique points. Cannot generate skeleton."); + return; + } + + const moveFlowLine = roof.moveFlowLine || 0; // Provide a default value + const moveUpDown = roof.moveUpDown || 0; // Provide a default value + + + + let points = roof.points; + + //마루이동 + if (moveFlowLine !== 0) { + points = movingRidgeFromSkeleton(roofId, canvas) + + const movePoints = { + points: points, + roofId: roofId, + } + canvas.set("movePoints", movePoints) + + } +//처마 + if(moveUpDown !== 0) { + + } + + const geoJSONPolygon = toGeoJSON(points) try { // SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거 geoJSONPolygon.pop() const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]]) - console.log(`지붕 형태: ${skeleton.roof_type}`, skeleton.edge_analysis) - // 스켈레톤 데이터를 기반으로 내부선 생성 - roof.innerLines = createInnerLinesFromSkeleton(skeleton, canvas, textMode, roof, baseLines) + roof.innerLines = roof.innerLines || []; + roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode) // 캔버스에 스켈레톤 상태 저장 if (!canvas.skeletonStates) { @@ -71,12 +230,35 @@ export const skeletonBuilder = (roofId, canvas, textMode, roof, baseLines) => { canvas.skeletonLines = [] } canvas.skeletonStates[roofId] = true + canvas.skeletonLines = []; + canvas.skeletonLines.push(...roof.innerLines) + canvas.set("skeletonLines", canvas.skeletonLines) + + const cleanSkeleton = { + Edges: skeleton.Edges.map(edge => ({ + X1: edge.Edge.Begin.X, + Y1: edge.Edge.Begin.Y, + X2: edge.Edge.End.X, + Y2: edge.Edge.End.Y, + Polygon: edge.Polygon, + + // Add other necessary properties, but skip circular references + })), + roofId: roofId, + // Add other necessary top-level properties + }; + canvas.skeleton = []; + canvas.skeleton = cleanSkeleton + + canvas.set("skeleton", cleanSkeleton); canvas.renderAll() } catch (e) { console.error('스켈레톤 생성 중 오류 발생:', e) if (canvas.skeletonStates) { canvas.skeletonStates[roofId] = false + canvas.skeletonStates = {} + canvas.skeletonLines = [] } } } @@ -90,16 +272,36 @@ export const skeletonBuilder = (roofId, canvas, textMode, roof, baseLines) => { * @param {string} textMode - 텍스트 표시 모드 ('plane', 'actual', 'none') * @param {Array} baseLines - 원본 외벽선 QLine 객체 배열 */ -const createInnerLinesFromSkeleton = (skeleton,canvas, textMode, roof, baseLines) => { +const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { if (!skeleton?.Edges) return [] + let roof = canvas?.getObjects().find((object) => object.id === roofId) const skeletonLines = [] const processedInnerEdges = new Set() // 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다. + skeleton.Edges.forEach((edgeResult, index) => { - processEavesEdge(edgeResult, processedInnerEdges, roof, skeletonLines, baseLines[index].attributes.pitch); + // const { Begin, End } = edgeResult.Edge; + // let outerLine = roof.lines.find(line => + // line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line) + // ); + // if(!outerLine){ + // + // for (const line of canvas.skeletonLines) { + // if (line.lineName === 'hip' && line.attributes.hipIndex === index) + // { + // outerLine = line; + // break; // Found the matching line, exit the loop + // } + // } + // + // } + // const pitch = outerLine.attributes?.pitch??0 + // console.log("pitch", pitch) + processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines); }); + /* // 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다. @@ -161,24 +363,45 @@ const createInnerLinesFromSkeleton = (skeleton,canvas, textMode, roof, baseLines // 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다. const innerLines = []; + const existingLines = new Set(); // 이미 추가된 라인을 추적하기 위한 Set + skeletonLines.forEach(line => { const { p1, p2, attributes, lineStyle } = line; + + // 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교) + const lineKey = [ + [p1.x, p1.y].sort().join(','), + [p2.x, p2.y].sort().join(',') + ].sort().join('|'); + + // 이미 추가된 라인인지 확인 + if (existingLines.has(lineKey)) { + return; // 이미 있는 라인이면 스킵 + } + const innerLine = new QLine([p1.x, p1.y, p2.x, p2.y], { parentId: roof.id, fontSize: roof.fontSize, stroke: lineStyle.color, strokeWidth: lineStyle.width, - name: attributes.type, - textMode: textMode, + name: (line.attributes.isOuterEdge)?'eaves': attributes.type, attributes: attributes, + isBaseLine: line.attributes.isOuterEdge, + lineName: (line.attributes.isOuterEdge)?'outerLine': attributes.type, + selectable:(!line.attributes.isOuterEdge), + roofId: roofId }); - canvas.add(innerLine); - innerLine.bringToFront(); - innerLines.push(innerLine); + //skeleton 라인에서 처마선은 삭제 + if(innerLine.lineName !== 'outerLine'){ + canvas.add(innerLine); + innerLine.bringToFront(); + existingLines.add(lineKey); // 추가된 라인을 추적 + } + innerLines.push(innerLine) + canvas.renderAll(); }); - canvas.renderAll(); return innerLines; } @@ -190,22 +413,65 @@ const createInnerLinesFromSkeleton = (skeleton,canvas, textMode, roof, baseLines * @param roof * @param pitch */ -function processEavesEdge(edgeResult, processedInnerEdges, roof, skeletonLines, pitch) { +function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { + let roof = canvas?.getObjects().find((object) => object.id === roofId) const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y })); - const currentDegree = getDegreeByChon(pitch) + //처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음 + const { Begin, End } = edgeResult.Edge; + let outerLine = roof.lines.find(line => + 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); + } + let pitch = outerLine?.attributes?.pitch??0 + + let eavesLines = [] for (let i = 0; i < polygonPoints.length; i++) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; // 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가 - if (!isOuterEdge(p1, p2, [edgeResult.Edge])) { - addRawLine(roof.id, skeletonLines, processedInnerEdges, p1, p2, 'RIDGE', '#FF0000', 3, currentDegree); - } + // if (!isOuterEdge(p1, p2, [edgeResult.Edge])) { + //외벽선 밖으로 나간 선을 정리한다(roof.line의 교점까지 정리한다) + // 지붕 경계선과 교차 확인 및 클리핑 + const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines); + console.log('clipped line', clippedLine.p1, clippedLine.p2); + const isOuterLine = isOuterEdge(p1, p2, [edgeResult.Edge]) + addRawLine(roof.id, skeletonLines, p1, p2, 'ridge', '#FF0000', 3, pitch, isOuterLine); + // } } } + +function findMatchingLine(edgePolygon, roof, roofPoints) { + const edgePoints = edgePolygon.map(p => ({ x: p.X, y: p.Y })); + + for (let i = 0; i < edgePoints.length; i++) { + const p1 = edgePoints[i]; + const p2 = edgePoints[(i + 1) % edgePoints.length]; + + for (let j = 0; j < roofPoints.length; j++) { + const rp1 = roofPoints[j]; + const rp2 = roofPoints[(j + 1) % roofPoints.length]; + + if ((isSamePoint(p1, rp1) && isSamePoint(p2, rp2)) || + (isSamePoint(p1, rp2) && isSamePoint(p2, rp1))) { + // 매칭되는 라인을 찾아서 반환 + return roof.lines.find(line => + (isSamePoint(line.p1, rp1) && isSamePoint(line.p2, rp2)) || + (isSamePoint(line.p1, rp2) && isSamePoint(line.p2, rp1)) + ); + } + } + } + return null; +} + + /** * GABLE(케라바) Edge를 처리하여 스켈레톤 선을 정리하고 연장합니다. * @param {object} edgeResult - 스켈레톤 Edge 데이터 @@ -217,7 +483,7 @@ function processEavesEdge(edgeResult, processedInnerEdges, roof, skeletonLines, function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine, lastSkeletonLines) { const edgePoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y })); //const polygons = createPolygonsFromSkeletonLines(skeletonLines, selectBaseLine); - console.log("edgePoints::::::", edgePoints) + //console.log("edgePoints::::::", edgePoints) // 1. Initialize processedLines with a deep copy of lastSkeletonLines let processedLines = [] // 1. 케라바 면과 관련된 불필요한 스켈레톤 선을 제거합니다. @@ -232,8 +498,8 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine, } } - console.log("skeletonLines::::::", skeletonLines) - console.log("lastSkeletonLines", lastSkeletonLines) + //console.log("skeletonLines::::::", skeletonLines) + //console.log("lastSkeletonLines", lastSkeletonLines) // 2. Find common lines between skeletonLines and lastSkeletonLines skeletonLines.forEach(line => { @@ -259,9 +525,9 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, selectBaseLine, // return !isEdgeLine; // }); - console.log("skeletonLines::::::", skeletonLines); - console.log("lastSkeletonLines", lastSkeletonLines); - console.log("processedLines after filtering", processedLines); + //console.log("skeletonLines::::::", skeletonLines); + //console.log("lastSkeletonLines", lastSkeletonLines); + //console.log("processedLines after filtering", processedLines); return processedLines; @@ -300,42 +566,50 @@ function isOuterEdge(p1, p2, edges) { * @param {number} width - 두께 * @param currentDegree */ -function addRawLine(id, skeletonLines, processedInnerEdges, p1, p2, lineType, color, width, currentDegree) { - 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; - processedInnerEdges.add(edgeKey); - +function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine) { + // 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; + // processedInnerEdges.add(edgeKey); + const currentDegree = getDegreeByChon(pitch) const dx = Math.abs(p2.x - p1.x); const dy = Math.abs(p2.y - p1.y); const isDiagonal = dx > 0.1 && dy > 0.1; const normalizedType = isDiagonal ? LINE_TYPE.SUBLINE.HIP : lineType; - const rawLines = [] - skeletonLines.push({ + // Count existing HIP lines + const existingEavesCount = skeletonLines.filter(line => + line.lineName === LINE_TYPE.SUBLINE.RIDGE + ).length; + + // If this is a HIP line, its index will be the existing count + const eavesIndex = normalizedType === LINE_TYPE.SUBLINE.RIDGE ? existingEavesCount : undefined; + + const newLine = { p1, p2, attributes: { - roofId:id, - + roofId: id, actualSize: (isDiagonal) ? calcLineActualSize( - { - x1: p1.x, - y1: p1.y, - x2: p2.x, - y2: p2.y - }, + { + x1: p1.x, + y1: p1.y, + x2: p2.x, + y2: p2.y + }, currentDegree - ) : calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }), - + ) : calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }), type: normalizedType, planeSize: calcLinePlaneSize({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y }), isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE, + isOuterEdge: isOuterLine, + pitch: pitch, + ...(eavesIndex !== undefined && { eavesIndex }) }, lineStyle: { color, width }, - }); - - console.log('skeletonLines', skeletonLines); + }; + skeletonLines.push(newLine); + //console.log('skeletonLines', skeletonLines); } /** @@ -812,6 +1086,8 @@ const isPointOnSegment = (point, segStart, segEnd) => { return dotProduct >= 0 && dotProduct <= squaredLength; }; + + // Export all necessary functions export { findAllIntersections, @@ -819,3 +1095,306 @@ export { createPolygonsFromSkeletonLines }; + +/** + * Finds lines in the roof that match certain criteria based on the given points + * @param {Array} lines - The roof lines to search through + * @param {Object} startPoint - The start point of the reference line + * @param {Object} endPoint - The end point of the reference line + * @param {Array} oldPoints - The old points to compare against + * @returns {Array} Array of matching line objects with their properties + */ +function findMatchingRoofLines(lines, startPoint, endPoint, oldPoints) { + const result = []; + + // If no lines provided, return empty array + if (!lines || !lines.length) return result; + + // Process each line in the roof + for (const line of lines) { + // Get the start and end points of the current line + const p1 = { x: line.x1, y: line.y1 }; + const p2 = { x: line.x2, y: line.y2 }; + + // Check if both points exist in the oldPoints array + const p1Exists = oldPoints.some(p => + Math.abs(p.x - p1.x) < 0.0001 && Math.abs(p.y - p1.y) < 0.0001 + ); + + const p2Exists = oldPoints.some(p => + Math.abs(p.x - p2.x) < 0.0001 && Math.abs(p.y - p2.y) < 0.0001 + ); + + // If both points exist in oldPoints, add to results + if (p1Exists && p2Exists) { + // Calculate line position relative to the reference line + const position = getLinePosition( + { start: p1, end: p2 }, + { start: startPoint, end: endPoint } + ); + + result.push({ + start: p1, + end: p2, + position: position, + line: line + }); + } + } + + return result; +} + +/** + * Finds the opposite line in a polygon based on the given line + * @param {Array} edges - The polygon edges from canvas.skeleton.Edges + * @param {Object} startPoint - The start point of the line to find opposite for + * @param {Object} endPoint - The end point of the line to find opposite for + * @param targetPosition + * @returns {Object|null} The opposite line if found, null otherwise + */ +function findOppositeLine(edges, startPoint, endPoint, points) { + const result = []; + // 1. 다각형 찾기 + const polygons = findPolygonsContainingLine(edges, startPoint, endPoint); + if (polygons.length === 0) return null; + + const referenceSlope = calculateSlope(startPoint, endPoint); + + // 각 다각형에 대해 처리 + for (const polygon of polygons) { + // 2. 기준 선분의 인덱스 찾기 + + let baseIndex = -1; + for (let i = 0; i < polygon.length; i++) { + const p1 = { x: polygon[i].X, y: polygon[i].Y }; + const p2 = { + x: polygon[(i + 1) % polygon.length].X, + y: polygon[(i + 1) % polygon.length].Y + }; + + + + + if ((isSamePoint(p1, startPoint) && isSamePoint(p2, endPoint)) || + (isSamePoint(p1, endPoint) && isSamePoint(p2, startPoint))) { + baseIndex = i; + break; + } + } + + if (baseIndex === -1) continue; // 현재 다각형에서 기준 선분을 찾지 못한 경우 + + // 3. 다각형의 각 선분을 순회하면서 평행한 선분 찾기 + const polyLength = polygon.length; + for (let i = 0; i < polyLength; i++) { + if (i === baseIndex) continue; // 기준 선분은 제외 + + const p1 = { x: polygon[i].X, y: polygon[i].Y }; + const p2 = { + x: polygon[(i + 1) % polyLength].X, + y: polygon[(i + 1) % polyLength].Y + }; + + + const p1Exist = points.some(p => + Math.abs(p.x - p1.x) < 0.0001 && Math.abs(p.y - p1.y) < 0.0001 + ); + + const p2Exist = points.some(p => + Math.abs(p.x - p2.x) < 0.0001 && Math.abs(p.y - p2.y) < 0.0001 + ); + + if(p1Exist && p2Exist){ + const position = getLinePosition( + { start: p1, end: p2 }, + { start: startPoint, end: endPoint } + ); + result.push({ + start: p1, + end: p2, + position: position, + polygon: polygon + }); + } + + // // 현재 선분의 기울기 계산 + // const currentSlope = calculateSlope(p1, p2); + // + // // 기울기가 같은지 확인 (평행한 선분) + // if (areLinesParallel(referenceSlope, currentSlope)) { + // // 동일한 선분이 아닌지 확인 + // if (!areSameLine(p1, p2, startPoint, endPoint)) { + // const position = getLinePosition( + // { start: p1, end: p2 }, + // { start: startPoint, end: endPoint } + // ); + // + // const lineMid = { + // x: (p1.x + p2.x) / 2, + // y: (p1.y + p2.y) / 2 + // }; + // + // const baseMid = { + // x: (startPoint.x + endPoint.x) / 2, + // y: (startPoint.y + endPoint.y) / 2 + // }; + // const distance = Math.sqrt( + // Math.pow(lineMid.x - baseMid.x, 2) + + // Math.pow(lineMid.y - baseMid.y, 2) + // ); + // + // const existingIndex = result.findIndex(line => line.position === position); + // + // if (existingIndex === -1) { + // // If no line with this position exists, add it + // result.push({ + // start: p1, + // end: p2, + // position: position, + // polygon: polygon, + // distance: distance + // }); + // } else if (distance > result[existingIndex].distance) { + // // If a line with this position exists but is closer, replace it + // result[existingIndex] = { + // start: p1, + // end: p2, + // position: position, + // polygon: polygon, + // distance: distance + // }; + // } + // } + // } + } + } + + return result.length > 0 ? result:[]; + +} + +function getLinePosition(line, referenceLine) { + const lineMidX = (line.start.x + line.end.x) / 2; + const lineMidY = (line.start.y + line.end.y) / 2; + const refMidX = (referenceLine.start.x + referenceLine.end.x) / 2; + const refMidY = (referenceLine.start.y + referenceLine.end.y) / 2; + + // Y축 차이가 더 크면 위/아래로 판단 + // Y축 차이가 더 크면 위/아래로 판단 + if (Math.abs(lineMidY - refMidY) > Math.abs(lineMidX - refMidX)) { + return lineMidY > refMidY ? 'bottom' : 'top'; + } + // X축 차이가 더 크면 왼쪽/오른쪽으로 판단 + else { + return lineMidX > refMidX ? 'right' : 'left'; + } +} + +/** + * Helper function to find if two points are the same within a tolerance + */ +function isSamePoint(p1, p2, tolerance = 0.1) { + return Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.y - p2.y) < tolerance; +} + +// 두 점을 지나는 직선의 기울기 계산 +function calculateSlope(p1, p2) { + // 수직선인 경우 (기울기 무한대) + if (p1.x === p2.x) return Infinity; + return (p2.y - p1.y) / (p2.x - p1.x); +} + +// 두 직선이 평행한지 확인 +// 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 + */ +function findPolygonsContainingLine(edges, p1, p2) { + const polygons = []; + for (const edge of edges) { + const polygon = edge.Polygon; + for (let i = 0; i < polygon.length; i++) { + const ep1 = { x: polygon[i].X, y: polygon[i].Y }; + const ep2 = { + x: polygon[(i + 1) % polygon.length].X, + y: polygon[(i + 1) % polygon.length].Y + }; + + if ((isSamePoint(ep1, p1) && isSamePoint(ep2, p2)) || + (isSamePoint(ep1, p2) && isSamePoint(ep2, p1))) { + polygons.push(polygon); + break; // 이 다각형에 대한 검사 완료 + } + } + } + return polygons; // 일치하는 모든 다각형 반환 +} + +/** + * roof.lines와 교차하는 선분(p1, p2)을 찾아 교차점에서 자릅니다. + * @param {Object} p1 - 선분의 시작점 {x, y} + * @param {Object} p2 - 선분의 끝점 {x, y} + * @param {Array} roofLines - 지붕 경계선 배열 (QLine 객체의 배열) + * @returns {Object} {p1: {x, y}, p2: {x, y}} - 교차점에서 자른 선분 또는 원래 선분 + */ +function clipLineToRoofBoundary(p1, p2, roofLines) { + if (!roofLines || !roofLines.length) return { p1, p2 }; + + let closestIntersection = null; + let minDistSq = Infinity; + const originalP1 = { ...p1 }; + const originalP2 = { ...p2 }; + + // 모든 지붕 경계선과의 교차점을 찾음 + for (const line of roofLines) { + const lineP1 = { x: line.x1, y: line.y1 }; + const lineP2 = { x: line.x2, y: line.y2 }; + + const intersection = getLineIntersection( + p1, p2, + lineP1, lineP2 + ); + + if (intersection) { + // 교차점과 p1 사이의 거리 계산 + const dx = intersection.x - p1.x; + const dy = intersection.y - p1.y; + const distSq = dx * dx + dy * dy; + + // p1에 가장 가까운 교차점 찾기 + if (distSq < minDistSq) { + minDistSq = distSq; + closestIntersection = intersection; + } + } + } + + // 교차점이 있으면 p2를 가장 가까운 교차점으로 업데이트 + if (closestIntersection) { + return { + p1: originalP1, + p2: closestIntersection + }; + } + + // 교차점이 없으면 원래 선분 반환 + return { p1: originalP1, p2: originalP2 }; +} + +// 기존 getLineIntersection 함수를 사용하거나, 없으면 아래 구현 사용