175 lines
5.6 KiB
GDScript
175 lines
5.6 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
## TileSet management — atlas inspection helpers.
|
|
|
|
const ErrorCodes := preload("res://addons/godot_ai/utils/error_codes.gd")
|
|
|
|
|
|
func _init() -> void:
|
|
pass
|
|
|
|
|
|
## Query all occupied atlas tile positions for a single source.
|
|
##
|
|
## params:
|
|
## tileset_path — res:// path to the TileSet resource (required, non-empty)
|
|
## source_id — raw TileSet source id (required)
|
|
##
|
|
## Returns:
|
|
## {"data": {"tiles": [{"col": int, "row": int}, ...], "count": int}}
|
|
## on success (including empty sources, where tiles=[] and count=0)
|
|
## ErrorCodes.make(code, message) on any validation or load failure
|
|
##
|
|
## Error codes:
|
|
## MISSING_REQUIRED_PARAM — tileset_path absent/empty, or source_id absent
|
|
## RESOURCE_NOT_FOUND — ResourceLoader.exists(tileset_path) is false
|
|
## WRONG_TYPE — loaded resource is not a TileSet, or source is
|
|
## not a TileSetAtlasSource
|
|
## VALUE_OUT_OF_RANGE — source_id not present in TileSet
|
|
##
|
|
## This method is read-only: it never calls ResourceSaver or modifies any resource.
|
|
func get_atlas_tiles(params: Dictionary) -> Dictionary:
|
|
var resolved := _resolve_atlas_source(params)
|
|
if resolved.has("error"):
|
|
return resolved
|
|
var src: TileSetAtlasSource = resolved.src
|
|
|
|
var tiles: Array = []
|
|
for i in range(src.get_tiles_count()):
|
|
var v: Vector2i = src.get_tile_id(i)
|
|
tiles.append({"col": v.x, "row": v.y})
|
|
|
|
return {"data": {"tiles": tiles, "count": tiles.size()}}
|
|
|
|
|
|
## Return the atlas texture of a TileSetAtlasSource as a Base64-encoded PNG.
|
|
##
|
|
## params:
|
|
## tileset_path — res:// path to the TileSet resource (required, non-empty)
|
|
## source_id — raw TileSet source id (required)
|
|
## max_size — optional int; if > 0, the image is scaled so its longest
|
|
## edge is at most max_size pixels (default 0 = full res)
|
|
##
|
|
## Returns:
|
|
## {"data": {"image_base64": String, "width": int, "height": int,
|
|
## "original_width": int, "original_height": int, "format": "png"}}
|
|
## on success
|
|
## ErrorCodes.make(code, message) on any validation or load failure
|
|
##
|
|
## Error codes:
|
|
## MISSING_REQUIRED_PARAM — tileset_path absent/empty, or source_id absent
|
|
## RESOURCE_NOT_FOUND — ResourceLoader.exists(tileset_path) is false
|
|
## WRONG_TYPE — loaded resource is not a TileSet, or source is
|
|
## not a TileSetAtlasSource, or texture is null
|
|
## VALUE_OUT_OF_RANGE — source_id not present in TileSet
|
|
##
|
|
## This method is read-only: it never calls ResourceSaver or modifies anything.
|
|
func get_atlas_image(params: Dictionary) -> Dictionary:
|
|
var resolved := _resolve_atlas_source(params)
|
|
if resolved.has("error"):
|
|
return resolved
|
|
var source_id: int = resolved.source_id
|
|
var src: TileSetAtlasSource = resolved.src
|
|
|
|
var tex: Texture2D = src.texture
|
|
if tex == null:
|
|
return ErrorCodes.make(
|
|
ErrorCodes.WRONG_TYPE,
|
|
"Source %d has no texture assigned" % source_id
|
|
)
|
|
|
|
var img: Image = tex.get_image()
|
|
if img == null:
|
|
return ErrorCodes.make(
|
|
ErrorCodes.WRONG_TYPE,
|
|
"Could not retrieve image data from texture of source %d" % source_id
|
|
)
|
|
if img.is_compressed():
|
|
var decompress_err := img.decompress()
|
|
if decompress_err != OK:
|
|
return ErrorCodes.make(
|
|
ErrorCodes.INTERNAL_ERROR,
|
|
"Could not decompress texture of source %d: %s" % [source_id, error_string(decompress_err)]
|
|
)
|
|
|
|
var original_width: int = img.get_width()
|
|
var original_height: int = img.get_height()
|
|
|
|
var max_size: int = params.get("max_size", 0)
|
|
if max_size > 0:
|
|
var longest_edge: int = max(original_width, original_height)
|
|
if longest_edge > max_size:
|
|
var scale: float = float(max_size) / float(longest_edge)
|
|
var new_w: int = max(1, int(original_width * scale))
|
|
var new_h: int = max(1, int(original_height * scale))
|
|
img.resize(new_w, new_h, Image.INTERPOLATE_LANCZOS)
|
|
|
|
var png_bytes: PackedByteArray = img.save_png_to_buffer()
|
|
if png_bytes.is_empty():
|
|
return ErrorCodes.make(
|
|
ErrorCodes.INTERNAL_ERROR,
|
|
"PNG encoding produced empty output for source %d" % source_id
|
|
)
|
|
var b64: String = Marshalls.raw_to_base64(png_bytes)
|
|
|
|
return {
|
|
"data": {
|
|
"image_base64": b64,
|
|
"width": img.get_width(),
|
|
"height": img.get_height(),
|
|
"original_width": original_width,
|
|
"original_height": original_height,
|
|
"format": "png",
|
|
}
|
|
}
|
|
|
|
|
|
func _resolve_atlas_source(params: Dictionary) -> Dictionary:
|
|
var tileset_path: String = params.get("tileset_path", "")
|
|
if tileset_path.is_empty():
|
|
return ErrorCodes.make(
|
|
ErrorCodes.MISSING_REQUIRED_PARAM,
|
|
"'tileset_path' parameter is required and must not be empty"
|
|
)
|
|
|
|
if not params.has("source_id"):
|
|
return ErrorCodes.make(
|
|
ErrorCodes.MISSING_REQUIRED_PARAM,
|
|
"'source_id' parameter is required"
|
|
)
|
|
|
|
if not ResourceLoader.exists(tileset_path):
|
|
return ErrorCodes.make(
|
|
ErrorCodes.RESOURCE_NOT_FOUND,
|
|
"TileSet resource not found: %s" % tileset_path
|
|
)
|
|
|
|
var ts = load(tileset_path)
|
|
if not ts is TileSet:
|
|
var loaded_type := "null" if ts == null else ts.get_class()
|
|
return ErrorCodes.make(
|
|
ErrorCodes.WRONG_TYPE,
|
|
"Resource at '%s' is not a TileSet (got %s)" % [tileset_path, loaded_type]
|
|
)
|
|
|
|
var source_id: int = int(params.get("source_id", -999))
|
|
if source_id < 0 or not ts.has_source(source_id):
|
|
return ErrorCodes.make(
|
|
ErrorCodes.VALUE_OUT_OF_RANGE,
|
|
"source_id %d does not exist in TileSet" % source_id
|
|
)
|
|
|
|
var src = ts.get_source(source_id)
|
|
if not src is TileSetAtlasSource:
|
|
var source_type: String = "null" if src == null else src.get_class()
|
|
return ErrorCodes.make(
|
|
ErrorCodes.WRONG_TYPE,
|
|
"Source %d is not a TileSetAtlasSource (got %s)" % [source_id, source_type]
|
|
)
|
|
|
|
return {
|
|
"source_id": source_id,
|
|
"src": src,
|
|
}
|