From aac4f1fb505cb16e6c57ad1f852530aec8974d81 Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 13 May 2026 16:43:13 +0900 Subject: [PATCH] =?UTF-8?q?[VALLEY-SKIP]=20isValleyVertex=20=EB=85=B8?= =?UTF-8?q?=EC=B9=98=20top=5Fin=20=EC=9E=98=EB=AA=BB=EB=90=9C=20eaveHelpLi?= =?UTF-8?q?ne=204=EA=B0=9C=20=EC=B0=A8=EB=8B=A8=20=E2=80=94=20zero-length?= =?UTF-8?q?=20neighbor=20+=20collinear=20continuation=202=EB=8B=A8=20?= =?UTF-8?q?=EA=B0=80=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 6ba11e2c..ce02181c 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -5153,6 +5153,12 @@ function isValleyVertex(targetPoint, connectedLine, allLines, isStartVertex) { let neighborLine = null; + // [valley degenerate skip 2026-05-13] + // SHOULDER_ABSORBED 로 인접 baseLine 이 zero-length 가 되면 (p1==p2) + // cross product 가 노이즈 작은 값이 되어 valley 판정이 우연에 좌우됨 + // (notch 케이스: cross=59.15 → valley=true 오판 → eaveHelpLine 4개 잘못 생성). + // length < 1.0 (관측치 0.10, 0.50) 이면 skip 하고 그 너머 진짜 인접 라인 탐색. + const DEGEN_LEN = 1.0; if (isStartVertex) { neighborLine = allLines.find(l => { if (l === connectedLine) return false; @@ -5160,6 +5166,7 @@ function isValleyVertex(targetPoint, connectedLine, allLines, isStartVertex) { const ly1 = l.y1 ?? l.get?.('y1'); const lx2 = l.x2 ?? l.get?.('x2'); const ly2 = l.y2 ?? l.get?.('y2'); + if (Math.hypot(lx2 - lx1, ly2 - ly1) < DEGEN_LEN) return false; // [valley degenerate skip 2026-05-13] const end = l.endPoint || { x: lx2, y: ly2 }; return isSamePoint(end, targetPoint, tolerance); }); @@ -5170,6 +5177,7 @@ function isValleyVertex(targetPoint, connectedLine, allLines, isStartVertex) { const ly1 = l.y1 ?? l.get?.('y1'); const lx2 = l.x2 ?? l.get?.('x2'); const ly2 = l.y2 ?? l.get?.('y2'); + if (Math.hypot(lx2 - lx1, ly2 - ly1) < DEGEN_LEN) return false; // [valley degenerate skip 2026-05-13] const start = l.startPoint || { x: lx1, y: ly1 }; return isSamePoint(start, targetPoint, tolerance); }); @@ -5200,7 +5208,29 @@ function isValleyVertex(targetPoint, connectedLine, allLines, isStartVertex) { } const crossProduct = getTurnDirection(p1, p2, p3); - logger.log('crossProduct:', crossProduct); + // [collinear continuation 2026-05-13] + // neigh→target→conn 이 거의 직선이면 (노치 꼬리/연속 segment) + // drift (round 0.1) 로 cross 가 미세한 양/음수 노이즈 → valley 오판. + // |cross| / (|v1|*|v2|) = |sin(theta)|. < 0.05 (≈ 2.9°) 면 직선 연속으로 간주. + const v1len = Math.hypot(p2.x - p1.x, p2.y - p1.y); + const v2len = Math.hypot(p3.x - p2.x, p3.y - p2.y); + let collinearSkip = false; + if (v1len > 0 && v2len > 0) { + const sinTheta = Math.abs(crossProduct) / (v1len * v2len); + if (sinTheta < 0.05) collinearSkip = true; + } + // [valley diag 2026-05-13] L자(정상) vs notch(E-1~E-4 발생) 비교용 + const fmt = (p) => `(${Math.round(p.x)},${Math.round(p.y)})`; + const cLen = Math.hypot(clx2 - clx1, cly2 - cly1); + const nLen = Math.hypot(nlx2 - nlx1, nly2 - nly1); + logger.log( + `[VALLEY] ${isStartVertex ? 'START' : 'END'} target=${fmt(targetPoint)} ` + + `conn=${fmt({x:clx1,y:cly1})}→${fmt({x:clx2,y:cly2})}[len=${cLen.toFixed(2)}] ` + + `neigh=${fmt({x:nlx1,y:nly1})}→${fmt({x:nlx2,y:nly2})}[len=${nLen.toFixed(2)}] ` + + `p1=${fmt(p1)} p2=${fmt(p2)} p3=${fmt(p3)} cross=${crossProduct.toFixed(2)} ` + + `valley=${collinearSkip ? false : (crossProduct > 0)}${collinearSkip ? ' (collinear-skip)' : ''}` + ); + if (collinearSkip) return false; return crossProduct > 0; }