Merge pull request 'sortRoofLines 개선' (#460) from dev_cha into dev
Reviewed-on: #460
This commit is contained in:
commit
f898b4434b
@ -5,6 +5,7 @@ import { QLine } from '@/components/fabric/QLine'
|
|||||||
import { getDegreeByChon } from '@/util/canvas-util'
|
import { getDegreeByChon } from '@/util/canvas-util'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import wallLine from '@/components/floor-plan/modal/wallLineOffset/type/WallLine'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
|
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
|
||||||
@ -317,10 +318,29 @@ const movingLineFromSkeleton = (roofId, canvas) => {
|
|||||||
* @param baseLines
|
* @param baseLines
|
||||||
*/
|
*/
|
||||||
export const skeletonBuilder = (roofId, canvas, textMode) => {
|
export const skeletonBuilder = (roofId, canvas, textMode) => {
|
||||||
|
|
||||||
//처마
|
//처마
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
||||||
|
|
||||||
|
// [추가] wall 객체를 찾아 roof.lines에 wallId를 직접 주입 (초기화)
|
||||||
|
// 지붕은 벽을 기반으로 생성되므로 라인의 순서(Index)가 동일합니다.
|
||||||
|
const wallObj = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
||||||
|
|
||||||
|
if (roof && wallObj && roof.lines && wallObj.lines) {
|
||||||
|
// 개선된 코드 (기하학적 매칭)
|
||||||
|
// or use some other unique properties
|
||||||
|
|
||||||
|
|
||||||
|
roof.lines.forEach((rLine, index) => {
|
||||||
|
// 벽 라인 중에서 시작점과 끝점이 일치하는 라인 찾기
|
||||||
|
const wLine = wallObj.lines[index]
|
||||||
|
if (wLine) {
|
||||||
|
// 안정적인 ID 생성
|
||||||
|
rLine.attributes.wallLine = wLine.id; // Use the stable ID
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
||||||
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
||||||
@ -329,40 +349,35 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
||||||
//const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
|
//const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
|
||||||
|
|
||||||
const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || [];
|
const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []
|
||||||
const baseLinePoints = baseLines.map((line) => ({x:line.left, y:line.top}));
|
const baseLinePoints = baseLines.map((line) => ({ x: line.left, y: line.top }))
|
||||||
|
|
||||||
const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || [];
|
const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || []
|
||||||
const outerLinePoints = outerLines.map((line) => ({x:line.left, y:line.top}))
|
const outerLinePoints = outerLines.map((line) => ({ x: line.left, y: line.top }))
|
||||||
|
|
||||||
const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || [];
|
const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || []
|
||||||
const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || [];
|
const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || []
|
||||||
|
|
||||||
//const skeletonLines = [];
|
//const skeletonLines = [];
|
||||||
// 1. 지붕 폴리곤 좌표 전처리
|
// 1. 지붕 폴리곤 좌표 전처리
|
||||||
const coordinates = preprocessPolygonCoordinates(roof.points);
|
const coordinates = preprocessPolygonCoordinates(roof.points)
|
||||||
if (coordinates.length < 3) {
|
if (coordinates.length < 3) {
|
||||||
console.warn("Polygon has less than 3 unique points. Cannot generate skeleton.");
|
console.warn('Polygon has less than 3 unique points. Cannot generate skeleton.')
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveFlowLine = roof.moveFlowLine || 0; // Provide a default value
|
const moveFlowLine = roof.moveFlowLine || 0 // Provide a default value
|
||||||
const moveUpDown = roof.moveUpDown || 0; // Provide a default value
|
const moveUpDown = roof.moveUpDown || 0 // Provide a default value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let points = roof.points;
|
|
||||||
|
|
||||||
|
let points = roof.points
|
||||||
|
|
||||||
|
|
||||||
//마루이동
|
//마루이동
|
||||||
if (moveFlowLine !== 0 || moveUpDown !== 0) {
|
if (moveFlowLine !== 0 || moveUpDown !== 0) {
|
||||||
|
|
||||||
points = movingLineFromSkeleton(roofId, canvas)
|
points = movingLineFromSkeleton(roofId, canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('points:', points)
|
||||||
console.log('points:', points);
|
|
||||||
const geoJSONPolygon = toGeoJSON(points)
|
const geoJSONPolygon = toGeoJSON(points)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -371,7 +386,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
||||||
|
|
||||||
// 스켈레톤 데이터를 기반으로 내부선 생성
|
// 스켈레톤 데이터를 기반으로 내부선 생성
|
||||||
roof.innerLines = roof.innerLines || [];
|
roof.innerLines = roof.innerLines || []
|
||||||
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
||||||
|
|
||||||
// 캔버스에 스켈레톤 상태 저장
|
// 캔버스에 스켈레톤 상태 저장
|
||||||
@ -380,12 +395,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
canvas.skeletonLines = []
|
canvas.skeletonLines = []
|
||||||
}
|
}
|
||||||
canvas.skeletonStates[roofId] = true
|
canvas.skeletonStates[roofId] = true
|
||||||
canvas.skeletonLines = [];
|
canvas.skeletonLines = []
|
||||||
canvas.skeletonLines.push(...roof.innerLines)
|
canvas.skeletonLines.push(...roof.innerLines)
|
||||||
roof.skeletonLines = canvas.skeletonLines;
|
roof.skeletonLines = canvas.skeletonLines
|
||||||
|
|
||||||
const cleanSkeleton = {
|
const cleanSkeleton = {
|
||||||
Edges: skeleton.Edges.map(edge => ({
|
Edges: skeleton.Edges.map((edge) => ({
|
||||||
X1: edge.Edge.Begin.X,
|
X1: edge.Edge.Begin.X,
|
||||||
Y1: edge.Edge.Begin.Y,
|
Y1: edge.Edge.Begin.Y,
|
||||||
X2: edge.Edge.End.X,
|
X2: edge.Edge.End.X,
|
||||||
@ -396,15 +411,14 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
})),
|
})),
|
||||||
roofId: roofId,
|
roofId: roofId,
|
||||||
// Add other necessary top-level properties
|
// Add other necessary top-level properties
|
||||||
};
|
}
|
||||||
canvas.skeleton = [];
|
canvas.skeleton = []
|
||||||
canvas.skeleton = cleanSkeleton
|
canvas.skeleton = cleanSkeleton
|
||||||
canvas.skeleton.lastPoints = points
|
canvas.skeleton.lastPoints = points
|
||||||
canvas.set("skeleton", cleanSkeleton);
|
canvas.set('skeleton', cleanSkeleton)
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
|
|
||||||
|
console.log('skeleton rendered.', canvas)
|
||||||
console.log('skeleton rendered.', canvas);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('스켈레톤 생성 중 오류 발생:', e)
|
console.error('스켈레톤 생성 중 오류 발생:', e)
|
||||||
if (canvas.skeletonStates) {
|
if (canvas.skeletonStates) {
|
||||||
@ -578,7 +592,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
|
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
|
||||||
let roofIdx = 0;
|
|
||||||
|
|
||||||
// roofLines.forEach((roofLine) => {
|
// roofLines.forEach((roofLine) => {
|
||||||
//
|
//
|
||||||
@ -589,13 +603,17 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
|
||||||
const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
|
const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
|
||||||
parentId: roof.id,
|
parentId: roof.id,
|
||||||
fontSize: roof.fontSize,
|
fontSize: roof.fontSize,
|
||||||
stroke: (sktLine.attributes.isOuterEdge)?'orange':lineStyle.color,
|
stroke: (sktLine.attributes.isOuterEdge)?'orange':lineStyle.color,
|
||||||
strokeWidth: lineStyle.width,
|
strokeWidth: lineStyle.width,
|
||||||
name: (sktLine.attributes.isOuterEdge)?'eaves': attributes.type,
|
name: (sktLine.attributes.isOuterEdge)?'eaves': attributes.type,
|
||||||
attributes: attributes,
|
attributes: {
|
||||||
|
...attributes,
|
||||||
|
|
||||||
|
},
|
||||||
direction: direction,
|
direction: direction,
|
||||||
isBaseLine: sktLine.attributes.isOuterEdge,
|
isBaseLine: sktLine.attributes.isOuterEdge,
|
||||||
lineName: (sktLine.attributes.isOuterEdge)?'roofLine': attributes.type,
|
lineName: (sktLine.attributes.isOuterEdge)?'roofLine': attributes.type,
|
||||||
@ -755,6 +773,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
const sortedRoofLines = sortCurrentRoofLines(roofLines);
|
const sortedRoofLines = sortCurrentRoofLines(roofLines);
|
||||||
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines);
|
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines);
|
||||||
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
|
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
|
||||||
|
const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
|
||||||
|
|
||||||
|
|
||||||
//wall.lines 는 기본 벽 라인
|
//wall.lines 는 기본 벽 라인
|
||||||
@ -770,11 +789,20 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
// const moveLine = sortedWallBaseLines[index]
|
// const moveLine = sortedWallBaseLines[index]
|
||||||
// const wallBaseLine = sortedWallBaseLines[index]
|
// const wallBaseLine = sortedWallBaseLines[index]
|
||||||
|
|
||||||
|
const roofLine = sortRoofLines[index];
|
||||||
|
if(roofLine.attributes.wallLine !== wallLine.id || (roofLine.idx - 1) !== index ){
|
||||||
|
console.log("wallLine2::::", wallLine.id)
|
||||||
|
console.log('roofLine:::',roofLine.attributes.wallLine)
|
||||||
|
console.log("w:::",wallLine.startPoint, wallLine.endPoint)
|
||||||
|
console.log("R:::",roofLine.startPoint, roofLine.endPoint)
|
||||||
|
console.log("not matching roofLine", roofLine);
|
||||||
|
return false
|
||||||
|
}//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId);
|
||||||
|
|
||||||
const roofLine = roofLines[index];
|
|
||||||
const currentRoofLine = currentRoofLines[index];
|
const currentRoofLine = currentRoofLines[index];
|
||||||
const moveLine = sortedBaseLines[index]
|
const moveLine = sortedBaseLines[index]
|
||||||
const wallBaseLine = sortedBaseLines[index]
|
const wallBaseLine = sortedBaseLines[index]
|
||||||
|
console.log("wallBaseLine", wallBaseLine);
|
||||||
|
|
||||||
//roofline 외곽선 설정
|
//roofline 외곽선 설정
|
||||||
|
|
||||||
@ -1448,7 +1476,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddLine(newPStart, newPEnd, 'red')
|
getAddLine(newPStart, newPEnd, 'red')
|
||||||
|
//canvas.remove(roofLine)
|
||||||
}
|
}
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
});
|
});
|
||||||
@ -1475,14 +1505,30 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|||||||
*/
|
*/
|
||||||
function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
||||||
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
||||||
|
// [1] 벽 객체를 가져옵니다.
|
||||||
|
let wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId);
|
||||||
|
|
||||||
const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
|
||||||
|
|
||||||
//처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음
|
//처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음
|
||||||
const { Begin, End } = edgeResult.Edge;
|
const { Begin, End } = edgeResult.Edge;
|
||||||
let outerLine = roof.lines.find(line =>
|
// [2] 현재 처리 중인 엣지가 roof.lines의 몇 번째 인덱스인지 찾습니다.
|
||||||
|
const roofLineIndex = roof.lines.findIndex(line =>
|
||||||
line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
|
line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let outerLine = null;
|
||||||
|
let targetWallId = null;
|
||||||
|
|
||||||
|
// [3] 인덱스를 통해 매칭되는 벽 라인의 불변 ID(wallId)를 가져옵니다.
|
||||||
|
if (roofLineIndex !== -1) {
|
||||||
|
outerLine = roof.lines[roofLineIndex];
|
||||||
|
if (wall && wall.lines && wall.lines[roofLineIndex]) {
|
||||||
|
targetWallId = wall.lines[roofLineIndex].attributes.wallId;
|
||||||
|
}
|
||||||
|
targetWallId = outerLine.attributes.wallId;
|
||||||
|
}
|
||||||
|
|
||||||
if(!outerLine) {
|
if(!outerLine) {
|
||||||
outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
||||||
console.log('Has matching line:', outerLine);
|
console.log('Has matching line:', outerLine);
|
||||||
@ -1519,7 +1565,7 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
|||||||
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine);
|
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine);
|
||||||
//console.log('clipped line', clippedLine.p1, clippedLine.p2);
|
//console.log('clipped line', clippedLine.p1, clippedLine.p2);
|
||||||
const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge])
|
const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge])
|
||||||
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine);
|
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine, targetWallId);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1644,7 +1690,7 @@ function isOuterEdge(p1, p2, edges) {
|
|||||||
* @param pitch
|
* @param pitch
|
||||||
* @param isOuterLine
|
* @param isOuterLine
|
||||||
*/
|
*/
|
||||||
function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine) {
|
function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine, wallLineId) {
|
||||||
// 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;
|
||||||
// processedInnerEdges.add(edgeKey);
|
// processedInnerEdges.add(edgeKey);
|
||||||
@ -1681,6 +1727,7 @@ function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, is
|
|||||||
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
|
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
|
||||||
isOuterEdge: isOuterLine,
|
isOuterEdge: isOuterLine,
|
||||||
pitch: pitch,
|
pitch: pitch,
|
||||||
|
wallLineId: wallLineId, // [5] attributes에 wallId 저장 (이 정보가 최종 roofLines에 들어갑니다)
|
||||||
...(eavesIndex !== undefined && { eavesIndex })
|
...(eavesIndex !== undefined && { eavesIndex })
|
||||||
},
|
},
|
||||||
lineStyle: { color, width },
|
lineStyle: { color, width },
|
||||||
@ -3322,7 +3369,8 @@ function findInteriorPoint(line, polygonLines) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
|
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
|
||||||
* 각 wallLine에 대해 가장 가깝고 평행한 baseLine을 찾아 정렬된 배열을 반환합니다.
|
* 1순위: 공통 ID(id, matchingId, parentId 등)를 이용한 직접 매칭
|
||||||
|
* 2순위: 기하학적 유사성(기울기, 길이, 위치)을 점수화하여 매칭
|
||||||
*
|
*
|
||||||
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
|
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
|
||||||
* @param {Array} wallLines - 기준이 되는 wallLine 배열
|
* @param {Array} wallLines - 기준이 되는 wallLine 배열
|
||||||
@ -3333,67 +3381,88 @@ export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
|
|||||||
return baseLines;
|
return baseLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedBaseLines = [];
|
const sortedBaseLines = new Array(wallLines.length).fill(null);
|
||||||
const usedIndices = new Set(); // 이미 매칭된 baseLine 인덱스를 추적
|
const usedBaseIndices = new Set();
|
||||||
|
|
||||||
|
// [1단계] ID 매칭 (기존 로직 유지 - 혹시 ID가 있는 경우를 대비)
|
||||||
|
// ... (ID 매칭 코드는 생략하거나 유지) ...
|
||||||
|
|
||||||
|
// [2단계] 'originPoint' 또는 좌표 일치성을 이용한 강력한 기하학적 매칭
|
||||||
|
wallLines.forEach((wLine, wIndex) => {
|
||||||
|
if (sortedBaseLines[wIndex]) return;
|
||||||
|
|
||||||
|
// 비교할 기준 좌표 설정 (originPoint가 있으면 그것을, 없으면 현재 좌표 사용)
|
||||||
|
const wStart = wLine.attributes?.originPoint
|
||||||
|
? { x: wLine.attributes.originPoint.x1, y: wLine.attributes.originPoint.y1 }
|
||||||
|
: { x: wLine.x1, y: wLine.y1 };
|
||||||
|
|
||||||
|
const wEnd = wLine.attributes?.originPoint
|
||||||
|
? { x: wLine.attributes.originPoint.x2, y: wLine.attributes.originPoint.y2 }
|
||||||
|
: { x: wLine.x2, y: wLine.y2 };
|
||||||
|
|
||||||
|
// 수직/수평 여부 판단
|
||||||
|
const isVertical = Math.abs(wStart.x - wEnd.x) < 0.1;
|
||||||
|
const isHorizontal = Math.abs(wStart.y - wEnd.y) < 0.1;
|
||||||
|
|
||||||
wallLines.forEach((wallLine) => {
|
|
||||||
let bestMatchIndex = -1;
|
let bestMatchIndex = -1;
|
||||||
let minDistance = Infinity;
|
let minDiff = Infinity;
|
||||||
|
|
||||||
// wallLine의 중점 계산
|
baseLines.forEach((bLine, bIndex) => {
|
||||||
const wMidX = (wallLine.x1 + wallLine.x2) / 2;
|
if (usedBaseIndices.has(bIndex)) return;
|
||||||
const wMidY = (wallLine.y1 + wallLine.y2) / 2;
|
|
||||||
|
|
||||||
// wallLine의 방향 벡터 (평행 확인용)
|
let diff = Infinity;
|
||||||
const wDx = wallLine.x2 - wallLine.x1;
|
|
||||||
const wDy = wallLine.y2 - wallLine.y1;
|
|
||||||
const wLen = Math.hypot(wDx, wDy);
|
|
||||||
|
|
||||||
baseLines.forEach((baseLine, index) => {
|
// 1. 수직선인 경우: X좌표가 일치해야 함 (예: 230.8 == 230.8)
|
||||||
// 이미 매칭된 라인은 건너뜀 (1:1 매칭)
|
if (isVertical) {
|
||||||
if (usedIndices.has(index)) return;
|
// bLine도 수직선인지 확인 (x1, x2 차이가 거의 없어야 함)
|
||||||
|
if (Math.abs(bLine.x1 - bLine.x2) < 1.0) {
|
||||||
// baseLine의 중점 계산
|
// X좌표 차이를 오차(diff)로 계산
|
||||||
const bMidX = (baseLine.x1 + baseLine.x2) / 2;
|
diff = Math.abs(wStart.x - bLine.x1);
|
||||||
const bMidY = (baseLine.y1 + baseLine.y2) / 2;
|
|
||||||
|
|
||||||
// 두 라인의 중점 사이 거리 계산
|
|
||||||
const dist = Math.hypot(wMidX - bMidX, wMidY - bMidY);
|
|
||||||
|
|
||||||
// 평행 여부 확인 (내적 사용)
|
|
||||||
const bDx = baseLine.x2 - baseLine.x1;
|
|
||||||
const bDy = baseLine.y2 - baseLine.y1;
|
|
||||||
const bLen = Math.hypot(bDx, bDy);
|
|
||||||
|
|
||||||
if (wLen > 0 && bLen > 0) {
|
|
||||||
// 단위 벡터 내적값 (-1 ~ 1)
|
|
||||||
const dot = (wDx * bDx + wDy * bDy) / (wLen * bLen);
|
|
||||||
|
|
||||||
// 내적의 절대값이 1에 가까우면 평행 (약 10도 오차 허용)
|
|
||||||
if (Math.abs(Math.abs(dot) - 1) < 0.1) {
|
|
||||||
if (dist < minDistance) {
|
|
||||||
minDistance = dist;
|
|
||||||
bestMatchIndex = index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2. 수평선인 경우: Y좌표가 일치해야 함
|
||||||
|
else if (isHorizontal) {
|
||||||
|
// bLine도 수평선인지 확인
|
||||||
|
if (Math.abs(bLine.y1 - bLine.y2) < 1.0) {
|
||||||
|
diff = Math.abs(wStart.y - bLine.y1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. 대각선인 경우: 기울기와 절편 비교 (복잡하므로 거리로 대체)
|
||||||
|
else {
|
||||||
|
// 중점 간 거리 + 기울기 차이
|
||||||
|
// (이전 답변의 로직 사용 가능)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 오차가 매우 작으면(예: 1px 미만) 같은 라인으로 간주
|
||||||
|
if (diff < 1.0 && diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
bestMatchIndex = bIndex;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bestMatchIndex !== -1) {
|
if (bestMatchIndex !== -1) {
|
||||||
sortedBaseLines.push(baseLines[bestMatchIndex]);
|
sortedBaseLines[wIndex] = baseLines[bestMatchIndex];
|
||||||
usedIndices.add(bestMatchIndex);
|
usedBaseIndices.add(bestMatchIndex);
|
||||||
} else {
|
|
||||||
// 매칭되는 라인을 찾지 못한 경우, 아직 사용되지 않은 첫 번째 라인을 할당 (Fallback)
|
|
||||||
const unusedIndex = baseLines.findIndex((_, idx) => !usedIndices.has(idx));
|
|
||||||
if (unusedIndex !== -1) {
|
|
||||||
sortedBaseLines.push(baseLines[unusedIndex]);
|
|
||||||
usedIndices.add(unusedIndex);
|
|
||||||
} else {
|
|
||||||
// 더 이상 남은 라인이 없으면 null 또는 기존 라인 중 하나(에러 방지)
|
|
||||||
sortedBaseLines.push(baseLines[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// [3단계] 남은 라인 처리 (Fallback)
|
||||||
|
// 매칭되지 않은 wallLine들에 대해 남은 baseLines를 순서대로 배정하거나
|
||||||
|
// 거리 기반 근사 매칭을 수행
|
||||||
|
// ... (기존 fallback 로직) ...
|
||||||
|
|
||||||
|
// 빈 구멍 채우기 (null 방지)
|
||||||
|
for(let i=0; i<sortedBaseLines.length; i++) {
|
||||||
|
if(!sortedBaseLines[i]) {
|
||||||
|
const unused = baseLines.findIndex((_, idx) => !usedBaseIndices.has(idx));
|
||||||
|
if(unused !== -1) {
|
||||||
|
sortedBaseLines[i] = baseLines[unused];
|
||||||
|
usedBaseIndices.add(unused);
|
||||||
|
} else {
|
||||||
|
sortedBaseLines[i] = baseLines[0]; // 최후의 수단
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sortedBaseLines;
|
return sortedBaseLines;
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user