121 lines
3.7 KiB
GDScript
121 lines
3.7 KiB
GDScript
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)
|