71 lines
2.8 KiB
GDScript
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
|