render: extract animated M2 materializer
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
class_name M2AnimatedInstanceMaterializer
|
||||
extends RefCounted
|
||||
|
||||
## Duplicates one ordered animated M2 batch, applies instance render settings,
|
||||
## starts playback and attaches a non-empty batch to the supplied parent.
|
||||
## All calls mutate Godot scene objects and therefore belong on the main thread.
|
||||
|
||||
const M2_ANIMATED_SCENE_FINALIZER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animated_scene_finalizer.gd"
|
||||
)
|
||||
const M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_animation_playback_controller.gd"
|
||||
)
|
||||
|
||||
var _animated_scene_finalizer := M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()
|
||||
var _animation_playback_controller := M2_ANIMATION_PLAYBACK_CONTROLLER_SCRIPT.new()
|
||||
|
||||
|
||||
## Returns the attached batch root and detached diagnostic entries. Invalid or
|
||||
## empty input, and a batch whose duplicates all fail, returns an empty result.
|
||||
## The caller owns the returned/attached batch through `m2_parent_root`.
|
||||
func materialize_batch(
|
||||
m2_parent_root: Node3D,
|
||||
relative_path: String,
|
||||
prototype: Node3D,
|
||||
transforms: Array,
|
||||
start_index: int,
|
||||
instance_count: int,
|
||||
batch_serial: int,
|
||||
visibility_range_end: float,
|
||||
visibility_range_margin: float,
|
||||
cast_shadows: bool,
|
||||
native_animator_script: Script,
|
||||
collect_native_diagnostics: bool
|
||||
) -> Dictionary:
|
||||
if (
|
||||
m2_parent_root == null
|
||||
or transforms.is_empty()
|
||||
or instance_count <= 0
|
||||
or prototype == null
|
||||
):
|
||||
return {}
|
||||
|
||||
var model_name := relative_path.get_file().get_basename()
|
||||
var batch_root := Node3D.new()
|
||||
batch_root.name = "%s_anim_%d" % [model_name, batch_serial]
|
||||
var diagnostic_entries: Array[Dictionary] = []
|
||||
for batch_offset in instance_count:
|
||||
var instance_index := start_index + batch_offset
|
||||
var instance := prototype.duplicate(
|
||||
Node.DUPLICATE_SIGNALS | Node.DUPLICATE_GROUPS | Node.DUPLICATE_SCRIPTS
|
||||
) as Node3D
|
||||
if instance == null:
|
||||
continue
|
||||
instance.name = "%s_%d" % [model_name, instance_index]
|
||||
instance.transform = transforms[instance_index]
|
||||
_apply_visibility_range_recursive(
|
||||
instance,
|
||||
visibility_range_end,
|
||||
visibility_range_margin
|
||||
)
|
||||
_apply_shadow_cast_recursive(instance, cast_shadows)
|
||||
_animation_playback_controller.copy_native_animator_data(
|
||||
prototype,
|
||||
instance,
|
||||
native_animator_script
|
||||
)
|
||||
batch_root.add_child(instance)
|
||||
var animation_players := _animated_scene_finalizer.animation_players_in_subtree(
|
||||
instance
|
||||
)
|
||||
var native_diagnostics := _animation_playback_controller.start_instance_playback(
|
||||
instance,
|
||||
relative_path,
|
||||
instance_index,
|
||||
native_animator_script,
|
||||
animation_players,
|
||||
collect_native_diagnostics
|
||||
)
|
||||
for native_diagnostic in native_diagnostics:
|
||||
diagnostic_entries.append({
|
||||
"instance_index": instance_index,
|
||||
"state": native_diagnostic,
|
||||
})
|
||||
|
||||
if batch_root.get_child_count() <= 0:
|
||||
batch_root.queue_free()
|
||||
return {}
|
||||
m2_parent_root.add_child(batch_root)
|
||||
return {
|
||||
"batch_root": batch_root,
|
||||
"native_diagnostics": diagnostic_entries,
|
||||
}
|
||||
|
||||
|
||||
func _apply_visibility_range_recursive(
|
||||
node: Node,
|
||||
range_end: float,
|
||||
range_end_margin: float
|
||||
) -> void:
|
||||
if range_end <= 0.0:
|
||||
return
|
||||
if node is GeometryInstance3D:
|
||||
var geometry := node as GeometryInstance3D
|
||||
geometry.visibility_range_end = range_end
|
||||
geometry.visibility_range_end_margin = range_end_margin
|
||||
for child in node.get_children():
|
||||
_apply_visibility_range_recursive(child, range_end, range_end_margin)
|
||||
|
||||
|
||||
func _apply_shadow_cast_recursive(node: Node, cast_shadows: bool) -> void:
|
||||
if node is GeometryInstance3D:
|
||||
var geometry := node as GeometryInstance3D
|
||||
geometry.cast_shadow = (
|
||||
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
if cast_shadows
|
||||
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
)
|
||||
for child in node.get_children():
|
||||
_apply_shadow_cast_recursive(child, cast_shadows)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkgy3rvsmlkvm
|
||||
@@ -2,8 +2,8 @@ class_name M2AnimationPlaybackController
|
||||
extends RefCounted
|
||||
|
||||
## Applies deterministic playback to one animated M2 instance and copies the
|
||||
## runtime fields of exact-script native animators. The caller owns instance
|
||||
## duplication, SceneTree attachment and diagnostic log formatting.
|
||||
## runtime fields of exact-script native animators. The materializer owns
|
||||
## instance duplication/attachment; its caller owns diagnostic log formatting.
|
||||
|
||||
|
||||
## Copies the five historical runtime fields between matching native animators
|
||||
|
||||
Reference in New Issue
Block a user