3

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?

Chloe Dev
  • 271
  • 1
  • 8
  • 1
    Your call to `setTransform(a, b, c, d, e, f)` creates a _3x3 affine transformation matrix_. You need the _inverse_ of that matrix to be applied to the cursor position. See e.g. https://matrix.reshish.com/inverse.php – Alnitak Jul 20 '20 at 22:02

2 Answers2

3

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>
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Wonderful! Thank you for taking the time. Would you happen to know why it takes an extra tile to go from `0xY` to `-1xY` and vice-versa? Is both the first inverse and normal tile considered `0xY`? – Chloe Dev Jul 20 '20 at 22:19
  • 1
    That's because you're using `trunc` (which rounds towards zero) instead of `floor`. – Alnitak Jul 20 '20 at 22:20
  • 1
    That's fixed in my version now. – Alnitak Jul 20 '20 at 22:21
-1

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

Lebster
  • 289
  • 5
  • 12
  • That would not work because that element would then block the pointer events on the canvas. If I can't resolve the transformation reversion through the canvas, how am I expected to be able to resolve the same query on another element? Nothing changes, even if the transformation is set through CSS, I still don't get the inner position _prior_ to the matrix. EDIT: just noticed you meant 1 element per tile, **no**. That should _never_ be done in a scale like this. – Chloe Dev Jul 20 '20 at 21:50
  • What’s the issue with 1 element per tile? In theory it should work. As for “resolving the transformation reversion” it isn’t needed, since you can use jquery’s onmouse in and out events – Lebster Jul 20 '20 at 21:53
  • Just because it will work doesn't mean you should do it... I'm not gonna put 1,000 elements for when there's 1,000 tiles... not that it _would_ work either, because again, it would block the pointer events away from the main canvas. – Chloe Dev Jul 20 '20 at 21:54
  • You never said the number of tiles was subject to change – Lebster Jul 20 '20 at 21:56
  • Because I mentioned that Path2D's are out of the question, _because_ I did mention that I want to be able to calculate the coordinate _even_ if the cursor is _not_ on a tile. – Chloe Dev Jul 20 '20 at 21:57