How can I properly calculate a coordinate out of a transformed floor map?

Viewed 71

I have a context where I draw several rectangles upon. The context has then a transformation applied which turns it into a 3D illusion of a floormap.

Matrix Example

I am trying to create a formula to calculate which coordinate the cursor is hovering over, while not using Path2Ds. This is because I need to be able to calculate what coordinate it is even if the tile is not drawn, but rather is on the grid regardless.

The transformation matrix has a...

  • horizontal

    • scaling of 1.0
    • skewing of 0.5
    • moving of (columns * 32) (amount of columns: 6)
  • vertical

    • scaling of 0.5
    • skewing of -1.0
    • moving of 0

With the help of Real mouse position in canvas's answer, I believe I'm on the right path, however, when the mouse goes down and left, the column decreases, despite being on the same column. When going down-right, the row decreases too, despite being on the same row.

const rows = 10;
const columns = 6;

const $coordinate = $("#coordinate");

const $canvas = $("#canvas");

canvas.width = (rows * 32) + (columns * 32);
canvas.height = (rows * 16) + (columns * 16);

const context = $canvas[0].getContext("2d");

context.imageSmoothingEnabled = false;

context.save();
  
context.fillStyle = "white";

context.setTransform(1, 0.5, -1, 0.5, (columns * 32), 0);

// (a) horizontal scaling: 1
// (b) horizontal skewing: 0.5
// (c) vertical skewing: -1
// (d) vertical scaling: 0.5
// (e) horizontal moving: (columns * 32)
// (f) vertical moving: 0

const matrix = {
    vertical: {
    scaling: 1.0,
    skewing: 0.5,
    
    moving: (columns * 32)
  },
  
  horizontal: {
    scaling: 0.5,
    skewing: -1,
    
    moving: 0
  }
};

for(let row = 0; row < rows; row++) {
  for(let column = 0; column < columns; column++) {
    context.rect(row * 32, column * 32, 31.5, 31.5);
  }
}

context.fill();


$canvas.mousemove(function(e) {
    const position = {
    left: e.pageX - $canvas.offset().left,
    top: e.pageY - $canvas.offset().top
  };
  
  const innerPosition = {
    left: position.left * matrix.horizontal.scaling + position.top * matrix.vertical.skewing + matrix.horizontal.moving, 
    top: position.left * matrix.horizontal.skewing + position.top * matrix.vertical.scaling + matrix.vertical.moving
  };

    const coordinate = {
    row: Math.trunc(innerPosition.top / 32),
    column: Math.trunc(innerPosition.left / 32)
  };
  
  $coordinate.html(coordinate.row + "x" + coordinate.column);
});
#canvas {
  background: green;
}

#coordinate {
  position: absolute;

  font-family: Arial, sans-serif;

  font-size: 16px;

  left: 12px;
  top: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<canvas id="canvas"></canvas>

<div id="coordinate">0x0</div>

I am not using any frameworks (except for JQuery, however, unrelated to question). How can I calculate the exact coordinate?

2 Answers

Your call to setTransform(a, b, c, d, e, f) creates a 3x3 affine transformation matrix:

|  a  c  e  |
|  b  d  f  |
|  0  0  1  |

which given your current values of (1, 0.5, -1, 0.5, n, 0) has an inverse matrix of:

|  0.5     1  -n/2  | 
| -0.5     1  +n/2  |
|    0     0     1  |

Applying that transformation matrix to your mouse coordinates (which need to be expressed as the 1x3 matrix [x, y, 1] should provide the desired grid coordinates:

const rows = 10;
const columns = 6;

const $coordinate = $("#coordinate");

const $canvas = $("#canvas");

canvas.width = (rows * 32) + (columns * 32);
canvas.height = (rows * 16) + (columns * 16);

const context = $canvas[0].getContext("2d");

context.imageSmoothingEnabled = false;

context.save();
  
context.fillStyle = "white";

context.setTransform(1, 0.5, -1, 0.5, (columns * 32), 0);

// (a) horizontal scaling: 1
// (b) horizontal skewing: 0.5
// (c) vertical skewing: -1
// (d) vertical scaling: 0.5
// (e) horizontal moving: (columns * 32)
// (f) vertical moving: 0

for(let row = 0; row < rows; row++) {
  for(let column = 0; column < columns; column++) {
    context.rect(row * 32, column * 32, 31.5, 31.5);
  }
}

context.fill();


$canvas.mousemove(function(e) {
    const position = {
    left: e.pageX - $canvas.offset().left,
    top: e.pageY - $canvas.offset().top
  };
  
  const innerPosition = {
    left: position.left * 0.5 + position.top - (columns * 32) / 2,
    top: position.left * -0.5 + position.top + (columns * 32) / 2
  };

    const coordinate = {
    row: Math.floor(innerPosition.top / 32),
    column: Math.floor(innerPosition.left / 32)
  };
  
  $coordinate.html(coordinate.row + "x" + coordinate.column);
});
#canvas {
  background: green;
}

#coordinate {
  position: absolute;

  font-family: Arial, sans-serif;

  font-size: 16px;

  left: 12px;
  top: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<canvas id="canvas"></canvas>

<div id="coordinate">0x0</div>

Just an idea: Why not try making a floor map in your html (with divs), styling it with CSS to match the location of the canvas floor map, and then hiding it. That way you can detect hovering on the divs with jquery, and use the coordnitates of the div to find out which rectangle should be changed on the canvas? I'm on my phone right now, but in 5 minutes I can make a quick demo on jsfiddle

Related