## Functions for automatically generating terrains for an atlas. class_name TerrainPreset ## Maps a Neighborhood to a Topology. const NEIGHBORHOOD_TOPOLOGIES := { TerrainDual.Neighborhood.SQUARE: Topology.SQUARE, TerrainDual.Neighborhood.ISOMETRIC: Topology.SQUARE, TerrainDual.Neighborhood.TRIANGLE_HORIZONTAL: Topology.TRIANGLE, TerrainDual.Neighborhood.TRIANGLE_VERTICAL: Topology.TRIANGLE, } ## Determines the available Terrain presets for a certain Atlas. enum Topology { SQUARE, TRIANGLE, } ## Maps a Neighborhood to a preset of the specified name. static func neighborhood_preset( neighborhood: TerrainDual.Neighborhood, preset_name: String = 'Standard' ) -> Dictionary: var topology: Topology = NEIGHBORHOOD_TOPOLOGIES[neighborhood] # TODO: test when the preset doesn't exist var available_presets = PRESETS[topology] if preset_name not in available_presets: return {'size': Vector2i.ONE, 'layers': []} var out: Dictionary = available_presets[preset_name].duplicate(true) # All Horizontal neighborhoods can be transposed to Vertical if neighborhood == TerrainDual.Neighborhood.TRIANGLE_VERTICAL: out.size = Util.transpose_vec(out.size) out.fg = Util.transpose_vec(out.fg) out.bg = Util.transpose_vec(out.bg) for seq in out.layers: for i in seq.size(): seq[i] = Util.transpose_vec(seq[i]) return out ## Contains all of the builtin Terrain presets for each topology const PRESETS := { Topology.SQUARE: { 'Standard': { 'size': Vector2i(4, 4), 'bg': Vector2i(0, 3), 'fg': Vector2i(2, 1), 'layers': [ [ # [] Vector2i(0, 3), Vector2i(3, 3), Vector2i(0, 2), Vector2i(1, 2), Vector2i(0, 0), Vector2i(3, 2), Vector2i(2, 3), Vector2i(3, 1), Vector2i(1, 3), Vector2i(0, 1), Vector2i(1, 0), Vector2i(2, 2), Vector2i(3, 0), Vector2i(2, 0), Vector2i(1, 1), Vector2i(2, 1), ], ], }, }, Topology.TRIANGLE: { 'Standard': { 'size': Vector2i(4, 4), 'bg': Vector2i(0, 0), 'fg': Vector2i(0, 2), 'layers': [ [ # v Vector2i(0, 1), Vector2i(2, 3), Vector2i(3, 1), Vector2i(1, 3), Vector2i(1, 1), Vector2i(3, 3), Vector2i(2, 1), Vector2i(0, 3), ], [ # ^ Vector2i(0, 0), Vector2i(2, 2), Vector2i(3, 0), Vector2i(1, 2), Vector2i(1, 0), Vector2i(3, 2), Vector2i(2, 0), Vector2i(0, 2), ], ] }, # Old template. # a bit inconvenient to use for Brick (Half-Off Square) tilesets. 'Winged': { 'size': Vector2i(4, 4), 'bg': Vector2i(0, 0), 'fg': Vector2i(0, 2), 'layers': [ [ # v Vector2i(0, 1), Vector2i(2, 1), Vector2i(3, 1), Vector2i(1, 3), Vector2i(1, 1), Vector2i(3, 3), Vector2i(2, 3), Vector2i(0, 3), ], [ # ^ Vector2i(0, 0), Vector2i(2, 0), Vector2i(3, 0), Vector2i(1, 2), Vector2i(1, 0), Vector2i(3, 2), Vector2i(2, 2), Vector2i(0, 2), ], ] }, # Old template. # The gaps between triangles made them harder to align. 'Alternating': { 'size': Vector2i(4, 4), 'bg': Vector2i(0, 0), 'fg': Vector2i(0, 2), 'layers': [ [ # v Vector2i(0, 0), Vector2i(2, 0), Vector2i(3, 1), Vector2i(1, 3), Vector2i(1, 1), Vector2i(3, 3), Vector2i(2, 2), Vector2i(0, 2), ], [ # ^ Vector2i(0, 1), Vector2i(2, 1), Vector2i(3, 0), Vector2i(1, 2), Vector2i(1, 0), Vector2i(3, 2), Vector2i(2, 3), Vector2i(0, 3), ], ], }, }, } ##[br] Would you like to automatically create tiles in the atlas? ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. ##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo. static func write_default_preset(urm: EditorUndoRedoManager, tile_set: TileSet, atlas: TileSetAtlasSource) -> void: #print('writing default') var neighborhood := TerrainDual.tileset_neighborhood(tile_set) var terrain := new_terrain( urm, tile_set, atlas.texture.resource_path.get_file() ) write_preset( urm, atlas, neighborhood, terrain, ) ##[br] Creates terrain set 0 (the primary terrain set) and terrain 0 (the 'any' terrain) ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. static func init_terrains(urm: EditorUndoRedoManager, tile_set: TileSet) -> void: urm.add_do_method(TerrainPreset, "_do_init_terrains", tile_set) urm.add_undo_method(TerrainPreset, "_undo_init_terrains", tile_set) static func _do_init_terrains(tile_set: TileSet) -> void: tile_set.add_terrain_set() tile_set.set_terrain_set_mode(0, TileSet.TERRAIN_MODE_MATCH_CORNERS) tile_set.add_terrain(0) tile_set.set_terrain_name(0, 0, "") tile_set.set_terrain_color(0, 0, Color.VIOLET) static func _undo_init_terrains(tile_set: TileSet) -> void: tile_set.remove_terrain_set(0) ##[br] Adds a new terrain type to terrain set 0 for the sprites to use. ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. static func new_terrain(urm: EditorUndoRedoManager, tile_set: TileSet, terrain_name: String) -> int: var terrain: int if tile_set.get_terrain_sets_count() == 0: init_terrains(urm, tile_set) terrain = 1 else: terrain = tile_set.get_terrains_count(0) urm.add_do_method(TerrainPreset, "_do_new_terrain", tile_set, terrain_name) urm.add_undo_method(TerrainPreset, "_undo_new_terrain", tile_set) return terrain static func _do_new_terrain(tile_set: TileSet, terrain_name: String) -> void: tile_set.add_terrain(0) var terrain := tile_set.get_terrains_count(0) - 1 tile_set.set_terrain_name(0, terrain, "FG -%s" % terrain_name) static func _undo_new_terrain(tile_set: TileSet) -> void: var terrain := tile_set.get_terrains_count(0) - 1 tile_set.remove_terrain(0, terrain) ##[br] Takes a preset and writes it onto the given atlas, replacing the previous configuration. ##[br] ARGUMENTS: ##[br] - atlas: the atlas source to apply the preset to. ##[br] - filters: the neighborhood filter ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. ##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo. static func write_preset( urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, neighborhood: TerrainDual.Neighborhood, terrain_foreground: int, terrain_background: int = 0, preset: Dictionary = neighborhood_preset(neighborhood), ) -> void: clear_and_divide_atlas(urm, atlas, preset.size) urm.add_do_method(TerrainPreset, '_do_write_preset', atlas, neighborhood, terrain_foreground, terrain_background, preset) static func _do_write_preset( atlas: TileSetAtlasSource, neighborhood: TerrainDual.Neighborhood, terrain_foreground: int, terrain_background: int, preset: Dictionary, ) -> void: var layers: Array = TerrainDual.NEIGHBORHOOD_LAYERS[neighborhood] # Set peering bits var sequences: Array = preset.layers for j in layers.size(): var terrain_neighborhood = layers[j].terrain_neighborhood var sequence: Array = sequences[j] for i in sequence.size(): var tile: Vector2i = sequence[i] atlas.create_tile(tile) var data := atlas.get_tile_data(tile, 0) data.terrain_set = 0 for neighbor in terrain_neighborhood: data.set_terrain_peering_bit( neighbor, [terrain_background, terrain_foreground][i & 1] ) i >>= 1 # Set terrains atlas.get_tile_data(preset.bg, 0).terrain = terrain_background atlas.get_tile_data(preset.fg, 0).terrain = terrain_foreground ##[br] Unregisters all the tiles in an atlas and changes the size of the individual sprites. ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. ##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo. static func clear_and_resize_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, size: Vector2) -> void: var atlas_data := _save_atlas_data(atlas) urm.add_do_method(TerrainPreset, '_do_clear_and_resize_atlas', atlas, size) urm.add_undo_method(TerrainPreset, '_undo_clear_and_resize_atlas', atlas, atlas_data) static func _do_clear_and_resize_atlas(atlas: TileSetAtlasSource, size: Vector2) -> void: # Clear all tiles atlas.texture_region_size = atlas.texture.get_size() + Vector2.ONE atlas.clear_tiles_outside_texture() # Resize the tiles atlas.texture_region_size = size static func _undo_clear_and_resize_atlas(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void: _load_atlas_data(atlas, atlas_data) ## NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else. static func _save_atlas_data(atlas: TileSetAtlasSource) -> Dictionary: var size_img := atlas.texture.get_size() var size_sprite := atlas.texture_region_size var size_dims := Vector2i(size_img) / size_sprite var tiles := [] for y in size_dims.y: var row := [] for x in size_dims.x: var tile := Vector2i(x, y) var exists := atlas.has_tile(tile) row.push_back(exists) tiles.push_back(row) return { 'size_sprite': size_sprite, 'size_dims': size_dims, 'tiles': tiles, } static func _load_atlas_data(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void: _do_clear_and_resize_atlas(atlas, atlas_data.size_sprite) for y in atlas_data.size_dims.y: for x in atlas_data.size_dims.x: if atlas_data.tiles[y][x]: var tile := Vector2i(x, y) atlas.create_tile(tile) ##[br] Unregisters all the tiles in an atlas and changes the size of the ## individual sprites to accomodate a divisions.x by divisions.y grid of sprites. ##[br] ##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called. ##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo. static func clear_and_divide_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, divisions: Vector2i) -> void: clear_and_resize_atlas(urm, atlas, atlas.texture.get_size() / Vector2(divisions))