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

71 lines
2.8 KiB
GDScript

##[br] Watches a TileSetAtlasSource for changes.
##[br] Causes its 'parent' TileSetWatcher to emit terrains_changed when the atlas changes.
##[br] Also emits parent.atlas_autotiled when it thinks the user auto-generated atlas tiles.
class_name AtlasWatcher
## Prevents the number of seen atlases from extending to infinity.
const UNDO_LIMIT = 1024
## Stores all of the atlas instance id's that have been seen before, to prevent autogen on redo.
static var _registered_atlases := []
## The TileSetWatcher that created this AtlasWatcher. Used to send signals back.
var parent: TileSetWatcher
## The Source ID of `self.atlas`.
var sid: int
## The atlas to be watched for changes.
var atlas: TileSetAtlasSource
func _init(parent: TileSetWatcher, sid: int, atlas: TileSetAtlasSource) -> void:
self.parent = parent
self.sid = sid
self.atlas = atlas
atlas.changed.connect(_atlas_changed, ConnectFlags.CONNECT_DEFERRED)
var id := atlas.get_instance_id()
# should not autogen if atlas was created through redo, i.e. its instance id already existed
if _atlas_is_empty() and id not in _registered_atlases:
_registered_atlases.push_back(id)
if _registered_atlases.size() > UNDO_LIMIT:
_registered_atlases.pop_front()
atlas.changed.connect(_detect_autogen, ConnectFlags.CONNECT_DEFERRED | ConnectFlags.CONNECT_ONE_SHOT)
func _atlas_is_empty() -> bool:
return atlas.get_tiles_count() == 0
## Returns true if the texture has any opaque pixels in the specified tile coordinates.
func _is_opaque_tile(image: Image, tile: Vector2i, p_threshold: float = 0.1) -> bool:
# We cannot use atlas.get_tile_texture_region(tile) as it fails on unregistered tiles.
var region := Rect2i(tile * atlas.texture_region_size, atlas.texture_region_size)
var sprite := image.get_region(region)
if sprite.is_invisible():
return false
# We're still not sure if the tile is empty or not.
# Godot's auto-gen considers 0.1 opacity as "transparent" but not "invisible".
for y in range(region.position.y, region.end.y):
for x in range(region.position.x, region.end.x):
if image.get_pixel(x, y).a > p_threshold:
return true
return false
##[br] HACK: literally just tries to guess which tiles the terrain autogen system would make
##[br] Called once, and only once, at the end of the first frame that a texture is created.
func _detect_autogen() -> void:
var size := Vector2i(atlas.texture.get_size()) / atlas.texture_region_size
var image := atlas.texture.get_image()
var expected_tiles := []
for y in size.y:
for x in size.x:
var tile := Vector2i(x, y)
if atlas.has_tile(tile) != _is_opaque_tile(image, tile):
return
parent.atlas_autotiled.emit(sid, atlas)
## Called every time the atlas changes. Simply flags that terrains have changed.
func _atlas_changed() -> void:
parent._flag_terrains_changed = true