skeleton v6 - 교점추가 절삭 추가
This commit is contained in:
parent
6cccdfb987
commit
3aac4f8ac0
@ -48,16 +48,13 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
||||
// 'gable'인 경우, 의도적으로 내부선을 생성하지 않아 빈 공간을 만듭니다.
|
||||
} else if (lineType === 'wall') {
|
||||
// TODO: 'wall' 타입에 대한 처리가 필요합니다.
|
||||
// 현재는 아무 작업도 하지 않지만, 향후 관련 로직이 이곳에 추가될 수 있습니다.
|
||||
// 예를 들어, 벽에 맞닿는 부분의 선을 다르게 처리하거나 특정 정보를 추가할 수 있습니다.
|
||||
} else {
|
||||
// 'gable' 또는 'wall'이 아닌 경우 (e.g., 'eaves') 내부선을 생성합니다.
|
||||
processEavesEdge(edgeResult, skeletonLines, processedInnerEdges);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- 3. 연결이 끊어진 선(케라바로 인해 생성됨)을 찾아 연장 ---
|
||||
// --- 3. 연결이 끊어진 선을 찾아 연장 ---
|
||||
const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, baseLines);
|
||||
disconnectedLines.forEach(dLine => {
|
||||
const { index, extendedLine, p1Connected, p2Connected } = dLine;
|
||||
@ -71,9 +68,12 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 3.5. 모든 선을 교차점에서 분할 ---
|
||||
const finalSkeletonLines = splitLinesAtIntersections(skeletonLines);
|
||||
|
||||
// --- 4. 최종 결과물을 지붕 객체에 저장하고 캔버스에 그리기 ---
|
||||
roof.skeletonLines = skeletonLines;
|
||||
roof.skeleton = rebuildSkeletonFromLines(skeletonLines, baseLines); // 데이터 구조 일관성 유지
|
||||
roof.skeletonLines = finalSkeletonLines;
|
||||
roof.skeleton = rebuildSkeletonFromLines(finalSkeletonLines, baseLines); // 데이터 구조 일관성 유지
|
||||
console.log("Skeleton processing complete. Storing final state.", roof.skeleton);
|
||||
|
||||
const innerLines = [];
|
||||
@ -123,7 +123,6 @@ function processEavesEdge(edgeResult, skeletonLines, processedInnerEdges) {
|
||||
const p1 = polygonPoints[i];
|
||||
const p2 = polygonPoints[(i + 1) % polygonPoints.length];
|
||||
|
||||
// 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가
|
||||
if (!isOuterEdge(p1, p2, [edgeResult.Edge])) {
|
||||
addRawLine(skeletonLines, processedInnerEdges, p1, p2, 'RIDGE', '#FF0000', 3);
|
||||
}
|
||||
@ -132,6 +131,18 @@ function processEavesEdge(edgeResult, skeletonLines, processedInnerEdges) {
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
/**
|
||||
* 두 점이 거의 같은 위치에 있는지 확인합니다.
|
||||
* @param {object} p1 - 점1 {x, y}
|
||||
* @param {object} p2 - 점2 {x, y}
|
||||
* @param {number} [epsilon=0.1] - 허용 오차
|
||||
* @returns {boolean} 동일한지 여부
|
||||
*/
|
||||
const pointsEqual = (p1, p2, epsilon = 0.1) => {
|
||||
return Math.abs(p1.x - p2.x) < epsilon && Math.abs(p1.y - p2.y) < epsilon;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 두 점으로 이루어진 선분이 외벽선인지 확인합니다.
|
||||
* @param {object} p1 - 점1 {x, y}
|
||||
@ -140,13 +151,11 @@ function processEavesEdge(edgeResult, skeletonLines, processedInnerEdges) {
|
||||
* @returns {boolean} 외벽선 여부
|
||||
*/
|
||||
function isOuterEdge(p1, p2, edges) {
|
||||
const tolerance = 0.1;
|
||||
return edges.some(edge => {
|
||||
const lineStart = { x: edge.Begin.X, y: edge.Begin.Y };
|
||||
const lineEnd = { x: edge.End.X, y: edge.End.Y };
|
||||
const forwardMatch = Math.abs(lineStart.x - p1.x) < tolerance && Math.abs(lineStart.y - p1.y) < tolerance && Math.abs(lineEnd.x - p2.x) < tolerance && Math.abs(lineEnd.y - p2.y) < tolerance;
|
||||
const backwardMatch = Math.abs(lineStart.x - p2.x) < tolerance && Math.abs(lineStart.y - p2.y) < tolerance && Math.abs(lineEnd.x - p1.x) < tolerance && Math.abs(lineEnd.y - p1.y) < tolerance;
|
||||
return forwardMatch || backwardMatch;
|
||||
return (pointsEqual(p1, lineStart) && pointsEqual(p2, lineEnd)) ||
|
||||
(pointsEqual(p1, lineEnd) && pointsEqual(p2, lineStart));
|
||||
});
|
||||
}
|
||||
|
||||
@ -187,11 +196,13 @@ function addRawLine(skeletonLines, processedInnerEdges, p1, p2, lineType, color,
|
||||
* @returns {boolean} 동일 여부
|
||||
*/
|
||||
const isSameLine = (edgeStartX, edgeStartY, edgeEndX, edgeEndY, baseLine) => {
|
||||
const tolerance = 0.1;
|
||||
const { x1, y1, x2, y2 } = baseLine;
|
||||
const forwardMatch = Math.abs(edgeStartX - x1) < tolerance && Math.abs(edgeStartY - y1) < tolerance && Math.abs(edgeEndX - x2) < tolerance && Math.abs(edgeEndY - y2) < tolerance;
|
||||
const backwardMatch = Math.abs(edgeStartX - x2) < tolerance && Math.abs(edgeStartY - y2) < tolerance && Math.abs(edgeEndX - x1) < tolerance && Math.abs(edgeEndY - y1) < tolerance;
|
||||
return forwardMatch || backwardMatch;
|
||||
const p1 = { x: edgeStartX, y: edgeStartY };
|
||||
const p2 = { x: edgeEndX, y: edgeEndY };
|
||||
const baseP1 = { x: x1, y: y1 };
|
||||
const baseP2 = { x: x2, y: y2 };
|
||||
return (pointsEqual(p1, baseP1) && pointsEqual(p2, baseP2)) ||
|
||||
(pointsEqual(p1, baseP2) && pointsEqual(p2, baseP1));
|
||||
};
|
||||
|
||||
// --- Disconnected Line Processing ---
|
||||
@ -247,47 +258,44 @@ function getRayIntersectionWithSegment(rayStart, rayDir, segA, segB) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 한 점에서 다른 점 방향으로 광선을 쏘아 가장 가까운 교차점을 찾습니다.
|
||||
* @param {object} p1 - 광선의 방향을 결정하는 끝점
|
||||
* @param {object} p2 - 광선의 시작점
|
||||
* 주어진 점에서 특정 방향으로 광선을 쏘아 가장 가까운 교차점을 찾습니다.
|
||||
* @param {object} rayOrigin - 광선의 시작점
|
||||
* @param {object} rayDirection - 광선의 방향 벡터
|
||||
* @param {Array<QLine>} baseLines - 외벽선 배열
|
||||
* @param {Array} skeletonLines - 스켈레톤 라인 배열
|
||||
* @param {number} excludeIndex - 검사에서 제외할 현재 라인의 인덱스
|
||||
* @returns {object|null} 가장 가까운 교차점 정보 또는 null
|
||||
*/
|
||||
function extendFromP2TowardP1(p1, p2, baseLines, skeletonLines, excludeIndex) {
|
||||
const dirVec = { x: p1.x - p2.x, y: p1.y - p2.y };
|
||||
const len = Math.sqrt(dirVec.x * dirVec.x + dirVec.y * dirVec.y) || 1;
|
||||
const dir = { x: dirVec.x / len, y: dirVec.y / len };
|
||||
function findClosestIntersection(rayOrigin, rayDirection, baseLines, skeletonLines, excludeIndex) {
|
||||
const len = Math.sqrt(rayDirection.x ** 2 + rayDirection.y ** 2) || 1;
|
||||
const dir = { x: rayDirection.x / len, y: rayDirection.y / len };
|
||||
let closestHit = null;
|
||||
|
||||
const checkHit = (hit) => {
|
||||
// 교차점이 원래 선분의 길이(len)보다 멀리 있어야 유효한 연장으로 간주
|
||||
if (hit && hit.t > len - 0.1) {
|
||||
const targetLines = [];
|
||||
if (Array.isArray(baseLines)) {
|
||||
baseLines.forEach(line => targetLines.push({ p1: { x: line.x1, y: line.y1 }, p2: { x: line.x2, y: line.y2 } }));
|
||||
}
|
||||
if (Array.isArray(skeletonLines)) {
|
||||
skeletonLines.forEach((line, i) => {
|
||||
if (i !== excludeIndex) {
|
||||
targetLines.push({ p1: line.p1, p2: line.p2 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
targetLines.forEach(targetLine => {
|
||||
const hit = getRayIntersectionWithSegment(rayOrigin, dir, targetLine.p1, targetLine.p2);
|
||||
if (hit && hit.t > 1e-6) {
|
||||
if (!closestHit || hit.t < closestHit.t) {
|
||||
closestHit = hit;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (Array.isArray(baseLines)) {
|
||||
baseLines.forEach(baseLine => {
|
||||
const hit = getRayIntersectionWithSegment(p2, dir, { x: baseLine.x1, y: baseLine.y1 }, { x: baseLine.x2, y: baseLine.y2 });
|
||||
checkHit(hit);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(skeletonLines)) {
|
||||
skeletonLines.forEach((seg, i) => {
|
||||
if (i === excludeIndex) return;
|
||||
const hit = getRayIntersectionWithSegment(p2, dir, seg.p1, seg.p2);
|
||||
checkHit(hit);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return closestHit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 연결이 끊어진 스켈레톤 라인들을 찾아 연장 정보를 계산합니다.
|
||||
* @param {Array} skeletonLines - 스켈레톤 라인 배열
|
||||
@ -332,7 +340,9 @@ export const findDisconnectedSkeletonLines = (skeletonLines, baseLines) => {
|
||||
|
||||
let extendedLine = null;
|
||||
if (!p1Connected) {
|
||||
extendedLine = extendFromP2TowardP1(line.p1, line.p2, baseLines, skeletonLines, index);
|
||||
const rayOrigin = line.p1;
|
||||
const rayDirection = { x: line.p1.x - line.p2.x, y: line.p1.y - line.p2.y };
|
||||
extendedLine = findClosestIntersection(rayOrigin, rayDirection, baseLines, skeletonLines, index);
|
||||
if (!extendedLine) {
|
||||
let minDistance = Infinity;
|
||||
let projection = null;
|
||||
@ -344,10 +354,12 @@ export const findDisconnectedSkeletonLines = (skeletonLines, baseLines) => {
|
||||
projection = p;
|
||||
}
|
||||
});
|
||||
if(projection) extendedLine = { point: projection };
|
||||
if (projection) extendedLine = { point: projection };
|
||||
}
|
||||
} else if (!p2Connected) {
|
||||
extendedLine = extendFromP2TowardP1(line.p2, line.p1, baseLines, skeletonLines, index);
|
||||
const rayOrigin = line.p2;
|
||||
const rayDirection = { x: line.p2.x - line.p1.x, y: line.p2.y - line.p1.y };
|
||||
extendedLine = findClosestIntersection(rayOrigin, rayDirection, baseLines, skeletonLines, index);
|
||||
if (!extendedLine) {
|
||||
let minDistance = Infinity;
|
||||
let projection = null;
|
||||
@ -359,7 +371,7 @@ export const findDisconnectedSkeletonLines = (skeletonLines, baseLines) => {
|
||||
projection = p;
|
||||
}
|
||||
});
|
||||
if(projection) extendedLine = { point: projection };
|
||||
if (projection) extendedLine = { point: projection };
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,19 +381,94 @@ export const findDisconnectedSkeletonLines = (skeletonLines, baseLines) => {
|
||||
return { disconnectedLines };
|
||||
};
|
||||
|
||||
|
||||
// --- Skeleton Rebuilding Functions ---
|
||||
// --- Line Splitting Functions ---
|
||||
|
||||
/**
|
||||
* 두 점이 거의 같은 위치에 있는지 확인합니다.
|
||||
* @param {object} p1 - 점1 {x, y}
|
||||
* @param {object} p2 - 점2 {x, y}
|
||||
* @param {number} [epsilon=0.1] - 허용 오차
|
||||
* @returns {boolean} 동일한지 여부
|
||||
* 두 선분의 교차점을 찾습니다. (선분 내부에서 교차하는 경우만)
|
||||
* @param {object} p1 - 선분1의 시작점 {x, y}
|
||||
* @param {object} p2 - 선분1의 끝점 {x, y}
|
||||
* @param {object} p3 - 선분2의 시작점 {x, y}
|
||||
* @param {object} p4 - 선분2의 끝점 {x, y}
|
||||
* @returns {object|null} 교차점 {x, y} 또는 null
|
||||
*/
|
||||
const pointsEqual = (p1, p2, epsilon = 0.1) => {
|
||||
return Math.abs(p1.x - p2.x) < epsilon && Math.abs(p1.y - p2.y) < epsilon;
|
||||
};
|
||||
function getLineSegmentIntersection(p1, p2, p3, p4) {
|
||||
const { x: x1, y: y1 } = p1;
|
||||
const { x: x2, y: y2 } = p2;
|
||||
const { x: x3, y: y3 } = p3;
|
||||
const { x: x4, y: y4 } = p4;
|
||||
|
||||
const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
||||
if (Math.abs(den) < 1e-6) return null;
|
||||
|
||||
const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
|
||||
const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
|
||||
|
||||
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
|
||||
const intersectX = x1 + t * (x2 - x1);
|
||||
const intersectY = y1 + t * (y2 - y1);
|
||||
return { x: intersectX, y: intersectY };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스켈레톤 라인들을 모든 교점에서 분할합니다.
|
||||
* @param {Array<object>} skeletonLines - 분할할 스켈레톤 라인 배열
|
||||
* @returns {Array<object>} - 모든 교점에서 분할된 새로운 라인 세그먼트 배열
|
||||
*/
|
||||
function splitLinesAtIntersections(skeletonLines) {
|
||||
if (!skeletonLines || skeletonLines.length === 0) return [];
|
||||
|
||||
const finalSegments = new Map();
|
||||
const lines = [...skeletonLines];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line1 = lines[i];
|
||||
const intersectionsOnLine1 = [];
|
||||
|
||||
for (let j = 0; j < lines.length; j++) {
|
||||
if (i === j) continue;
|
||||
const line2 = lines[j];
|
||||
|
||||
const intersection = getLineSegmentIntersection(line1.p1, line1.p2, line2.p1, line2.p2);
|
||||
if (intersection) {
|
||||
if (!pointsEqual(intersection, line1.p1) && !pointsEqual(intersection, line1.p2)) {
|
||||
intersectionsOnLine1.push(intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (intersectionsOnLine1.length > 0) {
|
||||
const pointsOnLine = [line1.p1, line1.p2, ...intersectionsOnLine1];
|
||||
pointsOnLine.sort((a, b) => {
|
||||
const distA = (a.x - line1.p1.x) ** 2 + (a.y - line1.p1.y) ** 2;
|
||||
const distB = (b.x - line1.p1.x) ** 2 + (b.y - line1.p1.y) ** 2;
|
||||
return distA - distB;
|
||||
});
|
||||
|
||||
for (let k = 0; k < pointsOnLine.length - 1; k++) {
|
||||
const pA = pointsOnLine[k];
|
||||
const pB = pointsOnLine[k + 1];
|
||||
|
||||
const key = [pointToKey(pA), pointToKey(pB)].sort().join('|');
|
||||
if (!finalSegments.has(key)) {
|
||||
const newSegment = { ...line1, p1: pA, p2: pB };
|
||||
finalSegments.set(key, newSegment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const key = [pointToKey(line1.p1), pointToKey(line1.p2)].sort().join('|');
|
||||
if (!finalSegments.has(key)) {
|
||||
finalSegments.set(key, line1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(finalSegments.values());
|
||||
}
|
||||
|
||||
|
||||
// --- Skeleton Rebuilding Functions ---
|
||||
|
||||
/**
|
||||
* 점 객체를 고유한 문자열 키로 변환합니다. 정밀도 문제를 피하기 위해 소수점 자리를 고정합니다.
|
||||
@ -404,15 +491,12 @@ const keyToPoint = (key) => {
|
||||
|
||||
/**
|
||||
* 모든 라인 세그먼트로부터 그래프를 구축합니다.
|
||||
* 각 정점(포인트)에 대해 연결된 이웃 정점 목록을 각도순으로 정렬하여 저장합니다.
|
||||
* 이는 면(face)을 시계 반대 방향으로 순회하는 데 필수적입니다.
|
||||
* @param {Array<object>} lines - `{p1, p2}` 형태의 라인 배열
|
||||
* @returns {Map<string, Array<{point: object, angle: number}>>} - 그래프 데이터 구조
|
||||
*/
|
||||
const buildAngularSortedGraph = (lines) => {
|
||||
const graph = new Map();
|
||||
|
||||
// 그래프에 양방향 간선을 추가하는 헬퍼 함수
|
||||
const addEdge = (p1, p2) => {
|
||||
const key1 = pointToKey(p1);
|
||||
const key2 = pointToKey(p2);
|
||||
@ -429,7 +513,6 @@ const buildAngularSortedGraph = (lines) => {
|
||||
|
||||
lines.forEach(line => addEdge(line.p1, line.p2));
|
||||
|
||||
// 각 정점의 이웃 리스트를 각도 기준으로 정렬
|
||||
for (const neighbors of graph.values()) {
|
||||
neighbors.sort((a, b) => a.angle - b.angle);
|
||||
}
|
||||
@ -439,30 +522,25 @@ const buildAngularSortedGraph = (lines) => {
|
||||
|
||||
/**
|
||||
* 각도순으로 정렬된 그래프에서 모든 면(폴리곤)을 찾습니다.
|
||||
* 평면 그래프의 모든 간선을 순회하며 아직 방문하지 않은 간선에서 출발하여
|
||||
* 하나의 면을 구성하는 사이클을 찾습니다.
|
||||
* @param {Map} graph - `buildAngularSortedGraph`로 생성된 그래프
|
||||
* @returns {Array<Array<object>>} - 폴리곤(점들의 배열)들의 배열
|
||||
*/
|
||||
const findFaces = (graph) => {
|
||||
const polygons = [];
|
||||
const visitedHalfEdges = new Set(); // "p1_key->p2_key" 형식으로 방문한 반-간선 저장
|
||||
const visitedHalfEdges = new Set();
|
||||
|
||||
for (const [p1Key, neighbors] of graph.entries()) {
|
||||
for (const neighbor of neighbors) {
|
||||
const p2Key = pointToKey(neighbor.point);
|
||||
const halfEdge = `${p1Key}->${p2Key}`;
|
||||
|
||||
if (visitedHalfEdges.has(halfEdge)) {
|
||||
continue; // 이미 다른 면을 통해 방문한 간선
|
||||
}
|
||||
if (visitedHalfEdges.has(halfEdge)) continue;
|
||||
|
||||
// 새로운 면 탐색 시작
|
||||
const newPolygon = [];
|
||||
let currentHalfEdge = halfEdge;
|
||||
|
||||
while (!visitedHalfEdges.has(currentHalfEdge)) {
|
||||
if (visitedHalfEdges.size > graph.size * 2) { // 무한 루프 방지
|
||||
if (visitedHalfEdges.size > graph.size * 2) {
|
||||
console.error("Infinite loop detected in face finding.");
|
||||
return [];
|
||||
}
|
||||
@ -471,7 +549,6 @@ const findFaces = (graph) => {
|
||||
const [startKey, endKey] = currentHalfEdge.split('->');
|
||||
newPolygon.push(keyToPoint(startKey));
|
||||
|
||||
// 현재 간선의 끝점에서, 들어온 간선의 다음(CCW) 간선을 찾아 다음 경로로 설정
|
||||
const endNodeNeighbors = graph.get(endKey);
|
||||
const incomingEdgeIndex = endNodeNeighbors.findIndex(n => pointToKey(n.point) === startKey);
|
||||
|
||||
@ -485,7 +562,6 @@ const findFaces = (graph) => {
|
||||
}
|
||||
|
||||
if (newPolygon.length > 2) {
|
||||
// 중복 폴리곤 방지
|
||||
const polygonKey = newPolygon.map(p => pointToKey(p)).sort().join('|');
|
||||
if (!polygons.some(p => p.key === polygonKey)) {
|
||||
polygons.push({ key: polygonKey, points: newPolygon });
|
||||
@ -499,29 +575,24 @@ const findFaces = (graph) => {
|
||||
|
||||
/**
|
||||
* 후처리된 스켈레톤 라인과 외벽선을 기반으로 스켈레톤 유사 구조를 재생성합니다.
|
||||
* @param {Array<object>} skeletonLines - 내부 스켈레톤 라인 배열. {p1, p2} 또는 QLine({x1, y1, x2, y2}) 형식을 지원합니다.
|
||||
* @param {Array<object>} baseLines - 외벽선 QLine 객체 배열. (e.g., fabric.Line)
|
||||
* x1, y1, x2, y2 속성을 가져야 합니다.
|
||||
* @param {Array<object>} skeletonLines - 내부 스켈레톤 라인 배열.
|
||||
* @param {Array<object>} baseLines - 외벽선 QLine 객체 배열.
|
||||
* @returns {object|null} - 원본 스켈레톤과 유사한 구조의 객체 { Edges: [...] }. 실패 시 null.
|
||||
*/
|
||||
export const rebuildSkeletonFromLines = (skeletonLines, baseLines) => {
|
||||
if (!skeletonLines || !baseLines) return null;
|
||||
|
||||
// 1. 모든 선분(내부선 + 외벽선)을 동일한 형식({p1, p2})으로 변환하여 결합합니다.
|
||||
// 입력되는 skeletonLines의 타입이 QLine({x1, y1, x2, y2}) 형식일 수 있으므로 두 경우 모두 처리합니다.
|
||||
const allLines = skeletonLines.map(line => {
|
||||
// { p1, p2 } 형태의 raw 객체 처리
|
||||
if (line.p1 && line.p2) {
|
||||
return { p1: line.p1, p2: line.p2 };
|
||||
}
|
||||
// QLine 또는 fabric.Line과 같이 x1, y1, x2, y2 속성을 가진 객체 처리
|
||||
if (typeof line.x1 === 'number' && typeof line.y1 === 'number' &&
|
||||
typeof line.x2 === 'number' && typeof line.y2 === 'number') {
|
||||
return { p1: { x: line.x1, y: line.y1 }, p2: { x: line.x2, y: line.y2 } };
|
||||
}
|
||||
console.warn('Unsupported line format in skeletonLines:', line);
|
||||
return null;
|
||||
}).filter(Boolean); // 유효하지 않은 형식은 걸러냅니다.
|
||||
}).filter(Boolean);
|
||||
|
||||
baseLines.forEach(line => {
|
||||
allLines.push({
|
||||
@ -530,20 +601,16 @@ export const rebuildSkeletonFromLines = (skeletonLines, baseLines) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 2. 그래프를 구축
|
||||
const graph = buildAngularSortedGraph(allLines);
|
||||
if(graph.size === 0) return { Edges: [] };
|
||||
if (graph.size === 0) return { Edges: [] };
|
||||
|
||||
// 3. 그래프에서 모든 면(폴리곤)을 찾음
|
||||
const polygons = findFaces(graph);
|
||||
|
||||
// 4. 각 외벽선에 해당하는 폴리곤을 찾아 스켈레톤 Edge 구조를 만듦
|
||||
const rebuiltEdges = [];
|
||||
baseLines.forEach(baseLine => {
|
||||
const p1 = { x: baseLine.x1, y: baseLine.y1 };
|
||||
const p2 = { x: baseLine.x2, y: baseLine.y2 };
|
||||
|
||||
// 이 baseLine을 변으로 포함하는 폴리곤을 찾음
|
||||
const associatedPolygon = polygons.find(polygon => {
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
const polyP1 = polygon[i];
|
||||
@ -563,8 +630,6 @@ export const rebuildSkeletonFromLines = (skeletonLines, baseLines) => {
|
||||
End: { X: p2.x, Y: p2.y }
|
||||
},
|
||||
Polygon: associatedPolygon.map(p => ({ X: p.x, Y: p.y })),
|
||||
// 원본 skeleton 객체의 다른 속성들(e.g., roof_type)은
|
||||
// 라인 정보만으로는 재생성할 수 없으므로 포함하지 않습니다.
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user