``` 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) ```