Search code examples
javascripthtmlmathcanvashexagonal-tiles

Drawing Hexagon on Canvas, testing mouseclick event vs Hexagon


Im drawing a hexagon-based grid on the canvas.

Each hexagon is an object that holds the 6 points in x/y coordinates. Each hexagon object also holds its X/Y columns/row index.

var canvas = document.getElementById("can");
    canvas.width = 200;
    canvas.height = 200;    
var ctx = canvas.getContext("2d");

var grid = []; // array that holds the Hex
var globalOffset = 30 // not important, just for smoother display atm

  function Point(x, y) {
    this.x = x;
    this.y = y;
  }

  function Hex(x, y, size) {
    this.size = 20;
    this.x = x;
    this.y = y;
    this.points = [];
    this.id = [];

    this.create = function(x, y) {
      var offSetX = (size / 2 * x) * -1
      var offSetY = 0;

      if (x % 2 == 1) {
        offSetY = Math.sqrt(3) / 2 * this.size;
      }

      var center = new Point(
        x * this.size * 2 + offSetX + globalOffset,
        y * Math.sqrt(3) / 2 * this.size * 2 + offSetY + globalOffset
      )

      this.midPoint = center;

      this.id[0] = x;
      this.id[1] = y;

      for (var i = 0; i < 6; i++) {
        var degree = 60 * i;
        var radian = Math.PI / 180 * degree;

        var point = new Point(
          center.x + size * Math.cos(radian),
          center.y + size * Math.sin(radian)
        )

        this.points.push(point);
      }
    }

    this.create(x, y);
  }
}



//Determine where was clicked
canvas.addEventListener("click", function(e) {

  var rect = canvas.getBoundingClientRect();

  var pos = {
    x: e.clientX - rect.left,
    y: e.clientY - rect.top
  }


  document.getElementById("pos").innerHTML = "click on: " + pos.x + " " + pos.y;
});


// Creating Hexagons, setting up their center point, pushing them into Grid.
function init() {
  for (var i = 0; i < 5; i++) {
    for (var j = 0; j < 4; j++) {
      var hex = new Hex(i, j, 20);
      grid.push(hex)
    }
  }

  //for each Hex in Grid, draw the Hex
  for (var hex in grid) {
    var item = grid[hex];
    ctx.beginPath();
    ctx.moveTo(item.points[0].x, item.points[0].y);

    for (var k = 1; k < item.points.length; k++) {
      ctx.lineTo(item.points[k].x, item.points[k].y);
    }

    ctx.closePath();
    ctx.stroke();

    var text = item.id;
    ctx.fillStyle = "black";
    ctx.fillText(text, item.midPoint.x - 7, item.midPoint.y - item.size / 2.2);

  }

When clicking on the canvas i want to determine if i clicked a hex or not, and if i did, which hex (by column/row). Its math problem.

How can i do this ?

fully working example here: http://codepen.io/anon/pen/RrMzKy?editors=1111


Solution

  • If you treat the hexagon centres as if they were the centres of circles, the clicked hexagon is the one whose centre is closest to the click. (It should be possible to optimise this without testing the distance to every possible cell).

    To account for the incomplete coverage, assume that there are more (invisible) hexagons in an additional ring surrounding the visible ones.

    If one of those is chosen, or if the distance is greater than the circle radius, then the click was not on a visible hexagon.

    Somewhat based on a refactoring of your own proposed code, and avoiding the two loops since the only gain is elimination of a single sqrt function:

    Grid.prototype.getHexAt = function(pos) {
    
        var closest = null;
        var min = Infinity;
    
        grid.hexes.forEach(function(hex) {
            var dx = hex.center.x - pos.x;
            var dy = hex.center.y - pos.y;
            var distance = Math.sqrt(v.x * v.x + v.y * v.y);
    
            if (distance < hex.size && distance < min) {
                min = distance;
                closest = hex;
            }
        });
    
        return closest;   // may return null
    }