From 545f6f203d6f2b332d4953c309e5aaaf80687fbd Mon Sep 17 00:00:00 2001 From: TigerKat Date: Tue, 30 Mar 2021 23:56:31 +0930 Subject: [PATCH] Now able to parse bone track data and skeleton hierarchies from .anim files. --- anim.py | 109 ++++++++++++++++++++++++++++++++++++++++++-- bones.py | 2 + compression_anim.py | 107 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 compression_anim.py diff --git a/anim.py b/anim.py index ce0e0a0..6fa2661 100644 --- a/anim.py +++ b/anim.py @@ -5,15 +5,17 @@ try: from .bones import * from .util import Data from .geomesh import GeoMesh, GeoFace, GeoVertex + from .compression_anim import * except: from bones import * from util import * from geomesh import GeoMesh, GeoFace, GeoVertex + from compression_anim import * #.anim header #Offset Size Description -#0 4 (int32) headerSize Size of this header plus BoneAnimTrack data. +#0 4 (int32) headerSize Size of this header plus BoneAnimTrack data and skeleton hierarchy data. #4 256 (asciiz) name #260 256 (asciiz) baseAnimName #516 4 (float32) max_hip_displacement @@ -45,7 +47,7 @@ except: #SkeletonHeirarchy #0 4 (int) heirarchy_root ?? First bone in the heirarchy. -#4 12*100 (BoneLink[BONES_ON_DISK]) +#4 12*100 (BoneLink[BONES_ON_DISK]) #1204 - #BONES_ON_DISK = 100 @@ -64,6 +66,43 @@ ROTATION_DELTACODED = 1 << 5 POSITION_DELTACODED = 1 << 6 ROTATION_COMPRESSED_NONLINEAR = 1 << 7 +ROTATION_MASK = ROTATION_UNCOMPRESSED | ROTATION_COMPRESSED_TO_5_BYTES | ROTATION_COMPRESSED_TO_8_BYTES | ROTATION_DELTACODED | ROTATION_COMPRESSED_NONLINEAR +POSITION_MASK = POSITION_UNCOMPRESS | POSITION_COMPRESSED_TO_6_BYTES | POSITION_DELTACODED + + +class BoneLink: + def __init__(self, data_or_child = None, next = None, boneid = None): + self.child = -1 + self.next = -1 + self.boneid = -1 + if data_or_child is None: + return + if next is None: + (self.child, self.next, self.boneid) = data_or_child.decode(" len(BONES_LIST): + print("bone_id: %s (?)" % (self.bone_id, )) + else: + print("bone_id: %s %s" % (self.bone_id, BONES_LIST[self.bone_id])) print("flags: 0x%2.2x" % (self.flags, )) print("padding: %s" % (self.padding, )) - + print("rotations: %s" % (self.rotations, )) + print("positions: %s" % (self.positions, )) + class Anim: def __init__(self): @@ -169,6 +262,11 @@ class Anim: self.data.seek(self.header_bone_tracks_offset) for i in range(self.header_bone_track_count): self.bone_tracks.append(BoneAnimTrack(self.data.read(20), self.data)) + self.skeleton_heirarchy = None + if self.header_skeleton_heirarchy_offset > 0: + self.data.seek(self.header_skeleton_heirarchy_offset) + self.skeleton_heirarchy = SkeletonHierarchy() + self.skeleton_heirarchy.parseData(self.data) pass def encode(self): self.data = None @@ -193,8 +291,11 @@ class Anim: print("Bone Tracks:") for bt in self.bone_tracks: bt.dump() + if self.skeleton_heirarchy is not None: + self.skeleton_heirarchy.dump() pass + if __name__ == "__main__": if len(sys.argv) <= 1 or len(sys.argv) > 3: print("Usage:") diff --git a/bones.py b/bones.py index 7c5dbe0..761382d 100644 --- a/bones.py +++ b/bones.py @@ -114,6 +114,8 @@ BONES_LIST = [ "Leg_R_Jet2", ] +BONES_ON_DISK = 100 + BONES_LEFT = [] BONES_RIGHT = [] BONES_SWAP = [] diff --git a/compression_anim.py b/compression_anim.py new file mode 100644 index 0000000..283a21f --- /dev/null +++ b/compression_anim.py @@ -0,0 +1,107 @@ +import math +import struct + +MAX_5_BYTE_QUATERNION = 1 / (2 ** 0.5) +SCALE_8_BYTE_QUATERNION_COMPRESS = 10000 +SCALE_8_BYTE_QUATERNION_DECOMPRESS = 1.0 / SCALE_8_BYTE_QUATERNION_COMPRESS +SCALE_6_BYTE_VECTOR3_COMPRESS = 32000 +SCALE_6_BYTE_VECTOR3_DECOMPRESS = 1.0 / SCALE_6_BYTE_VECTOR3_COMPRESS + +def findBiggest(quat): + biggest_i = -1 + biggest_v = 0 + for i, v in enumerate(quat): + if abs(v) > biggest_v: + biggest_v = abs(v) + biggest_i = i + if quat[biggest_i] < 0: + return (biggest_i, (-quat[0], -quat[1], -quat[2], -quat[3])) + else: + return (biggest_i, ( quat[0], quat[1], quat[2], quat[3])) + +def compressQuaternion_5Byte(quat): + #Find biggset value and negate the quaternion to make it positive. + missing, q = findBiggest(quat) + d = [] + for i in range(4): + if i == missing: + continue + v = int(math.round(q[i] / MAX_5_BYTE_QUATERNION * 2048)) + if v < -2048: + v = -2048 + elif v > 2047: + v = 2047 + d.append(q[i] * 2048) + v = ( + (missing << 36) | + (d[0] << 24) | + (d[1] << 12) | + d[2] + ) + s = struct.pack("> 36) & 0x3 + d = [ + (v >> 24) & 0xfff, + (v >> 12) & 0xfff, + v & 0xfff, + ] + + #Rescale values and summing the squares into x. + x = 0 + #print("%s" % (d, )) + for i in range(3): + d[i] = (d[i] - 2048) * MAX_5_BYTE_QUATERNION / 2048.0 + x += d[i] ** 2.0 + #print("%s -> %s" % (d, x)) + #Use pythagoras to compute the missing field into x. + x = (1 - x) ** 0.5 + #Rebuild the quaternion. + d_i = 0; + q = [] + for i in range(4): + if i == missing: + q.append(x) + else: + q.append(d[d_i]) + d_i += 1 + return q + +def compressQuaternion_8Byte(quat): + return struct.pack("