Source code for opengl.matrix

#
##
##  This file is part of pyFormex 2.3  (Mon Feb 22 15:38:03 CET 2021)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: http://pyformex.org
##  Project page:  http://savannah.nongnu.org/projects/pyformex/
##  Copyright 2004-2020 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
"""opengl/matrix.py

Python OpenGL framework for pyFormex

"""

import numpy as np
import pyformex.arraytools as at


DTYPE = at.Float


#TODO: replace with Coords4

[docs]class Vector4(np.ndarray): """A set of homogeneous coordinates The input can be 3D or 4D coordinates. The results is always a 2-dimensional array: a single point has nrows=1. Examples -------- >>> Vector4([1, 2, 3]) Vector4([[ 1., 2., 3., 1.]]) >>> Vector4([[1, 2, 3],[4, 5, 6]]) Vector4([[ 1., 2., 3., 1.], [ 4., 5., 6., 1.]]) >>> Vector4([[1, 2, 3, 0], [4., 5., 6., 1.]]) Vector4([[ 1., 2., 3., 0.], [ 4., 5., 6., 1.]]) >>> Vector4([[1,2,3]]).dtype dtype('float32') """ def __new__(clas, data=None): """Create a new Vector4 instance""" data = at.checkArray(data, kind='f', allow='i').astype(DTYPE) if data.ndim == 1: data = data.reshape(-1,data.shape[-1]) elif data.ndim > 2: raise ValueError("Expected 1-d or 2-d input array") if data.shape[-1] == 4: pass elif data.shape[-1] == 3: data = at.growAxis(data, 1, fill=1) else: raise ValueError("Expected length 3 or 4 for last axis") ar = data.view(clas) return ar
[docs]class Matrix4(np.ndarray): """A 4x4 transformation matrix for homogeneous coordinates. The matrix is to be used with post-multiplication on row vectors (i.e. OpenGL convention). Parameters ---------- data: array_like (4,4), optional If specified, should be a (4,4) float array or compatible. Else a 4x4 identity matrix is created. Examples -------- >>> I = Matrix4() >>> print(I) [[ 1. 0. 0. 0.] [ 0. 1. 0. 0.] [ 0. 0. 1. 0.] [ 0. 0. 0. 1.]] We can first scale and then rotate, or first rotate and then scale (with another scaling factor): >>> a = I.scale([4.,4.,4.]).rotate(45.,[0.,0.,1.]) >>> b = I.rotate(45.,[0.,0.,1.]).scale([2.,2.,2.]) >>> a Matrix4([[ 0., 4., 0., 0.], [-4., 0., 0., 0.], [ 0., 0., 8., 0.], [ 0., 0., 0., 1.]]) >>> (a==b).all() True """ def __new__(clas, data=None): """Create a new Matrix instance""" if data is None: data = np.eye(4, 4, dtype=DTYPE) else: data = at.checkArray(data, (4, 4), 'f').astype(DTYPE) ar = data.view(clas) ar._gl = None return ar def __array_finalize__(self, obj): """Finalize the new Matrix object. When a class is derived from numpy.ndarray and the constructor (the :meth:`__new__` method) defines new attributes, these atttributes need to be reset in this method. """ if obj is None: return self._gl = getattr(obj, '_gl', None) def __array_wrap__(self, out_arr, context=None): res = super().__array_wrap__(out_arr, context) if type(res) == Matrix4 and ( res.dtype != DTYPE or res.shape != (4,4) ): res = res.view(np.ndarray) return res
[docs] def gl(self): """Get the transformation matrix as a 'ready-to-use'-gl version. Returns the (4,4) Matrix as a rowwise flattened array of type float32. Example: >>> Matrix4().gl() Matrix4([ 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.]) """ if self._gl is None: self._gl = self.flatten().astype(np.float32) return self._gl
@property def rot(self): """Return the (3,3) rotation matrix""" return self[:3, :3] @rot.setter def rot(self, value): """Set the rotation matrix to (3,3) value""" self[:3, :3] = value self._gl = None @property def trl(self): """Return the (3,) translation vector""" return self[3, :3] @trl.setter def trl(self, value): """Set the translation vector to (3,) value""" self[3, :3] = value self._gl = None
[docs] def identity(self): """Reset the matrix to a 4x4 identity matrix.""" self = np.eye(4, 4) self._gl = None
[docs] def translate(self, vector): """Translate a 4x4 matrix by a (3,) vector. - `vector`: (3,) float array: the translation vector Changes the Matrix in place and also returns the result Example: >>> Matrix4().translate([1.,2.,3.]) Matrix4([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 1., 2., 3., 1.]]) """ vector = at.checkArray(vector, (3,), 'f') self.trl += np.dot(vector, self.rot) return self
[docs] def rotate(self, angle, axis=None): """Rotate a Matrix4. The rotation can be specified by - an angle and axis, - a 3x3 rotation matrix, - a 4x4 trtransformation matrix (Matrix4). Parameters: - `angle`: float: the rotation angle. A 3x3 or 4x4 matrix may be give instead, to directly specify the roation matrix. - `axis`: int or (3,) float: the axis to rotate around Changes the Matrix in place and also returns the result. Example: >>> Matrix4().rotate(90.,[0.,1.,0.]) Matrix4([[ 0., 0., -1., 0.], [ 0., 1., 0., 0.], [ 1., 0., 0., 0.], [ 0., 0., 0., 1.]]) """ ## !! TRANSPOSE!! ## x^2(1-c)+c xy(1-c)-zs xz(1-c)+ys 0 ## yx(1-c)+zs y^2(1-c)+c yz(1-c)-xs 0 ## xz(1-c)-ys yz(1-c)+xs z^2(1-c)+c 0 ## 0 0 0 1 try: rot = at.checkArray(angle, (4, 4), 'f')[:3, :3] except Exception: try: rot = at.checkArray(angle, (3, 3), 'f') except Exception: angle = at.checkFloat(angle) rot = at.rotationMatrix(angle, axis) self.rot = rot @ self.rot return self
[docs] def scale(self, vector): """Scale a 4x4 matrix by a (3,) vector. - `vector`: (3,) float array: the scaling vector Changes the Matrix in place and also returns the result Example: >>> Matrix4().scale([1.,2.,3.]) Matrix4([[ 1., 0., 0., 0.], [ 0., 2., 0., 0.], [ 0., 0., 3., 0.], [ 0., 0., 0., 1.]]) """ vector = at.checkArray(vector, (3,), 'f') for i in range(3): self[i, i] *= vector[i] self._gl = None return self
# Do we need these?
[docs] def swapRows(self, row1, row2): """Swap two rows. - `row1`, `row2`: index of the rows to swap """ temp = np.copy(self[row1]) self[row1] = self[row2] self[row2] = temp self._gl = None
[docs] def swapCols(self, col1, col2): """Swap two columns. - `col1`, `col2`: index of the columns to swap """ temp = np.copy(self[:, col1]) self[:, col1] = self[:, col2] self[:, col2] = temp self._gl = None
[docs] def inverse(self): """Return the inverse matrix""" return np.linalg.inv(self)
[docs] def transform(self, x): """Transform a vertex using this matrix. - `x`: a (3,) or (4,) vector. If the vector has length 4, it holds homogeneous coordinates, and the result is the dot product of the vector with the Matrix: x * M. If the vector has length 3, the 4th homogeneous coordinate is assumed to be 1, and the product is computed in an optimized way. """ try: x = at.checkArray(x, (3,), 'f') return np.dot(x, self[:3, :3]) + self[3, :3] except Exception: x = at.checkArray(x, (4,), 'f') return np.dot(x, self)
[docs] def invtransform(self, x): """Transform a vertex with the inverse of this matrix. - `x`: a (3,) or (4,) vector. """ return np.dot(Vector4(x), self.inverse())
[docs] def transinv(self): """Return the transpose of the inverse.""" return self.inverse().transpose()
# End