extends SceneTree
## Headless pose extraction. Run with:
## godot4 --headless --script scenes/player/procedural/extract_poses_headless.gd
const PoseLibraryClass := preload("res://scenes/player/procedural/pose_library.gd")
const EXTRACTION_MAP: Dictionary = {
"idle": { "anim": "default", "time": 0.042 },
"walk_a": { "anim": "walk-forward", "time": 0.042 },
"walk_b": { "anim": "walk-forward", "time": 0.458 },
"run_a": { "anim": "run", "time": 0.042 },
"run_b": { "anim": "run", "time": 0.458 },
"jump_launch": { "anim": "jump", "time": 0.042 },
"jump_air": { "anim": "jump", "time": 0.5 },
"fall": { "anim": "falling", "time": 0.042 },
"land": { "anim": "landing", "time": 0.042 },
}
const OUTPUT_PATH: String = "res://scenes/player/procedural/tumble_poses.tres"
func _init() -> void:
# Need to wait for tree to be ready
call_deferred("_extract")
func _extract() -> void:
var tumble_scene: PackedScene = load("res://assets/models/tumble/tumble.glb")
if not tumble_scene:
print("ERROR: Could not load tumble.glb")
quit()
return
var tumble: Node3D = tumble_scene.instantiate()
root.add_child(tumble)
var anim_player: AnimationPlayer = _find_child(tumble, "AnimationPlayer") as AnimationPlayer
var skeleton: Skeleton3D = _find_child(tumble, "Skeleton3D") as Skeleton3D
if not anim_player or not skeleton:
print("ERROR: Missing AnimationPlayer or Skeleton3D")
quit()
return
# List animations
print("=== Available Animations ===")
for anim_name in anim_player.get_animation_list():
var anim: Animation = anim_player.get_animation(anim_name)
print(" '", anim_name, "': length=", snapped(anim.length, 0.001), "s, loop=", anim.loop_mode)
print("")
# Extract
var lib: Resource = PoseLibraryClass.new()
for pose_name: String in EXTRACTION_MAP:
var entry: Dictionary = EXTRACTION_MAP[pose_name]
var anim_name: String = entry.get("anim", "")
var seek_time: float = entry.get("time", 0.0)
if not anim_player.has_animation(anim_name):
print("WARNING: Animation '", anim_name, "' not found, skipping '", pose_name, "'")
continue
anim_player.play(anim_name)
anim_player.seek(seek_time, true)
anim_player.advance(0.0)
var pose: Dictionary = {}
for bone_idx in skeleton.get_bone_count():
var bone_name: String = skeleton.get_bone_name(bone_idx)
var pos: Vector3 = skeleton.get_bone_pose_position(bone_idx)
var rot: Quaternion = skeleton.get_bone_pose_rotation(bone_idx)
var scl: Vector3 = skeleton.get_bone_pose_scale(bone_idx)
pose[bone_name] = Transform3D(Basis(rot).scaled(scl), pos)
lib.poses[pose_name] = pose
print("Captured '", pose_name, "' from '", anim_name, "' @ t=", seek_time, " (", pose.size(), " bones)")
anim_player.stop()
tumble.queue_free()
var err: int = ResourceSaver.save(lib, OUTPUT_PATH)
if err == OK:
print("\nSaved PoseLibrary to ", OUTPUT_PATH, " with ", lib.poses.size(), " poses")
print("Poses: ", lib.poses.keys())
else:
print("ERROR: Failed to save, error code ", err)
quit()
func _find_child(node: Node, type_name: String) -> Node:
for child in node.get_children():
if child.get_class() == type_name:
return child
var found: Node = _find_child(child, type_name)
if found:
return found
return null