85 lines
2.5 KiB
GDScript
85 lines
2.5 KiB
GDScript
class_name NodeUtils
|
|
extends RefCounted
|
|
## Static utility methods for node tree traversal.
|
|
|
|
|
|
static func find_child_of_type(node: Node, type_name: String) -> Node:
|
|
if node.get_class() == type_name:
|
|
return node
|
|
for child: Node in node.get_children():
|
|
var found := find_child_of_type(child, type_name)
|
|
if found != null:
|
|
return found
|
|
return null
|
|
|
|
|
|
static func find_first_mesh_with_blendshapes(
|
|
node: Node,
|
|
) -> MeshInstance3D:
|
|
if node is MeshInstance3D:
|
|
var mi: MeshInstance3D = node as MeshInstance3D
|
|
if mi.mesh != null and mi.mesh.get_blend_shape_count() > 0:
|
|
return mi
|
|
for child: Node in node.get_children():
|
|
var found := find_first_mesh_with_blendshapes(child)
|
|
if found != null:
|
|
return found
|
|
return null
|
|
|
|
|
|
static func find_blend_shape_index(mesh: MeshInstance3D, shape_name: String) -> int:
|
|
if mesh == null or mesh.mesh == null:
|
|
return -1
|
|
for i: int in range(mesh.mesh.get_blend_shape_count()):
|
|
if mesh.mesh.get_blend_shape_name(i) == shape_name:
|
|
return i
|
|
return -1
|
|
|
|
|
|
static func find_bone_case_insensitive(skeleton: Skeleton3D, bone_name: String) -> int:
|
|
var idx := skeleton.find_bone(bone_name)
|
|
if idx == -1:
|
|
idx = skeleton.find_bone(bone_name.to_lower())
|
|
if idx == -1:
|
|
idx = skeleton.find_bone(bone_name.to_upper())
|
|
return idx
|
|
|
|
|
|
static func read_vrm_morph_names(vrm_path: String) -> PackedStringArray:
|
|
## Reads original morph target names from a .vrm (GLB) file's JSON chunk.
|
|
## VRM4Godot renames blendshapes to morph_N during import, but the
|
|
## original names (e.g. Japanese MMD names) are preserved in
|
|
## mesh.primitives[].extras.targetNames inside the GLB JSON.
|
|
var f := FileAccess.open(vrm_path, FileAccess.READ)
|
|
if f == null:
|
|
return PackedStringArray()
|
|
|
|
# GLB header: magic(4) + version(4) + length(4)
|
|
f.get_32()
|
|
f.get_32()
|
|
f.get_32()
|
|
|
|
# First chunk: length(4) + type(4) + JSON data
|
|
var chunk_length := f.get_32()
|
|
f.get_32()
|
|
var json_bytes := f.get_buffer(chunk_length)
|
|
f.close()
|
|
|
|
var json := JSON.new()
|
|
if json.parse(json_bytes.get_string_from_utf8()) != OK:
|
|
return PackedStringArray()
|
|
|
|
var data: Dictionary = json.data
|
|
var meshes: Array = data.get("meshes", [])
|
|
for mesh_data: Variant in meshes:
|
|
var primitives: Array = mesh_data.get("primitives", [])
|
|
for prim: Variant in primitives:
|
|
var extras: Dictionary = prim.get("extras", {})
|
|
var target_names: Array = extras.get("targetNames", [])
|
|
if target_names.size() > 0:
|
|
var result := PackedStringArray()
|
|
for n: Variant in target_names:
|
|
result.append(str(n))
|
|
return result
|
|
|
|
return PackedStringArray()
|