diff options
Diffstat (limited to 'silx/gui/plot3d/scene/transform.py')
-rw-r--r-- | silx/gui/plot3d/scene/transform.py | 1008 |
1 files changed, 0 insertions, 1008 deletions
diff --git a/silx/gui/plot3d/scene/transform.py b/silx/gui/plot3d/scene/transform.py deleted file mode 100644 index 1b82397..0000000 --- a/silx/gui/plot3d/scene/transform.py +++ /dev/null @@ -1,1008 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2015-2018 European Synchrotron Radiation Facility -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# ###########################################################################*/ -"""This module provides 4x4 matrix operation and classes to handle them.""" - -from __future__ import absolute_import, division, unicode_literals - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "25/07/2016" - - -import itertools -import numpy - -from . import event - - -# Functions ################################################################### - -# Projections - -def mat4LookAtDir(position, direction, up): - """Creates matrix to look in direction from position. - - :param position: Array-like 3 coordinates of the point of view position. - :param direction: Array-like 3 coordinates of the sight direction vector. - :param up: Array-like 3 coordinates of the upward direction - in the image plane. - :returns: Corresponding matrix. - :rtype: numpy.ndarray of shape (4, 4) - """ - assert len(position) == 3 - assert len(direction) == 3 - assert len(up) == 3 - - direction = numpy.array(direction, copy=True, dtype=numpy.float32) - dirnorm = numpy.linalg.norm(direction) - assert dirnorm != 0. - direction /= dirnorm - - side = numpy.cross(direction, - numpy.array(up, copy=False, dtype=numpy.float32)) - sidenorm = numpy.linalg.norm(side) - assert sidenorm != 0. - up = numpy.cross(side / sidenorm, direction) - upnorm = numpy.linalg.norm(up) - assert upnorm != 0. - up /= upnorm - - matrix = numpy.identity(4, dtype=numpy.float32) - matrix[0, :3] = side - matrix[1, :3] = up - matrix[2, :3] = -direction - return numpy.dot(matrix, - mat4Translate(-position[0], -position[1], -position[2])) - - -def mat4LookAt(position, center, up): - """Creates matrix to look at center from position. - - See gluLookAt. - - :param position: Array-like 3 coordinates of the point of view position. - :param center: Array-like 3 coordinates of the center of the scene. - :param up: Array-like 3 coordinates of the upward direction - in the image plane. - :returns: Corresponding matrix. - :rtype: numpy.ndarray of shape (4, 4) - """ - position = numpy.array(position, copy=False, dtype=numpy.float32) - center = numpy.array(center, copy=False, dtype=numpy.float32) - direction = center - position - return mat4LookAtDir(position, direction, up) - - -def mat4Frustum(left, right, bottom, top, near, far): - """Creates a frustum projection matrix. - - See glFrustum. - """ - return numpy.array(( - (2.*near / (right-left), 0., (right+left) / (right-left), 0.), - (0., 2.*near / (top-bottom), (top+bottom) / (top-bottom), 0.), - (0., 0., -(far+near) / (far-near), -2.*far*near / (far-near)), - (0., 0., -1., 0.)), dtype=numpy.float32) - - -def mat4Perspective(fovy, width, height, near, far): - """Creates a perspective projection matrix. - - Similar to gluPerspective. - - :param float fovy: Field of view angle in degrees in the y direction. - :param float width: Width of the viewport. - :param float height: Height of the viewport. - :param float near: Distance to the near plane (strictly positive). - :param float far: Distance to the far plane (strictly positive). - :return: Corresponding matrix. - :rtype: numpy.ndarray of shape (4, 4) - """ - assert fovy != 0 - assert height != 0 - assert width != 0 - assert near > 0. - assert far > near - aspectratio = width / height - f = 1. / numpy.tan(numpy.radians(fovy) / 2.) - return numpy.array(( - (f / aspectratio, 0., 0., 0.), - (0., f, 0., 0.), - (0., 0., (far + near) / (near - far), 2. * far * near / (near - far)), - (0., 0., -1., 0.)), dtype=numpy.float32) - - -def mat4Orthographic(left, right, bottom, top, near, far): - """Creates an orthographic (i.e., parallel) projection matrix. - - See glOrtho. - """ - return numpy.array(( - (2. / (right - left), 0., 0., - (right + left) / (right - left)), - (0., 2. / (top - bottom), 0., - (top + bottom) / (top - bottom)), - (0., 0., -2. / (far - near), - (far + near) / (far - near)), - (0., 0., 0., 1.)), dtype=numpy.float32) - - -# Affine - -def mat4Translate(tx, ty, tz): - """4x4 translation matrix.""" - return numpy.array(( - (1., 0., 0., tx), - (0., 1., 0., ty), - (0., 0., 1., tz), - (0., 0., 0., 1.)), dtype=numpy.float32) - - -def mat4Scale(sx, sy, sz): - """4x4 scale matrix.""" - return numpy.array(( - (sx, 0., 0., 0.), - (0., sy, 0., 0.), - (0., 0., sz, 0.), - (0., 0., 0., 1.)), dtype=numpy.float32) - - -def mat4RotateFromAngleAxis(angle, x=0., y=0., z=1.): - """4x4 rotation matrix from angle and axis. - - :param float angle: The rotation angle in radians. - :param float x: The rotation vector x coordinate. - :param float y: The rotation vector y coordinate. - :param float z: The rotation vector z coordinate. - """ - ca = numpy.cos(angle) - sa = numpy.sin(angle) - return numpy.array(( - ((1.-ca) * x*x + ca, (1.-ca) * x*y - sa*z, (1.-ca) * x*z + sa*y, 0.), - ((1.-ca) * x*y + sa*z, (1.-ca) * y*y + ca, (1.-ca) * y*z - sa*x, 0.), - ((1.-ca) * x*z - sa*y, (1.-ca) * y*z + sa*x, (1.-ca) * z*z + ca, 0.), - (0., 0., 0., 1.)), dtype=numpy.float32) - - -def mat4RotateFromQuaternion(quaternion): - """4x4 rotation matrix from quaternion. - - :param quaternion: Array-like unit quaternion stored as (x, y, z, w) - """ - quaternion = numpy.array(quaternion, copy=True) - quaternion /= numpy.linalg.norm(quaternion) - - qx, qy, qz, qw = quaternion - return numpy.array(( - (1. - 2.*(qy**2 + qz**2), 2.*(qx*qy - qw*qz), 2.*(qx*qz + qw*qy), 0.), - (2.*(qx*qy + qw*qz), 1. - 2.*(qx**2 + qz**2), 2.*(qy*qz - qw*qx), 0.), - (2.*(qx*qz - qw*qy), 2.*(qy*qz + qw*qx), 1. - 2.*(qx**2 + qy**2), 0.), - (0., 0., 0., 1.)), dtype=numpy.float32) - - -def mat4Shear(axis, sx=0., sy=0., sz=0.): - """4x4 shear matrix: Skew two axes relative to a third fixed one. - - shearFactor = tan(shearAngle) - - :param str axis: The axis to keep constant and shear against. - In 'x', 'y', 'z'. - :param float sx: The shear factor for the X axis relative to axis. - :param float sy: The shear factor for the Y axis relative to axis. - :param float sz: The shear factor for the Z axis relative to axis. - """ - assert axis in ('x', 'y', 'z') - - matrix = numpy.identity(4, dtype=numpy.float32) - - # Make the shear column - index = 'xyz'.find(axis) - shearcolumn = numpy.array((sx, sy, sz, 0.), dtype=numpy.float32) - shearcolumn[index] = 1. - matrix[:, index] = shearcolumn - return matrix - - -# Transforms ################################################################## - -class Transform(event.Notifier): - - def __init__(self, static=False): - """Base class for (row-major) 4x4 matrix transforms. - - :param bool static: False (default) to reset cache when changed, - True for static matrices. - """ - super(Transform, self).__init__() - self._matrix = None - self._inverse = None - if not static: - self.addListener(self._changed) # Listening self for changes - - def __repr__(self): - return '%s(%s)' % (self.__class__.__init__, - repr(self.getMatrix(copy=False))) - - def inverse(self): - """Return the Transform of the inverse. - - The returned Transform is static, it is not updated when this - Transform is modified. - - :return: A Transform which is the inverse of this Transform. - """ - return Inverse(self) - - # Matrix - - def _makeMatrix(self): - """Override to build matrix""" - return numpy.identity(4, dtype=numpy.float32) - - def _makeInverse(self): - """Override to build inverse matrix.""" - return numpy.linalg.inv(self.getMatrix(copy=False)) - - def getMatrix(self, copy=True): - """The 4x4 matrix of this transform. - - :param bool copy: True (the default) to get a copy of the matrix, - False to get the internal matrix, do not modify! - :return: 4x4 matrix of this transform. - """ - if self._matrix is None: - self._matrix = self._makeMatrix() - if copy: - return self._matrix.copy() - else: - return self._matrix - - matrix = property(getMatrix, doc="The 4x4 matrix of this transform.") - - def getInverseMatrix(self, copy=False): - """The 4x4 matrix of the inverse of this transform. - - :param bool copy: True (the default) to get a copy of the matrix, - False to get the internal matrix, do not modify! - :return: 4x4 matrix of the inverse of this transform. - """ - if self._inverse is None: - self._inverse = self._makeInverse() - if copy: - return self._inverse.copy() - else: - return self._inverse - - inverseMatrix = property( - getInverseMatrix, - doc="The 4x4 matrix of the inverse of this transform.") - - # Listener - - def _changed(self, source): - """Default self listener reseting matrix cache.""" - self._matrix = None - self._inverse = None - - # Multiplication with vectors - - def transformPoints(self, points, direct=True, perspectiveDivide=False): - """Apply the transform to an array of points. - - :param points: 2D array of N vectors of 3 or 4 coordinates - :param bool direct: Whether to apply the direct (True, the default) - or inverse (False) transform. - :param bool perspectiveDivide: Whether to apply the perspective divide - (True) or not (False, the default). - :return: The transformed points. - :rtype: numpy.ndarray of same shape as points. - """ - if direct: - matrix = self.getMatrix(copy=False) - else: - matrix = self.getInverseMatrix(copy=False) - - points = numpy.array(points, copy=False) - assert points.ndim == 2 - - points = numpy.transpose(points) - - dimension = points.shape[0] - assert dimension in (3, 4) - - if dimension == 3: # Add 4th coordinate - points = numpy.append( - points, - numpy.ones((1, points.shape[1]), dtype=points.dtype), - axis=0) - - result = numpy.transpose(numpy.dot(matrix, points)) - - if perspectiveDivide: - mask = result[:, 3] != 0. - result[mask] /= result[mask, 3][:, numpy.newaxis] - - return result[:, :3] if dimension == 3 else result - - @staticmethod - def _prepareVector(vector, w): - """Add 4th coordinate (w) to vector if missing.""" - assert len(vector) in (3, 4) - vector = numpy.array(vector, copy=False, dtype=numpy.float32) - if len(vector) == 3: - vector = numpy.append(vector, w) - return vector - - def transformPoint(self, point, direct=True, perspectiveDivide=False): - """Apply the transform to a point. - - :param point: Array-like vector of 3 or 4 coordinates. - :param bool direct: Whether to apply the direct (True, the default) - or inverse (False) transform. - :param bool perspectiveDivide: Whether to apply the perspective divide - (True) or not (False, the default). - :return: The transformed point. - :rtype: numpy.ndarray of same length as point. - """ - if direct: - matrix = self.getMatrix(copy=False) - else: - matrix = self.getInverseMatrix(copy=False) - result = numpy.dot(matrix, self._prepareVector(point, 1.)) - - if perspectiveDivide and result[3] != 0.: - result /= result[3] - - if len(point) == 3: - return result[:3] - else: - return result - - def transformDir(self, direction, direct=True): - """Apply the transform to a direction. - - :param direction: Array-like vector of 3 coordinates. - :param bool direct: Whether to apply the direct (True, the default) - or inverse (False) transform. - :return: The transformed direction. - :rtype: numpy.ndarray of length 3. - """ - if direct: - matrix = self.getMatrix(copy=False) - else: - matrix = self.getInverseMatrix(copy=False) - return numpy.dot(matrix[:3, :3], direction[:3]) - - def transformNormal(self, normal, direct=True): - """Apply the transform to a normal: R = (M-1)t * V. - - :param normal: Array-like vector of 3 coordinates. - :param bool direct: Whether to apply the direct (True, the default) - or inverse (False) transform. - :return: The transformed normal. - :rtype: numpy.ndarray of length 3. - """ - if direct: - matrix = self.getInverseMatrix(copy=False).T - else: - matrix = self.getMatrix(copy=False).T - return numpy.dot(matrix[:3, :3], normal[:3]) - - _CUBE_CORNERS = numpy.array(list(itertools.product((0., 1.), repeat=3)), - dtype=numpy.float32) - """Unit cube corners used by :meth:`transformBounds`""" - - def transformBounds(self, bounds, direct=True): - """Apply the transform to an axes-aligned rectangular box. - - :param bounds: Min and max coords of the box for each axes. - :type bounds: 2x3 numpy.ndarray - :param bool direct: Whether to apply the direct (True, the default) - or inverse (False) transform. - :return: Axes-aligned rectangular box including the transformed box. - :rtype: 2x3 numpy.ndarray of float32 - """ - corners = numpy.ones((8, 4), dtype=numpy.float32) - corners[:, :3] = bounds[0] + \ - self._CUBE_CORNERS * (bounds[1] - bounds[0]) - - if direct: - matrix = self.getMatrix(copy=False) - else: - matrix = self.getInverseMatrix(copy=False) - - # Transform corners - cornerstransposed = numpy.dot(matrix, corners.T) - cornerstransposed = cornerstransposed / cornerstransposed[3] - - # Get min/max for each axis - transformedbounds = numpy.empty((2, 3), dtype=numpy.float32) - transformedbounds[0] = cornerstransposed.T[:, :3].min(axis=0) - transformedbounds[1] = cornerstransposed.T[:, :3].max(axis=0) - - return transformedbounds - - -class Inverse(Transform): - """Transform which is the inverse of another one. - - Static: It never gets updated. - """ - - def __init__(self, transform): - """Initializer. - - :param Transform transform: The transform to invert. - """ - - super(Inverse, self).__init__(static=True) - self._matrix = transform.getInverseMatrix(copy=True) - self._inverse = transform.getMatrix(copy=True) - - -class TransformList(Transform, event.HookList): - """List of transforms.""" - - def __init__(self, iterable=()): - Transform.__init__(self) - event.HookList.__init__(self, iterable) - - def _listWillChangeHook(self, methodName, *args, **kwargs): - for item in self: - item.removeListener(self._transformChanged) - - def _listWasChangedHook(self, methodName, *args, **kwargs): - for item in self: - item.addListener(self._transformChanged) - self.notify() - - def _transformChanged(self, source): - """Listen to transform changes of the list and its items.""" - if source is not self: # Avoid infinite recursion - self.notify() - - def _makeMatrix(self): - matrix = numpy.identity(4, dtype=numpy.float32) - for transform in self: - matrix = numpy.dot(matrix, transform.getMatrix(copy=False)) - return matrix - - -class StaticTransformList(Transform): - """Transform that is a snapshot of a list of Transforms - - It does not keep reference to the list of Transforms. - - :param iterable: Iterable of Transform used for initialization - """ - - def __init__(self, iterable=()): - super(StaticTransformList, self).__init__(static=True) - matrix = numpy.identity(4, dtype=numpy.float32) - for transform in iterable: - matrix = numpy.dot(matrix, transform.getMatrix(copy=False)) - self._matrix = matrix # Init matrix once - - -# Affine ###################################################################### - -class Matrix(Transform): - - def __init__(self, matrix=None): - """4x4 Matrix. - - :param matrix: 4x4 array-like matrix or None for identity matrix. - """ - super(Matrix, self).__init__(static=True) - self.setMatrix(matrix) - - def setMatrix(self, matrix=None): - """Update the 4x4 Matrix. - - :param matrix: 4x4 array-like matrix or None for identity matrix. - """ - if matrix is None: - self._matrix = numpy.identity(4, dtype=numpy.float32) - else: - matrix = numpy.array(matrix, copy=True, dtype=numpy.float32) - assert matrix.shape == (4, 4) - self._matrix = matrix - # Reset cached inverse as Transform is declared static - self._inverse = None - self.notify() - - # Redefined here to add a setter - matrix = property(Transform.getMatrix, setMatrix, - doc="The 4x4 matrix of this transform.") - - -class Translate(Transform): - """4x4 translation matrix.""" - - def __init__(self, tx=0., ty=0., tz=0.): - super(Translate, self).__init__() - self._tx, self._ty, self._tz = 0., 0., 0. - self.setTranslate(tx, ty, tz) - - def _makeMatrix(self): - return mat4Translate(self.tx, self.ty, self.tz) - - def _makeInverse(self): - return mat4Translate(-self.tx, -self.ty, -self.tz) - - @property - def tx(self): - return self._tx - - @tx.setter - def tx(self, tx): - self.setTranslate(tx=tx) - - @property - def ty(self): - return self._ty - - @ty.setter - def ty(self, ty): - self.setTranslate(ty=ty) - - @property - def tz(self): - return self._tz - - @tz.setter - def tz(self, tz): - self.setTranslate(tz=tz) - - @property - def translation(self): - return numpy.array((self.tx, self.ty, self.tz), dtype=numpy.float32) - - @translation.setter - def translation(self, translations): - tx, ty, tz = translations - self.setTranslate(tx, ty, tz) - - def setTranslate(self, tx=None, ty=None, tz=None): - if tx is not None: - self._tx = tx - if ty is not None: - self._ty = ty - if tz is not None: - self._tz = tz - self.notify() - - -class Scale(Transform): - """4x4 scale matrix.""" - - def __init__(self, sx=1., sy=1., sz=1.): - super(Scale, self).__init__() - self._sx, self._sy, self._sz = 0., 0., 0. - self.setScale(sx, sy, sz) - - def _makeMatrix(self): - return mat4Scale(self.sx, self.sy, self.sz) - - def _makeInverse(self): - return mat4Scale(1. / self.sx, 1. / self.sy, 1. / self.sz) - - @property - def sx(self): - return self._sx - - @sx.setter - def sx(self, sx): - self.setScale(sx=sx) - - @property - def sy(self): - return self._sy - - @sy.setter - def sy(self, sy): - self.setScale(sy=sy) - - @property - def sz(self): - return self._sz - - @sz.setter - def sz(self, sz): - self.setScale(sz=sz) - - @property - def scale(self): - return numpy.array((self._sx, self._sy, self._sz), dtype=numpy.float32) - - @scale.setter - def scale(self, scales): - sx, sy, sz = scales - self.setScale(sx, sy, sz) - - def setScale(self, sx=None, sy=None, sz=None): - if sx is not None: - assert sx != 0. - self._sx = sx - if sy is not None: - assert sy != 0. - self._sy = sy - if sz is not None: - assert sz != 0. - self._sz = sz - self.notify() - - -class Rotate(Transform): - - def __init__(self, angle=0., ax=0., ay=0., az=1.): - """4x4 rotation matrix. - - :param float angle: The rotation angle in degrees. - :param float ax: The x coordinate of the rotation axis. - :param float ay: The y coordinate of the rotation axis. - :param float az: The z coordinate of the rotation axis. - """ - super(Rotate, self).__init__() - self._angle = 0. - self._axis = None - self.setAngleAxis(angle, (ax, ay, az)) - - @property - def angle(self): - """The rotation angle in degrees.""" - return self._angle - - @angle.setter - def angle(self, angle): - self.setAngleAxis(angle=angle) - - @property - def axis(self): - """The normalized rotation axis as a numpy.ndarray.""" - return self._axis.copy() - - @axis.setter - def axis(self, axis): - self.setAngleAxis(axis=axis) - - def setAngleAxis(self, angle=None, axis=None): - """Update the angle and/or axis of the rotation. - - :param float angle: The rotation angle in degrees. - :param axis: Array-like axis vector (3 coordinates). - """ - if angle is not None: - self._angle = angle - if axis is not None: - assert len(axis) == 3 - axis = numpy.array(axis, copy=True, dtype=numpy.float32) - assert axis.size == 3 - norm = numpy.linalg.norm(axis) - if norm == 0.: # No axis, set rotation angle to 0. - self._angle = 0. - self._axis = numpy.array((0., 0., 1.), dtype=numpy.float32) - else: - self._axis = axis / norm - - if angle is not None or axis is not None: - self.notify() - - @property - def quaternion(self): - """Rotation unit quaternion as (x, y, z, w). - - Where: ||(x, y, z)|| = sin(angle/2), w = cos(angle/2). - """ - if numpy.linalg.norm(self._axis) == 0.: - return numpy.array((0., 0., 0., 1.), dtype=numpy.float32) - - else: - quaternion = numpy.empty((4,), dtype=numpy.float32) - halfangle = 0.5 * numpy.radians(self.angle) - quaternion[0:3] = numpy.sin(halfangle) * self._axis - quaternion[3] = numpy.cos(halfangle) - return quaternion - - @quaternion.setter - def quaternion(self, quaternion): - assert len(quaternion) == 4 - - # Normalize quaternion - quaternion = numpy.array(quaternion, copy=True) - quaternion /= numpy.linalg.norm(quaternion) - - # Get angle - sinhalfangle = numpy.linalg.norm(quaternion[0:3]) - coshalfangle = quaternion[3] - angle = 2. * numpy.arctan2(sinhalfangle, coshalfangle) - - # Axis will be normalized in setAngleAxis - self.setAngleAxis(numpy.degrees(angle), quaternion[0:3]) - - def _makeMatrix(self): - angle = numpy.radians(self.angle, dtype=numpy.float32) - return mat4RotateFromAngleAxis(angle, *self.axis) - - def _makeInverse(self): - return numpy.array(self.getMatrix(copy=False).transpose(), - copy=True, order='C', - dtype=numpy.float32) - - -class Shear(Transform): - - def __init__(self, axis, sx=0., sy=0., sz=0.): - """4x4 shear/skew matrix of 2 axes relative to the third one. - - :param str axis: The axis to keep fixed, in 'x', 'y', 'z' - :param float sx: The shear factor for the x axis. - :param float sy: The shear factor for the y axis. - :param float sz: The shear factor for the z axis. - """ - assert axis in ('x', 'y', 'z') - super(Shear, self).__init__() - self._axis = axis - self._factors = sx, sy, sz - - @property - def axis(self): - """The axis against which other axes are skewed.""" - return self._axis - - @property - def factors(self): - """The shear factors: shearFactor = tan(shearAngle)""" - return self._factors - - def _makeMatrix(self): - return mat4Shear(self.axis, *self.factors) - - def _makeInverse(self): - sx, sy, sz = self.factors - return mat4Shear(self.axis, -sx, -sy, -sz) - - -# Projection ################################################################## - -class _Projection(Transform): - """Base class for projection matrix. - - Handles near and far clipping plane values. - Subclasses must implement :meth:`_makeMatrix`. - - :param float near: Distance to the near plane. - :param float far: Distance to the far plane. - :param bool checkDepthExtent: Toggle checks near > 0 and far > near. - :param size: - Viewport's size used to compute the aspect ratio (width, height). - :type size: 2-tuple of float - """ - - def __init__(self, near, far, checkDepthExtent=False, size=(1., 1.)): - super(_Projection, self).__init__() - self._checkDepthExtent = checkDepthExtent - self._depthExtent = 1, 10 - self.setDepthExtent(near, far) # set _depthExtent - self._size = 1., 1. - self.size = size # set _size - - def setDepthExtent(self, near=None, far=None): - """Set the extent of the visible area along the viewing direction. - - :param float near: The near clipping plane Z coord. - :param float far: The far clipping plane Z coord. - """ - near = float(near) if near is not None else self._depthExtent[0] - far = float(far) if far is not None else self._depthExtent[1] - - if self._checkDepthExtent: - assert near > 0. - assert far > near - - self._depthExtent = near, far - self.notify() - - @property - def near(self): - """Distance to the near plane.""" - return self._depthExtent[0] - - @near.setter - def near(self, near): - if near != self.near: - self.setDepthExtent(near=near) - - @property - def far(self): - """Distance to the far plane.""" - return self._depthExtent[1] - - @far.setter - def far(self, far): - if far != self.far: - self.setDepthExtent(far=far) - - @property - def size(self): - """Viewport size as a 2-tuple of float (width, height).""" - return self._size - - @size.setter - def size(self, size): - assert len(size) == 2 - self._size = tuple(size) - self.notify() - - -class Orthographic(_Projection): - """Orthographic (i.e., parallel) projection which keeps aspect ratio. - - Clipping planes are adjusted to match the aspect ratio of - the :attr:`size` attribute. - - The left, right, bottom and top parameters defines the area which must - always remain visible. - Effective clipping planes are adjusted to keep the aspect ratio. - - :param float left: Coord of the left clipping plane. - :param float right: Coord of the right clipping plane. - :param float bottom: Coord of the bottom clipping plane. - :param float top: Coord of the top clipping plane. - :param float near: Distance to the near plane. - :param float far: Distance to the far plane. - :param size: - Viewport's size used to compute the aspect ratio (width, height). - :type size: 2-tuple of float - """ - - def __init__(self, left=0., right=1., bottom=1., top=0., near=-1., far=1., - size=(1., 1.)): - self._left, self._right = left, right - self._bottom, self._top = bottom, top - super(Orthographic, self).__init__(near, far, checkDepthExtent=False, - size=size) - # _update called when setting size - - def _makeMatrix(self): - return mat4Orthographic( - self.left, self.right, self.bottom, self.top, self.near, self.far) - - def _update(self, left, right, bottom, top): - width, height = self.size - aspect = width / height - - orthoaspect = abs(left - right) / abs(bottom - top) - - if orthoaspect >= aspect: # Keep width, enlarge height - newheight = \ - numpy.sign(top - bottom) * abs(left - right) / aspect - bottom = 0.5 * (bottom + top) - 0.5 * newheight - top = bottom + newheight - - else: # Keep height, enlarge width - newwidth = \ - numpy.sign(right - left) * abs(bottom - top) * aspect - left = 0.5 * (left + right) - 0.5 * newwidth - right = left + newwidth - - # Store values - self._left, self._right = left, right - self._bottom, self._top = bottom, top - - def setClipping(self, left=None, right=None, bottom=None, top=None): - """Set the clipping planes of the projection. - - Parameters are adjusted to keep aspect ratio. - If a clipping plane coord is not provided, it uses its current value - - :param float left: Coord of the left clipping plane. - :param float right: Coord of the right clipping plane. - :param float bottom: Coord of the bottom clipping plane. - :param float top: Coord of the top clipping plane. - """ - left = float(left) if left is not None else self.left - right = float(right) if right is not None else self.right - bottom = float(bottom) if bottom is not None else self.bottom - top = float(top) if top is not None else self.top - - self._update(left, right, bottom, top) - self.notify() - - left = property(lambda self: self._left, - doc="Coord of the left clipping plane.") - - right = property(lambda self: self._right, - doc="Coord of the right clipping plane.") - - bottom = property(lambda self: self._bottom, - doc="Coord of the bottom clipping plane.") - - top = property(lambda self: self._top, - doc="Coord of the top clipping plane.") - - @property - def size(self): - """Viewport size as a 2-tuple of float (width, height) or None.""" - return self._size - - @size.setter - def size(self, size): - assert len(size) == 2 - self._size = float(size[0]), float(size[1]) - self._update(self.left, self.right, self.bottom, self.top) - self.notify() - - -class Ortho2DWidget(_Projection): - """Orthographic projection with pixel as unit. - - Provides same coordinates as widgets: - origin: top left, X axis goes left, Y axis goes down. - - :param float near: Z coordinate of the near clipping plane. - :param float far: Z coordinante of the far clipping plane. - :param size: - Viewport's size used to compute the aspect ratio (width, height). - :type size: 2-tuple of float - """ - - def __init__(self, near=-1., far=1., size=(1., 1.)): - - super(Ortho2DWidget, self).__init__(near, far, size) - - def _makeMatrix(self): - width, height = self.size - return mat4Orthographic(0., width, height, 0., self.near, self.far) - - -class Perspective(_Projection): - """Perspective projection matrix defined by FOV and aspect ratio. - - :param float fovy: Vertical field-of-view in degrees. - :param float near: The near clipping plane Z coord (stricly positive). - :param float far: The far clipping plane Z coord (> near). - :param size: - Viewport's size used to compute the aspect ratio (width, height). - :type size: 2-tuple of float - """ - - def __init__(self, fovy=90., near=0.1, far=1., size=(1., 1.)): - - super(Perspective, self).__init__(near, far, checkDepthExtent=True) - self._fovy = 90. - self.fovy = fovy # Set _fovy - self.size = size # Set _ size - - def _makeMatrix(self): - width, height = self.size - return mat4Perspective(self.fovy, width, height, self.near, self.far) - - @property - def fovy(self): - """Vertical field-of-view in degrees.""" - return self._fovy - - @fovy.setter - def fovy(self, fovy): - self._fovy = float(fovy) - self.notify() |