Files
Hippolyzer/hippolyzer/lib/base/llanim.py
2021-04-30 17:30:24 +00:00

148 lines
4.7 KiB
Python

from __future__ import annotations
import dataclasses
import enum
from typing import *
import hippolyzer.lib.base.serialization as se
from hippolyzer.lib.base.datatypes import Vector3, Quaternion
from hippolyzer.lib.base.multidict import OrderedMultiDict
# Forward declarations so the dataclasses can be declared in natural order
JOINT_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(Joint))
CONSTRAINT_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(Constraint))
POSKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(PosKeyframe))
ROTKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(RotKeyframe))
@dataclasses.dataclass
class Animation:
major_version: int = se.dataclass_field(se.U16, default=1)
minor_version: int = se.dataclass_field(se.U16, default=0)
base_priority: int = se.dataclass_field(se.S32)
duration: float = se.dataclass_field(se.F32)
emote_name: str = se.dataclass_field(se.CStr())
loop_in_point: float = se.dataclass_field(se.F32)
loop_out_point: float = se.dataclass_field(se.F32)
loop: int = se.dataclass_field(se.S32)
ease_in_duration: float = se.dataclass_field(se.F32)
ease_out_duration: float = se.dataclass_field(se.F32)
hand_pose: HandPose = se.dataclass_field(lambda: se.IntEnum(HandPose, se.U32), default=0)
joints: OrderedMultiDict[str, Joint] = se.dataclass_field(se.MultiDictAdapter(
se.Collection(se.U32, se.Tuple(se.CStr(), JOINT_DATACLASS)),
))
constraints: List[Constraint] = se.dataclass_field(
se.Collection(se.S32, CONSTRAINT_DATACLASS),
)
def to_bytes(self):
writer = se.BufferWriter("<")
writer.write(se.Dataclass(type(self)), self)
return writer.copy_buffer()
@classmethod
def from_bytes(cls, buff) -> "Animation":
reader = se.BufferReader("<", buff)
return reader.read(se.Dataclass(cls))
@dataclasses.dataclass
class Joint:
priority: int = se.dataclass_field(se.S32)
rot_keyframes: List[RotKeyframe] = se.dataclass_field(
se.Collection(se.S32, ROTKEYFRAME_DATACLASS)
)
pos_keyframes: List[PosKeyframe] = se.dataclass_field(
se.Collection(se.S32, POSKEYFRAME_DATACLASS),
)
def _get_version_from_context(ctx: se.ParseContext) -> Tuple[int, int]:
return ctx._root.major_version, ctx._root.minor_version
@dataclasses.dataclass
class RotKeyframe:
time: float = se.dataclass_field(lambda: VERSIONED_TIME)
rot: Quaternion = se.dataclass_field(se.ContextSwitch(
_get_version_from_context,
{
(0, 1): se.PackedQuat(se.Vector3),
(1, 0): se.PackedQuat(se.Vector3U16(-1.0, 1.0)),
},
))
@dataclasses.dataclass
class PosKeyframe:
time: float = se.dataclass_field(lambda: VERSIONED_TIME)
pos: Vector3 = se.dataclass_field(se.ContextSwitch(
_get_version_from_context,
{
(0, 1): se.Vector3,
(1, 0): se.Vector3U16(-5.0, 5.0),
},
))
class QuantizedTime(se.QuantizedFloatBase):
def __init__(self, prim_spec: se.SerializablePrimitive):
super().__init__(prim_spec, zero_median=False)
def _get_upper_limit(self, ctx: se.ParseContext) -> float:
# Upper limit is the "duration" from the parent animation
return ctx._root.duration
def encode(self, val: Any, ctx: Optional[se.ParseContext]) -> Any:
return self._float_to_quantized(val, 0.0, self._get_upper_limit(ctx))
def decode(self, val: Any, ctx: Optional[se.ParseContext], pod: bool = False) -> Any:
return self._quantized_to_float(val, 0.0, self._get_upper_limit(ctx))
VERSIONED_TIME = se.ContextSwitch(
_get_version_from_context,
{
(0, 1): se.F32,
(1, 0): QuantizedTime(se.U16),
},
)
class ConstraintType(enum.IntEnum):
POINT = 0
PLANE = 1
@dataclasses.dataclass
class Constraint:
chain_length: int = se.dataclass_field(se.U8)
type: ConstraintType = se.dataclass_field(se.IntEnum(ConstraintType, se.U8))
source_volume: str = se.dataclass_field(se.StrFixed(16))
source_offset: Vector3 = se.dataclass_field(se.Vector3)
target_volume: str = se.dataclass_field(se.StrFixed(16))
target_offset: Vector3 = se.dataclass_field(se.Vector3)
target_dir: Vector3 = se.dataclass_field(se.Vector3())
ease_in_start: float = se.dataclass_field(se.F32)
ease_in_stop: float = se.dataclass_field(se.F32)
ease_out_start: float = se.dataclass_field(se.F32)
ease_out_stop: float = se.dataclass_field(se.F32)
class HandPose(enum.IntEnum):
SPREAD = 0
RELAXED = 1
POINT = 2
FIST = 3
RELAXED_L = 4
POINT_L = 5
FIST_L = 6
RELAXED_R = 7
POINT_R = 8
FIST_R = 9
SALUTE_R = 10
TYPING = 11
PEACE_R = 12
PALM_R = 13