skeleton 변경

This commit is contained in:
ysCha 2025-09-19 18:02:24 +09:00
parent a8d9988f24
commit 816e440ba0

View File

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