/* 509-3 공용 선 그리기 화면 밖 구간 건너뛰기 헬퍼 — `B04_PreProcess_UI_MapRender.ts` 의 * `drawPreparedLayer` 를 가짜 캔버스로 돌려 그린 구간 · 호출 수를 JSON 으로 낸다. * 판정 = 화면(+ 여백)에 걸치는 원래 구간이 모두 그려졌나 · 화면 밖 구간은 줄었나. */ const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const { load } = makeLoader({ root: ROOT, ui: () => ({ themeColor: () => "" }) }); const render = load("B04_PreProcess", "B04_PreProcess_UI_MapRender.ts"); const W = 800; const H = 600; const VIEW = { width: W, height: H, scale: 1, offsetX: 0, offsetY: 0, mapRect: { x: 0, y: 0, width: W, height: H }, }; /** 그린 선분을 모으는 가짜 캔버스. */ function fakeContext() { const calls = { moveTo: 0, lineTo: 0, stroke: 0, closePath: 0 }; const segments = []; let start = null; let pen = null; return { calls, segments, beginPath() { start = pen = null; }, moveTo(x, y) { calls.moveTo += 1; start = pen = [x, y]; }, lineTo(x, y) { calls.lineTo += 1; segments.push([pen, [x, y]]); pen = [x, y]; }, closePath() { calls.closePath += 1; segments.push([pen, start]); pen = start; }, stroke() { calls.stroke += 1; }, }; } /** 화면 px 점렬 → 한 피처 레이어(정규 좌표 = px / 화면 크기 · weights 없음 = 다 그림). */ function layerOf(points, closed = false) { const coords = []; points.forEach(([x, y]) => coords.push(x / W, y / H)); const xs = points.map((p) => p[0] / W); const ys = points.map((p) => p[1] / H); return { features: [ { kind: "line", parts: [{ coords: Float64Array.from(coords), closed, weights: null }], minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys), labelAnchorX: 0, labelAnchorY: 0, labelText: null, labelValue: null, }, ], }; } /** 선분이 사각형(여백 32)에 걸치나 — Liang–Barsky. */ function touches([a, b], m = 32) { let t0 = 0; let t1 = 1; const dx = b[0] - a[0]; const dy = b[1] - a[1]; const edges = [ [-dx, a[0] + m], [dx, W + m - a[0]], [-dy, a[1] + m], [dy, H + m - a[1]], ]; for (const [p, q] of edges) { if (p === 0) { if (q < 0) return false; continue; } const t = q / p; if (p < 0) t0 = Math.max(t0, t); else t1 = Math.min(t1, t); if (t0 > t1) return false; } return true; } const key = ([a, b]) => [a, b].map((p) => p.map((v) => v.toFixed(6)).join(",")).join(">"); function run(points, closed = false, pad = 0) { const context = fakeContext(); render.drawPreparedLayer(context, layerOf(points, closed), VIEW, "dot", undefined, pad); const drawn = new Set(context.segments.map(key)); const rev = new Set(context.segments.map(([a, b]) => key([b, a]))); const original = []; for (let i = 1; i < points.length; i += 1) original.push([points[i - 1], points[i]]); if (closed) original.push([points[points.length - 1], points[0]]); const visible = original.filter((segment) => touches(segment)); const missing = visible.filter((s) => !drawn.has(key(s)) && !rev.has(key(s))).length; return { calls: context.calls, drawn: context.segments.length, visible: visible.length, missing }; } // 1) 다 화면 안 — 예전과 같음(moveTo 1 · lineTo n-1 · stroke 1). const inside = run([ [100, 100], [200, 150], [300, 120], [400, 300], ]); // 2) 화면 밖 왼쪽으로 길게 돌다 돌아옴 — 밖 구간은 안 그림 · 보이는 구간 다 그림. const wander = [[400, 300]]; for (let i = 0; i < 200; i += 1) wander.push([-100 - (i % 7) * 10, 300 + i]); wander.push([400, 500]); const leftLoop = run(wander); // 3) 왼쪽 밖 → 아래 밖(모서리를 질러 감) — 쪽이 다르면 그대로 그림. const corner = run([ [-50, 300], [300, 700], ]); // 4) 닫힌 고리 다 안 = closePath · 일부 밖 = 닫는 구간 따로. const ringIn = run( [ [100, 100], [300, 100], [300, 300], [100, 300], ], true, ); const ringOut = run( [ [100, 100], [-200, 100], [-200, 300], [100, 300], ], true, ); // 5) 다 밖(한쪽) = 안 그림(stroke 0). const allOut = run([ [-200, 10], [-300, 200], [-250, 500], ]); // 6) 넓힘(pad) = 무한이면 하나도 안 거름(고른 등고선 덧그리기). const padded = run(wander, false, Infinity); // 7) 무작위 선 500 가닥 — 보이는 구간 빠짐 0. let seed = 7; const rand = () => ((seed = (seed * 16807) % 2147483647) / 2147483647) * 2 - 0.5; let randomMissing = 0; let randomDrawn = 0; let randomTotal = 0; for (let n = 0; n < 500; n += 1) { const points = []; let x = rand() * W * 2; let y = rand() * H * 2; for (let i = 0; i < 40; i += 1) { x += (rand() - 0.25) * 200; y += (rand() - 0.25) * 200; points.push([x, y]); } const result = run(points, n % 5 === 0); randomMissing += result.missing; randomDrawn += result.drawn; randomTotal += points.length - (n % 5 === 0 ? 0 : 1); } console.log( JSON.stringify({ inside, leftLoop, corner, ringIn, ringOut, allOut, padded, randomMissing, randomDrawn, randomTotal, }), );