c++ 2D TileMap Render optimization - c++

I have a question about 2D TileMap Optimization.
I rendered the Tilemap but the speed is too slow (Frame-rate == 50)
I think I can specify tiles to be rendered. So instead of rendering all tiles, just render tiles on Screen (Device).
This is my current method.
//Lower Layer
for(int y = 0; y < Height; ++y)
{
for(int x = 0; x < Width; ++x)
{
//if the tile index number is -1, then this is null tile (non drawing)
if( Layer1[y][x] != -1)
{
// this EPN_CheckCollision() Function is the AABB Collision Function.
// if there 2 Rect value's are collide , then return true.
if(EPN_CheckCollision( EPN_Rect(EPN_Pos(x*32, y*32)-CharacterPos) , EPN_Rect(0-32,0-32,1024+32,768+32) ) )
{
//EPN_Pos structure is consist of (float PosX , float PosY)
EPN_Pos TilePos = EPN_Pos(x * 32, y * 32)-CharacterPos;
//EPN_Rect structure is consist of below members
//float Top X
//float Top Y
//float BottomX (not Width)
//float BottomY (not Height)
EPN_Rect TileRect = EPN_Rect( Layer1[y][x] % 8 * 32, Layer1[y][x] / 8 * 32, Layer1[y][x] % 8 * 32 + 32, Layer1[y][x] / 8 * 32+32);
//Blt is Texture render function.
// 2nd Parameter is Render Pos and 3rd parameter is Render Texture's Rect.
pEPN_TI->Blt("MapTileset", TilePos, TileRect );
}
}
}
This is my TileMapRender Method.
( I use EPN Engine made by directX which is Unknown. So I annotated my code)
I rendered the tilemap that collides with the DeviceScreen ( 1024 * 768 , but for margin)
because I want to render visible tilemap on screen (I do not render tiles out of device screen).
So I Check the AABB Collision each tile and (1024, 768) device Screen, now I only render necessary tiles.
But I think this method has a problem, that it does not render out of screen tiles.
For statement also repeat all maptiles; what a inefficient method...
Maybe my games frame-rate problem is in this method. So may I ask STACK OVERFLOW how I could do this?
Is there another ways to optimize tilemap rendering?
Give me some tips please.
P.S
I'm sorry about my knotty question.
Please excuse my English ability.

You should only be rendering enough tiles to cover the screen, for example if your screen size is 640x480, and your tile size is 16x16 then:
Max tiles on X = (640/16)+2;
Max tiles on Y = (480/16)+2;
Notice how we add 2 for a margin on each side. Next thing we need to do is work out where we are in the tile map, for this we simply divide the camera x position by the tile width.
For example if the camera is at x=500 and y=20 then:
X tile index = 500/16
Y tile index = 20/16
You must also render your tile grid at an offset of 500%16 and 20%16 to account for the "sub tile pixel" scrolling.
For the collision its even easier, you only need to check collision with the tiles the player is on, so:
If the player size is 16x20 pixels and at position 120,200:
X tile index = 120/16
Y tile index = 200/16
Num X tiles to check = 16/16
Num Y tiles to check = 20/16
Hopefully this makes sense.

Related

How to convert from 2d world coordinates to 2d screen coordinates?

I am designing a 2d platformer tile-based game using SDL. I am having trouble with converting from world coordinates to screen coordinates. I defined (0, 0) as the top left for both the screen and the world and (x, y) as the bottom right.
Currently, I am storing the map data like so:
"." is an air tile
"#" is a grass tile
"i" is an iron tile
"p" is a wooden plank tile
"c" is the top layer of the ice
"b" is the bottom layers of the ice
...iiiiii...pppppp..cccccc...........................................
...iiiiii...pppppp..b....b...........................................
...iiiiii...p....p..b....b...........................................
...iiiiii...p....p..b....b...........................................
...iiiiii...p....p..b....b...........................................
...iiiiii...p....p..b....b...........................................
...iiiiii...p....p..bbbbbb...........................................
...iiiiii...pppppp..bbbbbb...........................................
...iiiiii...pppppp..bbbbbb...........................................
...iiiiii...pppppp..bbbbbb...........................................
...iiiiii...pppppp..bbbbbb...........................................
...iiiiii...pppppp..bbbbbb...........................................
#####################################################################
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
.....................................................................
....................................................................#
I am loading the map like so, in which the top left is (0, 0):
typedef struct Tile {
char texture_id;
int x;
int y;
} Tile;
Map::Map(std::string raw_map_data) {
// Initialize private variables
this->unparsed_map_data= raw_map_data;
// Split the map data into a std::vector<std::string> based off of newlines
std::vector<std::string> map_data_split = split_string(raw_map_data, "\n");
// Next, convert the map data to a vector Tile structs
int cur_x = 0;
int cur_y = 0;
std::vector<Tile> tiles_processed;
Tile tile;
for (size_t i = 0; i < map_data_split.size(); i++) {
for (size_t j = 0; j < map_data_split[i].size(); j++) {
tile.x = cur_x;
tile.y = cur_y;
tile.texture_id = map_data_split[i][j];
tiles_processed.push_back(tile);
cur_x += TILE_WIDTH;
}
cur_x = 0;
cur_y += TILE_HEIGHT;
}
this->tiles_parsed = tiles_processed;
//std::reverse(this->tiles_parsed.begin(), this->tiles_parsed.end());
DebugTiles(this->tiles_parsed); // DEBUG: used to print tiles to find errors
}
I have set the camera coordinates as the world coordinates of the top-left point of the window. Additionally, the player never leaves the center of the screen. Furthermore, the transformation that I am using to go from the world coordinates to the screen coordinates is as follows: screen coordinates = camera coordinates - world coordinates of tile
void Camera::Render(std::vector<Tile> tiles_to_render, SDL_Texture* player_texture) {
// Render the scene
for (size_t i = 0; i < tiles_to_render.size(); i++) {
if (tiles_to_render[i].texture_id != '.') {
// world_pos_tl is an SDL_Rect containing the camera coordinates
SDL_Rect dstrect = {this->world_pos_tl.x - tiles_to_render[i].x, this->world_pos_tl.y - tiles_to_render[i].y, TILE_WIDTH, TILE_HEIGHT};
SDL_RenderCopy(this->renderer, this->textures[tiles_to_render[i].texture_id], NULL, &dstrect);
}
}
// Render the character (always in center of screen)
SDL_Rect player_dstrect = {(WIDTH / 2) - TILE_WIDTH, (HEIGHT / 2) - TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT};
SDL_RenderCopy(this->renderer, player_texture, NULL, &player_dstrect);
}
However, when I compile and run, I get the following result, as the world is rotated 270 degrees.
The result of running the code:
Special thanks to Gorbit99 on the "One Lone Coder" discord server who figured this out for me.
The issue is that it is supposed to be screen position = world position - camera position. The reason for this is because when you move an object to the left and the camera stays still, the object should move left. But if the camera moves to the left and the object stays still, it should look like the object's moving to the right.
Side note: I am overusing the keyword, this a lot, as it is only meant to be used when a variable in a function has the same name as a class member and you want to specify that you want to use the class member. Therefore, in practice, the keyword this should rarely be used.

Faster way of calculating array 2D position

I have a custom image object, which is organised in tiles of fixed size, disposed in a tile grid. Let's assume, for instance, that the fixed tileSize is 256 pixels (each tile is a subimage of 256x256 pixels), and that my global image is 1000x500 pixels: this results in a tile grid of 4x2 tiles of 256x256 pixels.
Now, given a point in the global image coordinates, this is how I convert to tile coordinates (i.e., the tile position in the tile grid, plus the pixel position inside that pixel):
struct Point {
int x;
int y;
}
Point pGlobal = (...);
// Grid coordinates of the tile containing pGlobal
Point pTileGrid = Point(pGlobal.x / tileSize, pGlobal.y / tileSize);
// Pixel inside that tile that contains pGlobal
Point pTile = Point(pGlobal.x % tileSize, pGlobal.y % tileSize);
The problem is that this is painfully slow. How can I optimize this?
Thank you in advance. If I did not explain myself correctly in any point please ask me to improve my formulation.

Isometric Collision - 'Diamond' shape detection

My project uses an isometric perspective for the time being I am showing the co-ordinates in grid-format above them for debugging. However, when it comes to collision/grid-locking of the player, I have an issue.
Due to the nature of sprite drawing, my maths is creating some issues with the 'triangular' corner empty areas of the textures. I think that the issue is something like below (blue is what I think is the way my tiles are being detected, whereas the red is how they ideally should be detected for accurate roaming movement on the tiles:
As you can see, the boolean that checks the tile I am stood on (which takes the pixel central to the player's feet, the player will later be a car and take a pixel based on the direction of movement) is returning false and denying movement in several scenarios, as well as letting the player move in some places that shouldn't be allowed.
I think that it's because the cutoff areas of each texture are (I think) being considered part of the grid area, so when the player is in one of these corner areas it is not truly checking the correct tile, and so returning the wrong results.
The code I'm using for creating the grid is this:
int VisualComponent::TileConversion(Tile* tileToConvert, bool xOrY)
{
int X = (tileToConvert->x - tileToConvert->y) * 64; //change 64 to TILE_WIDTH_HALF
int Y = (tileToConvert->x + tileToConvert->y) * 25;
/*int X = (tileToConvert->x * 128 / 2) + (tileToConvert->y * 128 / 2) + 100;
int Y = (tileToConvert->y * 50 / 2) - (tileToConvert->x * 50 / 2) + 100;*/
if (xOrY)
{
return X;
}
else
{
return Y;
}
}
and the code for checking the player's movement is:
bool Clsentity::CheckMovementTile(int xpos, int ypos, ClsMapData* mapData) //check if the movement will end on a legitimate road tile UNOPTIMISED AS RUNS EVERY FRAME FOR EVERY TILE
{
int x = xpos + 7; //get the center bottom pixel as this is more suitable than the first on an iso grid (more realistic 'foot' placement)
int y = ypos + 45;
int mapX = (x / 64 + y / 25) / 2; //64 is TILE-WIDTH HALF and 25 is TILE HEIGHT
int mapY = (y / 25 - (x / 64)) / 2;
for (int i = 0; i < mapData->tilesList.size(); i++) //for each tile of the map
{
if (mapData->tilesList[i]->x == mapX && mapData->tilesList[i]->y == mapY) //if there is an existing tile that will be entered
{
if (mapData->tilesList[i]->movementTile)
{
HAPI->DebugText(std::to_string(mapX) + " is the x and the y is " + std::to_string(mapY));
return true;
}
}
}
return false;
}​
I'm a little stuck on progression until having this fixed in the game loop aspect of things. If anyone thinks they either know the issue from this or might be able to help it'd be great and I would appreciate it. For reference also, my tile textures are 128x64 pixels and the math behind drawing them to screen treats them as 128x50 (to cleanly link together).
Rather than writing specific routines for rendering and click mapping, seriously consider thinking of these as two views on the data, which can be transformed in terms of matrix transformations of a coordinate space. You can have two coordinate spaces - one is a nice rectangular grid that you use for positioning and logic. The other is the isometric view that you use for display and input.
If you're not familiar with linear algebra, it'll take a little bit to wrap your head around it, but once you do, it makes everything trivial.
So, how does that work? Your isometric view is merely a rotation of a bog standard grid view, right? Well, close. Isometric view also changes the dimensions if you're starting with a square grid. Anyhow: can we just do a simple coordinate transformation?
Logical coordinate system -> display system (e.g. for rendering)
Texture point => Rotate 45 degrees => Scale by sqrt(2) because a 45 degree rotation changes the dimension of the block by sqrt(1 * 1 + 1 * 1)
Display system -> logical coordinate system (e.g. for mapping clicks into logical space)
Click point => descale by sqrt(2) to unsquish => unrotate by 45 degrees
Why?
If you can do coordinate transformations, then you'd be dealing with a pretty bog-standard rectangular grid for everything else you write, which will make your any other logic MUCH simpler. Your calculations there won't involve computing angles or slopes. E.g. now your "can I move 'down'" logic is much simpler.
Let's say you have 64 x 64 tiles, for simplicity. Now transforming a screen space click to a logical tile is simply:
(int, int) whichTile(clickX, clickY) {
logicalX, logicalY = transform(clickX, clickY)
return (logicalX / 64, logicalY / 64)
}
You can do checks like see if x0,y0 and x1,y1 are on the same tile, in the logical space by someting as simple as:
bool isSameTile(x0, y0, x1, y1) {
return floor(x0/64) == floor(x1/64) && floor(y0/64) == floor(y1/64)
}
Everything gets much simpler once you define the transforms and work in the logical space.
http://en.wikipedia.org/wiki/Rotation_matrix
http://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation
http://www.alcove-games.com/advanced-tutorials/isometric-tile-picking/
If you don't want to deal with some matrix library, you can do the equivalent math pretty straightforwardly, but if you separate concerns of logic management from display / input through these transformations, I suspect you'll have a much easier time of it.

CPU Ray Casting

I'm attempting ray casting an octree on the CPU (I know the GPU is better, but I'm unable to get that working at this time, I believe my octree texture is created incorrectly).
I understand what needs to be done, and so far I cast a ray for each pixel, and check if that ray intersects any nodes within the octree. If it does and the node is not a leaf node, I check if the ray intersects it's child nodes. I keep doing this until a leaf node is hit. Once a leaf node is hit, I get the colour for that node.
My question is, what is the best way to draw this to the screen? Currently im storing the colours in an array and drawing them with glDrawPixels, but this does not produce correct results, with gaps in the renderings, as well as the projection been wrong (I am using glRasterPos3fv).
Edit: Here is some code so far, it needs cleaning up, sorry. I have omitted the octree ray casting code as I'm not sure it's needed, but I will post if it'll help :)
void Draw(Vector cameraPosition, Vector cameraLookAt)
{
// Calculate the right Vector
Vector rightVector = Cross(cameraLookAt, Vector(0, 1, 0));
// Set up the screen plane starting X & Y positions
float screenPlaneX, screenPlaneY;
screenPlaneX = cameraPosition.x() - ( ( WINDOWWIDTH / 2) * rightVector.x());
screenPlaneY = cameraPosition.y() + ( (float)WINDOWHEIGHT / 2);
float deltaX, deltaY;
deltaX = 1;
deltaY = 1;
int currentX, currentY, index = 0;
Vector origin, direction;
origin = cameraPosition;
vector<Vector4<int>> colours(WINDOWWIDTH * WINDOWHEIGHT);
currentY = screenPlaneY;
Vector4<int> colour;
for (int y = 0; y < WINDOWHEIGHT; y++)
{
// Set the current pixel along x to be the left most pixel
// on the image plane
currentX = screenPlaneX;
for (int x = 0; x < WINDOWWIDTH; x++)
{
// default colour is black
colour = Vector4<int>(0, 0, 0, 0);
// Cast the ray into the current pixel. Set the length of the ray to be 200
direction = Vector(currentX, currentY, cameraPosition.z() + ( cameraLookAt.z() * 200 ) ) - origin;
direction.normalize();
// Cast the ray against the octree and store the resultant colour in the array
colours[index] = RayCast(origin, direction, rootNode, colour);
// Move to next pixel in the plane
currentX += deltaX;
// increase colour arry index postion
index++;
}
// Move to next row in the image plane
currentY -= deltaY;
}
// Set the colours for the array
SetFinalImage(colours);
// Load array to 0 0 0 to set the raster position to (0, 0, 0)
GLfloat *v = new GLfloat[3];
v[0] = 0.0f;
v[1] = 0.0f;
v[2] = 0.0f;
// Set the raster position and pass the array of colours to drawPixels
glRasterPos3fv(v);
glDrawPixels(WINDOWWIDTH, WINDOWHEIGHT, GL_RGBA, GL_FLOAT, finalImage);
}
void SetFinalImage(vector<Vector4<int>> colours)
{
// The array is a 2D array, with the first dimension
// set to the size of the window (WINDOW_WIDTH * WINDOW_HEIGHT)
// Second dimension stores the rgba values for each pizel
for (int i = 0; i < colours.size(); i++)
{
finalImage[i][0] = (float)colours[i].r;
finalImage[i][1] = (float)colours[i].g;
finalImage[i][2] = (float)colours[i].b;
finalImage[i][3] = (float)colours[i].a;
}
}
Your pixel drawing code looks okay. But I'm not sure that your RayCasting routines are correct. When I wrote my raytracer, I had a bug that caused horizontal artifacts in on the screen, but it was related to rounding errors in the render code.
I would try this...create a result set of vector<Vector4<int>> where the colors are all red. Now render that to the screen. If it looks correct, then the opengl routines are correct. Divide and conquer is always a good debugging method.
Here's a question though....why are you using Vector4 when later on you write the image as GL_FLOAT? I'm not seeing any int->float conversion here....
You problem may be in your 3DDDA (octree raycaster), and specifically with adaptive termination. It results from the quantisation of rays into gridcell form, that causes certain octree nodes which lie slightly behind foreground nodes (i.e. of a higher z depth) and which thus should be partly visible & partly occluded, to not be rendered at all. The smaller your voxels are, the less noticeable this will be.
There is a very easy way to test whether this is the problem -- comment out the adaptive termination line(s) in your 3DDDA and see if you still get the same gap artifacts.

How can I store a game map and display a portion of it on the screen?

Hey.
I'm going to write a simple ship shooting game as CG homework, so I'm planning to use some map system (though there's no need for that, it'll be an "extra") and I have no clue about how to represent a map and show it 'in parts', I mean, not all map will be visible in a single frame.
How do people usually work with that?
Consider using a QuadTree. It breaks your map down into small components based by area. It lets you define how much space you want to see, which makes it ideal for zooming in and out, or panning around.
There's a C# implementation you could probably adapt to be C++ fairly easily.
You could also use tiles and a fixed size 2D array of pointers to tiles. The pointers are so you can reuse tiles in multiple places.
You might want to do it like how early video game hardware did: store a 2D array of bytes, where each byte is the index of an 8x8 pixel "tile". Elsewhere store an array of 8x8 pixel tiles. Also store the offset in pixels from the upper-left corner of the map to the upper-left corner of the screen.
When it is time to draw the map, render only the tiles that should be visible on the screen, according to the offset in pixels from the map corner to the screen corner:
int tile_x = pixel_x / tile_width;
int tile_y = pixel_y / tile_height;
for( int y=tile_y; y<=tile_y+screen_height_in_tiles; ++y )
{
for( int x=tile_x; x<=tile_x+screen_width_in_tiles; ++x )
{
int screen_x = tile_x * tile_width - ( pixel_x % tile_width );
int screen_y = tile_y * tile_height - ( pixel_y % tile_height );
render_tile( screen_x, screen_y, map[y][x] );
}
}
This code is not optimally fast, and some logic is missing, such as how to deal with a map that is partially scrolled off the screen. But it's a start.