This commit is contained in:
yscha 2025-12-21 00:03:04 +09:00
parent ad8990b74c
commit 20e820e298
2 changed files with 907 additions and 886 deletions

View File

@ -631,7 +631,7 @@ export function useMovementSetting(id) {
}
return Big(0); // 기본값으로 0 반환 또는 다른 적절한 기본값
})();
if (target.y1 === target.y2) {
if (Math.abs(target.y1 - target.y2) < 0.5) {
value = value.neg()
}
} else {

View File

@ -494,29 +494,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
});
/*
//2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다.
const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines);
if(disconnectedLines.length > 0) {
disconnectedLines.forEach(dLine => {
const { index, extendedLine, p1Connected, p2Connected } = dLine;
const newPoint = extendedLine?.point;
if (!newPoint) return;
// p1이 끊어졌으면 p1을, p2가 끊어졌으면 p2를 연장된 지점으로 업데이트
if (p1Connected) { //p2 연장
skeletonLines[index].p2 = { ...skeletonLines[index].p2, x: newPoint.x, y: newPoint.y };
} else if (p2Connected) {//p1 연장
skeletonLines[index].p1 = { ...skeletonLines[index].p1, x: newPoint.x, y: newPoint.y };
}
});
//2-1 확장된 스켈레톤 선이 연장되다가 서로 만나면 만난점(접점)에서 멈추어야 된다.
trimIntersectingExtendedLines(skeletonLines, disconnectedLines);
}
*/
//2. 연결이 끊어진 라인이 있을경우 찾아서 추가한다(동 이동일때)
@ -549,17 +527,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
{ x: sktLine.p2.x, y: sktLine.p2.y }
);
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
// roofLines.forEach((roofLine) => {
//
// if (isSameLine(p1.x, p1.y, p2.x, p2.y, roofLine) || isSameLine(p2.x, p2.y, p1.x, p1.y, roofLine)) {
// roofIdx = roofLine.idx;
// console.log("roofIdx::::::", roofIdx)
// return false; // forEach 중단
// }
// });
const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
@ -613,7 +580,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId)
let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId)
roofLineRects.forEach((roofLineRect) => {
canvas.remove(roofLineRect)
canvas.renderAll()
@ -629,69 +595,64 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
return [...lines].sort((a, b) => {
// Get all coordinates in a consistent order
const getCoords = (line) => {
const x1 = line.x1 ?? line.get('x1');
const y1 = line.y1 ?? line.get('y1');
const x2 = line.x2 ?? line.get('x2');
const y2 = line.y2 ?? line.get('y2');
const x1 = line.x1 ?? line.get('x1')
const y1 = line.y1 ?? line.get('y1')
const x2 = line.x2 ?? line.get('x2')
const y2 = line.y2 ?? line.get('y2')
// Sort points left-to-right, then top-to-bottom
return x1 < x2 || (x1 === x2 && y1 < y2)
? [x1, y1, x2, y2]
: [x2, y2, x1, y1];
};
return x1 < x2 || (x1 === x2 && y1 < y2) ? [x1, y1, x2, y2] : [x2, y2, x1, y1]
}
const aCoords = getCoords(a);
const bCoords = getCoords(b);
const aCoords = getCoords(a)
const bCoords = getCoords(b)
// Compare each coordinate in order
for (let i = 0; i < 4; i++) {
if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) {
return aCoords[i] - bCoords[i];
return aCoords[i] - bCoords[i]
}
}
return 0;
});
return 0
})
}
// 각 라인 집합 정렬
const sortWallLines = ensureCounterClockwiseLines(wallLines)
const sortWallBaseLines = ensureCounterClockwiseLines(wall.baseLines)
const sortRoofLines = ensureCounterClockwiseLines(roofLines)
// roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정
const alignLineDirection = (sourceLines, targetLines) => {
return sourceLines.map(sourceLine => {
return sourceLines.map((sourceLine) => {
// 가장 가까운 targetLine 찾기
const nearestTarget = targetLines.reduce((nearest, targetLine) => {
const sourceCenter = {
x: (sourceLine.x1 + sourceLine.x2) / 2,
y: (sourceLine.y1 + sourceLine.y2) / 2
};
y: (sourceLine.y1 + sourceLine.y2) / 2,
}
const targetCenter = {
x: (targetLine.x1 + targetLine.x2) / 2,
y: (targetLine.y1 + targetLine.y2) / 2
};
const distance = Math.hypot(
sourceCenter.x - targetCenter.x,
sourceCenter.y - targetCenter.y
);
y: (targetLine.y1 + targetLine.y2) / 2,
}
const distance = Math.hypot(sourceCenter.x - targetCenter.x, sourceCenter.y - targetCenter.y)
return !nearest || distance < nearest.distance
? { line: targetLine, distance }
: nearest;
}, null)?.line;
return !nearest || distance < nearest.distance ? { line: targetLine, distance } : nearest
}, null)?.line
if (!nearestTarget) return sourceLine;
if (!nearestTarget) return sourceLine
// 방향이 반대인지 확인 (벡터 내적을 사용)
const sourceVec = {
x: sourceLine.x2 - sourceLine.x1,
y: sourceLine.y2 - sourceLine.y1
};
y: sourceLine.y2 - sourceLine.y1,
}
const targetVec = {
x: nearestTarget.x2 - nearestTarget.x1,
y: nearestTarget.y2 - nearestTarget.y1
};
y: nearestTarget.y2 - nearestTarget.y1,
}
const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y;
const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y
// 내적이 음수이면 방향이 반대이므로 뒤집기
if (dotProduct < 0) {
@ -700,81 +661,43 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
x1: sourceLine.x2,
y1: sourceLine.y2,
x2: sourceLine.x1,
y2: sourceLine.y1
};
y2: sourceLine.y1,
}
}
return sourceLine;
});
};
return sourceLine
})
}
console.log("wallBaseLines", wall.baseLines)
// const sortedWallLines = sortCurrentRoofLines(wall.lines);
// roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬
const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines);
const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines)
// const sortedRoofLines = sortCurrentRoofLines(roofLines);
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines)
// const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
console.log('wallBaseLines', wall.baseLines)
// 원본 wallLines를 복사하여 사용
const sortedWallLines = [...wallLines];
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines);
const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines);
//wall.lines 는 기본 벽 라인
//wall.baseLine은 움직인라인
const movedLines = []
let movedLines = []
// 조건에 맞는 라인들만 필터링
const validWallLines = [...wallLines]
.sort((a, b) => a.idx - b.idx)
.filter((wallLine, index) => wallLine.idx - 1 === index);
const validWallLines = [...wallLines].sort((a, b) => a.idx - b.idx).filter((wallLine, index) => wallLine.idx - 1 === index)
wallLines.length > 3 && wallLines.forEach((wallLine, index) => {
const originalIndex = wallLines.indexOf(wallLine)
const roofLine = sortRoofLines[originalIndex]
const currentRoofLine = currentRoofLines[originalIndex]
const moveLine = wall.baseLines[originalIndex]
const wallBaseLine = wall.baseLines[originalIndex]
console.log('', sortRoofLines, sortWallLines, sortWallBaseLines)
sortWallLines.length > 3 &&
sortWallLines.forEach((wallLine, 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 currentRoofLine = currentRoofLines[index];
// const moveLine = wall.baseLines[index]
// const wallBaseLine = wall.baseLines[index]
//console.log("wallBaseLine", wallBaseLine);
const roofLine = sortRoofLines[index]
const wallBaseLine = sortWallBaseLines[index]
//roofline 외곽선 설정
const origin = moveLine.attributes?.originPoint
if (!origin) return
console.log('index::::', index)
console.log("", roofLines, wallLines, wall.baseLines)
console.log('roofLine:', roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2)
// console.log('moveLine:', moveLine.x1, moveLine.y1, moveLine.x2, moveLine.y2)
console.log('wallLine:', wallLine.x1, wallLine.y1, wallLine.x2, wallLine.y2)
console.log('wallBaseLine:', wallBaseLine.x1, wallBaseLine.y1, wallBaseLine.x2, wallBaseLine.y2)
console.log('isSamePoint result:', isSameLine2(wallBaseLine, wallLine))
console.log('isSamePoint result:', isSameLine2(moveLine, wallLine))
if (isSameLine2(moveLine, wallLine)) {
if (isSameLine2(wallBaseLine, wallLine)) {
return
}
const movedStart = Math.abs(moveLine.x1 - wallLine.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON
const movedEnd = Math.abs(moveLine.x2 - wallLine.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON
const movedStart = Math.abs(wallBaseLine.x1 - wallLine.x1) > EPSILON || Math.abs(wallBaseLine.y1 - wallLine.y1) > EPSILON
const movedEnd = Math.abs(wallBaseLine.x2 - wallLine.x2) > EPSILON || Math.abs(wallBaseLine.y2 - wallLine.y2) > EPSILON
const fullyMoved = movedStart && movedEnd
@ -787,8 +710,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const getAddLine = (p1, p2, stroke = '') => {
movedLines.push({ index, p1, p2 })
// Usage:
// let mergeLines = mergeMovedLines(movedLines);
//console.log("mergeLines:::::::", mergeLines);
const line = new QLine([p1.x, p1.y, p2.x, p2.y], {
parentId: roof.id,
@ -846,7 +767,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const positionType = isInPosition ? 'in' : 'out'
const condition = `${mLine.position}_${positionType}`
let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines)
let isStartEnd = findInteriorPoint(wallBaseLine, sortWallBaseLines)
let sPoint, ePoint
if (condition === 'left_in') {
isIn = true
@ -863,8 +784,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).toNumber()
const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
let idx = 0 > index - 1 ? roofLines.length : index
const pLineX = roofLines[idx - 1].x1
// let idx = 0 > index - 1 ? sortRoofLines.length : index
// const pLineX = sortRoofLines[idx - 1].x1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[prevIndex].x1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
@ -887,8 +812,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).plus(moveDist).toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y2).minus(0).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineX = roofLines[idx + 1].x2
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineX = sortRoofLines[idx + 1].x2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
@ -910,8 +839,12 @@ 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()
let idx = 0 >= index - 1 ? roofLines.length : index
const newLine = roofLines[idx - 1]
// let idx = 0 >= index - 1 ? sortRoofLines.length : index
// const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
if (inLine) {
@ -960,8 +893,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
newPEnd.y = aStartY
newPStart.y = roofLine.y1 //Big(roofLine.y1).plus(eLineY).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1]
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
if (inLine) {
@ -1015,8 +953,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index
const pLineX = roofLines[idx - 1].x1
// let idx = 0 >= index - 1 ? sortRoofLines.length : index
// const pLineX = sortRoofLines[idx - 1].x1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[prevIndex].x1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
//getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
@ -1039,8 +981,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const newPointX = Big(roofLine.x1).minus(moveDist).toNumber()
const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
const pLineY = Big(roofLine.y2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineX = roofLines[idx + 1].x2
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineX = sortRoofLines[idx + 1].x2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineX = sortRoofLines[nextIndex].x2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
@ -1063,8 +1009,12 @@ 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).plus(eLineY).toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index
const newLine = roofLines[idx - 1]
// let idx = 0 >= index - 1 ? sortRoofLines.length : index
// const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
if (inLine) {
@ -1113,8 +1063,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
newPEnd.y = aStartY
newPStart.y = roofLine.y1 //Big(roofLine.y1).minus(eLineY).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1]
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (inLine) {
if (inLine.x2 < inLine.x1) {
getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
@ -1172,7 +1127,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const positionType = isInPosition ? 'in' : 'out'
const condition = `${mLine.position}_${positionType}`
let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines)
let isStartEnd = findInteriorPoint(wallBaseLine, sortWallBaseLines)
let sPoint, ePoint
@ -1186,8 +1141,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
const pLineX = Big(roofLine.x1).minus(0).toNumber()
let idx = 0 >= index - 1 ? roofLines.length : index
const pLineY = roofLines[idx - 1].y1
// let idx = 0 >= index - 1 ? sortRoofLines.length : index
// const pLineY = sortRoofLines[idx - 1].y1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[prevIndex].y1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' })
@ -1207,8 +1168,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineY = roofLines[idx + 1].y2
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineY = sortRoofLines[idx + 1].y2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[nextIndex].y2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' })
@ -1231,8 +1198,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber()
newPStart.x = aStartX
let idx = 0 > index - 1 ? roofLines.length : index
const newLine = roofLines[idx - 1]
// let idx = 0 > index - 1 ? sortRoofLines.length : index
// const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
if (inLine) {
@ -1279,8 +1250,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
newPStart.x = roofLine.x1 //Big(newPStart.x).minus(eLineX).abs().toNumber()
newPEnd.x = aStartX
let idx = roofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1]
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
if (inLine) {
@ -1329,8 +1304,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
const pLineX = Big(roofLine.x1).minus(0).abs().toNumber()
let idx = 0 > index - 1 ? roofLines.length : index
const pLineY = roofLines[idx - 1].y1
// let idx = 0 > index - 1 ? sortRoofLines.length : index
// const pLineY = sortRoofLines[idx - 1].y1
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[prevIndex].y1
getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' })
@ -1350,8 +1331,15 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const pLineY = roofLines[idx + 1].y2
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const pLineY = sortRoofLines[idx + 1].y2
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length
const nextIndex = (index + 1) % sortRoofLines.length
const pLineY = sortRoofLines[nextIndex].y2
getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' })
@ -1372,8 +1360,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
newPEnd.x = roofLine.x2 //Big(roofLine.x2).minus(eLineX).toNumber()
newPStart.x = aStartX
let idx = 0 > index - 1 ? roofLines.length : index
const newLine = roofLines[idx - 1]
// let idx = 0 > index - 1 ? sortRoofLines.length : index
// const newLine = sortRoofLines[idx - 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[prevIndex]
if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
if (inLine) {
@ -1422,8 +1414,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
newPEnd.x = aStartX
newPStart.x = roofLine.x1 //Big(roofLine.x1).plus(eLineX).toNumber()
let idx = roofLines.length < index + 1 ? 0 : index
const newLine = roofLines[idx + 1]
// let idx = sortRoofLines.length < index + 1 ? 0 : index
// const newLine = sortRoofLines[idx + 1]
const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length;
const nextIndex = (index + 1) % sortRoofLines.length;
const newLine = sortRoofLines[nextIndex]
if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
if (inLine) {
@ -1473,8 +1469,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
canvas.renderAll()
});
}
})
}
getMoveUpDownLine()
}
@ -1766,6 +1762,115 @@ const isSameLine = (edgeStartX, edgeStartY, edgeEndX, edgeEndY, baseLine) => {
// --- Disconnected Line Processing ---
/**
* 라인들이 반시계 방향이 되도록 정렬하고, 왼쪽 상단에서 시작하는 배열 반환
* @param {Array} lines - x1, y1, x2, y2 속성을 가진 라인 객체 배열
* @returns {Array} 반시계 방향으로 정렬된 라인 배열
*/
export function ensureCounterClockwiseLines(lines) {
if (!lines || lines.length < 3) return [...(lines || [])];
// 1. 모든 점을 연결 그래프로 구성
const graph = new Map();
// 각 점에서 연결된 점들을 저장
lines.forEach(line => {
const p1 = `${line.x1},${line.y1}`;
const p2 = `${line.x2},${line.y2}`;
if (!graph.has(p1)) graph.set(p1, []);
if (!graph.has(p2)) graph.set(p2, []);
// 양방향 연결
graph.get(p1).push({ x: line.x2, y: line.y2, line });
graph.get(p2).push({ x: line.x1, y: line.y1, line });
});
// 2. 왼쪽 상단 점 찾기
let startPoint = null;
let minY = Infinity;
let minX = Infinity;
for (const [pointStr] of graph) {
const [x, y] = pointStr.split(',').map(Number);
if (y < minY || (y === minY && x < minX)) {
minY = y;
minX = x;
startPoint = { x, y };
}
}
if (!startPoint) return [...lines];
// 3. 점들을 순회하며 라인 구성
const visited = new Set();
const result = [];
let current = `${startPoint.x},${startPoint.y}`;
let prev = null;
while (true) {
if (visited.has(current)) break;
visited.add(current);
const neighbors = graph.get(current) || [];
if (neighbors.length === 0) break;
// 이전 점 제외
const nextPoints = neighbors.filter(n =>
!prev || `${n.x},${n.y}` !== `${prev.x},${prev.y}`
);
if (nextPoints.length === 0) break;
// 각도가 가장 작은(반시계 방향) 이웃 선택
const [cx, cy] = current.split(',').map(Number);
const next = nextPoints.reduce((best, curr) => {
const angleBest = Math.atan2(best.y - cy, best.x - cx);
const angleCurr = Math.atan2(curr.y - cy, curr.x - cx);
return angleCurr > angleBest ? curr : best;
}, nextPoints[0]);
// 라인 추가 (방향 유지)
const line = next.line;
const isReversed = (line.x1 !== next.x || line.y1 !== next.y);
result.push({
...line,
x1: isReversed ? line.x2 : line.x1,
y1: isReversed ? line.y2 : line.y1,
x2: isReversed ? line.x1 : line.x2,
y2: isReversed ? line.y1 : line.y2,
idx: result.length
});
prev = { x: cx, y: cy };
current = `${next.x},${next.y}`;
}
// 4. 시계 방향이면 뒤집기
let area = 0;
for (let i = 0; i < result.length; i++) {
const current = result[i];
const next = result[(i + 1) % result.length];
area += (next.x1 - current.x1) * (next.y1 + current.y1);
}
if (area > 0) {
return result.reverse().map((line, idx) => ({
...line,
x1: line.x2,
y1: line.y2,
x2: line.x1,
y2: line.y1,
idx
}));
}
return result;
}
/**
* 점을 선분에 투영한 점의 좌표를 반환합니다.
* @param {object} point - 투영할 {x, y}
@ -2992,17 +3097,32 @@ function pointToLineDistance(point, lineP1, lineP2) {
const getOrientation = (line, eps = 0.1) => {
const x1 = line.get('x1')
const y1 = line.get('y1')
const x2 = line.get('x2')
const y2 = line.get('y2')
const dx = Math.abs(x2 - x1)
const dy = Math.abs(y2 - y1)
if (!line) {
console.error('line 객체가 유효하지 않습니다:', line);
return null; // 또는 적절한 기본값 반환
}
if (dx < eps && dy >= eps) return 'vertical'
if (dy < eps && dx >= eps) return 'horizontal'
if (dx < eps && dy < eps) return 'point'
return 'diagonal'
// get 메서드가 있으면 사용하고, 없으면 직접 프로퍼티에 접근
const getValue = (obj, key) =>
obj && typeof obj.get === 'function' ? obj.get(key) : obj[key];
try {
const x1 = getValue(line, 'x1');
const y1 = getValue(line, 'y1');
const x2 = getValue(line, 'x2');
const y2 = getValue(line, 'y2');
const dx = Math.abs(x2 - x1);
const dy = Math.abs(y2 - y1);
if (dx < eps && dy >= eps) return 'vertical';
if (dy < eps && dx >= eps) return 'horizontal';
if (dx < eps && dy < eps) return 'point';
return 'diagonal';
} catch (e) {
console.error('방향 계산 중 오류 발생:', e);
return null;
}
}
@ -3371,102 +3491,3 @@ function findInteriorPoint(line, polygonLines) {
};
}
/**
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
* 1순위: 공통 ID(id, matchingId, parentId ) 이용한 직접 매칭
* 2순위: 기하학적 유사성(기울기, 길이, 위치) 점수화하여 매칭
*
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
* @param {Array} wallLines - 기준이 되는 wallLine 배열
* @returns {Array} wallLines 순서에 맞춰 정렬된 baseLines
*/
export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
if (!baseLines || !wallLines || baseLines.length === 0 || wallLines.length === 0) {
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;
let bestMatchIndex = -1;
let minDiff = Infinity;
baseLines.forEach((bLine, bIndex) => {
if (usedBaseIndices.has(bIndex)) return;
let diff = Infinity;
// 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 {
// 중점 간 거리 + 기울기 차이
// (이전 답변의 로직 사용 가능)
}
// 오차가 매우 작으면(예: 1px 미만) 같은 라인으로 간주
if (diff < 1.0 && diff < minDiff) {
minDiff = diff;
bestMatchIndex = bIndex;
}
});
if (bestMatchIndex !== -1) {
sortedBaseLines[wIndex] = baseLines[bestMatchIndex];
usedBaseIndices.add(bestMatchIndex);
}
});
// [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;
};