-
Hello!
What am I trying to do is to invent a bicycle make a click detection on isometric tiles. My current approach:
Map::ClickOn(sf::Vector2f pos){
for(int i = 0; i < 100; i++){
for(int j = 0; j < 100; j++){
if(tiles[i][j].getGlobalBounds().contains(pos)){
std::cout<<"Selected tile @ "<<i<<", "<<j<<std::endl;
return;
}
}
}
where 'pos' is the mouse click position. In 3D I'd simply cast a ray but with 2D there are overlapping bounding boxes so it often detects neighbouring tiles.
I thought that I can get all sprites with their bounding boxes are within 'pos' and omit the ones with transparent pixels on click position. sf::Sprite had GetPixel(x,y) method back in 1.6 but now it's gone. Is there any ways to check the color of sprite's each pixel? Or maybe a simplier approach to solve this problem?
TY!
-
Seems like you can solve this problem by transforming mouse position:
http://stackoverflow.com/questions/6915555/how-to-transform-mouse-location-in-isometric-tiling-map
-
If I understand this correctly, it'll work out just for tiles. But they're all the same size. What if there are different shaped objects? for ex. a tree sprite? Bounding box wont let to click behind and will catch clicks on that tree - that's why I thought about excluding transparent pixels.
-
I see. Then you could try:
http://sfml-dev.org/documentation/2.1/classsf_1_1Texture.php#aefc19bcd95565dd2348fd4cec0facddc
But you will have to transform from sprite to texture yourself. Also, that method is pretty slow, so I recommend using it once per texture and then accesing images via map:
http://www.cplusplus.com/reference/map/map/
-
I see. Then you could try:
http://sfml-dev.org/documentation/2.1/classsf_1_1Texture.php#aefc19bcd95565dd2348fd4cec0facddc
Ha-ha, actually I came to such idea by myself like 40 minutes ago but I kinda missed the copyToImage() :D
Thanks, I'll give it a try!
---
if(tiles[i][j].getGlobalBounds().contains(pos)){
sf::Image img = tiles[i][j].getTexture()->copyToImage();
sf::Vector2f tilePos = tiles[i][j].getPosition();
sf::Color c = img.getPixel(pos.x - tilePos.x, pos.y - tilePos.y);
if(c.a != 0){
std::cout<<"Selected tile @ "<<i<<", "<<j<<std::endl;
return;
}
}
Idk how this solution impacts performance-wise, with 10k tiles it works well. Maybe I should store all images for all of my textures separately.
Thanks again!~
-
It's definitely a bad idea to download texture data from the graphics card into an sf::Image every time you click.
Why don't you precompute the images? You don't need 32 bit per pixel anyway, that's a total waste of memory. 1 bit is enough to determine whether a pixel is transparent or not. You could use std::vector<bool> (or something else if you don't like this one) to store a texture as a bitset. Ideally, you directly do that when loading:
sf::Image image;
if (!image.loadFromFile(...))
... // error
std::vector<bool> bitset = computeBitset(image);
sf::Texture texture;
if (!texture.loadFromImage(image))
... // error
-
Hi Nexus, thank you for your reply.
I thought about 1bpp data and I've tried but FPS actually lower than with simple Image->getPixel(x,y). Maybe I'm doing something wrong?
std::map<std::string, std::vector<bool>> bitsets; //string contains texture name
after loading the image & texture I create the bitset for this texture:
bitsets[texturename] = calculateBitset(images[texturename]);
std::vector<bool> calculateBitset(sf::Image img){
sf::Vector2u imgSize = img.getSize();
std::vector<bool> retVal;
for(int i = 0; i < imgSize.x; i++){
for(int j = 0; j < imgSize.y; j++){
retVal.push_back(img.getPixel(i,j).a != 255); //false = transparent
}
}
return retVal;
}
then I check if given point is clickable:
bool isClickable(std::string textureName, sf::Vector2u pos){
int w = images[textureName].getSize().x;
return !bitsets[textureName][w*pos.y+pos.x];
}
With this code I'm experiencing 6-20 FPS drop (depending on map indexes)
If I load and store only sf::Images and call getPixel() every time, FPS drop is only 1-6 and equally lower cpu load.
-
Really? That's very strange. What if you try std::vector<char> instead of std::vector<bool>? You can leave most of the code, just store either the value 0 or 1 per char.
And you should make sure you don't copy unnecessarily. For example, pass sf::Image and std::string by const-reference, not by value. You could also make isClickable() const, i.e. use map::find() instead of map::operator[].
For example (try also with char):
std::vector<bool> calculateBitset(const sf::Image& img)
{
sf::Vector2u imgSize = img.getSize();
std::vector<bool> retVal(imgSize.x * imgSize.y); // allocate directly
for (unsigned int i = 0; i < imgSize.x; ++i) // unsigned, pre-increment (not crucial
for (unsigned int j = 0; j < imgSize.y; ++j) // here, but generally good practice)
retVal[imgSize.x*j + i] = (img.getPixel(i, j).a != 255);
return retVal;
}
-
I did this and in release configuration everything works much better. Also now I use const& every time when possible. But I think it can be optimised further and it may start lagging when thee will be like 3 layers on the map.
And you should make sure you don't copy unnecessarily. For example, pass sf::Image and std::string by const-reference, not by value. You could also make isClickable() const, i.e. use map::find() instead of map::operator[].
Isn't operator[] faster than find()?
TY again!
-
No, find() only performs a lookup. operator[] also inserts an element if it is not found, so the semantics are different.
See also http://en.cppreference.com/w/cpp/container/map