Source code for pyrender.camera

"""Virtual cameras compliant with the glTF 2.0 specification as described at
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-camera

Author: Matthew Matl
"""
import abc
import numpy as np
import six
import sys

from .constants import DEFAULT_Z_NEAR, DEFAULT_Z_FAR


[docs]@six.add_metaclass(abc.ABCMeta) class Camera(object): """Abstract base class for all cameras. Note ---- Camera poses are specified in the OpenGL format, where the z axis points away from the view direction and the x and y axes point to the right and up in the image plane, respectively. Parameters ---------- znear : float The floating-point distance to the near clipping plane. zfar : float The floating-point distance to the far clipping plane. ``zfar`` must be greater than ``znear``. name : str, optional The user-defined name of this object. """ def __init__(self, znear=DEFAULT_Z_NEAR, zfar=DEFAULT_Z_FAR, name=None): self.name = name self.znear = znear self.zfar = zfar @property def name(self): """str : The user-defined name of this object. """ return self._name @name.setter def name(self, value): if value is not None: value = str(value) self._name = value @property def znear(self): """float : The distance to the near clipping plane. """ return self._znear @znear.setter def znear(self, value): value = float(value) if value < 0: raise ValueError('z-near must be >= 0.0') self._znear = value @property def zfar(self): """float : The distance to the far clipping plane. """ return self._zfar @zfar.setter def zfar(self, value): value = float(value) if value <= 0 or value <= self.znear: raise ValueError('zfar must be >0 and >znear') self._zfar = value
[docs] @abc.abstractmethod def get_projection_matrix(self, width=None, height=None): """Return the OpenGL projection matrix for this camera. Parameters ---------- width : int Width of the current viewport, in pixels. height : int Height of the current viewport, in pixels. """ pass
[docs]class PerspectiveCamera(Camera): """A perspective camera for perspective projection. Parameters ---------- yfov : float The floating-point vertical field of view in radians. znear : float The floating-point distance to the near clipping plane. If not specified, defaults to 0.05. zfar : float, optional The floating-point distance to the far clipping plane. ``zfar`` must be greater than ``znear``. If None, the camera uses an infinite projection matrix. aspectRatio : float, optional The floating-point aspect ratio of the field of view. If not specified, the camera uses the viewport's aspect ratio. name : str, optional The user-defined name of this object. """ def __init__(self, yfov, znear=DEFAULT_Z_NEAR, zfar=None, aspectRatio=None, name=None): super(PerspectiveCamera, self).__init__( znear=znear, zfar=zfar, name=name, ) self.yfov = yfov self.aspectRatio = aspectRatio @property def yfov(self): """float : The vertical field of view in radians. """ return self._yfov @yfov.setter def yfov(self, value): value = float(value) if value <= 0.0: raise ValueError('Field of view must be positive') self._yfov = value @property def zfar(self): """float : The distance to the far clipping plane. """ return self._zfar @zfar.setter def zfar(self, value): if value is not None: value = float(value) if value <= 0 or value <= self.znear: raise ValueError('zfar must be >0 and >znear') self._zfar = value @property def aspectRatio(self): """float : The ratio of the width to the height of the field of view. """ return self._aspectRatio @aspectRatio.setter def aspectRatio(self, value): if value is not None: value = float(value) if value <= 0.0: raise ValueError('Aspect ratio must be positive') self._aspectRatio = value
[docs] def get_projection_matrix(self, width=None, height=None): """Return the OpenGL projection matrix for this camera. Parameters ---------- width : int Width of the current viewport, in pixels. height : int Height of the current viewport, in pixels. """ aspect_ratio = self.aspectRatio if aspect_ratio is None: if width is None or height is None: raise ValueError('Aspect ratio of camera must be defined') aspect_ratio = float(width) / float(height) a = aspect_ratio t = np.tan(self.yfov / 2.0) n = self.znear f = self.zfar P = np.zeros((4,4)) P[0][0] = 1.0 / (a * t) P[1][1] = 1.0 / t P[3][2] = -1.0 if f is None: P[2][2] = -1.0 P[2][3] = -2.0 * n else: P[2][2] = (f + n) / (n - f) P[2][3] = (2 * f * n) / (n - f) return P
[docs]class OrthographicCamera(Camera): """An orthographic camera for orthographic projection. Parameters ---------- xmag : float The floating-point horizontal magnification of the view. ymag : float The floating-point vertical magnification of the view. znear : float The floating-point distance to the near clipping plane. If not specified, defaults to 0.05. zfar : float The floating-point distance to the far clipping plane. ``zfar`` must be greater than ``znear``. If not specified, defaults to 100.0. name : str, optional The user-defined name of this object. """ def __init__(self, xmag, ymag, znear=DEFAULT_Z_NEAR, zfar=DEFAULT_Z_FAR, name=None): super(OrthographicCamera, self).__init__( znear=znear, zfar=zfar, name=name, ) self.xmag = xmag self.ymag = ymag @property def xmag(self): """float : The horizontal magnification of the view. """ return self._xmag @xmag.setter def xmag(self, value): value = float(value) if value <= 0.0: raise ValueError('X magnification must be positive') self._xmag = value @property def ymag(self): """float : The vertical magnification of the view. """ return self._ymag @ymag.setter def ymag(self, value): value = float(value) if value <= 0.0: raise ValueError('Y magnification must be positive') self._ymag = value @property def znear(self): """float : The distance to the near clipping plane. """ return self._znear @znear.setter def znear(self, value): value = float(value) if value <= 0: raise ValueError('z-near must be > 0.0') self._znear = value
[docs] def get_projection_matrix(self, width=None, height=None): """Return the OpenGL projection matrix for this camera. Parameters ---------- width : int Width of the current viewport, in pixels. Unused in this function. height : int Height of the current viewport, in pixels. Unused in this function. """ xmag = self.xmag ymag = self.ymag # If screen width/height defined, rescale xmag if width is not None and height is not None: xmag = width / height * ymag n = self.znear f = self.zfar P = np.zeros((4,4)) P[0][0] = 1.0 / xmag P[1][1] = 1.0 / ymag P[2][2] = 2.0 / (n - f) P[2][3] = (f + n) / (n - f) P[3][3] = 1.0 return P
[docs]class IntrinsicsCamera(Camera): """A perspective camera with custom intrinsics. Parameters ---------- fx : float X-axis focal length in pixels. fy : float Y-axis focal length in pixels. cx : float X-axis optical center in pixels. cy : float Y-axis optical center in pixels. znear : float The floating-point distance to the near clipping plane. If not specified, defaults to 0.05. zfar : float The floating-point distance to the far clipping plane. ``zfar`` must be greater than ``znear``. If not specified, defaults to 100.0. name : str, optional The user-defined name of this object. """ def __init__(self, fx, fy, cx, cy, znear=DEFAULT_Z_NEAR, zfar=DEFAULT_Z_FAR, name=None): super(IntrinsicsCamera, self).__init__( znear=znear, zfar=zfar, name=name, ) self.fx = fx self.fy = fy self.cx = cx self.cy = cy @property def fx(self): """float : X-axis focal length in meters. """ return self._fx @fx.setter def fx(self, value): self._fx = float(value) @property def fy(self): """float : Y-axis focal length in meters. """ return self._fy @fy.setter def fy(self, value): self._fy = float(value) @property def cx(self): """float : X-axis optical center in pixels. """ return self._cx @cx.setter def cx(self, value): self._cx = float(value) @property def cy(self): """float : Y-axis optical center in pixels. """ return self._cy @cy.setter def cy(self, value): self._cy = float(value)
[docs] def get_projection_matrix(self, width, height): """Return the OpenGL projection matrix for this camera. Parameters ---------- width : int Width of the current viewport, in pixels. height : int Height of the current viewport, in pixels. """ width = float(width) height = float(height) cx, cy = self.cx, self.cy fx, fy = self.fx, self.fy if sys.platform == 'darwin': cx = self.cx * 2.0 cy = self.cy * 2.0 fx = self.fx * 2.0 fy = self.fy * 2.0 P = np.zeros((4,4)) P[0][0] = 2.0 * fx / width P[1][1] = 2.0 * fy / height P[0][2] = 1.0 - 2.0 * cx / width P[1][2] = 2.0 * cy / height - 1.0 P[3][2] = -1.0 n = self.znear f = self.zfar if f is None: P[2][2] = -1.0 P[2][3] = -2.0 * n else: P[2][2] = (f + n) / (n - f) P[2][3] = (2 * f * n) / (n - f) return P
__all__ = ['Camera', 'PerspectiveCamera', 'OrthographicCamera', 'IntrinsicsCamera']