from lib.fileutils import * from dataclasses import dataclass, field from typing import List, Union, Optional import math import prettyprinter from prettyprinter import pprint import sys prettyprinter.install_extras(exclude=["ipython", "django", "ipython_repr_pretty", "attrs"]) 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) NAM_STRING_KEY = 0 NAM_HANDLE_KEY = 1 ANM_CLUSTER_ARRAY_SIZE = 128 ANM_BINDING_ARRAY_SIZE = ANM_CLUSTER_ARRAY_SIZE ANM_XFORM_TYPE_NONE = 0 ANM_XFORM_TYPE_SAMPLE = 1 ANM_XFORM_TYPE_PRS = 2 ANM_XFORM_TYPE_LOOKAT = 3 ANM_KEYS_ORT_ILLEGAL = 0 ANM_KEYS_ORT_CONSTANT = 1 ANM_KEYS_ORT_CYCLE = 2 ANM_KEYS_ORT_LOOP = 3 # This is cycle with continuity. ANM_KEYS_ORT_OSCILLATE = 4 ANM_KEYS_ORT_LINEAR = 5 ANM_KEYS_ORT_IDENTITY = 6 ANM_KEYS_ORT_RELATIVE = 7 GEO_MAT_ROTATION = (1 << 0) GEO_MAT_SCALE = (1 << 1) GEO_MAT_SHEAR = (1 << 2) GEO_MAT_SIGN = (1 << 3) GEO_MAT_TRANSLATION = (1 << 4) GEO_MAT_PROJECTION = (1 << 5) GEO_MAT_ORTHO = (GEO_MAT_ROTATION | GEO_MAT_SIGN) GEO_MAT_ORTHO_AFFINE = (GEO_MAT_ORTHO | GEO_MAT_TRANSLATION) GEO_MAT_LINEAR = (GEO_MAT_ORTHO | GEO_MAT_SCALE | GEO_MAT_SHEAR) GEO_MAT_AFFINE = (GEO_MAT_LINEAR | GEO_MAT_TRANSLATION) GEO_MAT_GENERAL = (GEO_MAT_AFFINE | GEO_MAT_PROJECTION) QUAT_COS_EPSILON = 0.000001 class ANM_KeyType: @classmethod def get_name(cls, i): for d in cls.__dict__: if cls.__dict__[d] == i: return d return None ANM_KEYS_TYPE_NULL = 0 ANM_KEYS_TYPE_REAL_TCB = 1 ANM_KEYS_TYPE_REAL_BEZ = 2 ANM_KEYS_TYPE_REAL_LIN = 3 ANM_KEYS_TYPE_VEC3_TCB = 4 ANM_KEYS_TYPE_VEC3_BEZ = 5 ANM_KEYS_TYPE_VEC3_LIN = 6 ANM_KEYS_TYPE_QUAT_TCB = 7 ANM_KEYS_TYPE_QUAT_BEZ = 8 ANM_KEYS_TYPE_QUAT_LIN = 9 ANM_KEYS_TYPE_AFFINE_SAM = 10 class ANM_KeyFlags: @classmethod def get_name(cls, i): for d in cls.__dict__: if cls.__dict__[d] == i: return d return None ANM_KEYS_FLAGS_CLOSED = (1 << 0) ANM_KEYS_FLAGS_ORT_DISABLE = (1 << 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)) @staticmethod def dot(a: "Vec3", b: "Vec3") -> float: return a.x * b.x + a.y * b.y + a.z * b.z @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) def __add__(self, other): return Vec3(self.x + other.x, self.y + other.y, self.z + other.z) def __sub__(self, other): return self + -other def __radd__(self, other): return Vec3(self.x + other.x, self.y + other.y, self.z + other.z) def __mul__(self, scalar): return Vec3(self.x * scalar, self.y * scalar, self.z * scalar) def __rmul__(self, scalar): return Vec3(self.x * scalar, self.y * scalar, self.z * scalar) def __neg__(self): return Vec3(-self.x, -self.y, -self.z) def __pos__(self): return Vec3(self.x, self.y, self.z) def __xor__(self, other): cx = self.y * other.z - self.z * other.y cy = self.z * other.x - self.x * other.z cz = self.x * other.y - self.y * other.x return Vec3(cx, cy, cz) def cross(self, other): return self ^ other @dataclass class Vec3Indexed(Vec3): index: int @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec3Indexed->x", b) y = read_float_and_print("Vec3Indexed->y", b) z = read_float_and_print("Vec3Indexed->z", b) index = read_int_and_print("Vec3Indexed->index", b) return cls(x=x, y=y, z=z, index=index) @dataclass class Vec3IndexedWeighted(Vec3Indexed): weight: float @classmethod @with_offset def create(cls, b): x = read_float_and_print("Vec3IndexedWeighted->x", b) y = read_float_and_print("Vec3IndexedWeighted->y", b) z = read_float_and_print("Vec3IndexedWeighted->z", b) index = read_int_and_print("Vec3IndexedWeighted->index", b) weight = read_float_and_print("Vec3IndexedWeighted->weight", b) return cls(x=x, y=y, z=z, index=index, weight=weight) @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) @staticmethod def squad(p: "Quat", a: "Quat", b: "Quat", q: "Quat", t: float) -> "Quat": # GEO_QuatSquad k = 2 * (1 - t) * t pq = Quat.slerp(p, q, t) ab = Quat.slerp(a, b, t) return Quat.slerp(pq, ab, k) @staticmethod def slerp(p: "Quat", q: "Quat", t: float) -> "Quat": # GEO_QuatSlerp cosom = p.x * q.x + p.y * q.y + p.z * q.z + p.w * q.w if 1 + cosom > QUAT_COS_EPSILON: # usual case if 1 - cosom > QUAT_COS_EPSILON: # usual case omega = math.acos(cosom) oosinom = 1 / math.sin(omega) sclp = math.sin((1 - t) * omega) * oosinom sclq = math.sin(t * omega) * oosinom else: # ends very close -- just lerp sclp = 1 - t sclq = t return Quat( sclp * p.x + sclq * q.x, sclp * p.y + sclq * q.y, sclp * p.z + sclq * q.z, sclp * p.w + sclq * q.w, ) else: return q Quat = Vec4 # alias @dataclass class AnmKey: time: int @classmethod @with_offset def create(cls, b, key_type: ANM_KeyType): time = read_int_and_print("ANM_Key->Time", b) if key_type == ANM_KeyType.ANM_KEYS_TYPE_REAL_TCB or key_type == ANM_KeyType.ANM_KEYS_TYPE_VEC3_TCB or key_type == ANM_KeyType.ANM_KEYS_TYPE_QUAT_TCB: tens = read_float_and_print("ANM_Key->Tens", b) cont = read_float_and_print("ANM_Key->Cont", b) bias = read_float_and_print("ANM_Key->Bias", b) easein = read_float_and_print("ANM_Key->EaseIn", b) easeout = read_float_and_print("ANM_Key->EaseOut", b) if key_type == ANM_KeyType.ANM_KEYS_TYPE_REAL_TCB: outtan = read_float_and_print("ANM_Key->OutTan", b) intan = read_float_and_print("ANM_Key->InTan", b) val = read_float_and_print("ANM_Key->Val", b) return AnmKeyRealTCB(time, tens, cont, bias, easein, easeout, outtan, intan, val) if key_type == ANM_KeyType.ANM_KEYS_TYPE_VEC3_TCB: outtan = Vec3.create(b) intan = Vec3.create(b) val = Vec3.create(b) return AnmKeyVec3TCB(time, tens, cont, bias, easein, easeout, outtan, intan, val) if key_type == ANM_KeyType.ANM_KEYS_TYPE_QUAT_TCB: outtan = Vec4.create(b) intan = Vec4.create(b) val = Vec4.create(b) return AnmKeyQuatTCB(time, tens, cont, bias, easein, easeout, outtan, intan, val) elif key_type == ANM_KeyType.ANM_KEYS_TYPE_REAL_BEZ or key_type == ANM_KeyType.ANM_KEYS_TYPE_VEC3_BEZ or key_type == ANM_KeyType.ANM_KEYS_TYPE_QUAT_BEZ: flags = read_float_and_print("ANM_Key->Flags", b) if key_type == ANM_KeyType.ANM_KEYS_TYPE_REAL_BEZ: outtan = read_float_and_print("ANM_Key->OutTan", b) intan = read_float_and_print("ANM_Key->InTan", b) val = read_float_and_print("ANM_Key->Val", b) return AnmKeyRealBez(time, flags, outtan, intan, val) if key_type == ANM_KeyType.ANM_KEYS_TYPE_VEC3_BEZ: outtan = Vec3.create(b) intan = Vec3.create(b) val = Vec3.create(b) return AnmKeyVec3Bez(time, flags, outtan, intan, val) if key_type == ANM_KeyType.ANM_KEYS_TYPE_QUAT_BEZ: outtan = Vec4.create(b) intan = Vec4.create(b) val = Vec4.create(b) return AnmKeyQuatBez(time, flags, outtan, intan, val) else: print("WARNING: key_type: %s (%s)" % (key_type, ANM_KeyType.get_name(key_type))) return cls(time=time) @dataclass class AnmKeyBez(AnmKey): flags: int ANM_BEZKEY_SMOOTH = 0 ANM_BEZKEY_LINEAR = 1 ANM_BEZKEY_STEP = 2 ANM_BEZKEY_FAST = 3 ANM_BEZKEY_SLOW = 4 ANM_BEZKEY_USER = 5 ANM_BEZKEY_NUMTYPEBITS = 3 ANM_BEZKEY_INTYPESHIFT = 7 ANM_BEZKEY_OUTTYPESHIFT = (ANM_BEZKEY_INTYPESHIFT + ANM_BEZKEY_NUMTYPEBITS) ANM_BEZKEY_TYPEMASK = 7 @staticmethod def ANM_GET_OUT_TAN_TYPE(flags: int): return int((flags >> AnmKeyBez.ANM_BEZKEY_OUTTYPESHIFT) & AnmKeyBez.ANM_BEZKEY_TYPEMASK) @staticmethod def ANM_GET_IN_TAN_TYPE(flags: int): return int((flags >> AnmKeyBez.ANM_BEZKEY_INTYPESHIFT) & AnmKeyBez.ANM_BEZKEY_TYPEMASK) @staticmethod def ANM_DU(key_info: "AnmKeyInfo") -> int: du = key_info.key1.time - key_info.key0.time if du < 0: du = key_info.key1.time - key_info.start_time + key_info.end_time - key_info.key0.time return du @staticmethod def bez_interpolate(bez_info: "ANM_BezInterpolateInfo"): # ANM_BezInterpolate key_info = bez_info.info key0: AnmKeyBez = key_info.key0 key1: AnmKeyBez = key_info.key1 t2 = key_info.t * key_info.t out_type = AnmKeyBez.ANM_GET_OUT_TAN_TYPE(key0.flags) in_type = AnmKeyBez.ANM_GET_IN_TAN_TYPE(key1.flags) if out_type == AnmKeyBez.ANM_BEZKEY_STEP or in_type == AnmKeyBez.ANM_BEZKEY_STEP: for el in range(bez_info.elements): if key_info.t == 1: bez_info.val[el] = bez_info.val1[el] else: bez_info.val[el] = bez_info.val0[el] return du = AnmKeyBez.ANM_DU(key_info) / 3 for el in range(bez_info.elements): out = bez_info.tan0[el] * du + bez_info.val0[el] _in = bez_info.tan1[el] * du + bez_info.val1[el] bez_info.val[el] = ((key_info.omt * bez_info.val0[el] + (3 * key_info.t) * out) * key_info.omt + (3 * t2) * _in) * key_info.omt + key_info.t * t2 * bez_info.val1[el] @dataclass class ANM_BezInterpolateInfo: info: "AnmKeyInfo" val0: List[float] tan0: List[float] tan1: List[float] val1: List[float] val: List[float] elements: int @dataclass class AnmKeyRealBez(AnmKeyBez): outtan: float intan: float val: float @staticmethod def interpolate(vec: float, info: "AnmKeyInfo") -> float: # ANM_InterpRealBEZ key0: AnmKeyRealBez = info.key0 key1: AnmKeyRealBez = info.key1 bez_info = ANM_BezInterpolateInfo(info, [key0.val], [key0.outtan], [key1.intan], [key1.val], [vec], 1) AnmKeyBez.bez_interpolate(bez_info) return bez_info.val[0] @dataclass class AnmKeyVec3Bez(AnmKeyBez): outtan: Vec3 intan: Vec3 val: Vec3 @staticmethod def interpolate(vec: Vec3, info: "AnmKeyInfo") -> Vec3: # ANM_InterpVec3BEZ key0: AnmKeyVec3Bez = info.key0 key1: AnmKeyVec3Bez = info.key1 bez_info = ANM_BezInterpolateInfo(info, [key0.val.x, key0.val.y, key0.val.z], [key0.outtan.x, key0.outtan.y, key0.outtan.z], [key1.intan.x, key1.intan.y, key1.intan.z], [key1.val.x, key1.val.y, key1.val.z], [vec.x, vec.y, vec.z], 3) AnmKeyBez.bez_interpolate(bez_info) return Vec3(bez_info.val[0], bez_info.val[1], bez_info.val[2]) @dataclass class AnmKeyQuatBez(AnmKeyBez): outtan: Quat intan: Quat val: Quat @dataclass class AnmKeyTCB(AnmKey): tens: float cont: float bias: float easein: float easeout: float @staticmethod def compute_hermit_basis(u: float) -> tuple: # ANM_TCBComputeHermiteBasis u2 = u*u u3 = u*u*u a = 2*u3 - 3*u2 v0 = 1 + a v1 = -a v2 = u - 2*u2 + u3 v3 = -u2 + u3 return v0, v1, v2, v3 @staticmethod def ease(u: float, a: float, b: float) -> float: # ANM_TCBEase s = a + b if u == 0 or u == 1: return u if s == 0: return u if s > 1: a = a / s b = b / s k = 1 / (2 - a - b) if u < a: return (k / a) * u * u elif u < 1 - b: return k * (2 * u - a) else: u = 1 - u return 1 - (k / b) * u * u @dataclass class AnmKeyRealTCB(AnmKeyTCB): outtan: float intan: float val: float @staticmethod def interpolate(info: "AnmKeyInfo") -> float: # ANM_InterpRealTCB key0: AnmKeyRealTCB = info.key0 key1: AnmKeyRealTCB = info.key1 t = AnmKeyTCB.ease(info.t, key0.easeout, key1.easein) v = AnmKeyTCB.compute_hermit_basis(t) return key0.val * v[0] + key1.val * v[1] + key0.outtan * v[2] + key1.intan * v[3] @dataclass class AnmKeyVec3TCB(AnmKeyTCB): outtan: Vec3 intan: Vec3 val: Vec3 @staticmethod def interpolate(info: "AnmKeyInfo") -> Vec3: # ANM_InterpVec3TCB key0: AnmKeyVec3TCB = info.key0 key1: AnmKeyVec3TCB = info.key1 t = AnmKeyTCB.ease(info.t, key0.easeout, key1.easein) v = AnmKeyTCB.compute_hermit_basis(t) return Vec3( key0.val.x * v[0] + key1.val.x * v[1] + key0.outtan.x * v[2] + key1.intan.x * v[3], key0.val.y * v[0] + key1.val.y * v[1] + key0.outtan.y * v[2] + key1.intan.y * v[3], key0.val.z * v[0] + key1.val.z * v[1] + key0.outtan.z * v[2] + key1.intan.z * v[3], ) @dataclass class AnmKeyQuatTCB(AnmKeyTCB): outtan: Vec4 intan: Vec4 val: Vec4 @staticmethod def interpolate(info: "AnmKeyInfo") -> Vec4: # ANM_InterpQuatTCB key0: AnmKeyQuatTCB = info.key0 key1: AnmKeyQuatTCB = info.key1 t = AnmKeyTCB.ease(info.t, key0.easeout, key1.easein) return Quat.squad(key0.val, key0.outtan, key1.intan, key1.val, t) @dataclass class AnmKeyInfo: start_time: int end_time: int key0: AnmKey key1: AnmKey time: int t: float omt: float @dataclass class AnmKeys: keys_type: int ort_before: int ort_after: int flags: int p_interpolate: int # pointer to interpolate # if ANM_XFORM_TYPE_PRS: # if posKey: Vec3Interpolate # if rotKey: QuatInterpolate # if sclKey: Vec3Interpolate # if ANM_XFORM_TYPE_LOOKAT: # if rolKey: RealInterpolate num_keys: int keys: List[AnmKey] = field(repr=False) @classmethod @with_offset def create(cls, b): keys_type = read_byte_and_print("ANM_Keys->Type", b) debug_print("ANM_KeyType: %s" % ANM_KeyType.get_name(keys_type)) 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, keys_type)) 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) def vec3_interpolate(self, keyinfo: AnmKeyInfo) -> Vec3: if self.keys_type in [ANM_KeyType.ANM_KEYS_TYPE_REAL_TCB, ANM_KeyType.ANM_KEYS_TYPE_VEC3_TCB, ANM_KeyType.ANM_KEYS_TYPE_QUAT_TCB]: return AnmKeyVec3TCB.interpolate(keyinfo) elif self.keys_type in [ANM_KeyType.ANM_KEYS_TYPE_REAL_BEZ, ANM_KeyType.ANM_KEYS_TYPE_VEC3_BEZ, ANM_KeyType.ANM_KEYS_TYPE_QUAT_BEZ]: return AnmKeyVec3Bez.interpolate(keyinfo) def quat_interpolate(self, keyinfo: AnmKeyInfo) -> Quat: if self.keys_type in [ANM_KeyType.ANM_KEYS_TYPE_REAL_TCB, ANM_KeyType.ANM_KEYS_TYPE_VEC3_TCB, ANM_KeyType.ANM_KEYS_TYPE_QUAT_TCB]: return AnmKeyQuatTCB.interpolate(keyinfo) elif self.keys_type in [ANM_KeyType.ANM_KEYS_TYPE_REAL_BEZ, ANM_KeyType.ANM_KEYS_TYPE_VEC3_BEZ, ANM_KeyType.ANM_KEYS_TYPE_QUAT_BEZ]: return AnmKeyQuatBez.interpolate(keyinfo) def find_info(self, anm_start_time, anm_end_time, time) -> AnmKeyInfo: # translation of ANM_KeyFind begin = 0 index = end = self.num_keys n = 0 start_time = self.keys[0].time end_time = self.keys[self.num_keys-1].time total_time = end_time - start_time if ~self.flags & ANM_KeyFlags.ANM_KEYS_FLAGS_ORT_DISABLE and total_time: if time < start_time: ort_before = self.ort_before if ort_before in [ANM_KEYS_ORT_LOOP, ANM_KEYS_ORT_CYCLE]: lapse = -(time - end_time) time = end_time - (lapse % total_time) if ort_before == ANM_KEYS_ORT_OSCILLATE: lapse = -(time - end_time) time = end_time - (lapse % total_time) if int(lapse / total_time) & 1: time = end_time - (time - start_time) if time > end_time: ort_after = self.ort_after if ort_after in [ANM_KEYS_ORT_LOOP, ANM_KEYS_ORT_CYCLE]: lapse = (time - start_time) time = start_time + (lapse % total_time) if ort_after == ANM_KEYS_ORT_OSCILLATE: lapse = (time - start_time) time = start_time + (lapse % total_time) if int(lapse / total_time) & 1: time = end_time - (time - start_time) index = self.num_keys # not sure about the while loop n = begin + ((end - begin) >> 1) while index != n: n = begin + ((end - begin) >> 1) index -= 1 test = time - self.keys[index].time if test: if test < 0: end = index else: begin = index else: break key0 = self.keys[index] wrap = key0.time > time if wrap: key1 = key0 key0 = self.keys[self.num_keys - 1] t = 1 else: wrap = index == self.num_keys - 1 if wrap: key1 = self.keys[0] t = 0 else: key1 = self.keys[index + 1] t = (time - key0.time) / (key1.time - key0.time) omt = 1 - t keyinfo = AnmKeyInfo(start_time=anm_start_time, end_time=anm_end_time, key0=key0, key1=key1, time=time, t=t, omt=omt) return keyinfo @dataclass class AnmObj: custom: int = field(repr=False) skin: Optional["AnmSkin"] # = field(repr=False) # will be set later by AnmSkin itself 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=None, 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 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) def apply_mat(self, mat: "GeoMat") -> List[Vec3]: # ANM_ClusterApplyMatSingleVertex ret: List[Vec3] = [] for vertex in self.vertices: x = mat.s.m[0][0] * vertex.x + mat.s.m[0][1] * vertex.y + mat.s.m[0][2] * vertex.z + mat.s.m[0][3] y = mat.s.m[1][0] * vertex.x + mat.s.m[1][1] * vertex.y + mat.s.m[1][2] * vertex.z + mat.s.m[1][3] z = mat.s.m[2][0] * vertex.x + mat.s.m[2][1] * vertex.y + mat.s.m[2][2] * vertex.z + mat.s.m[2][3] n = Vec3(x, y, z) ret.append(n) return ret class NamDictionnary: @staticmethod @with_offset def create(cls, b, depth=1): r = {} read_int_and_print("NAM_Dictionnary->MemGroup", b) # MAP_MemGroup* namemapper = read_int_and_print("NAM_Dictionnary->pNameMapper", b) # MAP_Mapper* flags = read_int_and_print("NAM_Dictionnary->Flags", b) # X_BOOL debug_print("Flags: NAM_FlagHasHandles: %s, NAM_FlagCaseInsensitive: %s" % ( flags & NAM_FlagHasHandles, flags & NAM_FlagCaseInsensitive)) read_short_and_print("NAM_Dictionnary->NameHandle", b) # NAM_NameHandle -> X_UINT16 b.seek(namemapper) read_int_and_print("MemGroup*", b) # MAP_MemGroup* read_int_and_print("MemBaseSize", b) # X_UINT read_int_and_print("MemBlocks", b) # X_UINT read_int_and_print("MemSize", b) # X_UINT read_int_and_print("MemFree", b) # X_UINT read_int_and_print("Mem*", b) # MAP_Mapping* read_int_and_print("MaxMappings", b) # X_UINT totalmappings = read_int_and_print("TotalMappings", b) # X_UINT total_keys = read_int_and_print("TotalKeys", b) # X_UINT p_key_contexts = read_int_and_print("KeyContexts*", b) # MAP_KeyContext* for key in range(total_keys): key_context_size = [ 4, # X_UINT Size 4, # MAP_BinSearchCallback* Callback 4, # X_UINT Index 4, # X_INT Test 4, # MAP_Mapping **LUT 4, # X_UINT Reserve 4, # X_VOID *Data ] key_context_size = sum(key_context_size) key_context_begin = p_key_contexts + key * key_context_size debug_print("getting keys for type %s" % key) b.seek(key_context_begin) # NAM_Dictionnary.KeyContexts[KEY] # we are now in the KeyContext for type "key" debug_print("Current keycontext: %s" % b.tell()) keytype_size = read_int_and_print("\tSize", b) # MAP_BinSearchContext -> X_UINT read_int_and_print("\t\tpCallback", b) # MAP_BinSearchContext -> MAP_BinSearchCallback* read_int_and_print("\t\tIndex", b) # MAP_BinSearchContext -> X_UINT read_int_and_print("\t\tTest", b) # MAP_BinSearchContext -> X_INT p_lut = read_int_and_print("\tMAP_Mapping** LUT", b) # MAP_Mapping** read_int_and_print("\tReserve", b) # X_UINT read_int_and_print("\tpData", b) # X_VOID* # i=0; tant que l'addresse que renvoie MAP_MappingGet (Dictionary->NameMapper, i, NAM_STRING_KEY) != 0; i++ # mappingget: return Mapper->KeyContexts[Key].LUT[Index]; """ """ key_index = 0 while True: if key_index >= totalmappings: break b.seek( p_lut + key_index * 4) # NAM_Dictionnary.KeyContexts[KEY].LUT[INDEX] -> LA je suis au MAP_Mapping* == NAM_Name* debug_print("MAP_Mapping*") addr_begin_mapping = read_int_and_print("MAP_Mapping*", b) # MAP_MapIndex* -> X_UINT16* if addr_begin_mapping == 0: break b.seek(addr_begin_mapping) # we are now in MAP_Mapping == NAM_Name header """ struct _MAP_Mapping { MAP_MapIndex TotalSize; }; typedef X_UINT16 MAP_MapIndex; """ debug_print("MAP_Mapping") read_short_and_print("TotalSize", b) # MAP_MapIndex* -> X_UINT16* after_mapping = b.tell() b.seek(after_mapping + key * 2) debug_print("MAP_MapIndex at %s" % b.tell()) index = read_short_and_print("MAP_MapIndex value", b) b.seek(addr_begin_mapping + index) debug_print("STRINGDATA* OU HANDLEDATA* at %s" % b.tell()) # pdata = read_int_and_print("Data*", b) # b.seek(pdata) if key == NAM_STRING_KEY: val = read_int_and_print("ValueOrCustom", b) # X_INT or X_VOID* name = read_string_until_none_and_print("name", b) if flags & NAM_FlagHasHandles: r[key_index] = {"Name": name} elif depth > 1: item = NamDictionnary.create(cls, b, depth - 1, offset=val) r[name] = item else: item = cls.create(b, offset=val) r[name] = item if key == NAM_HANDLE_KEY: read_short_and_print("RefCnt", b) # NAM_NameRefCnt == X_UINT16 handle = read_short_and_print("Handle", b) # NAM_NameHandle == X_UINT16 # MAYBE BREAKS SOMETHING: 23/07/2021 # maybevalue = read_int_and_print("TEST", b) # r[key_index]["obj"] = NamDictionnary.create(AnmNode, b, offset=maybevalue) # r[key_index]["obj1"] = AnmNode.create(b, offset=maybevalue) r[key_index]["Handle"] = handle key_index += 1 debug_print("Key_index=%s" % key_index) return r @dataclass class GeoMat4x4: m: List[List[float]] @classmethod @with_offset def create(cls, b): m = [] for _ in range(4): row = [] for _j in range(4): row.append(read_float(b)) m.append(row) return cls(m=m) @classmethod def create_new(cls): m = [] for _ in range(4): row = [] for _j in range(4): row.append(0) m.append(row) 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) def multiply(self, other: "GeoMat") -> "GeoMat": # translation of S_GEO_MatMultiply ret = GeoMat4x4.create_new() if self.mat_class & GEO_MAT_PROJECTION or other.mat_class & GEO_MAT_PROJECTION: for i in range(4): for j in range(4): val = self.s.m[i][0] * other.s.m[0][j] + \ self.s.m[i][1] * other.s.m[1][j] + \ self.s.m[i][2] * other.s.m[2][j] + \ self.s.m[i][3] * other.s.m[3][j] ret.m[i][j] = val else: if self.mat_class & GEO_MAT_LINEAR and other.mat_class & GEO_MAT_LINEAR: for i in range(3): for j in range(3): val = self.s.m[i][0] * other.s.m[0][j] + \ self.s.m[i][1] * other.s.m[1][j] + \ self.s.m[i][2] * other.s.m[2][j] ret.m[i][j] = val else: rot_src = self if self.mat_class & GEO_MAT_LINEAR else other for i in range(3 * 4): ret.m[0][i] = rot_src.s.m[0][i] if other.mat_class & GEO_MAT_TRANSLATION: for j in range(3): ret.m[j][3] = self.s.m[j][0] * other.s.m[0][3] + \ self.s.m[j][1] * other.s.m[1][3] + \ self.s.m[j][2] * other.s.m[2][3] + \ self.s.m[j][3] else: ret.m[0][3] = self.s.m[0][3] ret.m[1][3] = self.s.m[1][3] ret.m[2][3] = self.s.m[2][3] ret.m[3][0] = ret.m[3][1] = ret.m[3][2] = 0 ret.m[3][3] = 1 cls = ((self.mat_class | other.mat_class) & ~GEO_MAT_SIGN) | ((self.mat_class ^ other.mat_class) & GEO_MAT_SIGN) if self.mat_class & GEO_MAT_SCALE and other.mat_class & GEO_MAT_ROTATION: cls |= GEO_MAT_SHEAR return GeoMat(mat_class=cls, s=ret) @classmethod def quat(cls, quat: "Vec4") -> "GeoMat": x = quat.x y = quat.y z = quat.z w = quat.w xs = x + x ys = y + y zs = z + z wx = w * xs wy = w * ys wz = w * zs xx = x * xs xy = x * ys xz = x * zs yy = y * ys yz = y * zs zz = z * zs ret = GeoMat4x4.create_new() ret.m[0][0] = 1 - (yy + zz) ret.m[0][1] = xy - wz ret.m[0][2] = xz + wy ret.m[1][0] = xy + wz ret.m[1][1] = 1 - (xx + zz) ret.m[1][2] = yz - wx ret.m[2][0] = xz - wy ret.m[2][1] = yz + wx ret.m[2][2] = 1 - (xx + yy) ret.m[3][0] = ret.m[3][1] = ret.m[3][2] = ret.m[0][3] = ret.m[1][3] = ret.m[2][3] = 0 ret.m[3][3] = 1 c = GEO_MAT_ROTATION return cls(mat_class=c, s=ret) @classmethod def identity(cls): m = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ] m = GeoMat4x4(m=m) return cls(mat_class=0, s=m) def affine(self, affine: "GeoAffine") -> "GeoMat": # GEO_MatAffine if affine.affine_class & GEO_MAT_ROTATION: mat = self.quat(affine.quat) else: mat = GeoMat.identity() if affine.affine_class & GEO_MAT_TRANSLATION: mat.s.m[0][3] = affine.translation.x mat.s.m[1][3] = affine.translation.y mat.s.m[2][3] = affine.translation.z if affine.affine_class & (GEO_MAT_SCALE | GEO_MAT_SIGN): if affine.affine_class & GEO_MAT_SIGN: mat.s.m[0][0] *= -affine.scale.x mat.s.m[1][0] *= -affine.scale.x mat.s.m[2][0] *= -affine.scale.x mat.s.m[0][1] *= -affine.scale.y mat.s.m[1][1] *= -affine.scale.y mat.s.m[2][1] *= -affine.scale.y mat.s.m[0][2] *= -affine.scale.z mat.s.m[1][2] *= -affine.scale.z mat.s.m[2][2] *= -affine.scale.z else: mat.s.m[0][0] *= affine.scale.x mat.s.m[1][0] *= affine.scale.x mat.s.m[2][0] *= affine.scale.x mat.s.m[0][1] *= affine.scale.y mat.s.m[1][1] *= affine.scale.y mat.s.m[2][1] *= affine.scale.y mat.s.m[0][2] *= affine.scale.z mat.s.m[1][2] *= affine.scale.z mat.s.m[2][2] *= affine.scale.z mat.mat_class = affine.affine_class return mat @dataclass class GeoAffine: translation: 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(translation=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 anim: Optional["AnmAnim"] # = field(repr=False) parent: Optional["AnmNode"] p_target: int p_usercall: int p_usercontext: int stamp: int geo_mat: GeoMat = field(repr=False) transform: AnmTransform = field(repr=False) pos_keys: Union[AnmKeys, int] = field(repr=False) rot_or_aff_keys: Union[AnmKeys, int] = field(repr=False) scl_keys: Union[AnmKeys, int] = field(repr=False) @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) if p_parent: parent = AnmNode.create(b, offset=p_parent) else: parent = None 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_or_aff_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_or_aff_keys > 0: rot_or_aff_keys = AnmKeys.create(b, offset=rot_or_aff_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, anim=None, parent=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_or_aff_keys=rot_or_aff_keys, scl_keys=scl_keys) def get_mat(self, time: int, stamp): affine = self.get_affine(time, stamp) if not self.parent: return self.geo_mat.affine(affine) else: return self.geo_mat.affine(affine).multiply(self.parent.get_mat(time, stamp)) def get_affine(self, time: int, stamp) -> GeoAffine: # ANM_NodeGetAffine, Stamp is used for LOOKAT if self.transform.time != time: start_time = self.anim.start_time end_time = self.anim.end_time assert (self.x_form_type == ANM_XFORM_TYPE_PRS) # todo: process others if self.pos_keys: keyinfo = self.pos_keys.find_info(start_time, end_time, time) self.transform.affine.translation = AnmKeys.vec3_interpolate(keyinfo) self.transform.affine.affine_class |= GEO_MAT_TRANSLATION if self.rot_or_aff_keys: keyinfo = self.rot_or_aff_keys.find_info(start_time, end_time, time) self.transform.affine.quat = AnmKeys.quat_interpolate(keyinfo) self.transform.affine.affine_class |= GEO_MAT_ROTATION if self.scl_keys: keyinfo = self.scl_keys.find_info(start_time, end_time, time) self.transform.affine.scale = AnmKeys.vec3_interpolate(keyinfo) if self.transform.affine.affine_class & GEO_MAT_SIGN: self.transform.affine.scale.x *= -1 self.transform.affine.scale.y *= -1 self.transform.affine.scale.z *= -1 self.transform.affine.affine_class |= GEO_MAT_SCALE self.transform.time = time return self.transform.affine @dataclass class AnmBinding: cluster: Optional[AnmCluster] node: Optional[AnmNode] = field(repr=False) def dictionnary_find_node_by_handle(dictionnary: dict, handle: int, node_dict: dict): small_node = None for k, v in dictionnary.items(): if not isinstance(v, dict) or "Handle" not in v: raise Exception("dictionnary_find_by_handle: dict does not have handles") if v["Handle"] == handle: small_node = v if not small_node: raise Exception("dictionnary_find_by_handle: could not find handle %s in dict" % handle) else: name = small_node["Name"] if name in node_dict: return node_dict[name] else: raise Exception("dictionnary_find_by_handle: could not find name %s in node_dict" % name) @dataclass class AnmBinder: dictionnary: dict obj: "AnmObj" NumberOfBindings: int bindings: List["AnmBinding"] @classmethod def create(cls, anim: "AnmAnim", obj: "AnmObj"): dictionnary = anim.default_obj_dictionnary bindings = [] for i in range(obj.num_clusters): bindings.append(AnmBinding(None, None)) if obj.num_clusters > 0: num_bindings = obj.num_clusters for i in range(num_bindings): binding = bindings[i] binding.cluster = obj.clusters[i] binding.node = dictionnary_find_node_by_handle(obj.skin.node_dictionnary, binding.cluster.handle, anim.default_obj_dictionnary) else: num_bindings = 0 return cls(dictionnary=dictionnary, obj=obj, NumberOfBindings=num_bindings, bindings=bindings) def eval_vertices(self, time: int, vertices: List[Vec3], stride: int, vertexbase: int, stamp): # ANM_BinderEvalVertices for binding in self.bindings: if binding.node: mat = binding.node.get_mat(time, stamp) debug_print("Applying mat to cluster") binding.cluster.apply_mat(mat) @dataclass class AnmAnimation: dictionnary: dict anim: "AnmAnim" skin: "AnmSkin" GrandTotalNumberOfVertices: int NumberOfBinders: int binders: List["AnmBinder"] @classmethod def create(cls, skin: "AnmSkin", anim: "AnmAnim"): binders = [] num_objs = len(skin.default_obj_dictionnary) grand_total_num_vertices = 0 num_binders = num_objs for i in range(num_objs): key: str = list(skin.default_obj_dictionnary)[i] obj: AnmObj = skin.default_obj_dictionnary[key] binder = AnmBinder.create(anim, obj) binders.append(binder) grand_total_num_vertices += obj.total_number_vertices dictionnary = binders[0].dictionnary return cls(dictionnary=dictionnary, anim=anim, skin=skin, GrandTotalNumberOfVertices=grand_total_num_vertices, NumberOfBinders=num_binders, binders=binders) def eval(self, time: int, vertices: List[Vec3], stride: int, stamp): # ANM_AnimationEvalVertices vbase = 0 for binder in self.binders: debug_print("Evaluating binder") binder.eval_vertices(time, vertices, stride, vbase, stamp) vbase += binder.obj.total_number_vertices @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) for obj_name in treedict["Default Tree"]: # memory hack: prevent keeping in memory similar objects obj = treedict["Default Tree"][obj_name] for defobj_name in defaultobjdict: if obj_name == defobj_name: defaultobjdict[obj_name] = obj 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) r.fix_links() return r def fix_node_anim_recursive(self, node: AnmNode): node.anim = self if node.parent: self.fix_node_anim_recursive(node.parent) def fix_links(self): for node_name in self.default_obj_dictionnary: node: AnmNode = self.default_obj_dictionnary[node_name] self.fix_node_anim_recursive(node) @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): m = {} for node_index in nodedict: node = nodedict[node_index] m[node["Handle"]] = node["Name"] for obj_name, obj in defaultobjtree.items(): for cluster in obj.clusters: cluster.bone_name = m[cluster.handle] @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) for obj_name in treedict["Default Tree"]: # memory hack: prevent keeping in memory similar objects obj = treedict["Default Tree"][obj_name] for defobj_name in defaultobjdict: if obj_name == defobj_name: defaultobjdict[obj_name] = obj pprint([treedict, defaultobjdict]) # AnmSkin.link(nodedict, defaultobjdict) skin = 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) skin.fix_links() return skin def fix_links(self): for k in self.default_obj_dictionnary: obj = self.default_obj_dictionnary[k] obj.skin = self @dataclass class AnmFile: @staticmethod def parse(b, cls): # MAP_Image _ = 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)