diff options
Diffstat (limited to 'silx/gui/plot3d/scene/camera.py')
-rw-r--r-- | silx/gui/plot3d/scene/camera.py | 353 |
1 files changed, 0 insertions, 353 deletions
diff --git a/silx/gui/plot3d/scene/camera.py b/silx/gui/plot3d/scene/camera.py deleted file mode 100644 index 90de7ed..0000000 --- a/silx/gui/plot3d/scene/camera.py +++ /dev/null @@ -1,353 +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 classes to handle a perspective projection in 3D.""" - -from __future__ import absolute_import, division, unicode_literals - -__authors__ = ["T. Vincent"] -__license__ = "MIT" -__date__ = "25/07/2016" - - -import numpy - -from . import transform - - -# CameraExtrinsic ############################################################# - -class CameraExtrinsic(transform.Transform): - """Transform matrix to handle camera position and orientation. - - :param position: Coordinates of the point of view. - :type position: numpy.ndarray-like of 3 float32. - :param direction: Sight direction vector. - :type direction: numpy.ndarray-like of 3 float32. - :param up: Vector pointing upward in the image plane. - :type up: numpy.ndarray-like of 3 float32. - """ - - def __init__(self, position=(0., 0., 0.), - direction=(0., 0., -1.), - up=(0., 1., 0.)): - - super(CameraExtrinsic, self).__init__() - self._position = None - self.position = position # set _position - self._side = 1., 0., 0. - self._up = 0., 1., 0. - self._direction = 0., 0., -1. - self.setOrientation(direction=direction, up=up) # set _direction, _up - - def _makeMatrix(self): - return transform.mat4LookAtDir(self._position, - self._direction, self._up) - - def copy(self): - """Return an independent copy""" - return CameraExtrinsic(self.position, self.direction, self.up) - - def setOrientation(self, direction=None, up=None): - """Set the rotation of the point of view. - - :param direction: Sight direction vector or - None to keep the current one. - :type direction: numpy.ndarray-like of 3 float32 or None. - :param up: Vector pointing upward in the image plane or - None to keep the current one. - :type up: numpy.ndarray-like of 3 float32 or None. - :raises RuntimeError: if the direction and up are parallel. - """ - if direction is None: # Use current direction - direction = self.direction - else: - assert len(direction) == 3 - direction = numpy.array(direction, copy=True, dtype=numpy.float32) - direction /= numpy.linalg.norm(direction) - - if up is None: # Use current up - up = self.up - else: - assert len(up) == 3 - up = numpy.array(up, copy=True, dtype=numpy.float32) - - # Update side and up to make sure they are perpendicular and normalized - side = numpy.cross(direction, up) - sidenormal = numpy.linalg.norm(side) - if sidenormal == 0.: - raise RuntimeError('direction and up vectors are parallel.') - # Alternative: when one of the input parameter is None, it is - # possible to guess correct vectors using previous direction and up - side /= sidenormal - up = numpy.cross(side, direction) - up /= numpy.linalg.norm(up) - - self._side = side - self._up = up - self._direction = direction - self.notify() - - @property - def position(self): - """Coordinates of the point of view as a numpy.ndarray of 3 float32.""" - return self._position.copy() - - @position.setter - def position(self, position): - assert len(position) == 3 - self._position = numpy.array(position, copy=True, dtype=numpy.float32) - self.notify() - - @property - def direction(self): - """Sight direction (ndarray of 3 float32).""" - return self._direction.copy() - - @direction.setter - def direction(self, direction): - self.setOrientation(direction=direction) - - @property - def up(self): - """Vector pointing upward in the image plane (ndarray of 3 float32). - """ - return self._up.copy() - - @up.setter - def up(self, up): - self.setOrientation(up=up) - - @property - def side(self): - """Vector pointing towards the side of the image plane. - - ndarray of 3 float32""" - return self._side.copy() - - def move(self, direction, step=1.): - """Move the camera relative to the image plane. - - :param str direction: Direction relative to image plane. - One of: 'up', 'down', 'left', 'right', - 'forward', 'backward'. - :param float step: The step of the pan to perform in the coordinate - in which the camera position is defined. - """ - if direction in ('up', 'down'): - vector = self.up * (1. if direction == 'up' else -1.) - elif direction in ('left', 'right'): - vector = self.side * (1. if direction == 'right' else -1.) - elif direction in ('forward', 'backward'): - vector = self.direction * (1. if direction == 'forward' else -1.) - else: - raise ValueError('Unsupported direction: %s' % direction) - - self.position += step * vector - - def rotate(self, direction, angle=1.): - """First-person rotation of the camera towards the direction. - - :param str direction: Direction of movement relative to image plane. - In: 'up', 'down', 'left', 'right'. - :param float angle: The angle in degrees of the rotation. - """ - if direction in ('up', 'down'): - axis = self.side * (1. if direction == 'up' else -1.) - elif direction in ('left', 'right'): - axis = self.up * (1. if direction == 'left' else -1.) - else: - raise ValueError('Unsupported direction: %s' % direction) - - matrix = transform.mat4RotateFromAngleAxis(numpy.radians(angle), *axis) - newdir = numpy.dot(matrix[:3, :3], self.direction) - - if direction in ('up', 'down'): - # Rotate up to avoid up and new direction to be (almost) co-linear - newup = numpy.dot(matrix[:3, :3], self.up) - self.setOrientation(newdir, newup) - else: - # No need to rotate up here as it is the rotation axis - self.direction = newdir - - def orbit(self, direction, center=(0., 0., 0.), angle=1.): - """Rotate the camera around a point. - - :param str direction: Direction of movement relative to image plane. - In: 'up', 'down', 'left', 'right'. - :param center: Position around which to rotate the point of view. - :type center: numpy.ndarray-like of 3 float32. - :param float angle: he angle in degrees of the rotation. - """ - if direction in ('up', 'down'): - axis = self.side * (1. if direction == 'down' else -1.) - elif direction in ('left', 'right'): - axis = self.up * (1. if direction == 'right' else -1.) - else: - raise ValueError('Unsupported direction: %s' % direction) - - # Rotate viewing direction - rotmatrix = transform.mat4RotateFromAngleAxis( - numpy.radians(angle), *axis) - self.direction = numpy.dot(rotmatrix[:3, :3], self.direction) - - # Rotate position around center - center = numpy.array(center, copy=False, dtype=numpy.float32) - matrix = numpy.dot(transform.mat4Translate(*center), rotmatrix) - matrix = numpy.dot(matrix, transform.mat4Translate(*(-center))) - position = numpy.append(self.position, 1.) - self.position = numpy.dot(matrix, position)[:3] - - _RESET_CAMERA_ORIENTATIONS = { - 'side': ((-1., -1., -1.), (0., 1., 0.)), - 'front': ((0., 0., -1.), (0., 1., 0.)), - 'back': ((0., 0., 1.), (0., 1., 0.)), - 'top': ((0., -1., 0.), (0., 0., -1.)), - 'bottom': ((0., 1., 0.), (0., 0., 1.)), - 'right': ((-1., 0., 0.), (0., 1., 0.)), - 'left': ((1., 0., 0.), (0., 1., 0.)) - } - - def reset(self, face=None): - """Reset the camera position to pre-defined orientations. - - :param str face: The direction of the camera in: - side, front, back, top, bottom, right, left. - """ - if face not in self._RESET_CAMERA_ORIENTATIONS: - raise ValueError('Unsupported face: %s' % face) - - distance = numpy.linalg.norm(self.position) - direction, up = self._RESET_CAMERA_ORIENTATIONS[face] - self.setOrientation(direction, up) - self.position = - self.direction * distance - - -class Camera(transform.Transform): - """Combination of camera projection and position. - - See :class:`Perspective` and :class:`CameraExtrinsic`. - - :param float fovy: Vertical field-of-view in degrees. - :param float near: The near clipping plane Z coord (strictly 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 - :param position: Coordinates of the point of view. - :type position: numpy.ndarray-like of 3 float32. - :param direction: Sight direction vector. - :type direction: numpy.ndarray-like of 3 float32. - :param up: Vector pointing upward in the image plane. - :type up: numpy.ndarray-like of 3 float32. - """ - - def __init__(self, fovy=30., near=0.1, far=1., size=(1., 1.), - position=(0., 0., 0.), - direction=(0., 0., -1.), up=(0., 1., 0.)): - super(Camera, self).__init__() - self._intrinsic = transform.Perspective(fovy, near, far, size) - self._intrinsic.addListener(self._transformChanged) - self._extrinsic = CameraExtrinsic(position, direction, up) - self._extrinsic.addListener(self._transformChanged) - - def _makeMatrix(self): - return numpy.dot(self.intrinsic.matrix, self.extrinsic.matrix) - - def _transformChanged(self, source): - """Listener of intrinsic and extrinsic camera parameters instances.""" - if source is not self: - self.notify() - - def resetCamera(self, bounds): - """Change camera to have the bounds in the viewing frustum. - - It updates the camera position and depth extent. - Camera sight direction and up are not affected. - - :param bounds: The axes-aligned bounds to include. - :type bounds: numpy.ndarray: ((xMin, yMin, zMin), (xMax, yMax, zMax)) - """ - - center = 0.5 * (bounds[0] + bounds[1]) - radius = numpy.linalg.norm(0.5 * (bounds[1] - bounds[0])) - if radius == 0.: # bounds are all collapsed - radius = 1. - - if isinstance(self.intrinsic, transform.Perspective): - # Get the viewpoint distance from the bounds center - minfov = numpy.radians(self.intrinsic.fovy) - width, height = self.intrinsic.size - if width < height: - minfov *= width / height - - offset = radius / numpy.sin(0.5 * minfov) - - # Update camera - self.extrinsic.position = \ - center - offset * self.extrinsic.direction - self.intrinsic.setDepthExtent(offset - radius, offset + radius) - - elif isinstance(self.intrinsic, transform.Orthographic): - # Y goes up - self.intrinsic.setClipping( - left=center[0] - radius, - right=center[0] + radius, - bottom=center[1] - radius, - top=center[1] + radius) - - # Update camera - self.extrinsic.position = 0, 0, 0 - self.intrinsic.setDepthExtent(center[2] - radius, - center[2] + radius) - else: - raise RuntimeError('Unsupported camera: %s' % self.intrinsic) - - @property - def intrinsic(self): - """Intrinsic camera parameters, i.e., projection matrix.""" - return self._intrinsic - - @intrinsic.setter - def intrinsic(self, intrinsic): - self._intrinsic.removeListener(self._transformChanged) - self._intrinsic = intrinsic - self._intrinsic.addListener(self._transformChanged) - - @property - def extrinsic(self): - """Extrinsic camera parameters, i.e., position and orientation.""" - return self._extrinsic - - def move(self, *args, **kwargs): - """See :meth:`CameraExtrinsic.move`.""" - self.extrinsic.move(*args, **kwargs) - - def rotate(self, *args, **kwargs): - """See :meth:`CameraExtrinsic.rotate`.""" - self.extrinsic.rotate(*args, **kwargs) - - def orbit(self, *args, **kwargs): - """See :meth:`CameraExtrinsic.orbit`.""" - self.extrinsic.orbit(*args, **kwargs) |