Skip to content

Commit

Permalink
Simplify autotiling code more, loop-based autotile erasing algorithm
Browse files Browse the repository at this point in the history
The non-corner autotile erasing algorithm now uses a loop to perform the same actions as before, just without code duplication.

The `TileMap::autotile_corner` base function has been removed in favour of moving its functionality of placing corners to `autotile()`, where it will be checked whether the tilemap is a corner and the respective actions will be done. The autotile corner tile position offset of `0.5f` is now added by the `TileMap` autotiling functions as well.

Fixes an issue, where autotile erasing empty tiles adjacently to other tiles will not fix-up the existing tiles.
  • Loading branch information
Vankata453 committed Nov 12, 2024
1 parent a31e75e commit 126e60e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 102 deletions.
35 changes: 5 additions & 30 deletions src/editor/overlay_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,40 +182,24 @@ EditorOverlayWidget::input_autotile(const Vector& pos, uint32_t tile)
if (!tilemap || !is_position_inside_tilemap(tilemap, pos)) return;

tilemap->save_state();
tilemap->autotile(static_cast<int>(pos.x), static_cast<int>(pos.y), tile, get_current_autotileset());
tilemap->autotile(pos, tile, get_current_autotileset());
}

void
EditorOverlayWidget::input_autotile_corner(const Vector& corner, uint32_t tile /*, const Vector& override_pos */)
EditorOverlayWidget::input_autotile_erase(const Vector& pos)
{
auto tilemap = m_editor.get_selected_tilemap();
if (!tilemap) return;

// Erase the tile - the autotiling will add the necessary tile after
//if (override_pos != Vector(-1.f, -1.f))
// tilemap->change(static_cast<int>(override_pos.x), static_cast<int>(override_pos.y), 0);

tilemap->save_state();
tilemap->autotile_corner(static_cast<int>(corner.x), static_cast<int>(corner.y),
tile, get_current_autotileset(), true);
}

void
EditorOverlayWidget::input_autotile_erase(const Vector& pos, const Vector& corner_pos)
{
auto tilemap = m_editor.get_selected_tilemap();
if (!tilemap) return;
if (!tilemap || !is_position_inside_tilemap(tilemap, pos)) return;

tilemap->save_state();
tilemap->autotile_erase(pos, corner_pos, get_current_autotileset());
tilemap->autotile_erase(pos, get_current_autotileset());
}

