Mouse coordinates to grid coordinates within an isometric map
I wrote an algorithm to transform mouse screen coordinates within an isometric map to corresponding coordinates of an underlying 2D-square grid. The algorithm works and I tested it successfully. I wanted to post it here so interested people could maybe check the algorithm and see if I can make any optimizations beyond of what I did already. My goal is to make the algorithm as compact as possible. There maybe are some smart mathematical solutions I didn't think of to save some lines of code or make it faster.
The input data for the rendering of the map is a simple 2D-array like:
0,0,0,0
0,0,0,0
0,0,0,0
...
Here is a screenshot of what the map looks like when rendered. It is not really a map right now but more of an outlining of the tiles I will render. The red coordinates are x and y values of the underlying source grid.
It is rendered with a zig-zag approach with an origin outside of the screen (top left). It is rendered row by row from left to right whereby every uneven row is inset by half a tiles width. This is a common approach to avoid having to fill the source 2D-grid with "zombie-data" if one would render it in a diamond approach.
What the algorithm does is:
- the screen is logically divided into square rectangles with a width of my tile width and height of my tile height, i.e. 128px by 64px
- the mouse coordinates are scaled by tile width and tile height and then floored. This determines in which screen rectangle the user clicked
- then the mouse coordinates are compared against the center coordinates of this rectangle
- regarding this result, a right angled triangle is calculated to check if the user clicked within a certain area of the rectangle. I am using the cosine of this triangle to compare against my initial rotation angle of 26,565 (atan of 0.5). This is the angle in which the sides of the rendered rhombi are rotated to achieve a 2:1 ratio of width and height (classic isometric projection for video games).
- regarding on where the user clicked
- the y value is determined from a lower and upper boundary
- the x value is determined based on y being even or uneven
To run this code, you will need SDL2-2.0.9 and boost 1.67.0. to run. I am using MS Visual Studio Community 2017 as an IDE.
#include <iostream>
#include <vector>
#include <SDL.h>
#include <SDL_Image.h>
#include <boost/cast.hpp>
struct Point
{
std::size_t x;
std::size_t y;
};
Point mouseToGrid(int pMouseXPos, int pMouseYPos, std::size_t pTileWidth, std::size_t pTileHeight, const double PI, const int SCREEN_WIDTH, const int SCREEN_HEIGHT)
{
double mouseXPos = boost::numeric_cast<double>(pMouseXPos);
double mouseYPos = boost::numeric_cast<double>(pMouseYPos);
double tileWidth = boost::numeric_cast<double>(pTileWidth);
double tileHeight = boost::numeric_cast<double>(pTileHeight);
double tileWidthHalf = tileWidth / 2;
double tileHeightHalf = tileHeight / 2;
double mouseTileYPos = mouseYPos / tileHeight;
mouseTileYPos = std::floor(mouseTileYPos);
int screenRectCenterX = ((std::floor((mouseXPos / tileWidth))) * tileWidth) + tileWidthHalf;
int screenRectCenterY = (mouseTileYPos * tileHeight) + tileHeightHalf;
//determine lower and upper boundary for y
int minY = boost::numeric_cast<int>(2 * mouseTileYPos);
int maxY = boost::numeric_cast<int>((2 * mouseTileYPos) + 1);
if (mouseYPos >= screenRectCenterY)
{
minY = maxY;
maxY++;
}
//calc triangle sides in pixels
char mouseRectangleSector[2]{};
double opposite;
double adjacent;
if (mouseYPos >= screenRectCenterY)
{
mouseRectangleSector[0] = 'S';
opposite = mouseYPos - screenRectCenterY;
}
else
{
mouseRectangleSector[0] = 'N';
opposite = screenRectCenterY - mouseYPos;
}
if (mouseXPos >= screenRectCenterX)
{
mouseRectangleSector[1] = 'E';
adjacent = (screenRectCenterX + tileWidthHalf) - mouseXPos;
}
else{
mouseRectangleSector[1] = 'W';
adjacent = tileWidthHalf - (screenRectCenterX - mouseXPos);
}
double hypothenuse = std::sqrt(std::pow(opposite, 2) + std::pow(adjacent, 2));
//calculate cos and corresponding angle in rad and deg
double cos = adjacent / hypothenuse;
double angleRad = std::acos(cos);
double angleDeg = angleRad * 180 / PI;
//calculate initial rotation angle in rad and deg
double controlAtan = 0.5;
double controlAngleRad = std::atan(controlAtan);
double controlAngleDeg = controlAngleRad * 180 / PI;
//determine final position for y
if (mouseRectangleSector[0] == 'S')
{
if (angleRad > controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
else
{
if (angleRad < controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
//determine position for x
double mouseTileXPos;
if ((boost::numeric_cast<int>(mouseTileYPos)) % 2 == 0)
{
mouseTileXPos = (mouseXPos + tileWidthHalf) / tileWidth;
}
else
{
mouseTileXPos = mouseXPos / tileWidth;
}
mouseTileXPos = std::floor(mouseTileXPos);
Point gridXY{(std::size_t)mouseTileXPos, (std::size_t)mouseTileYPos};
return gridXY;
}
int main(int argc, char *args)
{
const double PI = 3.1415926535897932384626433832795;
const int SCREEN_WIDTH = 1600;
const int SCREEN_HEIGHT = 900;
//init SDL Components
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
return 1;
}
SDL_Window *sdlWindow = SDL_CreateWindow("A New Era", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer *sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (sdlWindow == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
return 1;
}
if (sdlRenderer == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
if (!(IMG_Init(IMG_INIT_PNG)))
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(sdlRenderer);
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
//tile dimensions
std::size_t tileWidth = 128;
std::size_t tileHeight = 64;
std::size_t tileWidthHalf = tileWidth / 2;
std::size_t tileHeightHalf = tileHeight / 2;
int originX = 0;
int originY = 0;
int xScreenOffset;
int yScreenOffset = 0 - tileHeightHalf;
//add pseudo data points to tile vector
std::vector<Point> mapTiles{};
for (std::size_t y = 0; y < 10; y++)
{
for (std::size_t x = 0; x < 10; x++)
{
Point point{x, y};
mapTiles.push_back(point);
}
}
for (std::vector<Point>::iterator itPoint = mapTiles.begin(); itPoint < mapTiles.end(); itPoint++)
{
if (itPoint->y % 2 == 0)
{
xScreenOffset = 0;
}
else
{
xScreenOffset = tileWidthHalf;
}
//draw 2:1 rombus
std::size_t tileOriginX = itPoint->x * tileWidth;
std::size_t tileOriginY = itPoint->y * tileHeightHalf;
std::size_t x1 = tileOriginX + tileWidthHalf;
std::size_t y1 = tileOriginY + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, tileOriginX + originX + xScreenOffset, tileOriginY + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
std::size_t x = x1;
std::size_t y = y1;
x1 = x - tileWidthHalf;
y1 = y + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = x - tileWidthHalf;
y1 = y - tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = tileOriginX;
y1 = tileOriginY;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
}
SDL_RenderPresent(sdlRenderer);
//game loop
//control variables
SDL_Event event;
bool quit = false;
//originX = originX - tileWidthHalf;
while (!quit)
{
while (SDL_PollEvent(&event) != 0)
{
if (event.type == SDL_QUIT)
{
quit = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
if (event.button.button == SDL_BUTTON_LEFT)
{
int mouseXPos;
int mouseYPos;
SDL_GetMouseState(&mouseXPos, &mouseYPos);
Point gridCoordinates = mouseToGrid(mouseXPos, mouseYPos, tileWidth, tileHeight, PI, SCREEN_WIDTH, SCREEN_HEIGHT);
std::cout << "x,y : " << gridCoordinates.x << "," << gridCoordinates.y << std::endl;
}
}
}
}
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 0;
}
As I said, the algorithm works so I don't need help getting it to run. I am looking for help or advice on how to make it more compact (reduce lines of code). Maybe I can get rid of a couple of if statements by doing some more maths. This is really the first time after my graduation I used this kind of maths in programming so there might be some tweaks that can be done.
c++ game coordinate-system
New contributor
add a comment |
I wrote an algorithm to transform mouse screen coordinates within an isometric map to corresponding coordinates of an underlying 2D-square grid. The algorithm works and I tested it successfully. I wanted to post it here so interested people could maybe check the algorithm and see if I can make any optimizations beyond of what I did already. My goal is to make the algorithm as compact as possible. There maybe are some smart mathematical solutions I didn't think of to save some lines of code or make it faster.
The input data for the rendering of the map is a simple 2D-array like:
0,0,0,0
0,0,0,0
0,0,0,0
...
Here is a screenshot of what the map looks like when rendered. It is not really a map right now but more of an outlining of the tiles I will render. The red coordinates are x and y values of the underlying source grid.
It is rendered with a zig-zag approach with an origin outside of the screen (top left). It is rendered row by row from left to right whereby every uneven row is inset by half a tiles width. This is a common approach to avoid having to fill the source 2D-grid with "zombie-data" if one would render it in a diamond approach.
What the algorithm does is:
- the screen is logically divided into square rectangles with a width of my tile width and height of my tile height, i.e. 128px by 64px
- the mouse coordinates are scaled by tile width and tile height and then floored. This determines in which screen rectangle the user clicked
- then the mouse coordinates are compared against the center coordinates of this rectangle
- regarding this result, a right angled triangle is calculated to check if the user clicked within a certain area of the rectangle. I am using the cosine of this triangle to compare against my initial rotation angle of 26,565 (atan of 0.5). This is the angle in which the sides of the rendered rhombi are rotated to achieve a 2:1 ratio of width and height (classic isometric projection for video games).
- regarding on where the user clicked
- the y value is determined from a lower and upper boundary
- the x value is determined based on y being even or uneven
To run this code, you will need SDL2-2.0.9 and boost 1.67.0. to run. I am using MS Visual Studio Community 2017 as an IDE.
#include <iostream>
#include <vector>
#include <SDL.h>
#include <SDL_Image.h>
#include <boost/cast.hpp>
struct Point
{
std::size_t x;
std::size_t y;
};
Point mouseToGrid(int pMouseXPos, int pMouseYPos, std::size_t pTileWidth, std::size_t pTileHeight, const double PI, const int SCREEN_WIDTH, const int SCREEN_HEIGHT)
{
double mouseXPos = boost::numeric_cast<double>(pMouseXPos);
double mouseYPos = boost::numeric_cast<double>(pMouseYPos);
double tileWidth = boost::numeric_cast<double>(pTileWidth);
double tileHeight = boost::numeric_cast<double>(pTileHeight);
double tileWidthHalf = tileWidth / 2;
double tileHeightHalf = tileHeight / 2;
double mouseTileYPos = mouseYPos / tileHeight;
mouseTileYPos = std::floor(mouseTileYPos);
int screenRectCenterX = ((std::floor((mouseXPos / tileWidth))) * tileWidth) + tileWidthHalf;
int screenRectCenterY = (mouseTileYPos * tileHeight) + tileHeightHalf;
//determine lower and upper boundary for y
int minY = boost::numeric_cast<int>(2 * mouseTileYPos);
int maxY = boost::numeric_cast<int>((2 * mouseTileYPos) + 1);
if (mouseYPos >= screenRectCenterY)
{
minY = maxY;
maxY++;
}
//calc triangle sides in pixels
char mouseRectangleSector[2]{};
double opposite;
double adjacent;
if (mouseYPos >= screenRectCenterY)
{
mouseRectangleSector[0] = 'S';
opposite = mouseYPos - screenRectCenterY;
}
else
{
mouseRectangleSector[0] = 'N';
opposite = screenRectCenterY - mouseYPos;
}
if (mouseXPos >= screenRectCenterX)
{
mouseRectangleSector[1] = 'E';
adjacent = (screenRectCenterX + tileWidthHalf) - mouseXPos;
}
else{
mouseRectangleSector[1] = 'W';
adjacent = tileWidthHalf - (screenRectCenterX - mouseXPos);
}
double hypothenuse = std::sqrt(std::pow(opposite, 2) + std::pow(adjacent, 2));
//calculate cos and corresponding angle in rad and deg
double cos = adjacent / hypothenuse;
double angleRad = std::acos(cos);
double angleDeg = angleRad * 180 / PI;
//calculate initial rotation angle in rad and deg
double controlAtan = 0.5;
double controlAngleRad = std::atan(controlAtan);
double controlAngleDeg = controlAngleRad * 180 / PI;
//determine final position for y
if (mouseRectangleSector[0] == 'S')
{
if (angleRad > controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
else
{
if (angleRad < controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
//determine position for x
double mouseTileXPos;
if ((boost::numeric_cast<int>(mouseTileYPos)) % 2 == 0)
{
mouseTileXPos = (mouseXPos + tileWidthHalf) / tileWidth;
}
else
{
mouseTileXPos = mouseXPos / tileWidth;
}
mouseTileXPos = std::floor(mouseTileXPos);
Point gridXY{(std::size_t)mouseTileXPos, (std::size_t)mouseTileYPos};
return gridXY;
}
int main(int argc, char *args)
{
const double PI = 3.1415926535897932384626433832795;
const int SCREEN_WIDTH = 1600;
const int SCREEN_HEIGHT = 900;
//init SDL Components
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
return 1;
}
SDL_Window *sdlWindow = SDL_CreateWindow("A New Era", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer *sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (sdlWindow == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
return 1;
}
if (sdlRenderer == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
if (!(IMG_Init(IMG_INIT_PNG)))
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(sdlRenderer);
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
//tile dimensions
std::size_t tileWidth = 128;
std::size_t tileHeight = 64;
std::size_t tileWidthHalf = tileWidth / 2;
std::size_t tileHeightHalf = tileHeight / 2;
int originX = 0;
int originY = 0;
int xScreenOffset;
int yScreenOffset = 0 - tileHeightHalf;
//add pseudo data points to tile vector
std::vector<Point> mapTiles{};
for (std::size_t y = 0; y < 10; y++)
{
for (std::size_t x = 0; x < 10; x++)
{
Point point{x, y};
mapTiles.push_back(point);
}
}
for (std::vector<Point>::iterator itPoint = mapTiles.begin(); itPoint < mapTiles.end(); itPoint++)
{
if (itPoint->y % 2 == 0)
{
xScreenOffset = 0;
}
else
{
xScreenOffset = tileWidthHalf;
}
//draw 2:1 rombus
std::size_t tileOriginX = itPoint->x * tileWidth;
std::size_t tileOriginY = itPoint->y * tileHeightHalf;
std::size_t x1 = tileOriginX + tileWidthHalf;
std::size_t y1 = tileOriginY + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, tileOriginX + originX + xScreenOffset, tileOriginY + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
std::size_t x = x1;
std::size_t y = y1;
x1 = x - tileWidthHalf;
y1 = y + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = x - tileWidthHalf;
y1 = y - tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = tileOriginX;
y1 = tileOriginY;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
}
SDL_RenderPresent(sdlRenderer);
//game loop
//control variables
SDL_Event event;
bool quit = false;
//originX = originX - tileWidthHalf;
while (!quit)
{
while (SDL_PollEvent(&event) != 0)
{
if (event.type == SDL_QUIT)
{
quit = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
if (event.button.button == SDL_BUTTON_LEFT)
{
int mouseXPos;
int mouseYPos;
SDL_GetMouseState(&mouseXPos, &mouseYPos);
Point gridCoordinates = mouseToGrid(mouseXPos, mouseYPos, tileWidth, tileHeight, PI, SCREEN_WIDTH, SCREEN_HEIGHT);
std::cout << "x,y : " << gridCoordinates.x << "," << gridCoordinates.y << std::endl;
}
}
}
}
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 0;
}
As I said, the algorithm works so I don't need help getting it to run. I am looking for help or advice on how to make it more compact (reduce lines of code). Maybe I can get rid of a couple of if statements by doing some more maths. This is really the first time after my graduation I used this kind of maths in programming so there might be some tweaks that can be done.
c++ game coordinate-system
New contributor
3
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
the code in my question is now a fully functional minimal example
– Tremah
2 days ago
add a comment |
I wrote an algorithm to transform mouse screen coordinates within an isometric map to corresponding coordinates of an underlying 2D-square grid. The algorithm works and I tested it successfully. I wanted to post it here so interested people could maybe check the algorithm and see if I can make any optimizations beyond of what I did already. My goal is to make the algorithm as compact as possible. There maybe are some smart mathematical solutions I didn't think of to save some lines of code or make it faster.
The input data for the rendering of the map is a simple 2D-array like:
0,0,0,0
0,0,0,0
0,0,0,0
...
Here is a screenshot of what the map looks like when rendered. It is not really a map right now but more of an outlining of the tiles I will render. The red coordinates are x and y values of the underlying source grid.
It is rendered with a zig-zag approach with an origin outside of the screen (top left). It is rendered row by row from left to right whereby every uneven row is inset by half a tiles width. This is a common approach to avoid having to fill the source 2D-grid with "zombie-data" if one would render it in a diamond approach.
What the algorithm does is:
- the screen is logically divided into square rectangles with a width of my tile width and height of my tile height, i.e. 128px by 64px
- the mouse coordinates are scaled by tile width and tile height and then floored. This determines in which screen rectangle the user clicked
- then the mouse coordinates are compared against the center coordinates of this rectangle
- regarding this result, a right angled triangle is calculated to check if the user clicked within a certain area of the rectangle. I am using the cosine of this triangle to compare against my initial rotation angle of 26,565 (atan of 0.5). This is the angle in which the sides of the rendered rhombi are rotated to achieve a 2:1 ratio of width and height (classic isometric projection for video games).
- regarding on where the user clicked
- the y value is determined from a lower and upper boundary
- the x value is determined based on y being even or uneven
To run this code, you will need SDL2-2.0.9 and boost 1.67.0. to run. I am using MS Visual Studio Community 2017 as an IDE.
#include <iostream>
#include <vector>
#include <SDL.h>
#include <SDL_Image.h>
#include <boost/cast.hpp>
struct Point
{
std::size_t x;
std::size_t y;
};
Point mouseToGrid(int pMouseXPos, int pMouseYPos, std::size_t pTileWidth, std::size_t pTileHeight, const double PI, const int SCREEN_WIDTH, const int SCREEN_HEIGHT)
{
double mouseXPos = boost::numeric_cast<double>(pMouseXPos);
double mouseYPos = boost::numeric_cast<double>(pMouseYPos);
double tileWidth = boost::numeric_cast<double>(pTileWidth);
double tileHeight = boost::numeric_cast<double>(pTileHeight);
double tileWidthHalf = tileWidth / 2;
double tileHeightHalf = tileHeight / 2;
double mouseTileYPos = mouseYPos / tileHeight;
mouseTileYPos = std::floor(mouseTileYPos);
int screenRectCenterX = ((std::floor((mouseXPos / tileWidth))) * tileWidth) + tileWidthHalf;
int screenRectCenterY = (mouseTileYPos * tileHeight) + tileHeightHalf;
//determine lower and upper boundary for y
int minY = boost::numeric_cast<int>(2 * mouseTileYPos);
int maxY = boost::numeric_cast<int>((2 * mouseTileYPos) + 1);
if (mouseYPos >= screenRectCenterY)
{
minY = maxY;
maxY++;
}
//calc triangle sides in pixels
char mouseRectangleSector[2]{};
double opposite;
double adjacent;
if (mouseYPos >= screenRectCenterY)
{
mouseRectangleSector[0] = 'S';
opposite = mouseYPos - screenRectCenterY;
}
else
{
mouseRectangleSector[0] = 'N';
opposite = screenRectCenterY - mouseYPos;
}
if (mouseXPos >= screenRectCenterX)
{
mouseRectangleSector[1] = 'E';
adjacent = (screenRectCenterX + tileWidthHalf) - mouseXPos;
}
else{
mouseRectangleSector[1] = 'W';
adjacent = tileWidthHalf - (screenRectCenterX - mouseXPos);
}
double hypothenuse = std::sqrt(std::pow(opposite, 2) + std::pow(adjacent, 2));
//calculate cos and corresponding angle in rad and deg
double cos = adjacent / hypothenuse;
double angleRad = std::acos(cos);
double angleDeg = angleRad * 180 / PI;
//calculate initial rotation angle in rad and deg
double controlAtan = 0.5;
double controlAngleRad = std::atan(controlAtan);
double controlAngleDeg = controlAngleRad * 180 / PI;
//determine final position for y
if (mouseRectangleSector[0] == 'S')
{
if (angleRad > controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
else
{
if (angleRad < controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
//determine position for x
double mouseTileXPos;
if ((boost::numeric_cast<int>(mouseTileYPos)) % 2 == 0)
{
mouseTileXPos = (mouseXPos + tileWidthHalf) / tileWidth;
}
else
{
mouseTileXPos = mouseXPos / tileWidth;
}
mouseTileXPos = std::floor(mouseTileXPos);
Point gridXY{(std::size_t)mouseTileXPos, (std::size_t)mouseTileYPos};
return gridXY;
}
int main(int argc, char *args)
{
const double PI = 3.1415926535897932384626433832795;
const int SCREEN_WIDTH = 1600;
const int SCREEN_HEIGHT = 900;
//init SDL Components
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
return 1;
}
SDL_Window *sdlWindow = SDL_CreateWindow("A New Era", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer *sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (sdlWindow == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
return 1;
}
if (sdlRenderer == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
if (!(IMG_Init(IMG_INIT_PNG)))
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(sdlRenderer);
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
//tile dimensions
std::size_t tileWidth = 128;
std::size_t tileHeight = 64;
std::size_t tileWidthHalf = tileWidth / 2;
std::size_t tileHeightHalf = tileHeight / 2;
int originX = 0;
int originY = 0;
int xScreenOffset;
int yScreenOffset = 0 - tileHeightHalf;
//add pseudo data points to tile vector
std::vector<Point> mapTiles{};
for (std::size_t y = 0; y < 10; y++)
{
for (std::size_t x = 0; x < 10; x++)
{
Point point{x, y};
mapTiles.push_back(point);
}
}
for (std::vector<Point>::iterator itPoint = mapTiles.begin(); itPoint < mapTiles.end(); itPoint++)
{
if (itPoint->y % 2 == 0)
{
xScreenOffset = 0;
}
else
{
xScreenOffset = tileWidthHalf;
}
//draw 2:1 rombus
std::size_t tileOriginX = itPoint->x * tileWidth;
std::size_t tileOriginY = itPoint->y * tileHeightHalf;
std::size_t x1 = tileOriginX + tileWidthHalf;
std::size_t y1 = tileOriginY + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, tileOriginX + originX + xScreenOffset, tileOriginY + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
std::size_t x = x1;
std::size_t y = y1;
x1 = x - tileWidthHalf;
y1 = y + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = x - tileWidthHalf;
y1 = y - tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = tileOriginX;
y1 = tileOriginY;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
}
SDL_RenderPresent(sdlRenderer);
//game loop
//control variables
SDL_Event event;
bool quit = false;
//originX = originX - tileWidthHalf;
while (!quit)
{
while (SDL_PollEvent(&event) != 0)
{
if (event.type == SDL_QUIT)
{
quit = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
if (event.button.button == SDL_BUTTON_LEFT)
{
int mouseXPos;
int mouseYPos;
SDL_GetMouseState(&mouseXPos, &mouseYPos);
Point gridCoordinates = mouseToGrid(mouseXPos, mouseYPos, tileWidth, tileHeight, PI, SCREEN_WIDTH, SCREEN_HEIGHT);
std::cout << "x,y : " << gridCoordinates.x << "," << gridCoordinates.y << std::endl;
}
}
}
}
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 0;
}
As I said, the algorithm works so I don't need help getting it to run. I am looking for help or advice on how to make it more compact (reduce lines of code). Maybe I can get rid of a couple of if statements by doing some more maths. This is really the first time after my graduation I used this kind of maths in programming so there might be some tweaks that can be done.
c++ game coordinate-system
New contributor
I wrote an algorithm to transform mouse screen coordinates within an isometric map to corresponding coordinates of an underlying 2D-square grid. The algorithm works and I tested it successfully. I wanted to post it here so interested people could maybe check the algorithm and see if I can make any optimizations beyond of what I did already. My goal is to make the algorithm as compact as possible. There maybe are some smart mathematical solutions I didn't think of to save some lines of code or make it faster.
The input data for the rendering of the map is a simple 2D-array like:
0,0,0,0
0,0,0,0
0,0,0,0
...
Here is a screenshot of what the map looks like when rendered. It is not really a map right now but more of an outlining of the tiles I will render. The red coordinates are x and y values of the underlying source grid.
It is rendered with a zig-zag approach with an origin outside of the screen (top left). It is rendered row by row from left to right whereby every uneven row is inset by half a tiles width. This is a common approach to avoid having to fill the source 2D-grid with "zombie-data" if one would render it in a diamond approach.
What the algorithm does is:
- the screen is logically divided into square rectangles with a width of my tile width and height of my tile height, i.e. 128px by 64px
- the mouse coordinates are scaled by tile width and tile height and then floored. This determines in which screen rectangle the user clicked
- then the mouse coordinates are compared against the center coordinates of this rectangle
- regarding this result, a right angled triangle is calculated to check if the user clicked within a certain area of the rectangle. I am using the cosine of this triangle to compare against my initial rotation angle of 26,565 (atan of 0.5). This is the angle in which the sides of the rendered rhombi are rotated to achieve a 2:1 ratio of width and height (classic isometric projection for video games).
- regarding on where the user clicked
- the y value is determined from a lower and upper boundary
- the x value is determined based on y being even or uneven
To run this code, you will need SDL2-2.0.9 and boost 1.67.0. to run. I am using MS Visual Studio Community 2017 as an IDE.
#include <iostream>
#include <vector>
#include <SDL.h>
#include <SDL_Image.h>
#include <boost/cast.hpp>
struct Point
{
std::size_t x;
std::size_t y;
};
Point mouseToGrid(int pMouseXPos, int pMouseYPos, std::size_t pTileWidth, std::size_t pTileHeight, const double PI, const int SCREEN_WIDTH, const int SCREEN_HEIGHT)
{
double mouseXPos = boost::numeric_cast<double>(pMouseXPos);
double mouseYPos = boost::numeric_cast<double>(pMouseYPos);
double tileWidth = boost::numeric_cast<double>(pTileWidth);
double tileHeight = boost::numeric_cast<double>(pTileHeight);
double tileWidthHalf = tileWidth / 2;
double tileHeightHalf = tileHeight / 2;
double mouseTileYPos = mouseYPos / tileHeight;
mouseTileYPos = std::floor(mouseTileYPos);
int screenRectCenterX = ((std::floor((mouseXPos / tileWidth))) * tileWidth) + tileWidthHalf;
int screenRectCenterY = (mouseTileYPos * tileHeight) + tileHeightHalf;
//determine lower and upper boundary for y
int minY = boost::numeric_cast<int>(2 * mouseTileYPos);
int maxY = boost::numeric_cast<int>((2 * mouseTileYPos) + 1);
if (mouseYPos >= screenRectCenterY)
{
minY = maxY;
maxY++;
}
//calc triangle sides in pixels
char mouseRectangleSector[2]{};
double opposite;
double adjacent;
if (mouseYPos >= screenRectCenterY)
{
mouseRectangleSector[0] = 'S';
opposite = mouseYPos - screenRectCenterY;
}
else
{
mouseRectangleSector[0] = 'N';
opposite = screenRectCenterY - mouseYPos;
}
if (mouseXPos >= screenRectCenterX)
{
mouseRectangleSector[1] = 'E';
adjacent = (screenRectCenterX + tileWidthHalf) - mouseXPos;
}
else{
mouseRectangleSector[1] = 'W';
adjacent = tileWidthHalf - (screenRectCenterX - mouseXPos);
}
double hypothenuse = std::sqrt(std::pow(opposite, 2) + std::pow(adjacent, 2));
//calculate cos and corresponding angle in rad and deg
double cos = adjacent / hypothenuse;
double angleRad = std::acos(cos);
double angleDeg = angleRad * 180 / PI;
//calculate initial rotation angle in rad and deg
double controlAtan = 0.5;
double controlAngleRad = std::atan(controlAtan);
double controlAngleDeg = controlAngleRad * 180 / PI;
//determine final position for y
if (mouseRectangleSector[0] == 'S')
{
if (angleRad > controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
else
{
if (angleRad < controlAngleRad)
{
mouseTileYPos = maxY;
}
else
{
mouseTileYPos = minY;
}
}
//determine position for x
double mouseTileXPos;
if ((boost::numeric_cast<int>(mouseTileYPos)) % 2 == 0)
{
mouseTileXPos = (mouseXPos + tileWidthHalf) / tileWidth;
}
else
{
mouseTileXPos = mouseXPos / tileWidth;
}
mouseTileXPos = std::floor(mouseTileXPos);
Point gridXY{(std::size_t)mouseTileXPos, (std::size_t)mouseTileYPos};
return gridXY;
}
int main(int argc, char *args)
{
const double PI = 3.1415926535897932384626433832795;
const int SCREEN_WIDTH = 1600;
const int SCREEN_HEIGHT = 900;
//init SDL Components
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
return 1;
}
SDL_Window *sdlWindow = SDL_CreateWindow("A New Era", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer *sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (sdlWindow == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_Quit();
return 1;
}
if (sdlRenderer == nullptr)
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
if (!(IMG_Init(IMG_INIT_PNG)))
{
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 1;
}
SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
SDL_RenderClear(sdlRenderer);
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
//tile dimensions
std::size_t tileWidth = 128;
std::size_t tileHeight = 64;
std::size_t tileWidthHalf = tileWidth / 2;
std::size_t tileHeightHalf = tileHeight / 2;
int originX = 0;
int originY = 0;
int xScreenOffset;
int yScreenOffset = 0 - tileHeightHalf;
//add pseudo data points to tile vector
std::vector<Point> mapTiles{};
for (std::size_t y = 0; y < 10; y++)
{
for (std::size_t x = 0; x < 10; x++)
{
Point point{x, y};
mapTiles.push_back(point);
}
}
for (std::vector<Point>::iterator itPoint = mapTiles.begin(); itPoint < mapTiles.end(); itPoint++)
{
if (itPoint->y % 2 == 0)
{
xScreenOffset = 0;
}
else
{
xScreenOffset = tileWidthHalf;
}
//draw 2:1 rombus
std::size_t tileOriginX = itPoint->x * tileWidth;
std::size_t tileOriginY = itPoint->y * tileHeightHalf;
std::size_t x1 = tileOriginX + tileWidthHalf;
std::size_t y1 = tileOriginY + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, tileOriginX + originX + xScreenOffset, tileOriginY + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
std::size_t x = x1;
std::size_t y = y1;
x1 = x - tileWidthHalf;
y1 = y + tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = x - tileWidthHalf;
y1 = y - tileHeightHalf;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
x = x1;
y = y1;
x1 = tileOriginX;
y1 = tileOriginY;
SDL_RenderDrawLine(sdlRenderer, x + originX + xScreenOffset, y + originY + yScreenOffset, x1 + originX + xScreenOffset, y1 + originY + yScreenOffset);
}
SDL_RenderPresent(sdlRenderer);
//game loop
//control variables
SDL_Event event;
bool quit = false;
//originX = originX - tileWidthHalf;
while (!quit)
{
while (SDL_PollEvent(&event) != 0)
{
if (event.type == SDL_QUIT)
{
quit = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
if (event.button.button == SDL_BUTTON_LEFT)
{
int mouseXPos;
int mouseYPos;
SDL_GetMouseState(&mouseXPos, &mouseYPos);
Point gridCoordinates = mouseToGrid(mouseXPos, mouseYPos, tileWidth, tileHeight, PI, SCREEN_WIDTH, SCREEN_HEIGHT);
std::cout << "x,y : " << gridCoordinates.x << "," << gridCoordinates.y << std::endl;
}
}
}
}
SDL_DestroyWindow(sdlWindow);
SDL_DestroyRenderer(sdlRenderer);
SDL_Quit();
return 0;
}
As I said, the algorithm works so I don't need help getting it to run. I am looking for help or advice on how to make it more compact (reduce lines of code). Maybe I can get rid of a couple of if statements by doing some more maths. This is really the first time after my graduation I used this kind of maths in programming so there might be some tweaks that can be done.
c++ game coordinate-system
c++ game coordinate-system
New contributor
New contributor
edited Jan 2 at 2:16
New contributor
asked Jan 1 at 17:05
Tremah
212
212
New contributor
New contributor
3
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
the code in my question is now a fully functional minimal example
– Tremah
2 days ago
add a comment |
3
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
the code in my question is now a fully functional minimal example
– Tremah
2 days ago
3
3
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
the code in my question is now a fully functional minimal example
– Tremah
2 days ago
the code in my question is now a fully functional minimal example
– Tremah
2 days ago
add a comment |
1 Answer
1
active
oldest
votes
If it is immaterial whether minY
or maxY
is used in case of angleRad == controlAngleRad
, you can avoid duplication in the final position for y-handling:
//determine final position for y
mouseTileYPos = (mouseRectangleSector[0] == 'S')
== (angleRad > controlAngleRad) ? maxY : minY;
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools likeindent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.
– greybeard
Jan 1 at 19:13
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Tremah is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210695%2fmouse-coordinates-to-grid-coordinates-within-an-isometric-map%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
If it is immaterial whether minY
or maxY
is used in case of angleRad == controlAngleRad
, you can avoid duplication in the final position for y-handling:
//determine final position for y
mouseTileYPos = (mouseRectangleSector[0] == 'S')
== (angleRad > controlAngleRad) ? maxY : minY;
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools likeindent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.
– greybeard
Jan 1 at 19:13
add a comment |
If it is immaterial whether minY
or maxY
is used in case of angleRad == controlAngleRad
, you can avoid duplication in the final position for y-handling:
//determine final position for y
mouseTileYPos = (mouseRectangleSector[0] == 'S')
== (angleRad > controlAngleRad) ? maxY : minY;
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools likeindent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.
– greybeard
Jan 1 at 19:13
add a comment |
If it is immaterial whether minY
or maxY
is used in case of angleRad == controlAngleRad
, you can avoid duplication in the final position for y-handling:
//determine final position for y
mouseTileYPos = (mouseRectangleSector[0] == 'S')
== (angleRad > controlAngleRad) ? maxY : minY;
If it is immaterial whether minY
or maxY
is used in case of angleRad == controlAngleRad
, you can avoid duplication in the final position for y-handling:
//determine final position for y
mouseTileYPos = (mouseRectangleSector[0] == 'S')
== (angleRad > controlAngleRad) ? maxY : minY;
answered Jan 1 at 19:12
greybeard
1,3491621
1,3491621
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools likeindent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.
– greybeard
Jan 1 at 19:13
add a comment |
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools likeindent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.
– greybeard
Jan 1 at 19:13
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools like
indent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.– greybeard
Jan 1 at 19:13
As for number of lines, I prefer block delimiters in-line with control structures, and more often than not use them when mandatory, only. IDEs and tools like
indent
(the flags *do me*.
) are good at bulk-changing such conventions to see whether one prefers one convention over the other.– greybeard
Jan 1 at 19:13
add a comment |
Tremah is a new contributor. Be nice, and check out our Code of Conduct.
Tremah is a new contributor. Be nice, and check out our Code of Conduct.
Tremah is a new contributor. Be nice, and check out our Code of Conduct.
Tremah is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210695%2fmouse-coordinates-to-grid-coordinates-within-an-isometric-map%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
3
Code stubs aren't appropriate for this site. Flesh it out to make it a complete code with the minimal amount required to compile and run it.
– tinstaafl
Jan 1 at 17:17
I will add a fully functional example tomorrow, thank your for the advice
– Tremah
Jan 2 at 0:24
the code in my question is now a fully functional minimal example
– Tremah
2 days ago