class_name StickmanThumbnail extends Node const STICKMAN_FACTORY := preload("res://scripts/stickman_factory.gd") const STAGE_SPAWNER := preload("res://scripts/stage_spawner.gd") const SIZE := Vector2i(200, 200) var _viewport: SubViewport var _world: Node2D var _camera: Camera2D func _ready() -> void: _viewport = SubViewport.new() _viewport.size = SIZE _viewport.transparent_bg = true _viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS add_child(_viewport) _world = Node2D.new() _viewport.add_child(_world) _camera = Camera2D.new() _camera.enabled = true _viewport.add_child(_camera) _camera.make_current() func render(stk_data: Dictionary) -> Texture2D: _clear_world() var rig: Node2D = STICKMAN_FACTORY.spawn_from_data(stk_data) if rig == null: return null _world.add_child(rig) rig.position = Vector2.ZERO var bbox := STAGE_SPAWNER.get_world_aabb(rig) if not bbox.has_area() or bbox.size.x < 1.0 or bbox.size.y < 1.0: bbox = Rect2(Vector2(-60, -500), Vector2(120, 500)) _frame_camera(bbox) await RenderingServer.frame_post_draw await RenderingServer.frame_post_draw var tex := _viewport.get_texture() rig.queue_free() if tex == null: return null var img := tex.get_image() if _is_blank(img): return null return ImageTexture.create_from_image(img) func _clear_world() -> void: for child: Node in _world.get_children(): child.queue_free() func _frame_camera(bbox: Rect2) -> void: var margin := 12.0 var fit := minf((SIZE.x - margin * 2.0) / bbox.size.x, (SIZE.y - margin * 2.0) / bbox.size.y) _camera.position = bbox.get_center() _camera.zoom = Vector2(maxf(fit, 0.05), maxf(fit, 0.05)) func _is_blank(img: Image) -> bool: if img == null or img.is_empty(): return true var used := img.get_used_rect() if used.size.x <= 0 or used.size.y <= 0: return true for y: int in range(used.position.y, used.end.y): for x: int in range(used.position.x, used.end.x): if img.get_pixel(x, y).a > 0.0: return false return true