|
|
|
@@ -305,28 +305,26 @@ func _parse_glb_as_avatar(glb_data: PackedByteArray, path: String) -> Node3D:
|
|
|
|
|
avatar.add_child(attachments_container)
|
|
|
|
|
|
|
|
|
|
# Parse the GLB and extract skeleton + meshes.
|
|
|
|
|
# Godot's GLTFState allows custom parsing.
|
|
|
|
|
var gltf_document := GLTFDocument.new()
|
|
|
|
|
var gltf_state := GLTFState.new()
|
|
|
|
|
|
|
|
|
|
var parse_result := gltf_document.parse(glb_data, gltf_state, "", "")
|
|
|
|
|
var parse_result := gltf_document.append_from_buffer(glb_data, "", gltf_state)
|
|
|
|
|
if parse_result != OK:
|
|
|
|
|
push_error("GLB parse error: %s" % Error.parse_error(parse_result))
|
|
|
|
|
push_error("GLB parse error: %s" % error_string(parse_result))
|
|
|
|
|
avatar.queue_free()
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
# Export the scene from GLTFState.
|
|
|
|
|
var root_nodes := gltf_state.get_nodes_as_resources()
|
|
|
|
|
if root_nodes.is_empty():
|
|
|
|
|
push_error("No root nodes in GLB: %s" % path)
|
|
|
|
|
# Generate the actual scene tree from the parsed GLTF state.
|
|
|
|
|
var scene_root := gltf_document.generate_scene(gltf_state)
|
|
|
|
|
if scene_root == null:
|
|
|
|
|
push_error("Failed to generate scene from GLB: %s" % path)
|
|
|
|
|
avatar.queue_free()
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
# Merge GLB nodes into our avatar structure.
|
|
|
|
|
for node_resource in root_nodes:
|
|
|
|
|
if node_resource is Node3D:
|
|
|
|
|
_merge_node_into_avatar(node_resource, avatar, body_meshes,
|
|
|
|
|
attach_points, attachments_container)
|
|
|
|
|
# Merge GLB nodes into our avatar structure (recurses into children).
|
|
|
|
|
if scene_root is Node3D:
|
|
|
|
|
_merge_node_into_avatar(scene_root, avatar, body_meshes,
|
|
|
|
|
attach_points, attachments_container)
|
|
|
|
|
|
|
|
|
|
return avatar
|
|
|
|
|
|
|
|
|
@@ -338,22 +336,21 @@ func _parse_glb_as_attachment(glb_data: PackedByteArray, path: String,
|
|
|
|
|
var gltf_document := GLTFDocument.new()
|
|
|
|
|
var gltf_state := GLTFState.new()
|
|
|
|
|
|
|
|
|
|
var parse_result := gltf_document.parse(glb_data, gltf_state, "", "")
|
|
|
|
|
var parse_result := gltf_document.append_from_buffer(glb_data, "", gltf_state)
|
|
|
|
|
if parse_result != OK:
|
|
|
|
|
push_error("GLB parse error: %s" % Error.parse_error(parse_result))
|
|
|
|
|
push_error("GLB parse error: %s" % error_string(parse_result))
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
var root_nodes := gltf_state.get_nodes_as_resources()
|
|
|
|
|
if root_nodes.is_empty():
|
|
|
|
|
push_error("No root nodes in attachment GLB: %s" % path)
|
|
|
|
|
var scene_root := gltf_document.generate_scene(gltf_state)
|
|
|
|
|
if scene_root == null:
|
|
|
|
|
push_error("Failed to generate scene from attachment GLB: %s" % path)
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
var attachment := Node3D.new()
|
|
|
|
|
attachment.name = "%s_%s" % [slot_id, path.get_file().get_basename()]
|
|
|
|
|
|
|
|
|
|
for node_resource in root_nodes:
|
|
|
|
|
if node_resource is Node3D:
|
|
|
|
|
attachment.add_child(node_resource.duplicate())
|
|
|
|
|
if scene_root is Node3D:
|
|
|
|
|
attachment.add_child(scene_root.duplicate())
|
|
|
|
|
|
|
|
|
|
return attachment
|
|
|
|
|
|
|
|
|
@@ -392,6 +389,13 @@ func _merge_node_into_avatar(node: Node3D, avatar: Node3D,
|
|
|
|
|
var dst_parent_idx := dst_skel.find_bone(parent_name)
|
|
|
|
|
if dst_parent_idx >= 0:
|
|
|
|
|
dst_skel.set_bone_parent(new_idx, dst_parent_idx)
|
|
|
|
|
# Skinned meshes are commonly children of the skeleton node in a GLB
|
|
|
|
|
# export, not siblings — fall through to the recursion below instead
|
|
|
|
|
# of returning, so they still get merged in.
|
|
|
|
|
for child in src_skel.get_children():
|
|
|
|
|
if child is Node3D:
|
|
|
|
|
_merge_node_into_avatar(child, avatar, body_meshes,
|
|
|
|
|
attach_points, attachments_container)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Identify attachment point nodes (empty nodes named attach_*).
|
|
|
|
@@ -468,18 +472,16 @@ func _retarget_skin(attachment_node: Node3D) -> void:
|
|
|
|
|
if mesh == null:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Handle MultiMeshInstance3D.
|
|
|
|
|
if mesh is MultiMesh:
|
|
|
|
|
# MultiMesh doesn't have skin weights per-vertex in the same way.
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Note: MeshInstance3D.mesh is always a Mesh, never a MultiMesh —
|
|
|
|
|
# MultiMesh skinning goes through MultiMeshInstance3D.multimesh,
|
|
|
|
|
# a completely separate node/property, not reachable here.
|
|
|
|
|
if mesh is ArrayMesh:
|
|
|
|
|
var arr_mesh := mesh as ArrayMesh
|
|
|
|
|
var surface_count := arr_mesh.get_surface_count()
|
|
|
|
|
|
|
|
|
|
for surface_idx in surface_count:
|
|
|
|
|
var arrays := arr_mesh.surface_get_arrays(surface_idx)
|
|
|
|
|
var joints_array := arrays[Mesh.ARRAY_JOINTS] as PackedInt32Array
|
|
|
|
|
var joints_array := arrays[Mesh.ARRAY_BONES] as PackedInt32Array
|
|
|
|
|
var weights_array := arrays[Mesh.ARRAY_WEIGHTS] as PackedFloat32Array
|
|
|
|
|
|
|
|
|
|
if joints_array.is_empty():
|
|
|
|
@@ -494,10 +496,10 @@ func _retarget_skin(attachment_node: Node3D) -> void:
|
|
|
|
|
new_joints.append(0)
|
|
|
|
|
else:
|
|
|
|
|
var bone_name := skeleton.get_bone_name(old_joint)
|
|
|
|
|
var new_idx := _bone_index_map.get(bone_name, 0)
|
|
|
|
|
var new_idx: int = _bone_index_map.get(bone_name, 0)
|
|
|
|
|
new_joints.append(new_idx)
|
|
|
|
|
|
|
|
|
|
arrays[Mesh.ARRAY_JOINTS] = new_joints
|
|
|
|
|
arrays[Mesh.ARRAY_BONES] = new_joints
|
|
|
|
|
arr_mesh.surface_set_arrays(surface_idx, arrays)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|