1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 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:
            self.x = x
            self.y = y
        else:
            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)