Spaces:
Running
Running
import operator | |
import numpy as np | |
import numpy.core.umath_tests as ut | |
from visualization.Quaternions import Quaternions | |
class Animation: | |
""" | |
Animation is a numpy-like wrapper for animation data | |
Animation data consists of several arrays consisting | |
of F frames and J joints. | |
The animation is specified by | |
rotations : (F, J) Quaternions | Joint Rotations | |
positions : (F, J, 3) ndarray | Joint Positions | |
The base pose is specified by | |
orients : (J) Quaternions | Joint Orientations | |
offsets : (J, 3) ndarray | Joint Offsets | |
And the skeletal structure is specified by | |
parents : (J) ndarray | Joint Parents | |
""" | |
def __init__(self, rotations, positions, orients, offsets, parents, names, frametime): | |
self.rotations = rotations | |
self.positions = positions | |
self.orients = orients | |
self.offsets = offsets | |
self.parents = parents | |
self.names = names | |
self.frametime = frametime | |
def __op__(self, op, other): | |
return Animation( | |
op(self.rotations, other.rotations), | |
op(self.positions, other.positions), | |
op(self.orients, other.orients), | |
op(self.offsets, other.offsets), | |
op(self.parents, other.parents)) | |
def __iop__(self, op, other): | |
self.rotations = op(self.roations, other.rotations) | |
self.positions = op(self.roations, other.positions) | |
self.orients = op(self.orients, other.orients) | |
self.offsets = op(self.offsets, other.offsets) | |
self.parents = op(self.parents, other.parents) | |
return self | |
def __sop__(self, op): | |
return Animation( | |
op(self.rotations), | |
op(self.positions), | |
op(self.orients), | |
op(self.offsets), | |
op(self.parents)) | |
def __add__(self, other): | |
return self.__op__(operator.add, other) | |
def __sub__(self, other): | |
return self.__op__(operator.sub, other) | |
def __mul__(self, other): | |
return self.__op__(operator.mul, other) | |
def __div__(self, other): | |
return self.__op__(operator.div, other) | |
def __abs__(self): | |
return self.__sop__(operator.abs) | |
def __neg__(self): | |
return self.__sop__(operator.neg) | |
def __iadd__(self, other): | |
return self.__iop__(operator.iadd, other) | |
def __isub__(self, other): | |
return self.__iop__(operator.isub, other) | |
def __imul__(self, other): | |
return self.__iop__(operator.imul, other) | |
def __idiv__(self, other): | |
return self.__iop__(operator.idiv, other) | |
def __len__(self): | |
return len(self.rotations) | |
def __getitem__(self, k): | |
if isinstance(k, tuple): | |
return Animation( | |
self.rotations[k], | |
self.positions[k], | |
self.orients[k[1:]], | |
self.offsets[k[1:]], | |
self.parents[k[1:]], | |
self.names[k[1:]], | |
self.frametime[k[1:]]) | |
else: | |
return Animation( | |
self.rotations[k], | |
self.positions[k], | |
self.orients, | |
self.offsets, | |
self.parents, | |
self.names, | |
self.frametime) | |
def __setitem__(self, k, v): | |
if isinstance(k, tuple): | |
self.rotations.__setitem__(k, v.rotations) | |
self.positions.__setitem__(k, v.positions) | |
self.orients.__setitem__(k[1:], v.orients) | |
self.offsets.__setitem__(k[1:], v.offsets) | |
self.parents.__setitem__(k[1:], v.parents) | |
else: | |
self.rotations.__setitem__(k, v.rotations) | |
self.positions.__setitem__(k, v.positions) | |
self.orients.__setitem__(k, v.orients) | |
self.offsets.__setitem__(k, v.offsets) | |
self.parents.__setitem__(k, v.parents) | |
def shape(self): | |
return (self.rotations.shape[0], self.rotations.shape[1]) | |
def copy(self): | |
return Animation( | |
self.rotations.copy(), self.positions.copy(), | |
self.orients.copy(), self.offsets.copy(), | |
self.parents.copy(), self.names, | |
self.frametime) | |
def repeat(self, *args, **kw): | |
return Animation( | |
self.rotations.repeat(*args, **kw), | |
self.positions.repeat(*args, **kw), | |
self.orients, self.offsets, self.parents, self.frametime, self.names) | |
def ravel(self): | |
return np.hstack([ | |
self.rotations.log().ravel(), | |
self.positions.ravel(), | |
self.orients.log().ravel(), | |
self.offsets.ravel()]) | |
def unravel(cls, anim, shape, parents): | |
nf, nj = shape | |
rotations = anim[nf * nj * 0:nf * nj * 3] | |
positions = anim[nf * nj * 3:nf * nj * 6] | |
orients = anim[nf * nj * 6 + nj * 0:nf * nj * 6 + nj * 3] | |
offsets = anim[nf * nj * 6 + nj * 3:nf * nj * 6 + nj * 6] | |
return cls( | |
Quaternions.exp(rotations), positions, | |
Quaternions.exp(orients), offsets, | |
parents.copy()) | |
# local transformation matrices | |
def transforms_local(anim): | |
""" | |
Computes Animation Local Transforms | |
As well as a number of other uses this can | |
be used to compute global joint transforms, | |
which in turn can be used to compete global | |
joint positions | |
Parameters | |
---------- | |
anim : Animation | |
Input animation | |
Returns | |
------- | |
transforms : (F, J, 4, 4) ndarray | |
For each frame F, joint local | |
transforms for each joint J | |
""" | |
transforms = anim.rotations.transforms() | |
transforms = np.concatenate([transforms, np.zeros(transforms.shape[:2] + (3, 1))], axis=-1) | |
transforms = np.concatenate([transforms, np.zeros(transforms.shape[:2] + (1, 4))], axis=-2) | |
# the last column is filled with the joint positions! | |
transforms[:, :, 0:3, 3] = anim.positions | |
transforms[:, :, 3:4, 3] = 1.0 | |
return transforms | |
def transforms_multiply(t0s, t1s): | |
""" | |
Transforms Multiply | |
Multiplies two arrays of animation transforms | |
Parameters | |
---------- | |
t0s, t1s : (F, J, 4, 4) ndarray | |
Two arrays of transforms | |
for each frame F and each | |
joint J | |
Returns | |
------- | |
transforms : (F, J, 4, 4) ndarray | |
Array of transforms for each | |
frame F and joint J multiplied | |
together | |
""" | |
return ut.matrix_multiply(t0s, t1s) | |
def transforms_inv(ts): | |
fts = ts.reshape(-1, 4, 4) | |
fts = np.array(list(map(lambda x: np.linalg.inv(x), fts))) | |
return fts.reshape(ts.shape) | |
def transforms_blank(anim): | |
""" | |
Blank Transforms | |
Parameters | |
---------- | |
anim : Animation | |
Input animation | |
Returns | |
------- | |
transforms : (F, J, 4, 4) ndarray | |
Array of identity transforms for | |
each frame F and joint J | |
""" | |
ts = np.zeros(anim.shape + (4, 4)) | |
ts[:, :, 0, 0] = 1.0; | |
ts[:, :, 1, 1] = 1.0; | |
ts[:, :, 2, 2] = 1.0; | |
ts[:, :, 3, 3] = 1.0; | |
return ts | |
# global transformation matrices | |
def transforms_global(anim): | |
""" | |
Global Animation Transforms | |
This relies on joint ordering | |
being incremental. That means a joint | |
J1 must not be a ancestor of J0 if | |
J0 appears before J1 in the joint | |
ordering. | |
Parameters | |
---------- | |
anim : Animation | |
Input animation | |
Returns | |
------ | |
transforms : (F, J, 4, 4) ndarray | |
Array of global transforms for | |
each frame F and joint J | |
""" | |
locals = transforms_local(anim) | |
globals = transforms_blank(anim) | |
globals[:, 0] = locals[:, 0] | |
for i in range(1, anim.shape[1]): | |
globals[:, i] = transforms_multiply(globals[:, anim.parents[i]], locals[:, i]) | |
return globals | |
# !!! useful! | |
def positions_global(anim): | |
""" | |
Global Joint Positions | |
Given an animation compute the global joint | |
positions at at every frame | |
Parameters | |
---------- | |
anim : Animation | |
Input animation | |
Returns | |
------- | |
positions : (F, J, 3) ndarray | |
Positions for every frame F | |
and joint position J | |
""" | |
# get the last column -- corresponding to the coordinates | |
positions = transforms_global(anim)[:, :, :, 3] | |
return positions[:, :, :3] / positions[:, :, 3, np.newaxis] | |
""" Rotations """ | |
def rotations_global(anim): | |
""" | |
Global Animation Rotations | |
This relies on joint ordering | |
being incremental. That means a joint | |
J1 must not be a ancestor of J0 if | |
J0 appears before J1 in the joint | |
ordering. | |
Parameters | |
---------- | |
anim : Animation | |
Input animation | |
Returns | |
------- | |
points : (F, J) Quaternions | |
global rotations for every frame F | |
and joint J | |
""" | |
joints = np.arange(anim.shape[1]) | |
parents = np.arange(anim.shape[1]) | |
locals = anim.rotations | |
globals = Quaternions.id(anim.shape) | |
globals[:, 0] = locals[:, 0] | |
for i in range(1, anim.shape[1]): | |
globals[:, i] = globals[:, anim.parents[i]] * locals[:, i] | |
return globals | |
def rotations_parents_global(anim): | |
rotations = rotations_global(anim) | |
rotations = rotations[:, anim.parents] | |
rotations[:, 0] = Quaternions.id(len(anim)) | |
return rotations | |
""" Offsets & Orients """ | |
def orients_global(anim): | |
joints = np.arange(anim.shape[1]) | |
parents = np.arange(anim.shape[1]) | |
locals = anim.orients | |
globals = Quaternions.id(anim.shape[1]) | |
globals[:, 0] = locals[:, 0] | |
for i in range(1, anim.shape[1]): | |
globals[:, i] = globals[:, anim.parents[i]] * locals[:, i] | |
return globals | |
def offsets_transforms_local(anim): | |
transforms = anim.orients[np.newaxis].transforms() | |
transforms = np.concatenate([transforms, np.zeros(transforms.shape[:2] + (3, 1))], axis=-1) | |
transforms = np.concatenate([transforms, np.zeros(transforms.shape[:2] + (1, 4))], axis=-2) | |
transforms[:, :, 0:3, 3] = anim.offsets[np.newaxis] | |
transforms[:, :, 3:4, 3] = 1.0 | |
return transforms | |
def offsets_transforms_global(anim): | |
joints = np.arange(anim.shape[1]) | |
parents = np.arange(anim.shape[1]) | |
locals = offsets_transforms_local(anim) | |
globals = transforms_blank(anim) | |
globals[:, 0] = locals[:, 0] | |
for i in range(1, anim.shape[1]): | |
globals[:, i] = transforms_multiply(globals[:, anim.parents[i]], locals[:, i]) | |
return globals | |
def offsets_global(anim): | |
offsets = offsets_transforms_global(anim)[:, :, :, 3] | |
return offsets[0, :, :3] / offsets[0, :, 3, np.newaxis] | |
""" Lengths """ | |
def offset_lengths(anim): | |
return np.sum(anim.offsets[1:] ** 2.0, axis=1) ** 0.5 | |
def position_lengths(anim): | |
return np.sum(anim.positions[:, 1:] ** 2.0, axis=2) ** 0.5 | |
""" Skinning """ | |
def skin(anim, rest, weights, mesh, maxjoints=4): | |
full_transforms = transforms_multiply( | |
transforms_global(anim), | |
transforms_inv(transforms_global(rest[0:1]))) | |
weightids = np.argsort(-weights, axis=1)[:, :maxjoints] | |
weightvls = np.array(list(map(lambda w, i: w[i], weights, weightids))) | |
weightvls = weightvls / weightvls.sum(axis=1)[..., np.newaxis] | |
verts = np.hstack([mesh, np.ones((len(mesh), 1))]) | |
verts = verts[np.newaxis, :, np.newaxis, :, np.newaxis] | |
verts = transforms_multiply(full_transforms[:, weightids], verts) | |
verts = (verts[:, :, :, :3] / verts[:, :, :, 3:4])[:, :, :, :, 0] | |
return np.sum(weightvls[np.newaxis, :, :, np.newaxis] * verts, axis=2) |