SWBF-msh-Blender-IO/addons/io_scene_swbf_msh/zaa_to_blend.py

358 lines
11 KiB
Python

import os
import bpy
import re
from .zaa_reader import *
from .crc import *
from .msh_model import *
from .msh_model_utilities import *
from .msh_utilities import *
from typing import List, Set, Dict, Tuple
#anims #bones #comps #keyframes: index,value
def decompress_curves(input_file) -> Dict[int, Dict[int, List[ Dict[int,float]]]]:
decompressed_anims: Dict[int, Dict[int, List[ Dict[int,float]]]] = {}
with ZAAReader(input_file) as head:
head.skip_until("SMNA")
head.skip_bytes(20)
num_anims = head.read_u16()
print("\nFile contains {} animations\n".format(num_anims))
head.skip_bytes(2)
anim_crcs = []
anim_metadata = {}
with head.read_child() as mina:
for i in range(num_anims):
mina.skip_bytes(8)
anim_hash = mina.read_u32()
anim_crcs += [anim_hash]
anim_data = {}
anim_data["num_frames"] = mina.read_u16()
anim_data["num_bones"] = mina.read_u16()
anim_metadata[anim_hash] = anim_data
with head.read_child() as tnja:
for i,anim_crc in enumerate(anim_crcs):
bone_params = {}
for _ in range(anim_metadata[anim_crc]["num_bones"]):
bone_hash = tnja.read_u32()
rot_offsets = [tnja.read_u32() for _ in range(4)]
loc_offsets = [tnja.read_u32() for _ in range(3)]
qparams = [tnja.read_f32() for _ in range(4)]
params = {"rot_offsets" : rot_offsets, "loc_offsets" : loc_offsets, "qparams" : qparams}
bone_params[bone_hash] = params
anim_metadata[anim_crc]["bone_params"] = bone_params
with head.read_child() as tada:
for anim_crc in anim_crcs:
decompressed_anims[anim_crc] = {}
num_frames = anim_metadata[anim_crc]["num_frames"]
num_bones = anim_metadata[anim_crc]["num_bones"]
#print("\n\tAnim hash: {} Num frames: {} Num joints: {}".format(hex(anim_crc), num_frames, num_bones))
#num_frames = 5
for bone_num, bone_hash in enumerate(anim_metadata[anim_crc]["bone_params"]):
keyframes = []
params_bone = anim_metadata[anim_crc]["bone_params"][bone_hash]
offsets_list = params_bone["rot_offsets"] + params_bone["loc_offsets"]
qparams = params_bone["qparams"]
#print("\n\t\tBone #{} hash: {}".format(bone_num,hex(bone_hash)))
#print("\n\t\tQParams: {}, {}, {}, {}".format(*qparams))
for o,start_offset in enumerate(offsets_list):
tada.skip_bytes(start_offset)
curve = {}
val = 0.0
if o < 4:
mult = 1 / 2047
offset = 0.0
else:
mult = qparams[-1]
offset = qparams[o - 4]
#print("\n\t\t\tBias = {}, multiplier = {}".format(offset, mult))
#print("\n\t\t\tOffset {}: {} ({}, {} remaining)".format(o,start_offset, tada.get_current_pos(), tada.how_much_left(tada.get_current_pos())))
j = 0
exit_loop = False
while (j < num_frames and not exit_loop):
val = offset + mult * tada.read_i16()
curve[j if j < num_frames else num_frames] = val
#print("\t\t\t\t{}: {}".format(j, val))
j+=1
if (j >= num_frames):
break
while (True):
if (j >= num_frames):
exit_loop = True
break
control = tada.read_i8()
if control == 0x00:
#curve[j if j < num_frames else num_frames] = val
#print("\t\t\t\tControl: HOLDING FOR A FRAME")
#print("\t\t\t\t{}: {}".format(j, val))
j+=1
if (j >= num_frames):
break
elif control == -0x7f:
#print("\t\t\t\tControl: READING NEXT FRAME")
break #get ready for new frame
elif control == -0x80:
num_skips = tada.read_u8()
#print("\t\t\t\tControl: HOLDING FOR {} FRAMES".format(num_skips))
for _ in range(num_skips):
j+=1
if (j >= num_frames):
break
else:
val += mult * float(control)
curve[j if j < num_frames else num_frames] = val
#print("\t\t\t\t{}: {}".format(j, val))
j+=1
curve[num_frames - 1] = val
tada.reset_pos()
keyframes.append(curve)
decompressed_anims[anim_crc][bone_hash] = keyframes
return decompressed_anims
def read_anims_file(anims_file_path):
if not os.path.exists(anims_file_path):
return None
anims_text = ""
with open(anims_file_path, 'r') as file:
anims_text = file.read()
splits = anims_text.split('"')
if len(splits) > 1:
return splits[1:-1:2]
return None
def extract_and_apply_munged_anim(input_file_path):
with open(input_file_path,"rb") as input_file:
discrete_curves = decompress_curves(input_file)
anim_names = None
if input_file_path.endswith(".zaabin"):
anim_names = read_anims_file(input_file_path.replace(".zaabin", ".anims"))
arma = bpy.context.view_layer.objects.active
if arma.type != 'ARMATURE':
raise Exception("Select an armature to attach the imported animation to!")
if arma.animation_data is not None:
arma.animation_data_clear()
arma.animation_data_create()
bone_bind_poses = {}
for bone in arma.data.bones:
bone_obj = bpy.data.objects[bone.name]
bone_obj_parent = bone_obj.parent
bind_mat = bone_obj.matrix_local
stack_mat = Matrix.Identity(4)
while(True):
if bone_obj_parent is None or bone_obj_parent.name in arma.data.bones:
break
bind_mat = bone_obj_parent.matrix_local @ bind_mat
stack_mat = bone_obj_parent.matrix_local @ stack_mat
bone_obj_parent = bone_obj_parent.parent
bone_bind_poses[bone.name] = bind_mat.inverted() @ stack_mat
for anim_crc in discrete_curves:
anim_str = str(hex(anim_crc))
if anim_names is not None:
for anim_name in anim_names:
if anim_crc == crc(anim_name):
anim_str = anim_name
#if crc(anim_name) not in discrete_curves:
# continue
#print("\nExtracting anim: " + anim_crc_str)
if anim_str in bpy.data.actions:
bpy.data.actions[anim_str].use_fake_user = False
bpy.data.actions.remove(bpy.data.actions[anim_str])
action = bpy.data.actions.new(anim_str)
action.use_fake_user = True
anim_curves = discrete_curves[anim_crc]
for bone in arma.pose.bones:
bone_crc = crc(bone.name)
#print("\tGetting curves for bone: " + bone.name)
if bone_crc not in anim_curves:
continue;
bind_mat = bone_bind_poses[bone.name]
loc_data_path = "pose.bones[\"{}\"].location".format(bone.name)
rot_data_path = "pose.bones[\"{}\"].rotation_quaternion".format(bone.name)
bone_curves = anim_curves[bone_crc]
num_frames = max(bone_curves[0])
#print("\t\tNum frames: " + str(num_frames))
last_values = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
def get_quat(index):
nonlocal bone_curves, last_values
q = Quaternion()
valmap = [1,2,3,0]
#valmap = [0,1,2,3]
has_key = False
for i in range(4):
curve = bone_curves[i]
if index in curve:
has_key = True
last_values[i] = curve[index]
q[valmap[i]] = last_values[i]
return q if has_key else None
def get_vec(index):
nonlocal bone_curves, last_values
v = Vector()
has_key = False
for i in range(4,7):
curve = bone_curves[i]
if index in curve:
has_key = True
last_values[i] = curve[index]
v[i - 4] = last_values[i]
return v if has_key else None
fcurve_rot_w = action.fcurves.new(rot_data_path, index=0, action_group=bone.name)
fcurve_rot_x = action.fcurves.new(rot_data_path, index=1, action_group=bone.name)
fcurve_rot_y = action.fcurves.new(rot_data_path, index=2, action_group=bone.name)
fcurve_rot_z = action.fcurves.new(rot_data_path, index=3, action_group=bone.name)
fcurve_loc_x = action.fcurves.new(loc_data_path, index=0, action_group=bone.name)
fcurve_loc_y = action.fcurves.new(loc_data_path, index=1, action_group=bone.name)
fcurve_loc_z = action.fcurves.new(loc_data_path, index=2, action_group=bone.name)
for frame in range(num_frames):
q = get_quat(frame)
if q is not None:
q = (bind_mat @ convert_rotation_space(q).to_matrix().to_4x4()).to_quaternion()
fcurve_rot_w.keyframe_points.insert(frame,q.w)
fcurve_rot_x.keyframe_points.insert(frame,q.x)
fcurve_rot_y.keyframe_points.insert(frame,q.y)
fcurve_rot_z.keyframe_points.insert(frame,q.z)
t = get_vec(frame)
if t is not None:
t = (bind_mat @ Matrix.Translation(convert_vector_space(t))).translation
fcurve_loc_x.keyframe_points.insert(frame,t.x)
fcurve_loc_y.keyframe_points.insert(frame,t.y)
fcurve_loc_z.keyframe_points.insert(frame,t.z)
arma.animation_data.action = action