render: extract cached M2 animation resource observer
This commit is contained in:
@@ -73,6 +73,9 @@ const M2_STATIC_BATCH_MATERIALIZER_SCRIPT := preload(
|
||||
const M2_STATIC_BUILD_RESOURCE_OBSERVER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_static_build_resource_observer.gd"
|
||||
)
|
||||
const M2_CACHED_ANIMATION_RESOURCE_OBSERVER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_cached_animation_resource_observer.gd"
|
||||
)
|
||||
const M2_RUNTIME_MESH_FINALIZER_SCRIPT := preload(
|
||||
"res://src/render/m2/m2_runtime_mesh_finalizer.gd"
|
||||
)
|
||||
@@ -283,6 +286,9 @@ var _m2_build_dispatch_planner := M2_BUILD_DISPATCH_PLANNER_SCRIPT.new()
|
||||
var _m2_static_build_resource_observer := (
|
||||
M2_STATIC_BUILD_RESOURCE_OBSERVER_SCRIPT.new()
|
||||
)
|
||||
var _m2_animation_resource_observer := (
|
||||
M2_CACHED_ANIMATION_RESOURCE_OBSERVER_SCRIPT.new()
|
||||
)
|
||||
var _m2_unique_placement_registry := (
|
||||
M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT.new()
|
||||
)
|
||||
@@ -4350,20 +4356,34 @@ func _process_m2_build_jobs() -> void:
|
||||
var groups := _m2_build_queue_state.groups_for(key)
|
||||
var transforms: Array = groups.get(rel_path, [])
|
||||
var offset := _m2_build_queue_state.transform_offset_for(key)
|
||||
var animated_prototype: Node3D = null
|
||||
var resource_snapshot: RefCounted
|
||||
if enable_m2_animated_instances:
|
||||
animated_prototype = _get_or_load_m2_native_animated_prototype(rel_path)
|
||||
if animated_prototype == null:
|
||||
animated_prototype = _get_or_load_m2_animated_prototype(rel_path)
|
||||
var animation_request_pending := (
|
||||
enable_m2_animated_instances
|
||||
and _m2_animation_load_pipeline_state.has_request(normalized_rel)
|
||||
)
|
||||
var resource_snapshot: RefCounted = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_rel,
|
||||
animated_prototype,
|
||||
animation_request_pending
|
||||
)
|
||||
var native_animated_prototype := (
|
||||
_get_or_load_m2_native_animated_prototype(rel_path)
|
||||
)
|
||||
if native_animated_prototype != null:
|
||||
resource_snapshot = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_rel,
|
||||
native_animated_prototype,
|
||||
false
|
||||
)
|
||||
else:
|
||||
resource_snapshot = _m2_animation_resource_observer.observe(
|
||||
normalized_rel,
|
||||
m2_cache_dir,
|
||||
m2_animated_max_primitives,
|
||||
m2_animated_allowlist_patterns,
|
||||
m2_animated_denylist_patterns,
|
||||
_m2_prototype_cache_state,
|
||||
_m2_animation_load_pipeline_state,
|
||||
debug_streaming
|
||||
)
|
||||
else:
|
||||
resource_snapshot = M2_BUILD_RESOURCE_SNAPSHOT_SCRIPT.new(
|
||||
normalized_rel,
|
||||
null,
|
||||
false
|
||||
)
|
||||
var batch_plan: Dictionary = {}
|
||||
var batch_count := 0
|
||||
if not bool(resource_snapshot.call("animation_request_pending")):
|
||||
@@ -4875,75 +4895,6 @@ func _is_m2_native_animation_candidate(normalized_rel: String) -> bool:
|
||||
return normalized_rel.to_lower().contains("gryphonroost")
|
||||
|
||||
|
||||
func _get_or_load_m2_animated_prototype(rel_path: String) -> Node3D:
|
||||
var normalized_rel := _normalize_m2_rel_path(rel_path)
|
||||
if normalized_rel.is_empty():
|
||||
return null
|
||||
var cached_animated := _m2_prototype_cache_state.find_animated_prototype(
|
||||
normalized_rel
|
||||
)
|
||||
if cached_animated != null:
|
||||
return cached_animated
|
||||
if _m2_prototype_cache_state.is_animation_static(normalized_rel):
|
||||
return null
|
||||
if _m2_animation_load_pipeline_state.has_request(normalized_rel):
|
||||
return null
|
||||
|
||||
_request_m2_animation_load(normalized_rel)
|
||||
return null
|
||||
|
||||
|
||||
func _request_m2_animation_load(normalized_rel: String) -> void:
|
||||
if normalized_rel.is_empty() or _m2_animation_load_pipeline_state.has_request(normalized_rel):
|
||||
return
|
||||
var cache_res_path := _find_m2_animated_glb_cache_path(normalized_rel)
|
||||
if cache_res_path.is_empty():
|
||||
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
|
||||
return
|
||||
var err := ResourceLoader.load_threaded_request(
|
||||
cache_res_path,
|
||||
"",
|
||||
false,
|
||||
ResourceLoader.CACHE_MODE_REUSE)
|
||||
if err == OK or err == ERR_BUSY:
|
||||
_m2_animation_load_pipeline_state.remember_request(normalized_rel, cache_res_path)
|
||||
else:
|
||||
_m2_prototype_cache_state.mark_animation_static(normalized_rel)
|
||||
|
||||
|
||||
func _find_m2_animated_glb_cache_path(normalized_rel: String) -> String:
|
||||
if not _is_m2_animation_allowed(normalized_rel):
|
||||
return ""
|
||||
if _is_m2_animation_denied(normalized_rel):
|
||||
return ""
|
||||
for cache_res_path in _get_m2_cache_resource_paths(normalized_rel, [".glb"]):
|
||||
if not ResourceLoader.exists(cache_res_path):
|
||||
continue
|
||||
if _glb_cache_is_safe_for_runtime_animation(cache_res_path):
|
||||
return cache_res_path
|
||||
return ""
|
||||
|
||||
|
||||
func _is_m2_animation_allowed(normalized_rel: String) -> bool:
|
||||
if m2_animated_allowlist_patterns.is_empty():
|
||||
return false
|
||||
var lower := normalized_rel.to_lower()
|
||||
for pattern in m2_animated_allowlist_patterns:
|
||||
var needle := String(pattern).strip_edges().to_lower()
|
||||
if not needle.is_empty() and lower.contains(needle):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _is_m2_animation_denied(normalized_rel: String) -> bool:
|
||||
var lower := normalized_rel.to_lower()
|
||||
for pattern in m2_animated_denylist_patterns:
|
||||
var needle := String(pattern).strip_edges().to_lower()
|
||||
if not needle.is_empty() and lower.contains(needle):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _get_or_load_m2_material_prototype(normalized_rel: String) -> Node3D:
|
||||
if normalized_rel.is_empty():
|
||||
return null
|
||||
@@ -4967,62 +4918,6 @@ func _get_or_load_m2_material_prototype(normalized_rel: String) -> Node3D:
|
||||
return null
|
||||
|
||||
|
||||
func _glb_cache_is_safe_for_runtime_animation(cache_res_path: String) -> bool:
|
||||
var abs_path := ProjectSettings.globalize_path(cache_res_path)
|
||||
if not FileAccess.file_exists(abs_path):
|
||||
return false
|
||||
var file := FileAccess.open(abs_path, FileAccess.READ)
|
||||
if file == null or file.get_length() < 20:
|
||||
return false
|
||||
var magic := file.get_32()
|
||||
var version := file.get_32()
|
||||
file.get_32()
|
||||
if magic != 0x46546c67 or version != 2:
|
||||
return false
|
||||
var json_len := int(file.get_32())
|
||||
var chunk_type := file.get_32()
|
||||
if json_len <= 0 or chunk_type != 0x4e4f534a:
|
||||
return false
|
||||
var json_text := file.get_buffer(json_len).get_string_from_utf8()
|
||||
var parsed = JSON.parse_string(json_text)
|
||||
if not (parsed is Dictionary):
|
||||
return false
|
||||
var gltf := parsed as Dictionary
|
||||
var animations: Array = gltf.get("animations", [])
|
||||
if animations.is_empty():
|
||||
return false
|
||||
var primitive_count := _glb_primitive_count(gltf)
|
||||
if m2_animated_max_primitives > 0 and primitive_count > m2_animated_max_primitives:
|
||||
return false
|
||||
var schema := _glb_animation_schema(gltf)
|
||||
if schema == "pivot_prefix_v1":
|
||||
return true
|
||||
# Older simple critter GLBs were generated before the schema marker existed.
|
||||
# Accept them only after allowlist/denylist filtering and primitive-count checks.
|
||||
if schema.is_empty():
|
||||
return true
|
||||
if debug_streaming:
|
||||
print("M2_ANIM_REJECT schema=%s primitives=%d cache=%s" % [schema, primitive_count, cache_res_path])
|
||||
return false
|
||||
|
||||
|
||||
func _glb_primitive_count(gltf: Dictionary) -> int:
|
||||
var count := 0
|
||||
var meshes: Array = gltf.get("meshes", [])
|
||||
for mesh_variant in meshes:
|
||||
if not (mesh_variant is Dictionary):
|
||||
continue
|
||||
var primitives: Array = (mesh_variant as Dictionary).get("primitives", [])
|
||||
count += primitives.size()
|
||||
return count
|
||||
|
||||
|
||||
func _glb_animation_schema(gltf: Dictionary) -> String:
|
||||
var asset: Dictionary = gltf.get("asset", {})
|
||||
var extras: Dictionary = asset.get("extras", {})
|
||||
return String(extras.get("openwc_m2_anim_schema", ""))
|
||||
|
||||
|
||||
func _get_or_load_wmo_prototype(rel_path: String) -> Node3D:
|
||||
var normalized_rel := rel_path.replace("\\", "/")
|
||||
if normalized_rel.is_empty():
|
||||
|
||||
Reference in New Issue
Block a user