Tileset crop calculation C++ - c++

So I have this problem with my tileset rendering. It's a little tricky to explain but i'll do my best.
Basicly what i do is i load these "tile numbers" from TMX files and they only represent the tileset number in the tileset. So if i use a tile that is on a row below the first one i only get the number. But i want both X and Y position to calculate where i would crop the image when i display it in my game.
So my array would look something like this,
1,1,1,2,1,1,1,1,9,1,6,1,1
1,1,1,2,2,2,1,1,1,1,1,1,1
1,1,1,1,1,2,1,1,1,1,1,1,1
1,1,1,1,1,2,1,1,1,3,1,1,1
1,6,1,1,1,2,1,1,1,1,1,1,1
1,1,5,5,1,2,2,2,1,1,1,1,1
1,1,1,1,1,1,1,2,1,1,1,1,1
Tileset:
http://puu.sh/9Tv2X/7938b6abf4.png
so when i render all the tiles in the first row in this tileset, numbers 1-4 (since the tileset is 4 tiles long) it works fine but anything past that would get cropped outside the image.
So for that i need the Y position to increase and to reset the X position everytime it goes outside the tileset width which is (32 * (tileNumber - 1)) but my brain cant figure out how to do this.
tile width and height is 32.
my code for drawing the tilset:
void TileMap::Draw()
{
for (unsigned int i = 0; i < mapVector.size(); i++)
{
for (unsigned int j = 0; j < mapVector[i].size(); j++)
{
int tileY = 0;
int tileX = mapVector[i][j] - 1;
if (tileWidth * mapVector[i][j] > tilesetWidth)
{
//now what
}
if (mapVector[i][j] > 0)
{
tileSprite->SetPosition(j * tileWidth, i * tileHeight);
tileSprite->SetTextureRect(tileX * tileWidth, tileY * tileHeight, 32, 32);
tileSprite->Draw();
}
}
}
}

Related

Fill in new rows and columns with the average of the surrounding pixels

The Zoom method creates an image (pixel array) magnified x2. The current function creates a new auxiliary image and adds the pixel values of the original image. C++ code of the defined method:
Image Zoom(int nrow, int ncol, int scroll) {
Image aux(2 * scroll - 1, 2 * scroll - 1);
//FILL IMAGE
for (int i = nrow; i < nrow + (scroll * 2 - 1); i += 2) //ROWS
for (int j = ncol; j < ncol + (scroll * 2 - 1); j += 2) //COLUMNS
aux.set_pixel(i - nrow, j - ncol, get_pixel(i, j));
return aux;
}
Functions get_pixel and set_pixel can be used.
The problem is that aux Image has black rows and columns, which should be filled with a value (the average of the pixels around them). I need to add the code to fill the unfilled rows with the average of the original filled pixels around them but I don't know how to do it.

How to account for spacing between tiles in a tile sheet

My tile-sheet has tiles that are 64x64, however between each tile there is a 10px gap and i need to account for that gap when setting the texture rectangle in the image in order to draw that tile
I tried simply adding the space upon setting the texture rectangle but the image still looks distorted
for (auto y = 0u; y < map.getTileCount().y; ++y)
{
for (auto x = 0u; x < map.getTileCount().x; ++x)
{
auto posX = static_cast<float>(x * map.getTileSize().x);
auto posY = static_cast<float>(y * map.getTileSize().y);
sf::Vector2f position(posX, posY);
tileSprite.setPosition(position);
auto tileID = tiles[y * map.getTileCount().x + x].ID; //the id of the current tile
if (tileID == 0)
{
continue; //empty tile
}
auto i = 0;
while (tileID < tileSets[i].getFirstGID())
{
++i;
}
auto relativeID = tileID - tileSets[i].getFirstGID();
auto tileX = relativeID % tileSets[i].getColumnCount();
auto tileY = relativeID / tileSets[i].getColumnCount();
textureRect.left = tileX * tileSets[i].getTileSize().x; //i am guessing this is where
// i should account for the spacing
textureRect.top = tileY * tileSets[i].getTileSize().y;
tileSprite.setTexture(mTextureHolder.get(Textures::SpriteSheet));
tileSprite.setTextureRect(textureRect);
mMapTexture.draw(tileSprite);
}
}
The code itself is working and its drawing the tiles in the correct sizes, if i use a normal 64x64 tileset without any spacing the final image looks right however with spacing included the tiles are cut out.
How do i add the gap between the tiles when setting the texture rectangle?
this is how it looks:
this is how it should look:
(NOTE: The "how it should look" image is from the Tiled editor )
Removing the spaces with a python script i found and gimp fixed the problem, however if anyone knows how to account for the spacing feel free to answer as i might need it someday

Determine which tile is clicked in a window

