| 11 | } |
| 12 | |
| 13 | export function buildDensity(placed: PlacedPoint[], baseW: number, baseH: number): DensityCell[] { |
| 14 | const cols = Math.max(8, Math.round(baseW / 30)); |
| 15 | const rows = Math.max(6, Math.round(baseH / 30)); |
| 16 | const cw = baseW / cols; |
| 17 | const ch = baseH / rows; |
| 18 | const counts = new Float32Array(cols * rows); |
| 19 | for (const p of placed) { |
| 20 | const cx = Math.min(cols - 1, Math.max(0, Math.floor(p.x / cw))); |
| 21 | const cy = Math.min(rows - 1, Math.max(0, Math.floor(p.y / ch))); |
| 22 | counts[cy * cols + cx] += 1; |
| 23 | } |
| 24 | const blurred = new Float32Array(cols * rows); |
| 25 | for (let y = 0; y < rows; y++) { |
| 26 | for (let x = 0; x < cols; x++) { |
| 27 | let sum = 0; |
| 28 | let n = 0; |
| 29 | for (let dy = -1; dy <= 1; dy++) { |
| 30 | for (let dx = -1; dx <= 1; dx++) { |
| 31 | const xx = x + dx; |
| 32 | const yy = y + dy; |
| 33 | if (xx < 0 || yy < 0 || xx >= cols || yy >= rows) continue; |
| 34 | sum += counts[yy * cols + xx]; |
| 35 | n += 1; |
| 36 | } |
| 37 | } |
| 38 | blurred[y * cols + x] = sum / n; |
| 39 | } |
| 40 | } |
| 41 | let max = 0; |
| 42 | for (const v of blurred) if (v > max) max = v; |
| 43 | if (max <= 0) return []; |
| 44 | const cells: DensityCell[] = []; |
| 45 | for (let y = 0; y < rows; y++) { |
| 46 | for (let x = 0; x < cols; x++) { |
| 47 | const v = blurred[y * cols + x]; |
| 48 | if (v <= 0.01) continue; |
| 49 | cells.push({ |
| 50 | x: x * cw, |
| 51 | y: y * ch, |
| 52 | w: cw + 0.5, |
| 53 | h: ch + 0.5, |
| 54 | opacity: Math.min(0.34, (v / max) * 0.34), |
| 55 | }); |
| 56 | } |
| 57 | } |
| 58 | return cells; |
| 59 | } |