Files
project-hood/addons/TileMapDual/TerrainPreset.gd

323 lines
10 KiB
GDScript

## 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, "<any>")
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))