I am drawing a tilemap on a SFML renderwindow. I want to determine which tile is clicked by the user, but I just cant seem to find a solution. First of all, each tile has 32 width and height.
What i try at the moment : Get the position of the click. Loop trough the tilemap until a tile is found which position is between 100. So if I click on (100,100) the tile should begin at (96,96) but this does not seem to work.
Here is my code snippet from the function getTile(mousepos x,mousepos y)
Tile* TileMap::getTile(int x, int y)
{
Tile *t = NULL;
for(int i = 0; i < tilemap.size(); i++)
{
for(int j = 0; j < tilemap[i].size(); j++)
{
if(x > tilemap[i][j].sprite.getPosition().x
&& x < (tilemap[i][j].sprite.getPosition().x+32))
{
if(y > tilemap[i][j].sprite.getPosition().y
&& y < (tilemap[i][j].sprite.getPosition().y+32))
{
t = &tilemap[i][j];
break;
}
}
}
}
return t;
}
Based on your code, I am going to assume that you are basing your tilemap on a 2d array of Tiles: tilemap[x][y]. I am also going to assume that tilemap[0][0] is the top left tile.
There should be a much easier way to find out which tile is being clicked on instead of testing every single tile.
If you are at 100,100 and tiles are 32x32, then we can get the x and y of the tile within the tilemap by doing something as simple as:
x = 100 / 32 = 3
y = 100 / 32 = 3
Therefor the tile in your tilemap that corresponds to a mouse position of (100,100) is tilemap[3][3].

SDL - drawing 'negative' circles (Fog of War)

