# Filename: vec2d_jdm.py
# Author: James D. Miller; Gustavus Adolphus College.
# This Vec2D class is based on the Vec2d class found here:
# http://pygame.org/wiki/2DVectorClass
import math
class Vec2D:
# Components of the vector can be input as individual arguments or as
# a tuple pair in one argument.
def __init__(self, x_or_pair, y = None, int_flag = "not_int"):
if y == None:
self.x = x_or_pair[0]
self.y = x_or_pair[1]
else:
self.x = x_or_pair
self.y = y
if int_flag == "int":
self.x = int(round(self.x))
self.y = int(round(self.y))
else:
self.x = float(self.x)
self.y = float(self.y)
# This establishes the format of the output string if you print a vector (an object instance).
def __str__(self):
return 'Vec2D(%s, %s)' % (self.x, self.y)
#============================================================================
# This section defines methods for vector arithmetic and comparison.
# Operator overloading is established here.
# Addition
def add_vector(self, vec_B):
return Vec2D(self.x + vec_B.x, self.y + vec_B.y)
def __add__(self, vec_B):
return Vec2D(self.x + vec_B.x, self.y + vec_B.y)
# Subtraction
def sub_vector(self, vec_B):
return Vec2D(self.x - vec_B.x, self.y - vec_B.y)
def __sub__(self, vec_B):
return Vec2D(self.x - vec_B.x, self.y - vec_B.y)
# Scaling operations. Note that the operator overloading cases
# must have the vector preceding the scaling factor!
# e.g. vector * 1.2 or vector / 3.2, but not 1.2 * vector
def scale_vector(self, scale_factor):
return Vec2D( self.x * scale_factor, self.y * scale_factor)
def __mul__(self, scale_factor):
return Vec2D( self.x * scale_factor, self.y * scale_factor)
def __div__(self, scale_factor):
return Vec2D( self.x / scale_factor, self.y / scale_factor)
# Comparisons.
def equal(self, vec_B):
return (self.x == vec_B.x) and (self.y == vec_B.y)
def not_equal(self, vec_B):
return (self.x != vec_B.x) or (self.y != vec_B.y)
#============================================================================
# Determine the scaler length of the vector. This uses the
# squareroot operation.
def length(self):
return (self.x*self.x + self.y*self.y)**0.5
# Length squared can sometimes be used in substitution for the scaler length
# of a vector. Length squared is much faster to calculate since there is
# no squareroot operation.
def length_squared(self):
return (self.x*self.x + self.y*self.y)
# Returns a vector in the same direction as the
# original but with a length of 1 (unit length).
def normal(self):
#return self.scale_vector( 1/ self.length())
return self / self.length()
# Returns a vector in the same direction as the
# original but with a length equal to the target magnitude.
def set_magnitude(self, magnitude_target):
return self.normal() * target_magnitude
# Dot product with another vector.
def dot(self, vec_B):
return (self.x * vec_B.x) + (self.y * vec_B.y)
# The vector component of the self vector along the direction of the B vector.
# (Refer to the drawings in the PDF.)
def projection_onto(self, vec_B):
vB_dot_vB = vec_B.dot(vec_B)
if (vB_dot_vB > 0):
return vec_B * ( self.dot(vec_B) / vB_dot_vB )
else:
# If vec_B has zero magnitude, return a zero magnitude vector. In this
# case, the dot product of the two vectors will be zero. This leaves the
# projection undetermined; vec_B * (0/0). It would be appropriate to
# return a None value here! But the zero magnitude vector is handy for
# dealing with the spring-anchored pucks (because the separation distance
# between the puck and the pinning point eventually goes to zero as the
# disturbed puck loses energy). So again, it would be better to return a
# None here and catch this in the code following the projection.
return self * 0
# Rotate 90 degrees counterclockwise.
def rotate90(self):
return Vec2D(-self.y, self.x)
# Flip it around (reverse the direction of the vector).
def rotate180(self):
return Vec2D(-self.x, -self.y)
# Rotate (change the direction of) the original vector by a specified number of degrees.
# (Original vector rotated by angle_degrees.)
def rotated(self, angle_degrees, sameVector=False):
angle_radians = math.radians(angle_degrees)
cos = math.cos(angle_radians)
sin = math.sin(angle_radians)
# The rotation transformation.
x = self.x * cos - self.y * sin
y = self.x * sin + self.y * cos
if sameVector:
# Modify the original vector.
self.x = x
self.y = y
else:
# A new vector object
return Vec2D(x, y)
# Set the direction of the vector to a specific angle.
def set_angle(self, angle_degrees):
self.x = self.length()
self.y = 0
return self.rotated(angle_degrees)
# Determine the angle that this vector makes with the x axis. Measure
# counterclockwise from the x axis.
def get_angle(self):
if (self.length_squared() == 0):
return 0
return math.degrees(math.atan2(self.y, self.x))
# Determine the angle between two vectors.
def get_angle_between(self, vec_B):
cross = self.x*vec_B.y - self.y*vec_B.x #= ABsin(theta)
dot = self.x*vec_B.x + self.y*vec_B.y #= ABcos(theta)
# Use the two parameter input (y,x) form of the arctan. This is safer than
# taking the arctan of the cross/dot which can be zero in the denominator.
return math.degrees(math.atan2(cross, dot))
# Return a tuple containing the two components of the vector.
def tuple(self):
return (self.x, self.y)