import numpy as np def quat2mat(qw, qx, qy, qz): s = 1 return np.array([ [1-2*s*(qy**2+qz**2), 2*s*(qx*qy-qz*qw), 2*s*(qx*qz+qy*qw), 0], [2*s*(qx*qy+qz*qw), 1-2*s*(qx**2+qz**2), 2*s*(qy*qz-qx*qw), 0], [2*s*(qx*qz-qy*qw), 2*s*(qy*qz+qx*qw), 1-2*s*(qx**2+qy**2), 0], [0, 0, 0, 1], ]) def rotation_quaternion(axis, angle): """Returns a quaternion for rotating by some axis and angle. Inputs: axis -- numpy array of shape (3,), with axis to rotate around angle -- angle in radians by which to rotate """ qc = np.cos(angle / 2) qs = np.sin(angle / 2) qv = qs * np.array(axis) return (qc, qv[0], qv[1], qv[2]) class Transform(object): def __init__(self, mtx=None): if mtx is None: self.mtx = np.identity(4) else: self.mtx = mtx def _compose(self, mtx2): return Transform(self.mtx @ mtx2) def compose(self, xform): return self._compose(xform.mtx) def scale(self, *a, **kw): return self._compose(mtx_scale(*a, **kw)) def translate(self, *a, **kw): return self._compose(mtx_translate(*a, **kw)) def rotate(self, *a, **kw): return self._compose(mtx_rotate(*a, **kw)) def reflect(self, *a, **kw): return self._compose(mtx_reflect(*a, **kw)) def identity(self, *a, **kw): return self._compose(mtx_identity(*a, **kw)) def apply_to(self, vs): # Homogeneous coords, so append a column of ones. vh is then shape (N,4): vh = np.hstack([vs, np.ones((vs.shape[0], 1), dtype=vs.dtype)]) # As we have row vectors, we're doing basically (A*x)^T=(x^T)*(A^T) # hence transposing the matrix, while vectors are already transposed. return (vh @ self.mtx.T)[:,0:3] def get_scale(self): norms = np.linalg.norm(self.mtx, axis=0) return norms[:3] def mtx_scale(sx, sy=None, sz=None): if sy is None: sy = sx if sz is None: sz = sx return np.array([ [sx, 0, 0, 0], [0, sy, 0, 0], [0, 0, sz, 0], [0, 0, 0, 1], ]) def mtx_translate(x, y, z): return np.array([ [1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], [0, 0, 0, 1], ]) def mtx_rotate(axis, angle): q = rotation_quaternion(axis, angle) return quat2mat(*q) def mtx_reflect(axis): # axis must be norm-1 axis = np.array(axis) axis = axis / np.linalg.norm(axis) a,b,c = axis[0], axis[1], axis[2] return np.array([ [1-2*a*a, -2*a*b, -2*a*c, 0], [-2*a*b, 1-2*b*b, -2*b*c, 0], [-2*a*c, -2*b*c, 1-2*c*c, 0], [0, 0, 0, 1], ]) def mtx_identity(): return np.eye(4)