roofLine변경

This commit is contained in:
ysCha 2025-12-10 19:05:12 +09:00
parent d0168ad553
commit 94fe5889ea

View File

@ -5,6 +5,7 @@ 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'
/**
* 지붕 폴리곤의 스켈레톤(중심선) 생성하고 캔버스에 그립니다.
@ -321,6 +322,28 @@ 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) {
roof.lines.forEach((rLine, index) => {
// 동일한 인덱스의 벽 라인 가져오기
const wLine = wallObj.lines[index];
if (wLine) {
// attributes.wallId는 부모(벽) ID라서 모두 동일합니다.
// 개별 라인을 식별하기 위해 wLine.id (라인 객체 고유 ID)를 저장합니다.
// * 새로고침 시 wLine.id가 바뀌어도, 이 로직이 그때마다 돌면서 최신 ID로 갱신해줍니다.
rLine.attributes.wallLineId = wLine.id;
// 필요하다면 부모 ID도 같이 저장 (참조용)
if (wLine.attributes?.wallId) {
rLine.attributes.parentWallId = wLine.attributes.wallId;
}
}
});
}
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
@ -578,7 +601,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
);
//그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
let roofIdx = 0;
// roofLines.forEach((roofLine) => {
//
@ -589,13 +612,17 @@ 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,
@ -755,6 +782,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
const sortedRoofLines = sortCurrentRoofLines(roofLines);
const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines);
const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
//wall.lines 는 기본 벽 라인
@ -770,11 +798,13 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
// const moveLine = sortedWallBaseLines[index]
// const wallBaseLine = sortedWallBaseLines[index]
const roofLine = roofLines[index];
console.log("wallLine::::", wallLine)
const roofLine = sortRoofLines[index]//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId);
console.log("roofLine", roofLine);
const currentRoofLine = currentRoofLines[index];
const moveLine = sortedBaseLines[index]
const wallBaseLine = sortedBaseLines[index]
console.log("wallBaseLine", wallBaseLine);
//roofline 외곽선 설정
@ -862,7 +892,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
//두 포인트가 변경된 라인인
if (fullyMoved ) {
//반시계방향향
const mLine = getSelectLinePosition(wall, wallBaseLine)
if (getOrientation(roofLine) === 'vertical') {
@ -1448,7 +1478,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
// }
}
}
getAddLine(newPStart, newPEnd, 'red')
//canvas.remove(roofLine)
}
canvas.renderAll()
});
@ -1475,14 +1507,30 @@ 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;
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)
);
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);
@ -1519,7 +1567,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);
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine, targetWallId);
// }
}
}
@ -1644,7 +1692,7 @@ function isOuterEdge(p1, p2, edges) {
* @param pitch
* @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('|');
// if (processedInnerEdges.has(edgeKey)) return;
// processedInnerEdges.add(edgeKey);
@ -1681,6 +1729,7 @@ 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 },
@ -3322,7 +3371,8 @@ function findInteriorPoint(line, polygonLines) {
/**
* baseLines의 순서를 wallLines의 순서와 일치시킵니다.
* wallLine에 대해 가장 가깝고 평행한 baseLine을 찾아 정렬된 배열을 반환합니다.
* 1순위: 공통 ID(id, matchingId, parentId ) 이용한 직접 매칭
* 2순위: 기하학적 유사성(기울기, 길이, 위치) 점수화하여 매칭
*
* @param {Array} baseLines - 정렬할 원본 baseLine 배열
* @param {Array} wallLines - 기준이 되는 wallLine 배열
@ -3333,67 +3383,88 @@ export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
return baseLines;
}
const sortedBaseLines = [];
const usedIndices = new Set(); // 이미 매칭된 baseLine 인덱스를 추적
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;
wallLines.forEach((wallLine) => {
let bestMatchIndex = -1;
let minDistance = Infinity;
let minDiff = Infinity;
// wallLine의 중점 계산
const wMidX = (wallLine.x1 + wallLine.x2) / 2;
const wMidY = (wallLine.y1 + wallLine.y2) / 2;
baseLines.forEach((bLine, bIndex) => {
if (usedBaseIndices.has(bIndex)) return;
// wallLine의 방향 벡터 (평행 확인용)
const wDx = wallLine.x2 - wallLine.x1;
const wDy = wallLine.y2 - wallLine.y1;
const wLen = Math.hypot(wDx, wDy);
let diff = Infinity;
baseLines.forEach((baseLine, index) => {
// 이미 매칭된 라인은 건너뜀 (1:1 매칭)
if (usedIndices.has(index)) return;
// 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;
}
// 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.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]);
}
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;
};