diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java index 4668ce64..68fabce2 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java @@ -1,9 +1,9 @@ package com.bartlomiejpluta.base.engine.world.autotile.manager; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager; import com.bartlomiejpluta.base.engine.error.AppException; import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration; -import com.bartlomiejpluta.base.engine.util.mesh.MeshManager; import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset; import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; import lombok.RequiredArgsConstructor; @@ -19,14 +19,12 @@ import java.util.Map; @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class DefaultAutoTileSetManager implements AutoTileManager { private final TextureManager textureManager; - private final MeshManager meshManager; - private final Map autoTiles = new HashMap<>(); + private final Map tileSets = new HashMap<>(); private final Map assets = new HashMap<>(); private final ProjectConfiguration configuration; @Override public void init() { - // TODO: this.mesh = meshManager.createQuad(1, 1, 0, 0); } @Override @@ -42,9 +40,9 @@ public class DefaultAutoTileSetManager implements AutoTileManager { @Override public AutoTileSet loadObject(String uid) { - var autoTile = autoTiles.get(uid); + var tileSet = tileSets.get(uid); - if (autoTile == null) { + if (tileSet == null) { var asset = assets.get(uid); if (asset == null) { @@ -53,11 +51,12 @@ public class DefaultAutoTileSetManager implements AutoTileManager { var source = configuration.projectFile("autotiles", asset.getSource()); var texture = textureManager.loadTexture(source, asset.getRows() * asset.getLayout().getRows() * 2, asset.getColumns() * asset.getLayout().getColumns() * 2); - autoTile = new AutoTileSet(texture, asset.getRows(), asset.getColumns(), asset.getLayout()); + tileSet = new AutoTileSet(texture, asset.getLayout()); + log.info("Loading auto tile set from assets to cache under the key: [{}]", uid); - autoTiles.put(uid, autoTile); + tileSets.put(uid, tileSet); } - return autoTile; + return tileSet; } } diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java deleted file mode 100644 index 5349f243..00000000 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.bartlomiejpluta.base.engine.world.autotile.model; - -import com.bartlomiejpluta.base.engine.core.gl.object.material.Material; -import com.bartlomiejpluta.base.engine.world.object.Sprite; -import lombok.Getter; -import lombok.NonNull; -import org.joml.Vector2f; -import org.joml.Vector2fc; -import org.joml.Vector2ic; - -@Getter -public class AutoSubTile extends Sprite { - private final Vector2fc tileSpriteSize; - private final Vector2f tileScale = new Vector2f(1, 1); - - public AutoSubTile(AutoTileSet autoTileSet) { - super(Material.textured(autoTileSet.getTexture())); - - tileSpriteSize = getMaterial().getTexture().getSpriteSize(); - - super.setScale(tileSpriteSize.x() * tileScale.x, tileSpriteSize.y() * tileScale.y); - } - - public void recalculate(@NonNull Vector2ic spritePosition) { - setFrame(spritePosition.x(), spritePosition.y()); - } - - @Override - public void setScale(float scale) { - this.tileScale.x = scale; - this.tileScale.y = scale; - super.setScale(tileSpriteSize.x() * scale, tileSpriteSize.y() * scale); - } - - public void setScale(float scaleX, float scaleY) { - this.tileScale.x = scaleX; - this.tileScale.y = scaleY; - super.setScale(tileSpriteSize.x() * scaleX, tileSpriteSize.y() * scaleY); - } - - @Override - public float getScaleX() { - return tileScale.x; - } - - @Override - public void setScaleX(float scaleX) { - this.tileScale.x = scaleX; - super.setScaleX(tileSpriteSize.x() * scaleX); - } - - @Override - public float getScaleY() { - return tileScale.y; - } - - @Override - public void setScaleY(float scaleY) { - this.tileScale.y = scaleY; - super.setScaleY(tileSpriteSize.x() * scaleY); - } -} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java deleted file mode 100644 index c5d5c32a..00000000 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.bartlomiejpluta.base.engine.world.autotile.model; - -import com.bartlomiejpluta.base.api.camera.Camera; -import com.bartlomiejpluta.base.api.screen.Screen; -import com.bartlomiejpluta.base.internal.render.Renderable; -import com.bartlomiejpluta.base.internal.render.ShaderManager; -import lombok.Getter; -import lombok.NonNull; - -public class AutoTile implements Renderable { - private final AutoTileSet autoTileSet; - private final AutoSubTile topLeftSubTile; - private final AutoSubTile topRightSubTile; - private final AutoSubTile bottomLeftSubTile; - private final AutoSubTile bottomRightSubTile; - - @Getter - private int setId; - - public AutoTile(@NonNull AutoTileSet autoTileSet, int setId) { - this.topLeftSubTile = new AutoSubTile(autoTileSet); - this.topRightSubTile = new AutoSubTile(autoTileSet); - this.bottomLeftSubTile = new AutoSubTile(autoTileSet); - this.bottomRightSubTile = new AutoSubTile(autoTileSet); - this.autoTileSet = autoTileSet; - this.setId = setId; - } - - public void setCoordinates(int column, int row) { - var x = column * autoTileSet.getTileSize().x() * 2; - var y = row * autoTileSet.getTileSize().y() * 2; - - topLeftSubTile.setPosition(x, y); - topRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y); - bottomLeftSubTile.setPosition(x, y + autoTileSet.getTileSize().y()); - bottomRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y + autoTileSet.getTileSize().y()); - } - - public void regularTile(Integer setId, int topLeft, int topRight, int bottomLeft, int bottomRight) { - if (setId == null) { - setId = this.setId; - } - - topLeftSubTile.recalculate(autoTileSet.getTopLeftSubTiles()[setId][topLeft]); - topRightSubTile.recalculate(autoTileSet.getTopRightSubTiles()[setId][topRight]); - bottomLeftSubTile.recalculate(autoTileSet.getBottomLeftSubTiles()[setId][bottomLeft]); - bottomRightSubTile.recalculate(autoTileSet.getBottomRightSubTiles()[setId][bottomRight]); - } - - public void islandTile(Integer setId) { - if (setId == null) { - setId = this.setId; - } - - topLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][0]); - topRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][1]); - bottomLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][2]); - bottomRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][3]); - } - - public void shiftTileSet() { - this.setId = (setId + 1) % autoTileSet.getSetsCount(); - } - - @Override - public void render(Screen screen, Camera camera, ShaderManager shaderManager) { - topLeftSubTile.render(screen, camera, shaderManager); - topRightSubTile.render(screen, camera, shaderManager); - bottomLeftSubTile.render(screen, camera, shaderManager); - bottomRightSubTile.render(screen, camera, shaderManager); - } -} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java index 8656ff91..b4348673 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java @@ -4,201 +4,22 @@ import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; import lombok.Getter; import lombok.NonNull; import org.joml.Vector2fc; -import org.joml.Vector2i; import org.joml.Vector2ic; -import java.util.LinkedList; - -// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 +@Getter public class AutoTileSet { - private static final Vector2ic ISLAND_TILE_2x3 = new Vector2i(0, 0); - private static final Vector2ic CROSS_TILE_2x3 = new Vector2i(1, 0); - private static final Vector2ic TOP_LEFT_TILE_2x3 = new Vector2i(0, 1); - private static final Vector2ic TOP_RIGHT_TILE_2x3 = new Vector2i(1, 1); - private static final Vector2ic BOTTOM_LEFT_TILE_2x3 = new Vector2i(0, 2); - private static final Vector2ic BOTTOM_RIGHT_TILE_2x3 = new Vector2i(1, 2); - - private static final Vector2ic TOP_LEFT_TILE_2x2 = new Vector2i(0, 0); - private static final Vector2ic TOP_RIGHT_TILE_2x2 = new Vector2i(1, 0); - private static final Vector2ic BOTTOM_LEFT_TILE_2x2 = new Vector2i(0, 1); - private static final Vector2ic BOTTOM_RIGHT_TILE_2x2 = new Vector2i(1, 1); - - private final int textureSubTilesRows; - private final int textureSubTilesColumns; - - - @Getter - private Vector2ic[][] islandSubTiles; - - @Getter - private Vector2ic[][] topLeftSubTiles; - - @Getter - private Vector2ic[][] topRightSubTiles; - - @Getter - private Vector2ic[][] bottomLeftSubTiles; - - @Getter - private Vector2ic[][] bottomRightSubTiles; - - @Getter private final Texture texture; - - @Getter - private final Vector2fc tileSize; - - @Getter private final AutoTileLayout layout; - private final int rows; - private final int columns; - - @Getter + private final int setsColumns; + private final int setsRows; private final int setsCount; - - public AutoTileSet(@NonNull Texture texture, int rows, int columns, @NonNull AutoTileLayout layout) { + + public AutoTileSet(@NonNull Texture texture, @NonNull AutoTileLayout layout) { this.texture = texture; - this.rows = rows; - this.columns = columns; - this.tileSize = texture.getSpriteSize(); this.layout = layout; - this.setsCount = rows * columns; - this.textureSubTilesRows = layout.getRows() * 2; - this.textureSubTilesColumns = layout.getColumns() * 2; - switch (layout) { - case LAYOUT_2X2 -> init2x2(); - case LAYOUT_2X3 -> init2x3(); - } - } - - private void init2x3() { - var islandSubTiles = new LinkedList(); - var topLeftSubTiles = new LinkedList(); - var topRightSubTiles = new LinkedList(); - var bottomLeftSubTiles = new LinkedList(); - var bottomRightSubTiles = new LinkedList(); - - for (int setId = 0; setId < setsCount; ++setId) { - var crossSubTiles = cutSubTiles(setId, CROSS_TILE_2x3); - var topLeftTileSubTiles = cutSubTiles(setId, TOP_LEFT_TILE_2x3); - var topRightTileSubTiles = cutSubTiles(setId, TOP_RIGHT_TILE_2x3); - var bottomLeftTileSubTiles = cutSubTiles(setId, BOTTOM_LEFT_TILE_2x3); - var bottomRightTileSubTiles = cutSubTiles(setId, BOTTOM_RIGHT_TILE_2x3); - - /* - * Indexes: - * 0 - No connected tiles - * 1 - Left tile is connected - * 2 - Right tile is connected - * 3 - Left and Right tiles are connected - * 4 - Left, Right, and Center tiles are connected. - */ - var tl3 = crossSubTiles[0]; - var tr3 = crossSubTiles[1]; - var bl3 = crossSubTiles[2]; - var br3 = crossSubTiles[3]; - - var tl0 = topLeftTileSubTiles[0]; - var tr2 = topLeftTileSubTiles[1]; - var bl1 = topLeftTileSubTiles[2]; - var br4 = topLeftTileSubTiles[3]; - - var tl1 = topRightTileSubTiles[0]; - var tr0 = topRightTileSubTiles[1]; - var bl4 = topRightTileSubTiles[2]; - var br2 = topRightTileSubTiles[3]; - - var tl2 = bottomLeftTileSubTiles[0]; - var tr4 = bottomLeftTileSubTiles[1]; - var bl0 = bottomLeftTileSubTiles[2]; - var br1 = bottomLeftTileSubTiles[3]; - - var tl4 = bottomRightTileSubTiles[0]; - var tr1 = bottomRightTileSubTiles[1]; - var bl2 = bottomRightTileSubTiles[2]; - var br0 = bottomRightTileSubTiles[3]; - - islandSubTiles.add(cutSubTiles(setId, ISLAND_TILE_2x3)); - topLeftSubTiles.add(new Vector2ic[]{tl0, tl1, tl2, tl3, tl4}); - topRightSubTiles.add(new Vector2ic[]{tr0, tr1, tr2, tr3, tr4}); - bottomLeftSubTiles.add(new Vector2ic[]{bl0, bl1, bl2, bl3, bl4}); - bottomRightSubTiles.add(new Vector2ic[]{br0, br1, br2, br3, br4}); - } - - this.islandSubTiles = islandSubTiles.toArray(new Vector2ic[islandSubTiles.size()][]); - this.topLeftSubTiles = topLeftSubTiles.toArray(new Vector2ic[topLeftSubTiles.size()][]); - this.topRightSubTiles = topRightSubTiles.toArray(new Vector2ic[topRightSubTiles.size()][]); - this.bottomLeftSubTiles = bottomLeftSubTiles.toArray(new Vector2ic[bottomLeftSubTiles.size()][]); - this.bottomRightSubTiles = bottomRightSubTiles.toArray(new Vector2ic[bottomRightSubTiles.size()][]); - } - - private void init2x2() { - var topLeftSubTiles = new LinkedList(); - var topRightSubTiles = new LinkedList(); - var bottomLeftSubTiles = new LinkedList(); - var bottomRightSubTiles = new LinkedList(); - - for (int setId = 0; setId < setsCount; ++setId) { - var topLeftTileSubTiles = cutSubTiles(setId, TOP_LEFT_TILE_2x2); - var topRightTileSubTiles = cutSubTiles(setId, TOP_RIGHT_TILE_2x2); - var bottomLeftTileSubTiles = cutSubTiles(setId, BOTTOM_LEFT_TILE_2x2); - var bottomRightTileSubTiles = cutSubTiles(setId, BOTTOM_RIGHT_TILE_2x2); - - /* - * Indexes: - * 0 - No connected tiles - * 1 - Left tile is connected - * 2 - Right tile is connected - * 3 - Left, Right, and Center tiles are connected. - */ - - var tl0 = topLeftTileSubTiles[0]; - var tr2 = topLeftTileSubTiles[1]; - var bl1 = topLeftTileSubTiles[2]; - var br3 = topLeftTileSubTiles[3]; - - var tl1 = topRightTileSubTiles[0]; - var tr0 = topRightTileSubTiles[1]; - var bl3 = topRightTileSubTiles[2]; - var br2 = topRightTileSubTiles[3]; - - var tl2 = bottomLeftTileSubTiles[0]; - var tr3 = bottomLeftTileSubTiles[1]; - var bl0 = bottomLeftTileSubTiles[2]; - var br1 = bottomLeftTileSubTiles[3]; - - var tl3 = bottomRightTileSubTiles[0]; - var tr1 = bottomRightTileSubTiles[1]; - var bl2 = bottomRightTileSubTiles[2]; - var br0 = bottomRightTileSubTiles[3]; - - topLeftSubTiles.add(new Vector2ic[]{tl0, tl1, tl2, tl3}); - topRightSubTiles.add(new Vector2ic[]{tr0, tr1, tr2, tr3}); - bottomLeftSubTiles.add(new Vector2ic[]{bl0, bl1, bl2, bl3}); - bottomRightSubTiles.add(new Vector2ic[]{br0, br1, br2, br3}); - } - - this.topLeftSubTiles = topLeftSubTiles.toArray(new Vector2ic[topLeftSubTiles.size()][]); - this.topRightSubTiles = topRightSubTiles.toArray(new Vector2ic[topRightSubTiles.size()][]); - this.bottomLeftSubTiles = bottomLeftSubTiles.toArray(new Vector2ic[bottomLeftSubTiles.size()][]); - this.bottomRightSubTiles = bottomRightSubTiles.toArray(new Vector2ic[bottomRightSubTiles.size()][]); - } - - private Vector2ic[] cutSubTiles(int setId, Vector2ic tile) { - var topLeft = getTile(setId, tile.y() * 2, tile.x() * 2); - var topRight = getTile(setId, tile.y() * 2, tile.x() * 2 + 1); - var bottomLeft = getTile(setId, tile.y() * 2 + 1, tile.x() * 2); - var bottomRight = getTile(setId, tile.y() * 2 + 1, tile.x() * 2 + 1); - - return new Vector2ic[]{topLeft, topRight, bottomLeft, bottomRight}; - } - - private Vector2ic getTile(int setId, int row, int column) { - return new Vector2i(((setId / columns) * textureSubTilesRows) + row, ((setId % columns) * textureSubTilesColumns) + column); - } - - public AutoTile createTile(int setId) { - return new AutoTile(this, setId); + this.setsColumns = texture.getColumns() / layout.getColumns() / 2; + this.setsRows = texture.getRows() / layout.getRows() / 2; + this.setsCount = setsRows * setsColumns; } } diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer2x2.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer2x2.java new file mode 100644 index 00000000..7d53d89d --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer2x2.java @@ -0,0 +1,110 @@ +package com.bartlomiejpluta.base.engine.world.map.layer.autotile; + +import com.bartlomiejpluta.base.api.map.model.GameMap; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; + +/* + * 0 1 2 3 + * + * +-------------------+---------+---------+ + * | . | . | + * 0 | TL0 . TR2 | TL1 . TR0 | + * | . | . | + * + . . . . + . . . . + . . . . + . . . . + + * | . | . | + * 1 | BL1 . BR3 | BL3 . BR2 | + * | . | . | + * +---------+---------+---------+---------+ + * | . | . | + * 2 | TL2 . TR3 | TL3 . TR1 | + * | . | . | + * + . . . . + . . . . + . . . . + . . . . + + * | . | . | + * 3 | BL0 . BR1 | BL2 . BR0 | + * | . | . | + * +---------+---------+---------+---------+ + * + * Legend: + * TL - top left + * TR - top right + * BL - bottom left + * BR - bottom right + * 0 - Not connected + * 1 - Left tile connected + * 2 - Right tile connected + * 3 - Left and right tiles connected + * 4 - Left, right and center tiles connected + */ +public class AutoTileLayer2x2 extends BaseAutoTileLayer { + private static final int ROWS = 2; + private static final int COLUMNS = 2; + private static final int SUB_ROWS = ROWS * 2; + private static final int SUB_COLUMNS = COLUMNS * 2; + + // 0 1 2 3 + private static final int[][] TOP_LEFT = {{0, 0}, {2, 0}, {0, 2}, {2, 2}}; + private static final int[][] TOP_RIGHT = {{3, 0}, {3, 2}, {1, 0}, {1, 2}}; + private static final int[][] BOTTOM_LEFT = {{0, 3}, {0, 1}, {2, 3}, {2, 1}}; + private static final int[][] BOTTOM_RIGHT = {{3, 3}, {1, 3}, {3, 1}, {1, 3}}; + + private final int tileSetColumns; + + + public AutoTileLayer2x2(@NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) { + super(map, tileSet, rows, columns, connect, animated, animationDuration); + + this.tileSetColumns = tileSet.getSetsColumns(); + } + + @Override + protected void recalculateTile(int row, int column) { + if (layer[row][column] == EMPTY_TILE) { + return; + } + + var topLeft = 0; + var topRight = 0; + var bottomLeft = 0; + var bottomRight = 0; + + var tile = layer[row][column]; + + // Top + if (row > 0 && (layer[row - 1][column] != EMPTY_TILE && (connect || layer[row - 1][column] == tile))) { + topLeft += 2; + topRight += 1; + } + + // Bottom + if (row < map.getRows() - 1 && (layer[row + 1][column] != EMPTY_TILE && (connect || layer[row + 1][column] == tile))) { + bottomLeft += 1; + bottomRight += 2; + } + + // Left + if (column > 0 && (layer[row][column - 1] != EMPTY_TILE && (connect || layer[row][column - 1] == tile))) { + topLeft += 1; + bottomLeft += 2; + } + + // Right + if (column < map.getColumns() - 1 && (layer[row][column + 1] != EMPTY_TILE && (connect || layer[row][column + 1] == tile))) { + topRight += 2; + bottomRight += 1; + } + + setSubTiles( + tile % tileSetColumns * SUB_COLUMNS, + tile / tileSetColumns * SUB_ROWS, + row, + column, + TOP_LEFT[topLeft], + TOP_RIGHT[topRight], + BOTTOM_LEFT[bottomLeft], + BOTTOM_RIGHT[bottomRight]); + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer3x2.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer3x2.java new file mode 100644 index 00000000..84039c0f --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/AutoTileLayer3x2.java @@ -0,0 +1,138 @@ +package com.bartlomiejpluta.base.engine.world.map.layer.autotile; + +import com.bartlomiejpluta.base.api.map.layer.autotile.AutoTileLayer; +import com.bartlomiejpluta.base.api.map.model.GameMap; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; + +/* + * 0 1 2 3 + * + * +-------------------+---------+---------+ + * | | . | + * 0 | | TL3 . TR3 | + * | | . | + * | ISLAND + . . . . + . . . . + + * | | . | + * 1 | | BL3 . BR3 | + * | | . | + * +-------------------+---------+---------+ + * | . | . | + * 2 | TL0 . TR2 | TL1 . TR0 | + * | . | . | + * + . . . . + . . . . + . . . . + . . . . + + * | . | . | + * 3 | BL1 . BR4 | BL4 . BR2 | + * | . | . | + * +---------+---------+---------+---------+ + * | . | . | + * 4 | TL2 . TR4 | TL4 . TR1 | + * | . | . | + * + . . . . + . . . . + . . . . + . . . . + + * | . | . | + * 5 | BL0 . BR1 | BL2 . BR0 | + * | . | . | + * +---------+---------+---------+---------+ + * + * Legend: + * TL - top left + * TR - top right + * BL - bottom left + * BR - bottom right + * 0 - Not connected + * 1 - Left tile connected + * 2 - Right tile connected + * 3 - Left and right tiles connected + * 4 - Left, right and center tiles connected + */ +public class AutoTileLayer3x2 extends BaseAutoTileLayer implements AutoTileLayer { + private static final int ROWS = 3; + private static final int COLUMNS = 2; + private static final int SUB_ROWS = ROWS * 2; + private static final int SUB_COLUMNS = COLUMNS * 2; + + // 0 1 2 3 4 + private static final int[][] TOP_LEFT = {{0, 2}, {2, 2}, {0, 4}, {2, 0}, {2, 4}}; + private static final int[][] TOP_RIGHT = {{3, 2}, {3, 4}, {1, 2}, {3, 0}, {1, 4}}; + private static final int[][] BOTTOM_LEFT = {{0, 5}, {0, 3}, {2, 5}, {2, 1}, {2, 3}}; + private static final int[][] BOTTOM_RIGHT = {{3, 5}, {1, 5}, {3, 3}, {3, 1}, {1, 3}}; + + private final int tileSetColumns; + + public AutoTileLayer3x2(@NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) { + super(map, tileSet, rows, columns, connect, animated, animationDuration); + + this.tileSetColumns = tileSet.getSetsColumns(); + } + + @Override + protected void recalculateTile(int row, int column) { + if (layer[row][column] == EMPTY_TILE) { + return; + } + + byte topLeft = 0; + byte topRight = 0; + byte bottomLeft = 0; + byte bottomRight = 0; + + var tile = layer[row][column]; + + // Top + if (row > 0 && (layer[row - 1][column] != EMPTY_TILE && (connect || layer[row - 1][column] == tile))) { + topLeft += 2; + topRight += 1; + } + + // Bottom + if (row < map.getRows() - 1 && (layer[row + 1][column] != EMPTY_TILE && (connect || layer[row + 1][column] == tile))) { + bottomLeft += 1; + bottomRight += 2; + } + + // Left + if (column > 0 && (layer[row][column - 1] != EMPTY_TILE && (connect || layer[row][column - 1] == tile))) { + topLeft += 1; + bottomLeft += 2; + } + + // Right + if (column < map.getColumns() - 1 && (layer[row][column + 1] != EMPTY_TILE && (connect || layer[row][column + 1] == tile))) { + topRight += 2; + bottomRight += 1; + } + + // Top left + if (row > 0 && column > 0 && (layer[row - 1][column - 1] != EMPTY_TILE && (connect || layer[row - 1][column - 1] == tile)) && topLeft == 3) { + topLeft = 4; + } + + // Top right + if (row > 0 && column < map.getColumns() - 1 && (layer[row - 1][column + 1] != EMPTY_TILE && (connect || layer[row - 1][column + 1] == tile)) && topRight == 3) { + topRight = 4; + } + + // Bottom left + if (row < map.getRows() - 1 && column > 0 && (layer[row + 1][column - 1] != EMPTY_TILE && (connect || layer[row + 1][column - 1] == tile)) && bottomLeft == 3) { + bottomLeft = 4; + } + + // Bottom right + if (row < map.getRows() - 1 && column < map.getColumns() - 1 && (layer[row + 1][column + 1] != EMPTY_TILE && (connect || layer[row + 1][column + 1] == tile)) && bottomRight == 3) { + bottomRight = 4; + } + + setSubTiles( + tile % tileSetColumns * SUB_COLUMNS, + tile / tileSetColumns * SUB_ROWS, + row, + column, + TOP_LEFT[topLeft], + TOP_RIGHT[topRight], + BOTTOM_LEFT[bottomLeft], + BOTTOM_RIGHT[bottomRight]); + } +} \ No newline at end of file diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/BaseAutoTileLayer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/BaseAutoTileLayer.java new file mode 100644 index 00000000..b6ab9e5b --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/BaseAutoTileLayer.java @@ -0,0 +1,117 @@ +package com.bartlomiejpluta.base.engine.world.map.layer.autotile; + +import com.bartlomiejpluta.base.api.camera.Camera; +import com.bartlomiejpluta.base.api.map.model.GameMap; +import com.bartlomiejpluta.base.api.screen.Screen; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer; +import com.bartlomiejpluta.base.engine.world.map.layer.tile.ChunkManager; +import com.bartlomiejpluta.base.internal.render.ShaderManager; +import lombok.NonNull; +import org.joml.Vector2f; + +import static java.util.Arrays.fill; +import static java.util.Arrays.stream; + +// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 +public abstract class BaseAutoTileLayer extends BaseLayer { + protected static final int EMPTY_TILE = -1; + + protected final int[][] layer; + protected final ChunkManager chunkManager; + protected final boolean connect; + protected final boolean animated; + protected final double animationDuration; + private final int setsCount; + + private float accumulator = 0f; + private boolean dirty = true; + + public BaseAutoTileLayer(@NonNull GameMap map, @NonNull AutoTileSet tileSet, int rows, int columns, boolean connect, boolean animated, double animationDuration) { + super(map); + this.layer = new int[rows][columns]; + this.connect = connect; + this.animated = animated; + this.animationDuration = animationDuration; + this.setsCount = tileSet.getSetsCount(); + + this.chunkManager = new ChunkManager( + tileSet.getTexture(), + rows * 4, + columns * 4, + new Vector2f(tileSet.getTexture().getSpriteSize()).div(2) + ); + + stream(layer).forEach(tiles -> fill(tiles, EMPTY_TILE)); + } + + public void setTile(int row, int column, int setId) { + layer[row][column] = setId; + dirty = true; + } + + public void clearTile(int row, int column) { + layer[row][column] = EMPTY_TILE; + dirty = true; + } + + public void recalculate() { + for (int row = 0; row < map.getRows(); ++row) { + for (int column = 0; column < map.getColumns(); ++column) { + recalculateTile(row, column); + } + } + + dirty = false; + } + + protected abstract void recalculateTile(int row, int column); + + protected void setSubTiles(int tileSetColumnOffset, int tileSetRowOffset, int row, int column, int[] topLeft, int[] topRight, int[] bottomLeft, int[] bottomRight) { + var y = row * 4; + var x = column * 4; + + chunkManager.upsertTile(y, x, topLeft[0] + tileSetColumnOffset, topLeft[1] + tileSetRowOffset); + chunkManager.upsertTile(y, x + 2, topRight[0] + tileSetColumnOffset, topRight[1] + tileSetRowOffset); + chunkManager.upsertTile(y + 2, x, bottomLeft[0] + tileSetColumnOffset, bottomLeft[1] + tileSetRowOffset); + chunkManager.upsertTile(y + 2, x + 2, bottomRight[0] + tileSetColumnOffset, bottomRight[1] + tileSetRowOffset); + } + + private void nextAnimationFrame() { + for (int row = 0; row < map.getRows(); ++row) { + for (int column = 0; column < map.getColumns(); ++column) { + var tile = layer[row][column]; + if (tile != EMPTY_TILE) { + layer[row][column] = (tile + 1) % setsCount; + } + } + } + + dirty = true; + } + + @Override + public void update(float dt) { + super.update(dt); + + if (animated) { + accumulator += dt; + + if (accumulator > animationDuration) { + nextAnimationFrame(); + accumulator = 0; + } + } + } + + @Override + public void render(Screen screen, Camera camera, ShaderManager shaderManager) { + if (dirty) { + recalculate(); + } + + chunkManager.render(screen, camera, shaderManager); +// super.render(screen, camera, shaderManager); + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java deleted file mode 100644 index 69e8046c..00000000 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.bartlomiejpluta.base.engine.world.map.layer.autotile; - -import com.bartlomiejpluta.base.api.camera.Camera; -import com.bartlomiejpluta.base.api.map.layer.autotile.AutoTileLayer; -import com.bartlomiejpluta.base.api.map.model.GameMap; -import com.bartlomiejpluta.base.api.screen.Screen; -import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTile; -import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; -import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer; -import com.bartlomiejpluta.base.internal.render.ShaderManager; -import lombok.NonNull; - -import java.util.Arrays; - -// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 -public class DefaultAutoTileLayer extends BaseLayer implements AutoTileLayer { - private final AutoTileSet autoTileSet; - private final AutoTile[][] layer; - private final boolean animated; - private final double animationDuration; - private final boolean connect; - private double accumulator; - - public DefaultAutoTileLayer(@NonNull GameMap map, @NonNull AutoTileSet autoTileSet, int rows, int columns, boolean animated, double animationDuration, boolean connect) { - super(map); - this.autoTileSet = autoTileSet; - this.animated = animated; - this.animationDuration = animationDuration; - this.connect = connect; - layer = new AutoTile[rows][columns]; - Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null)); - } - - public void setTile(int row, int column, int setId) { - var tile = autoTileSet.createTile(setId); - tile.setCoordinates(column, row); - layer[row][column] = tile; - } - - public void clearTile(int row, int column) { - layer[row][column] = null; - } - - public void recalculate(Integer setId) { - for (int row = 0; row < map.getRows(); ++row) { - for (int column = 0; column < map.getColumns(); ++column) { - recalculateTile(setId, row, column); - } - } - } - - private void recalculateTile(Integer setId, int row, int column) { - switch(autoTileSet.getLayout()) { - case LAYOUT_2X2 -> recalculateTile2x2(setId, row, column); - case LAYOUT_2X3 -> recalculateTile2x3(setId, row, column); - } - } - - private void recalculateTile2x2(Integer setId, int row, int column) { - if (layer[row][column] == null) { - return; - } - - var topLeft = 0; - var topRight = 0; - var bottomLeft = 0; - var bottomRight = 0; - - var tile = layer[row][column]; - var centerSetId = tile.getSetId(); - - if (animated) { - tile.shiftTileSet(); - } - - // Top - if (row > 0 && (layer[row - 1][column] != null && (connect || layer[row - 1][column].getSetId() == centerSetId))) { - topLeft += 2; - topRight += 1; - } - - // Bottom - if (row < map.getRows() - 1 && (layer[row + 1][column] != null && (connect || layer[row + 1][column].getSetId() == centerSetId))) { - bottomLeft += 1; - bottomRight += 2; - } - - // Left - if (column > 0 && (layer[row][column - 1] != null && (connect || layer[row][column - 1].getSetId() == centerSetId))) { - topLeft += 1; - bottomLeft += 2; - } - - // Right - if (column < map.getColumns() - 1 && (layer[row][column + 1] != null && (connect || layer[row][column + 1].getSetId() == centerSetId))) { - topRight += 2; - bottomRight += 1; - } - - tile.regularTile(setId, topLeft, topRight, bottomLeft, bottomRight); - } - - private void recalculateTile2x3(Integer setId, int row, int column) { - if (layer[row][column] == null) { - return; - } - - var topLeft = 0; - var topRight = 0; - var bottomLeft = 0; - var bottomRight = 0; - - var tile = layer[row][column]; - var centerSetId = tile.getSetId(); - - if (animated) { - tile.shiftTileSet(); - } - - // Top - if (row > 0 && (layer[row - 1][column] != null && (connect || layer[row - 1][column].getSetId() == centerSetId))) { - topLeft += 2; - topRight += 1; - } - - // Bottom - if (row < map.getRows() - 1 && (layer[row + 1][column] != null && (connect || layer[row + 1][column].getSetId() == centerSetId))) { - bottomLeft += 1; - bottomRight += 2; - } - - // Left - if (column > 0 && (layer[row][column - 1] != null && (connect || layer[row][column - 1].getSetId() == centerSetId))) { - topLeft += 1; - bottomLeft += 2; - } - - // Right - if (column < map.getColumns() - 1 && (layer[row][column + 1] != null && (connect || layer[row][column + 1].getSetId() == centerSetId))) { - topRight += 2; - bottomRight += 1; - } - - // Top left - if (row > 0 && column > 0 && (layer[row - 1][column - 1] != null && (connect || layer[row - 1][column - 1].getSetId() == centerSetId)) && topLeft == 3) { - topLeft = 4; - } - - // Top right - if (row > 0 && column < map.getColumns() - 1 && (layer[row - 1][column + 1] != null && (connect || layer[row - 1][column + 1].getSetId() == centerSetId)) && topRight == 3) { - topRight = 4; - } - - // Bottom left - if (row < map.getRows() - 1 && column > 0 && (layer[row + 1][column - 1] != null && (connect || layer[row + 1][column - 1].getSetId() == centerSetId)) && bottomLeft == 3) { - bottomLeft = 4; - } - - // Bottom right - if (row < map.getRows() - 1 && column < map.getColumns() - 1 && (layer[row + 1][column + 1] != null && (connect || layer[row + 1][column + 1].getSetId() == centerSetId)) && bottomRight == 3) { - bottomRight = 4; - } - - if (topLeft == 0 && topRight == 0 && bottomLeft == 0 && bottomRight == 0) { - tile.islandTile(setId); - return; - } - - tile.regularTile(setId, topLeft, topRight, bottomLeft, bottomRight); - } - - @Override - public void update(float dt) { - super.update(dt); - - if (animated) { - accumulator += dt; - - if (accumulator > animationDuration) { - accumulator = 0; - recalculate(null); - } - } - } - - @Override - public void render(Screen screen, Camera camera, ShaderManager shaderManager) { - for (var row : layer) { - for (var tile : row) { - if (tile != null) { - tile.render(screen, camera, shaderManager); - } - } - } - - super.render(screen, camera, shaderManager); - } -} \ No newline at end of file diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/ChunkManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/ChunkManager.java index b004d555..4f025535 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/ChunkManager.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/ChunkManager.java @@ -6,6 +6,8 @@ import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; import com.bartlomiejpluta.base.internal.gc.Disposable; import com.bartlomiejpluta.base.internal.render.Renderable; import com.bartlomiejpluta.base.internal.render.ShaderManager; +import lombok.NonNull; +import org.joml.Vector2fc; import java.util.HashMap; import java.util.Map; @@ -25,12 +27,20 @@ public class ChunkManager implements Renderable, Disposable { this(tileSet, rows, columns, DEFAULT_CHUNK_SIZE); } + public ChunkManager(Texture tileSet, int rows, int columns, Vector2fc tileSize) { + this(tileSet, rows, columns, tileSize, DEFAULT_CHUNK_SIZE); + } + public ChunkManager(Texture tileSet, int rows, int columns, int chunkSize) { + this(tileSet, rows, columns, tileSet.getSpriteSize(), chunkSize); + } + + public ChunkManager(@NonNull Texture tileSet, int rows, int columns, @NonNull Vector2fc tileSize, int chunkSize) { this.tileSet = tileSet; this.mapRows = rows; this.mapColumns = columns; - this.tileWidth = tileSet.getSpriteSize().x(); - this.tileHeight = tileSet.getSpriteSize().y(); + this.tileWidth = tileSize.x(); + this.tileHeight = tileSize.y(); this.chunkSize = chunkSize; } @@ -63,6 +73,21 @@ public class ChunkManager implements Renderable, Disposable { return chunk; } + public void setTile(int row, int column, int tileX, int tileY) { + if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) { + throw new IllegalArgumentException("Tile coordinates out of bounds"); + } + + clearTile(row, column); + + var chunk = getOrCreateChunk(row, column); + var chunkKey = getChunkKey(row, column); + var localTileKey = getLocalTileKey(row, column); + + var quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileX, tileY); + + chunkTileIds.get(chunkKey).put(localTileKey, quadId); + } public void setTile(int row, int column, int tileId) { if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) { @@ -80,6 +105,33 @@ public class ChunkManager implements Renderable, Disposable { chunkTileIds.get(chunkKey).put(localTileKey, quadId); } + public void updateTile(int row, int column, int tileId) { + var chunk = getOrCreateChunk(row, column); + var chunkKey = getChunkKey(row, column); + var localTileKey = getLocalTileKey(row, column); + var quadId = chunkTileIds.get(chunkKey).get(localTileKey); + chunk.updateTile(quadId, tileId); + } + + public void upsertTile(int row, int column, int tileX, int tileY) { + if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) { + throw new IllegalArgumentException("Tile coordinates out of bounds"); + } + + var chunk = getOrCreateChunk(row, column); + var chunkKey = getChunkKey(row, column); + var localTileKey = getLocalTileKey(row, column); + var tileMap = chunkTileIds.get(chunkKey); + var quadId = tileMap.get(localTileKey); + + if (quadId == null) { + quadId = chunk.addTile(column % chunkSize * (int) this.tileHeight, row % chunkSize * (int) this.tileWidth, tileX, tileY); + chunkTileIds.get(chunkKey).put(localTileKey, quadId); + } else { + chunk.updateTile(quadId, tileX, tileY); + } + } + public void clearTile(int row, int column) { if (row < 0 || row >= mapRows || column < 0 || column >= mapColumns) { return; diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/TileChunk.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/TileChunk.java index 03b7cfa0..49c4568a 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/TileChunk.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/tile/TileChunk.java @@ -35,10 +35,22 @@ public class TileChunk extends Model implements Placeable, Renderable, Disposabl this.originY = originY; } + public int addTile(int x, int y, int tileX, int tileY) { + return mesh.addQuad(template, x, y, tileSet.getTextureCoordinates(tileX, tileY)); + } + public int addTile(int x, int y, int tileId) { return mesh.addQuad(template, x, y, tileSet.getTextureCoordinates(tileId)); } + public void updateTile(int quadId, int tileX, int tileY) { + mesh.setQuadTextureCoordinates(quadId, tileSet.getTextureCoordinates(tileX, tileY)); + } + + public void updateTile(int quadId, int tileId) { + mesh.setQuadTextureCoordinates(quadId, tileSet.getTextureCoordinates(tileId)); + } + public void removeTile(int quadId) { mesh.removeQuad(quadId); } diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java index 2a472d40..ca42a8c2 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java @@ -12,10 +12,12 @@ import com.bartlomiejpluta.base.api.map.layer.object.PassageAbility; import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer; import com.bartlomiejpluta.base.api.map.model.GameMap; import com.bartlomiejpluta.base.api.screen.Screen; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName; import com.bartlomiejpluta.base.engine.util.mesh.MeshManager; import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; -import com.bartlomiejpluta.base.engine.world.map.layer.autotile.DefaultAutoTileLayer; +import com.bartlomiejpluta.base.engine.world.map.layer.autotile.AutoTileLayer2x2; +import com.bartlomiejpluta.base.engine.world.map.layer.autotile.AutoTileLayer3x2; import com.bartlomiejpluta.base.engine.world.map.layer.color.DefaultColorLayer; import com.bartlomiejpluta.base.engine.world.map.layer.image.DefaultImageLayer; import com.bartlomiejpluta.base.engine.world.map.layer.object.DefaultObjectLayer; @@ -139,8 +141,15 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap { return layer; } - public DefaultAutoTileLayer createAutoTileLayer(@NonNull AutoTileSet autoTileSet, boolean animated, double animationDuration, boolean connect) { - var layer = new DefaultAutoTileLayer(this, autoTileSet, rows, columns, animated, animationDuration, connect); + public AutoTileLayer3x2 createAutoTileLayer3x2(@NonNull AutoTileSet tileSet, boolean animated, double animationDuration, boolean connect) { + var layer = new AutoTileLayer3x2(this, tileSet, rows, columns, connect, animated, animationDuration); + layers.add(layer); + + return layer; + } + + public AutoTileLayer2x2 createAutoTileLayer2x2(@NonNull AutoTileSet tileSet, boolean animated, double animationDuration, boolean connect) { + var layer = new AutoTileLayer2x2(this, tileSet, rows, columns, connect, animated, animationDuration); layers.add(layer); return layer; diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java index ba84138b..c2852351 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java @@ -73,21 +73,19 @@ public class ProtobufMapDeserializer extends MapDeserializer { var animationDuration = proto.getAutoTileLayer().getAnimationDuration(); var connect = proto.getAutoTileLayer().getConnect(); - var layer = map.createAutoTileLayer(autoTileSet, animated, animationDuration, connect); + var layer = switch(autoTileSet.getLayout()) { + case LAYOUT_2X3 -> map.createAutoTileLayer3x2(autoTileSet, animated, animationDuration, connect); + case LAYOUT_2X2 -> map.createAutoTileLayer2x2(autoTileSet, animated, animationDuration, connect); + }; + var columns = map.getColumns(); var tiles = proto.getAutoTileLayer().getTilesList(); for (var i = 0; i < tiles.size(); ++i) { var tile = tiles.get(i); - if (tile == 0) { - layer.clearTile(i / columns, i % columns); - } else { - layer.setTile(i / columns, i % columns, tile - 1); - } + layer.setTile(i / columns, i % columns, tile - 1); } - - layer.recalculate(null); } private void deserializeObjectLayer(DefaultGameMap map, GameMapProto.Layer proto) {