From c05c08e390286659d5d0706a8ea04fffbf42134f Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 8 Apr 2026 16:22:06 +0900 Subject: [PATCH] sk4/8 --- src/hooks/useMode.js | 14 +- src/util/skeleton-utils.js | 958 ++++++++++++++----------------------- 2 files changed, 353 insertions(+), 619 deletions(-) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index a94ca909..46219a65 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1765,10 +1765,10 @@ export function useMode() { const reversedVertices = [...polygon.vertices].reverse() const reversedLines = [] const n = lines.length - // lines[i] 는 edge i (vertex[i] → vertex[i+1]) 에 해당. reverse 후의 edge j 는 - // 원본 edge (n - 1 - j) 와 같은 선분이므로 lines 배열도 동일 매핑으로 재정렬. + // reverse 후 edge j 는 원본 edge (n-2-j) 의 역방향에 해당 + // (reverse edge 0 = v[n-1]→v[n-2] = 원본 edge n-2 역방향, ..., 마지막 edge = 원본 edge n-1 역방향) for (let j = 0; j < n; j++) { - reversedLines.push(lines[(n - 1 - j) % n]) + reversedLines.push(lines[((n - 2 - j) % n + n) % n]) } polygon = createRoofPolygon(reversedVertices) lines = reversedLines @@ -1777,7 +1777,7 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = lines[i % lines.length].attributes.offset + const offset = lines[i % lines.length].attributes.offset === 0 ? 1 : lines[i % lines.length].attributes.offset const dx = edge.outwardNormal.x * offset const dy = edge.outwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) @@ -1819,8 +1819,9 @@ export function useMode() { const reversedVertices = [...polygon.vertices].reverse() const reversedLines = [] const n = lines.length + // reverse 후 edge j 는 원본 edge (n-2-j) 의 역방향에 해당 for (let j = 0; j < n; j++) { - reversedLines.push(lines[(n - 1 - j) % n]) + reversedLines.push(lines[((n - 2 - j) % n + n) % n]) } polygon = createRoofPolygon(reversedVertices) lines = reversedLines @@ -1829,8 +1830,7 @@ export function useMode() { const offsetEdges = [] polygon.edges.forEach((edge, i) => { - const offset = lines[i % lines.length].attributes.offset - + const offset = lines[i % lines.length].attributes.offset === 0 ? 1 : lines[i % lines.length].attributes.offset const dx = edge.inwardNormal.x * offset const dy = edge.inwardNormal.y * offset offsetEdges.push(createOffsetEdge(edge, dx, dy)) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index ae92a224..3719bd9a 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -403,8 +403,20 @@ 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) + + // 디버그: baseLines의 offset/type/좌표 확인 + baseLines.forEach((bl, i) => { + console.log(`[skeleton] baseLine[${i}] type=${bl.attributes?.type} offset=${bl.attributes?.offset} (${Math.round(bl.x1)},${Math.round(bl.y1)})→(${Math.round(bl.x2)},${Math.round(bl.y2)})`) + }) + roof.points.forEach((p, i) => { + console.log(`[skeleton] roof.points[${i}] (${Math.round(p.x)},${Math.round(p.y)})`) + }) + // roof.points 순서와 맞춰야 offset 매핑이 정확함 const baseLinePoints = createOrderedBasePoints(roof.points, baseLines) + baseLinePoints.forEach((p, i) => { + console.log(`[skeleton] orderedBase[${i}] (${Math.round(p.x)},${Math.round(p.y)})`) + }) const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || [] @@ -480,6 +492,11 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { // - 45도 대각 방향(signDx, signDy 각 ±1)이므로 실제 x·y 이동량은 각 축으로 maxStep const maxContactDistance = Math.hypot(maxStep, maxStep) + console.log("orderedBaseLinePoints",orderedBaseLinePoints) + console.log("contactData",contactData) + console.log("roofLineContactPoints",roofLineContactPoints) + console.log("maxContactDistance",maxContactDistance) + let changRoofLinePoints = orderedBaseLinePoints.map((point, index) => { const contactPoint = roofLineContactPoints[index] if (!contactPoint) return point @@ -495,6 +512,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { } }) + console.log("changRoofLinePoints1:::", changRoofLinePoints) // 6. 마루이동(용마루 위치 수동 조정)이 설정된 경우 movingLineFromSkeleton 결과로 대체 if (moveFlowLine !== 0 || moveUpDown !== 0) { @@ -533,6 +551,13 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { changRoofLinePoints = movedPoints } + console.log("roof.points:::", roof.points) + console.log("baseLinePoints:::",baseLinePoints) + console.log("changRoofLinePoints2:::", changRoofLinePoints) + console.log("roofLineContactPoints:::", roofLineContactPoints) + console.log("roofId:::", roofId) + console.log("canvas:::", canvas) + // changRoofLinePoints를 roof.skeletonPoints에 저장 (roof.points 원본은 유지) roof.skeletonPoints = changRoofLinePoints.map(p => ({ x: p.x, y: p.y })) @@ -660,10 +685,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { skeleton.Edges.forEach((edgeResult, index) => { - processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines); }); + // 1-1. dead end(막다른 점) 라인 분석 (로그만, 삭제 안함) + logDeadEndLines(skeletonLines, roof); + // 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다. skeleton.Edges.forEach(edgeResult => { @@ -829,6 +856,137 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const sortWallBaseLines = ensureCounterClockwiseLines(wall.baseLines) const sortRoofLines = ensureCounterClockwiseLines(roofLines) + // ===== 공통 헬퍼 함수 ===== + // axis config: vertical(left/right) → moveAxis='x', lineAxis='y' + // horizontal(top/bottom) → moveAxis='y', lineAxis='x' + + // _in + start 또는 end 처리 + const processInStartEnd = ({ + condition, isStart, roofLine, wallLine, wallBaseLine, index, + newPStart, newPEnd, getAddLine, sortRoofLines, findPoints, + moveAxis, lineAxis, inSign + }) => { + // start: 1번 좌표 사용, end: 2번 좌표 사용 + const k = isStart ? '1' : '2' + const otherK = isStart ? '2' : '1' + const m = `${moveAxis}${k}` // x1 or y1 + const l = `${lineAxis}${k}` // y1 or x1 + const otherL = `${lineAxis}${otherK}` // y2 or x2 + + console.log(`${condition}::::isStartEnd:::::`) + + // 고정 끝점 설정 + if (isStart) { + newPEnd[lineAxis] = roofLine[`${lineAxis}2`] + newPEnd[moveAxis] = roofLine[`${moveAxis}2`] + } else { + newPStart[lineAxis] = roofLine[`${lineAxis}1`] + newPStart[moveAxis] = roofLine[`${moveAxis}1`] + } + + const moveDist = Big(wallBaseLine[m]).minus(wallLine[m]).toNumber() + const ePoint = { [moveAxis]: wallBaseLine[m], [lineAxis]: wallBaseLine[l] } + if (isStart) { + newPStart[lineAxis] = wallBaseLine[l] + } else { + newPEnd[lineAxis] = wallBaseLine[l] + } + + findPoints.push({ ...ePoint, position: `${condition}_${isStart ? 'start' : 'end'}` }) + + let newPointM = Big(roofLine[`${moveAxis}1`]).plus(moveDist).toNumber() + const pLineL = roofLine[l] + + const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length + const nextIndex = (index + 1) % sortRoofLines.length + const adjIndex = isStart ? prevIndex : nextIndex + const pLineM = sortRoofLines[adjIndex][m] + + // blue 라인 + const blueP = isStart ? newPStart : newPEnd + getAddLine({ [moveAxis]: blueP[moveAxis], [lineAxis]: blueP[lineAxis] }, ePoint, 'blue') + + // green, pink 라인 (같은 lineAxis 좌표 근처일 때) + const wbL = wallBaseLine[l] + const wL = wallLine[l] + if (Math.abs(wbL - wL) < 0.1) { + console.warn(`⚠️ [${condition.toUpperCase()}_${isStart ? 'START' : 'END'}] Line crossing detected! newPoint:`, newPointM, 'pLine:', pLineM) + if (moveAxis === 'x') { + getAddLine({ x: pLineM, y: pLineL }, { x: newPointM, y: pLineL }, 'green') + getAddLine({ x: newPointM, y: pLineL }, ePoint, 'pink') + } else { + getAddLine({ x: pLineL, y: pLineM }, { x: pLineL, y: newPointM }, 'green') + getAddLine({ x: pLineL, y: newPointM }, ePoint, 'pink') + } + } + + getAddLine(newPStart, newPEnd, 'red') + } + + // _in + 양쪽 이동 (HIP extensionLine) 처리 + const processInBoth = ({ + condition, roofLine, wallBaseLine, index, + getAddLine, sortRoofLines, findPoints, innerLines, + moveAxis, lineAxis, inSign + }) => { + console.log(`${condition}::::isStartEnd (both):::::`) + + // 원본 기준: moveDistY = abs(roofLine.y - wallBaseLine.y), moveDistX = abs(roofLine.x - wallBaseLine.x) + const moveDistY1 = Math.abs(Big(roofLine.y1).minus(wallBaseLine.y1).toNumber()) + const moveDistX1 = Math.abs(Big(roofLine.x1).minus(wallBaseLine.x1).toNumber()) + const moveDistX2 = Math.abs(Big(roofLine.x2).minus(wallBaseLine.x2).toNumber()) + const moveDistY2 = Math.abs(Big(roofLine.y2).minus(wallBaseLine.y2).toNumber()) + + const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } + const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } + + // 체크 기준: vertical(left/right)→moveDistY, horizontal(top/bottom)→moveDistX + // 즉 항상 lineAxis 방향 거리로 체크 + const checkDist1 = moveAxis === 'x' ? moveDistY1 : moveDistX1 + const checkDist2 = moveAxis === 'x' ? moveDistY2 : moveDistX2 + + // HIP 방향 부호: + // vertical(left/right): ySign=+1/-1(start/end), xSign=inSign + // horizontal(top/bottom): ySign=inSign, xSign=-inSign/inSign(start/end) + const ySignStart = moveAxis === 'x' ? 1 : inSign + const xSignStart = moveAxis === 'x' ? inSign : -inSign + const ySignEnd = moveAxis === 'x' ? -1 : inSign + const xSignEnd = inSign + + const createHipLine = (fromPoint, toPoint) => { + const createdLine = getAddLine(fromPoint, toPoint, 'orange', 'extensionLine') + console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) + createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#FF0000', strokeWidth: 2 }) + createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } + innerLines.push(createdLine) + } + + if (checkDist1 > 0) { + // findPoints 불필요: wallBaseLine 좌표가 ridge 라인 위에 없음 (스켈레톤이 이동된 폴리곤 기준으로 재계산되므로) + // HIP extension 라인만 생성 + if (moveDistY1 > moveDistX1) { + const dist = moveDistY1 - moveDistX1 + createHipLine(sPoint, { x: roofLine.x1, y: roofLine.y1 + ySignStart * dist }) + } else { + const dist = moveDistX1 - moveDistY1 + createHipLine(sPoint, { x: roofLine.x1 + xSignStart * dist, y: roofLine.y1 }) + } + } + + if (checkDist2 > 0) { + if (moveDistY2 > moveDistX2) { + const dist = moveDistY2 - moveDistX2 + createHipLine(ePoint, { x: roofLine.x2, y: roofLine.y2 + ySignEnd * dist }) + } else { + const dist = moveDistX2 - moveDistY2 + createHipLine(ePoint, { x: roofLine.x2 + xSignEnd * dist, y: roofLine.y2 }) + } + } + } + + // _out 처리는 포지션별로 직접 처리 (아래 각 분기에서 인라인 처리) + + // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정 // const alignLineDirection = (sourceLines, targetLines) => { // return sourceLines.map((sourceLine) => { @@ -976,6 +1134,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { //coordinateText(line) canvas.add(line) line.bringToFront() + + // extensionLine은 innerLines에 추가 + // if (lineType === 'extensionLine') { + // innerLines.push(line) + // } + canvas.renderAll() return line } @@ -1013,159 +1177,41 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const condition = `${mLine.position}_${positionType}` let isStartEnd = findInteriorPoint(wallBaseLine, sortWallBaseLines) let sPoint, ePoint - if (condition === 'left_in') { - isIn = true + + // 공통 파라미터: vertical → moveAxis='x', lineAxis='y' + const commonParams = { + roofLine, wallLine, wallBaseLine, index, + newPStart, newPEnd, getAddLine, sortRoofLines, findPoints, innerLines, + moveAxis: 'x', lineAxis: 'y' + } + + // left: inSign=+1(안으로 가면 x 증가), right: inSign=-1 + // left_out: start=-1, end=+1 / right_out: start=+1, end=-1 + const inSign = mLine.position === 'left' ? 1 : -1 + + if (positionType === 'in') { + if (mLine.position === 'left') isIn = true if (isStartEnd.start) { - console.log('left_in::::isStartEnd:::::', isStartEnd) - newPEnd.y = roofLine.y2 - newPEnd.x = roofLine.x2 - - const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - ePoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - newPStart.y = wallBaseLine.y1 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_start' }) - let 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 ? 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') - - if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - // Validate: newPointX should not cross over pLineX (adjacent line) - if (newPointX > pLineX) { - console.warn('⚠️ [LEFT_IN_START] Line crossing detected! newPointX:', newPointX, 'exceeds pLineX:', pLineX, '| Clamping to prevent overlap') - newPointX = pLineX - } else { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - } - - getAddLine(newPStart, newPEnd, 'red') + processInStartEnd({ ...commonParams, condition, isStart: true, inSign }) } - if (isStartEnd.end) { - console.log('left_in::::isStartEnd:::::', isStartEnd) - newPStart.y = roofLine.y1 - newPStart.x = roofLine.x1 - - const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - newPEnd.y = wallBaseLine.y2 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_end' }) - let 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 = 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') - - if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - // Validate: newPointX should not cross over pLineX (adjacent line) - if (newPointX > pLineX) { - console.warn('⚠️ [LEFT_IN_END] Line crossing detected! newPointX:', newPointX, 'exceeds pLineX:', pLineX, '| Clamping to prevent overlap') - newPointX = pLineX - } else { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - } - //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - getAddLine(newPStart, newPEnd, 'red') + processInStartEnd({ ...commonParams, condition, isStart: false, inSign }) } - - if(!isStartEnd.start && !isStartEnd.end){ - console.log('left_in::::isStartEnd:::::', isStartEnd) - const moveDistY1 = Big(roofLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const moveDistX1 = Big(roofLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const moveDistX2 = Big(roofLine.x2).minus(wallBaseLine.x2).abs().toNumber() - const moveDistY2 = Big(roofLine.y2).minus(wallBaseLine.y2).abs().toNumber() - - const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - - - if(moveDistY1 > 0) { - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'left_in_start' }) - - if(moveDistY1 > moveDistX1){ - const dist = moveDistY1 - moveDistX1 - // console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 + dist }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX1 - moveDistY1 - // console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 + dist, y: roofLine.y1 }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - } - - } - if(moveDistY2 > 0) { - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_end' }) - - if(moveDistY2 > moveDistX2){ - const dist = moveDistY2 - moveDistX2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red') - // console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'orange', 'extensionLine') - console.log('extensionLine created1 - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX2 - moveDistY2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red') - // console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 + dist, y: roofLine.y2 }, 'orange', 'extensionLine') - console.log('extensionLine created2 - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - } - } - + if (!isStartEnd.start && !isStartEnd.end) { + processInBoth({ ...commonParams, condition, inSign }) } - } else if (condition === 'left_out') { - console.log('left_out::::isStartEnd:::::', isStartEnd) if (isStartEnd.start) { const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const aStartY = Big(roofLine.y1).minus(moveDist).abs().toNumber() - const bStartY = Big(wallLine.y1).minus(moveDist).abs().toNumber() + const aStartY = Big(roofLine.y1).minus(moveDist).toNumber() + const bStartY = Big(wallLine.y1).minus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x2 }) - const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() + const eLineY = Big(bStartY).minus(wallLine.y1).toNumber() newPStart.y = aStartY - newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber() - // let idx = 0 >= index - 1 ? sortRoofLines.length : index - // const newLine = sortRoofLines[idx - 1] + newPEnd.y = roofLine.y2 const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length const nextIndex = (index + 1) % sortRoofLines.length @@ -1181,12 +1227,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') findPoints.push({ y: aStartY, x: newPStart.x, position: 'left_out_start' }) - }else{ + } else { newPStart.y = roofLine.y1 } - } else { - const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() + const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).toNumber() newPStart.y = Big(newPStart.y).minus(cLineY).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) if (inLine) { @@ -1196,8 +1241,6 @@ 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).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) @@ -1219,18 +1262,14 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const aStartY = Big(roofLine.y2).plus(moveDist).toNumber() const bStartY = Big(wallLine.y2).plus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - 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() - // let idx = sortRoofLines.length < index + 1 ? 0 : index - // const newLine = sortRoofLines[idx + 1] + newPStart.y = roofLine.y1 const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length; const nextIndex = (index + 1) % sortRoofLines.length; const newLine = sortRoofLines[prevIndex] - if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { if (inLine) { if (inLine.x1 < inLine.x2) { @@ -1241,10 +1280,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta') getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray') findPoints.push({ y: aStartY, x: newPEnd.x, position: 'left_out_end' }) - }else{ + } else { newPEnd.y = roofLine.y2 } - } else { const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() newPEnd.y = Big(newPEnd.y).plus(cLineY).toNumber() @@ -1256,9 +1294,6 @@ 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).plus(rLineM).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) @@ -1275,158 +1310,17 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine(newPStart, newPEnd, 'red') } - } else if (condition === 'right_in') { - - if (isStartEnd.start) { - console.log('right_in::::isStartEnd:::::', isStartEnd) - newPEnd.y = roofLine.y2 - newPEnd.x = roofLine.x2 - - const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - ePoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - newPStart.y = wallBaseLine.y1 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_start' }) - let 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 ? 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') - - if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - // Validate: newPointX should not cross over pLineX (adjacent line) - if (newPointX < pLineX) { - console.warn('⚠️ [RIGHT_IN_START] Line crossing detected! newPointX:', newPointX, 'is less than pLineX:', pLineX, '| Clamping to prevent overlap') - newPointX = pLineX - } else { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - } - getAddLine(newPStart, newPEnd, 'red') - } - - if (isStartEnd.end) { - console.log('left_in::::isStartEnd:::::', isStartEnd) - newPStart.y = roofLine.y1 - newPStart.x = roofLine.x1 - - const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - newPEnd.y = wallBaseLine.y2 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_end' }) - let 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 = 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: Big(roofLine.y1).minus(moveDist).toNumber() }, { x: newPointX, y: Big(roofLine.y1).minus(moveDist).toNumber()}, 'orange') - - if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - // Validate: newPointX should not cross over pLineX (adjacent line) - if (newPointX < pLineX) { - console.warn('⚠️ [RIGHT_IN_END] Line crossing detected! newPointX:', newPointX, 'is less than pLineX:', pLineX, '| Clamping to prevent overlap') - newPointX = pLineX - } else { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - } - //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - getAddLine(newPStart, newPEnd, 'red') - } - - if(!isStartEnd.start && !isStartEnd.end){ - console.log('right_in::::isStartEnd:::::', isStartEnd) - const moveDistY1 = Big(roofLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const moveDistX1 = Big(roofLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const moveDistX2 = Big(roofLine.x2).minus(wallBaseLine.x2).abs().toNumber() - const moveDistY2 = Big(roofLine.y2).minus(wallBaseLine.y2).abs().toNumber() - - const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - - - if(moveDistY1 > 0) { - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'right_in_start' }) - - if(moveDistY1 > moveDistX1){ - const dist = moveDistY1 - moveDistX1 - // console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 + dist }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX1 - moveDistY1 - // console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 - dist, y: roofLine.y1 }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - } - - } - if(moveDistY2 > 0) { - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_end' }) - - if(moveDistY2 > moveDistX2){ - const dist = moveDistY2 - moveDistX2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red') - // console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'orange', 'extensionLine') - console.log('extensionLine created1 - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX2 - moveDistY2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red') - // console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'orange', 'extensionLine') - console.log('extensionLine created2 - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - } - } - - } - - } else if (condition === 'right_out') { - console.log('right_out::::isStartEnd:::::', isStartEnd) + if (isStartEnd.start) { - //x1 inside + console.log('right_out::::isStartEnd:::::', isStartEnd) const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() const aStartY = Big(roofLine.y1).plus(moveDist).toNumber() const bStartY = Big(wallLine.y1).plus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - console.log('startLines:::::::', inLine) - const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() + const eLineY = Big(bStartY).minus(wallLine.y1).toNumber() newPStart.y = aStartY - newPEnd.y = roofLine.y2 //Big(roofLine.y2).plus(eLineY).toNumber() - // let idx = 0 >= index - 1 ? sortRoofLines.length : index - // const newLine = sortRoofLines[idx - 1] + newPEnd.y = roofLine.y2 const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length const nextIndex = (index + 1) % sortRoofLines.length @@ -1442,10 +1336,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_start' }) - }else{ + } else { newPStart.y = roofLine.y1 } - } else { const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() newPStart.y = Big(newPStart.y).plus(cLineY).toNumber() @@ -1457,10 +1350,8 @@ 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).toNumber() + newPStart.y = Big(wallBaseLine.y1).minus(rLineM).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) if (inLine) { if (inLine.x2 > inLine.x1) { @@ -1479,12 +1370,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const aStartY = Big(roofLine.y2).minus(moveDist).toNumber() const bStartY = Big(wallLine.y2).minus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - 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() - // let idx = sortRoofLines.length < index + 1 ? 0 : index - // const newLine = sortRoofLines[idx + 1] + newPStart.y = roofLine.y1 const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length; const nextIndex = (index + 1) % sortRoofLines.length; @@ -1512,9 +1400,6 @@ 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).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) @@ -1532,7 +1417,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } } else if (getOrientation(roofLine) === 'horizontal') { - //red if (['top', 'bottom'].includes(mLine.position)) { if (Math.abs(wallLine.y1 - wallBaseLine.y1) < 0.1 || Math.abs(wallLine.y2 - wallBaseLine.y2) < 0.1) { @@ -1552,154 +1436,38 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { let sPoint, ePoint - if (condition === 'top_in') { + // 공통 파라미터: horizontal → moveAxis='y', lineAxis='x' + const commonParams = { + roofLine, wallLine, wallBaseLine, index, + newPStart, newPEnd, getAddLine, sortRoofLines, findPoints, innerLines, + moveAxis: 'y', lineAxis: 'x' + } + + // top: inSign=+1(안으로 가면 y 증가), bottom: inSign=-1 + const inSign = mLine.position === 'top' ? 1 : -1 + + if (positionType === 'in') { if (isStartEnd.start) { - console.log('top_in start') - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - newPStart.x = wallBaseLine.x1 - - let newPointY = Big(roofLine.y2).plus(moveDist).toNumber() - - const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() - const pLineX = Big(roofLine.x1).minus(0).toNumber() - // 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' }) - - if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - // Validate: newPointY should not cross over pLineY (adjacent line) - if (newPointY > pLineY) { - console.warn('⚠️ [TOP_IN_START] Line crossing detected! newPointY:', newPointY, 'exceeds pLineY:', pLineY, '| Clamping to prevent overlap') - newPointY = pLineY - } else { - 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.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') - getAddLine(newPStart, newPEnd, 'red') + processInStartEnd({ ...commonParams, condition, isStart: true, inSign }) } - if (isStartEnd.end) { - console.log('top_in end', isStartEnd) - const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() - sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - newPEnd.x = wallBaseLine.x2 - - let newPointY = Big(roofLine.y1).plus(moveDist).toNumber() - - const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() - const pLineX = Big(roofLine.x2).minus(0).toNumber() - - // 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' }) - - if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - // Validate: newPointY should not cross over pLineY (adjacent line) - if (newPointY > pLineY) { - console.warn('⚠️ [TOP_IN_END] Line crossing detected! newPointY:', newPointY, 'exceeds pLineY:', pLineY, '| Clamping to prevent overlap') - newPointY = pLineY - } else { - 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') - getAddLine(newPStart, newPEnd, 'red') + processInStartEnd({ ...commonParams, condition, isStart: false, inSign }) } - - if(!isStartEnd.start && !isStartEnd.end){ - console.log('top_in start and end false') - - const moveDistY1 = Big(roofLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const moveDistX1 = Big(roofLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const moveDistX2 = Big(roofLine.x2).minus(wallBaseLine.x2).abs().toNumber() - const moveDistY2 = Big(roofLine.y2).minus(wallBaseLine.y2).abs().toNumber() - - const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - - - if(moveDistX1 > 0) { - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' }) - - if(moveDistY1 > moveDistX1){ - const dist = moveDistY1 - moveDistX1 - // console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 + dist }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX1 - moveDistY1 - // console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 - dist, y: roofLine.y1 }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - } - - } - if(moveDistX2 > 0) { - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'top_in_end' }) - - if(moveDistY2 > moveDistX2){ - const dist = moveDistY2 - moveDistX2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red') - // console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 + dist }, 'orange', 'extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX2 - moveDistY2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red') - // console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 + dist, y: roofLine.y2 }, 'orange', 'extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - } - } - + if (!isStartEnd.start && !isStartEnd.end) { + processInBoth({ ...commonParams, condition, inSign }) } } else if (condition === 'top_out') { - console.log('top_out isStartEnd:::::::', isStartEnd) if (isStartEnd.start) { + console.log('top_out isStartEnd:::::::', isStartEnd) const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() const aStartX = Big(roofLine.x1).plus(moveDist).toNumber() const bStartX = Big(wallLine.x1).plus(moveDist).toNumber() 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 = roofLine.x2 newPStart.x = aStartX - // 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; @@ -1715,10 +1483,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_start' }) - }else{ //라인머지 + } else { newPStart.x = roofLine.x1 } - } else { const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() newPStart.x = Big(newPStart.x).plus(cLineX).toNumber() @@ -1730,7 +1497,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') } } else { - //외곽 라인 그리기 const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber() newPStart.x = Big(wallBaseLine.x1).plus(rLineM).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) @@ -1751,12 +1517,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const aStartX = Big(roofLine.x2).minus(moveDist).toNumber() const bStartX = Big(wallLine.x2).minus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y }) - console.log('startLines:::::::', inLine) const eLineX = Big(bStartX).minus(wallLine.x2).toNumber() - newPStart.x = roofLine.x1 //Big(newPStart.x).minus(eLineX).abs().toNumber() + newPStart.x = roofLine.x1 newPEnd.x = aStartX - // 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; @@ -1772,10 +1535,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_end' }) - }else{ + } else { newPEnd.x = roofLine.x2 } - } else { const cLineX = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() newPEnd.x = Big(newPEnd.x).minus(cLineX).toNumber() @@ -1787,8 +1549,6 @@ 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).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) @@ -1803,141 +1563,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } getAddLine(newPStart, newPEnd, 'red') } - } else if (condition === 'bottom_in') { - console.log('bottom_in::::isStartEnd:::::', isStartEnd) - if (isStartEnd.start) { - console.log('isStartEnd:::::', isStartEnd) - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - newPStart.x = wallBaseLine.x1 - - let newPointY = Big(roofLine.y2).minus(moveDist).toNumber() - - const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() - const pLineX = Big(roofLine.x1).minus(0).toNumber() - - // 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' }) - - if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - // Validate: newPointY should not cross over pLineY (adjacent line) - if (newPointY < pLineY) { - console.warn('⚠️ [BOTTOM_IN_START] Line crossing detected! newPointY:', newPointY, 'is less than pLineY:', pLineY, '| Clamping to prevent overlap') - newPointY = pLineY - } else { - 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.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') - getAddLine(newPStart, newPEnd, 'red') - } - - if (isStartEnd.end) { - console.log('isStartEnd:::::', isStartEnd) - const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() - sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - newPEnd.x = wallBaseLine.x2 - - let newPointY = Big(roofLine.y1).minus(moveDist).toNumber() - - const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() - const pLineX = Big(roofLine.x2).minus(0).toNumber() - - // 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' }) - - if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - // Validate: newPointY should not cross over pLineY (adjacent line) - if (newPointY < pLineY) { - console.warn('⚠️ [BOTTOM_IN_END] Line crossing detected! newPointY:', newPointY, 'is less than pLineY:', pLineY, '| Clamping to prevent overlap') - newPointY = pLineY - }else { - 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') - getAddLine(newPStart, newPEnd, 'red') - } - - if(!isStartEnd.start && !isStartEnd.end){ - - console.log('isStartEnd:::::', isStartEnd) - - const moveDistY1 = Big(roofLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const moveDistX1 = Big(roofLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const moveDistX2 = Big(roofLine.x2).minus(wallBaseLine.x2).abs().toNumber() - const moveDistY2 = Big(roofLine.y2).minus(wallBaseLine.y2).abs().toNumber() - - const sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 } - const ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - - - if(moveDistX1 > 0) { - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' }) - - if(moveDistY1 > moveDistX1){ - const dist = moveDistY1 - moveDistX1 - // console.log('Creating extensionLine (Y1 > X1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1, y: roofLine.y1 - dist }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX1 - moveDistY1 - // console.log('Creating extensionLine (X1 > Y1):', { sPoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: sPoint.x, y: sPoint.y }, { x: roofLine.x1 + dist, y: roofLine.y1 }, 'orange','extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - } - - } - if(moveDistX2 > 0) { - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'bottom_in_end' }) - - if(moveDistY2 > moveDistX2){ - const dist = moveDistY2 - moveDistX2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'red') - // console.log('Creating extensionLine (Y2 > X2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2, y: roofLine.y2 - dist }, 'orange', 'extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - }else{ - const dist = moveDistX2 - moveDistY2 - // getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'red') - // console.log('Creating extensionLine (X2 > Y2):', { ePoint, roofLine, dist, roofId }) - const createdLine = getAddLine({ x: ePoint.x, y: ePoint.y }, { x: roofLine.x2 - dist, y: roofLine.y2 }, 'orange', 'extensionLine') - console.log('extensionLine created - length:', createdLine.length, 'coords:', { x1: createdLine.x1, y1: createdLine.y1, x2: createdLine.x2, y2: createdLine.y2 }) - createdLine.set({ name: LINE_TYPE.SUBLINE.HIP, lineName: LINE_TYPE.SUBLINE.HIP, stroke: '#1083E3', strokeWidth: 2 }) - createdLine.attributes = { ...createdLine.attributes, type: LINE_TYPE.SUBLINE.HIP } - innerLines.push(createdLine) - - } - } - } } else if (condition === 'bottom_out') { console.log('bottom_out isStartEnd:::::::', isStartEnd) if (isStartEnd.start) { @@ -1946,12 +1571,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const aStartX = Big(roofLine.x1).minus(moveDist).toNumber() const bStartX = Big(wallLine.x1).minus(moveDist).toNumber() 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 = roofLine.x2 newPStart.x = aStartX - // 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; @@ -1967,10 +1589,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_start' }) - }else { + } else { newPStart.x = roofLine.x1 } - } else { const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() newPStart.x = Big(newPStart.x).minus(cLineX).toNumber() @@ -1982,8 +1603,6 @@ 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).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) @@ -2005,12 +1624,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const aStartX = Big(roofLine.x2).plus(moveDist).toNumber() const bStartX = Big(wallLine.x2).plus(moveDist).toNumber() const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 }) - 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() - // let idx = sortRoofLines.length < index + 1 ? 0 : index - // const newLine = sortRoofLines[idx + 1] + newPStart.x = roofLine.x1 const prevIndex = (index - 1 + sortRoofLines.length) % sortRoofLines.length; const nextIndex = (index + 1) % sortRoofLines.length; @@ -2026,10 +1642,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_end' }) - }else{ + } else { newPEnd.x = roofLine.x2 } - } else { const cLineX = Big(wallBaseLine.y2).minus(wallLine.y2).abs().toNumber() newPEnd.x = Big(newPEnd.x).plus(cLineX).toNumber() @@ -2041,8 +1656,6 @@ 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).toNumber() const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) @@ -2163,23 +1776,140 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { return false } + console.log('📐 [processEavesEdge] face 분석:', { + hasOuterLine: !!outerLine, + outerLineType: outerLine?.attributes?.type, + pitch, + roofLineIndex, + edgeBegin: { x: Math.round(Begin.X), y: Math.round(Begin.Y) }, + edgeEnd: { x: Math.round(End.X), y: Math.round(End.Y) }, + polygonPointCount: polygonPoints.length, + polygonPoints: polygonPoints.map(p => ({ x: Math.round(p.x), y: Math.round(p.y) })), + skPtsCount: skPts.length, + }) + for (let i = 0; i < polygonPoints.length; i++) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; + const _isSkipOuter = skPts.length > 0 && isSkeletonOuterEdge(p1, p2) + // 확장된 외곽선에 해당하는 edge는 스킵 - if (skPts.length > 0 && isSkeletonOuterEdge(p1, p2)) continue + if (_isSkipOuter) { + console.log(' [edge', i, '] SKIP(outerEdge)', { p1: {x: Math.round(p1.x), y: Math.round(p1.y)}, p2: {x: Math.round(p2.x), y: Math.round(p2.y)} }) + continue + } // 지붕 경계선과 교차 확인 및 클리핑 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]) + + const _dx = Math.abs(clippedLine.p2.x - clippedLine.p1.x) + const _dy = Math.abs(clippedLine.p2.y - clippedLine.p1.y) + const _lineType = (_dx < 0.5 && _dy > 0.5) ? '⚠️수직' : (_dy < 0.5 && _dx > 0.5) ? '수평' : '대각' + console.log(` [edge ${i}] ${_lineType} ${isOuterLine ? 'OUTER' : 'INNER'} (${Math.round(clippedLine.p1.x)},${Math.round(clippedLine.p1.y)})→(${Math.round(clippedLine.p2.x)},${Math.round(clippedLine.p2.y)}) dx:${Math.round(_dx)} dy:${Math.round(_dy)}`) + addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine, targetWallId); // } } } +/** + * 중복 제거 후 막다른 점(dead end)을 가진 라인과 제로 길이 라인을 제거합니다. + * 정상 스켈레톤의 내부 꼭짓점은 최소 3개 라인이 만나야 하지만, + * artifact 라인의 끝점은 자기 자신(역방향 포함)과 제로 길이 라인만 연결됩니다. + */ +/** + * dead end(막다른 점) 라인 분석 - 로그만 출력, 삭제하지 않음 + * 조건: degree=1(유니크 라인 1개만 연결) + 지붕 꼭짓점 아님 → 삭제 후보 + */ +function logDeadEndLines(skeletonLines, roof) { + const tolerance = 2.0; + const isNear = (a, b) => Math.abs(a.x - b.x) < tolerance && Math.abs(a.y - b.y) < tolerance; + + // 지붕 꼭짓점 수집 + const roofVertices = []; + (roof.skeletonPoints || []).forEach(p => roofVertices.push(p)); + (roof.points || []).forEach(p => roofVertices.push(p)); + (roof.lines || []).forEach(line => { + roofVertices.push({ x: line.x1, y: line.y1 }); + roofVertices.push({ x: line.x2, y: line.y2 }); + }); + + const isRoofVertex = (p) => roofVertices.some(v => isNear(v, p)); + + // 1. 중복 제거 (방향 무시) + 제로 길이 제거 + const uniqueLines = []; + const seenKeys = new Set(); + + skeletonLines.forEach(line => { + const dx = Math.abs(line.p2.x - line.p1.x); + const dy = Math.abs(line.p2.y - line.p1.y); + if (dx < 0.5 && dy < 0.5) return; + + const key = [ + `${line.p1.x.toFixed(1)},${line.p1.y.toFixed(1)}`, + `${line.p2.x.toFixed(1)},${line.p2.y.toFixed(1)}` + ].sort().join('|'); + + if (!seenKeys.has(key)) { + seenKeys.add(key); + uniqueLines.push(line); + } + }); + + // 2. 각 꼭짓점의 degree 카운트 + const vertexDegree = new Map(); + const vertexKey = (p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`; + + uniqueLines.forEach(line => { + const k1 = vertexKey(line.p1); + const k2 = vertexKey(line.p2); + vertexDegree.set(k1, (vertexDegree.get(k1) || 0) + 1); + vertexDegree.set(k2, (vertexDegree.get(k2) || 0) + 1); + }); + + // 3. dead end 꼭짓점: degree=1 이면서 지붕 꼭짓점이 아닌 점 + const deadEndVertices = new Set(); + vertexDegree.forEach((degree, key) => { + if (degree === 1) { + const [x, y] = key.split(',').map(Number); + if (!isRoofVertex({ x, y })) { + deadEndVertices.add(key); + } + } + }); + + // 4. 로그 출력 + skeletonLines.forEach((line, idx) => { + const dx = Math.abs(line.p2.x - line.p1.x); + const dy = Math.abs(line.p2.y - line.p1.y); + + if (dx < 0.5 && dy < 0.5) { + console.log('⚠️ [logDeadEndLines] 제로 길이:', { idx, p1: { x: Math.round(line.p1.x), y: Math.round(line.p1.y) } }); + return; + } + + const k1 = vertexKey(line.p1); + const k2 = vertexKey(line.p2); + const p1Dead = deadEndVertices.has(k1); + const p2Dead = deadEndVertices.has(k2); + + if (p1Dead || p2Dead) { + console.log('⚠️ [logDeadEndLines] 삭제 후보:', { + idx, + p1: { x: Math.round(line.p1.x), y: Math.round(line.p1.y), deadEnd: p1Dead, isRoof: isRoofVertex(line.p1) }, + p2: { x: Math.round(line.p2.x), y: Math.round(line.p2.y), deadEnd: p2Dead, isRoof: isRoofVertex(line.p2) }, + dx: Math.round(dx), dy: Math.round(dy), + }); + } + }); + + console.log(`🔍 [logDeadEndLines] 전체: ${skeletonLines.length}, 유니크: ${uniqueLines.length}, dead end 꼭짓점: ${deadEndVertices.size}`); +} + function findMatchingLine(edgePolygon, roof, roofPoints) { const edgePoints = edgePolygon.map(p => ({ x: p.X, y: p.Y })); @@ -3858,8 +3588,12 @@ function isPointOnLineSegment(point, lineStart, lineEnd, tolerance = 0.1) { */ function updateAndAddLine(innerLines, targetPoint) { - // 1. Find the line containing the target point - const foundLine = findLineContainingPoint(innerLines, targetPoint); + // 1. Find the line containing the target point (HIP 라인 제외 - HIP 위의 점이 잘못 매칭되는 것 방지) + const nonHipLines = innerLines.filter(line => line.attributes?.type !== LINE_TYPE.SUBLINE.HIP) + const foundLine = findLineContainingPoint(nonHipLines, targetPoint); + console.log(`[updateAndAddLine] position=${targetPoint.position} point=(${targetPoint.x},${targetPoint.y})`, + foundLine ? `foundLine: (${foundLine.x1},${foundLine.y1})→(${foundLine.x2},${foundLine.y2}) name=${foundLine.name||foundLine.lineName||'?'} type=${foundLine.attributes?.type||'?'}` : 'NOT FOUND', + `innerLines count=${innerLines.length}, nonHip=${nonHipLines.length}`) if (!foundLine) { console.warn('No line found containing the target point'); return [...innerLines];