from __future__ import division
from collections import namedtuple
from math import acos, cos, sin, sqrt
from random import randint, uniform
EPSILON = 1e-7
[docs]class Vector(namedtuple('VectorBase', 'x y z')):
'''
3-component named tuple: (x, y, z), with some methods, including value
type equality semantics.
.. function:: __init__(x, y, z)
Arithmetic operators are supported:
.. function:: __neg__(): unary minus to invert direction
.. function:: __add__(other): addition of vector or 3-tuple
.. function:: __sub__(other): subtraction of vector or 3-tuple
.. function:: __mul__(float): scale a vector
.. function:: __div__(float): scale a vector (truediv also supported)
'''
__slots__ = []
COMPONENTS = 3
def __repr__(self):
return 'Vector(%.2g, %.2g, %.2g)' % (self.x, self.y, self.z)
def __eq__(self, other):
return (
isinstance(other, tuple) and
abs(self[0] - other[0]) < EPSILON and
abs(self[1] - other[1]) < EPSILON and
abs(self[2] - other[2]) < EPSILON
)
# __neq__ as 'not __eq__' seems to be inherited from tuple
__hash__ = None # Vector are mutable, so do not allow hashing
def __neg__(self):
return Vector(-self.x, -self.y, -self.z)
def __add__(self, other):
ox, oy, oz = other
return Vector(
self.x + ox,
self.y + oy,
self.z + oz,
)
def __radd__(self, other):
return self.__add__(other)
def __sub__(self, other):
return Vector(
self.x - other[0],
self.y - other[1],
self.z - other[2],
)
def __rsub__(self, other):
return Vector(*other).__sub__(self)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar, self.z * scalar)
def __rmul__(self, scalar):
return self.__mul__(scalar)
def __truediv__(self, scalar):
return Vector(self.x / scalar, self.y / scalar, self.z / scalar)
__div__ = __truediv__
@staticmethod
[docs] def RandomCube(size, ints=False):
'''
A new random Vector, evenly distributed within a cube of `size` sides.
'''
rand = randint if ints else uniform
return Vector(
rand(-size/2, +size/2),
rand(-size/2, +size/2),
rand(-size/2, +size/2),
)
@staticmethod
[docs] def RandomSphere(radius):
'''
A new random Vector, evenly distributed within a sphere of `radius`.
'''
while True:
p = Vector.RandomCube(radius)
if p.length2 < radius ** 2:
return p
@staticmethod
[docs] def RandomShell(radius):
'''
A new random Vector, evenly distributed on surface a sphere of `radius`.
'''
while True:
p = Vector.RandomCube(radius)
if p.length2 < radius ** 2:
return p.normalized() * radius
@property
[docs] def length(self):
return sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
@property
[docs] def length2(self):
'''
Length squared. Cheaper to calculate.
'''
return self.x ** 2 + self.y ** 2 + self.z ** 2
[docs] def normalized(self, length=1):
'''
Return a new vector in the same direction, but given length (default 1)
'''
factor = length / self.length
return Vector(self.x * factor, self.y * factor, self.z * factor)
[docs] def cross(self, other):
'''
Return a new vector, the cross product
a x b = (a2b3 - a3b2, a3b1 - a1b3, a1b2 - a2b1)
http://en.wikipedia.org/wiki/Cross_product
'''
return Vector(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x)
[docs] def dot(self, other):
'''
Return the scalar dot product
'''
return self[0] * other[0] + self[1] * other[1] + self[2] * other[2]
[docs] def angle(self, other):
'''
return the angle between this vector and the given one
'''
return acos(self.dot(other) / (self.length * other.length))
[docs] def rotate(self, axis, angle):
'''
Return a new vector, rotated about the given axis
If rotating many verts around the same axis, consider creating a
Matrix to represent the rotation instead, and calling m.transform(v)
on each vertex, which might be faster.
'''
c = cos(-angle)
t = 1 - c
s = sin(-angle)
# Matrix 'M' rotates about axis
d11 = t * axis.x ** 2 + c
d12 = t * axis.x * axis.y - s * axis.z
d13 = t * axis.x * axis.z + s * axis.y
d21 = t * axis.x * axis.y + s * axis.z
d22 = t * axis.y ** 2 + c
d23 = t * axis.y * axis.z - s * axis.x
d31 = t * axis.x * axis.z - s * axis.y
d32 = t * axis.y * axis.z + s * axis.x
d33 = t * axis.z ** 2 + c
# multiply M * self
return Vector(
d11 * self.x + d12 * self.y + d13 * self.z,
d21 * self.x + d22 * self.y + d23 * self.z,
d31 * self.x + d32 * self.y + d33 * self.z,
)
[docs] def any_orthogonal(self):
'''
Return any unit vector at right angles to the given vector
'''
assert self != Vector.Origin
# friend = any vector at all, so long as it isn't parallel to orig
if abs(self.x) < abs(self.y):
friend = Vector.XAxis
else:
friend = Vector.YAxis
return self.cross(friend).normalized()
Vector.Origin = Vector(0, 0, 0)
Vector.XAxis = Vector(1, 0, 0)
Vector.YAxis = Vector(0, 1, 0)
Vector.ZAxis = Vector(0, 0, 1)
Vector.XNegAxis = Vector(-1, 0, 0)
Vector.YNegAxis = Vector( 0, -1, 0)
Vector.ZNegAxis = Vector( 0, 0, -1)