diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 7a8ddda7..fb23d06c 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -5429,7 +5429,35 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //추가된 하단 지붕 라인 innerLines에 추가. innerLines.push(...downRoofLines) - //지붕선에 따라 라인추가 작업 처리. + //지붕선에 따라 라인추가 작업 처리. (split 공용 함수 — docs §3-1) + splitRoofLinesToInnerLines(roof, innerLines, canvas, textMode) + + //지붕 innerLines에 추가. + roof.innerLines = innerLines + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() +} + +/** + * roof.lines(처마 외곽선)를 innerLine 끝점 기준으로 split 해 편집 가능한 innerLine(hip/roofLine)으로 만든다. + * 변별/용마루 경로 공용. innerLines 에 결과를 in-place push 하고 반환한다. + * (용마루 경로는 추녀 연장 후 호출해야 종점이 처마변 위에 있어 분할점이 잡힌다 — docs §9) + * @param {fabric.Object} roof - 대상 지붕 (roof.lines 사용) + * @param {Array} innerLines - 내부선 배열 (split 결과가 여기에 in-place 추가됨) + * @param {fabric.Canvas} canvas + * @param {string} textMode + * @returns {Array} innerLines + */ +export const splitRoofLinesToInnerLines = (roof, innerLines, canvas, textMode) => { + // 분할점이 처마변 끝점(코너)과 이 거리(px) 이내면 분할에서 제외. + // SK 추녀 연장이 처마 코너에 닿을 때 ray cast 부동소수 오차(관측 0.02px)로 코너와 미세하게 + // 어긋나면, 기존 almostEqual(1e-10)은 "끝점 아님"으로 오판 → 0 길이 조각 생성. END_EPS 로 흡수. + // 변별 경로의 진짜 분할점은 끝점에서 수백 px 떨어져 있어 영향 없음. + const END_EPS = 1.0 const innerLinesPoints = [] innerLines.forEach((line) => { const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1) @@ -5464,17 +5492,15 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (hasOverlapLine) return innerLinesPoints.forEach((point) => { - if ( - isPointOnLineNew(currentLine, point) && - !(almostEqual(currentLine.x1, point.x) && almostEqual(currentLine.y1, point.y)) && - !(almostEqual(currentLine.x2, point.x) && almostEqual(currentLine.y2, point.y)) - ) { - const distance = Math.sqrt((point.x - currentLine.x1) ** 2 + (point.y - currentLine.y1) ** 2) - splitPoint.push({ point, distance }) + const distToStart = Math.hypot(point.x - currentLine.x1, point.y - currentLine.y1) + const distToEnd = Math.hypot(point.x - currentLine.x2, point.y - currentLine.y2) + // 변 위 점이면서 양 끝점(코너)에서 END_EPS 보다 멀 때만 진짜 분할점으로 채택. + if (isPointOnLineNew(currentLine, point) && distToStart > END_EPS && distToEnd > END_EPS) { + splitPoint.push({ point, distance: distToStart }) } }) if (splitPoint.length > 0) { - splitPoint.sort((a, b) => a[1] - b[1]) + splitPoint.sort((a, b) => a.distance - b.distance) let startPoint = { x: currentLine.x1, y: currentLine.y1 } for (let i = 0; i < splitPoint.length; i++) { const point = splitPoint[i].point @@ -5495,14 +5521,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - //지붕 innerLines에 추가. - roof.innerLines = innerLines - - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() + return innerLines } /**