// Some basic utils to work with matrices and vectors:
function matrixVectorMultiply(matrix, vector) {
let result = [];
for (let i = 0; i < matrix.length; i++) {
let sum = 0;
for (let j = 0; j < vector.length; j++) {
sum += matrix[i][j] * vector[j];
}
result.push(sum);
}
return result;
}
function invertMatrix(matrix) {
const n = matrix.length;
let identity = [];
for (let i = 0; i < n; i++) {
identity.push([]);
for (let j = 0; j < n; j++) {
identity[i].push(i === j ? 1 : 0);
}
}
// Apply Gauss-Jordan elimination:
for (let i = 0; i < n; i++) {
let pivot = matrix[i][i];
for (let j = 0; j < n; j++) {
matrix[i][j] /= pivot;
identity[i][j] /= pivot;
}
for (let k = 0; k < n; k++) {
if (k !== i) {
let factor = matrix[k][i];
for (let j = 0; j < n; j++) {
matrix[k][j] -= factor * matrix[i][j];
identity[k][j] -= factor * identity[i][j];
}
}
}
}
return identity;
}
// Define the grid data (colors of each cell):
const gridData = [
['#008800', '#00FF00', '#008800', '#00FF00', '#008800', '#00FF00'],
['#00FF00', '#008800', '#00FF00', '#008800', '#00FF00', '#008800'],
['#008800', '#00FF00', '#000088', '#00FF00', '#008800', '#00FF00'],
['#00FF00', '#008800', '#00FF00', '#008800', '#00FF00', '#000088'],
['#008800', '#00FF00', '#008800', '#00FF00', '#000088', '#0000FF'],
['#00FF00', '#008800', '#00FF00', '#000088', '#0000FF', '#000088'],
];
// This is just for the demo. In a real application, the grid data matrix would
// probably contain all the information on each cell objects (array items):
const gridColorToType = {
'#008800': 'Grass',
'#00FF00': 'Grass',
'#000088': 'Water',
'#0000FF': 'Water',
};
const selectedCellBolor = '#000000';
// Get the UI elements:
const positionLabelElement = document.getElementById('positionLabel');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
positionLabelElement.textContent = ' ';
// Adjust the canvas to the window:
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
// Grid sizing params:
const cellSizeLong = 100;
const cellHalfSizeLong = cellSizeLong / 2;
const cellSizeShort = cellSizeLong / 3;
const cellHalfSizeShort = cellSizeShort / 2;
// Keep track of the selected/highlighted cell:
let currentRow = 0;
let currentCol = 0;
// Drawing functions:
function drawCell(ctx, color, row, col) {
ctx.fillStyle = color;
// Calculate the position of the cell
const x = (col - row) * cellHalfSizeLong + width / 2;
const y = (col + row) * cellHalfSizeShort;
// Fill:
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + cellHalfSizeLong, y + cellHalfSizeShort);
ctx.lineTo(x, y + cellSizeShort);
ctx.lineTo(x - cellHalfSizeLong, y + cellHalfSizeShort);
ctx.closePath();
ctx.fill();
// Border:
ctx.strokeStyle = '#000000';
ctx.stroke();
}
function drawBoard() {
ctx.clearRect(0, 0, width, height);
const numRows = gridData.length;
const numCols = gridData[0].length;
// Draw all the cells in their respective color:
for (let row = 0; row < numRows; ++row) {
for (let col = 0; col < numCols; ++col) {
drawCell(ctx, gridData[row][col], row, col);
}
}
// And re-draw the selected one on top (you might want to do this differently):
drawCell(ctx, selectedCellBolor, currentRow, currentCol);
}
canvas.addEventListener('mousemove', () => {
const x_C = width / 2 - event.clientX;
const y_C = event.clientY;
// First column is the columns vector in the 2.5D grid.
// Second column is the rows vector in the 2.5 grid.
const M_BC = [
[cellHalfSizeLong, -cellHalfSizeLong],
[cellHalfSizeShort, cellHalfSizeShort],
];
// We need the inverse of that matrix to translate canonical basis
// coordinates to coordinates in the 2.5D space's base:
const M_CB = invertMatrix(M_BC);
const [x_B, y_B] = matrixVectorMultiply(M_CB, [x_C, y_C]);
const int_x_B = Math.floor(x_B);
const int_y_B = Math.floor(y_B);
currentRow = int_x_B;
currentCol = int_y_B;
const cellType = gridColorToType[gridData[currentRow]?.[currentCol]] || 'Void';
positionLabelElement.textContent = `(${
(x_C | 0).toFixed().padStart(4, ' ')
}, ${
(y_C | 0).toFixed().padStart(4, ' ')
}) => (${
x_B.toFixed(2).padStart(5, ' ')
}, ${
y_B.toFixed(2).padStart(5, ' ')
}) => (${
int_x_B.toFixed().padStart(2, ' ')
}, ${
int_y_B.toFixed().padStart(2, ' ')
}) => ${ cellType }`;
requestAnimationFrame(() => {
drawBoard();
});
});
drawBoard();
body {
background: #777;
}
#canvas {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
#positionLabel {
position: fixed;
bottom: 0;
left: 0;
background: rgba(255, 255, 255, .5);
padding: 8px;
border-radius: 0 4px 0 0;
font-family: monospace;
font-weight: bold;
white-space: pre;
backdrop-filter: blur(8px);
pointer-events: none;
}
<canvas id="canvas"></canvas>
<div id="positionLabel"> <div>