| import numpy as np |
| import scipy.linalg as linalg |
|
|
| from visualization import Animation |
| from visualization import AnimationStructure |
|
|
| from visualization.Quaternions import Quaternions |
|
|
|
|
| class BasicInverseKinematics: |
| """ |
| Basic Inverse Kinematics Solver |
| |
| This is an extremely simple full body IK |
| solver. |
| |
| It works given the following conditions: |
| |
| * All joint targets must be specified |
| * All joint targets must be in reach |
| * All joint targets must not differ |
| extremely from the starting pose |
| * No bone length constraints can be violated |
| * The root translation and rotation are |
| set to good initial values |
| |
| It works under the observation that if the |
| _directions_ the joints are pointing toward |
| match the _directions_ of the vectors between |
| the target joints then the pose should match |
| that of the target pose. |
| |
| Therefore it iterates over joints rotating |
| each joint such that the vectors between it |
| and it's children match that of the target |
| positions. |
| |
| Parameters |
| ---------- |
| |
| animation : Animation |
| animation input |
| |
| positions : (F, J, 3) ndarray |
| target positions for each frame F |
| and each joint J |
| |
| iterations : int |
| Optional number of iterations. |
| If the above conditions are met |
| 1 iteration should be enough, |
| therefore the default is 1 |
| |
| silent : bool |
| Optional if to suppress output |
| defaults to False |
| """ |
|
|
| def __init__(self, animation, positions, iterations=1, silent=True): |
|
|
| self.animation = animation |
| self.positions = positions |
| self.iterations = iterations |
| self.silent = silent |
|
|
| def __call__(self): |
|
|
| children = AnimationStructure.children_list(self.animation.parents) |
|
|
| for i in range(self.iterations): |
|
|
| for j in AnimationStructure.joints(self.animation.parents): |
|
|
| c = np.array(children[j]) |
| if len(c) == 0: continue |
|
|
| anim_transforms = Animation.transforms_global(self.animation) |
| anim_positions = anim_transforms[:, :, :3, 3] |
| anim_rotations = Quaternions.from_transforms(anim_transforms) |
|
|
| jdirs = anim_positions[:, c] - anim_positions[:, np.newaxis, j] |
| ddirs = self.positions[:, c] - anim_positions[:, np.newaxis, j] |
|
|
| jsums = np.sqrt(np.sum(jdirs ** 2.0, axis=-1)) + 1e-10 |
| dsums = np.sqrt(np.sum(ddirs ** 2.0, axis=-1)) + 1e-10 |
|
|
| jdirs = jdirs / jsums[:, :, np.newaxis] |
| ddirs = ddirs / dsums[:, :, np.newaxis] |
|
|
| angles = np.arccos(np.sum(jdirs * ddirs, axis=2).clip(-1, 1)) |
| axises = np.cross(jdirs, ddirs) |
| axises = -anim_rotations[:, j, np.newaxis] * axises |
|
|
| rotations = Quaternions.from_angle_axis(angles, axises) |
|
|
| if rotations.shape[1] == 1: |
| averages = rotations[:, 0] |
| else: |
| averages = Quaternions.exp(rotations.log().mean(axis=-2)) |
|
|
| self.animation.rotations[:, j] = self.animation.rotations[:, j] * averages |
|
|
| if not self.silent: |
| anim_positions = Animation.positions_global(self.animation) |
| error = np.mean(np.sum((anim_positions - self.positions) ** 2.0, axis=-1) ** 0.5) |
| print('[BasicInverseKinematics] Iteration %i Error: %f' % (i + 1, error)) |
|
|
| return self.animation |
|
|
|
|
| class JacobianInverseKinematics: |
| """ |
| Jacobian Based Full Body IK Solver |
| |
| This is a full body IK solver which |
| uses the dampened least squares inverse |
| jacobian method. |
| |
| It should remain fairly stable and effective |
| even for joint positions which are out of |
| reach and it can also take any number of targets |
| to treat as end effectors. |
| |
| Parameters |
| ---------- |
| |
| animation : Animation |
| animation to solve inverse problem on |
| |
| targets : {int : (F, 3) ndarray} |
| Dictionary of target positions for each |
| frame F, mapping joint index to |
| a target position |
| |
| references : (F, 3) |
| Optional list of J joint position |
| references for which the result |
| should bias toward |
| |
| iterations : int |
| Optional number of iterations to |
| compute. More iterations results in |
| better accuracy but takes longer to |
| compute. Default is 10. |
| |
| recalculate : bool |
| Optional if to recalcuate jacobian |
| each iteration. Gives better accuracy |
| but slower to compute. Defaults to True |
| |
| damping : float |
| Optional damping constant. Higher |
| damping increases stability but |
| requires more iterations to converge. |
| Defaults to 5.0 |
| |
| secondary : float |
| Force, or bias toward secondary target. |
| Defaults to 0.25 |
| |
| silent : bool |
| Optional if to suppress output |
| defaults to False |
| """ |
|
|
| def __init__(self, animation, targets, |
| references=None, iterations=10, |
| recalculate=True, damping=2.0, |
| secondary=0.25, translate=False, |
| silent=False, weights=None, |
| weights_translate=None): |
|
|
| self.animation = animation |
| self.targets = targets |
| self.references = references |
|
|
| self.iterations = iterations |
| self.recalculate = recalculate |
| self.damping = damping |
| self.secondary = secondary |
| self.translate = translate |
| self.silent = silent |
| self.weights = weights |
| self.weights_translate = weights_translate |
|
|
| def cross(self, a, b): |
| o = np.empty(b.shape) |
| o[..., 0] = a[..., 1] * b[..., 2] - a[..., 2] * b[..., 1] |
| o[..., 1] = a[..., 2] * b[..., 0] - a[..., 0] * b[..., 2] |
| o[..., 2] = a[..., 0] * b[..., 1] - a[..., 1] * b[..., 0] |
| return o |
|
|
| def jacobian(self, x, fp, fr, ts, dsc, tdsc): |
|
|
| """ Find parent rotations """ |
| prs = fr[:, self.animation.parents] |
| prs[:, 0] = Quaternions.id((1)) |
|
|
| """ Find global positions of target joints """ |
| tps = fp[:, np.array(list(ts.keys()))] |
|
|
| """ Get partial rotations """ |
| qys = Quaternions.from_angle_axis(x[:, 1:prs.shape[1] * 3:3], np.array([[[0, 1, 0]]])) |
| qzs = Quaternions.from_angle_axis(x[:, 2:prs.shape[1] * 3:3], np.array([[[0, 0, 1]]])) |
|
|
| """ Find axis of rotations """ |
| es = np.empty((len(x), fr.shape[1] * 3, 3)) |
| es[:, 0::3] = ((prs * qzs) * qys) * np.array([[[1, 0, 0]]]) |
| es[:, 1::3] = ((prs * qzs) * np.array([[[0, 1, 0]]])) |
| es[:, 2::3] = ((prs * np.array([[[0, 0, 1]]]))) |
|
|
| """ Construct Jacobian """ |
| j = fp.repeat(3, axis=1) |
| j = dsc[np.newaxis, :, :, np.newaxis] * (tps[:, np.newaxis, :] - j[:, :, np.newaxis]) |
| j = self.cross(es[:, :, np.newaxis, :], j) |
| j = np.swapaxes(j.reshape((len(x), fr.shape[1] * 3, len(ts) * 3)), 1, 2) |
|
|
| if self.translate: |
| es = np.empty((len(x), fr.shape[1] * 3, 3)) |
| es[:, 0::3] = prs * np.array([[[1, 0, 0]]]) |
| es[:, 1::3] = prs * np.array([[[0, 1, 0]]]) |
| es[:, 2::3] = prs * np.array([[[0, 0, 1]]]) |
|
|
| jt = tdsc[np.newaxis, :, :, np.newaxis] * es[:, :, np.newaxis, :].repeat(tps.shape[1], axis=2) |
| jt = np.swapaxes(jt.reshape((len(x), fr.shape[1] * 3, len(ts) * 3)), 1, 2) |
|
|
| j = np.concatenate([j, jt], axis=-1) |
|
|
| return j |
|
|
| |
| def __call__(self, descendants=None, gamma=1.0): |
|
|
| self.descendants = descendants |
|
|
| """ Calculate Masses """ |
| if self.weights is None: |
| self.weights = np.ones(self.animation.shape[1]) |
|
|
| if self.weights_translate is None: |
| self.weights_translate = np.ones(self.animation.shape[1]) |
|
|
| """ Calculate Descendants """ |
| if self.descendants is None: |
| self.descendants = AnimationStructure.descendants_mask(self.animation.parents) |
|
|
| self.tdescendants = np.eye(self.animation.shape[1]) + self.descendants |
|
|
| self.first_descendants = self.descendants[:, np.array(list(self.targets.keys()))].repeat(3, axis=0).astype(int) |
| self.first_tdescendants = self.tdescendants[:, np.array(list(self.targets.keys()))].repeat(3, axis=0).astype( |
| int) |
|
|
| """ Calculate End Effectors """ |
| self.endeff = np.array(list(self.targets.values())) |
| self.endeff = np.swapaxes(self.endeff, 0, 1) |
|
|
| if not self.references is None: |
| self.second_descendants = self.descendants.repeat(3, axis=0).astype(int) |
| self.second_tdescendants = self.tdescendants.repeat(3, axis=0).astype(int) |
| self.second_targets = dict([(i, self.references[:, i]) for i in range(self.references.shape[1])]) |
|
|
| nf = len(self.animation) |
| nj = self.animation.shape[1] |
|
|
| if not self.silent: |
| gp = Animation.positions_global(self.animation) |
| gp = gp[:, np.array(list(self.targets.keys()))] |
| error = np.mean(np.sqrt(np.sum((self.endeff - gp) ** 2.0, axis=2))) |
| print('[JacobianInverseKinematics] Start | Error: %f' % error) |
|
|
| for i in range(self.iterations): |
|
|
| """ Get Global Rotations & Positions """ |
| gt = Animation.transforms_global(self.animation) |
| gp = gt[:, :, :, 3] |
| gp = gp[:, :, :3] / gp[:, :, 3, np.newaxis] |
| gr = Quaternions.from_transforms(gt) |
|
|
| x = self.animation.rotations.euler().reshape(nf, -1) |
| w = self.weights.repeat(3) |
|
|
| if self.translate: |
| x = np.hstack([x, self.animation.positions.reshape(nf, -1)]) |
| w = np.hstack([w, self.weights_translate.repeat(3)]) |
|
|
| """ Generate Jacobian """ |
| if self.recalculate or i == 0: |
| j = self.jacobian(x, gp, gr, self.targets, self.first_descendants, self.first_tdescendants) |
|
|
| """ Update Variables """ |
| l = self.damping * (1.0 / (w + 0.001)) |
| d = (l * l) * np.eye(x.shape[1]) |
| e = gamma * (self.endeff.reshape(nf, -1) - gp[:, np.array(list(self.targets.keys()))].reshape(nf, -1)) |
|
|
| x += np.array(list(map(lambda jf, ef: |
| linalg.lu_solve(linalg.lu_factor(jf.T.dot(jf) + d), jf.T.dot(ef)), j, e))) |
|
|
| """ Generate Secondary Jacobian """ |
| if self.references is not None: |
|
|
| ns = np.array(list(map(lambda jf: |
| np.eye(x.shape[1]) - linalg.solve(jf.T.dot(jf) + d, jf.T.dot(jf)), j))) |
|
|
| if self.recalculate or i == 0: |
| j2 = self.jacobian(x, gp, gr, self.second_targets, self.second_descendants, |
| self.second_tdescendants) |
|
|
| e2 = self.secondary * (self.references.reshape(nf, -1) - gp.reshape(nf, -1)) |
|
|
| x += np.array(list(map(lambda nsf, j2f, e2f: |
| nsf.dot(linalg.lu_solve(linalg.lu_factor(j2f.T.dot(j2f) + d), j2f.T.dot(e2f))), |
| ns, j2, e2))) |
|
|
| """ Set Back Rotations / Translations """ |
| self.animation.rotations = Quaternions.from_euler( |
| x[:, :nj * 3].reshape((nf, nj, 3)), order='xyz', world=True) |
|
|
| if self.translate: |
| self.animation.positions = x[:, nj * 3:].reshape((nf, nj, 3)) |
|
|
| """ Generate Error """ |
|
|
| if not self.silent: |
| gp = Animation.positions_global(self.animation) |
| gp = gp[:, np.array(list(self.targets.keys()))] |
| error = np.mean(np.sum((self.endeff - gp) ** 2.0, axis=2) ** 0.5) |
| print('[JacobianInverseKinematics] Iteration %i | Error: %f' % (i + 1, error)) |
| return self.animation |
|
|
|
|
| class BasicJacobianIK: |
| """ |
| Same interface as BasicInverseKinematics |
| but uses the Jacobian IK Solver Instead |
| """ |
|
|
| def __init__(self, animation, positions, iterations=10, silent=True, **kw): |
| targets = dict([(i, positions[:, i]) for i in range(positions.shape[1])]) |
| self.ik = JacobianInverseKinematics(animation, targets, iterations=iterations, silent=silent, **kw) |
|
|
| def __call__(self, **kw): |
| return self.ik(**kw) |
|
|
|
|
| class ICP: |
|
|
| def __init__(self, |
| anim, rest, weights, mesh, goal, |
| find_closest=True, damping=10, |
| iterations=10, silent=True, |
| translate=True, recalculate=True, |
| weights_translate=None): |
|
|
| self.animation = anim |
| self.rest = rest |
| self.vweights = weights |
| self.mesh = mesh |
| self.goal = goal |
| self.find_closest = find_closest |
| self.iterations = iterations |
| self.silent = silent |
| self.translate = translate |
| self.damping = damping |
| self.weights = None |
| self.weights_translate = weights_translate |
| self.recalculate = recalculate |
|
|
| def cross(self, a, b): |
| o = np.empty(b.shape) |
| o[..., 0] = a[..., 1] * b[..., 2] - a[..., 2] * b[..., 1] |
| o[..., 1] = a[..., 2] * b[..., 0] - a[..., 0] * b[..., 2] |
| o[..., 2] = a[..., 0] * b[..., 1] - a[..., 1] * b[..., 0] |
| return o |
|
|
| def jacobian(self, x, fp, fr, goal, weights, des_r, des_t): |
|
|
| """ Find parent rotations """ |
| prs = fr[:, self.animation.parents] |
| prs[:, 0] = Quaternions.id((1)) |
|
|
| """ Get partial rotations """ |
| qys = Quaternions.from_angle_axis(x[:, 1:prs.shape[1] * 3:3], np.array([[[0, 1, 0]]])) |
| qzs = Quaternions.from_angle_axis(x[:, 2:prs.shape[1] * 3:3], np.array([[[0, 0, 1]]])) |
|
|
| """ Find axis of rotations """ |
| es = np.empty((len(x), fr.shape[1] * 3, 3)) |
| es[:, 0::3] = ((prs * qzs) * qys) * np.array([[[1, 0, 0]]]) |
| es[:, 1::3] = ((prs * qzs) * np.array([[[0, 1, 0]]])) |
| es[:, 2::3] = ((prs * np.array([[[0, 0, 1]]]))) |
|
|
| """ Construct Jacobian """ |
| j = fp.repeat(3, axis=1) |
| j = des_r[np.newaxis, :, :, :, np.newaxis] * ( |
| goal[:, np.newaxis, :, np.newaxis] - j[:, :, np.newaxis, np.newaxis]) |
| j = np.sum(j * weights[np.newaxis, np.newaxis, :, :, np.newaxis], 3) |
| j = self.cross(es[:, :, np.newaxis, :], j) |
| j = np.swapaxes(j.reshape((len(x), fr.shape[1] * 3, goal.shape[1] * 3)), 1, 2) |
|
|
| if self.translate: |
| es = np.empty((len(x), fr.shape[1] * 3, 3)) |
| es[:, 0::3] = prs * np.array([[[1, 0, 0]]]) |
| es[:, 1::3] = prs * np.array([[[0, 1, 0]]]) |
| es[:, 2::3] = prs * np.array([[[0, 0, 1]]]) |
|
|
| jt = des_t[np.newaxis, :, :, :, np.newaxis] * es[:, :, np.newaxis, np.newaxis, :].repeat(goal.shape[1], |
| axis=2) |
| jt = np.sum(jt * weights[np.newaxis, np.newaxis, :, :, np.newaxis], 3) |
| jt = np.swapaxes(jt.reshape((len(x), fr.shape[1] * 3, goal.shape[1] * 3)), 1, 2) |
|
|
| j = np.concatenate([j, jt], axis=-1) |
|
|
| return j |
|
|
| |
| def __call__(self, descendants=None, maxjoints=4, gamma=1.0, transpose=False): |
|
|
| """ Calculate Masses """ |
| if self.weights is None: |
| self.weights = np.ones(self.animation.shape[1]) |
|
|
| if self.weights_translate is None: |
| self.weights_translate = np.ones(self.animation.shape[1]) |
|
|
| nf = len(self.animation) |
| nj = self.animation.shape[1] |
| nv = self.goal.shape[1] |
|
|
| weightids = np.argsort(-self.vweights, axis=1)[:, :maxjoints] |
| weightvls = np.array(list(map(lambda w, i: w[i], self.vweights, weightids))) |
| weightvls = weightvls / weightvls.sum(axis=1)[..., np.newaxis] |
|
|
| if descendants is None: |
| self.descendants = AnimationStructure.descendants_mask(self.animation.parents) |
| else: |
| self.descendants = descendants |
|
|
| des_r = np.eye(nj) + self.descendants |
| des_r = des_r[:, weightids].repeat(3, axis=0) |
|
|
| des_t = np.eye(nj) + self.descendants |
| des_t = des_t[:, weightids].repeat(3, axis=0) |
|
|
| if not self.silent: |
| curr = Animation.skin(self.animation, self.rest, self.vweights, self.mesh, maxjoints=maxjoints) |
| error = np.mean(np.sqrt(np.sum((curr - self.goal) ** 2.0, axis=-1))) |
| print('[ICP] Start | Error: %f' % error) |
|
|
| for i in range(self.iterations): |
|
|
| """ Get Global Rotations & Positions """ |
| gt = Animation.transforms_global(self.animation) |
| gp = gt[:, :, :, 3] |
| gp = gp[:, :, :3] / gp[:, :, 3, np.newaxis] |
| gr = Quaternions.from_transforms(gt) |
|
|
| x = self.animation.rotations.euler().reshape(nf, -1) |
| w = self.weights.repeat(3) |
|
|
| if self.translate: |
| x = np.hstack([x, self.animation.positions.reshape(nf, -1)]) |
| w = np.hstack([w, self.weights_translate.repeat(3)]) |
|
|
| """ Get Current State """ |
| curr = Animation.skin(self.animation, self.rest, self.vweights, self.mesh, maxjoints=maxjoints) |
|
|
| """ Find Cloest Points """ |
| if self.find_closest: |
| mapping = np.argmin( |
| (curr[:, :, np.newaxis] - |
| self.goal[:, np.newaxis, :]) ** 2.0, axis=2) |
| e = gamma * (np.array(list(map(lambda g, m: g[m], self.goal, mapping))) - curr).reshape(nf, -1) |
| else: |
| e = gamma * (self.goal - curr).reshape(nf, -1) |
|
|
| """ Generate Jacobian """ |
| if self.recalculate or i == 0: |
| j = self.jacobian(x, gp, gr, self.goal, weightvls, des_r, des_t) |
|
|
| """ Update Variables """ |
| l = self.damping * (1.0 / (w + 1e-10)) |
| d = (l * l) * np.eye(x.shape[1]) |
|
|
| if transpose: |
| x += np.array(list(map(lambda jf, ef: jf.T.dot(ef), j, e))) |
| else: |
| x += np.array(list(map(lambda jf, ef: |
| linalg.lu_solve(linalg.lu_factor(jf.T.dot(jf) + d), jf.T.dot(ef)), j, e))) |
|
|
| """ Set Back Rotations / Translations """ |
| self.animation.rotations = Quaternions.from_euler( |
| x[:, :nj * 3].reshape((nf, nj, 3)), order='xyz', world=True) |
|
|
| if self.translate: |
| self.animation.positions = x[:, nj * 3:].reshape((nf, nj, 3)) |
|
|
| if not self.silent: |
| curr = Animation.skin(self.animation, self.rest, self.vweights, self.mesh) |
| error = np.mean(np.sqrt(np.sum((curr - self.goal) ** 2.0, axis=-1))) |
| print('[ICP] Iteration %i | Error: %f' % (i + 1, error)) |
|
|
| import torch |
| from torch import nn |
| class InverseKinematics: |
| def __init__(self, rotations: torch.Tensor, positions: torch.Tensor, offset, parents, constrains): |
| self.rotations = rotations.cuda() |
| self.rotations.requires_grad_(True) |
| self.position = positions.cuda() |
| self.position.requires_grad_(True) |
|
|
| self.parents = parents |
| self.offset = offset.cuda() |
| self.constrains = constrains.cuda() |
| |
| self.optimizer = torch.optim.AdamW([self.position, self.rotations], lr=5e-2, betas=(0.9, 0.999)) |
| self.crit = nn.MSELoss() |
| self.weights = torch.ones([1,22,1]).cuda() |
| self.weights[:, [4, 8]] = 0.8 |
| self.weights[:, [1, 5]] = 2. |
|
|
| def step(self): |
| self.optimizer.zero_grad() |
| glb = self.forward(self.rotations, self.position, self.offset, order='', quater=True, world=True) |
| |
| loss = self.crit(glb*self.weights, self.constrains*self.weights) |
| |
| loss += 0.5 * self.crit(self.rotations[1:, [3, 7, 12, 16, 20]], self.rotations[:-1, [3, 7, 12, 16, 20]]) + 0.1 * self.crit(self.rotations[1:], self.rotations[:-1]) |
| loss.backward() |
| self.optimizer.step() |
| self.glb = glb |
| return loss.item() |
|
|
| def tloss(self, time): |
| return self.crit(self.glb[time, :], self.constrains[time, :]) |
|
|
| def all_loss(self): |
| res = [self.tloss(t).detach().numpy() for t in range(self.constrains.shape[0])] |
| return np.array(res) |
|
|
| ''' |
| rotation should have shape batch_size * Joint_num * (3/4) * Time |
| position should have shape batch_size * 3 * Time |
| offset should have shape batch_size * Joint_num * 3 |
| output have shape batch_size * Time * Joint_num * 3 |
| ''' |
|
|
| def forward(self, rotation: torch.Tensor, position: torch.Tensor, offset: torch.Tensor, order='xyz', quater=False, |
| world=True): |
| ''' |
| if not quater and rotation.shape[-2] != 3: raise Exception('Unexpected shape of rotation') |
| if quater and rotation.shape[-2] != 4: raise Exception('Unexpected shape of rotation') |
| rotation = rotation.permute(0, 3, 1, 2) |
| position = position.permute(0, 2, 1) |
| ''' |
| result = torch.empty(rotation.shape[:-1] + (3,), device=position.device) |
|
|
| norm = torch.norm(rotation, dim=-1, keepdim=True) |
| rotation = rotation / norm |
|
|
| |
| transform = self.transform_from_quaternion(rotation) |
| |
| |
|
|
| offset = offset.reshape((-1, 1, offset.shape[-2], offset.shape[-1], 1)) |
|
|
| result[..., 0, :] = position |
| for i, pi in enumerate(self.parents): |
| if pi == -1: |
| assert i == 0 |
| continue |
|
|
| result[..., i, :] = torch.matmul(transform[..., pi, :, :], offset[..., i, :, :]).squeeze() |
| transform[..., i, :, :] = torch.matmul(transform[..., pi, :, :].clone(), transform[..., i, :, :].clone()) |
| if world: result[..., i, :] += result[..., pi, :] |
| return result |
|
|
| @staticmethod |
| def transform_from_axis(euler, axis): |
| transform = torch.empty(euler.shape[0:3] + (3, 3), device=euler.device) |
| cos = torch.cos(euler) |
| sin = torch.sin(euler) |
| cord = ord(axis) - ord('x') |
|
|
| transform[..., cord, :] = transform[..., :, cord] = 0 |
| transform[..., cord, cord] = 1 |
|
|
| if axis == 'x': |
| transform[..., 1, 1] = transform[..., 2, 2] = cos |
| transform[..., 1, 2] = -sin |
| transform[..., 2, 1] = sin |
| if axis == 'y': |
| transform[..., 0, 0] = transform[..., 2, 2] = cos |
| transform[..., 0, 2] = sin |
| transform[..., 2, 0] = -sin |
| if axis == 'z': |
| transform[..., 0, 0] = transform[..., 1, 1] = cos |
| transform[..., 0, 1] = -sin |
| transform[..., 1, 0] = sin |
|
|
| return transform |
|
|
| @staticmethod |
| def transform_from_quaternion(quater: torch.Tensor): |
| qw = quater[..., 0] |
| qx = quater[..., 1] |
| qy = quater[..., 2] |
| qz = quater[..., 3] |
|
|
| x2 = qx + qx |
| y2 = qy + qy |
| z2 = qz + qz |
| xx = qx * x2 |
| yy = qy * y2 |
| wx = qw * x2 |
| xy = qx * y2 |
| yz = qy * z2 |
| wy = qw * y2 |
| xz = qx * z2 |
| zz = qz * z2 |
| wz = qw * z2 |
|
|
| m = torch.empty(quater.shape[:-1] + (3, 3), device=quater.device) |
| m[..., 0, 0] = 1.0 - (yy + zz) |
| m[..., 0, 1] = xy - wz |
| m[..., 0, 2] = xz + wy |
| m[..., 1, 0] = xy + wz |
| m[..., 1, 1] = 1.0 - (xx + zz) |
| m[..., 1, 2] = yz - wx |
| m[..., 2, 0] = xz - wy |
| m[..., 2, 1] = yz + wx |
| m[..., 2, 2] = 1.0 - (xx + yy) |
|
|
| return m |