refactor(M03): extract M2 animated scene finalizer
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
class_name M2AnimatedSceneFinalizer
|
||||
extends RefCounted
|
||||
|
||||
## Instantiates and validates detached animated M2 scene candidates and repairs
|
||||
## their surface materials from an existing static material prototype.
|
||||
## The caller owns ResourceLoader I/O, permits and prototype cache adoption.
|
||||
|
||||
|
||||
## Instantiates one PackedScene as a detached Node3D candidate. Unsupported
|
||||
## Resources return null; a created non-Node3D root is freed before return.
|
||||
func instantiate_candidate(resource: Resource) -> Node3D:
|
||||
if not (resource is PackedScene):
|
||||
return null
|
||||
var candidate_root := (resource as PackedScene).instantiate()
|
||||
if candidate_root is Node3D:
|
||||
return candidate_root as Node3D
|
||||
if candidate_root != null:
|
||||
candidate_root.free()
|
||||
return null
|
||||
|
||||
|
||||
## Applies the historical depth-first source-to-target material mapping. A null
|
||||
## source or target leaves the candidate unchanged.
|
||||
func repair_materials(animated_root: Node3D, material_source_root: Node3D) -> void:
|
||||
if animated_root == null or material_source_root == null:
|
||||
return
|
||||
var source_meshes := mesh_instances_in_subtree(material_source_root)
|
||||
var target_meshes := mesh_instances_in_subtree(animated_root)
|
||||
if source_meshes.is_empty() or target_meshes.is_empty():
|
||||
return
|
||||
|
||||
var fallback_material := _first_mesh_material(source_meshes)
|
||||
for target_index in target_meshes.size():
|
||||
var target := target_meshes[target_index]
|
||||
if target == null or target.mesh == null:
|
||||
continue
|
||||
var source := source_meshes[mini(target_index, source_meshes.size() - 1)]
|
||||
for surface_index in target.mesh.get_surface_count():
|
||||
var material := _mesh_instance_surface_material(source, surface_index)
|
||||
if material == null:
|
||||
material = fallback_material
|
||||
if material != null:
|
||||
target.set_surface_override_material(surface_index, material)
|
||||
|
||||
|
||||
## Accepts a detached candidate only when it contains an AnimationPlayer.
|
||||
## Rejected candidates are freed synchronously. A successful result transfers
|
||||
## the exact prototype reference and its AnimationPlayer count to the caller.
|
||||
func finalize_candidate(candidate_root: Node3D) -> Dictionary:
|
||||
if candidate_root == null:
|
||||
return {}
|
||||
var animation_players := animation_players_in_subtree(candidate_root)
|
||||
if animation_players.is_empty():
|
||||
candidate_root.free()
|
||||
return {}
|
||||
return {
|
||||
"prototype": candidate_root,
|
||||
"animation_player_count": animation_players.size(),
|
||||
}
|
||||
|
||||
|
||||
## Returns MeshInstance3D descendants in depth-first preorder, including root.
|
||||
func mesh_instances_in_subtree(root: Node) -> Array[MeshInstance3D]:
|
||||
var mesh_instances: Array[MeshInstance3D] = []
|
||||
if root != null:
|
||||
_collect_mesh_instances(root, mesh_instances)
|
||||
return mesh_instances
|
||||
|
||||
|
||||
## Returns AnimationPlayer descendants in depth-first preorder, including root.
|
||||
func animation_players_in_subtree(root: Node) -> Array[AnimationPlayer]:
|
||||
var animation_players: Array[AnimationPlayer] = []
|
||||
if root != null:
|
||||
_collect_animation_players(root, animation_players)
|
||||
return animation_players
|
||||
|
||||
|
||||
func _collect_mesh_instances(node: Node, result: Array[MeshInstance3D]) -> void:
|
||||
if node is MeshInstance3D:
|
||||
result.append(node as MeshInstance3D)
|
||||
for child in node.get_children():
|
||||
_collect_mesh_instances(child, result)
|
||||
|
||||
|
||||
func _collect_animation_players(node: Node, result: Array[AnimationPlayer]) -> void:
|
||||
if node is AnimationPlayer:
|
||||
result.append(node as AnimationPlayer)
|
||||
for child in node.get_children():
|
||||
_collect_animation_players(child, result)
|
||||
|
||||
|
||||
func _first_mesh_material(meshes: Array[MeshInstance3D]) -> Material:
|
||||
for mesh_instance in meshes:
|
||||
if mesh_instance == null or mesh_instance.mesh == null:
|
||||
continue
|
||||
for surface_index in mesh_instance.mesh.get_surface_count():
|
||||
var material := _mesh_instance_surface_material(mesh_instance, surface_index)
|
||||
if material != null:
|
||||
return material
|
||||
return null
|
||||
|
||||
|
||||
func _mesh_instance_surface_material(
|
||||
mesh_instance: MeshInstance3D,
|
||||
surface_index: int
|
||||
) -> Material:
|
||||
if mesh_instance == null or mesh_instance.mesh == null:
|
||||
return null
|
||||
if surface_index < mesh_instance.get_surface_override_material_count():
|
||||
var override_material := mesh_instance.get_surface_override_material(surface_index)
|
||||
if override_material != null:
|
||||
return override_material
|
||||
if surface_index < mesh_instance.mesh.get_surface_count():
|
||||
return mesh_instance.mesh.surface_get_material(surface_index)
|
||||
return null
|
||||
@@ -0,0 +1 @@
|
||||
uid://cjjim3v675w66
|
||||
Reference in New Issue
Block a user