OpenMotionLab/MotionGPT
118
1"""Virtual cameras compliant with the glTF 2.0 specification as described at2https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-camera3 4Author: Matthew Matl5"""6import abc7import numpy as np8import six9import sys10 11from .constants import DEFAULT_Z_NEAR, DEFAULT_Z_FAR12 13 14@six.add_metaclass(abc.ABCMeta)15class Camera(object):16 """Abstract base class for all cameras.17 18 Note19 ----20 Camera poses are specified in the OpenGL format,21 where the z axis points away from the view direction and the22 x and y axes point to the right and up in the image plane, respectively.23 24 Parameters25 ----------26 znear : float27 The floating-point distance to the near clipping plane.28 zfar : float29 The floating-point distance to the far clipping plane.30 ``zfar`` must be greater than ``znear``.31 name : str, optional32 The user-defined name of this object.33 """34 35 def __init__(self,36 znear=DEFAULT_Z_NEAR,37 zfar=DEFAULT_Z_FAR,38 name=None):39 self.name = name40 self.znear = znear41 self.zfar = zfar42 43 @property44 def name(self):45 """str : The user-defined name of this object.46 """47 return self._name48 49 @name.setter50 def name(self, value):51 if value is not None:52 value = str(value)53 self._name = value54 55 @property56 def znear(self):57 """float : The distance to the near clipping plane.58 """59 return self._znear60 61 @znear.setter62 def znear(self, value):63 value = float(value)64 if value < 0:65 raise ValueError('z-near must be >= 0.0')66 self._znear = value67 68 @property69 def zfar(self):70 """float : The distance to the far clipping plane.71 """72 return self._zfar73 74 @zfar.setter75 def zfar(self, value):76 value = float(value)77 if value <= 0 or value <= self.znear:78 raise ValueError('zfar must be >0 and >znear')79 self._zfar = value80 81 @abc.abstractmethod82 def get_projection_matrix(self, width=None, height=None):83 """Return the OpenGL projection matrix for this camera.84 85 Parameters86 ----------87 width : int88 Width of the current viewport, in pixels.89 height : int90 Height of the current viewport, in pixels.91 """92 pass93 94 95class PerspectiveCamera(Camera):96 97 """A perspective camera for perspective projection.98 99 Parameters100 ----------101 yfov : float102 The floating-point vertical field of view in radians.103 znear : float104 The floating-point distance to the near clipping plane.105 If not specified, defaults to 0.05.106 zfar : float, optional107 The floating-point distance to the far clipping plane.108 ``zfar`` must be greater than ``znear``.109 If None, the camera uses an infinite projection matrix.110 aspectRatio : float, optional111 The floating-point aspect ratio of the field of view.112 If not specified, the camera uses the viewport's aspect ratio.113 name : str, optional114 The user-defined name of this object.115 """116 117 def __init__(self,118 yfov,119 znear=DEFAULT_Z_NEAR,120 zfar=None,121 aspectRatio=None,122 name=None):123 super(PerspectiveCamera, self).__init__(124 znear=znear,125 zfar=zfar,126 name=name,127 )128 129 self.yfov = yfov130 self.aspectRatio = aspectRatio131 132 @property133 def yfov(self):134 """float : The vertical field of view in radians.135 """136 return self._yfov137 138 @yfov.setter139 def yfov(self, value):140 value = float(value)141 if value <= 0.0:142 raise ValueError('Field of view must be positive')143 self._yfov = value144 145 @property146 def zfar(self):147 """float : The distance to the far clipping plane.148 """149 return self._zfar150 151 @zfar.setter152 def zfar(self, value):153 if value is not None:154 value = float(value)155 if value <= 0 or value <= self.znear:156 raise ValueError('zfar must be >0 and >znear')157 self._zfar = value158 159 @property160 def aspectRatio(self):161 """float : The ratio of the width to the height of the field of view.162 """163 return self._aspectRatio164 165 @aspectRatio.setter166 def aspectRatio(self, value):167 if value is not None:168 value = float(value)169 if value <= 0.0:170 raise ValueError('Aspect ratio must be positive')171 self._aspectRatio = value172 173 def get_projection_matrix(self, width=None, height=None):174 """Return the OpenGL projection matrix for this camera.175 176 Parameters177 ----------178 width : int179 Width of the current viewport, in pixels.180 height : int181 Height of the current viewport, in pixels.182 """183 aspect_ratio = self.aspectRatio184 if aspect_ratio is None:185 if width is None or height is None:186 raise ValueError('Aspect ratio of camera must be defined')187 aspect_ratio = float(width) / float(height)188 189 a = aspect_ratio190 t = np.tan(self.yfov / 2.0)191 n = self.znear192 f = self.zfar193 194 P = np.zeros((4,4))195 P[0][0] = 1.0 / (a * t)196 P[1][1] = 1.0 / t197 P[3][2] = -1.0198 199 if f is None:200 P[2][2] = -1.0201 P[2][3] = -2.0 * n202 else:203 P[2][2] = (f + n) / (n - f)204 P[2][3] = (2 * f * n) / (n - f)205 206 return P207 208 209class OrthographicCamera(Camera):210 """An orthographic camera for orthographic projection.211 212 Parameters213 ----------214 xmag : float215 The floating-point horizontal magnification of the view.216 ymag : float217 The floating-point vertical magnification of the view.218 znear : float219 The floating-point distance to the near clipping plane.220 If not specified, defaults to 0.05.221 zfar : float222 The floating-point distance to the far clipping plane.223 ``zfar`` must be greater than ``znear``.224 If not specified, defaults to 100.0.225 name : str, optional226 The user-defined name of this object.227 """228 229 def __init__(self,230 xmag,231 ymag,232 znear=DEFAULT_Z_NEAR,233 zfar=DEFAULT_Z_FAR,234 name=None):235 super(OrthographicCamera, self).__init__(236 znear=znear,237 zfar=zfar,238 name=name,239 )240 241 self.xmag = xmag242 self.ymag = ymag243 244 @property245 def xmag(self):246 """float : The horizontal magnification of the view.247 """248 return self._xmag249 250 @xmag.setter251 def xmag(self, value):252 value = float(value)253 if value <= 0.0:254 raise ValueError('X magnification must be positive')255 self._xmag = value256 257 @property258 def ymag(self):259 """float : The vertical magnification of the view.260 """261 return self._ymag262 263 @ymag.setter264 def ymag(self, value):265 value = float(value)266 if value <= 0.0:267 raise ValueError('Y magnification must be positive')268 self._ymag = value269 270 @property271 def znear(self):272 """float : The distance to the near clipping plane.273 """274 return self._znear275 276 @znear.setter277 def znear(self, value):278 value = float(value)279 if value <= 0:280 raise ValueError('z-near must be > 0.0')281 self._znear = value282 283 def get_projection_matrix(self, width=None, height=None):284 """Return the OpenGL projection matrix for this camera.285 286 Parameters287 ----------288 width : int289 Width of the current viewport, in pixels.290 Unused in this function.291 height : int292 Height of the current viewport, in pixels.293 Unused in this function.294 """295 xmag = self.xmag296 ymag = self.ymag297 298 # If screen width/height defined, rescale xmag299 if width is not None and height is not None:300 xmag = width / height * ymag301 302 n = self.znear303 f = self.zfar304 P = np.zeros((4,4))305 P[0][0] = 1.0 / xmag306 P[1][1] = 1.0 / ymag307 P[2][2] = 2.0 / (n - f)308 P[2][3] = (f + n) / (n - f)309 P[3][3] = 1.0310 return P311 312 313class IntrinsicsCamera(Camera):314 """A perspective camera with custom intrinsics.315 316 Parameters317 ----------318 fx : float319 X-axis focal length in pixels.320 fy : float321 Y-axis focal length in pixels.322 cx : float323 X-axis optical center in pixels.324 cy : float325 Y-axis optical center in pixels.326 znear : float327 The floating-point distance to the near clipping plane.328 If not specified, defaults to 0.05.329 zfar : float330 The floating-point distance to the far clipping plane.331 ``zfar`` must be greater than ``znear``.332 If not specified, defaults to 100.0.333 name : str, optional334 The user-defined name of this object.335 """336 337 def __init__(self,338 fx,339 fy,340 cx,341 cy,342 znear=DEFAULT_Z_NEAR,343 zfar=DEFAULT_Z_FAR,344 name=None):345 super(IntrinsicsCamera, self).__init__(346 znear=znear,347 zfar=zfar,348 name=name,349 )350 351 self.fx = fx352 self.fy = fy353 self.cx = cx354 self.cy = cy355 356 @property357 def fx(self):358 """float : X-axis focal length in meters.359 """360 return self._fx361 362 @fx.setter363 def fx(self, value):364 self._fx = float(value)365 366 @property367 def fy(self):368 """float : Y-axis focal length in meters.369 """370 return self._fy371 372 @fy.setter373 def fy(self, value):374 self._fy = float(value)375 376 @property377 def cx(self):378 """float : X-axis optical center in pixels.379 """380 return self._cx381 382 @cx.setter383 def cx(self, value):384 self._cx = float(value)385 386 @property387 def cy(self):388 """float : Y-axis optical center in pixels.389 """390 return self._cy391 392 @cy.setter393 def cy(self, value):394 self._cy = float(value)395 396 def get_projection_matrix(self, width, height):397 """Return the OpenGL projection matrix for this camera.398 399 Parameters400 ----------401 width : int402 Width of the current viewport, in pixels.403 height : int404 Height of the current viewport, in pixels.405 """406 width = float(width)407 height = float(height)408 409 cx, cy = self.cx, self.cy410 fx, fy = self.fx, self.fy411 if sys.platform == 'darwin':412 cx = self.cx * 2.0413 cy = self.cy * 2.0414 fx = self.fx * 2.0415 fy = self.fy * 2.0416 417 P = np.zeros((4,4))418 P[0][0] = 2.0 * fx / width419 P[1][1] = 2.0 * fy / height420 P[0][2] = 1.0 - 2.0 * cx / width421 P[1][2] = 2.0 * cy / height - 1.0422 P[3][2] = -1.0423 424 n = self.znear425 f = self.zfar426 if f is None:427 P[2][2] = -1.0428 P[2][3] = -2.0 * n429 else:430 P[2][2] = (f + n) / (n - f)431 P[2][3] = (2 * f * n) / (n - f)432 433 return P434 435 436__all__ = ['Camera', 'PerspectiveCamera', 'OrthographicCamera',437 'IntrinsicsCamera']438 