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

1338 lines
47 KiB
Python

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)