def arc_to(self, endpoint, center=None, start_slant=None, end_slant=None): """ Draw an arc ending at the specified point, starting tangent to the current position and heading. """ if points_equal(self._position, endpoint): return # Handle unspecified center. # We need to find the center of the arc, so we can find its radius. The # center of this arc is uniquely defined by the intersection of two # lines: # 1. The first line is perpendicular to the pen heading, passing # through the pen position. # 2. The second line is the perpendicular bisector of the pen position # and the target arc end point. v_pen = self._vector() v_perp = vec.perp(self._vector()) v_chord = vec.vfrom(self._position, endpoint) if center is None: midpoint = vec.div(vec.add(self._position, endpoint), 2) v_bisector = vec.perp(v_chord) center = intersect_lines( self._position, vec.add(self._position, v_perp), midpoint, vec.add(midpoint, v_bisector), ) # Determine true start heading. This may not be the same as the # original pen heading in some circumstances. assert not points_equal(center, self._position) v_radius_start = vec.vfrom(center, self._position) v_radius_perp = vec.perp(v_radius_start) if vec.dot(v_radius_perp, v_pen) < 0: v_radius_perp = vec.neg(v_radius_perp) start_heading = math.degrees(vec.heading(v_radius_perp)) self.turn_to(start_heading) # Refresh v_pen and v_perp based on the new start heading. v_pen = self._vector() v_perp = vec.perp(self._vector()) # Calculate the arc angle. # The arc angle is double the angle between the pen vector and the # chord vector. Arcing to the left is a positive angle, and arcing to # the right is a negative angle. arc_angle = 2 * math.degrees(vec.angle(v_pen, v_chord)) radius = vec.mag(v_radius_start) # Check which side of v_pen the goes toward. if vec.dot(v_chord, v_perp) < 0: arc_angle = -arc_angle radius = -radius self._arc( center, radius, endpoint, arc_angle, start_slant, end_slant, )
def intersect_lines(a, b, c, d, segment=False): """ Find the intersection of lines a-b and c-d. If the "segment" argument is true, treat the lines as segments, and check whether the intersection point is off the end of either segment. """ # Reference: # http://geomalgorithms.com/a05-_intersect-1.html u = vec.vfrom(a, b) v = vec.vfrom(c, d) w = vec.vfrom(c, a) u_perp_dot_v = vec.dot(vec.perp(u), v) if float_equal(u_perp_dot_v, 0): return None # We have collinear segments, no single intersection. v_perp_dot_w = vec.dot(vec.perp(v), w) s = v_perp_dot_w / u_perp_dot_v if segment and (s < 0 or s > 1): return None u_perp_dot_w = vec.dot(vec.perp(u), w) t = u_perp_dot_w / u_perp_dot_v if segment and (t < 0 or t > 1): return None return vec.add(a, vec.mul(u, s))
def set_slants(self, start_slant, end_slant): if start_slant is not None: start_slant = Heading(start_slant) if end_slant is not None: end_slant = Heading(end_slant) self.start_slant = start_slant self.end_slant = end_slant # Intersect the slant lines with the left and right offset lines # to find the corners. line_left = self.offset_line_left() line_right = self.offset_line_right() # Start corners. if start_slant is None: v_slant = vec.perp(self._vector()) else: v_slant = vec.from_heading(start_slant.rad) a = self.a b = vec.add(self.a, v_slant) left = intersect_lines(a, b, line_left[0], line_left[1]) right = intersect_lines(a, b, line_right[0], line_right[1]) if left is None or right is None: self.start_joint_illegal = True else: self.a_left = Point(*left) self.a_right = Point(*right) # End corners. if end_slant is None: v_slant = vec.perp(self._vector()) else: v_slant = vec.from_heading(end_slant.rad) a = self.b b = vec.add(self.b, v_slant) left = intersect_lines(a, b, line_left[0], line_left[1]) right = intersect_lines(a, b, line_right[0], line_right[1]) if left is None or right is None: self.end_joint_illegal = True else: self.b_left = Point(*left) self.b_right = Point(*right) # Done, make sure we didn't cross self.check_degenerate_segment()
def interpret_controls(self): if not hasattr(self.input, 'turn_direction'): # Interpret controls using x and y axis to pick a target direction, # then translate into turn direction and thrust. # If the player is pushing towards a direction and not braking, # then it is thrusting. self.do_brake = self.input.brake self.turn_direction = 0 self.do_thrust = False self.intended_direction = (self.input.x_axis, -self.input.y_axis) if self.intended_direction != (0, 0): if not self.do_brake: self.do_thrust = True # Determine which direction we should turn to come closer to the # correct one. side = vec.dot(self.intended_direction, vec.perp(self.direction)) if (vec.angle(self.intended_direction, self.direction) < c.player_intended_turn_threshold): self.turn_direction = 0 elif side < 0: self.turn_direction = +1 elif side > 0: self.turn_direction = -1 else: # Interpret controls using thrust, brake, and turn direction. self.turn_direction = self.input.turn_direction self.do_brake = self.input.brake self.do_thrust = self.input.thrust and not self.do_brake
def arc_left( self, arc_angle, radius=None, center=None, start_slant=None, end_slant=None, ): if ( (radius is None and center is None) or (radius is not None and center is not None) ): raise TypeError('You must specify exactly one of center or radius.') arc_angle = Angle(arc_angle) # Create a radius vector, which is a vector from the arc center to the # current position. Subtract to find the center, then rotate the radius # vector to find the arc end point. if center is None: if arc_angle < 0: radius = -abs(radius) v_radius = vec.neg(vec.perp(self._vector(radius))) center = vec.sub(self._position, v_radius) elif radius is None: v_radius = vec.vfrom(center, self._position) radius = vec.mag(v_radius) if arc_angle < 0: radius = -radius endpoint = vec.add(center, vec.rotate(v_radius, arc_angle.rad)) self._arc( center, radius, endpoint, arc_angle, start_slant=start_slant, end_slant=end_slant, )
def interpret_controls(self): if not hasattr(self.input, 'turn_direction'): # Interpret controls using x and y axis to pick a target direction, # then translate into turn direction and thrust. # If the player is pushing towards a direction and not braking, # then it is thrusting. self.do_brake = self.input.brake self.turn_direction = 0 self.do_thrust = False self.intended_direction = (self.input.x_axis, -self.input.y_axis) if self.intended_direction != (0, 0): if not self.do_brake: self.do_thrust = True # Determine which direction we should turn to come closer to the # correct one. side = vec.dot(self.intended_direction, vec.perp(self.direction)) if ( vec.angle(self.intended_direction, self.direction) < c.player_intended_turn_threshold ): self.turn_direction = 0 elif side < 0: self.turn_direction = +1 elif side > 0: self.turn_direction = -1 else: # Interpret controls using thrust, brake, and turn direction. self.turn_direction = self.input.turn_direction self.do_brake = self.input.brake self.do_thrust = self.input.thrust and not self.do_brake
def collide_particles(p1, p2): restitution = p1.restitution * p2.restitution # Don't collide immovable particles. if p1.immovable and p2.immovable: return # Test if p1 and p2 are actually intersecting. if not intersect(p1, p2): return # If one particle is immovable, make it the first one. if not p1.immovable and p2.immovable: p1, p2 = p2, p1 # Vector spanning between the centers, normal to contact surface. v_span = vec.vfrom(p1.pos, p2.pos) # Split into normal and tangential components and calculate # initial velocities. normal = vec.norm(v_span) tangent = vec.perp(normal) v1_tangent = vec.proj(p1.velocity, tangent) v2_tangent = vec.proj(p2.velocity, tangent) p1_initial = vec.dot(p1.velocity, normal) p2_initial = vec.dot(p2.velocity, normal) # Don't collide if particles were actually moving away from each other, so # they don't get stuck inside one another. if p1_initial - p2_initial < 0: return # Handle immovable particles specially. if p1.immovable: p2_final = -p2_initial * restitution p2.velocity = vec.add( v2_tangent, vec.mul(normal, p2_final), ) return # Elastic collision equations along normal component. m1, m2 = p1.mass, p2.mass m1plusm2 = (m1 + m2) / restitution p1_final = (p1_initial * (m1 - m2) / m1plusm2 + p2_initial * (2 * m2) / m1plusm2) p2_final = (p2_initial * (m2 - m1) / m1plusm2 + p1_initial * (2 * m1) / m1plusm2) # Tangential component is unchanged, recombine. p1.velocity = vec.add( v1_tangent, vec.mul(normal, p1_final), ) p2.velocity = vec.add( v2_tangent, vec.mul(normal, p2_final), )
def intersect_circles(center1, radius1, center2, radius2): radius1 = abs(radius1) radius2 = abs(radius2) if radius2 > radius1: return intersect_circles(center2, radius2, center1, radius1) transverse = vec.vfrom(center1, center2) dist = vec.mag(transverse) # Check for identical or concentric circles. These will have either # no points in common or all points in common, and in either case, we # return an empty list. if points_equal(center1, center2): return [] # Check for exterior or interior tangent. radius_sum = radius1 + radius2 radius_difference = abs(radius1 - radius2) if (float_equal(dist, radius_sum) or float_equal(dist, radius_difference)): return [ vec.add(center1, vec.norm(transverse, radius1)), ] # Check for non intersecting circles. if dist > radius_sum or dist < radius_difference: return [] # If we've reached this point, we know that the two circles intersect # in two distinct points. # Reference: # http://mathworld.wolfram.com/Circle-CircleIntersection.html # Pretend that the circles are arranged along the x-axis. # Find the x-value of the intersection points, which is the same for both # points. Then find the chord length "a" between the two intersection # points, and use vector math to find the points. dist2 = vec.mag2(transverse) x = (dist2 - radius2**2 + radius1**2) / (2 * dist) a = ((1 / dist) * sqrt( (-dist + radius1 - radius2) * (-dist - radius1 + radius2) * (-dist + radius1 + radius2) * (dist + radius1 + radius2))) chord_middle = vec.add( center1, vec.norm(transverse, x), ) perp = vec.perp(transverse) return [ vec.add(chord_middle, vec.norm(perp, a / 2)), vec.add(chord_middle, vec.norm(perp, -a / 2)), ]
def rebound(self, normal, point=None, restitution=1): # Split into normal and tangential components. tangent = vec.perp(normal) v_tangent = vec.proj(self.velocity, tangent) v_normal = vec.proj(self.velocity, normal) # Invert normal component and recombine, with restitution. v_normal = vec.neg(v_normal) self.velocity = vec.add(v_tangent, vec.mul(v_normal, restitution)) # If the particle is partially inside the wall, move it out. if point is not None: v = vec.vfrom(point, self.pos) if vec.mag2(v) < self.radius ** 2: v = vec.norm(v, self.radius) self.pos = vec.add(point, v)
def collide_particles(p1, p2): restitution = p1.restitution * p2.restitution # Don't collide immovable particles. if p1.immovable and p2.immovable: return # Test if p1 and p2 are actually intersecting. if not intersect(p1, p2): return # If one particle is immovable, make it the first one. if not p1.immovable and p2.immovable: p1, p2 = p2, p1 # Vector spanning between the centers, normal to contact surface. v_span = vec.vfrom(p1.pos, p2.pos) # Split into normal and tangential components and calculate # initial velocities. normal = vec.norm(v_span) tangent = vec.perp(normal) v1_tangent = vec.proj(p1.velocity, tangent) v2_tangent = vec.proj(p2.velocity, tangent) p1_initial = vec.dot(p1.velocity, normal) p2_initial = vec.dot(p2.velocity, normal) # Don't collide if particles were actually moving away from each other, so # they don't get stuck inside one another. if p1_initial - p2_initial < 0: return # Handle immovable particles specially. if p1.immovable: p2_final = -p2_initial * restitution p2.velocity = vec.add(v2_tangent, vec.mul(normal, p2_final)) return # Elastic collision equations along normal component. m1, m2 = p1.mass, p2.mass m1plusm2 = (m1 + m2) / restitution p1_final = p1_initial * (m1 - m2) / m1plusm2 + p2_initial * (2 * m2) / m1plusm2 p2_final = p2_initial * (m2 - m1) / m1plusm2 + p1_initial * (2 * m1) / m1plusm2 # Tangential component is unchanged, recombine. p1.velocity = vec.add(v1_tangent, vec.mul(normal, p1_final)) p2.velocity = vec.add(v2_tangent, vec.mul(normal, p2_final))
def rebound(self, normal, point=None, restitution=1): # Split into normal and tangential components. tangent = vec.perp(normal) v_tangent = vec.proj(self.velocity, tangent) v_normal = vec.proj(self.velocity, normal) # Invert normal component and recombine, with restitution. v_normal = vec.neg(v_normal) self.velocity = vec.add( v_tangent, vec.mul(v_normal, restitution), ) # If the particle is partially inside the wall, move it out. if point is not None: v = vec.vfrom(point, self.pos) if vec.mag2(v) < self.radius**2: v = vec.norm(v, self.radius) self.pos = vec.add(point, v)
def intersect_circle_line(center, radius, line_start, line_end): """ Find the intersection of a circle with a line. """ radius = abs(radius) # First check whether the line is too far away, or if we have a # single point of contact. # Reference: # http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html r = vec.vfrom(center, line_start) v = vec.perp(vec.vfrom(line_start, line_end)) d = vec.proj(r, v) dist = vec.mag(d) if float_equal(dist, radius): # Single intersection point, because the circle and line are tangent. point = vec.add(center, d) return [point] elif dist > radius: return [] # Set up parametric equations for the line and the circle, and solve them. # Reference: # http://www.cs.cf.ac.uk/Dave/CM0268/PDF/circle_line_intersect_proof.pdf xc, yc = center x0, y0 = line_start x1, y1 = line_end line_x, line_y = (x1 - x0), (y1 - y0) # f, g dx, dy = (x0 - xc), (y0 - yc) a = line_x**2 + line_y**2 b = 2 * (line_x * dx + line_y * dy) c = dx**2 + dy**2 - radius**2 t0, t1 = quadratic_formula(a, b, c) return [ ( x0 + line_x * t0, y0 + line_y * t0, ), ( x0 + line_x * t1, y0 + line_y * t1, ), ]
def calc_rudder_force(self): # We continuously bring the direction of the player's movement to be # closer in line with the direction it is facing. target_velocity = vec.norm(self.direction, self.speed) force = vec.vfrom(self.velocity, target_velocity) if force == (0, 0): return (0, 0) # The strength of the rudder is highest when acting perpendicular to # the direction of movement. v_perp = vec.norm(vec.perp(self.velocity)) angle_multiplier = abs(vec.dot(v_perp, self.direction)) strength = self.speed * c.player_rudder_strength strength = min(strength, c.player_max_rudder_strength) strength *= angle_multiplier if strength == 0: return (0, 0) force = vec.norm(force, strength) return force
def partition(points, l1, l2, s=None): """ Partition a set of points by a line. The line is defined by l1, l2. The desired side of the line is given by the point s. If s is not given, return points to the right of the line. If eq is True, also include points on the line. >>> sorted(partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1), (2,0))) [(0, 0), (1, 0)] >>> sorted(partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1), (-2,0))) [(-1, 0), (0, 0)] >>> points = [(-2,2), (-1,0), (0,0), (1,0)] >>> sorted(partition(points, (-1,0), (0,1), (3,0))) [(-1, 0), (0, 0), (1, 0)] >>> sorted(partition(points, (-1,0), (0,1), (-3,0))) [(-2, 2), (-1, 0)] You can omit the argument "s" if you don't care. >>> sorted(partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1))) [(-1, 0), (0, 0)] """ if s is None: s = vec.add(l1, vec.perp(vec.vfrom(l1, l2))) if l1 == l2: raise ValueError('l1 equals l2') sign = sign_of(cmp_line(l1, l2, s)) if sign == 0: raise ValueError('s is on the line l1 l2') for p in points: c = cmp_line(l1, l2, p) if c == sign: yield p elif c == 0: yield p
def partition(points, l1, l2, s=None): """ Partition a set of points by a line. The line is defined by l1, l2. The desired side of the line is given by the point s. >>> partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1), (2,0))[0] {(1, 0)} >>> partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1), (-2,0))[0] {(-1, 0)} >>> points = [(-2,2), (-1,0), (0,0), (1,0)] >>> sorted(partition(points, (-1,0), (0,1), (3,0))[0]) [(0, 0), (1, 0)] >>> sorted(partition(points, (-1,0), (0,1), (-3,0))[0]) [(-2, 2)] You can omit the argument "s" if you don't care. >>> partition([(-1,0), (0,0), (1,0)], (0,1), (0,-1)) ({(-1, 0)}, {(1, 0)}) """ if s is None: s = vec.add(l1, vec.perp(vec.vfrom(l1, l2))) if l1 == l2: raise ValueError('l1 equals l2') sign = cmp_line(l1, l2, s) if sign == 0: raise ValueError('s is on the line l1 l2') forward = set() reverse = set() for p in points: c = cmp_line(l1, l2, p) if c == sign: forward.add(p) elif c == -sign: reverse.add(p) return forward, reverse
def intersect_circles(center1, radius1, center2, radius2): radius1 = abs(radius1) radius2 = abs(radius2) if radius2 > radius1: return intersect_circles(center2, radius2, center1, radius1) transverse = vec.vfrom(center1, center2) dist = vec.mag(transverse) # Check for identical or concentric circles. These will have either # no points in common or all points in common, and in either case, we # return an empty list. if points_equal(center1, center2): return [] # Check for exterior or interior tangent. radius_sum = radius1 + radius2 radius_difference = abs(radius1 - radius2) if ( float_equal(dist, radius_sum) or float_equal(dist, radius_difference) ): return [ vec.add( center1, vec.norm(transverse, radius1) ), ] # Check for non intersecting circles. if dist > radius_sum or dist < radius_difference: return [] # If we've reached this point, we know that the two circles intersect # in two distinct points. # Reference: # http://mathworld.wolfram.com/Circle-CircleIntersection.html # Pretend that the circles are arranged along the x-axis. # Find the x-value of the intersection points, which is the same for both # points. Then find the chord length "a" between the two intersection # points, and use vector math to find the points. dist2 = vec.mag2(transverse) x = (dist2 - radius2**2 + radius1**2) / (2 * dist) a = ( (1 / dist) * sqrt( (-dist + radius1 - radius2) * (-dist - radius1 + radius2) * (-dist + radius1 + radius2) * (dist + radius1 + radius2) ) ) chord_middle = vec.add( center1, vec.norm(transverse, x), ) perp = vec.perp(transverse) return [ vec.add(chord_middle, vec.norm(perp, a / 2)), vec.add(chord_middle, vec.norm(perp, -a / 2)), ]
def _width_vector(self): v = self._vector() v = vec.perp(v) v = vec.norm(v, self.width / 2) return v
def __init__(self, p1, p2, restitution=c.restitution_wall): self.p1 = p1 self.p2 = p2 self.restitution = restitution self.tangent = vec.vfrom(self.p1, self.p2) self.normal = vec.perp(self.tangent)