I want to implement an isometric map with rectangular tiles similar to how it was in simcity 2000, where tiles could have different heights and tiles with different heights were connected by slopes. Here's an example of a hill in this game:
These hills can form complicated structures for things like mountains:
I also want to be able to select tiles with the mouse. I used this solution for a flat map:
auto realMapPos = worldPos_; realMapPos.x -= m_tileSize.x / 2.0f; realMapPos.y -= m_tileSize.y / 2.0f; realMapPos -= m_origin; Vector2<int> res; res.x = round((realMapPos.x / m_tileSize.x) - (realMapPos.y / m_tileSize.y)); res.y = round((realMapPos.x / m_tileSize.x) + (realMapPos.y / m_tileSize.y)); But it doesn't work for tiles with height.
Solutions I've found:
This question. The proposed solution does not work because slopes may have different silhouettes so you can't simplify every tile into lines with the same angles.
This solution. As I understand, I'd have to create a second rectangular texture around each tile and use color data to represent the central tile and its neighbours. Still, I don't think it would work well in my case because the tile can be drawn far from its actual position due to height or a large hill can completely cover it in front of it.
The solution I've done by myself:
Short version: I split my map into small chunks of 100x100 pixels and use their red and green channels to store info about respective tiles.
Full version: I have these fields in my TownLevel class with a map:
std::unique_ptr<Texture> m_mouseMap; // Full map std::vector<std::unique_ptr<Texture>> m_mouseMapChunks; // Array of small chunks Vector2<int> m_mouseChunkSize = {100, 100}; // Size of a single chunk Vector2<int> m_mouseChunkMapSize; // Number of chunks along each axis I render an entire map to one texture and split it into chunks. I can't use the full map by itself because SDL_RenderReadPixels() works too slowly with large textures. The full map is not necessary and can break my code on other systems due to SDL_Texture size limitations, I'll remove it later. Also, I use a Texture class for all textures. I created a full map and chunks in the constructor:
// Make full mouse map texture m_mouseMap = std::make_unique<Texture>(application_->getRenderer()->createTexture(size_.x, size_.y)); //m_mouseMap = std::make_unique<Texture>(application_->getRenderer()->createTexture(1000, 1000)); if (m_mouseMap->getSprite() == nullptr) { std::cout << "Failed to create mouse map\n"; } else { SDL_SetTextureBlendMode(m_mouseMap->getSprite(), SDL_BLENDMODE_BLEND); renderer.setRenderTarget(m_mouseMap->getSprite()); renderer.fillRenderer({0, 0, 0, 0}); renderer.setRenderTarget(nullptr); } // Split map into small chunks m_mouseChunkMapSize.x = size_.x / m_mouseChunkSize.x; m_mouseChunkMapSize.y = size_.y / m_mouseChunkSize.y; // Create textures for chunks for (int i = 0; i < m_mouseChunkMapSize.x * m_mouseChunkMapSize.y; ++i) { auto chunk = std::make_unique<Texture>(application_->getRenderer()->createTexture(m_mouseChunkSize.x, m_mouseChunkSize.y)); SDL_SetTextureBlendMode(chunk->getSprite(), SDL_BLENDMODE_BLEND); renderer.setRenderTarget(chunk->getSprite()); renderer.fillRenderer({0, 0, 0, 0}); m_mouseMapChunks.push_back(std::move(chunk)); } renderer.setRenderTarget(nullptr); Texture class has a method to generate a pure white version of a texture:
void generateWhiteTexture(Renderer &renderer_) { if (whiteTex != nullptr) return; std::cout << "Created white " << w << " " << h << std::endl; whiteTex = renderer_.createTexture(w, h); auto blendmode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDOPERATION_MAXIMUM, SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ZERO, SDL_BLENDOPERATION_ADD); renderer_.setRenderTarget(whiteTex); renderer_.fillRenderer({255, 255, 255, 0}); // Every pixel from original texture turns into {255 * a, 255 * a, 255 * a, a} (a is alpha) SDL_SetTextureBlendMode(tex, blendmode); renderer_.renderTexture(tex, 0, 0, w, h); SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); SDL_SetTextureBlendMode(whiteTex, SDL_BLENDMODE_BLEND); renderer_.setRenderTarget(nullptr); } Tile class has a method that renders this white texture with SDL_SetTextureColorMod() and SDL_SetTextureAlphaMod():
void Tile::renderFilled(Uint8 r, Uint8 g, Uint8 b, Uint8 alpha, Camera &camera_) { auto &renderer = *m_application->getRenderer(); SDL_SetTextureColorMod(m_tex->getWhiteSprite(), r, g, b); SDL_SetTextureAlphaMod(m_tex->getWhiteSprite(), alpha); renderer.renderTexture(m_tex->getWhiteSprite(), m_pos.x, m_pos.y, m_renderSize.x, m_renderSize.y, camera_, 0, SDL_FLIP_NONE); } TownLevel class uses this method sets these parameters to tile coordinates and generates mouse map:
// Generate full map renderer.setRenderTarget(m_mouseMap->getSprite()); for (auto *ent : m_entities) { auto pos = ent->getTilePos(); Uint8 colors[4] = {(Uint8)pos.y, (Uint8)pos.x, 255, 255}; ent->renderFilled(colors[0], colors[1], colors[2], colors[3], m_camera); } renderer.setRenderTarget(nullptr); // Generate chunks for (int y = 0; y < m_mouseChunkMapSize.y; ++y) { for (int x = 0; x < m_mouseChunkMapSize.x; ++x) { auto *chunk = m_mouseMapChunks[y * m_mouseChunkMapSize.x + x].get(); renderer.setRenderTarget(chunk->getSprite()); renderer.renderTexture(m_mouseMap->getSprite(), -m_mouseChunkSize.x * x, -m_mouseChunkSize.y * y); } } renderer.setRenderTarget(nullptr); And I use this method to calculate the current chunk of the mouse and to get data from the pixel:
Vector2<int> TownLevel::worldPosToTile(const Vector2<float> worldPos_) { auto &renderer = *m_application->getRenderer(); Uint8 colors[4]; Uint32 *data = (Uint32*)colors; // X and Y of chunk Vector2<int> chunkPos = {(int)worldPos_.x / m_mouseChunkSize.x, (int)worldPos_.y / m_mouseChunkSize.y}; // Real position of top left corner of chunk Vector2<float> chunkOrigin = chunkPos.mulComponents(m_mouseChunkSize); std::cout << "Chunk " << chunkPos << " (" << chunkOrigin << ")\n"; // Rect that selects single pixel in worldPos_ position in chunk SDL_Rect rect; rect.x = worldPos_.x - chunkOrigin.x; rect.y = worldPos_.y - chunkOrigin.y; rect.w = 1; rect.h = 1; // Read pixel from chunk renderer.setRenderTarget(m_mouseMapChunks[chunkPos.y * m_mouseChunkMapSize.x + chunkPos.x]->getSprite()); SDL_RenderReadPixels(renderer.getRenderer(), &rect, SDL_PIXELFORMAT_RGBA8888, (void*)data, 1); std::cout << (int)colors[0] << " " << (int)colors[1] << " " << (int)colors[2] << " " << (int)colors[3] << std::endl; renderer.setRenderTarget(nullptr); // Return data from pixel return Vector2{(int)colors[2], (int)colors[3]}; } Question:
Since I use only 2 channels of SDL_PIXELFORMAT_RGBA8888 pixel instead of 3 or 4, I limit size of the map to 2^16 instead of 2^24 or 2^32. How can I use pixel data more efficiently?

