polygon drawing (splatter balls)
by Ben Gillbanks
HTML
<div class="row">
<canvas id="canvas" width="32" height="32"></canvas>
</div>
<div class="row">
<label for="polygonInput">Load Polygon:</label><br />
<input type="text" id="polygonInput" placeholder="polygon array">
</div>
<div class="row">
<button id="loadPolygonButton">Load</button>
<button id="clearButton">Clear</button>
<button id="rotateButton">Rotate</button>
</div>
CSS
body {
padding: 10px;
font-family: arial, san-serif;
font-size: 16px;
}
button {
padding: 2px 12px;
}
#canvas {
border: 1px solid #000;
width: 320px;
height: 320px;
image-rendering: pixelated;
}
#polygonInput {
width: 320px;
}
div.row {
margin-bottom: 10px;
}
JavaScript
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let points = [];
const gridStep = 4; // draw grid every 4
const snapStep = 1; // snap points to 1
const scale = 10; // how many real pixels per logical unit
const logicalSize = 32; // your world is 32x32
const WORLD_SIZE = 32;
const ORIGIN = WORLD_SIZE / 2;
canvas.width = logicalSize * scale;
canvas.height = logicalSize * scale;
ctx.setTransform(scale, 0, 0, scale, 0, 0);
// Disable antialiasing
ctx.imageSmoothingEnabled = false;
const hitRadius = 4; // logical units (not pixels)
let dragIndex = -1;
const inputEl = document.getElementById('polygonInput');
function getSignedArea(vertices) {
let area = 0;
for (let i = 0; i < vertices.length; i++) {
const [x1, y1] = vertices[i];
const [x2, y2] = vertices[(i + 1) % vertices.length];
area += (x1 * y2) - (x2 * y1);
}
return area / 2;
}
function ensureCounterClockwise(vertices) {
if (vertices.length < 3) return vertices;
return getSignedArea(vertices) < 0
? [...vertices].reverse()
: vertices;
}
function clamp(v, min, max) {
return Math.max(min, Math.min(max, v));
}
function closestPointOnSegment(px, py, ax, ay, bx, by) {
const abx = bx - ax;
const aby = by - ay;
const apx = px - ax;
const apy = py - ay;
const abLen2 = abx * abx + aby * aby;
if (abLen2 === 0) {
return { x: ax, y: ay, t: 0, d2: (px - ax) ** 2 + (py - ay) ** 2 };
}
let t = (apx * abx + apy * aby) / abLen2;
t = clamp(t, 0, 1);
const cx = ax + t * abx;
const cy = ay + t * aby;
const dx = px - cx;
const dy = py - cy;
return { x: cx, y: cy, t, d2: dx * dx + dy * dy };
}
function findClosestEdgeIndex(x, y, maxDist) {
if (points.length < 2) return -1;
const maxD2 = maxDist * maxDist;
let bestEdgeStart = -1;
let bestD2 = maxD2;
for (let i = 0; i < points.length; i++) {
const a = points[i];
const b = points[(i + 1) % points.length]; //...