From 03c31c82ff7e8ce7c71d0d29aa4bc773dd6ac5c4 Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 31 Oct 2025 18:08:23 +0900 Subject: [PATCH] =?UTF-8?q?log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 78 +- src/util/skeleton-utils.js | 1104 +++++++++++++++++---- 2 files changed, 941 insertions(+), 241 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index f421a3bb..91ca0093 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -8,6 +8,7 @@ import { useSwal } from '@/hooks/useSwal' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' import { calcLinePlaneSize } from '@/util/qpolygon-utils' +import { getSelectLinePosition } from '@/util/skeleton-utils' import { useMouse } from '@/hooks/useMouse' //동선이동 형 올림 내림 @@ -285,7 +286,14 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - let linePosition = classifyWallLine(wall, target).position; + const result = getSelectLinePosition(wall, target, { + testDistance: 5, // 테스트 거리 + debug: true // 디버깅 로그 출력 + }); + //console.log("1111litarget:::::", target); + //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' + + let linePosition = result.position; if (target.y1 === target.y2) { //수평벽 @@ -440,7 +448,8 @@ export function useMovementSetting(id) { roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; roof.moveUpDown = parseInt(moveUpDown, 10) || 0; roof.moveDirect = ""; - roof.moveSelectLine = target; + roof.moveSelectLine = target + //console.log("target::::", target, roof.moveSelectLine) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const baseLines = wall.baseLines let centerPoint = wall.getCenterPoint(); @@ -582,7 +591,7 @@ export function useMovementSetting(id) { value = value.neg() } } else { - console.log("error::", UP_DOWN_REF) + //console.log("error::", UP_DOWN_REF) value = UP_DOWN_REF.FILLED_INPUT_REF.current.value !== '' ? Big(UP_DOWN_REF.FILLED_INPUT_REF.current.value) @@ -606,10 +615,22 @@ export function useMovementSetting(id) { // value = value.neg() // } } + // console.log("2222titarget:::::", target); + // console.log("2222저장된 moveSelectLine:", roof.moveSelectLine); + // console.log("222wall::::", wall.points) + const result = getSelectLinePosition(wall, target, { + testDistance: 5, // 테스트 거리 + debug: true // 디버깅 로그 출력 + }); - let linePosition = classifyWallLine(wall, target).position; + //console.log("2222linePosition:::::", result.position); + +// 디버깅용 분류 결과 확인 + + let linePosition = result.position; + roof.movePosition = linePosition value = value.div(10) targetBaseLines .filter((line) => Math.sqrt(Math.pow(line.line.x2 - line.line.x1, 2) + Math.pow(line.line.y2 - line.line.y1, 2)) >= 1) @@ -617,7 +638,7 @@ export function useMovementSetting(id) { const currentLine = target.line //console.log("linePosition::::::::::::::", linePosition) - if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked ){ + if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked ){ //position확인 if(linePosition === 'bottom' || linePosition === 'right') { //console.log("1value::::::::::::::", value.toString()) @@ -685,52 +706,6 @@ export function useMovementSetting(id) { } // javascript - const classifyWallLine = (wall, line, epsilon = 0.5, offset = 10)=> { - if (!wall || !line) return { orientation: 'unknown', position: 'unknown' } - - const center = wall.getCenterPoint() - const dx = Math.abs(line.x2 - line.x1) - const dy = Math.abs(line.y2 - line.y1) - - let orientation = 'slanted' - if (dy < epsilon && dx >= epsilon) orientation = 'horizontal' - else if (dx < epsilon && dy >= epsilon) orientation = 'vertical' - else orientation = dx > dy ? 'horizontal' : 'vertical' - - const midX = (line.x1 + line.x2) / 2 - const midY = (line.y1 + line.y2) / 2 - - // checkPoint: mid에서 center 방향으로 약간 이동한 점을 계산 - let checkX = midX - let checkY = midY - if (orientation === 'horizontal') { - const dir = Math.sign(center.y - midY) || 1 - checkY = midY + dir * offset - } else if (orientation === 'vertical') { - const dir = Math.sign(center.x - midX) || 1 - checkX = midX + dir * offset - } else { - const dirX = Math.sign(center.x - midX) || 1 - const dirY = Math.sign(center.y - midY) || 1 - checkX = midX + dirX * offset - checkY = midY + dirY * offset - } - - const inside = typeof wall.inPolygon === 'function' ? wall.inPolygon({ x: checkX, y: checkY }) : true - - let position = 'unknown' - if (orientation === 'horizontal') { - const dir = center.y - midY - if (inside) position = dir > 0 ? 'top' : 'bottom' - else position = dir > 0 ? 'bottom' : 'top' - } else if (orientation === 'vertical') { - const dir = center.x - midX - if (inside) position = dir > 0 ? 'left' : 'right' - else position = dir > 0 ? 'right' : 'left' - } - - return { orientation, position, mid: { x: midX, y: midY }, center, checkPoint: { x: checkX, y: checkY }, inside } - } return { @@ -742,7 +717,6 @@ export function useMovementSetting(id) { FLOW_LINE_REF, UP_DOWN_REF, handleSave, - classifyWallLine } } diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index d7168e74..0571569b 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -6,6 +6,7 @@ import { getDegreeByChon } from '@/util/canvas-util' import Big from 'big.js' import { line } from 'framer-motion/m' import { QPolygon } from '@/components/fabric/QPolygon' +import { point } from '@turf/turf' /** * 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다. @@ -23,13 +24,17 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => { } -const movingRidgeFromSkeleton = (roofId, canvas) => { +const movingLineFromSkeleton = (roofId, canvas) => { let roof = canvas?.getObjects().find((object) => object.id === roofId) + let moveDirection = roof.moveDirect; let moveFlowLine = roof.moveFlowLine??0; - const selectLine = roof.moveSelectLine; + let moveUpDown = roof.moveUpDown??0; + const getSelectLine = () => roof.moveSelectLine; + const selectLine = getSelectLine(); + let movePosition = roof.movePosition; const startPoint = selectLine.startPoint const endPoint = selectLine.endPoint @@ -37,6 +42,10 @@ const movingRidgeFromSkeleton = (roofId, canvas) => { const oldPoints = canvas?.skeleton.lastPoints ?? orgRoofPoints // 여기도 변경 const oppositeLine = findOppositeLine(canvas.skeleton.Edges, startPoint, endPoint, oldPoints); + const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) + const baseLines = wall.baseLines + roof.basePoints = createOrderedBasePoints(roof.points, baseLines) + const skeletonPolygon = canvas.getObjects().filter((object) => object.skeletonType === 'polygon' && object.parentId === roofId) const skeletonLines = canvas.getObjects().filter((object) => object.skeletonType === 'line' && object.parentId === roofId) @@ -46,193 +55,261 @@ const movingRidgeFromSkeleton = (roofId, canvas) => { console.log('No opposite line found'); } - let baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []; - console.log('baseLines::::', baseLines); - let baseLinePoints = baseLines.map((line) => ({x:line.x1, y:line.y1})); + if(moveFlowLine !== 0) { + return oldPoints.map((point, index) => { + console.log('Point:', point); + const newPoint = { ...point }; + const absMove = Big(moveFlowLine).times(2).div(10); + + console.log('skeletonBuilder moveDirection:', moveDirection); + + switch (moveDirection) { + case 'left': + // Move left: decrease X + if (moveFlowLine !== 0) { + for (const line of oppositeLine) { + if (line.position === 'left') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).plus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).plus(absMove).toNumber(); + } + + break; + + } + + } + } else if (moveUpDown !== 0) { + + } + + break; + case 'right': + for (const line of oppositeLine) { + if (line.position === 'right') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).minus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).minus(absMove).toNumber(); + } + 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(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + break; + + } + } + + break; + case 'down': + // Move down: increase Y (toward bottom of screen) + for (const line of oppositeLine) { + + if (line.position === 'bottom') { + + console.log('oldPoint:', point); + + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + break; + } - /* - walls.forEach((wall) => { - if (wall.baseLines.length === 0) { - wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id) + } + break; + default : +// 사용 예시 } - // Extract points from each baseLine - wall.baseLines.forEach(line => { - console.log("useSk:::", line.x1, line.y1, line.x2, line.y2); - // 시작점과 끝점을 배열에 추가 - const points = [ - { x: line.x1, y: line.y1 }, - { x: line.x2, y: line.y2 } - ]; - - points.forEach(point => { - const key = `${point.x},${point.y}`; - if (!pointSet.has(key)) { - pointSet.add(key); - baseLinePoints.push(point); - } - }); - }); - + console.log('newPoint:', newPoint); + //baseline 변경 + return newPoint; }) - return [...baseLinePoints]; - */ - - return oldPoints.map((point, index) => { - - const newPoint = { ...point }; - const absMove = Big(moveFlowLine).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); - // } + } else if(moveUpDown !== 0) { + // const selectLine = getSelectLine(); + // + // console.log("wall::::", wall.points) + // console.log("저장된 3333moveSelectLine:", roof.moveSelectLine); + // console.log("저장된 3moveSelectLine:", selectLine); + // const result = getSelectLinePosition(wall, selectLine, { + // testDistance: 5, // 테스트 거리 + // debug: true // 디버깅 로그 출력 + // }); + // console.log("3333linePosition:::::", result.position); - console.log('skeletonBuilder moveDirection:', moveDirection); + const position = movePosition //result.position; + const absMove = Big(moveUpDown).times(1).div(10); + const modifiedStartPoints = []; + // oldPoints를 복사해서 새로운 points 배열 생성 + let newPoints = oldPoints.map(point => ({...point})); - 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).plus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.x = Big(line.end.x).plus(absMove).toNumber(); - } + // selectLine과 일치하는 baseLines 찾기 + const matchingLines = baseLines + .map((line, index) => ({ ...line, findIndex: index })) + .filter(line => + (isSamePoint(line.startPoint, selectLine.startPoint) && + isSamePoint(line.endPoint, selectLine.endPoint)) || + (isSamePoint(line.startPoint, selectLine.endPoint) && + isSamePoint(line.endPoint, selectLine.startPoint)) + ); - break; - // } else if (line.position === 'right') { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.x = Big(line.start.x).minus(absMove).toNumber(); - // (newPoint.x < originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // }else if(isSamePoint(newPoint, line.end)) { - // newPoint.x = Big(line.end.x).minus(absMove).toNumber(); - // (newPoint.x < originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } - } - } + matchingLines.forEach(line => { + const originalStartPoint = line.startPoint; + const originalEndPoint = line.endPoint; + const offset = line.attributes.offset + // 새로운 좌표 계산 + let newStartPoint = {...originalStartPoint}; + let newEndPoint = {...originalEndPoint}; - break; - case 'right': - for (const line of oppositeLine) { - if (line.position === 'right') { - if (isSamePoint(newPoint, line.start)) { - newPoint.x = Big(line.start.x).minus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.x = Big(line.end.x).minus(absMove).toNumber(); - } - break - // }else if(line.position === 'left') { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.x = Big(line.start.x).plus(absMove).toNumber(); - // (newPoint.x > originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } else if (isSamePoint(newPoint, line.end)) { - // newPoint.x= Big(line.end.x).plus(absMove).toNumber(); - // (newPoint.x > originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } - } - - } - - 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(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + // 위치와 방향에 따라 좌표 조정 +/* + switch (position) { + case 'left': + if (moveDirection === 'up') { + newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); + } + break; + case 'right': + if (moveDirection === 'up') { + newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); + } + break; + case 'top': + if (moveDirection === 'up') { + newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); + } + break; + case 'bottom': + if (moveDirection === 'up') { + newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); } break; - - // }else if(line.position === 'bottom') { - // if(newPoint.y !== originalPoint.y) { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.y = Big(line.start.y).minus(absMove).toNumber(); - // (newPoint.y < originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // }else if(isSamePoint(newPoint, line.end)) { - // newPoint.y = Big(line.end.y).minus(absMove).toNumber(); - // (newPoint.y < originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } - // } - } } - - break; - case 'down': - // Move down: increase Y (toward bottom of screen) - for (const line of oppositeLine) { - - if (line.position === 'bottom') { - - console.log('oldPoint:', point); - - if (isSamePoint(newPoint, line.start)) { - newPoint.y = Big(line.start.y).minus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.y = Big(line.end.y).minus(absMove).toNumber(); +*/ +// 원본 라인 업데이트 + // newPoints 배열에서 일치하는 포인트들을 찾아서 업데이트 + console.log('absMove::', absMove); + newPoints.forEach((point, index) => { + if(position === 'bottom'){ + if (moveDirection === 'in') { + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + }else if (moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + } + + }else if (position === 'top'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + } + + }else if(position === 'left'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + } + + }else if(position === 'right'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } } - // }else if(line.position === 'top') { - // - // if(newPoint.y !== originalPoint.y) { - // - // if (isSamePoint(newPoint, line.start)) { - // newPoint.y = Big(line.start.y).plus(absMove).toNumber(); - // (newPoint.y > originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } else if (isSamePoint(newPoint, line.end)) { - // newPoint.y = Big(line.end.y).plus(absMove).toNumber(); - // (newPoint.y > originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } - // } - break; } - } - break; - } - - - console.log('newPoint:', newPoint); - //baseline 변경 - return newPoint; - }) + }); + // 원본 baseLine도 업데이트 + line.startPoint = newStartPoint; + line.endPoint = newEndPoint; + }); + return newPoints; + } } + /** * SkeletonBuilder를 사용하여 스켈레톤을 생성하고 내부선을 그립니다. * @param {string} roofId - 지붕 ID @@ -281,18 +358,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { //마루이동 - if (moveFlowLine !== 0) { - - - points = movingRidgeFromSkeleton(roofId, canvas) - - + if (moveFlowLine !== 0 || moveUpDown !== 0) { + points = movingLineFromSkeleton(roofId, canvas) } -//처마 - if(moveUpDown !== 0) { - } + console.log('points:', points); const geoJSONPolygon = toGeoJSON(points) @@ -313,6 +384,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { canvas.skeletonStates[roofId] = true canvas.skeletonLines = []; canvas.skeletonLines.push(...roof.innerLines) + roof.skeletonLines = canvas.skeletonLines; const cleanSkeleton = { Edges: skeleton.Edges.map(edge => ({ @@ -355,7 +427,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { if (!skeleton?.Edges) return [] let roof = canvas?.getObjects().find((object) => object.id === roofId) - const skeletonLines = [] + let skeletonLines = [] const processedInnerEdges = new Set() // 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다. @@ -366,10 +438,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines); }); - -/* // 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다. - skeleton.Edges.forEach(edgeResult => { const { Begin, End } = edgeResult.Edge; @@ -401,9 +470,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } }); -* + //2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다. - const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, baseLines); + const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines); if(disconnectedLines.length > 0) { @@ -424,7 +493,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } -*/ + // 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다. const innerLines = []; @@ -514,7 +583,7 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { ); if(!outerLine) { outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points); - console.log('Has matching line:', outerLine); + //console.log('Has matching line:', outerLine); } let pitch = outerLine?.attributes?.pitch??0 @@ -543,11 +612,9 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; - // 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가 - // if (!isOuterEdge(p1, p2, [edgeResult.Edge])) { - //외벽선 밖으로 나간 선을 정리한다(roof.line의 교점까지 정리한다) + // 지붕 경계선과 교차 확인 및 클리핑 - const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines); + 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]) addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', 'red', 5, pitch, isOuterLine); @@ -1471,13 +1538,18 @@ function findPolygonsContainingLine(edges, p1, p2) { * @param {Object} p1 - 선분의 시작점 {x, y} * @param {Object} p2 - 선분의 끝점 {x, y} * @param {Array} roofLines - 지붕 경계선 배열 (QLine 객체의 배열) + * @param skeletonLines * @returns {Object} {p1: {x, y}, p2: {x, y}} - 다각형 내부로 클리핑된 선분 */ -function clipLineToRoofBoundary(p1, p2, roofLines) { +function clipLineToRoofBoundary(p1, p2, roofLines, selectLine) { if (!roofLines || !roofLines.length) { return { p1: { ...p1 }, p2: { ...p2 } }; } + const dx = Math.abs(p2.x - p1.x); + const dy = Math.abs(p2.y - p1.y); + const isDiagonal = dx > 0.5 && dy > 0.5; + // 기본값으로 원본 좌표 설정 let clippedP1 = { x: p1.x, y: p1.y }; let clippedP2 = { x: p2.x, y: p2.y }; @@ -1492,6 +1564,10 @@ function clipLineToRoofBoundary(p1, p2, roofLines) { // 두 점 모두 내부에 있으면 그대로 반환 if (p1Inside && p2Inside) { + if(!selectLine || isDiagonal){ + return { p1: clippedP1, p2: clippedP2 }; + } + console.log('평행선::', clippedP1, clippedP2) return { p1: clippedP1, p2: clippedP2 }; } @@ -1516,7 +1592,7 @@ function clipLineToRoofBoundary(p1, p2, roofLines) { } } - console.log('Found intersections:', intersections.length); + //console.log('Found intersections:', intersections.length); // 교차점들을 t 값으로 정렬 intersections.sort((a, b) => a.t - b.t); @@ -1634,4 +1710,654 @@ function getLineDirection(p1, p2) { if ((angle >= 45 && angle < 135)) return 'bottom'; if ((angle >= 135 || angle < -135)) return 'left'; return 'top'; // (-135 ~ -45) +} + +// selectLine과 baseLines 비교하여 방향 찾기 +function findLineDirection(selectLine, baseLines) { + for (const baseLine of baseLines) { + // baseLine의 시작점과 끝점 + const baseStart = baseLine.startPoint; + const baseEnd = baseLine.endPoint; + + // selectLine의 시작점과 끝점 + const selectStart = selectLine.startPoint; + const selectEnd = selectLine.endPoint; + + // 정방향 또는 역방향으로 일치하는지 확인 + if ((isSamePoint(baseStart, selectStart) && isSamePoint(baseEnd, selectEnd)) || + (isSamePoint(baseStart, selectEnd) && isSamePoint(baseEnd, selectStart))) { + + // baseLine의 방향 계산 + const dx = baseEnd.x - baseStart.x; + const dy = baseEnd.y - baseStart.y; + + // 기울기를 바탕으로 방향 판단 + if (Math.abs(dx) > Math.abs(dy)) { + return dx > 0 ? 'right' : 'left'; + } else { + return dy > 0 ? 'down' : 'up'; + } + } + } + + return null; // 일치하는 라인이 없는 경우 +} + +function getLinePositionRelativeToWall(selectLine, wall) { + // wall의 경계를 가져옵니다. + const bounds = wall.getBoundingRect(); + const { left, top, width, height } = bounds; + const right = left + width; + const bottom = top + height; + + // selectLine의 중간점을 계산합니다. + const midX = (selectLine.startPoint.x + selectLine.endPoint.x) / 2; + const midY = (selectLine.startPoint.y + selectLine.endPoint.y) / 2; + + // 경계로부터의 거리를 계산합니다. + const distanceToLeft = Math.abs(midX - left); + const distanceToRight = Math.abs(midX - right); + const distanceToTop = Math.abs(midY - top); + const distanceToBottom = Math.abs(midY - bottom); + + // 가장 가까운 경계를 찾습니다. + const minDistance = Math.min( + distanceToLeft, + distanceToRight, + distanceToTop, + distanceToBottom + ); + + // 가장 가까운 경계를 반환합니다. + if (minDistance === distanceToLeft) return 'left'; + if (minDistance === distanceToRight) return 'right'; + if (minDistance === distanceToTop) return 'top'; + return 'bottom'; +} + +/** + * Convert a line into an array of coordinate points + * @param {Object} line - Line object with startPoint and endPoint + * @param {Object} line.startPoint - Start point with x, y coordinates + * @param {Object} line.endPoint - End point with x, y coordinates + * @param {number} [step=1] - Distance between points (default: 1) + * @returns {Array} Array of points [{x, y}, ...] + */ +function lineToPoints(line, step = 1) { + const { startPoint, endPoint } = line; + const points = []; + + // Add start point + points.push({ x: startPoint.x, y: startPoint.y }); + + // Calculate distance between points + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const steps = Math.ceil(distance / step); + + // Add intermediate points + for (let i = 1; i < steps; i++) { + const t = i / steps; + points.push({ + x: startPoint.x + dx * t, + y: startPoint.y + dy * t + }); + } + + // Add end point + points.push({ x: endPoint.x, y: endPoint.y }); + + return points; +} + +/** + * 다각형의 모든 좌표를 offset만큼 안쪽/바깥쪽으로 이동 + * @param {Array} points - 다각형 좌표 배열 [{x, y}, ...] + * @param {number} offset - offset 값 (양수: 안쪽, 음수: 바깥쪽) + * @returns {Array} offset이 적용된 새로운 좌표 배열 + */ +function offsetPolygon(points, offset) { + if (points.length < 3) return points; + + const offsetPoints = []; + const numPoints = points.length; + + for (let i = 0; i < numPoints; i++) { + const prevIndex = (i - 1 + numPoints) % numPoints; + const currentIndex = i; + const nextIndex = (i + 1) % numPoints; + + const prevPoint = points[prevIndex]; + const currentPoint = points[currentIndex]; + const nextPoint = points[nextIndex]; + + // 이전 변의 방향 벡터 + const prevVector = { + x: currentPoint.x - prevPoint.x, + y: currentPoint.y - prevPoint.y + }; + + // 다음 변의 방향 벡터 + const nextVector = { + x: nextPoint.x - currentPoint.x, + y: nextPoint.y - currentPoint.y + }; + + // 정규화 + const prevLength = Math.sqrt(prevVector.x * prevVector.x + prevVector.y * prevVector.y); + const nextLength = Math.sqrt(nextVector.x * nextVector.x + nextVector.y * nextVector.y); + + if (prevLength === 0 || nextLength === 0) continue; + + const prevNormal = { + x: -prevVector.y / prevLength, + y: prevVector.x / prevLength + }; + + const nextNormal = { + x: -nextVector.y / nextLength, + y: nextVector.x / nextLength + }; + + // 평균 법선 벡터 계산 + const avgNormal = { + x: (prevNormal.x + nextNormal.x) / 2, + y: (prevNormal.y + nextNormal.y) / 2 + }; + + // 평균 법선 벡터 정규화 + const avgLength = Math.sqrt(avgNormal.x * avgNormal.x + avgNormal.y * avgNormal.y); + if (avgLength === 0) continue; + + const normalizedAvg = { + x: avgNormal.x / avgLength, + y: avgNormal.y / avgLength + }; + + // 각도 보정 (예각일 때 offset 조정) + const cosAngle = prevNormal.x * nextNormal.x + prevNormal.y * nextNormal.y; + const adjustedOffset = Math.abs(cosAngle) > 0.1 ? offset / Math.abs(cosAngle) : offset; + + // 새로운 점 계산 + const offsetPoint = { + x: currentPoint.x + normalizedAvg.x * adjustedOffset, + y: currentPoint.y + normalizedAvg.y * adjustedOffset + }; + + offsetPoints.push(offsetPoint); + } + + return offsetPoints; +} + +/** + * baseLines를 연결하여 다각형 순서로 정렬된 점들 반환 + * @param {Array} baseLines - 라인 배열 + * @returns {Array} 순서대로 정렬된 점들의 배열 + */ +function getOrderedBasePoints(baseLines) { + if (baseLines.length === 0) return []; + + const points = []; + const usedLines = new Set(); + + // 첫 번째 라인으로 시작 + let currentLine = baseLines[0]; + points.push({ ...currentLine.startPoint }); + points.push({ ...currentLine.endPoint }); + usedLines.add(0); + + let lastPoint = currentLine.endPoint; + + // 연결된 라인들을 찾아가며 점들 수집 + while (usedLines.size < baseLines.length) { + let foundNext = false; + + for (let i = 0; i < baseLines.length; i++) { + if (usedLines.has(i)) continue; + + const line = baseLines[i]; + + // 현재 끝점과 연결되는 라인 찾기 + if (isSamePoint(lastPoint, line.startPoint)) { + points.push({ ...line.endPoint }); + lastPoint = line.endPoint; + usedLines.add(i); + foundNext = true; + break; + } else if (isSamePoint(lastPoint, line.endPoint)) { + points.push({ ...line.startPoint }); + lastPoint = line.startPoint; + usedLines.add(i); + foundNext = true; + break; + } + } + + if (!foundNext) break; // 연결되지 않는 경우 중단 + } + + // 마지막 점이 첫 번째 점과 같으면 제거 (닫힌 다각형) + if (points.length > 2 && isSamePoint(points[0], points[points.length - 1])) { + points.pop(); + } + + return points; +} + +/** + * roof.points와 baseLines가 정확히 대응되는 경우의 간단한 버전 + */ +function createOrderedBasePoints(roofPoints, baseLines) { + const basePoints = []; + + // baseLines에서 연결된 순서대로 점들을 추출 + const orderedBasePoints = getOrderedBasePoints(baseLines); + + // roofPoints의 개수와 맞추기 + if (orderedBasePoints.length >= roofPoints.length) { + return orderedBasePoints.slice(0, roofPoints.length); + } + + // 부족한 경우 roofPoints 기반으로 보완 + roofPoints.forEach((roofPoint, index) => { + if (index < orderedBasePoints.length) { + basePoints.push(orderedBasePoints[index]); + } else { + basePoints.push({ ...roofPoint }); // fallback + } + }); + + return basePoints; +} + +export const getSelectLinePosition = (wall, selectLine, options = {}) => { + const { testDistance = 10, epsilon = 0.5, debug = false } = options; + + if (!wall || !selectLine) { + if (debug) console.log('ERROR: wall 또는 selectLine이 없음'); + return { position: 'unknown', orientation: 'unknown', error: 'invalid_input' }; + } + + // selectLine의 좌표 추출 + const lineCoords = extractLineCoords(selectLine); + if (!lineCoords.valid) { + if (debug) console.log('ERROR: selectLine 좌표가 유효하지 않음'); + return { position: 'unknown', orientation: 'unknown', error: 'invalid_coords' }; + } + + const { x1, y1, x2, y2 } = lineCoords; + + // 라인 방향 분석 + const lineInfo = analyzeLineOrientation(x1, y1, x2, y2, epsilon); + + if (debug) { + console.log('=== getSelectLinePosition ==='); + console.log('selectLine 좌표:', lineCoords); + console.log('라인 방향:', lineInfo.orientation); + } + + // 라인의 중점 + const midX = (x1 + x2) / 2; + const midY = (y1 + y2) / 2; + + let position = 'unknown'; + + if (lineInfo.orientation === 'horizontal') { + // 수평선: top 또는 bottom 판단 + + // 바로 위쪽 테스트 포인트 + const topTestPoint = { x: midX, y: midY - testDistance }; + // 바로 아래쪽 테스트 포인트 + const bottomTestPoint = { x: midX, y: midY + testDistance }; + + const topIsInside = checkPointInPolygon(topTestPoint, wall); + const bottomIsInside = checkPointInPolygon(bottomTestPoint, wall); + + if (debug) { + console.log('수평선 테스트:'); + console.log(' 위쪽 포인트:', topTestPoint, '-> 내부:', topIsInside); + console.log(' 아래쪽 포인트:', bottomTestPoint, '-> 내부:', bottomIsInside); + } + + // top 조건: 위쪽이 외부, 아래쪽이 내부 + if (!topIsInside && bottomIsInside) { + position = 'top'; + } + // bottom 조건: 위쪽이 내부, 아래쪽이 외부 + else if (topIsInside && !bottomIsInside) { + position = 'bottom'; + } + + } else if (lineInfo.orientation === 'vertical') { + // 수직선: left 또는 right 판단 + + // 바로 왼쪽 테스트 포인트 + const leftTestPoint = { x: midX - testDistance, y: midY }; + // 바로 오른쪽 테스트 포인트 + const rightTestPoint = { x: midX + testDistance, y: midY }; + + const leftIsInside = checkPointInPolygon(leftTestPoint, wall); + const rightIsInside = checkPointInPolygon(rightTestPoint, wall); + + if (debug) { + console.log('수직선 테스트:'); + console.log(' 왼쪽 포인트:', leftTestPoint, '-> 내부:', leftIsInside); + console.log(' 오른쪽 포인트:', rightTestPoint, '-> 내부:', rightIsInside); + } + + // left 조건: 왼쪽이 외부, 오른쪽이 내부 + if (!leftIsInside && rightIsInside) { + position = 'left'; + } + // right 조건: 오른쪽이 외부, 왼쪽이 내부 + else if (leftIsInside && !rightIsInside) { + position = 'right'; + } + + } else { + // 대각선 + if (debug) console.log('대각선은 지원하지 않음'); + return { position: 'unknown', orientation: 'diagonal', error: 'not_supported' }; + } + + const result = { + position, + orientation: lineInfo.orientation, + method: 'inside_outside_test', + confidence: position !== 'unknown' ? 1.0 : 0.0, + testPoints: lineInfo.orientation === 'horizontal' ? { + top: { x: midX, y: midY - testDistance }, + bottom: { x: midX, y: midY + testDistance } + } : { + left: { x: midX - testDistance, y: midY }, + right: { x: midX + testDistance, y: midY } + }, + midPoint: { x: midX, y: midY } + }; + + if (debug) { + console.log('최종 결과:', result); + } + + return result; +}; + +// 점이 다각형 내부에 있는지 확인하는 함수 +const checkPointInPolygon = (point, wall) => { + // 1. wall의 inPolygon 메서드가 있으면 사용 + if (typeof wall.inPolygon === 'function') { + return wall.inPolygon(point); + } + + // 2. wall.baseLines를 이용한 Ray Casting Algorithm + if (!wall.baseLines || !Array.isArray(wall.baseLines)) { + console.warn('wall.baseLines가 없습니다'); + return false; + } + + return raycastingAlgorithm(point, wall.baseLines); +}; + +// Ray Casting Algorithm 구현 +const raycastingAlgorithm = (point, lines) => { + const { x, y } = point; + let intersectionCount = 0; + + for (const line of lines) { + const coords = extractLineCoords(line); + if (!coords.valid) continue; + + const { x1, y1, x2, y2 } = coords; + + // Ray casting: 점에서 오른쪽으로 수평선을 그어서 다각형 경계와의 교점 개수를 셈 + // 교점 개수가 홀수면 내부, 짝수면 외부 + + // 선분의 y 범위 확인 + if ((y1 > y) !== (y2 > y)) { + // x 좌표에서의 교점 계산 + const intersectX = (x2 - x1) * (y - y1) / (y2 - y1) + x1; + + // 점의 오른쪽에 교점이 있으면 카운트 + if (x < intersectX) { + intersectionCount++; + } + } + } + + // 홀수면 내부, 짝수면 외부 + return intersectionCount % 2 === 1; +}; + +// 라인 객체에서 좌표를 추출하는 헬퍼 함수 (중복 방지용 - 이미 있다면 제거) +const extractLineCoords = (line) => { + if (!line) { + return { x1: 0, y1: 0, x2: 0, y2: 0, valid: false }; + } + + let x1, y1, x2, y2; + + // 다양한 라인 객체 형태에 대응 + if (line.x1 !== undefined && line.y1 !== undefined && + line.x2 !== undefined && line.y2 !== undefined) { + x1 = line.x1; + y1 = line.y1; + x2 = line.x2; + y2 = line.y2; + } + else if (line.startPoint && line.endPoint) { + x1 = line.startPoint.x; + y1 = line.startPoint.y; + x2 = line.endPoint.x; + y2 = line.endPoint.y; + } + else if (line.p1 && line.p2) { + x1 = line.p1.x; + y1 = line.p1.y; + x2 = line.p2.x; + y2 = line.p2.y; + } + else { + return { x1: 0, y1: 0, x2: 0, y2: 0, valid: false }; + } + + const coords = [x1, y1, x2, y2]; + const valid = coords.every(coord => + typeof coord === 'number' && + !Number.isNaN(coord) && + Number.isFinite(coord) + ); + + return { x1, y1, x2, y2, valid }; +}; + +// 라인 방향 분석 함수 (중복 방지용 - 이미 있다면 제거) +const analyzeLineOrientation = (x1, y1, x2, y2, epsilon = 0.5) => { + const dx = x2 - x1; + const dy = y2 - y1; + const absDx = Math.abs(dx); + const absDy = Math.abs(dy); + const length = Math.sqrt(dx * dx + dy * dy); + + let orientation; + if (absDy < epsilon && absDx >= epsilon) { + orientation = 'horizontal'; + } else if (absDx < epsilon && absDy >= epsilon) { + orientation = 'vertical'; + } else { + orientation = 'diagonal'; + } + + return { + orientation, + dx, dy, absDx, absDy, length, + midX: (x1 + x2) / 2, + midY: (y1 + y2) / 2, + isHorizontal: orientation === 'horizontal', + isVertical: orientation === 'vertical' + }; +}; + +function extendLineToBoundary(p1, p2, roofLines) { + // 1. Calculate line direction and length + const dx = p2.x - p1.x; + const dy = p2.y - p1.y; + const length = Math.sqrt(dx * dx + dy * dy); + if (length === 0) return { p1: { ...p1 }, p2: { ...p2 } }; + + // 2. Get all polygon points + const points = []; + const seen = new Set(); + + for (const line of roofLines) { + const p1 = { x: line.x1, y: line.y1 }; + const p2 = { x: line.x2, y: line.y2 }; + + const key1 = `${p1.x},${p1.y}`; + const key2 = `${p2.x},${p2.y}`; + + if (!seen.has(key1)) { + points.push(p1); + seen.add(key1); + } + if (!seen.has(key2)) { + points.push(p2); + seen.add(key2); + } + } + + // 3. Find the bounding box + let minX = Infinity, minY = Infinity; + let maxX = -Infinity, maxY = -Infinity; + + for (const p of points) { + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } + + // 4. Extend line to bounding box + const bboxLines = [ + { x1: minX, y1: minY, x2: maxX, y2: minY }, // top + { x1: maxX, y1: minY, x2: maxX, y2: maxY }, // right + { x1: maxX, y1: maxY, x2: minX, y2: maxY }, // bottom + { x1: minX, y1: maxY, x2: minX, y2: minY } // left + ]; + + const intersections = []; + + // 5. Find intersections with bounding box + for (const line of bboxLines) { + const intersect = getLineIntersection( + p1, p2, + { x: line.x1, y: line.y1 }, + { x: line.x2, y: line.y2 } + ); + + if (intersect) { + const t = ((intersect.x - p1.x) * dx + (intersect.y - p1.y) * dy) / (length * length); + if (t >= 0 && t <= 1) { + intersections.push({ x: intersect.x, y: intersect.y, t }); + } + } + } + + // 6. If we have two intersections, use them + if (intersections.length >= 2) { + // Sort by t value + intersections.sort((a, b) => a.t - b.t); + return { + p1: { x: intersections[0].x, y: intersections[0].y }, + p2: { + x: intersections[intersections.length - 1].x, + y: intersections[intersections.length - 1].y + } + }; + } + + // 7. Fallback to original points + return { p1: { ...p1 }, p2: { ...p2 } }; +} + +/** + * 점에서 특정 방향으로 경계선과의 교차점을 찾습니다. + * @param {Object} point - 시작점 {x, y} + * @param {Object} direction - 방향 벡터 {x, y} (정규화된 값) + * @param {Array} roofLines - 지붕 경계선 배열 + * @returns {Object|null} 교차점 {x, y} 또는 null + */ +function findBoundaryIntersection(point, direction, roofLines) { + let closestIntersection = null; + let minDistance = Infinity; + + // 충분히 긴 거리로 광선 생성 (임의로 큰 값 사용) + const rayLength = 10000; + const rayEnd = { + x: point.x + direction.x * rayLength, + y: point.y + direction.y * rayLength + }; + + // 모든 경계선과의 교차점 확인 + for (const line of roofLines) { + const lineP1 = { x: line.x1, y: line.y1 }; + const lineP2 = { x: line.x2, y: line.y2 }; + + const intersection = getLineIntersection(point, rayEnd, lineP1, lineP2); + + if (intersection) { + // 교차점까지의 거리 계산 + const distance = Math.sqrt( + Math.pow(intersection.x - point.x, 2) + + Math.pow(intersection.y - point.y, 2) + ); + + // 가장 가까운 교차점 저장 (거리가 0보다 큰 경우만) + if (distance > 0.01 && distance < minDistance) { + minDistance = distance; + closestIntersection = intersection; + } + } + } + + return closestIntersection; +} + +/** + * 점이 다른 스켈레톤 라인과의 교점인지 확인합니다. + * @param {Object} point - 확인할 점 {x, y} + * @param {Array} skeletonLines - 모든 스켈레톤 라인 배열 + * @param {Object} currentLine - 현재 라인 {p1, p2} (자기 자신 제외용) + * @param {number} tolerance - 허용 오차 + * @returns {boolean} 교점이면 true + */ +function hasIntersectionWithOtherLines(point, skeletonLines, currentLine, tolerance = 0.5) { + if (!skeletonLines || skeletonLines.length === 0) { + return false; + } + + let connectionCount = 0; + + for (const line of skeletonLines) { + // 자기 자신과의 비교는 제외 + if (line.p1 && line.p2 && currentLine.p1 && currentLine.p2) { + const isSameLineCheck = + (isSamePoint(line.p1, currentLine.p1, tolerance) && isSamePoint(line.p2, currentLine.p2, tolerance)) || + (isSamePoint(line.p1, currentLine.p2, tolerance) && isSamePoint(line.p2, currentLine.p1, tolerance)); + + if (isSameLineCheck) continue; + } + + // 다른 라인의 끝점이 현재 점과 일치하는지 확인 + if (line.p1 && isSamePoint(point, line.p1, tolerance)) { + connectionCount++; + } + if (line.p2 && isSamePoint(point, line.p2, tolerance)) { + connectionCount++; + } + } + + // 1개 이상의 다른 라인과 연결되어 있으면 교점으로 간주 + return connectionCount >= 1; } \ No newline at end of file