|
|
|
@ -9,18 +9,17 @@ var nickname: String |
|
|
|
|
|
|
|
|
|
var version: int = -1 |
|
|
|
|
var bone_lookup: Array[String] |
|
|
|
|
var blendshape_lookup: Array[String] |
|
|
|
|
var blendshape_lookup: Array[NodePath] |
|
|
|
|
|
|
|
|
|
var model_name : String |
|
|
|
|
var model : Node |
|
|
|
|
var skeleton : Skeleton3D |
|
|
|
|
var anim_player : AnimationPlayer |
|
|
|
|
var anim_root : Node |
|
|
|
|
|
|
|
|
|
# Reusable buffer to write data for synchronizing models. |
|
|
|
|
static var write_stream: StreamBuffer = StreamBuffer.with_capacity(2048) |
|
|
|
|
|
|
|
|
|
# Allows us to use the "apply_animations" function to apply blendshapes to a model. |
|
|
|
|
static var BlendShapes: Script = load("res://Mods/MediaPipe/MediaPipeController_BlendShapes.gd") |
|
|
|
|
|
|
|
|
|
func _ready() -> void: |
|
|
|
|
module = get_parent().get_parent() |
|
|
|
|
model_controller = get_parent() |
|
|
|
@ -40,7 +39,7 @@ func change_nickname(new_nickname: String) -> void: |
|
|
|
|
func change_model( |
|
|
|
|
new_version: int, |
|
|
|
|
new_bone_lookup: Array[String], |
|
|
|
|
new_blendshape_lookup: Array[String], |
|
|
|
|
new_blendshape_lookup: Array[NodePath], |
|
|
|
|
filename: String, |
|
|
|
|
) -> void: |
|
|
|
|
# These should be safe to update even if the model doesn't load. |
|
|
|
@ -63,6 +62,8 @@ func change_model( |
|
|
|
|
model_name = filename |
|
|
|
|
model = model_controller.get_node_or_null("Model") |
|
|
|
|
skeleton = model_controller._get_model_skeleton() |
|
|
|
|
anim_player = model.find_child("AnimationPlayer", false, false) |
|
|
|
|
anim_root = anim_player.get_node(anim_player.root_node) |
|
|
|
|
|
|
|
|
|
module.print_log("%s switched to '%s'" % [ get_display_name(), filename ]) |
|
|
|
|
|
|
|
|
@ -72,38 +73,41 @@ func sync_model_animation( |
|
|
|
|
buffer: PackedByteArray, |
|
|
|
|
) -> void: |
|
|
|
|
if version != current_version: return |
|
|
|
|
if (not model) or (not skeleton): return |
|
|
|
|
if (not model) or (not skeleton) or (not anim_root): return |
|
|
|
|
|
|
|
|
|
var uncompressed_buffer := buffer.decompress(uncompressed_length, FileAccess.COMPRESSION_ZSTD); |
|
|
|
|
var stream := StreamBuffer.from_buffer(uncompressed_buffer) |
|
|
|
|
|
|
|
|
|
model.transform = stream.read_transform16() |
|
|
|
|
|
|
|
|
|
# We skipped some bones, so reset the skipped ones to the rest pose. |
|
|
|
|
var all_bones := {} |
|
|
|
|
for bone_name in module.bone_to_lookup: |
|
|
|
|
var all_bones: Array[Dictionary] = [] |
|
|
|
|
var all_blendshapes: Array[Dictionary] = [] |
|
|
|
|
|
|
|
|
|
# We filter out bones at rest and blendshapes at zero, so this |
|
|
|
|
# is initializing all_bones/blendshapes to the default values. |
|
|
|
|
for bone_name in bone_lookup: |
|
|
|
|
var bone_idx := skeleton.find_bone(bone_name) |
|
|
|
|
var bone_rest := skeleton.get_bone_rest(bone_idx) |
|
|
|
|
all_bones[bone_name] = { idx = bone_idx, pose = bone_rest } |
|
|
|
|
|
|
|
|
|
# Override rest poses with ones from the packet. |
|
|
|
|
var num_bones := stream.read_uint8() |
|
|
|
|
for i in num_bones: |
|
|
|
|
var bone_name := bone_lookup[stream.read_uint8()] |
|
|
|
|
all_bones[bone_name].pose = stream.read_bone_pose() |
|
|
|
|
|
|
|
|
|
# Apply bone poses to skeleton. |
|
|
|
|
for bone_name in all_bones: |
|
|
|
|
var bone: Dictionary = all_bones[bone_name] |
|
|
|
|
all_bones.append({ name = bone_name, idx = bone_idx, pose = bone_rest }) |
|
|
|
|
for anim_path in blendshape_lookup: |
|
|
|
|
all_blendshapes.append({ path = anim_path, value = 0.0 }) |
|
|
|
|
|
|
|
|
|
# 256 bones (and blendshapes) should be enough, right? |
|
|
|
|
for i in stream.read_uint8(): |
|
|
|
|
var lookup := stream.read_uint8() |
|
|
|
|
all_bones[lookup].pose = stream.read_bone_pose() |
|
|
|
|
for i in stream.read_uint8(): |
|
|
|
|
var lookup := stream.read_uint8() |
|
|
|
|
all_blendshapes[lookup].value = stream.read_range16() |
|
|
|
|
|
|
|
|
|
# Apply all the values to bones / blendshapes. |
|
|
|
|
for bone in all_bones: |
|
|
|
|
if bone.idx == -1: continue # Different model might not have this bone. |
|
|
|
|
skeleton.set_bone_pose(bone.idx, bone.pose) |
|
|
|
|
|
|
|
|
|
var shape_dict := {} |
|
|
|
|
var num_shapes := stream.read_uint8() # 256 blendshapes (and bones) should be enough, right? |
|
|
|
|
for i in num_shapes: |
|
|
|
|
var shape_name := blendshape_lookup[stream.read_uint8()] |
|
|
|
|
var shape_alpha := stream.read_float16() |
|
|
|
|
shape_dict[shape_name] = shape_alpha |
|
|
|
|
BlendShapes.apply_animations(model, shape_dict) |
|
|
|
|
for blendshape in all_blendshapes: |
|
|
|
|
var anim_node := anim_root.get_node_or_null(blendshape.path) |
|
|
|
|
if not anim_node: continue # Different model might not have this node. |
|
|
|
|
anim_node.set("blend_shapes/" + blendshape.path.get_subname(0), blendshape.value) |
|
|
|
|
|
|
|
|
|
@warning_ignore("shadowed_variable") |
|
|
|
|
static func send_model_animation(module: copyMultiplayer) -> void: |
|
|
|
@ -112,31 +116,41 @@ static func send_model_animation(module: copyMultiplayer) -> void: |
|
|
|
|
|
|
|
|
|
var model := module.get_model() |
|
|
|
|
var skeleton := module.get_skeleton() |
|
|
|
|
if (not model) or (not skeleton): return |
|
|
|
|
var anim_player := model.find_child("AnimationPlayer", false, false) |
|
|
|
|
var anim_root := anim_player.get_node_or_null(anim_player.root_node) |
|
|
|
|
if (not model) or (not skeleton) or (not anim_root): return |
|
|
|
|
|
|
|
|
|
write_stream.write_transform16(model.transform) |
|
|
|
|
|
|
|
|
|
# Pre-filter any bones that are in rest pose. |
|
|
|
|
var restless_bones := {} |
|
|
|
|
for bone_name in module.bone_to_lookup: |
|
|
|
|
# Pre-filter any bones that are at rest / blendshapes that are at zero. |
|
|
|
|
# Unless most bones / blendshapes are active, this should reduce packet size. |
|
|
|
|
var restless_bones := [] |
|
|
|
|
var active_blendshapes := [] |
|
|
|
|
|
|
|
|
|
for i in module.bone_lookup.size(): |
|
|
|
|
var bone_name := module.bone_lookup[i] |
|
|
|
|
var bone_idx := skeleton.find_bone(bone_name) |
|
|
|
|
var bone_pose := skeleton.get_bone_pose(bone_idx) |
|
|
|
|
var bone_rest := skeleton.get_bone_rest(bone_idx) |
|
|
|
|
if not bone_pose.is_equal_approx(bone_rest): |
|
|
|
|
restless_bones[bone_name] = bone_pose |
|
|
|
|
restless_bones.append({ lookup = i, pose = bone_pose }) |
|
|
|
|
|
|
|
|
|
for i in module.blendshape_lookup.size(): |
|
|
|
|
var anim_path := module.blendshape_lookup[i] |
|
|
|
|
var anim_node := anim_root.get_node_or_null(anim_path) |
|
|
|
|
var value: float = anim_node.get("blend_shapes/" + anim_path.get_subname(0)) |
|
|
|
|
if not is_zero_approx(value): |
|
|
|
|
active_blendshapes.append({ lookup = i, value = value }) |
|
|
|
|
|
|
|
|
|
write_stream.write_uint8(restless_bones.size()) |
|
|
|
|
for bone_name in restless_bones: |
|
|
|
|
write_stream.write_uint8(module.bone_to_lookup[bone_name]) |
|
|
|
|
write_stream.write_bone_pose(restless_bones[bone_name]) |
|
|
|
|
|
|
|
|
|
# TODO: Only write non-default blendshapes. Anything missing = default. |
|
|
|
|
var shape_dict: Dictionary = {} # TODO: Redo the blendshapes. |
|
|
|
|
write_stream.write_uint8(module.blendshape_to_lookup.size()) |
|
|
|
|
for shape_name in module.blendshape_to_lookup: |
|
|
|
|
var shape_alpha: float = shape_dict[shape_name] |
|
|
|
|
write_stream.write_uint8(module.blendshape_to_lookup[shape_name]) |
|
|
|
|
write_stream.write_float16(shape_alpha) |
|
|
|
|
for bone in restless_bones: |
|
|
|
|
write_stream.write_uint8(bone.lookup) |
|
|
|
|
write_stream.write_bone_pose(bone.pose) |
|
|
|
|
|
|
|
|
|
write_stream.write_uint8(active_blendshapes.size()) |
|
|
|
|
for blendshape in active_blendshapes: |
|
|
|
|
write_stream.write_uint8(blendshape.lookup) |
|
|
|
|
write_stream.write_range16(blendshape.value) |
|
|
|
|
|
|
|
|
|
# The compression still helps, so we'll keep it for now. |
|
|
|
|
var compressed_buffer := write_stream.slice().compress(FileAccess.COMPRESSION_ZSTD); |
|
|
|
|