Skip to content

Instantly share code, notes, and snippets.

@jacekkopecky
Created September 7, 2022 15:05
Show Gist options
  • Save jacekkopecky/eaace7ff7a12bf3c9f30b5c5567731bf to your computer and use it in GitHub Desktop.
Save jacekkopecky/eaace7ff7a12bf3c9f30b5c5567731bf to your computer and use it in GitHub Desktop.
Hex grid, with code to pick the right hex based on normal coordinates (quick and dirty)
const cos30 = Math.sqrt(3)/2;
/**
* A grid of hexagons with flat faces top and bottom, and points left and right.
*/
export class HexGrid {
constructor(height) {
this.h = height;
this.w = height * cos30; // small width - x distance between centers
this.W = height / cos30; // big width - from left point to right point
}
toHexCoords(xCanvas, yCanvas) {
const x = xCanvas / this.w;
const y = yCanvas / this.h - x / 2;
return {x, y};
}
pickHex(xCanvas, yCanvas) {
const {x, y} = this.toHexCoords(xCanvas, yCanvas);
let retval = null;
let minDistSquare = Infinity;
const checkCenterDistance = (hex) => {
const hexCenter = this.toCanvasCoords(hex.x, hex.y);
const distSquare = (hexCenter.x - xCanvas)**2 + (hexCenter.y - yCanvas)**2;
if (distSquare < minDistSquare) {
minDistSquare = distSquare;
retval = hex;
}
}
checkCenterDistance({x: Math.floor(x), y: Math.floor(y)});
checkCenterDistance({x: Math.floor(x), y: Math.ceil(y)});
checkCenterDistance({x: Math.ceil(x), y: Math.floor(y)});
checkCenterDistance({x: Math.ceil(x), y: Math.ceil(y)});
return retval;
}
toCanvasCoords(xHex, yHex) {
return {
x: xHex * this.w,
y: (yHex+xHex/2) * this.h,
}
}
getHexOutlinePoints(xHex, yHex) {
const {x, y} = this.toCanvasCoords(xHex, yHex);
return [
{x: x - this.W/4, y: y - this.h/2},
{x: x + this.W/4, y: y - this.h/2},
{x: x + this.W/2, y: y },
{x: x + this.W/4, y: y + this.h/2},
{x: x - this.W/4, y: y + this.h/2},
{x: x - this.W/2, y: y },
];
}
}
<!DOCTYPE html>
<meta charset='utf-8'>
<title>hexes</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<script src='main.js' type="module"></script>
<canvas id="c" width="1000" height="1000">
import { HexGrid } from './hex-grid.js';
window.addEventListener('load', init);
const grid = new HexGrid(160);
function *range(start, end) {
if (end > start) {
for (let i=start; i<=end; i+=1) {
yield i;
}
} else {
for (let i=start; i>=end; i-=1) {
yield i;
}
}
}
let highlightedHexes = [];
function init() {
const canvas = document.querySelector("#c");
/** @type {CanvasRenderingContext2D} */
const c = canvas.getContext("2d");
c.fillStyle = "transparent";
const size = Math.ceil(canvas.width / grid.w);
for (const x of range(0, size)) {
const xoff = Math.round(-x/2);
for (const y of range(xoff, size + xoff)) {
drawHex(c, x, y);
}
}
highlightedHexes = [{x: Math.round(size/2), y: 0}];
const imageData = c.getImageData(0, 0, canvas.width, canvas.height);
draw();
canvas.addEventListener("mousemove", handleMouse);
function draw() {
c.clearRect(0, 0, c.canvas.width, c.canvas.height);
c.putImageData(imageData, 0, 0);
c.fillStyle = "yellow";
for (const hex of highlightedHexes) {
drawHex(c, hex.x, hex.y);
}
requestAnimationFrame(draw);
}
}
function handleMouse(e) {
const canvas = e.currentTarget;
const { left, top } = getClickCoordinates(e, canvas);
const {x, y} = grid.toHexCoords(left, top);
highlightedHexes = [ grid.pickHex(left, top) ];
}
/**
* @param {CanvasRenderingContext2D} c
*/
function drawHex(c, xHex, yHex, fill=false) {
const points = grid.getHexOutlinePoints(xHex, yHex);
drawPoly(c, points, fill);
}
/**
* @param {CanvasRenderingContext2D} c
*/
function drawPoly(c, points, fill=false) {
c.beginPath();
c.moveTo(points.at(-1).x, points.at(-1).y);
for (const p of points) {
c.lineTo(p.x, p.y);
}
c.fill();
c.stroke();
}
// this function computes the coordinates of a given mouse event in the element
// it returns an object like {left: 100, top: 312}
function getClickCoordinates(event, element) {
const rect = element.getBoundingClientRect();
return {
left: event.clientX - rect.left,
top: event.clientY - rect.top,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment