123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 |
- from sympy.core.numbers import Rational
- from sympy.core.singleton import S
- from sympy.functions.elementary.complexes import (conjugate, im, re, sign)
- from sympy.functions.elementary.exponential import (exp, log as ln)
- from sympy.functions.elementary.miscellaneous import sqrt
- from sympy.functions.elementary.trigonometric import (acos, cos, sin)
- from sympy.simplify.trigsimp import trigsimp
- from sympy.integrals.integrals import integrate
- from sympy.matrices.dense import MutableDenseMatrix as Matrix
- from sympy.core.sympify import sympify
- from sympy.core.expr import Expr
- from mpmath.libmp.libmpf import prec_to_dps
- class Quaternion(Expr):
- """Provides basic quaternion operations.
- Quaternion objects can be instantiated as Quaternion(a, b, c, d)
- as in (a + b*i + c*j + d*k).
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q
- 1 + 2*i + 3*j + 4*k
- Quaternions over complex fields can be defined as :
- >>> from sympy import Quaternion
- >>> from sympy import symbols, I
- >>> x = symbols('x')
- >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
- >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
- >>> q1
- x + x**3*i + x*j + x**2*k
- >>> q2
- (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
- References
- ==========
- .. [1] http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
- .. [2] https://en.wikipedia.org/wiki/Quaternion
- """
- _op_priority = 11.0
- is_commutative = False
- def __new__(cls, a=0, b=0, c=0, d=0, real_field=True):
- a = sympify(a)
- b = sympify(b)
- c = sympify(c)
- d = sympify(d)
- if any(i.is_commutative is False for i in [a, b, c, d]):
- raise ValueError("arguments have to be commutative")
- else:
- obj = Expr.__new__(cls, a, b, c, d)
- obj._a = a
- obj._b = b
- obj._c = c
- obj._d = d
- obj._real_field = real_field
- return obj
- @property
- def a(self):
- return self._a
- @property
- def b(self):
- return self._b
- @property
- def c(self):
- return self._c
- @property
- def d(self):
- return self._d
- @property
- def real_field(self):
- return self._real_field
- @classmethod
- def from_axis_angle(cls, vector, angle):
- """Returns a rotation quaternion given the axis and the angle of rotation.
- Parameters
- ==========
- vector : tuple of three numbers
- The vector representation of the given axis.
- angle : number
- The angle by which axis is rotated (in radians).
- Returns
- =======
- Quaternion
- The normalized rotation quaternion calculated from the given axis and the angle of rotation.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import pi, sqrt
- >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
- >>> q
- 1/2 + 1/2*i + 1/2*j + 1/2*k
- """
- (x, y, z) = vector
- norm = sqrt(x**2 + y**2 + z**2)
- (x, y, z) = (x / norm, y / norm, z / norm)
- s = sin(angle * S.Half)
- a = cos(angle * S.Half)
- b = x * s
- c = y * s
- d = z * s
- # note that this quaternion is already normalized by construction:
- # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1
- # so, what we return is a normalized quaternion
- return cls(a, b, c, d)
- @classmethod
- def from_rotation_matrix(cls, M):
- """Returns the equivalent quaternion of a matrix. The quaternion will be normalized
- only if the matrix is special orthogonal (orthogonal and det(M) = 1).
- Parameters
- ==========
- M : Matrix
- Input matrix to be converted to equivalent quaternion. M must be special
- orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.
- Returns
- =======
- Quaternion
- The quaternion equivalent to given matrix.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import Matrix, symbols, cos, sin, trigsimp
- >>> x = symbols('x')
- >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
- >>> q = trigsimp(Quaternion.from_rotation_matrix(M))
- >>> q
- sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k
- """
- absQ = M.det()**Rational(1, 3)
- a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
- b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
- c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
- d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2
- b = b * sign(M[2, 1] - M[1, 2])
- c = c * sign(M[0, 2] - M[2, 0])
- d = d * sign(M[1, 0] - M[0, 1])
- return Quaternion(a, b, c, d)
- def __add__(self, other):
- return self.add(other)
- def __radd__(self, other):
- return self.add(other)
- def __sub__(self, other):
- return self.add(other*-1)
- def __mul__(self, other):
- return self._generic_mul(self, other)
- def __rmul__(self, other):
- return self._generic_mul(other, self)
- def __pow__(self, p):
- return self.pow(p)
- def __neg__(self):
- return Quaternion(-self._a, -self._b, -self._c, -self.d)
- def __truediv__(self, other):
- return self * sympify(other)**-1
- def __rtruediv__(self, other):
- return sympify(other) * self**-1
- def _eval_Integral(self, *args):
- return self.integrate(*args)
- def diff(self, *symbols, **kwargs):
- kwargs.setdefault('evaluate', True)
- return self.func(*[a.diff(*symbols, **kwargs) for a in self.args])
- def add(self, other):
- """Adds quaternions.
- Parameters
- ==========
- other : Quaternion
- The quaternion to add to current (self) quaternion.
- Returns
- =======
- Quaternion
- The resultant quaternion after adding self to other
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import symbols
- >>> q1 = Quaternion(1, 2, 3, 4)
- >>> q2 = Quaternion(5, 6, 7, 8)
- >>> q1.add(q2)
- 6 + 8*i + 10*j + 12*k
- >>> q1 + 5
- 6 + 2*i + 3*j + 4*k
- >>> x = symbols('x', real = True)
- >>> q1.add(x)
- (x + 1) + 2*i + 3*j + 4*k
- Quaternions over complex fields :
- >>> from sympy import Quaternion
- >>> from sympy import I
- >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
- >>> q3.add(2 + 3*I)
- (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
- """
- q1 = self
- q2 = sympify(other)
- # If q2 is a number or a SymPy expression instead of a quaternion
- if not isinstance(q2, Quaternion):
- if q1.real_field and q2.is_complex:
- return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
- elif q2.is_commutative:
- return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
- else:
- raise ValueError("Only commutative expressions can be added with a Quaternion.")
- return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
- + q2.d)
- def mul(self, other):
- """Multiplies quaternions.
- Parameters
- ==========
- other : Quaternion or symbol
- The quaternion to multiply to current (self) quaternion.
- Returns
- =======
- Quaternion
- The resultant quaternion after multiplying self with other
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import symbols
- >>> q1 = Quaternion(1, 2, 3, 4)
- >>> q2 = Quaternion(5, 6, 7, 8)
- >>> q1.mul(q2)
- (-60) + 12*i + 30*j + 24*k
- >>> q1.mul(2)
- 2 + 4*i + 6*j + 8*k
- >>> x = symbols('x', real = True)
- >>> q1.mul(x)
- x + 2*x*i + 3*x*j + 4*x*k
- Quaternions over complex fields :
- >>> from sympy import Quaternion
- >>> from sympy import I
- >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
- >>> q3.mul(2 + 3*I)
- (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
- """
- return self._generic_mul(self, other)
- @staticmethod
- def _generic_mul(q1, q2):
- """Generic multiplication.
- Parameters
- ==========
- q1 : Quaternion or symbol
- q2 : Quaternion or symbol
- It's important to note that if neither q1 nor q2 is a Quaternion,
- this function simply returns q1 * q2.
- Returns
- =======
- Quaternion
- The resultant quaternion after multiplying q1 and q2
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import Symbol
- >>> q1 = Quaternion(1, 2, 3, 4)
- >>> q2 = Quaternion(5, 6, 7, 8)
- >>> Quaternion._generic_mul(q1, q2)
- (-60) + 12*i + 30*j + 24*k
- >>> Quaternion._generic_mul(q1, 2)
- 2 + 4*i + 6*j + 8*k
- >>> x = Symbol('x', real = True)
- >>> Quaternion._generic_mul(q1, x)
- x + 2*x*i + 3*x*j + 4*x*k
- Quaternions over complex fields :
- >>> from sympy import Quaternion
- >>> from sympy import I
- >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
- >>> Quaternion._generic_mul(q3, 2 + 3*I)
- (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
- """
- q1 = sympify(q1)
- q2 = sympify(q2)
- # None is a Quaternion:
- if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
- return q1 * q2
- # If q1 is a number or a SymPy expression instead of a quaternion
- if not isinstance(q1, Quaternion):
- if q2.real_field and q1.is_complex:
- return Quaternion(re(q1), im(q1), 0, 0) * q2
- elif q1.is_commutative:
- return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
- else:
- raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
- # If q2 is a number or a SymPy expression instead of a quaternion
- if not isinstance(q2, Quaternion):
- if q1.real_field and q2.is_complex:
- return q1 * Quaternion(re(q2), im(q2), 0, 0)
- elif q2.is_commutative:
- return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
- else:
- raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
- return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
- q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
- -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
- q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d)
- def _eval_conjugate(self):
- """Returns the conjugate of the quaternion."""
- q = self
- return Quaternion(q.a, -q.b, -q.c, -q.d)
- def norm(self):
- """Returns the norm of the quaternion."""
- q = self
- # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
- # arise when from_axis_angle is used).
- return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))
- def normalize(self):
- """Returns the normalized form of the quaternion."""
- q = self
- return q * (1/q.norm())
- def inverse(self):
- """Returns the inverse of the quaternion."""
- q = self
- if not q.norm():
- raise ValueError("Cannot compute inverse for a quaternion with zero norm")
- return conjugate(q) * (1/q.norm()**2)
- def pow(self, p):
- """Finds the pth power of the quaternion.
- Parameters
- ==========
- p : int
- Power to be applied on quaternion.
- Returns
- =======
- Quaternion
- Returns the p-th power of the current quaternion.
- Returns the inverse if p = -1.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q.pow(4)
- 668 + (-224)*i + (-336)*j + (-448)*k
- """
- p = sympify(p)
- q = self
- if p == -1:
- return q.inverse()
- res = 1
- if not p.is_Integer:
- return NotImplemented
- if p < 0:
- q, p = q.inverse(), -p
- while p > 0:
- if p % 2 == 1:
- res = q * res
- p = p//2
- q = q * q
- return res
- def exp(self):
- """Returns the exponential of q (e^q).
- Returns
- =======
- Quaternion
- Exponential of q (e^q).
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q.exp()
- E*cos(sqrt(29))
- + 2*sqrt(29)*E*sin(sqrt(29))/29*i
- + 3*sqrt(29)*E*sin(sqrt(29))/29*j
- + 4*sqrt(29)*E*sin(sqrt(29))/29*k
- """
- # exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
- q = self
- vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
- a = exp(q.a) * cos(vector_norm)
- b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
- c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
- d = exp(q.a) * sin(vector_norm) * q.d / vector_norm
- return Quaternion(a, b, c, d)
- def _ln(self):
- """Returns the natural logarithm of the quaternion (_ln(q)).
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q._ln()
- log(sqrt(30))
- + 2*sqrt(29)*acos(sqrt(30)/30)/29*i
- + 3*sqrt(29)*acos(sqrt(30)/30)/29*j
- + 4*sqrt(29)*acos(sqrt(30)/30)/29*k
- """
- # _ln(q) = _ln||q|| + v/||v||*arccos(a/||q||)
- q = self
- vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
- q_norm = q.norm()
- a = ln(q_norm)
- b = q.b * acos(q.a / q_norm) / vector_norm
- c = q.c * acos(q.a / q_norm) / vector_norm
- d = q.d * acos(q.a / q_norm) / vector_norm
- return Quaternion(a, b, c, d)
- def _eval_evalf(self, prec):
- """Returns the floating point approximations (decimal numbers) of the quaternion.
- Returns
- =======
- Quaternion
- Floating point approximations of quaternion(self)
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import sqrt
- >>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4))
- >>> q.evalf()
- 1.00000000000000
- + 0.707106781186547*i
- + 0.577350269189626*j
- + 0.500000000000000*k
- """
- nprec = prec_to_dps(prec)
- return Quaternion(*[arg.evalf(n=nprec) for arg in self.args])
- def pow_cos_sin(self, p):
- """Computes the pth power in the cos-sin form.
- Parameters
- ==========
- p : int
- Power to be applied on quaternion.
- Returns
- =======
- Quaternion
- The p-th power in the cos-sin form.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q.pow_cos_sin(4)
- 900*cos(4*acos(sqrt(30)/30))
- + 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i
- + 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j
- + 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k
- """
- # q = ||q||*(cos(a) + u*sin(a))
- # q^p = ||q||^p * (cos(p*a) + u*sin(p*a))
- q = self
- (v, angle) = q.to_axis_angle()
- q2 = Quaternion.from_axis_angle(v, p * angle)
- return q2 * (q.norm()**p)
- def integrate(self, *args):
- """Computes integration of quaternion.
- Returns
- =======
- Quaternion
- Integration of the quaternion(self) with the given variable.
- Examples
- ========
- Indefinite Integral of quaternion :
- >>> from sympy import Quaternion
- >>> from sympy.abc import x
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q.integrate(x)
- x + 2*x*i + 3*x*j + 4*x*k
- Definite integral of quaternion :
- >>> from sympy import Quaternion
- >>> from sympy.abc import x
- >>> q = Quaternion(1, 2, 3, 4)
- >>> q.integrate((x, 1, 5))
- 4 + 8*i + 12*j + 16*k
- """
- # TODO: is this expression correct?
- return Quaternion(integrate(self.a, *args), integrate(self.b, *args),
- integrate(self.c, *args), integrate(self.d, *args))
- @staticmethod
- def rotate_point(pin, r):
- """Returns the coordinates of the point pin(a 3 tuple) after rotation.
- Parameters
- ==========
- pin : tuple
- A 3-element tuple of coordinates of a point which needs to be
- rotated.
- r : Quaternion or tuple
- Axis and angle of rotation.
- It's important to note that when r is a tuple, it must be of the form
- (axis, angle)
- Returns
- =======
- tuple
- The coordinates of the point after rotation.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import symbols, trigsimp, cos, sin
- >>> x = symbols('x')
- >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
- >>> trigsimp(Quaternion.rotate_point((1, 1, 1), q))
- (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
- >>> (axis, angle) = q.to_axis_angle()
- >>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle)))
- (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
- """
- if isinstance(r, tuple):
- # if r is of the form (vector, angle)
- q = Quaternion.from_axis_angle(r[0], r[1])
- else:
- # if r is a quaternion
- q = r.normalize()
- pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q)
- return (pout.b, pout.c, pout.d)
- def to_axis_angle(self):
- """Returns the axis and angle of rotation of a quaternion
- Returns
- =======
- tuple
- Tuple of (axis, angle)
- Examples
- ========
- >>> from sympy import Quaternion
- >>> q = Quaternion(1, 1, 1, 1)
- >>> (axis, angle) = q.to_axis_angle()
- >>> axis
- (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)
- >>> angle
- 2*pi/3
- """
- q = self
- if q.a.is_negative:
- q = q * -1
- q = q.normalize()
- angle = trigsimp(2 * acos(q.a))
- # Since quaternion is normalised, q.a is less than 1.
- s = sqrt(1 - q.a*q.a)
- x = trigsimp(q.b / s)
- y = trigsimp(q.c / s)
- z = trigsimp(q.d / s)
- v = (x, y, z)
- t = (v, angle)
- return t
- def to_rotation_matrix(self, v=None):
- """Returns the equivalent rotation transformation matrix of the quaternion
- which represents rotation about the origin if v is not passed.
- Parameters
- ==========
- v : tuple or None
- Default value: None
- Returns
- =======
- tuple
- Returns the equivalent rotation transformation matrix of the quaternion
- which represents rotation about the origin if v is not passed.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import symbols, trigsimp, cos, sin
- >>> x = symbols('x')
- >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
- >>> trigsimp(q.to_rotation_matrix())
- Matrix([
- [cos(x), -sin(x), 0],
- [sin(x), cos(x), 0],
- [ 0, 0, 1]])
- Generates a 4x4 transformation matrix (used for rotation about a point
- other than the origin) if the point(v) is passed as an argument.
- Examples
- ========
- >>> from sympy import Quaternion
- >>> from sympy import symbols, trigsimp, cos, sin
- >>> x = symbols('x')
- >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
- >>> trigsimp(q.to_rotation_matrix((1, 1, 1)))
- Matrix([
- [cos(x), -sin(x), 0, sin(x) - cos(x) + 1],
- [sin(x), cos(x), 0, -sin(x) - cos(x) + 1],
- [ 0, 0, 1, 0],
- [ 0, 0, 0, 1]])
- """
- q = self
- s = q.norm()**-2
- m00 = 1 - 2*s*(q.c**2 + q.d**2)
- m01 = 2*s*(q.b*q.c - q.d*q.a)
- m02 = 2*s*(q.b*q.d + q.c*q.a)
- m10 = 2*s*(q.b*q.c + q.d*q.a)
- m11 = 1 - 2*s*(q.b**2 + q.d**2)
- m12 = 2*s*(q.c*q.d - q.b*q.a)
- m20 = 2*s*(q.b*q.d - q.c*q.a)
- m21 = 2*s*(q.c*q.d + q.b*q.a)
- m22 = 1 - 2*s*(q.b**2 + q.c**2)
- if not v:
- return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]])
- else:
- (x, y, z) = v
- m03 = x - x*m00 - y*m01 - z*m02
- m13 = y - x*m10 - y*m11 - z*m12
- m23 = z - x*m20 - y*m21 - z*m22
- m30 = m31 = m32 = 0
- m33 = 1
- return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13],
- [m20, m21, m22, m23], [m30, m31, m32, m33]])
|