pygame is
Python
Simple DirectMedia Layer
 
 
pygame.org is
Site Swing
Wiki

3DVectorClass

      
Search:  
 
 

Here is a 3D vector class I made from the source of 2DVectorClass. It lacks the method "perpendicular," and rotations/angles have been defined as around the positive x, y, or z axis (in the YZ, ZX, or XY planes).

--Kevin Conner

########################################################################
import operator
import math
 
class Vec3d(object):
	"""3d vector class, supports vector and scalar operators,
		and also provides a bunch of high level functions.
		reproduced from the vec2d class on the pygame wiki site.
		"""
	__slots__ = ['x', 'y', 'z']
 
	def __init__(self, x_or_triple, y = None, z = None):
		if y == None:
			self.x = x_or_triple[0]
			self.y = x_or_triple[1]
			self.z = x_or_triple[2]
		else:
			self.x = x_or_triple
			self.y = y
			self.z = z
 
	def __len__(self):
		return 3
 
	def __getitem__(self, key):
		if key == 0:
			return self.x
		elif key == 1:
			return self.y
		elif key == 2:
			return self.z
		else:
			raise IndexError("Invalid subscript "+str(key)+" to Vec3d")
 
	def __setitem__(self, key, value):
		if key == 0:
			self.x = value
		elif key == 1:
			self.y = value
		elif key == 2:
			self.z = value
		else:
			raise IndexError("Invalid subscript "+str(key)+" to Vec3d")
 
	# String representaion (for debugging)
	def __repr__(self):
		return 'Vec3d(%s, %s, %s)' % (self.x, self.y, self.z)
	
	# Comparison
	def __eq__(self, other):
		if hasattr(other, "__getitem__") and len(other) == 3:
			return self.x == other[0] and self.y == other[1] and self.z == other[2]
		else:
			return False
	
	def __ne__(self, other):
		if hasattr(other, "__getitem__") and len(other) == 3:
			return self.x != other[0] or self.y != other[1] or self.z != other[2]
		else:
			return True
 
	def __nonzero__(self):
		return self.x or self.y or self.z
 
	# Generic operator handlers
	def _o2(self, other, f):
		"Any two-operator operation where the left operand is a Vec3d"
		if isinstance(other, Vec3d):
			return Vec3d(f(self.x, other.x),
						 f(self.y, other.y),
						 f(self.z, other.z))
		elif (hasattr(other, "__getitem__")):
			return Vec3d(f(self.x, other[0]),
						 f(self.y, other[1]),
						 f(self.z, other[2]))
		else:
			return Vec3d(f(self.x, other),
						 f(self.y, other),
						 f(self.z, other))
 
	def _r_o2(self, other, f):
		"Any two-operator operation where the right operand is a Vec3d"
		if (hasattr(other, "__getitem__")):
			return Vec3d(f(other[0], self.x),
						 f(other[1], self.y),
						 f(other[2], self.z))
		else:
			return Vec3d(f(other, self.x),
						 f(other, self.y),
						 f(other, self.z))
 
	def _io(self, other, f):
		"inplace operator"
		if (hasattr(other, "__getitem__")):
			self.x = f(self.x, other[0])
			self.y = f(self.y, other[1])
			self.z = f(self.z, other[2])
		else:
			self.x = f(self.x, other)
			self.y = f(self.y, other)
			self.z = f(self.z, other)
		return self
 
	# Addition
	def __add__(self, other):
		if isinstance(other, Vec3d):
			return Vec3d(self.x + other.x, self.y + other.y, self.z + other.z)
		elif hasattr(other, "__getitem__"):
			return Vec3d(self.x + other[0], self.y + other[1], self.z + other[2])
		else:
			return Vec3d(self.x + other, self.y + other, self.z + other)
	__radd__ = __add__
	
	def __iadd__(self, other):
		if isinstance(other, Vec3d):
			self.x += other.x
			self.y += other.y
			self.z += other.z
		elif hasattr(other, "__getitem__"):
			self.x += other[0]
			self.y += other[1]
			self.z += other[2]
		else:
			self.x += other
			self.y += other
			self.z += other
		return self
 
	# Subtraction
	def __sub__(self, other):
		if isinstance(other, Vec3d):
			return Vec3d(self.x - other.x, self.y - other.y, self.z - other.z)
		elif (hasattr(other, "__getitem__")):
			return Vec3d(self.x - other[0], self.y - other[1], self.z - other[2])
		else:
			return Vec3d(self.x - other, self.y - other, self.z - other)
	def __rsub__(self, other):
		if isinstance(other, Vec3d):
			return Vec3d(other.x - self.x, other.y - self.y, other.z - self.z)
		if (hasattr(other, "__getitem__")):
			return Vec3d(other[0] - self.x, other[1] - self.y, other[2] - self.z)
		else:
			return Vec3d(other - self.x, other - self.y, other - self.z)
	def __isub__(self, other):
		if isinstance(other, Vec3d):
			self.x -= other.x
			self.y -= other.y
			self.z -= other.z
		elif (hasattr(other, "__getitem__")):
			self.x -= other[0]
			self.y -= other[1]
			self.z -= other[2]
		else:
			self.x -= other
			self.y -= other
			self.z -= other
		return self
 
	# Multiplication
	def __mul__(self, other):
		if isinstance(other, Vec3d):
			return Vec3d(self.x*other.x, self.y*other.y, self.z*other.z)
		if (hasattr(other, "__getitem__")):
			return Vec3d(self.x*other[0], self.y*other[1], self.z*other[2])
		else:
			return Vec3d(self.x*other, self.y*other, self.z*other)
	__rmul__ = __mul__
	
	def __imul__(self, other):
		if isinstance(other, Vec3d):
			self.x *= other.x
			self.y *= other.y
			self.z *= other.z
		elif (hasattr(other, "__getitem__")):
			self.x *= other[0]
			self.y *= other[1]
			self.z *= other[2]
		else:
			self.x *= other
			self.y *= other
			self.z *= other
		return self
 
	# Division
	def __div__(self, other):
		return self._o2(other, operator.div)
	def __rdiv__(self, other):
		return self._r_o2(other, operator.div)
	def __idiv__(self, other):
		return self._io(other, operator.div)
 
	def __floordiv__(self, other):
		return self._o2(other, operator.floordiv)
	def __rfloordiv__(self, other):
		return self._r_o2(other, operator.floordiv)
	def __ifloordiv__(self, other):
		return self._io(other, operator.floordiv)
 
	def __truediv__(self, other):
		return self._o2(other, operator.truediv)
	def __rtruediv__(self, other):
		return self._r_o2(other, operator.truediv)
	def __itruediv__(self, other):
		return self._io(other, operator.floordiv)
 
	# Modulo
	def __mod__(self, other):
		return self._o2(other, operator.mod)
	def __rmod__(self, other):
		return self._r_o2(other, operator.mod)
 
	def __divmod__(self, other):
		return self._o2(other, operator.divmod)
	def __rdivmod__(self, other):
		return self._r_o2(other, operator.divmod)
 
	# Exponentation
	def __pow__(self, other):
		return self._o2(other, operator.pow)
	def __rpow__(self, other):
		return self._r_o2(other, operator.pow)
 
	# Bitwise operators
	def __lshift__(self, other):
		return self._o2(other, operator.lshift)
	def __rlshift__(self, other):
		return self._r_o2(other, operator.lshift)
 
	def __rshift__(self, other):
		return self._o2(other, operator.rshift)
	def __rrshift__(self, other):
		return self._r_o2(other, operator.rshift)
 
	def __and__(self, other):
		return self._o2(other, operator.and_)
	__rand__ = __and__
 
	def __or__(self, other):
		return self._o2(other, operator.or_)
	__ror__ = __or__
 
	def __xor__(self, other):
		return self._o2(other, operator.xor)
	__rxor__ = __xor__
 
	# Unary operations
	def __neg__(self):
		return Vec3d(operator.neg(self.x), operator.neg(self.y), operator.neg(self.z))
 
	def __pos__(self):
		return Vec3d(operator.pos(self.x), operator.pos(self.y), operator.pos(self.z))
 
	def __abs__(self):
		return Vec3d(abs(self.x), abs(self.y), abs(self.z))
 
	def __invert__(self):
		return Vec3d(-self.x, -self.y, -self.z)
 
	# vectory functions
	def get_length_sqrd(self): 
		return self.x**2 + self.y**2 + self.z**2
 
	def get_length(self):
		return math.sqrt(self.x**2 + self.y**2 + self.z**2)		 
	def __setlength(self, value):
		length = self.get_length()
		self.x *= value/length
		self.y *= value/length
		self.z *= value/length
	length = property(get_length, __setlength, None, "gets or sets the magnitude of the vector")
		
	def rotate_around_z(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		x = self.x*cos - self.y*sin
		y = self.x*sin + self.y*cos
		self.x = x
		self.y = y
 
	def rotate_around_x(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		y = self.y*cos - self.z*sin
		z = self.y*sin + self.z*cos
		self.y = y
		self.z = z
 
	def rotate_around_y(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		z = self.z*cos - self.x*sin
		x = self.z*sin + self.x*cos
		self.z = z
		self.x = x
 
	def rotated_around_z(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		x = self.x*cos - self.y*sin
		y = self.x*sin + self.y*cos
		return Vec3d(x, y, self.z)
	
	def rotated_around_x(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		y = self.y*cos - self.z*sin
		z = self.y*sin + self.z*cos
		return Vec3d(self.x, y, z)
	
	def rotated_around_y(self, angle_degrees):
		radians = math.radians(angle_degrees)
		cos = math.cos(radians)
		sin = math.sin(radians)
		z = self.z*cos - self.x*sin
		x = self.z*sin + self.x*cos
		return Vec3d(x, self.y, z)
	
	def get_angle_around_z(self):
		if (self.get_length_sqrd() == 0):
			return 0
		return math.degrees(math.atan2(self.y, self.x))
	def __setangle_around_z(self, angle_degrees):
		self.x = math.sqrt(self.x**2 + self.y**2)
		self.y = 0
		self.rotate_around_z(angle_degrees)
	angle_around_z = property(get_angle_around_z, __setangle_around_z, None, "gets or sets the angle of a vector in the XY plane")
 
	def get_angle_around_x(self):
		if (self.get_length_sqrd() == 0):
			return 0
		return math.degrees(math.atan2(self.z, self.y))
	def __setangle_around_x(self, angle_degrees):
		self.y = math.sqrt(self.y**2 + self.z**2)
		self.z = 0
		self.rotate_around_x(angle_degrees)
	angle_around_x = property(get_angle_around_x, __setangle_around_x, None, "gets or sets the angle of a vector in the YZ plane")
 
	def get_angle_around_y(self):
		if (self.get_length_sqrd() == 0):
			return 0
		return math.degrees(math.atan2(self.x, self.z))
	def __setangle_around_y(self, angle_degrees):
		self.z = math.sqrt(self.z**2 + self.x**2)
		self.x = 0
		self.rotate_around_y(angle_degrees)
	angle_around_y = property(get_angle_around_y, __setangle_around_y, None, "gets or sets the angle of a vector in the ZX plane")
 
	def get_angle_between(self, other):
		v1 = self.normalized()
		v2 = Vec3d(other)
		v2.normalize_return_length()
		return math.degrees(math.acos(v1.dot(v2)))
			
	def normalized(self):
		length = self.length
		if length != 0:
			return self/length
		return Vec3d(self)
 
	def normalize_return_length(self):
		length = self.length
		if length != 0:
			self.x /= length
			self.y /= length
			self.z /= length
		return length
 
	def dot(self, other):
		return float(self.x*other[0] + self.y*other[1] + self.z*other[2])
		
	def get_distance(self, other):
		return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2 + (self.z - other[2])**2)
		
	def get_dist_sqrd(self, other):
		return (self.x - other[0])**2 + (self.y - other[1])**2 + (self.z - other[2])**2
		
	def projection(self, other):
		other_length_sqrd = other[0]*other[0] + other[1]*other[1] + other[2]*other[2]
		projected_length_times_other_length = self.dot(other)
		return other*(projected_length_times_other_length/other_length_sqrd)
	
	def cross(self, other):
		return Vec3d(self.y*other[2] - self.z*other[1], self.z*other[0] - self.x*other[2], self.x*other[1] - self.y*other[0])
	
	def interpolate_to(self, other, range):
		return Vec3d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range, self.z + (other[2] - self.z)*range)
	
	def convert_to_basis(self, x_vector, y_vector, z_vector):
		return Vec3d(self.dot(x_vector)/x_vector.get_length_sqrd(),
			self.dot(y_vector)/y_vector.get_length_sqrd(),
			self.dot(z_vector)/z_vector.get_length_sqrd())
 
	def __getstate__(self):
		return [self.x, self.y, self.z]
		
	def __setstate__(self, dict):
		self.x, self.y, self.z = dict
		
########################################################################
## Unit Testing														  ##
########################################################################
if __name__ == "__main__":
 
	import unittest
	import pickle
 
	####################################################################
	class UnitTestVec3d(unittest.TestCase):
	
		def setUp(self):
			pass
		
		def testCreationAndAccess(self):
			v = Vec3d(111,222,333)
			self.assert_(v.x == 111 and v.y == 222 and v.z == 333)
			v.x = 333
			v[1] = 444
			v.z = 555
			self.assert_(v[0] == 333 and v[1] == 444 and v[2] == 555)
 
		def testMath(self):
			v = Vec3d(111,222,333)
			self.assertEqual(v + 1, Vec3d(112,223,334))
			self.assert_(v - 2 == [109,220,331])
			self.assert_(v * 3 == (333,666,999))
			self.assert_(v / 2.0 == Vec3d(55.5, 111, 166.5))
			self.assert_(v / 2 == (55, 111, 166))
			self.assert_(v ** Vec3d(2,3,2) == [12321, 10941048, 110889])
			self.assert_(v + [-11, 78, 67] == Vec3d(100, 300, 400))
			self.assert_(v / [11,2,9] == [10,111,37])
 
		def testReverseMath(self):
			v = Vec3d(111,222,333)
			self.assert_(1 + v == Vec3d(112,223,334))
			self.assert_(2 - v == [-109,-220,-331])
			self.assert_(3 * v == (333,666,999))
			self.assert_([222,999,666] / v == [2,4,2])
			self.assert_([111,222,333] ** Vec3d(2,3,2) == [12321, 10941048, 110889])
			self.assert_([-11, 78,67] + v == Vec3d(100, 300, 400))
 
		def testUnary(self):
			v = Vec3d(111,222,333)
			v = -v
			self.assert_(v == [-111,-222,-333])
			v = abs(v)
			self.assert_(v == [111,222,333])
 
		def testLength(self):
			v = Vec3d(1,4,8)
			self.assert_(v.length == 9)
			self.assert_(v.get_length_sqrd() == 81)
			self.assert_(v.normalize_return_length() == 9)
			self.assert_(v.length == 1)
			v.length = 9
			self.assert_(v == Vec3d(1,4,8))
			v2 = Vec3d(10, -2, 12)
			self.assert_(v.get_distance(v2) == (v - v2).get_length())
			
		def testAngles(self):			 
			v = Vec3d(0, 3, -3)
			self.assertEquals(v.angle_around_y, 180)
			self.assertEquals(v.angle_around_x, -45)
			self.assertEquals(v.angle_around_z, 90)
 
			v2 = Vec3d(v)
			v.rotate_around_x(-90)
			self.assertEqual(v.get_angle_between(v2), 90)
 
			v = Vec3d(v2)
			v.rotate_around_y(-90)
			self.assertAlmostEqual(v.get_angle_between(v2), 60)
 
			v = Vec3d(v2)
			v.rotate_around_z(-90)
			self.assertAlmostEqual(v.get_angle_between(v2), 60)
 
			v2.angle_around_z -= 90
			self.assertEqual(v.length, v2.length)
			self.assertEquals(v2.angle_around_z, 0)
			self.assertEqual(v2, [3, 0, -3])
			self.assert_((v - v2).length < .00001)
			self.assertEqual(v.length, v2.length)
			v2.rotate_around_y(300)
			self.assertAlmostEquals(v.get_angle_between(v2), 60)
			v2.rotate_around_y(v2.get_angle_between(v))
			angle = v.get_angle_between(v2)
			self.assertAlmostEquals(v.get_angle_between(v2), 0)	 
 
		def testHighLevel(self):
			basis0 = Vec3d(5.0, 0, 0)
			basis1 = Vec3d(0, .5, 0)
			basis2 = Vec3d(0, 0, 3)
			v = Vec3d(10, 1, 6)
			self.assert_(v.convert_to_basis(basis0, basis1, basis2) == [2, 2, 2])
			self.assert_(v.projection(basis0) == (10, 0, 0))
			self.assert_(basis0.dot(basis1) == 0)
			
		def testCross(self):
			lhs = Vec3d(1, .5, 3)
			rhs = Vec3d(4, 6, 1)
			self.assert_(lhs.cross(rhs) == [-17.5, 11, 4])
			
		def testComparison(self):
			int_vec = Vec3d(3, -2, 4)
			flt_vec = Vec3d(3.0, -2.0, 4.0)
			zero_vec = Vec3d(0, 0, 0)
			self.assert_(int_vec == flt_vec)
			self.assert_(int_vec != zero_vec)
			self.assert_((flt_vec == zero_vec) == False)
			self.assert_((flt_vec != int_vec) == False)
			self.assert_(int_vec == (3, -2, 4))
			self.assert_(int_vec != [0, 0, 0])
			self.assert_(int_vec != 5)
			self.assert_(int_vec != [3, -2, 4, 15])
		
		def testInplace(self):
			inplace_vec = Vec3d(5, 13, 17)
			inplace_ref = inplace_vec
			inplace_src = Vec3d(inplace_vec)
			inplace_vec *= .5
			inplace_vec += .5
			inplace_vec /= (3, 6, 9)
			inplace_vec += Vec3d(-1, -1, -1)
			alternate = (inplace_src*.5 + .5)/Vec3d(3, 6, 9) + [-1, -1, -1]
			self.assertEquals(inplace_vec, inplace_ref)
			self.assertEquals(inplace_vec, alternate)
		
		def testPickle(self):
			testvec = Vec3d(5, .3, 8.6)
			testvec_str = pickle.dumps(testvec)
			loaded_vec = pickle.loads(testvec_str)
			self.assertEquals(testvec, loaded_vec)
	
	####################################################################
	unittest.main()
 
	######################################################################## 
spotlight

 
our projects
pygame.org welcomes all python game, art, music, sound, video and multimedia projects. If they use pygame or not.
 
recent releases
Nov 13, 2014

Nov 11, 2014


Nov 10, 2014

Nov 9, 2014

Nov 6, 2014


Nov 4, 2014

Nov 3, 2014

Oct 28, 2014

Oct 21, 2014

Oct 16, 2014

... more!
 
for pygame related questions, comments, and suggestions, please see help (lists, irc)