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:
[![][1]][1]
These hills can form complicated structures for things like mountains:[![][2]][2]
I also want to be able to select tiles with 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: ###
1. [This question][3]. The proposed solution does not work because slopes may have different silhouettes so you can't simplify every tile into lines with same angles
2. [This solution][4]. As I understand, I'd have to create a second rectangular texture around each tile and use color data to represent central tile and its neighbours, but I don't think it would work well in my case because tile can be drawn far from its actual position due to height or it can be completely covered by a large hill in front of it.
### The solution I've done by myself: ###
**Short version:**
I split my map into small chunks 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 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 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 create 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]};
}
```
### Questions: ###
1. Is it possible to use all 4 channels of a color? Current implementation limits map size to 256x256. My initial thought was to treat a single pixel as a 32-bit number, but SDL uses alpha channel for blending. Basically, I need to to overwrite destination with the color and alpha from the source if the alpha in the source pixel is not 0, is that possible to achieve with `SDL_ComposeCustomBlendMode()`?
2. If its impossible to use alpha channel, is it possible to efficiently use 8 bits from blue channel? If I could do it, I could make maps with the size up to 4096x4096, which is probably enough for me, but c++ doesn't have built-in 24- or 12-bit types and I'm not sure how to use 24 bits in this case.
3. Is this the optimal solution in general or am I overcomplicating things?
[1]: https://i.sstatic.net/pbSq6.png
[2]: https://i.sstatic.net/Enuns.jpg
[3]: https://stackoverflow.com/questions/21842814/mouse-position-to-isometric-tile-including-height
[4]: https://www.gamedev.net/reference/articles/article2026.asp