Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import { Graphics, Container, Text } from 'pixi.js'; import { EdgeInfo, CellInfo, Point, ColorValue } from '../../types'; import { colorToHex } from '../utils/color-utils'; import { pointsClose } from '../utils/geometry'; import { filterInBounds, Grid } from '../grid'; export class GridRenderer { render( container: Container, edgeContainer: Container, width: number, height: number, grid: Grid, cellStates: number[][], palette: Record<number, ColorValue>, edgeColor: ColorValue, edgeWidth: number, visualizeEdgeDelta: boolean, edgePalette: Record<number, ColorValue>, showCoordinates: boolean = false ) { container.removeChildren(); edgeContainer.removeChildren(); const drawnEdges = new Set<string>(); for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { const state = cellStates[row]?.[col] ?? 0; const fillColor = colorToHex(palette[state] || '#000000'); const poly = grid.getCellPolygon({ col, row }); const cellShape = this.createPolygonShape(poly); cellShape.fill(fillColor); container.addChild(cellShape); if (showCoordinates) { const center = this.getPolygonCentroid(poly); // Scale estimation for text size: distance between first two points of polygon / 4 roughly const scaleEst = Math.sqrt(Math.pow(poly[0].x - poly[1].x, 2) + Math.pow(poly[0].y - poly[1].y, 2)); this.drawCoordinates(container, `${col},${row}`, center.x, center.y, scaleEst / 6); } if (visualizeEdgeDelta) { const edges = grid.getCellEdges({ col, row }); const neighbors = filterInBounds( grid.getNeighbors({ col, row }), width, height ); // We need to match edges to neighbors. // Since getCellEdges and getNeighbors order is consistent in our implementations (usually CCW or CW), // we might try to assume index matching, but it's brittle. // Instead, let's find the neighbor that shares the edge geometrically. for (const edge of edges) { // Unique key for edge to avoid drawing twice if transparent? // But we are drawing opaque lines usually. // To avoid double drawing, we can order points. const p1 = edge.points[0]; const p2 = edge.points[1]; const key = (p1.x < p2.x || (p1.x === p2.x && p1.y < p2.y)) ? `${p1.x.toFixed(2)},${p1.y.toFixed(2)}-${p2.x.toFixed(2)},${p2.y.toFixed(2)}` : `${p2.x.toFixed(2)},${p2.y.toFixed(2)}-${p1.x.toFixed(2)},${p1.y.toFixed(2)}`; if (drawnEdges.has(key)) continue; drawnEdges.add(key); let delta = 0; // Find neighbor sharing this edge for (const n of neighbors) { const nPoly = grid.getCellPolygon(n); // Check if nPoly has edge matching p1, p2 (order reversed or same) if (this.hasEdge(nPoly, p1, p2)) { const nState = cellStates[n.row][n.col]; delta = Math.abs(state - nState); break; } } // If no neighbor found, it's a boundary edge. // Delta remains 0 (or we could choose a specific boundary color). // Using edgePalette[delta]. const color = edgePalette[delta] || edgeColor; // Fallback const graphics = new Graphics(); graphics.moveTo(p1.x, p1.y).lineTo(p2.x, p2.y).stroke({ color: colorToHex(color), width: edgeWidth }); edgeContainer.addChild(graphics); } } else { // Original behavior: draw polygon outline const edgeShape = this.createPolygonShape(poly); edgeShape.stroke({ color: colorToHex(edgeColor), width: edgeWidth }); edgeContainer.addChild(edgeShape); } } } } private hasEdge(poly: Point[], p1: Point, p2: Point): boolean { const epsilon = 0.1; for (let i = 0; i < poly.length; i++) { const v1 = poly[i]; const v2 = poly[(i + 1) % poly.length]; // Check if segment (v1, v2) is same as (p1, p2) or (p2, p1) if ((pointsClose(v1, p1, epsilon) && pointsClose(v2, p2, epsilon)) || (pointsClose(v1, p2, epsilon) && pointsClose(v2, p1, epsilon))) { return true; } } return false; } private createPolygonShape(points: Point[]): Graphics { const graphics = new Graphics(); if (points.length < 3) return graphics; graphics.moveTo(points[0].x, points[0].y); for (let i = 1; i < points.length; i++) { graphics.lineTo(points[i].x, points[i].y); } graphics.closePath(); return graphics; } private drawCoordinates(container: Container, text: string, x: number, y: number, fontSize: number) { const coordText = new Text({ text, style: { fontSize: Math.max(8, fontSize), fill: 0xffffff, stroke: { color: 0x000000, width: 2 }, align: 'center' } }); coordText.anchor.set(0.5); coordText.position.set(x, y); container.addChild(coordText); } private getPolygonCentroid(points: Point[]): Point { let x = 0; let y = 0; for (const point of points) { x += point.x; y += point.y; } const count = points.length || 1; return { x: x / count, y: y / count }; } // unused? // getEdgesAtVertex(vertex: Point, width: number, height: number, grid: Grid): EdgeInfo[] { // return grid.getEdgesAtVertex(vertex, width, height); // } drawEdge(edgeInfo: EdgeInfo, color: ColorValue): Graphics { const graphics = new Graphics(); graphics.moveTo(edgeInfo.points[0].x, edgeInfo.points[0].y).lineTo(edgeInfo.points[1].x, edgeInfo.points[1].y).stroke({ color: colorToHex(color), width: 3 }); return graphics; } drawVertex(vertex: Point, color: ColorValue): Graphics { const graphics = new Graphics(); graphics.circle(vertex.x, vertex.y, 5).fill(colorToHex(color)); return graphics; } drawCellHighlight(cellInfo: CellInfo, grid: Grid, color: ColorValue): Graphics { const graphics = new Graphics(); const poly = grid.getCellPolygon({ col: cellInfo.col, row: cellInfo.row }); const shape = this.createPolygonShape(poly); shape.stroke({ color: colorToHex(color), width: 3, alignment: 0 }); graphics.addChild(shape); return graphics; } } |