280 lines
10 KiB
Python
280 lines
10 KiB
Python
# ----------------------------------------------------------------------------
|
|
# pyglet
|
|
# Copyright (c) 2006-2008 Alex Holkner
|
|
# Copyright (c) 2008-2021 pyglet contributors
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in
|
|
# the documentation and/or other materials provided with the
|
|
# distribution.
|
|
# * Neither the name of pyglet nor the names of its
|
|
# contributors may be used to endorse or promote products
|
|
# derived from this software without specific prior written
|
|
# permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
# ----------------------------------------------------------------------------
|
|
|
|
"""Matrix and Vector operations.
|
|
|
|
This module provides classes for Matrix and Vector math.
|
|
A :py:class:`~pyglet.matrix.Mat4` class is available for representing
|
|
4x4 matricies, including helper methods for rotating, scaling, and
|
|
transforming. The internal datatype of :py:class:`~pyglet.matrix.Mat4`
|
|
is a 1-dimensional array, so instances can be passed directly to OpenGL.
|
|
|
|
"""
|
|
|
|
import math as _math
|
|
import operator as _operator
|
|
import warnings as _warnings
|
|
|
|
|
|
def create_orthogonal(left, right, bottom, top, znear, zfar):
|
|
"""Create a Mat4 orthographic projection matrix."""
|
|
width = right - left
|
|
height = top - bottom
|
|
depth = zfar - znear
|
|
|
|
sx = 2.0 / width
|
|
sy = 2.0 / height
|
|
sz = 2.0 / -depth
|
|
|
|
tx = -(right + left) / width
|
|
ty = -(top + bottom) / height
|
|
tz = -(zfar + znear) / depth
|
|
|
|
return Mat4((sx, 0.0, 0.0, 0.0,
|
|
0.0, sy, 0.0, 0.0,
|
|
0.0, 0.0, sz, 0.0,
|
|
tx, ty, tz, 1.0))
|
|
|
|
|
|
def create_perspective(left, right, bottom, top, znear, zfar, fov=60):
|
|
"""Create a Mat4 perspective projection matrix."""
|
|
width = right - left
|
|
height = top - bottom
|
|
aspect = width / height
|
|
|
|
xymax = znear * _math.tan(fov * _math.pi / 360)
|
|
ymin = -xymax
|
|
xmin = -xymax
|
|
|
|
width = xymax - xmin
|
|
height = xymax - ymin
|
|
depth = zfar - znear
|
|
q = -(zfar + znear) / depth
|
|
qn = -2 * zfar * znear / depth
|
|
|
|
w = 2 * znear / width
|
|
w = w / aspect
|
|
h = 2 * znear / height
|
|
|
|
return Mat4((w, 0, 0, 0,
|
|
0, h, 0, 0,
|
|
0, 0, q, -1,
|
|
0, 0, qn, 0))
|
|
|
|
|
|
class Mat4(tuple):
|
|
"""A 4x4 Matrix
|
|
|
|
`Mat4` is a simple immutable 4x4 Matrix, with a few operators.
|
|
Two types of multiplication are possible. The "*" operator
|
|
will perform elementwise multiplication, wheras the "@"
|
|
operator will perform Matrix multiplication. Internally,
|
|
data is stored in a linear 1D array, allowing direct passing
|
|
to OpenGL.
|
|
"""
|
|
|
|
def __new__(cls, values=None):
|
|
"""Create a 4x4 Matrix
|
|
|
|
A Matrix can be created with a list or tuple of 16 values.
|
|
If no values are provided, an "identity matrix" will be created
|
|
(1.0 on the main diagonal). Matrix objects are immutable, so
|
|
all operations return a new Mat4 object.
|
|
|
|
:Parameters:
|
|
`values` : tuple of float or int
|
|
A tuple or list containing 16 floats or ints.
|
|
"""
|
|
assert values is None or len(values) == 16, "A 4x4 Matrix requires 16 values"
|
|
return super().__new__(Mat4, values or (1.0, 0.0, 0.0, 0.0,
|
|
0.0, 1.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
0.0, 0.0, 0.0, 1.0))
|
|
|
|
def row(self, index):
|
|
"""Get a specific row as a tuple."""
|
|
return self[index*4:index*4+4]
|
|
|
|
def column(self, index):
|
|
"""Get a specific column as a tuple."""
|
|
return self[index::4]
|
|
|
|
def scale(self, x=1, y=1, z=1):
|
|
"""Get a scale Matrix on x, y, or z axis."""
|
|
temp = list(self)
|
|
temp[0] *= x
|
|
temp[5] *= y
|
|
temp[10] *= z
|
|
return Mat4(temp)
|
|
|
|
def translate(self, x=0, y=0, z=0):
|
|
"""Get a translate Matrix along x, y, and z axis."""
|
|
return Mat4(self) @ Mat4((1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1))
|
|
|
|
def rotate(self, angle=0, x=0, y=0, z=0):
|
|
"""Get a rotation Matrix on x, y, or z axis."""
|
|
assert all(abs(n) <= 1 for n in (x, y, z)), "x,y,z must be normalized (<=1)"
|
|
c = _math.cos(angle)
|
|
s = _math.sin(angle)
|
|
t = 1 - c
|
|
tempx, tempy, tempz = t * x, t * y, t * z
|
|
|
|
ra = c + tempx * x
|
|
rb = 0 + tempx * y + s * z
|
|
rc = 0 + tempx * z - s * y
|
|
re = 0 + tempy * x - s * z
|
|
rf = c + tempy * y
|
|
rg = 0 + tempy * z + s * x
|
|
ri = 0 + tempz * x + s * y
|
|
rj = 0 + tempz * y - s * x
|
|
rk = c + tempz * z
|
|
|
|
# ra, rb, rc, --
|
|
# re, rf, rg, --
|
|
# ri, rj, rk, --
|
|
# --, --, --, --
|
|
|
|
return Mat4(self) @ Mat4((ra, rb, rc, 0, re, rf, rg, 0, ri, rj, rk, 0, 0, 0, 0, 1))
|
|
|
|
def transpose(self):
|
|
"""Get a tranpose of this Matrix."""
|
|
return Mat4(self[0::4] + self[1::4] + self[2::4] + self[3::4])
|
|
|
|
def __add__(self, other):
|
|
assert len(other) == 16, "Can only add to other Mat4 types"
|
|
return Mat4(tuple(s + o for s, o in zip(self, other)))
|
|
|
|
def __sub__(self, other):
|
|
assert len(other) == 16, "Can only subtract from other Mat4 types"
|
|
return Mat4(tuple(s - o for s, o in zip(self, other)))
|
|
|
|
def __pos__(self):
|
|
return self
|
|
|
|
def __neg__(self):
|
|
return Mat4(tuple(-v for v in self))
|
|
|
|
def __invert__(self):
|
|
a = self[10] * self[15] - self[11] * self[14]
|
|
b = self[9] * self[15] - self[11] * self[13]
|
|
c = self[9] * self[14] - self[10] * self[13]
|
|
d = self[8] * self[15] - self[11] * self[12]
|
|
e = self[8] * self[14] - self[10] * self[12]
|
|
f = self[8] * self[13] - self[9] * self[12]
|
|
g = self[6] * self[15] - self[7] * self[14]
|
|
h = self[5] * self[15] - self[7] * self[13]
|
|
i = self[5] * self[14] - self[6] * self[13]
|
|
j = self[6] * self[11] - self[7] * self[10]
|
|
k = self[5] * self[11] - self[7] * self[9]
|
|
l = self[5] * self[10] - self[6] * self[9]
|
|
m = self[4] * self[15] - self[7] * self[12]
|
|
n = self[4] * self[14] - self[6] * self[12]
|
|
o = self[4] * self[11] - self[7] * self[8]
|
|
p = self[4] * self[10] - self[6] * self[8]
|
|
q = self[4] * self[13] - self[5] * self[12]
|
|
r = self[4] * self[9] - self[5] * self[8]
|
|
|
|
det = (self[0] * (self[5] * a - self[6] * b + self[7] * c)
|
|
- self[1] * (self[4] * a - self[6] * d + self[7] * e)
|
|
+ self[2] * (self[4] * b - self[5] * d + self[7] * f)
|
|
- self[3] * (self[4] * c - self[5] * e + self[6] * f))
|
|
|
|
if det == 0:
|
|
_warnings.warn("Unable to calculate inverse of singular Matrix")
|
|
return self
|
|
|
|
pdet = 1 / det
|
|
ndet = -pdet
|
|
|
|
return Mat4((pdet * (self[5] * a - self[6] * b + self[7] * c),
|
|
ndet * (self[1] * a - self[2] * b + self[3] * c),
|
|
pdet * (self[1] * g - self[2] * h + self[3] * i),
|
|
ndet * (self[1] * j - self[2] * k + self[3] * l),
|
|
ndet * (self[4] * a - self[6] * d + self[7] * e),
|
|
pdet * (self[0] * a - self[2] * d + self[3] * e),
|
|
ndet * (self[0] * g - self[2] * m + self[3] * n),
|
|
pdet * (self[0] * j - self[2] * o + self[3] * p),
|
|
pdet * (self[4] * b - self[5] * d + self[7] * f),
|
|
ndet * (self[0] * b - self[1] * d + self[3] * f),
|
|
pdet * (self[0] * h - self[1] * m + self[3] * q),
|
|
ndet * (self[0] * k - self[1] * o + self[3] * r),
|
|
ndet * (self[4] * c - self[5] * e + self[6] * f),
|
|
pdet * (self[0] * c - self[1] * e + self[2] * f),
|
|
ndet * (self[0] * i - self[1] * n + self[2] * q),
|
|
pdet * (self[0] * l - self[1] * p + self[2] * r)))
|
|
|
|
def __round__(self, n=None):
|
|
return Mat4(tuple(round(v, n) for v in self))
|
|
|
|
def __mul__(self, other):
|
|
raise NotImplementedError("Please use the @ operator for Matrix multiplication.")
|
|
|
|
def __matmul__(self, other):
|
|
assert len(other) == 16, "Can only multiply with other Mat4 types"
|
|
# Rows:
|
|
r0 = self[0:4]
|
|
r1 = self[4:8]
|
|
r2 = self[8:12]
|
|
r3 = self[12:16]
|
|
# Columns:
|
|
c0 = other[0::4]
|
|
c1 = other[1::4]
|
|
c2 = other[2::4]
|
|
c3 = other[3::4]
|
|
|
|
# Multiply and sum rows * colums:
|
|
return Mat4((sum(map(_operator.mul, r0, c0)),
|
|
sum(map(_operator.mul, r0, c1)),
|
|
sum(map(_operator.mul, r0, c2)),
|
|
sum(map(_operator.mul, r0, c3)),
|
|
|
|
sum(map(_operator.mul, r1, c0)),
|
|
sum(map(_operator.mul, r1, c1)),
|
|
sum(map(_operator.mul, r1, c2)),
|
|
sum(map(_operator.mul, r1, c3)),
|
|
|
|
sum(map(_operator.mul, r2, c0)),
|
|
sum(map(_operator.mul, r2, c1)),
|
|
sum(map(_operator.mul, r2, c2)),
|
|
sum(map(_operator.mul, r2, c3)),
|
|
|
|
sum(map(_operator.mul, r3, c0)),
|
|
sum(map(_operator.mul, r3, c1)),
|
|
sum(map(_operator.mul, r3, c2)),
|
|
sum(map(_operator.mul, r3, c3))))
|
|
|
|
def __repr__(self):
|
|
return f"{self.__class__.__name__}{self[0:4]}\n {self[4:8]}\n {self[8:12]}\n {self[12:16]}"
|