#
##
## 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 transinv(self):
"""Return the transpose of the inverse."""
return self.inverse().transpose()
# End