gck-map-extract-objects/lib/skn_anm.py

489 lines
16 KiB
Python

from lib.fileutils import *
from dataclasses import dataclass, field
from typing import List, Union
import math
import prettyprinter
from prettyprinter import pprint
prettyprinter.install_extras()
def with_offset(func):
def wrapped_do_offset(*args, **kwargs):
offset = kwargs.pop("offset", None)
b = args[1]
old = b.tell()
if offset:
b.seek(offset)
r = func(*args, **kwargs)
if offset:
b.seek(old)
return r
return wrapped_do_offset
NAM_FlagHasHandles = (1 << 0)
NAM_FlagCaseInsensitive = (1 << 1)
ANM_NODE_FLAG_POS_ANIMATED = (1 << 0)
ANM_NODE_FLAG_ROT_ANIMATED = (1 << 1)
ANM_NODE_FLAG_ROL_ANIMATED = (1 << 2)
ANM_NODE_FLAG_SCL_ANIMATED = (1 << 3)
ANM_NODE_FLAG_AFF_ANIMATED = (1 << 4)
ANM_NODE_FLAG_USR_ANIMATED = (1 << 5)
ANM_NODE_FLAG_ANIMATED = ((1 << 6) - 1)
@dataclass
class Vec3:
x: float
y: float
z: float
def distance(self, other: 'Vec3') -> float:
return math.sqrt(math.pow(self.x - other.x, 2) + math.pow(self.y - other.y, 2) + math.pow(self.z - other.z, 2))
@classmethod
@with_offset
def create(cls, b):
x = read_float_and_print("Vec3->x", b)
y = read_float_and_print("Vec3->y", b)
z = read_float_and_print("Vec3->z", b)
return cls(x=x, y=y, z=z)
@dataclass
class Vec3Indexed(Vec3):
index: int
@classmethod
@with_offset
def create(cls, b):
x = read_float_and_print("Vec3->x", b)
y = read_float_and_print("Vec3->y", b)
z = read_float_and_print("Vec3->z", b)
index = read_int_and_print("Vec3->index", b)
return cls(x=x, y=y, z=z, index=index)
@dataclass
class Vec4(Vec3):
w: float
@classmethod
@with_offset
def create(cls, b):
x = read_float_and_print("Vec4->x", b)
y = read_float_and_print("Vec4->y", b)
z = read_float_and_print("Vec4->z", b)
w = read_float_and_print("Vec4->w", b)
return cls(x=x, y=y, z=z, w=w)
@dataclass
class AnmKey:
time: int
@classmethod
@with_offset
def create(cls, b):
time = read_byte_and_print("ANM_Key->Time", b)
return cls(time=time)
@dataclass
class AnmKeys:
keys_type: int
ort_before: int
ort_after: int
flags: int
p_interpolate: int
num_keys: int
keys: list
@classmethod
@with_offset
def create(cls, b):
keys_type = read_byte_and_print("ANM_Keys->Type", b)
ort_before = read_byte_and_print("ANM_Keys->ORTBefore", b)
ort_after = read_byte_and_print("ANM_Keys->ORTAfter", b)
flags = read_byte_and_print("ANM_Keys->Flags", b)
p_interpolate = read_int_and_print("ANM_Keys->Interpolate", b)
num_keys = read_int_and_print("ANM_Keys->NumKeys", b)
p_keys = read_int_and_print("ANM_Keys->Keys", b)
keys = []
_old = b.tell()
b.seek(p_keys)
for _ in range(num_keys):
keys.append(AnmKey.create(b))
b.seek(_old)
return cls(keys_type=keys_type, ort_before=ort_before, ort_after=ort_after, flags=flags,
p_interpolate=p_interpolate, num_keys=num_keys, keys=keys)
@dataclass
class AnmObj:
custom: int = field(repr=False)
skin: int = field(repr=False)
zero_cluster: int
total_number_vertices: int
num_clusters: int
clusters: List['AnmCluster']
@classmethod
@with_offset
def create(cls, b):
custom = read_int_and_print("ANM_Obj->Custom", b)
skin = read_int_and_print("ANM_Obj->Skin", b)
zero = read_int_and_print("ANM_Obj->ZeroCluster", b)
total_vertices = read_int_and_print("ANM_Obj->TotalNumberOfVertices", b)
num_clusters = read_int_and_print("ANM_Obj->NumberOfClusters", b)
clusters_offset = read_int_and_print("ANM_Obj->Clusters", b)
clusters = []
_old1 = b.tell()
b.seek(clusters_offset)
for _ in range(num_clusters):
cluster = AnmCluster.create(b)
clusters.append(cluster)
b.seek(_old1)
r = cls(custom=custom, skin=skin, zero_cluster=zero, total_number_vertices=total_vertices,
num_clusters=num_clusters, clusters=clusters)
for cluster in clusters:
cluster.obj = r
return r
@dataclass
class AnmCluster:
custom: int = field(repr=False)
obj: Union[AnmObj, None] = field(repr=False)
handle: int = field(repr=False)
bounding_box: List[Vec3] = field(repr=False) # size 2
num_vertices: int
vertices: List[Vec3Indexed] = field(repr=False)
bone_name: str = ""
@classmethod
@with_offset
def create(cls, b):
custom = read_int_and_print("ANM_Cluster->Custom", b)
obj_offset = read_int_and_print("ANM_Cluster->Obj", b)
if obj_offset == 0: # ugly hack, dunno why we need that
custom = read_int_and_print("ANM_Cluster->Custom", b)
obj_offset = read_int_and_print("ANM_Cluster->Obj", b)
obj = None # is set by parent Obj
handle = read_int_and_print("ANM_Cluster->Handle", b)
bbox1 = Vec3.create(b)
bbox2 = Vec3.create(b)
bounding_box = [bbox1, bbox2]
num_vertices = read_int_and_print("ANM_Cluster->num_vertices", b)
vertices = []
for _ in range(num_vertices):
v = Vec3Indexed.create(b)
vertices.append(v)
return cls(custom=custom, obj=obj, handle=handle, bounding_box=bounding_box, num_vertices=num_vertices,
vertices=vertices)
class NamDictionnary:
@staticmethod
@with_offset
def create(cls, b, depth=1):
r = {}
read_int_and_print("NAM_Dictionnary->pMemGroup", b)
namemapper = read_int_and_print("NAM_Dictionnary->pNameMapper", b)
flags = read_int_and_print("NAM_Dictionnary->Flags", b)
print("Flags: NAM_FlagHasHandles: %s, NAM_FlagCaseInsensitive: %s" % (
flags & NAM_FlagHasHandles, flags & NAM_FlagCaseInsensitive))
read_short_and_print("NAM_Dictionnary->NameHandle", b)
b.seek(namemapper)
read_int_and_print("pMemGroup", b)
read_int_and_print("MemBaseSize", b)
read_int_and_print("MemBlocks", b)
read_int_and_print("MemSize", b)
read_int_and_print("MemFree", b)
read_int_and_print("pMem", b)
read_int_and_print("MaxMappings", b)
totalmappings = read_int_and_print("TotalMappings", b)
read_int_and_print("TotalKeys", b)
p_key_contexts = read_int_and_print("pKeyContexts", b)
b.seek(p_key_contexts)
_ = read_int_and_print("\tSize", b)
read_int_and_print("\tpCallback", b)
read_int_and_print("\tIndex", b)
read_int_and_print("\tTest", b)
p_lut = read_int_and_print("pLUT", b)
read_int_and_print("Reserve", b)
read_int_and_print("pData", b)
for i in range(totalmappings):
b.seek(p_lut)
pindx = read_int(b)
for _ in range(i):
pindx = read_int(b)
b.seek(pindx)
if not flags & NAM_FlagHasHandles:
read_short_and_print("pPtr", b)
read_short_and_print("pPtr2", b)
val = read_int_and_print("Value", b)
name = read_string_until_none_and_print("name", b)
if depth > 1:
item = NamDictionnary.create(cls, b, depth - 1, offset=val)
else:
item = cls.create(b, offset=val)
r[name] = item
else:
refcnt = read_short_and_print("RefCnt", b)
handle = read_short_and_print("Handle", b)
read_int_and_print("Test", b)
read_int_and_print("Test", b)
bone_name = read_string_until_none_and_print("BoneName", b)
r[str(i)] = {"BoneName": bone_name}
return r
@dataclass
class GeoMat4x4:
m: List[float]
@classmethod
@with_offset
def create(cls, b):
m = []
for _ in range(4):
for _j in range(4):
m.append(read_float(b))
return cls(m=m)
@dataclass
class GeoMat:
mat_class: int
s: GeoMat4x4
@classmethod
@with_offset
def create(cls, b):
mat_class = read_int(b)
s = GeoMat4x4.create(b)
return cls(mat_class=mat_class, s=s)
@dataclass
class GeoAffine:
transform: Vec3
quat: Vec4
scale: Vec3
affine_class: int
@classmethod
@with_offset
def create(cls, b):
trans = Vec3.create(b)
quat = Vec4.create(b)
scale = Vec3.create(b)
affine_class = read_int(b)
return cls(transform=trans, quat=quat, scale=scale, affine_class=affine_class)
@dataclass
class AnmTransform:
time: int
affine: GeoAffine
@classmethod
@with_offset
def create(cls, b):
time = read_int(b)
affine = GeoAffine.create(b)
return cls(time=time, affine=affine)
@dataclass
class AnmNode:
custom: int
node_type: int
x_form_type: int
flags: int
p_anim: int
p_parent: int
p_target: int
p_usercall: int
p_usercontext: int
stamp: int
geo_mat: GeoMat
transform: AnmTransform
pos_keys: Union[AnmKeys, int]
rot_keys: Union[AnmKeys, int]
scl_keys: Union[AnmKeys, int]
@classmethod
@with_offset
def create(cls, b):
custom = read_int_and_print("ANM_Node->Custom", b)
node_type = read_short_and_print("ANM_Node->Type", b)
x_form_type = read_short_and_print("ANM_Node->XformType", b)
flags = read_int_and_print("ANM_Node->Flags", b)
p_anim = read_int_and_print("ANM_Node->pAnim", b)
p_parent = read_int_and_print("ANM_Node->pParent", b)
p_target = read_int_and_print("ANM_Node->pTarget", b)
p_usercall = read_int_and_print("ANM_Node->pUserCall", b)
p_usercontext = read_int_and_print("ANM_Node->pUserContext", b)
stamp = read_int_and_print("ANM_Node->Stamp", b)
geo_mat = GeoMat.create(b)
transform = AnmTransform.create(b)
pos_keys = read_int_and_print("ANM_Node->pPosKeys", b)
rot_keys = read_int_and_print("ANM_Node->pRotKeys", b)
scl_keys = read_int_and_print("ANM_Node->pSclKeys", b)
if pos_keys > 0:
pos_keys = AnmKeys.create(b, offset=pos_keys)
if rot_keys > 0:
rot_keys = AnmKeys.create(b, offset=rot_keys)
if scl_keys > 0:
scl_keys = AnmKeys.create(b, offset=scl_keys)
return cls(custom=custom, node_type=node_type, x_form_type=x_form_type, flags=flags, p_anim=p_anim,
p_parent=p_parent, p_target=p_target, p_usercall=p_usercall, p_usercontext=p_usercontext,
stamp=stamp, geo_mat=geo_mat, transform=transform, pos_keys=pos_keys, rot_keys=rot_keys,
scl_keys=scl_keys)
@dataclass
class AnmAnim:
custom: int
fileversion: int
image: int
memgroup: int
start_time: int
end_time: int
ticks_per_frame: int
framerate: int
tree_dictionnary: dict
default_obj_dictionnary: dict
num_nodes: int
extra_stamp: int
extra_flags: int
extra_time1: int
extra_stamp1: int
extra_time2: int
extra_stamp2: int
extra_factor: float
extra_other_anim: int
name: str
@classmethod
@with_offset
def create(cls, b):
custom = read_int_and_print("ANM_Anim->Custom", b)
version = read_int_and_print("ANM_Anim->FileVersion", b)
image = read_int_and_print("ANM_Anim->pImage", b)
memgroup = read_int_and_print("ANM_Anim->pMemGroup", b)
start_time = read_int_and_print("ANM_Anim->StartTime", b)
end_time = read_int_and_print("ANM_Anim->EndTime", b)
ticks_per_frame = read_int_and_print("TicksPerFrame", b)
framerate = read_int_and_print("FrameRate", b)
treedict = read_int_and_print("pTreeDictionary", b)
defaultobjdict = read_int_and_print("pDefaultObjDictionary", b)
num_nodes = read_int_and_print("NumberOfNodes", b)
stamp = read_int_and_print("ANM_AnimExtra->Stamp", b)
flags = read_int_and_print("ANM_AnimExtra->Flags", b)
time1 = read_int_and_print("ANM_AnimExtra->Time1", b)
stamp1 = read_int_and_print("ANM_AnimExtra->Stamp1", b)
time2 = read_int_and_print("ANM_AnimExtra->Time2", b)
stamp2 = read_int_and_print("ANM_AnimExtra->Stamp2", b)
factor = read_float_and_print("ANM_AnimExtra->Factor", b)
otheranim = read_int_and_print("ANM_AnimExtra->OtherAnim", b)
name = read_string_until_none(b)
treedict = NamDictionnary.create(AnmNode, b, depth=2, offset=treedict)
defaultobjdict = NamDictionnary.create(AnmNode, b, offset=defaultobjdict)
return cls(custom=custom, fileversion=version, image=image, memgroup=memgroup, start_time=start_time,
end_time=end_time, ticks_per_frame=ticks_per_frame, framerate=framerate, tree_dictionnary=treedict,
default_obj_dictionnary=defaultobjdict, num_nodes=num_nodes, extra_stamp=stamp, extra_flags=flags,
extra_time1=time1, extra_stamp1=stamp1, extra_time2=time2, extra_stamp2=stamp2, extra_factor=factor,
extra_other_anim=otheranim, name=name)
@dataclass
class AnmSkin:
custom: int
file_version: int
image: int
memgroup: int
node_dictionnary: dict
tree_dictionnary: dict
default_obj_dictionnary: dict
total_number_trees: int
total_number_objs: int
total_number_clusters: int
name: str
@staticmethod
def link(nodedict, defaultobjtree):
for obj_name, obj in defaultobjtree.items():
for cluster in obj.clusters:
cluster_handle = cluster.handle
k = str(cluster_handle - 1)
cluster.bone_name = nodedict[k]["BoneName"]
@classmethod
@with_offset
def create(cls, b):
custom = read_int_and_print("pCustom", b)
version = read_int_and_print("FileVersion", b)
image = read_int_and_print("pImage", b)
memgroup = read_int_and_print("pMemGroup", b)
node = read_int_and_print("pNodeDictionary", b)
tree = read_int_and_print("pTreeDictionary", b)
defaultobj = read_int_and_print("pDefaultObjDictionary", b)
num_trees = read_int_and_print("TotalNumberOfTrees", b)
num_objs = read_int_and_print("TotalNumberOfObjs", b)
num_clusters = read_int_and_print("TotalNumberOfClusters", b)
name = read_string_until_none_and_print("Name", b)
nodedict = NamDictionnary.create(AnmObj, b, offset=node)
treedict = NamDictionnary.create(AnmObj, b, depth=2, offset=tree)
defaultobjdict = NamDictionnary.create(AnmObj, b, offset=defaultobj)
# AnmSkin.link(nodedict, defaultobjdict)
return cls(custom=custom, file_version=version, image=image, memgroup=memgroup, node_dictionnary=nodedict,
tree_dictionnary=treedict, default_obj_dictionnary=defaultobjdict, total_number_trees=num_trees,
total_number_objs=num_objs, total_number_clusters=num_clusters, name=name)
@dataclass
class AnmFile:
@staticmethod
def parse(b, cls):
_ = read_int_and_print("size", b)
memgroupoffset = read_int_and_print("memgroupoffset", b)
b.seek(memgroupoffset)
read_int_and_print("\tNode", b)
read_int_and_print("\tSize", b)
read_int_and_print("\tFlags", b)
read_int_and_print("pMemGroup", b)
read_int_and_print("MemList", b)
read_int_and_print("TotalSize", b)
read_int_and_print("TotalBlocks", b)
base = read_int_and_print("pBase", b)
return cls.create(b, offset=base)