diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js
index 841bbcc2..ba5b4e87 100644
--- a/src/util/skeleton-utils.js
+++ b/src/util/skeleton-utils.js
@@ -3,26 +3,28 @@ import Big from 'big.js'
import { SkeletonBuilder } from '@/lib/skeletons'
import { arePointsEqual, calcLineActualSize, calcLinePlaneSize, calculateAngle, toGeoJSON } from '@/util/qpolygon-utils'
import { QLine } from '@/components/fabric/QLine'
-import { getDegreeByChon, isPointOnLine } from '@/util/canvas-util'
+import { getDegreeByChon } from '@/util/canvas-util'
+
/**
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
* @param {string} roofId - 대상 지붕 객체의 ID
* @param {fabric.Canvas} canvas - Fabric.js 캔버스 객체
* @param {string} textMode - 텍스트 표시 모드
+ * @param existingSkeletonLines
*/
-export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
+export const drawSkeletonRidgeRoof = (roofId, canvas, textMode, existingSkeletonLines = []) => {
const roof = canvas?.getObjects().find((object) => object.id === roofId)
if (!roof) {
console.error(`Roof with id "${roofId}" not found.`);
return;
}
-
+ const skeletonLines = [...existingSkeletonLines];
// 1. 기존 스켈레톤 라인 제거
- const existingSkeletonLines = canvas.getObjects().filter(obj =>
- obj.parentId === roofId && obj.attributes?.type === 'skeleton'
- );
- existingSkeletonLines.forEach(line => canvas.remove(line));
+ // const existingSkeletonLines = canvas.getObjects().filter(obj =>
+ // obj.parentId === roofId && obj.attributes?.type === 'skeleton'
+ // );
+ // existingSkeletonLines.forEach(line => canvas.remove(line));
// 2. 지붕 폴리곤 좌표 전처리
const coordinates = preprocessPolygonCoordinates(roof.points);
@@ -45,7 +47,8 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
- skeletonBuilder(roofId, canvas, textMode, roof)
+ skeletonBuilder(roofId, canvas, textMode, roof, skeletonLines)
+
}
/**
@@ -175,7 +178,7 @@ export const transformMultipleEdges = (skeleton, edgeConfigs) => {
* @param roof
* @param edgeProperties
*/
-export const skeletonBuilder = (roofId, canvas, textMode, roof) => {
+export const skeletonBuilder = (roofId, canvas, textMode, roof, skeletonLines) => {
// 1. 다각형 좌표를 GeoJSON 형식으로 변환합니다.
const geoJSONPolygon = toGeoJSON(roof.points)
@@ -188,7 +191,7 @@ export const skeletonBuilder = (roofId, canvas, textMode, roof) => {
console.log('Edge 분석:', skeleton.edge_analysis)
// 3. 라인을 그림
- const innerLines = createInnerLinesFromSkeleton(skeleton, roof.lines, roof, canvas, textMode)
+ const innerLines = createInnerLinesFromSkeleton(skeleton, roof.lines, roof, canvas, textMode, skeletonLines)
console.log("innerLines::", innerLines)
// 4. 생성된 선들을 캔버스에 추가하고 지붕 객체에 저장
@@ -312,14 +315,14 @@ const mergeCollinearLines = (lines) => {
}
//조건에 따른 스켈레톤을 그린다.
-const createInnerLinesFromSkeleton = (skeleton, baseLines, roof, canvas, textMode) => {
+const createInnerLinesFromSkeleton = (skeleton, baseLines, roof, canvas, textMode, skeletonLines) => {
console.log('=== Edge Properties 기반 후처리 시작 ===')
if (!skeleton || !skeleton.Edges) return []
const innerLines = []
const processedInnerEdges = new Set()
- const skeletonLines = []
+ //const skeletonLines = []
// 1. 기본 skeleton에서 모든 내부 선분 수집
//edge 순서와 baseLines 순서가 같을수가 없다.
@@ -464,59 +467,12 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, processedInnerEd
x: (gableStart.x + gableEnd.x) / 2,
y: (gableStart.y + gableEnd.y) / 2,
}
- //
- // // polygonPoints와 gableMidpoint 비교: x 또는 y가 같은 점 찾기 (허용 오차 적용)
- // const axisTolerance = 0.1
- // const sameXPoints = polygonPoints.filter((p) => Math.abs(p.x - gableMidpoint.x) < axisTolerance)
- // const sameYPoints = polygonPoints.filter((p) => Math.abs(p.y - gableMidpoint.y) < axisTolerance)
- // if (sameXPoints.length || sameYPoints.length) {
- // console.log('GABLE: gableMidpoint와 같은 축의 폴리곤 점', {
- // gableMidpoint,
- // sameXPoints,
- // sameYPoints,
- // })
- // }
- //
+
// 폴리곤 중심점 (대략적)
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
- //
- // // 폴리곤 선분 생성 (연속 점 쌍)
- // const segments = []
- // for (let i = 0; i < polygonPoints.length; i++) {
- // const p1 = polygonPoints[i]
- // const p2 = polygonPoints[(i + 1) % polygonPoints.length]
- // segments.push({ p1, p2 })
- // }
- //
- // // gableMidpoint와 같은 축(Y 또는 X)에 있는 수직/수평 선분만 추출
- // const sameAxisSegments = segments.filter(({ p1, p2 }) => {
- // const isVertical = Math.abs(p1.x - p2.x) < colinearityTolerance
- // const isHorizontal = Math.abs(p1.y - p2.y) < colinearityTolerance
- // const sameXAxis = isVertical && Math.abs(p1.x - gableMidpoint.x) < axisTolerance
- // const sameYAxis = isHorizontal && Math.abs(p1.y - gableMidpoint.y) < axisTolerance
- // return sameXAxis || sameYAxis
- // })
- //
- // // 가장 가까운(또는 가장 긴) 용마루 후보 선택
- // let ridgeCandidate = null
- // if (sameAxisSegments.length) {
- // // 1) 중점과의 최단거리 기준
- // ridgeCandidate = sameAxisSegments.reduce((best, seg) => {
- // const mid = { x: (seg.p1.x + seg.p2.x) / 2, y: (seg.p1.y + seg.p2.y) / 2 }
- // const dist2 = (mid.x - gableMidpoint.x) ** 2 + (mid.y - gableMidpoint.y) ** 2
- // if (!best) return { seg, score: dist2 }
- // return dist2 < best.score ? { seg, score: dist2 } : best
- // }, null)?.seg
- //
- //
- // }
- //
const selectBaseLine = baseLines[baseLineIndex];
@@ -555,214 +511,83 @@ function processGableEdge(edgeResult, baseLines, skeletonLines, processedInnerEd
}
//확장
- // 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
+ const breakLinePont = findDisconnectedSkeletonLines(skeletonLines, baseLines)
+ console.log('breakLinePont:', breakLinePont)
- 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') {
+if(breakLinePont.disconnectedLines.length > 0) {
- skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: intersection2.x, y: intersection2.y };
- } else {
+ for (const dLine of breakLinePont.disconnectedLines) {
+ const inx = dLine.index;
+ const exLine = dLine.extendedLine;
- skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: intersection2.x, y: intersection2.y };
- }
+ //확장
+ if (dLine.p1Connected) {
+ skeletonLines[inx].p2 = { ...skeletonLines[inx].p2, x: exLine.p2.x, y: exLine.p2.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 };
- }
+ } else if (dLine.p2Connected) {
+ skeletonLines[inx].p1 = { ...skeletonLines[inx].p1, x: exLine.p1.x, y: exLine.p1.y };
}
- }
-// for (const polyPoint of edgeResult.Polygon) {
+ }
+}
+ //확장(연장)
+// 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 };
//
-// 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);
+// let hasP1 = false;
+// let hasP2 = false;
+// console.log('edgeResult.Edge::',edgeResult.Edge)
+// //선택한 라인과 다각형을 생성하는 라인 여부
+// const matchingLinePoint = findMatchingLinePoints(line, edgeResult.Polygon);
+// console.log(matchingLinePoint);
//
-// skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: extendedPoint1.x, y: extendedPoint1.Y };
-// //skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: edgeResult.Edge.Begin.X};
+//
+// if(matchingLinePoint.hasMatch) {
+//
+// if (matchingLinePoint.matches[0].type === 'diagonal') {
+// console.log("lineP1:", lineP1)
+// console.log("lineP2:", lineP2)
+// const intersectionPoint = getLineIntersectionParametric(lineP1, lineP2, gableStart, gableEnd);
+// console.log('intersectionPoint:', intersectionPoint);
+// console.log('gableStart:', gableStart);
+// console.log('gableEnd:', gableEnd);
+// // 교차점이 생겼다면 절삭(교차점 이하(이상) 삭제)
+// if (!intersectionPoint) {
+// console.warn('No valid intersection point found between line and gable edge');
+// return; // or handle the null case appropriately
+// }
+//
+// if (matchingLinePoint.matches[0].linePoint === 'p1') {
+// skeletonLines[i].p1 = { ...skeletonLines[i].p1, x: intersectionPoint.x, y: intersectionPoint.y };
+// } else {
+// skeletonLines[i].p2 = { ...skeletonLines[i].p2, x: intersectionPoint.x, y: intersectionPoint.y };
+// }
+//
+// } else if (matchingLinePoint.matches[0].type === 'horizontal') {
+// if (matchingLinePoint.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 (matchingLinePoint.matches[0].type === 'vertical') {
+// if (matchingLinePoint.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 };
+// }
// }
//
-// 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') {
-
- // 선택한 기준선 을 중심으로 대각선 삭제
- // Get line and edge points
- const edgeStart = { x: edgeResult.Edge.Begin.X, y: edgeResult.Edge.Begin.Y };
- const edgeEnd = { x: edgeResult.Edge.End.X, y: edgeResult.Edge.End.Y };
- const lineStart = { x: line.p1.x, y: line.p1.y };
- const lineEnd = { x: line.p2.x, y: line.p2.y };
-
- const pointsEqual = (p1, p2) => {
- return p1.x === p2.x && p1.y === p2.y;
- }
- // Check if line shares an endpoint with the edge
- const sharesStartPoint = pointsEqual(edgeStart, lineStart) || pointsEqual(edgeStart, lineEnd);
- const sharesEndPoint = pointsEqual(edgeEnd, lineStart) || pointsEqual(edgeEnd, lineEnd);
-
- if (sharesStartPoint || sharesEndPoint) {
- 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){
-
- let hasP1 = false;
- let hasP2 = false;
-
- for (const polyPoint of edgeResult.Polygon) {
- if (!hasP1 && polyPoint.X === lineP1.x && polyPoint.Y === lineP1.y) {
- hasP1 = true;
- }
- if (!hasP2 && polyPoint.X === lineP2.x && polyPoint.Y === lineP2.y) {
- hasP2 = true;
- }
-
- // Early exit if both points are found
- if (hasP1 && hasP2) break;
- }
-
- if (hasP1 && hasP2) {
- 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 skeletonLines:', skeletonLines)
-*/
-
-
-
- // addRawLine(
- // skeletonLines,
- // processedInnerEdges,
- // gableMidpoint,
- // polygonCenter,
- // 'RIDGE',
- // '#0000FF', // 파란색으로 구분
- // 3, // 두껍게
- // )
}
// ✅ 헬퍼 함수들
@@ -1092,6 +917,7 @@ const extendLineToOppositeEdge = (p1, p2, polygonPoints, selectedEdgeIndex) => {
// 방향 벡터 정규화
const length = Math.sqrt(direction.x * direction.x + direction.y * direction.y)
+
if (length === 0) return null
const normalizedDir = {
@@ -1195,6 +1021,7 @@ const transformWallToRoof = (roof, canvas, edgeIndex) => {
return roof
}
+
/**
* 사용 예제: 첫 번째 edge를 45도 각도로 변형
* drawSkeletonWithTransformedEdges(roofId, canvas, textMode, [
@@ -1558,7 +1385,7 @@ class Advanced2DRoofBuilder extends SkeletonBuilder {
}
if (edgeIndex < 0 || edgeIndex >= totalPoints) {
- console.error(`edgeIndex ${edgeIndex}가 범위 [0, ${totalPoints - 1}]을 벗어났습니다`)
+ console.error(`edgeIndex ${edgeIndex}가 범위를 벗어났습니다`)
return
}
@@ -1658,9 +1485,13 @@ class Advanced2DRoofBuilder extends SkeletonBuilder {
const centerX = validPoints.reduce((sum, p) => sum + p[0], 0) / totalPoints
const centerY = validPoints.reduce((sum, p) => sum + p[1], 0) / totalPoints
+ console.log(`중심점: (${centerX}, ${centerY})`)
+
+ // edge 중점
const midX = (p1[0] + p2[0]) / 2
const midY = (p1[1] + p2[1]) / 2
+ // 외향 방향
const dirX = centerX - midX
const dirY = centerY - midY
const length = Math.sqrt(dirX * dirX + dirY * dirY)
@@ -1778,87 +1609,6 @@ const isSameLine = (edgeStartX, edgeStartY, edgeEndX, edgeEndY, baseLine) => {
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 - 검색할 라인 배열
@@ -1912,198 +1662,389 @@ function findIntersection(p1, p2, p3, p4) {
// 교차하지 않는 경우
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
+// baseLine 좌표 추출 헬퍼 함수
+const extractBaseLineCoordinates = (baseLine) => {
+ const left = baseLine.left || 0;
+ const top = baseLine.top || 0;
+ const width = baseLine.width || 0;
+ const height = baseLine.height || 0;
+
+ // 수평선인 경우 (height가 0에 가까움)
+ if (Math.abs(height) < 0.1) {
+ return {
+ p1: { x: left, y: top },
+ p2: { x: left + width, y: top }
};
+ }
+ // 수직선인 경우 (width가 0에 가까움)
+ else if (Math.abs(width) < 0.1) {
+ return {
+ p1: { x: left, y: top },
+ p2: { x: left, y: top + height }
+ };
+ }
+ // 기타 경우 (기본값)
+ else {
+ return {
+ p1: { x: left, y: top },
+ p2: { x: left + width, y: top + height }
+ };
+ }
+};
- // 모든 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
- };
+// 연결이 끊어진 라인들을 찾는 함수
+export const findDisconnectedSkeletonLines = (skeletonLines, baseLines, options = {}) => {
+ const {
+ includeDiagonal = true,
+ includeStraight = true,
+ minLength = 0
+ } = options;
- // 선분 교차 검사
- 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
- });
- }
- }
+ if (!skeletonLines?.length) {
+ return {
+ disconnectedLines: [],
+ diagonalLines: [],
+ straightLines: [],
+ statistics: { total: 0, diagonal: 0, straight: 0, disconnected: 0 }
+ };
}
- return intersections;
-}
+ const disconnectedLines = [];
+ const diagonalLines = [];
+ const straightLines = [];
-// Helper function to extend a line to intersect with polygon edges
-function extendLineToIntersections(p1, p2, polygonPoints) {
- let intersections = [];
- const line = { p1, p2 };
+ // 점 일치 확인 헬퍼 함수
+ const pointsEqual = (p1, p2, epsilon = 0.1) => {
+ return Math.abs(p1.x - p2.x) < epsilon && Math.abs(p1.y - p2.y) < epsilon;
+ };
- // 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];
+ // baseLine 좌표 추출
+ const extractBaseLineCoordinates = (baseLine) => {
+ const left = baseLine.left || 0;
+ const top = baseLine.top || 0;
+ const width = baseLine.width || 0;
+ const height = baseLine.height || 0;
-
- }
-
- 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
+ if (Math.abs(height) < 0.1) {
+ return { p1: { x: left, y: top }, p2: { x: left + width, y: top } };
+ } else if (Math.abs(width) < 0.1) {
+ return { p1: { x: left, y: top }, p2: { x: left, y: top + height } };
+ } else {
+ return { p1: { x: left, y: top }, p2: { x: left + width, y: top + height } };
}
};
-}
-function getExtensionIntersection(
- startX, startY, // 대각선 시작점
- currentX, currentY, // 대각선 현재 위치
- lineStartX, lineStartY, // 연장할 선의 시작점
- lineEndX, lineEndY // 연장할 선의 끝점
-) {
- // 대각선 방향 벡터
- const dx = currentX - startX;
- const dy = currentY - startY;
+ // baseLine에 점이 있는지 확인
+ const isPointOnBase = (point) => {
+ return baseLines?.some(baseLine => {
+ const coords = extractBaseLineCoordinates(baseLine);
+ return pointsEqual(point, coords.p1) || pointsEqual(point, coords.p2);
+ }) || false;
+ };
- // 연장할 선의 기울기
- const m = (lineEndY - lineStartY) / (lineEndX - lineStartX);
+ // baseLine과 교차하는지 확인
+ const isIntersectingWithBase = (skeletonLine) => {
+ return baseLines?.some(baseLine => {
+ const coords = extractBaseLineCoordinates(baseLine);
+ const intersection = findIntersection(
+ skeletonLine.p1.x, skeletonLine.p1.y, skeletonLine.p2.x, skeletonLine.p2.y,
+ coords.p1.x, coords.p1.y, coords.p2.x, coords.p2.y
+ );
+ return intersection !== null;
+ }) || false;
+ };
- // 매개변수 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;
+ // 라인 타입 확인
+ const getLineType = (p1, p2) => {
+ const dx = Math.abs(p2.x - p1.x);
+ const dy = Math.abs(p2.y - p1.y);
+ const tolerance = 0.1;
- if (denominator === 0) {
- // 평행하거나 일치하여 교점 없음
- return null;
- }
+ if (dy < tolerance) return 'horizontal';
+ if (dx < tolerance) return 'vertical';
+ return 'diagonal';
+ };
- const t = numerator / denominator;
+ // 라인 길이 계산
+ const getLineLength = (p1, p2) => {
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ };
- const intersectX = startX + t * dx;
- const intersectY = startY + t * dy;
+ /**
+ * 연결 상태 확인 함수
+ *
+ * @param {Object} line - 검사할 skeletonLine (p1, p2)
+ * @param {number} lineIndex - 현재 라인의 인덱스
+ * @returns {{isConnected: boolean, p1Connected: boolean, p2Connected: boolean}} 연결되어 있으면 true, 끊어져 있으면 false
+ *
+ * 연결 판단 기준:
+ * 1. p1이 baseLine과 연결되어 있는지 확인
+ * 2. p1이 연결되어 있으면 p2가 skeletonLine과 연결되어 있는지 확인
+ * 3. p1이 연결되어 있지 않으면 p2가 baseLine과 연결되어 있는지 확인
+ * 4. p2도 연결되어 있지 않으면 p1과 p2가 skeletonLine과 연결되어 있는지 확인
+ */
+ const isConnected = (line, lineIndex) => {
+ const result= {
+ isConnected: false,
+ p1Connected: false,
+ p2Connected: false,
+ extendedLine: []
+ }
+ const { p1, p2 } = line;
- return { x: intersectX, y: intersectY };
-}
+ // 1. p1이 baseLine과 연결되어 있는지 확인
+ const isP1OnBase = isPointOnBase(p1);
+ const isP2OnBase = isPointOnBase(p2);
-function findMatchingLinePoints(Aline, APolygon, epsilon = 1e-10) {
- const { p1, p2 } = Aline;
- const matches = [];
+ if (isP1OnBase || isP2OnBase) {
- // 선의 방향 판단
- 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: '수평선 (가로)'
- };
+ // 2. p1 또는 p2가 baseLine과 연결되어 있음
+ // -> 연결되지 않은 점이 skeletonLine과 연결되어 있는지 확인
+ for (let i = 0; i < skeletonLines.length; i++) {
+ if (i === lineIndex) continue; // 자기 자신은 제외
+
+ const otherLine = skeletonLines[i];
+ const otherP1 = otherLine.p1;
+ const otherP2 = otherLine.p2;
+
+ // p2가 다른 skeletonLine의 p1 또는 p2와 일치하는지 확인
+ if (pointsEqual(p2, otherP1) || pointsEqual(p2, otherP2)) {
+ result.p1Connected = true;
+ result.p2Connected = true;
+ result.isConnected = true;
+ // p2가 연결되어 있으므로 전체 라인이 연결됨
+ }else{
+ result.p1Connected = true;
+ result.p2Connected = false;
+ result.isConnected = false;
+ }
+
+ if (pointsEqual(p1, otherP1) || pointsEqual(p1, otherP2)) {
+ result.p1Connected = true;
+ result.p2Connected = true;
+ // p1이 다른 skeletonLine의 p1 또는 p2와 일치하는지 확인
+ result.isConnected = true;
+ // p1이 연결되어 있으므로 전체 라인이 연결됨
+ }else{
+ result.p1Connected = true;
+ result.p2Connected = false;
+ result.isConnected = false;
+ }
+
+ }
+ return result;
+
} else {
- return {
- type: 'diagonal',
- description: '대각선'
- };
- }
- }
+ // 3. p1과 p2 모두 baseLine과 연결되어 있지 않음
+ // -> p1과 p2가 skeletonLine과 연결되어 있는지 확인
+ let p1Connected = false; // p1이 skeletonLine과 연결되어 있는지
+ let p2Connected = false; // p2가 skeletonLine과 연결되어 있는지
- // 선의 방향 정보 계산
- 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
- });
+ for (let i = 0; i < skeletonLines.length; i++) {
+ if (i === lineIndex) continue; // 자기 자신은 제외
+
+ const otherLine = skeletonLines[i];
+ const otherP1 = otherLine.p1;
+ const otherP2 = otherLine.p2;
+
+ // p1이 다른 skeletonLine의 p1 또는 p2와 일치하는지 확인
+ if (!p1Connected && (pointsEqual(p1, otherP1) || pointsEqual(p1, otherP2))) {
+ p1Connected = true;
+ result.p1Connected = true;
+ }else{
+ // p1이 skeletonLine과 연결되지 않음 - baseLine까지 연장
+ result.extendedLine = extendToBaseLine(p1, baseLines);
+ }
+
+// p2가 다른 skeletonLine의 p1 또는 p2와 일치하는지 확인
+ if (!p2Connected && (pointsEqual(p2, otherP1) || pointsEqual(p2, otherP2))) {
+ p2Connected = true;
+ result.p2Connected = true;
+ }else{
+ // p2가 skeletonLine과 연결되지 않음 - baseLine까지 연장
+ result.extendedLine = extendToBaseLine(p1, baseLines);
+ }
+
+ // p1과 p2가 모두 연결되어 있으면 전체 라인이 연결됨
+ if (p1Connected && p2Connected) {
+ result.isConnected = true;
+ }
+
+
+ }
+
+ return result
}
- // 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
+ };
+
+ // 각 라인 분석
+ skeletonLines.forEach((line, index) => {
+ const { p1, p2 } = line;
+ const length = getLineLength(p1, p2);
+ const type = getLineType(p1, p2);
+
+ if (length < minLength) return;
+
+ const connected = isConnected(line, index);
+ const extendedLine = connected.extendedLine;
+ const p1Connected = connected.p1Connected;
+ const p2Connected = connected.p2Connected;
+
+ if (type === 'diagonal') {
+ diagonalLines.push({ line, index, length, type, connected});
+ } else {
+ straightLines.push({ line, index, length, type, connected });
+ }
+
+ if (!connected.isConnected) {
+ disconnectedLines.push({
+ line, index, length, type,
+ isDiagonal: type === 'diagonal',
+ isHorizontal: type === 'horizontal',
+ isVertical: type === 'vertical',
+ p1Connected: p1Connected,
+ p2Connected: p2Connected,
+ extendedLine: extendedLine,
+
+
+
});
}
});
+ const filteredDisconnected = includeDiagonal && includeStraight
+ ? disconnectedLines
+ : disconnectedLines.filter(item =>
+ (includeDiagonal && item.isDiagonal) ||
+ (includeStraight && (item.isHorizontal || item.isVertical))
+ );
+
return {
- hasMatch: matches.length > 0,
- lineDirection: lineDirection,
- matches: matches
+ disconnectedLines: filteredDisconnected,
+ diagonalLines: includeDiagonal ? diagonalLines : [],
+ straightLines: includeStraight ? straightLines : [],
+ statistics: {
+ total: skeletonLines.length,
+ diagonal: diagonalLines.length,
+ straight: straightLines.length,
+ disconnected: filteredDisconnected.length
+ }
};
-}
+};
+const extendToBaseLine = (point, baseLines) => {
+ // point에서 가장 가까운 baseLine을 찾아서 연장
+ let closestBaseLine = null;
+ let minDistance = Infinity;
-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 }; // 두번째 직선 방향벡터
+ for (const baseLine of baseLines) {
+ // point와 baseLine 사이의 거리 계산
+ const distance = getDistanceToLine(point, baseLine);
- // 평행선 체크 (외적이 0이면 평행)
- const cross = d1.x * d2.y - d1.y * d2.x;
- if (Math.abs(cross) < Number.EPSILON) {
- return null; // 평행선
+ if (distance < minDistance) {
+ minDistance = distance;
+ closestBaseLine = baseLine;
+ }
}
- // 매개변수 t 계산
- const dx = p3.x - p1.x;
- const dy = p3.y - p1.y;
- const t = (dx * d2.y - dy * d2.x) / cross;
+ if (closestBaseLine) {
+ // point에서 closestBaseLine으로 연장하는 라인 생성
+ // 연장된 라인을 skeletonLines에 추가
+ return {
+ p1: point,
+ p2: getProjectionPoint(point, closestBaseLine)
+ }
+ }
+};
+/**
+ * 점과 선분 사이의 거리를 계산하는 함수
+ * @param {Object} point - 거리를 계산할 점 {x, y}
+ * @param {Object} line - 선분 {x1, y1, x2, y2}
+ * @returns {number} 점과 선분 사이의 최단 거리
+ */
+const getDistanceToLine = (point, line) => {
+ const { x: px, y: py } = point;
+ const { x1, y1, x2, y2 } = line;
- // 교점: p1 + t * d1
- return {
- x: p1.x + t * d1.x,
- y: p1.y + t * d1.y
- };
-}
\ No newline at end of file
+ // 선분의 방향 벡터
+ const dx = x2 - x1;
+ const dy = y2 - y1;
+ const lineLength = Math.sqrt(dx * dx + dy * dy);
+
+ if (lineLength === 0) {
+ // 선분이 점인 경우
+ return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
+ }
+
+ // 선분의 단위 방향 벡터
+ const ux = dx / lineLength;
+ const uy = dy / lineLength;
+
+ // 점에서 선분 시작점으로의 벡터
+ const vx = px - x1;
+ const vy = py - y1;
+
+ // 투영된 길이 (스칼라 투영)
+ const projectionLength = vx * ux + vy * uy;
+
+ // 투영점이 선분 범위 내에 있는지 확인
+ if (projectionLength >= 0 && projectionLength <= lineLength) {
+ // 투영점이 선분 내에 있음
+ const distance = Math.sqrt(vx * vx + vy * vy - projectionLength * projectionLength);
+ return distance;
+ } else {
+ // 투영점이 선분 밖에 있음 - 끝점까지의 거리 중 작은 값
+ const distToStart = Math.sqrt(vx * vx + vy * vy);
+ const distToEnd = Math.sqrt((px - x2) * (px - x2) + (py - y2) * (py - y2));
+ return Math.min(distToStart, distToEnd);
+ }
+};
+
+/**
+ * 점을 선분에 투영한 점을 반환하는 함수
+ * @param {Object} point - 투영할 점 {x, y}
+ * @param {Object} line - 선분 {x1, y1, x2, y2}
+ * @returns {Object} 투영된 점 {x, y}
+ */
+const getProjectionPoint = (point, line) => {
+ const { x: px, y: py } = point;
+ const { x1, y1, x2, y2 } = line;
+
+ // 선분의 방향 벡터
+ const dx = x2 - x1;
+ const dy = y2 - y1;
+ const lineLength = Math.sqrt(dx * dx + dy * dy);
+
+ if (lineLength === 0) {
+ // 선분이 점인 경우
+ return { x: x1, y: y1 };
+ }
+
+ // 선분의 단위 방향 벡터
+ const ux = dx / lineLength;
+ const uy = dy / lineLength;
+
+ // 점에서 선분 시작점으로의 벡터
+ const vx = px - x1;
+ const vy = py - y1;
+
+ // 투영된 길이 (스칼라 투영)
+ const projectionLength = vx * ux + vy * uy;
+
+ // 투영점이 선분 범위 내에 있는지 확인
+ if (projectionLength >= 0 && projectionLength <= lineLength) {
+ // 투영점이 선분 내에 있음
+ const projX = x1 + ux * projectionLength;
+ const projY = y1 + uy * projectionLength;
+ return { x: projX, y: projY };
+ } else if (projectionLength < 0) {
+ // 투영점이 시작점 앞에 있음
+ return { x: x1, y: y1 };
+ } else {
+ // 투영점이 끝점 뒤에 있음
+ return { x: x2, y: y2 };
+ }
+};
\ No newline at end of file