void
EditorOverlayWidget::put_tiles(const Vector& target_tile, TileSelection* tiles)
{
m_editor.get_selected_tilemap()->save_state();

const Vector hovered_corner = target_tile + Vector(0.5f, 0.5f);
Vector add_tile(0.0f, 0.0f);
for (add_tile.x = static_cast<float>(tiles->m_width) - 1.0f; add_tile.x >= 0.0f; add_tile.x--)
{
Expand All @@ -229,18 +213,9 @@ EditorOverlayWidget::put_tiles(const Vector& target_tile, TileSelection* tiles)
if (autotileset)
{
if (tile == 0)
{
input_autotile_erase(target_tile + add_tile, hovered_corner + add_tile);
}
else if (autotileset->is_corner())
{
input_autotile_corner(hovered_corner + add_tile,
tile /*, target_tile + add_tile */);
}
input_autotile_erase(target_tile + add_tile);
else
{
input_autotile(target_tile + add_tile, tile);
}
continue;
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/editor/overlay_widget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ class EditorOverlayWidget final : public Widget
private:
void input_tile(const Vector& pos, uint32_t tile);
void input_autotile(const Vector& pos, uint32_t tile);
void input_autotile_corner(const Vector& corner, uint32_t tile /*, const Vector& override_pos = Vector(-1.f, -1.f) */);
void input_autotile_erase(const Vector& pos, const Vector& corner_pos);
void input_autotile_erase(const Vector& pos);
void put_tiles(const Vector& target_tile, TileSelection* tiles);
void put_next_tiles();
void draw_rectangle();
Expand Down
127 changes: 65 additions & 62 deletions src/object/tilemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -752,42 +752,60 @@ TileMap::change_all(uint32_t oldtile, uint32_t newtile)
}

void
TileMap::autotile(int pos_x, int pos_y, uint32_t tile, AutotileSet* autotileset)
TileMap::autotile(const Vector& pos, uint32_t tile, AutotileSet* autotileset)
{
if (pos_x < 0 || pos_x >= m_width || pos_y < 0 || pos_y >= m_height)
if (!autotileset || !autotileset->is_member(tile))
return;

if (!autotileset || !autotileset->is_member(tile))
if (pos.x < 0.f || pos.x >= static_cast<float>(m_width) ||
pos.y < 0.f || pos.y >= static_cast<float>(m_height))
return;

m_tiles[pos_y*m_width + pos_x] = tile;
if (autotileset->is_corner())
{
const int x = static_cast<int>(pos.x + 0.5f), y = static_cast<int>(pos.y + 0.5f);
if (x < 0 || x >= m_width || y < 0 || y >= m_height)
return;

for (int y = static_cast<int>(pos_y) - 1; y <= static_cast<int>(pos_y) + 1; y++)
autotile_single_corner(x-1, y-1, autotileset, AutotileCornerOperation::ADD_BOTTOM_RIGHT);
autotile_single_corner(x, y-1, autotileset, AutotileCornerOperation::ADD_BOTTOM_LEFT);
autotile_single_corner(x-1, y, autotileset, AutotileCornerOperation::ADD_TOP_RIGHT);
autotile_single_corner(x, y, autotileset, AutotileCornerOperation::ADD_TOP_LEFT);
}
else
{
if (y < 0 || y >= m_height)
continue;
const int pos_x = static_cast<int>(pos.x), pos_y = static_cast<int>(pos.y);
m_tiles[pos_y*m_width + pos_x] = tile;

for (int x = static_cast<int>(pos_x) - 1; x <= static_cast<int>(pos_x) + 1; x++)
for (int y = static_cast<int>(pos_y) - 1; y <= static_cast<int>(pos_y) + 1; y++)
{
if (x < 0 || x >= m_width)
if (y < 0 || y >= m_height)
continue;

if (x != pos_x || y != pos_y)
for (int x = static_cast<int>(pos_x) - 1; x <= static_cast<int>(pos_x) + 1; x++)
{
// Do not allow replacing adjacent tiles if they are not a part of the current autotileset.
const uint32_t current_tile = m_tiles[y*m_width + x];
if (current_tile != 0 && !autotileset->is_member(current_tile))
if (x < 0 || x >= m_width)
continue;
}

autotile_single(x, y, autotileset);
if (x != pos_x || y != pos_y)
{
// Do not allow replacing adjacent tiles if they are not a part of the current autotileset.
const uint32_t current_tile = m_tiles[y*m_width + x];
if (current_tile != 0 && !autotileset->is_member(current_tile))
continue;
}

autotile_single(x, y, autotileset);
}
}
}
}

void
TileMap::autotile_single(int x, int y, AutotileSet* autotileset)
{
// autotile() and autotile_erase() already perform validity checks for x, y and autotileset.

m_tiles[y*m_width + x] = autotileset->get_autotile(m_tiles[y*m_width + x],
autotileset->is_solid(get_tile_id(x-1, y-1)),
autotileset->is_solid(get_tile_id(x , y-1)),
Expand All @@ -802,38 +820,15 @@ TileMap::autotile_single(int x, int y, AutotileSet* autotileset)
}

void
TileMap::autotile_corner(int x, int y, uint32_t tile, AutotileSet* autotileset, bool add)
TileMap::autotile_single_corner(int x, int y, AutotileSet* autotileset, AutotileCornerOperation op)
{
if (x < 0 || x >= m_width || y < 0 || y >= m_height)
return;
// autotile() and autotile_erase() already perform a validity check for autotileset.

if (!autotileset || !autotileset->is_corner() || !autotileset->is_member(tile))
return;

if (add)
{
autotile_corner(x-1, y-1, tile, autotileset, AutotileCornerOperation::ADD_BOTTOM_RIGHT);
autotile_corner(x, y-1, tile, autotileset, AutotileCornerOperation::ADD_BOTTOM_LEFT);
autotile_corner(x-1, y, tile, autotileset, AutotileCornerOperation::ADD_TOP_RIGHT);
autotile_corner(x, y, tile, autotileset, AutotileCornerOperation::ADD_TOP_LEFT);
}
else
{
autotile_corner(x, y, tile, autotileset, AutotileCornerOperation::REMOVE_TOP_LEFT);
autotile_corner(x-1, y, tile, autotileset, AutotileCornerOperation::REMOVE_TOP_RIGHT);
autotile_corner(x, y-1, tile, autotileset, AutotileCornerOperation::REMOVE_BOTTOM_LEFT);
autotile_corner(x-1, y-1, tile, autotileset, AutotileCornerOperation::REMOVE_BOTTOM_RIGHT);
}
}

void
TileMap::autotile_corner(int x, int y, uint32_t tile, AutotileSet* autotileset, AutotileCornerOperation op)
{
if (x < 0 || x >= m_width || y < 0 || y >= m_height)
return;

// If tile is not empty or already of the appropriate tileset, abort
const uint32_t current_tile = m_tiles[y*m_width + x];
// Corner autotiling shouldn't replace existing tiles not from this autotileset.
if (current_tile != 0 && !autotileset->is_member(current_tile))
return;

Expand Down Expand Up @@ -862,30 +857,41 @@ TileMap::autotile_corner(int x, int y, uint32_t tile, AutotileSet* autotileset,
}

void
TileMap::autotile_erase(const Vector& pos, const Vector& corner_pos, AutotileSet* autotileset)
TileMap::autotile_erase(const Vector& pos, AutotileSet* autotileset)
{
if (pos.x < 0.f || pos.x >= static_cast<float>(m_width) ||
pos.y < 0.f || pos.y >= static_cast<float>(m_height))
if (!autotileset)
return;

if (corner_pos.x < 0.f || corner_pos.x >= static_cast<float>(m_width) ||
corner_pos.y < 0.f || corner_pos.y >= static_cast<float>(m_height))
return;

const uint32_t current_tile = m_tiles[static_cast<int>(pos.y)*m_width
+ static_cast<int>(pos.x)];
if (!autotileset || !autotileset->is_member(current_tile))
if (pos.x < 0.f || pos.x >= static_cast<float>(m_width) ||
pos.y < 0.f || pos.y >= static_cast<float>(m_height))
return;

if (autotileset->is_corner())
{
autotile_corner(static_cast<int>(corner_pos.x), static_cast<int>(corner_pos.y),
current_tile, autotileset, false);
const int x = static_cast<int>(pos.x + 0.5f), y = static_cast<int>(pos.y + 0.5f);
if (x < 0 || x >= m_width || y < 0 || y >= m_height)
return;

const uint32_t current_tile = m_tiles[y*m_width + x];
// Allowing empty tiles allows for autotiling when erasing empty tiles adjacently to autotileable tiles.
if (current_tile != 0 && !autotileset->is_member(current_tile))
return;

autotile_single_corner(x, y, autotileset, AutotileCornerOperation::REMOVE_TOP_LEFT);
autotile_single_corner(x-1, y, autotileset, AutotileCornerOperation::REMOVE_TOP_RIGHT);
autotile_single_corner(x, y-1, autotileset, AutotileCornerOperation::REMOVE_BOTTOM_LEFT);
autotile_single_corner(x-1, y-1, autotileset, AutotileCornerOperation::REMOVE_BOTTOM_RIGHT);
}
else
{
const int pos_x = static_cast<int>(pos.x), pos_y = static_cast<int>(pos.y);
m_tiles[pos_y * m_width + pos_x] = 0;

const uint32_t current_tile = m_tiles[pos_y*m_width + pos_x];
// Allowing empty tiles allows for autotiling when erasing empty tiles adjacently to autotileable tiles.
if (current_tile != 0 && !autotileset->is_member(current_tile))
return;

m_tiles[pos_y*m_width + pos_x] = 0;

for (int y = pos_y - 1; y <= pos_y + 1; y++)
{
Expand All @@ -897,16 +903,13 @@ TileMap::autotile_erase(const Vector& pos, const Vector& corner_pos, AutotileSet
if ((x == pos_x && y == pos_y) || x < 0 || x >= m_width)
continue;

const int change_tile = m_tiles[y * m_width + x];
if (change_tile == 0)
const uint32_t change_tile = m_tiles[y*m_width + x];
if (!autotileset->is_member(change_tile))
continue;

if (autotileset->is_member(change_tile))
{
if (m_tiles[pos_y * m_width + pos_x] == 0)
autotile_single(pos_x, pos_y, autotileset);
autotile_single(x, y, autotileset);
}
if (m_tiles[pos_y*m_width + pos_x] == 0)
autotile_single(pos_x, pos_y, autotileset);
autotile_single(x, y, autotileset);
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions src/object/tilemap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,11 @@ class TileMap final : public GameObject,
*/
void change_all(uint32_t oldtile, uint32_t newtile);

/** Puts the correct autotile block at the given position */
void autotile(int x, int y, uint32_t tile, AutotileSet* autotileset);

/** Puts the correct autotile blocks at the tiles around the given corner */
void autotile_corner(int x, int y, uint32_t tile, AutotileSet* autotileset, bool add);
/** Puts the correct autotile blocks at the given position */
void autotile(const Vector& pos, uint32_t tile, AutotileSet* autotileset);

/** Erases in autotile mode */
void autotile_erase(const Vector& pos, const Vector& corner_pos, AutotileSet* autotileset);
void autotile_erase(const Vector& pos, AutotileSet* autotileset);

/** Returns the Autotilesets associated with the given tile */
std::vector<AutotileSet*> get_autotilesets(uint32_t tile) const;
Expand Down Expand Up @@ -273,6 +270,7 @@ class TileMap final : public GameObject,
void update_effective_solid(bool update_manager = true);
void float_channel(float target, float &current, float remaining_time, float dt_sec);

/** Puts the correct single autotile block at the given position */
void autotile_single(int x, int y, AutotileSet* autotileset);

enum class AutotileCornerOperation {
Expand All @@ -286,8 +284,8 @@ class TileMap final : public GameObject,
REMOVE_BOTTOM_RIGHT,
};

/** Puts the correct autotile blocks at the tiles around the given corner */
void autotile_corner(int x, int y, uint32_t tile, AutotileSet* autotileset, AutotileCornerOperation op);
/** Puts the correct autotile blocks at the tiles around the single given corner */
void autotile_single_corner(int x, int y, AutotileSet* autotileset, AutotileCornerOperation op);

void apply_offset_x(int fill_id, int xoffset);
void apply_offset_y(int fill_id, int yoffset);
Expand Down

0 comments on commit 126e60e

Please sign in to comment.