import math pi2 = math.pi * 2 def rotatePoint(point, angle): """Assuming 0,0 is the center, rotate the given point around it.""" x,y = point r = math.sqrt(x**2 + y**2) if r == 0: return 0, 0 theta = math.acos(x/r) if y < 0: theta = -theta theta = theta + angle return int(round(r*math.cos(theta))), int(round(r*math.sin(theta))) def rotatePoly(points, angle): """Rotate the given list of points around 0,0 by angle.""" return [ rotatePoint(point, angle) for point in points ] def displace(point, disp, limits): """Displace point by disp, wrapping around limits.""" x = (point[0] + disp[0]) while x >= limits[0]: x = x - limits[0] while x < 0: x = x + limits[0] y = (point[1] + disp[1]) while y >= limits[1]: y = y - limits[1] while y < 0: y = y + limits[1] return x,y def displacePoly(points, disp, limits, coordSequence=False): """Displace each point (x,y) in 'points' by 'disp' (x,y). The limits of the drawing space are assumed to be at x=0, y=0 and x=limits[0], y=limits[1]. If the poly overlaps the edge of the drawing space, the poly is duplicated on each side. @param coordSequence: If true, the coordinates are returned as a sequence - x1, y1, x2, y2, ... This is need by some PIL drawing commands. @returns: A list of polys, displaced by disp """ xDup = 0; yDup = 0 maxX, maxY = limits basePoints = [] for point in points: x,y = int(point[0] + disp[0]), int(point[1] + disp[1]) # Check if duplication is needed on each axis if x > maxX: # If this is negative, then we need to duplicate in the negative # direction. xDup = -1 elif x < 0: xDup = 1 if y > maxY: yDup = -1 elif y < 0: yDup = 1 basePoints.append( (x,y) ) polys = [basePoints] if xDup: polys.append([(x + maxX*xDup, y) for x,y in basePoints] ) if yDup: polys.append([(x, maxY*yDup + y) for x,y in basePoints] ) if xDup and yDup: polys.append([(x+maxX*xDup, maxY*yDup+y) for x,y in basePoints]) # Switch coordinates to sequence mode. # (x1, y1, x2, y2) instead of ((x1, y1), (x2, y2)) if coordSequence: seqPolys = [] for poly in polys: points = [] for point in poly: points.extend(point) seqPolys.append(points) polys = seqPolys return polys def polar2cart(r, theta): """Return the cartesian coordinates for r, theta.""" x = r*math.cos(theta) y = r*math.sin(theta) return x,y def minShift(center, point, limits): """Get the minimum distances between the two points, given that the board wraps at the givin limits.""" dx = point[0] - center[0] if dx < -limits[0]/2.0: dx = point[0] + limits[0] - center[0] elif dx > limits[0]/2.0: dx = point[0] - (center[0] + limits[0]) dy = point[1] - center[1] if dy < - limits[1]/2.0: dy = point[1] + limits[1] - center[1] elif dy > limits[1]/2.0: dy = point[1] - (limits[1] + center[1]) return dx, dy def relativePolar(center, point, limits): """Returns the angle, from zero, to the given point assuming this center is the origin. Take into account wrapping round the limits of the board. @returns: r, theta """ dx, dy = minShift(center, point, limits) r = math.sqrt(dx**2 + dy**2) theta = math.acos(dx/r) if dy < 0: theta = pi2 - theta return r, theta def reduceAngle(angle): """Reduce the angle such that it is in 0 <= angle < 2pi""" return angle % pi2 def angleDiff(angle1, angle2): """Returns the difference between the two angles. They are assumed to be in radians, and must be in the range 0 <= angle < 2*pi. @raises AssertionError: The angles given must be in the range 0 <= angle < 2pi @returns: The minimum distance between the two angles; The distance is negative if angle2 leads angle1 (clockwise).. """ ret = (angle2 - angle1) % pi2 if ret > math.pi: ret -= pi2 return ret def getDist(point1, point2): """Returns the distance between point1 and point2.""" dx = point2[0] - point1[0] dy = point2[1] - point1[1] return math.sqrt(dx**2 + dy**2) def segmentCircleCollision(segment, center, radius): """Return True if the given circle touches the given line segment. @param segment: A list of two points [(x1,y1), (x2, y2)] that define the line segment. @param center: The center point of the circle. @param radius: The radius of the circle. @returns: True if the the circle touches the line segment, False otherwise. """ a = getDist(segment[0], center) c = getDist(segment[1], center) base = getDist(segment[0], segment[1]) # If we're close enough to the end points, then we're close # enough to the segment. if a < radius or c < radius: return True # First we find the are of the triangle formed by the line segment # and point. I use Heron's formula for the area. Using this, we'll # find the distance d from the point to the line. We'll later make # sure that the collision is with the line segment, and not just the # line. s = (a + c + base)/2 A = math.sqrt(s*(s - a)*(s - c)*(s - base)) d = 2*A/base # print s, a, c, A, d, radius # If the distance from the point to the line is more than the # target radius, this isn't a hit. if d > radius: return False # If the distance from an endpoint to the intersection between # our line segment and the line perpendicular to it that passes through # the point is longer than the line segment, then this isn't a hit. elif math.sqrt(a**2 - d**2) > base or \ math.sqrt(c**2 - d**2) > base: return False else: # The triangle is acute, that means we're close enough. return True