I have this 800x600square I want to draw to the screen. I want to 'cut' circles in it (where alpha would be 0). Basically I'm drawing this whole rectangle over a map so in these 'circles' I drew, you can see the map, otherwise you see the grey square
So, I assume you're trying to add fog of war to one of you game?
I had a small demo I made for a local University a few weeks ago to show A* pathfinding, so I thought I could add fog of war to it for you. Here's the results:
Initial map
First, you start with a complete map, totally visible
Fog
Then, I added a surface to cover the entire screen (take note that my map is smaller than the screen, so for this case I just added fog of war on the screen, but if you have scrolling, make sure it covers each map pixel 1:1)
mFogOfWar = SDL_CreateRGBSurface(SDL_HWSURFACE, in_Width, in_Height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
SDL_Rect screenRect = {0, 0, in_Width, in_Height};
SDL_FillRect(mFogOfWar, &screenRect, 0xFF202020);
Then, you need to draw it... I added this call after drawing the game objects and before drawing the UI
DrawSurface(mFogOfWar, 0, 0);
Where
void RenderingManager::DrawSurface(SDL_Surface* in_Surface, int in_X, int in_Y)
{
SDL_Rect Dest = { in_X, in_Y, 0, 0 };
SDL_BlitSurface(in_Surface, NULL, mScreen, &Dest);
}
Which should give you the following result:
"Punch Surface"
I then created a 32 bits .png that looks like this (checkerboard shows alpha)
When rendering my main character, I added this call:
gRenderingManager.RemoveFogOfWar(int(mX) + SPRITE_X_OFFSET, int(mY) + SPRITE_Y_OFFSET);
The offset is only there to center the punch with the sprite, basically, what I'm passing to RemoveFogOfWar is the center of my sprite.
Remove Fog Of War
Now the meat of the fog of war. I did two versions, one where Fog of War is removed permanently and one where the fog of war is reset. My fog of war reset relies on my punch surface to have a contour where the alpha is reset to 0 and the fact that my character moves of less pixels than the contour contains per frame, otherwise I would keep the Rect where my punch was applied and I would refill it before drawing again the new punch.
Since I couldn't find a "multiply" blend with SDL, I decided to write a simple function that iterates on the punch surface and updates the alpha on the fog of war surface. The most important part is to make sure you stay within the bounds of your surfaces, so it takes up most of the code... there might be some crop functions but I didn't bother checking:
void RenderingManager::RemoveFogOfWar(int in_X, int in_Y)
{
const int halfWidth = mFogOfWarPunch->w / 2;
const int halfHeight = mFogOfWarPunch->h / 2;
SDL_Rect sourceRect = { 0, 0, mFogOfWarPunch->w, mFogOfWarPunch->h };
SDL_Rect destRect = { in_X - halfWidth, in_Y - halfHeight, mFogOfWarPunch->w, mFogOfWarPunch->h };
// Make sure our rects stays within bounds
if(destRect.x < 0)
{
sourceRect.x -= destRect.x; // remove the pixels outside of the surface
sourceRect.w -= sourceRect.x; // shrink to the surface, not to offset fog
destRect.x = 0;
destRect.w -= sourceRect.x; // shrink the width to stay within bounds
}
if(destRect.y < 0)
{
sourceRect.y -= destRect.y; // remove the pixels outside
sourceRect.h -= sourceRect.y; // shrink to the surface, not to offset fog
destRect.y = 0;
destRect.h -= sourceRect.y; // shrink the height to stay within bounds
}
int xDistanceFromEdge = (destRect.x + destRect.w) - mFogOfWar->w;
if(xDistanceFromEdge > 0) // we're busting
{
sourceRect.w -= xDistanceFromEdge;
destRect.w -= xDistanceFromEdge;
}
int yDistanceFromEdge = (destRect.y + destRect.h) - mFogOfWar->h;
if(yDistanceFromEdge > 0) // we're busting
{
sourceRect.h -= yDistanceFromEdge;
destRect.h -= yDistanceFromEdge;
}
SDL_LockSurface(mFogOfWar);
Uint32* destPixels = (Uint32*)mFogOfWar->pixels;
Uint32* srcPixels = (Uint32*)mFogOfWarPunch->pixels;
static bool keepFogRemoved = false;
for(int x = 0; x < destRect.w; ++x)
{
for(int y = 0; y < destRect.h; ++y)
{
Uint32* destPixel = destPixels + (y + destRect.y) * mFogOfWar->w + destRect.x + x;
Uint32* srcPixel = srcPixels + (y + sourceRect.y) * mFogOfWarPunch->w + sourceRect.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3; // fetch alpha channel
unsigned char* srcAlpha = (unsigned char*)srcPixel + 3; // fetch alpha channel
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
*destAlpha = *srcAlpha;
}
}
SDL_UnlockSurface(mFogOfWar);
}
Which then gave me this with keepFogRemoved = false even after the character had moved around
And this with keepFogRemoved = true
Validation
The important part is really to make sure you don't write outside of your pixel buffer, so watch out with negative offsets or offsets that would bring you out of the width or height. To validate my code, I added a simple call to RemoveFogOfWar when the mouse is clicked and tried corners and edges to make sure I didn't have a "off by one" problem
case SDL_MOUSEBUTTONDOWN:
{
if(Event.button.button == SDL_BUTTON_LEFT)
{
gRenderingManager.RemoveFogOfWar(Event.button.x, Event.button.y);
}
break;
}
Notes
Obviously, you don't need a 32 bits texture for the "punch", but it was the clearest way I could think of to show you how to do it. It could be done using as little as 1 bit per pixel (on / off). You can also add some gradient, and change the
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
To something like
if(*srcAlpha > *destAlpha)
{
continue;
}
To keep a smooth blend like this:
3 State Fog of War
I thought I should add this... I added a way to create a 3 state fog of war: visible, seen and fogged.
To do this, I simply keep the SDL_Rect of where I last "punched" the fog of war, and if the alpha is lower than a certain value, I clamp it at that value.
So, by simply adding
for(int x = 0; x < mLastFogOfWarPunchPosition.w; ++x)
{
for(int y = 0; y < mLastFogOfWarPunchPosition.h; ++y)
{
Uint32* destPixel = destPixels + (y + mLastFogOfWarPunchPosition.y) * mFogOfWar->w + mLastFogOfWarPunchPosition.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3;
if(*destAlpha < 0x60)
{
*destAlpha = 0x60;
}
}
}
mLastFogOfWarPunchPosition = destRect;
right before the loop where the fog of war is "punched", I get a fog of war similar to what you could have in games like StarCraft:
Now, since the "seen" fog of war is semi transparent, you will need to tweak your rendering method to properly clip "enemies" that would be in the fog, so you don't see them but you still see the terrain.
Hope this helps!

Can someone explain how I am to access this array? (image processing program)

I am working on the implementation of functions for an already written image processing program. I am given explanations of functions, but not sure how they are designating pixels of the image.
In this case, I need to flip the image horizontally, i.e., rotates 180 degrees around the vertical axis
Is this what makes the "image" i am to flip?
void Image::createImage(int width_x, int height_y)
{
width = width_x;
height = height_y;
if (pixelData!=NULL)
freePixelData();
if (width <= 0 || height <= 0) {
return;
}
pixelData = new Color* [width]; // array of Pixel*
for (int x = 0; x < width; x++) {
pixelData[x] = new Color [height]; // this is 2nd dimension of pixelData
}
}
I do not know if all the functions I have written are correct.
Also, the Image class calls on a Color class
So to re-ask: what am I "flipping" here?
Prototype for function is:
void flipLeftRight();
As there is no input into the function, and I am told it modifies pixelData, how do I flip left to right?
A quick in place flip. Untested, but the idea is there.
void flipHorizontal(u8 *image, u32 width, u32 height)
{
for(int i=0; i < height; i++)
{
for(int j=0; j < width/2; j++)
{
int sourceIndex = i * width + j;
int destIndex = (i+1) * width - j - 1;
image[sourceIndex] ^= image[destIndex];
image[destIndex] ^= image[sourceIndex];
image[sourceIndex] ^= image[destIndex];
}
}
}
well, the simplest approach would be to read it 1 row at a time into a temporary buffer the same size as 1 row.
Then you could use something like std::reverse on the temporary buffer and write it back.
You could also do it in place, but this is the simplest approach.
EDIT: what i;ve described is a mirror, not a flip, to mirror you also need to reverse the order of the rows. Nothing too bad, to do that I would create a buffer the same size as the image, copy the image and then write it back with the coordinates adjusted. Something like y = height - x and x = width - x.