Source code for opengl.matrix

#
##
##  This file is part of pyFormex 2.0  (Mon Sep 14 12:29:05 CEST 2020)
##  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

This OpenGL framework is intended to replace (in due time)
the current OpenGL framework in pyFormex.

(C) 2013 Benedict Verhegghe and the pyFormex project.

"""

import numpy as np
import pyformex.arraytools as at


#TODO: Remove the dependence on np.matrix, which is deprecated


[docs]class Vector4(np.matrix): """One or more homogeneous coordinates """ def __new__(clas, data=None): """Create a new Vector4 instance""" data = np.asanyarray(data) if data.ndim < 1 or data.ndim > 2: raise ValueError("Expected 1 or 2-dimensinal data") 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 fro last axis") ar = data.view(clas) return ar
[docs]class Matrix4(np.matrix): """A 4x4 transformation matrix for homogeneous coordinates. The matrix is to be used with post-multiplication on row vectors (i.e. OpenGL convention). - `data`: if specified, should be a (4,4) float array or compatible. Else a 4x4 identity matrix is created. Example: >>> 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.]) >>> print(a) [[ 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) else: data = at.checkArray(data, (4, 4), 'f') 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. """ self._gl = getattr(obj, '_gl', None)
[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.matrix(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 += 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 = np.matrix(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 Vector4(x) * self.inverse()
[docs] def transinv(self): """Return the transpose of the inverse.""" return self.inverse().transpose()
# End