|
|
|
|
@ -5,7 +5,6 @@ import { QLine } from '@/components/fabric/QLine'
|
|
|
|
|
import { getDegreeByChon } from '@/util/canvas-util'
|
|
|
|
|
import Big from 'big.js'
|
|
|
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
|
|
|
import wallLine from '@/components/floor-plan/modal/wallLineOffset/type/WallLine'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
|
|
|
|
|
@ -318,29 +317,10 @@ const movingLineFromSkeleton = (roofId, canvas) => {
|
|
|
|
|
* @param baseLines
|
|
|
|
|
*/
|
|
|
|
|
export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|
|
|
|
|
|
|
|
|
//처마
|
|
|
|
|
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 gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
|
|
|
|
@ -349,35 +329,40 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|
|
|
|
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 = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []
|
|
|
|
|
const baseLinePoints = baseLines.map((line) => ({ x: line.left, y: line.top }))
|
|
|
|
|
const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || [];
|
|
|
|
|
const baseLinePoints = baseLines.map((line) => ({x:line.left, y:line.top}));
|
|
|
|
|
|
|
|
|
|
const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || []
|
|
|
|
|
const outerLinePoints = outerLines.map((line) => ({ x: line.left, y: line.top }))
|
|
|
|
|
const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || [];
|
|
|
|
|
const outerLinePoints = outerLines.map((line) => ({x:line.left, y:line.top}))
|
|
|
|
|
|
|
|
|
|
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 hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || [];
|
|
|
|
|
const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || [];
|
|
|
|
|
|
|
|
|
|
//const skeletonLines = [];
|
|
|
|
|
// 1. 지붕 폴리곤 좌표 전처리
|
|
|
|
|
const coordinates = preprocessPolygonCoordinates(roof.points)
|
|
|
|
|
const coordinates = preprocessPolygonCoordinates(roof.points);
|
|
|
|
|
if (coordinates.length < 3) {
|
|
|
|
|
console.warn('Polygon has less than 3 unique points. Cannot generate skeleton.')
|
|
|
|
|
return
|
|
|
|
|
console.warn("Polygon has less than 3 unique points. Cannot generate skeleton.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const moveFlowLine = roof.moveFlowLine || 0 // Provide a default value
|
|
|
|
|
const moveUpDown = roof.moveUpDown || 0 // Provide a default value
|
|
|
|
|
const moveFlowLine = roof.moveFlowLine || 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) {
|
|
|
|
|
|
|
|
|
|
points = movingLineFromSkeleton(roofId, canvas)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('points:', points)
|
|
|
|
|
|
|
|
|
|
console.log('points:', points);
|
|
|
|
|
const geoJSONPolygon = toGeoJSON(points)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -386,7 +371,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|
|
|
|
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
|
|
|
|
|
|
|
|
|
// 스켈레톤 데이터를 기반으로 내부선 생성
|
|
|
|
|
roof.innerLines = roof.innerLines || []
|
|
|
|
|
roof.innerLines = roof.innerLines || [];
|
|
|
|
|
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
|
|
|
|
|
|
|
|
|
// 캔버스에 스켈레톤 상태 저장
|
|
|
|
|
@ -395,12 +380,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|
|
|
|
canvas.skeletonLines = []
|
|
|
|
|
}
|
|
|
|
|
canvas.skeletonStates[roofId] = true
|
|
|
|
|
canvas.skeletonLines = []
|
|
|
|
|
canvas.skeletonLines = [];
|
|
|
|
|
canvas.skeletonLines.push(...roof.innerLines)
|
|
|
|
|
roof.skeletonLines = canvas.skeletonLines
|
|
|
|
|
roof.skeletonLines = canvas.skeletonLines;
|
|
|
|
|
|
|
|
|
|
const cleanSkeleton = {
|
|
|
|
|
Edges: skeleton.Edges.map((edge) => ({
|
|
|
|
|
Edges: skeleton.Edges.map(edge => ({
|
|
|
|
|
X1: edge.Edge.Begin.X,
|
|
|
|
|
Y1: edge.Edge.Begin.Y,
|
|
|
|
|
X2: edge.Edge.End.X,
|
|
|
|
|
@ -411,14 +396,15 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|
|
|
|
})),
|
|
|
|
|
roofId: roofId,
|
|
|
|
|
// Add other necessary top-level properties
|
|
|
|
|
}
|
|
|
|
|
canvas.skeleton = []
|
|
|
|
|
};
|
|
|
|
|
canvas.skeleton = [];
|
|
|
|
|
canvas.skeleton = cleanSkeleton
|
|
|
|
|
canvas.skeleton.lastPoints = points
|
|
|
|
|
canvas.set('skeleton', cleanSkeleton)
|
|
|
|
|
canvas.set("skeleton", cleanSkeleton);
|
|
|
|
|
canvas.renderAll()
|
|
|
|
|
|
|
|
|
|
console.log('skeleton rendered.', canvas)
|
|
|
|
|
|
|
|
|
|
console.log('skeleton rendered.', canvas);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('스켈레톤 생성 중 오류 발생:', e)
|
|
|
|
|
if (canvas.skeletonStates) {
|
|
|
|
|
@ -592,7 +578,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
|
|
|
|
|
|
|
|
|
|
let roofIdx = 0;
|
|
|
|
|
|
|
|
|
|
// roofLines.forEach((roofLine) => {
|
|
|
|
|
//
|
|
|
|
|
@ -603,17 +589,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
|
|
|
|
|
parentId: roof.id,
|
|
|
|
|
fontSize: roof.fontSize,
|
|
|
|
|
stroke: (sktLine.attributes.isOuterEdge)?'orange':lineStyle.color,
|
|
|
|
|
strokeWidth: lineStyle.width,
|
|
|
|
|
name: (sktLine.attributes.isOuterEdge)?'eaves': attributes.type,
|
|
|
|
|
attributes: {
|
|
|
|
|
...attributes,
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
attributes: attributes,
|
|
|
|
|
direction: direction,
|
|
|
|
|
isBaseLine: sktLine.attributes.isOuterEdge,
|
|
|
|
|
lineName: (sktLine.attributes.isOuterEdge)?'roofLine': attributes.type,
|
|
|
|
|
@ -766,19 +748,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// const sortedWallLines = sortCurrentRoofLines(wall.lines);
|
|
|
|
|
const sortedWallLines = sortCurrentRoofLines(wall.lines);
|
|
|
|
|
// roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬
|
|
|
|
|
const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines);
|
|
|
|
|
const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines);
|
|
|
|
|
// const sortedRoofLines = sortCurrentRoofLines(roofLines);
|
|
|
|
|
const sortedRoofLines = sortCurrentRoofLines(roofLines);
|
|
|
|
|
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines);
|
|
|
|
|
// const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
|
|
|
|
|
const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
|
|
|
|
|
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
|
|
|
|
|
|
|
|
|
|
// 원본 wallLines를 복사하여 사용
|
|
|
|
|
const sortedWallLines = [...wallLines];
|
|
|
|
|
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines);
|
|
|
|
|
const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines);
|
|
|
|
|
|
|
|
|
|
//wall.lines 는 기본 벽 라인
|
|
|
|
|
//wall.baseLine은 움직인라인
|
|
|
|
|
@ -793,29 +770,35 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
// const moveLine = 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 moveLine = wall.baseLines[index]
|
|
|
|
|
const wallBaseLine = wall.baseLines[index]
|
|
|
|
|
//console.log("wallBaseLine", wallBaseLine);
|
|
|
|
|
const moveLine = sortedBaseLines[index]
|
|
|
|
|
const wallBaseLine = sortedBaseLines[index]
|
|
|
|
|
|
|
|
|
|
//roofline 외곽선 설정
|
|
|
|
|
console.log("index::::", index)
|
|
|
|
|
console.log('roofLine:::',roofLine)
|
|
|
|
|
console.log('wallLine', wallLine)
|
|
|
|
|
console.log('wallBaseLine', wallBaseLine)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check if wallBaseLine is inside the polygon formed by sortedWallLines
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
console.log('=== Line Coordinates ===');
|
|
|
|
|
console.table({
|
|
|
|
|
'Point' : ['X', 'Y'],
|
|
|
|
|
'roofLine' : [roofLine.x1, roofLine.y1],
|
|
|
|
|
'currentRoofLine': [currentRoofLine.x1, currentRoofLine.y1],
|
|
|
|
|
'moveLine' : [moveLine.x1, moveLine.y1],
|
|
|
|
|
'wallBaseLine' : [wallBaseLine.x1, wallBaseLine.y1]
|
|
|
|
|
});
|
|
|
|
|
console.log('End Points:');
|
|
|
|
|
console.table({
|
|
|
|
|
'Point' : ['X', 'Y'],
|
|
|
|
|
'roofLine' : [roofLine.x2, roofLine.y2],
|
|
|
|
|
'currentRoofLine': [currentRoofLine.x2, currentRoofLine.y2],
|
|
|
|
|
'moveLine' : [moveLine.x2, moveLine.y2],
|
|
|
|
|
'wallBaseLine' : [wallBaseLine.x2, wallBaseLine.y2]
|
|
|
|
|
});
|
|
|
|
|
*/
|
|
|
|
|
const origin = moveLine.attributes?.originPoint
|
|
|
|
|
if (!origin) return
|
|
|
|
|
|
|
|
|
|
@ -865,7 +848,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
return line
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//getAddLine(roofLine.startPoint, roofLine.endPoint, ) //외곽선을 그린다
|
|
|
|
|
getAddLine(roofLine.startPoint, roofLine.endPoint, )
|
|
|
|
|
|
|
|
|
|
newPStart = { x: roofLine.x1, y: roofLine.y1 }
|
|
|
|
|
newPEnd = { x: roofLine.x2, y: roofLine.y2 }
|
|
|
|
|
@ -958,7 +941,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
|
|
|
|
|
const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
|
|
|
|
|
newPStart.y = aStartY
|
|
|
|
|
newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber()
|
|
|
|
|
newPEnd.y = Big(roofLine.y2).minus(eLineY).toNumber()
|
|
|
|
|
let idx = (0 >= index - 1)?roofLines.length:index
|
|
|
|
|
const newLine = roofLines[idx-1];
|
|
|
|
|
|
|
|
|
|
@ -985,16 +968,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1},{ y: newPStart.y, x: newPStart.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
//newPStart.y = wallLine.y1;
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber();
|
|
|
|
|
newPStart.y = Big(wallBaseLine.y1).minus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
|
|
|
|
|
if(inLine.x2 > inLine.x1 ) {
|
|
|
|
|
getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
newPStart.y = wallLine.y1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1009,7 +983,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
|
|
|
|
|
newPEnd.y = aStartY
|
|
|
|
|
newPStart.y = roofLine.y1//Big(roofLine.y1).plus(eLineY).toNumber()
|
|
|
|
|
newPStart.y = Big(roofLine.y1).plus(eLineY).toNumber()
|
|
|
|
|
let idx = (roofLines.length < index + 1)?0:index
|
|
|
|
|
const newLine = roofLines[idx+1];
|
|
|
|
|
|
|
|
|
|
@ -1036,17 +1010,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
|
|
|
|
|
// newPEnd.y = wallLine.y2
|
|
|
|
|
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber();
|
|
|
|
|
newPEnd.y = Big(wallBaseLine.y2).plus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
|
|
|
|
|
if(inLine.x2 > inLine.x1 ) {
|
|
|
|
|
getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
newPEnd.y = wallLine.y2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1113,7 +1077,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
|
|
|
|
|
newPStart.y = aStartY
|
|
|
|
|
newPEnd.y = roofLine.y2//Big(roofLine.y2).plus(eLineY).toNumber()
|
|
|
|
|
newPEnd.y = Big(roofLine.y2).plus(eLineY).toNumber()
|
|
|
|
|
let idx = (0 >= index - 1)?roofLines.length:index
|
|
|
|
|
const newLine = roofLines[idx-1];
|
|
|
|
|
|
|
|
|
|
@ -1139,19 +1103,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPStart.y, x: newPStart.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
//newPStart.y = wallLine.y1;
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.x1).minus(roofLine.x1).abs().toNumber();
|
|
|
|
|
newPStart.y = Big(wallBaseLine.y1).plus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.x2 > inLine.x1 ) {
|
|
|
|
|
getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newPStart.y = wallLine.y1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1166,7 +1118,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
|
|
|
|
|
newPEnd.y = aStartY
|
|
|
|
|
newPStart.y = roofLine.y1//Big(roofLine.y1).minus(eLineY).toNumber()
|
|
|
|
|
newPStart.y = Big(roofLine.y1).minus(eLineY).toNumber()
|
|
|
|
|
let idx = (roofLines.length < index + 1)?0:index
|
|
|
|
|
const newLine = roofLines[idx+1];
|
|
|
|
|
if(inLine){
|
|
|
|
|
@ -1191,26 +1143,26 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
//newPEnd.y = wallLine.y2;
|
|
|
|
|
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber();
|
|
|
|
|
newPEnd.y = Big(wallBaseLine.y2).minus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.x2 > inLine.x1 ) {
|
|
|
|
|
getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y2, x: inLine.x2}, { y: newPEnd.y, x: newPEnd.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
newPEnd.y = wallLine.y2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// switch (condition) {
|
|
|
|
|
// case 'left_in':
|
|
|
|
|
// break;
|
|
|
|
|
// case 'left_out':
|
|
|
|
|
// break;
|
|
|
|
|
// case 'right_in':
|
|
|
|
|
// break;
|
|
|
|
|
// case 'right_out':
|
|
|
|
|
// break;
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else if (getOrientation(roofLine) === 'horizontal') { //red
|
|
|
|
|
|
|
|
|
|
if (['top', 'bottom'].includes(mLine.position)) {
|
|
|
|
|
@ -1248,7 +1200,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
|
|
|
|
|
}
|
|
|
|
|
//getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange')
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(isStartEnd.end){
|
|
|
|
|
@ -1269,14 +1220,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green')
|
|
|
|
|
getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}else if(condition === 'top_out') {
|
|
|
|
|
console.log("top_out isStartEnd:::::::", isStartEnd);
|
|
|
|
|
|
|
|
|
|
if (isStartEnd.start ) {
|
|
|
|
|
const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
|
|
|
|
|
const aStartX = Big(roofLine.x1).plus(moveDist).toNumber()
|
|
|
|
|
@ -1284,7 +1232,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y })
|
|
|
|
|
|
|
|
|
|
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
|
|
|
|
|
newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber()
|
|
|
|
|
newPEnd.x = Big(newPEnd.x).plus(eLineX).toNumber()
|
|
|
|
|
newPStart.x = aStartX
|
|
|
|
|
let idx = (0 > index - 1)?roofLines.length:index
|
|
|
|
|
const newLine = roofLines[idx-1];
|
|
|
|
|
@ -1312,17 +1260,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}else {
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber();
|
|
|
|
|
newPStart.x = Big(wallBaseLine.x1).plus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.y2 > inLine.y1 ) {
|
|
|
|
|
getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
newPStart.x = wallLine.x1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1334,7 +1272,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y })
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
|
|
|
|
|
newPStart.x = roofLine.x1;//Big(newPStart.x).minus(eLineX).abs().toNumber()
|
|
|
|
|
newPStart.x = Big(newPStart.x).minus(eLineX).abs().toNumber()
|
|
|
|
|
newPEnd.x = aStartX
|
|
|
|
|
let idx = (roofLines.length < index + 1)?0:index
|
|
|
|
|
const newLine = roofLines[idx+1];
|
|
|
|
|
@ -1362,18 +1300,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
//newPEnd.x = wallLine.x2;
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber();
|
|
|
|
|
newPEnd.x = Big(wallBaseLine.x2).minus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.y1 > inLine.y2 ) {
|
|
|
|
|
getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPEnd.y, x: newPEnd.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
newPEnd.x = wallLine.x2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1432,7 +1359,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 })
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
|
|
|
|
|
newPEnd.x = roofLine.x2//Big(roofLine.x2).minus(eLineX).toNumber()
|
|
|
|
|
newPEnd.x = Big(roofLine.x2).minus(eLineX).toNumber()
|
|
|
|
|
newPStart.x = aStartX
|
|
|
|
|
let idx = (0 > index - 1)?roofLines.length:index
|
|
|
|
|
const newLine = roofLines[idx-1];
|
|
|
|
|
@ -1460,18 +1387,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
//newPStart.x = wallLine.x1;
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber();
|
|
|
|
|
newPStart.x = Big(wallBaseLine.x1).minus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.y2 > inLine.y1 ) {
|
|
|
|
|
getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
newPStart.x = wallLine.x1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@ -1485,7 +1401,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
console.log("startLines:::::::", inLine);
|
|
|
|
|
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
|
|
|
|
|
newPEnd.x = aStartX
|
|
|
|
|
newPStart.x = roofLine.x1;//Big(roofLine.x1).plus(eLineX).toNumber()
|
|
|
|
|
newPStart.x = Big(roofLine.x1).plus(eLineX).toNumber()
|
|
|
|
|
let idx = (roofLines.length < index + 1)?0:index
|
|
|
|
|
const newLine = roofLines[idx + 1];
|
|
|
|
|
|
|
|
|
|
@ -1511,34 +1427,29 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple')
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
//newPEnd.x = wallLine.x2;
|
|
|
|
|
//외곽 라인 그리기
|
|
|
|
|
const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber();
|
|
|
|
|
newPEnd.x = Big(wallBaseLine.x2).plus(rLineM).abs().toNumber();
|
|
|
|
|
const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
|
|
|
|
|
if(inLine){
|
|
|
|
|
if(inLine.y1 > inLine.y2 ) {
|
|
|
|
|
getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
newPEnd.x = wallLine.x2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// switch (condition) {
|
|
|
|
|
// case 'top_in':
|
|
|
|
|
// //console.log("findInteriorPoint result:::::::", isStartEnd);
|
|
|
|
|
// break;
|
|
|
|
|
// case 'top_out':
|
|
|
|
|
// //console.log("findInteriorPoint result:::::::", isStartEnd);
|
|
|
|
|
// break;
|
|
|
|
|
// case 'bottom_in':
|
|
|
|
|
// break;
|
|
|
|
|
// case 'bottom_out':
|
|
|
|
|
// break;
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
getAddLine(newPStart, newPEnd, 'red')
|
|
|
|
|
//canvas.remove(roofLine)
|
|
|
|
|
}else{
|
|
|
|
|
getAddLine(roofLine.startPoint, roofLine.endPoint, )
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
canvas.renderAll()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -1564,34 +1475,17 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
|
|
|
|
*/
|
|
|
|
|
function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
|
|
|
|
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 }));
|
|
|
|
|
|
|
|
|
|
//처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음
|
|
|
|
|
const { Begin, End } = edgeResult.Edge;
|
|
|
|
|
// [2] 현재 처리 중인 엣지가 roof.lines의 몇 번째 인덱스인지 찾습니다.
|
|
|
|
|
const roofLineIndex = roof.lines.findIndex(line =>
|
|
|
|
|
let outerLine = roof.lines.find(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) {
|
|
|
|
|
outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
|
|
|
|
console.log('Has matching line:', outerLine);
|
|
|
|
|
//if(outerLine === null) return
|
|
|
|
|
}
|
|
|
|
|
let pitch = outerLine?.attributes?.pitch??0
|
|
|
|
|
|
|
|
|
|
@ -1625,7 +1519,7 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
|
|
|
|
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine);
|
|
|
|
|
//console.log('clipped line', clippedLine.p1, clippedLine.p2);
|
|
|
|
|
const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge])
|
|
|
|
|
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine, targetWallId);
|
|
|
|
|
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine);
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -1750,7 +1644,7 @@ function isOuterEdge(p1, p2, edges) {
|
|
|
|
|
* @param pitch
|
|
|
|
|
* @param isOuterLine
|
|
|
|
|
*/
|
|
|
|
|
function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine, wallLineId) {
|
|
|
|
|
function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine) {
|
|
|
|
|
// 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;
|
|
|
|
|
// processedInnerEdges.add(edgeKey);
|
|
|
|
|
@ -1787,7 +1681,6 @@ function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, is
|
|
|
|
|
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
|
|
|
|
|
isOuterEdge: isOuterLine,
|
|
|
|
|
pitch: pitch,
|
|
|
|
|
wallLineId: wallLineId, // [5] attributes에 wallId 저장 (이 정보가 최종 roofLines에 들어갑니다)
|
|
|
|
|
...(eavesIndex !== undefined && { eavesIndex })
|
|
|
|
|
},
|
|
|
|
|
lineStyle: { color, width },
|
|
|
|
|
@ -3429,8 +3322,7 @@ function findInteriorPoint(line, polygonLines) {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
|
|
|
|
|
* 1순위: 공통 ID(id, matchingId, parentId 등)를 이용한 직접 매칭
|
|
|
|
|
* 2순위: 기하학적 유사성(기울기, 길이, 위치)을 점수화하여 매칭
|
|
|
|
|
* 각 wallLine에 대해 가장 가깝고 평행한 baseLine을 찾아 정렬된 배열을 반환합니다.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
|
|
|
|
|
* @param {Array} wallLines - 기준이 되는 wallLine 배열
|
|
|
|
|
@ -3441,88 +3333,67 @@ export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
|
|
|
|
|
return baseLines;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sortedBaseLines = new Array(wallLines.length).fill(null);
|
|
|
|
|
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;
|
|
|
|
|
const sortedBaseLines = [];
|
|
|
|
|
const usedIndices = new Set(); // 이미 매칭된 baseLine 인덱스를 추적
|
|
|
|
|
|
|
|
|
|
wallLines.forEach((wallLine) => {
|
|
|
|
|
let bestMatchIndex = -1;
|
|
|
|
|
let minDiff = Infinity;
|
|
|
|
|
let minDistance = Infinity;
|
|
|
|
|
|
|
|
|
|
baseLines.forEach((bLine, bIndex) => {
|
|
|
|
|
if (usedBaseIndices.has(bIndex)) return;
|
|
|
|
|
// wallLine의 중점 계산
|
|
|
|
|
const wMidX = (wallLine.x1 + wallLine.x2) / 2;
|
|
|
|
|
const wMidY = (wallLine.y1 + wallLine.y2) / 2;
|
|
|
|
|
|
|
|
|
|
let diff = Infinity;
|
|
|
|
|
// wallLine의 방향 벡터 (평행 확인용)
|
|
|
|
|
const wDx = wallLine.x2 - wallLine.x1;
|
|
|
|
|
const wDy = wallLine.y2 - wallLine.y1;
|
|
|
|
|
const wLen = Math.hypot(wDx, wDy);
|
|
|
|
|
|
|
|
|
|
// 1. 수직선인 경우: X좌표가 일치해야 함 (예: 230.8 == 230.8)
|
|
|
|
|
if (isVertical) {
|
|
|
|
|
// bLine도 수직선인지 확인 (x1, x2 차이가 거의 없어야 함)
|
|
|
|
|
if (Math.abs(bLine.x1 - bLine.x2) < 1.0) {
|
|
|
|
|
// X좌표 차이를 오차(diff)로 계산
|
|
|
|
|
diff = Math.abs(wStart.x - bLine.x1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 2. 수평선인 경우: Y좌표가 일치해야 함
|
|
|
|
|
else if (isHorizontal) {
|
|
|
|
|
// bLine도 수평선인지 확인
|
|
|
|
|
if (Math.abs(bLine.y1 - bLine.y2) < 1.0) {
|
|
|
|
|
diff = Math.abs(wStart.y - bLine.y1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 3. 대각선인 경우: 기울기와 절편 비교 (복잡하므로 거리로 대체)
|
|
|
|
|
else {
|
|
|
|
|
// 중점 간 거리 + 기울기 차이
|
|
|
|
|
// (이전 답변의 로직 사용 가능)
|
|
|
|
|
}
|
|
|
|
|
baseLines.forEach((baseLine, index) => {
|
|
|
|
|
// 이미 매칭된 라인은 건너뜀 (1:1 매칭)
|
|
|
|
|
if (usedIndices.has(index)) return;
|
|
|
|
|
|
|
|
|
|
// 오차가 매우 작으면(예: 1px 미만) 같은 라인으로 간주
|
|
|
|
|
if (diff < 1.0 && diff < minDiff) {
|
|
|
|
|
minDiff = diff;
|
|
|
|
|
bestMatchIndex = bIndex;
|
|
|
|
|
// baseLine의 중점 계산
|
|
|
|
|
const bMidX = (baseLine.x1 + baseLine.x2) / 2;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (bestMatchIndex !== -1) {
|
|
|
|
|
sortedBaseLines[wIndex] = baseLines[bestMatchIndex];
|
|
|
|
|
usedBaseIndices.add(bestMatchIndex);
|
|
|
|
|
sortedBaseLines.push(baseLines[bestMatchIndex]);
|
|
|
|
|
usedIndices.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;
|
|
|
|
|
};
|