vumichien's picture
First commit
b4c8bc3
raw history blame
No virus
11.6 kB
"""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
@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
@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
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
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
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
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
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)
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']