Source code for gloopy.geom.orientation

from math import pi, degrees
from random import uniform

from OpenGL import GL

from .vector import Vector
from .matrix import Matrix



EPSILON = 1e-15
matrix_type = GL.GLfloat * 16


class Orientation(object):
[docs] ''' Defines an orientation by maintaining a `forward` and `up` vector (and a derived `right`, orthogonal to both.) .. function:: __init__(forward=None, up=None) Constructs an orientation looking along the `forward` vector. If none is given then defaul to the negative Z axis. If `up` is specified, it must lie at right angles to `forward`. If none is given then a default is chosen, the vector at right angles to forward which lies closest to the positive Y axis. Orientation.Identity, which results in zero rotation, is with forward pointing along the negative Z axis, up along the positive Y axis (and hence right along the positive X axis.) ''' def __init__(self, forward=None, up=None):
[docs] ''' 'forward' and 'up' should be Vector or 3-part tuple. If 'up' is omitted, a sensible default up vector is chosen. ''' if forward is None: forward = Vector.ZNegAxis elif not isinstance(forward, Vector): forward = Vector(*forward) self._forward = forward.normalized() if up is None: up = self._get_default_up() elif not isinstance(up, Vector): up = Vector(*up) angle_between = forward.angle(up) assert abs(angle_between - pi/2) < EPSILON, \ "up (%s) must be 90deg to forward (%s), actually %f deg" % \ (up, forward, degrees(angle_between)) self._up = up.normalized() self.right = self._get_right() # cached return value for 'matrix' property. Needs reseting to None # whenever self.forward or self.up change. self._matrix = None def __repr__(self):
return 'Orientation(%s, up=%s)' % (self.forward, self.up) def __eq__(self, other): return ( isinstance(other, Orientation) and self.forward == other.forward and self.up == other.up) def __ne__(self, other): return not self.__eq__(other) __hash__ = None # Orientations are mutable, so do not allow hashing @staticmethod def Random():
[docs] ''' Return a new random Orientation ''' fwd = Vector.RandomSphere(1) return Orientation(fwd).roll(uniform(-pi, +pi)) def _set_forward(self, new):
self._forward = new self._matrix = None forward = property(lambda s: s._forward, _set_forward, None, 'The forward vector') def _set_up(self, new): self._up = new self._matrix = None up = property(lambda s: s._up, _set_up, None, 'The up vector') def _get_default_up(self): ''' returns a sensible default up vector (ie. orthogonal to forward, but pointed as near to the Y axis as possible) ''' # special case for forward is y-axis or negative y-axis if self.forward == Vector.YAxis: return Vector.ZAxis elif self.forward == Vector.YNegAxis: return Vector.ZNegAxis # project 'forward' onto y=0 plane flat = Vector(self.forward.x, 0, self.forward.z) # find 'axis', a vector in the y=0 plane at right angles to 'flat' axis = flat.cross(Vector.YAxis) # rotate 'forward' by 90 deg about 'axis' up = self.forward.rotate(axis, -pi/2) return up.normalized() def _get_right(self): ''' value of self.right is always derived from self.forward and self.up ''' return self.forward.cross(self.up) def roll(self, angle):
[docs] ''' Rotate new Orientation, rotated about the 'forward' axis (ie. +ve angle rolls to the right.) ''' return Orientation( self.forward, self.up.rotate(self.forward, -angle).normalized() ) def yaw(self, angle):
[docs] ''' Rotate about the 'down' axis (ie. +ve angle yaws to the right.) ''' return Orientation( self.forward.rotate(self.up, angle).normalized(), self.up ) def pitch(self, angle):
[docs] ''' Rotate about the 'right' axis (ie. +ve angle pitches up.) ''' return Orientation( self.forward.rotate(self.right, -angle).normalized(), self.up.rotate(self.right, -angle).normalized() ) def rotate(self, axis, angle):
[docs] ''' Rotate about the given axis by the given angle. ''' return Orientation( self.forward.rotate(axis, angle), self.up.rotate(axis, angle) ) # This method is an ugly performance hack. Orientation shouldn't need # to import matrix, nor to know about ctypes. One day this will be # replaced by replacing every gameitem's position and orientation # attributes with a single attribute which stores position and # orientation stored natively as a ctypes matrix suitable for passing # directly to glMultMatrix or shader uniforms. @property
def matrix(self):
[docs] ''' The matrix that the OpenGL modelview matrix should be multiplied by to represent this orientation. It's likely that this method will disappear in later releases of Gloopy. ''' if self._matrix is None: self._matrix = matrix_type( *Matrix(Vector.Origin, self) ) return self._matrix Orientation.Identity = Orientation()
# ugly hack to prevent cyclic imports Matrix._zero_rotation = Orientation.Identity