diff --git a/anm.py b/anm.py index e9067bb..000fb80 100644 --- a/anm.py +++ b/anm.py @@ -1,267 +1,16 @@ -from lib.fileutils import * -import logging -import sys +from lib.skn_anm import * -class MConsoleHandler(logging.StreamHandler): - special_code = '[!n]' - - def emit(self, record) -> None: - record.msg = str(record.msg) - if self.special_code in record.msg: - record.msg = record.msg.replace(self.special_code, '') - self.terminator = '' - else: - self.terminator = '\n' - return super().emit(record) - - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) -cs = MConsoleHandler(sys.stdout) -fmt = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") -fmt = logging.Formatter("%(message)s") -cs.setFormatter(fmt) -logger.addHandler(cs) - -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) - - -def printdebug(msg): - logger.debug(msg) - - -def printinfo(msg): - logger.info(msg, ) - - -def tb(i): - return 1 if i > 0 else 0 - - -def read_int_and_print(n, b): - a = read_int(b) - printdebug("%s=%s" % (n, a)) - return a - - -def read_float_and_print(n, b): - a = read_float(b) - printdebug("%s=%s" % (n, a)) - return a - - -def read_byte_and_print(n, b): - a = read_byte(b) - printdebug("%s=%s" % (n, a)) - return a - - -def read_short_and_print(n, b): - a = read_short(b) - printdebug("%s=%s" % (n, a)) - return a - - -class ANM_Anim: - def __init__(self): - self.custom = 0 - self.file_version = 0 - self.image = 0 - self.memgroup = 0 - self.node_dictionary = 0 - self.tree_dictionary = 0 - self.default_obj_dictionnary = 0 - self.total_number_trees = 0 - self.total_number_objs = 0 - self.total_number_clusters = 0 - self.name = 0 - - @classmethod - def parse(cls, b): - printinfo("Decoding %s" % b.name) - - printdebug("MAP_Image") - read_int_and_print("size", b) - memgroupoffset = read_int_and_print("memgroupoffset", b) - - b.seek(memgroupoffset) - - printdebug("MAP_MemGroup") - printdebug("\tMAP_Mem") - 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) - - b.seek(base) - printdebug("=========") - printdebug("ANM_Anim") - read_int_and_print("pCustom", b) - read_int_and_print("FileVersion", b) - read_int_and_print("pImage", b) - read_int_and_print("pMemGroup", b) - starttime = read_int_and_print("StartTime", b) - endtime = read_int_and_print("EndTime", b) - ticksperframe = read_int_and_print("TicksPerFrame", b) - framerate = read_int_and_print("FrameRate", b) - tree = read_int_and_print("pTreeDictionary", b) - defaultobj = read_int_and_print("pDefaultObjDictionary", b) - numnodes = read_int_and_print("NumberOfNodes", b) - printdebug("ANM_AnimExtra") - read_int_and_print("ANM_AnimExtra->Stamp", b) - read_int_and_print("ANM_AnimExtra->Flags", b) - read_int_and_print("ANM_AnimExtra->Time1", b) - read_int_and_print("ANM_AnimExtra->Stamp1", b) - read_int_and_print("ANM_AnimExtra->Time2", b) - read_int_and_print("ANM_AnimExtra->Stamp2", b) - read_float_and_print("ANM_AnimExtra->Factor", b) - read_int_and_print("ANM_AnimExtra->OtherAnim", b) - name = read_string_until_none(b) - printdebug(name) - - printinfo("[%s] StartTime:%s EndTime:%s TicksPerFrame:%s FrameRate:%s NumNodes:%s" % (name, starttime, endtime, ticksperframe , framerate, numnodes)) - - printdebug("=========") - - printdebug("ANM_Anim->pTreeDictionary NAM_Dictionary") - b.seek(defaultobj) - read_int_and_print("pMemGroup", b) - namemapper = read_int_and_print("pNameMapper", b) - flags = read_int_and_print("Flags", b) - printdebug("Flags: NAM_FlagHasHandles: %s, NAM_FlagCaseInsensitive: %s" % (flags & NAM_FlagHasHandles, flags & NAM_FlagCaseInsensitive)) - namehandle = read_short_and_print("NameHandle", b) - - - printdebug("MAP_Image->ANM_Anim->NAM_Dictionnary->MAP_Mapper") - b.seek(namemapper) - pMemGroup = 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) - mem = 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) - pKeyContexts = read_int_and_print("pKeyContexts", b) - - printdebug("MAP_Image->ANM_Anim->NAM_Dictionnary->MAP_Mapper->MAP_KeyContext NAM_STRING_KEY") - b.seek(pKeyContexts) - read_int_and_print("\tSize", b) - read_int_and_print("\tpCallback", b) - read_int_and_print("\tIndex", b) - read_int_and_print("\tTest", b) - pLUTNAM_STRING_KEY = read_int_and_print("pLUT", b) - read_int_and_print("Reserve", b) - read_int_and_print("pData", b) - - def showdictbyindex(i): - assert(0 <= i < totalmappings) - printdebug("LUT NAM_STRING_KEY") - b.seek(pLUTNAM_STRING_KEY) - pind0_string = read_int_and_print("LUT NAM_STRING_KEY[0]", b) - for _ in range(i): - pind0_string = read_int_and_print("LUT NAM_STRING_KEY[0]", b) - printdebug("NAM_StringData") - b.seek(pind0_string) - read_int_and_print("pPtr->ANM_Obj", b) - val = read_int_and_print("Value", b) - printdebug(b.tell()) - nodename = read_string_until_none(b) - printdebug(nodename) - - b.seek(val) - - read_int_and_print("ANM_Node->Custom", b) - read_short_and_print("ANM_Node->Type", b) - read_short_and_print("ANM_Node->XformType", b) - nodeflags = read_int_and_print("ANM_Node->Flags", b) - read_int_and_print("ANM_Node->pAnim", b) - read_int_and_print("ANM_Node->pParent", b) - read_int_and_print("ANM_Node->pTarget", b) - read_int_and_print("ANM_Node->pUserCall", b) - read_int_and_print("ANM_Node->pUserContext", b) - stamp = read_int_and_print("ANM_Node->Stamp", b) - printdebug("ANM_Node->Mat") - read_int_and_print("GEO_Mat->Class", b) - for _i in range(4): - for _j in range(4): - read_float_and_print("GEO_Mat->E", b) - - printdebug("ANM_Node->Transform") - transformtime = read_int_and_print("ANM_Transform->Time", b) - printdebug("ANM_Transform->Affine") - read_float_and_print("GEO_Affine->Trans[x]", b) - read_float_and_print("GEO_Affine->Trans[y]", b) - read_float_and_print("GEO_Affine->Trans[z]", b) - read_float_and_print("GEO_Affine->Quat[x]", b) - read_float_and_print("GEO_Affine->Quat[y]", b) - read_float_and_print("GEO_Affine->Quat[z]", b) - read_float_and_print("GEO_Affine->Quat[w]", b) - read_float_and_print("GEO_Affine->Scale[x]", b) - read_float_and_print("GEO_Affine->Scale[y]", b) - read_float_and_print("GEO_Affine->Scale[z]", b) - read_int_and_print("GEO_Affine->Class", b) - - posKeys = read_int_and_print("ANM_Node->pPosKeys", b) - rotKeys = read_int_and_print("ANM_Node->pRotKeys", b) - sclKeys = read_int_and_print("ANM_Node->pSclKeys", b) - - printinfo("\t[%s] Animated:%s[!n]" % (nodename, bool(nodeflags & ANM_NODE_FLAG_ANIMATED))) - if nodeflags & ANM_NODE_FLAG_POS_ANIMATED: - printinfo(" PosAnimated:True[!n]") - if nodeflags & ANM_NODE_FLAG_ROT_ANIMATED: - printinfo(" RotAnimated:True[!n]") - if nodeflags & ANM_NODE_FLAG_ROL_ANIMATED: - printinfo(" RolAnimated:True[!n]") - if nodeflags & ANM_NODE_FLAG_SCL_ANIMATED: - printinfo(" SclAnimated:True[!n]") - if nodeflags & ANM_NODE_FLAG_AFF_ANIMATED: - printinfo(" AffAnimated:True[!n]") - if nodeflags & ANM_NODE_FLAG_USR_ANIMATED: - printinfo(" UsrAnimated:True[!n]") - printinfo("") - - """ - printinfo("\t[%s] Anim:%s Pos:%s Rot:%s Rol:%s Scl:%s Aff:%s Usr:%s" % ( - nodename, tb(nodeflags & ANM_NODE_FLAG_ANIMATED), tb(nodeflags & ANM_NODE_FLAG_POS_ANIMATED), tb(nodeflags & ANM_NODE_FLAG_ROT_ANIMATED), - tb(nodeflags & ANM_NODE_FLAG_ROL_ANIMATED), tb(nodeflags & ANM_NODE_FLAG_SCL_ANIMATED), tb(nodeflags & ANM_NODE_FLAG_AFF_ANIMATED), - tb(nodeflags & ANM_NODE_FLAG_USR_ANIMATED))) - """ - - if nodeflags & ANM_NODE_FLAG_POS_ANIMATED: - b.seek(posKeys) - read_byte_and_print("\t\tType", b) - read_byte_and_print("\t\tORTBefore", b) - read_byte_and_print("\t\tORTAfter", b) - read_byte_and_print("\t\tFlags", b) - read_int_and_print("\t\tpInterpolate", b) - read_int_and_print("\t\tNumberOfKeys", b) - read_int_and_print("\t\tpKeys", b) - - for p in range(totalmappings): - showdictbyindex(p) +def read_skn(filepath) -> AnmAnim: + with open(filepath, "rb") as fp: + r = AnmFile.parse(fp, AnmAnim) + return r def main(): - #with open("/home/tasty/Projects/gck-map-extract-objects/anm_skn/sm_book.anm", "rb") as fp: - # with open("/home/tasty/Projects/gck-map-extract-objects/anm_skn/vp_hit.anm", "rb") as fp: - with open("/home/tasty/Projects/gck-map-extract-objects/anm_skn/Mc_salute.anm", "rb") as fp: - ANM_Anim.parse(fp) + anm = read_skn("/home/tasty/Projects/gck-map-extract-objects/anm_skn/Mc_salute.anm") + pprint(anm) + if __name__ == "__main__": main() diff --git a/lib/skn_anm.py b/lib/skn_anm.py index 05d27e3..95df191 100644 --- a/lib/skn_anm.py +++ b/lib/skn_anm.py @@ -1,6 +1,12 @@ 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() NAM_FlagHasHandles = (1 << 0) @@ -15,10 +21,29 @@ ANM_NODE_FLAG_ANIMATED = ((1 << 6) - 1) @dataclass -class Vec3Indexed: +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 + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + x = read_float_and_print("Vec3->x", b) + y = read_float_and_print("Vec3->y", b) + z = read_float_and_print("Vec3->z", b) + if offset: + b.seek(old) + return cls(x=x, y=y, z=z) + + +@dataclass +class Vec3Indexed(Vec3): index: int @classmethod @@ -36,27 +61,26 @@ class Vec3Indexed: @dataclass -class Vec3: - x: float - y: float - z: float +class Vec4(Vec3): + w: float @classmethod def create(cls, b, offset=None): old = b.tell() if offset: b.seek(offset) - x = read_float_and_print("Vec3->x", b) - y = read_float_and_print("Vec3->y", b) - z = read_float_and_print("Vec3->z", 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) if offset: b.seek(old) - return cls(x=x, y=y, z=z) + return cls(x=x, y=y, z=z, w=w) @dataclass class AnmObj: - custom: int + custom: int = field(repr=False) skin: int = field(repr=False) zero_cluster: int total_number_vertices: int @@ -93,8 +117,8 @@ class AnmObj: @dataclass class AnmCluster: custom: int = field(repr=False) - obj: Union[AnmObj, None] - handle: int + 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) @@ -187,7 +211,233 @@ class NamDictionnary: 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)] = {"RefCnt": refcnt, "Handle": handle, "BoneName": bone_name} + r[str(i)] = {"BoneName": bone_name} + + if offset: + b.seek(old) + + return r + + +@dataclass +class GeoMat4x4: + m: List[float] + + @classmethod + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + m = [] + for _ in range(4): + for _j in range(4): + m.append(read_float(b)) + + r = cls(m=m) + + if offset: + b.seek(old) + + return r + + +@dataclass +class GeoMat: + mat_class: int + s: GeoMat4x4 + + @classmethod + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + mat_class = read_int(b) + s = GeoMat4x4.create(b) + + r = cls(mat_class=mat_class, s=s) + + if offset: + b.seek(old) + + return r + + +@dataclass +class GeoAffine: + transform: Vec3 + quat: Vec4 + scale: Vec3 + affine_class: int + + @classmethod + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + trans = Vec3.create(b) + quat = Vec4.create(b) + scale = Vec3.create(b) + affine_class = read_int(b) + + r = cls(transform=trans, quat=quat, scale=scale, affine_class=affine_class) + + if offset: + b.seek(old) + + return r + + +@dataclass +class AnmTransform: + time: int + affine: GeoAffine + + @classmethod + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + time = read_int(b) + affine = GeoAffine.create(b) + + r = cls(time=time, affine=affine) + + if offset: + b.seek(old) + + return r + + +@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: int + rot_keys: int + scl_keys: int + + @classmethod + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + 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 nodeflags & ANM_NODE_FLAG_POS_ANIMATED: + b.seek(posKeys) + read_byte_and_print("\t\tType", b) + read_byte_and_print("\t\tORTBefore", b) + read_byte_and_print("\t\tORTAfter", b) + read_byte_and_print("\t\tFlags", b) + read_int_and_print("\t\tpInterpolate", b) + read_int_and_print("\t\tNumberOfKeys", b) + read_int_and_print("\t\tpKeys", b) + """ + + r = 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) + + if offset: + b.seek(old) + + return r + + +@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 + def create(cls, b, offset=None): + old = b.tell() + if offset: + b.seek(offset) + + 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(b, AnmNode, depth=2, offset=treedict) + defaultobjdict = NamDictionnary.create(b, AnmNode, offset=defaultobjdict) + + r = 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) if offset: b.seek(old) @@ -201,9 +451,9 @@ class AnmSkin: file_version: int image: int memgroup: int - node_dictionnary: List[List] - tree_dictionnary: List[List[AnmObj]] - default_obj_dictionnary: List[AnmObj] + node_dictionnary: dict + tree_dictionnary: dict + default_obj_dictionnary: dict total_number_trees: int total_number_objs: int total_number_clusters: int @@ -239,7 +489,7 @@ class AnmSkin: treedict = NamDictionnary.create(b, AnmObj, depth=2, offset=tree) defaultobjdict = NamDictionnary.create(b, AnmObj, offset=defaultobj) - AnmSkin.link(nodedict, defaultobjdict) + # AnmSkin.link(nodedict, defaultobjdict) r = 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) if offset: diff --git a/skn.py b/skn.py index 9ee9516..e5915f8 100644 --- a/skn.py +++ b/skn.py @@ -1,18 +1,27 @@ from lib.skn_anm import * -def read_skn(filepath): +def read_skn(filepath) -> AnmSkin: with open(filepath, "rb") as fp: r = AnmFile.parse(fp, AnmSkin) - print(r) + return r def main(): - f = ["/home/tasty/Projects/gck-map-extract-objects/anm_skn/mc_l0.skn", - "/home/tasty/Projects/gck-map-extract-objects/anm_skn/rp_l0.skn", - "/home/tasty/Projects/gck-map-extract-objects/anm_skn/kb_l0.skn"] + f = ["/home/tasty/Projects/gck-map-extract-objects/anm_skn/rp_l0.skn"] + my_vertex = Vec3(-0.927200, 0.099500, 3.108000) + mini_dist_v = Vec3(0, 0, 0) for p in f: - read_skn(p) + skn = read_skn(p) + pprint(skn.default_obj_dictionnary) + pprint(skn.node_dictionnary) + for obj_name, obj in skn.default_obj_dictionnary.items(): + for cluster in obj.clusters: + for vertex in cluster.vertices: + if vertex.distance(my_vertex) < mini_dist_v.distance(my_vertex): + mini_dist_v = vertex + + print("%s: dist=%s" % (mini_dist_v, mini_dist_v.distance(my_vertex))) if __name__ == "__main__":