Files
nodewars/addons/curved_lines_2d/svs_scene_exporter.gd
2026-05-13 18:52:00 +02:00

93 lines
3.8 KiB
GDScript

@tool
extends Object
class_name SVSSceneExporter
static func export_image(export_root_node : Node, stored_box_ref : Dictionary[String, Vector2] = {},
render_parent := Node.new(), forward_aa := true) -> Image:
var sub_viewport := SubViewport.new()
render_parent.add_child(sub_viewport)
sub_viewport.transparent_bg = true
sub_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
var copied : Node = export_root_node.duplicate()
sub_viewport.add_child(copied)
var box = copied.get_bounding_box() if copied is ScalableVectorShape2D else [Vector2.ZERO]
var child_list := copied.get_children()
var min_x = box.map(func(corner): return corner.x).min()
var min_y = box.map(func(corner): return corner.y).min()
var max_x = box.map(func(corner): return corner.x).max()
var max_y = box.map(func(corner): return corner.y).max()
while child_list.size() > 0:
var child : Node = child_list.pop_back()
if child is Camera2D:
child.enabled = false
child_list.append_array(child.get_children())
if child is ScalableVectorShape2D:
var box1 = child.get_bounding_box()
var min_x1 = box1.map(func(corner): return corner.x).min()
var min_y1 = box1.map(func(corner): return corner.y).min()
var max_x1 = box1.map(func(corner): return corner.x).max()
var max_y1 = box1.map(func(corner): return corner.y).max()
min_x = floori(min_x if min_x1 > min_x else min_x1)
min_y = floori(min_y if min_y1 > min_y else min_y1)
max_x = ceili(max_x if max_x1 < max_x else max_x1)
max_y = ceili(max_y if max_y1 < max_y else max_y1)
sub_viewport.canvas_transform.origin = -Vector2(min_x, min_y)
sub_viewport.size = Vector2(max_x, max_y) - Vector2(min_x, min_y)
if forward_aa:
sub_viewport.msaa_2d = Viewport.MSAA_8X
else:
sub_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA
stored_box_ref["tl"] = Vector2(min_x, min_y)
stored_box_ref["br"] = Vector2(max_x, max_y)
await RenderingServer.frame_post_draw
var img = sub_viewport.get_texture().get_image()
sub_viewport.queue_free()
return img
static func export_sprite_frames(root_node : Node,
animation_player : AnimationPlayer,
anim_name : String, fps : int,
render_parent : Node, on_progress : Callable,
forward_aa := false) -> Array[Image]:
var interval := 1.0 / fps
animation_player.stop()
animation_player.current_animation = anim_name
animation_player.get_animation(anim_name)
var boxes : Array[Dictionary] = []
var images : Array[Image] = []
var frame_count := ceili(animation_player.current_animation_length / interval)
if on_progress:
on_progress.call("Rendering animation %s at %d fps to %d frames" % [anim_name, fps, frame_count])
# Render each frame into an image
for idx in range(frame_count):
var pos = idx * interval
if pos > animation_player.current_animation_length:
pos = animation_player.current_animation_length
animation_player.seek(pos, true)
animation_player.pause()
var box : Dictionary[String, Vector2] = {}
var im = await export_image(root_node, box, render_parent, forward_aa)
boxes.append(box)
images.append(im)
animation_player.stop()
# Determine the bounding box into which all the rendered images would fit
var min_x = boxes.map(func(box): return box["tl"].x).min()
var min_y = boxes.map(func(box): return box["tl"].y).min()
var max_x = boxes.map(func(box): return box["br"].x).max()
var max_y = boxes.map(func(box): return box["br"].y).max()
var return_list : Array[Image] = []
# rerender and realign the images such that they all have the same size
for idx in images.size():
var im : Image = Image.create_empty(ceili(max_x) - floori(min_x), ceili(max_y) - floor(min_y), false, images[idx].get_format())
for x in images[idx].get_size().x:
for y in images[idx].get_size().y:
im.set_pixel(floori(boxes[idx]["tl"].x) - min_x + x, floori(boxes[idx]["tl"].y) - min_y + y, images[idx].get_pixel(x, y))
return_list.append(im)
return return_list