OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / player / procedural / pose_extractor.gd

112 lines · gd ref:
@tool
class_name PoseExtractor extends Node
## Tool script to extract poses from tumble.glb animations into a PoseLibrary.
## Attach to a scene containing the tumble model, configure extraction_map,
## then press "Extract Poses" in the inspector.
##
## Usage:
##   1. Add this node as a child of the scene root
##   2. Set target_skeleton to the Skeleton3D inside TumbleModel
##   3. Set target_anim_player to the AnimationPlayer inside TumbleModel
##   4. Click "Extract Poses" button
##   5. PoseLibrary is saved to output_path

@export var target_skeleton: Skeleton3D
@export var target_anim_player: AnimationPlayer
@export var output_path: String = "res://scenes/player/procedural/tumble_poses.tres"
@export var extract_now: bool = false: set = _on_extract

## Map of pose_name -> { anim: "animation_name", time: seconds_or_normalized }
## You will fill this in based on what animations tumble.glb has.
@export var extraction_map: Dictionary = {
	# These are starting guesses — you'll adjust frame numbers after inspecting
	# the actual tumble.glb animations in the Godot editor.
	#
	# Format: "pose_name": { "anim": "anim_name", "time": 0.0 }
	# time can be absolute seconds or use "frame" key for frame number
}


func _on_extract(value: bool) -> void:
	if not value:
		return
	extract_now = false
	_do_extract()


func _do_extract() -> void:
	if not target_skeleton:
		push_error("[PoseExtractor] No target_skeleton set")
		return
	if not target_anim_player:
		push_error("[PoseExtractor] No target_anim_player set")
		return

	print("[PoseExtractor] Starting extraction...")
	print("[PoseExtractor] Skeleton: ", target_skeleton.get_path())
	print("[PoseExtractor] AnimPlayer: ", target_anim_player.get_path())

	# List available animations
	var anims: PackedStringArray = target_anim_player.get_animation_list()
	print("[PoseExtractor] Available animations: ", anims)

	if extraction_map.is_empty():
		print("[PoseExtractor] extraction_map is empty. Listing animations with durations:")
		for anim_name in anims:
			var anim: Animation = target_anim_player.get_animation(anim_name)
			if anim:
				print("  '", anim_name, "': length=", anim.length, "s, tracks=", anim.get_track_count())
		print("[PoseExtractor] Fill in extraction_map and try again.")
		return

	var lib := PoseLibrary.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 target_anim_player.has_animation(anim_name):
			push_warning("[PoseExtractor] Animation '", anim_name, "' not found, skipping pose '", pose_name, "'")
			continue

		# Seek to the target time
		target_anim_player.play(anim_name)
		target_anim_player.seek(seek_time, true)
		# Force skeleton update
		target_anim_player.advance(0.0)

		# Capture all bone poses
		var pose: Dictionary = {}
		for bone_idx in target_skeleton.get_bone_count():
			var bone_name: String = target_skeleton.get_bone_name(bone_idx)
			var pos: Vector3 = target_skeleton.get_bone_pose_position(bone_idx)
			var rot: Quaternion = target_skeleton.get_bone_pose_rotation(bone_idx)
			var scl: Vector3 = target_skeleton.get_bone_pose_scale(bone_idx)
			pose[bone_name] = Transform3D(Basis(rot).scaled(scl), pos)

		lib.poses[pose_name] = pose
		print("[PoseExtractor] Captured pose '", pose_name, "' from '", anim_name, "' at t=", seek_time, " (", pose.size(), " bones)")

	# Reset animation
	target_anim_player.stop()

	# Save
	var err: int = ResourceSaver.save(lib, output_path)
	if err == OK:
		print("[PoseExtractor] Saved PoseLibrary to ", output_path, " with ", lib.poses.size(), " poses")
	else:
		push_error("[PoseExtractor] Failed to save: error ", err)


## Utility: list all bones in the skeleton (for debugging)
func list_bones() -> void:
	if not target_skeleton:
		push_error("[PoseExtractor] No skeleton")
		return
	print("[PoseExtractor] Bones (", target_skeleton.get_bone_count(), "):")
	for i in target_skeleton.get_bone_count():
		var parent: int = target_skeleton.get_bone_parent(i)
		var parent_name: String = target_skeleton.get_bone_name(parent) if parent >= 0 else "(root)"
		print("  [", i, "] ", target_skeleton.get_bone_name(i), " <- ", parent_name)