From d1f4cc61cc0cc04caaf97d9ec207b952dd128cc0 Mon Sep 17 00:00:00 2001 From: Abhas Bhattacharya Date: Fri, 14 Jun 2013 07:59:48 +0530 Subject: [PATCH] Circle class and tiltedRect class updated --- Upcoming implementations.markdown | 5 +- base.py | 320 +++++++++++++++++++++++++++--- 2 files changed, 293 insertions(+), 32 deletions(-) diff --git a/Upcoming implementations.markdown b/Upcoming implementations.markdown index 080ce13..d956613 100644 --- a/Upcoming implementations.markdown +++ b/Upcoming implementations.markdown @@ -2,7 +2,7 @@ Upcoming in pyFun ----------------- *** -1. Circle class ( __Doing__ ) and Angled rect class (subclassed from rect) +1. Circle class (__Doing__) and Tilted rect and Line class (subclassed from rect) 2. Image module (similar to GM sprites) + Make a custom image object which should support + Sprites and shapes, with good query hooks @@ -24,10 +24,11 @@ Upcoming in pyFun 4. Timer (alarm) module * Support arbitrary number of timers and also tracking them with id. 5. Add other keyboard and mouse module extras like GM +6. Room support 6. Add events mentioned in GM "Other" event dropdown. 7. Surfaces 8. Particles 9. Sound module 10. Path module 11. Timeline module -12. Highscore api +12. Highscore api \ No newline at end of file diff --git a/base.py b/base.py index b246049..f0fa198 100644 --- a/base.py +++ b/base.py @@ -2,6 +2,7 @@ from uniquelist import * # imports uniquelist import pygame from pygame.locals import * # imports the constants +from math import * #Mouse constants B_LEFT_CLICK=1 B_RIGHT_CLICK=3 @@ -296,6 +297,7 @@ class Circle(object): self.center=[x,y] self.radius=radius self.color=color + @property def center_x(self): return self.center[0] @@ -315,6 +317,9 @@ class Circle(object): width=self.radius*2 height=self.radius*2 return pygame.Rect(left, top, width, height) + # todo: remember about the colour. document - center is list unlike tuple in case of rect. + def __repr__(self): + return "{0.__class__}( center={0.center}, radius={0.radius}, color={0.color})".format(self) def copy(self): return Circle((self.center_x,self.center_y),self.radius,self.color) @@ -332,19 +337,12 @@ class Circle(object): def inflate_ip(self,inflate_radius): self.radius+=inflate_radius - def __union(self,((x1,y1),radius_1),((x2,y2),radius_2)): - ''' self.__union((new_center_x,new_center_y),new_radius) -> ([x,y],radius) which are the resultant values. ''' - temp_center_x = float(x1+x2)/2 # Does integer division, losses precision - temp_center_y = float(y1+y2)/2 - temp_radius = float( radius_1 + radius_2 + distance( (x1,y1), (x2,y2) ) )/2 - return ([temp_center_x,temp_center_y],temp_radius) - def is_inside(self,another_circle): - ''' Checks if this circle is inside another_circle ''' + ''' Checks if this circle is fully inside another_circle ''' return distance( self.center, another_circle.center ) ([x,y],radius) which are the resultant values. ''' + return self.circumcircle_from_rect(circle_1.bbox.union(circle_2.bbox)) + + def union(self,new_circle): + ''' Returns a new circle which is the union of these two circles ''' + return self._union(self,new_circle) + + def union_ip(self,new_circle): + ''' Turns this circle into a circle which is a union of these two circles. ''' + temp_circle=self._union(self,new_circle) + self.center, self.radius = temp_circle.center, temp_circle.radius + + def _unionall(self,circle_1,seq_circle): # list_circle_params : [((x1,y1),radius_1), ((x2,y2),radius_2), ...] + ''' returns ([x,y],radius) for the resultant union of all circles. + sequence_circle must be a sequence of circles. ''' + return self.circumcircle_from_rect(circle_1.bbox.unionall([temp_circle.bbox for temp_circle in seq_circle])) + + def unionall(self,seq_circle): #seq_circle: sequence of circles + return self._unionall(self,seq_circle) + + def unionall_ip(self,seq_circle): + ''' Turns this circle into a circle which is a union of this circles with all circles in seq_circle''' + temp_circle=self._unionall(self,seq_circle) + self.center, self.radius = temp_circle.center, temp_circle.radius + + # draw functions + def draw(self,surface,(x,y)=(None,None),color=None,width=1,use_antialiasing=False): + ''' Draws this Circle on surface. It must be of width and height greater than or equal to its bbox. + (x,y)->co-ordinates whether the center of the Circle should be drawn on the surface. + width argument doesnt work with antialiasing. width=0 makes it filled. + antialiasing may break in later versions as the backend pygame.gfxdraw is experimental. + color falls back first to Circle.color (if present) and then to blue color.''' + if (x,y)==(None,None): + (x,y)=self.center + if not color: + if self.color: + color=self.color + else: + color=pygame.Color("blue") + + if not use_antialiasing: + pygame.draw.circle(surface, color,(x,y), self.radius, width) + else: + pygame.gfxdraw.aacircle(surface, x, y, self.radius, color) + + def get_surface(self,surface=None,flags=0): + ''' Returns a new surface with dimensions of (Circle.bbox.width,Circle.bbox.width) and other properties as surface. + This Circle can be safely drawn on it.''' + if surface: + return pygame.Surface((self.bbox.width,self.bbox.height),flags,surface) + else: + return pygame.Surface((self.bbox.width,self.bbox.height),flags) + + def get_surface_drawn(self,(x,y)=(None,None),color=None,width=1,use_antialiasing=False,surface=None,flags=0): + ''' Returns a surface which has the tiltedRect drwan on it. ''' + temp_surf=self.get_surface(surface,flags) + self.draw(temp_surf,(x,y),color,width,use_antialiasing) + return temp_surf + # end of Circle class + #start of tiltedRect class +class tiltedRect(pygame.Rect): + # make a metaclass which wraps a lot of functions just like move is defined below. + # functions to be wrapped - move,inflate + # Think about them - clamp,clip,union,fit,contains,collide + # the metaclass can probably also change the __str__ of the class. + # About drawing - use pygame.transform.rotate to get a rotated surface to be drawed. + def __init__(self,(left,top),(width,height),angle,centered_coords=False,color=None): + ''' Angle in degrees. + centered_coords -> whether the (left,top) is actually intended to be (center_x,center_y) or not. + I.e. if centered_coords==True the (left,top) co-ordinates are used as (center_x,center_y) co-ordinates of the tiltedRect''' + self.angle=angle + self.color=color + if centered_coords==True: + left=left-width/2 + top=top-height/2 + super(tiltedRect,self).__init__((left,top),(width,height)) + def __copy__(self): + return tiltedRect((self.left,self.top),(self.width,self.height),self.angle) + def __repr__(self): + return "{0.__class__.__name__}((top={0.top}, left={0.left}), (width={0.width}, height={0.height}), angle={0.angle}, color={0.color})".format(self) + @classmethod + def create_from_rect(cls,another_rect,angle): + return cls((another_rect.left,another_rect.top),(another_rect.width,another_rect.height),angle) + def get_rect(self): + return pygame.Rect((left,top),(width,height)) + def move(self,x,y): + return self.create_from_rect(super(tiltedRect,self).move(x,y),self.angle) + def inflate(self,x,y): + return self.create_from_rect(super(tiltedRect,self).inflate(x,y),self.angle) + def normalize(self): # not sure what this one does - ask others + return self.create_from_rect(super(tiltedRect,self).normalize(),self.angle) + def rotate(self,some_angle): + temp_return=self.__copy__() + temp_return.angle+=some_angle + return temp_return + def rotate_ip(self,some_angle): + self.angle+=some_angle + + def rotate_point_relative(self,x,y,theta=None):#generalise the parameters to accept x1,y1,x2,y2,rot_angle + ''' x,y -> Absolute co-ords of the point to be rotated wrt to the rectangles center. self.centerx,self.centery are internally substracted from them. + theta -> angle (in degrees) by which the point is to be rotated. Default value is self.angle. + Returns absolute positions of the points after rotation. ''' + if theta==None: + theta=self.angle + return self.rotate_point(x,y,self.centerx,self.centery,theta) + + @classmethod + def rotate_point(cls,x,y,origin_x,origin_y,theta): # to be allowed as rotate_point(class) + ''' x,y -> Absolute co-ords of the point to be rotated. origin_x,origin_y are internally substracted from them. + (origin_x,origin_y) -> Absolute co-ords of the origin wrt to which the point will be rotated + theta -> angle (in degrees) by which the point is to be rotated. + Returns absolute positions of the points after rotation. ''' + import math + x_diff=x-origin_x + y_diff=y-origin_y + y_diff=-y_diff # -y_diff because of the inverted nature of y co-ordinate system in pygame + r=math.sqrt(x_diff**2+y_diff**2) + initial_angle=math.atan2(y_diff,x_diff) + final_angle=initial_angle+math.radians(theta) + rotated_x_diff=r*math.cos(final_angle) + rotated_y_diff=r*math.sin(final_angle) + rotated_y_diff = -rotated_y_diff # - used for same reason as above + rotated_x = rotated_x_diff + origin_x + rotated_y = rotated_y_diff + origin_y + return (rotated_x,rotated_y) + + def rotate_point_polar(self,r,initial_angle,theta=None): + ''' theta and initial_angle in radians. + initial_angle ->angle r makes with horizontal. + theta -> angle to be rotated by. ''' + if theta==None: + theta=self.angle + final_angle=initial_angle+theta + rotated_x=r*math.cos(final_angle) + rotated_y=r*math.sin(final_angle) + return (rotated_x,rotated_y) + + @property + def bottomleft_rotated(self): + temp=self.bottomleft + return self.rotate_point_relative(temp[0],temp[1]) + @property + def topleft_rotated(self): + temp=self.topleft + return self.rotate_point_relative(temp[0],temp[1]) + @property + def bottomright_rotated(self): + temp=self.bottomright + return self.rotate_point_relative(temp[0],temp[1]) + @property + def topright_rotated(self): + temp=self.topright + return self.rotate_point_relative(temp[0],temp[1]) + @property + def midleft_rotated(self): + temp=self.midleft + return self.rotate_point_relative(temp[0],temp[1]) + @property + def midright_rotated(self): + temp=self.midright + return self.rotate_point_relative(temp[0],temp[1]) + @property + def midtop_rotated(self): + temp=self.midtop + return self.rotate_point_relative(temp[0],temp[1]) + @property + def midbottom_rotated(self): + temp=self.midbottom + return self.rotate_point_relative(temp[0],temp[1]) + + # related to bounding box + @property + def bbox(self): + list_x=[self.topleft_rotated[0],self.topright_rotated[0],self.bottomleft_rotated[0],self.bottomright_rotated[0]] + list_y=[self.topleft_rotated[1],self.topright_rotated[1],self.bottomleft_rotated[1],self.bottomright_rotated[1]] + min_x=floor(min(list_x)) + max_x=ceil(max(list_x)) + min_y=floor(min(list_y)) + max_y=floor(max(list_y)) + return pygame.Rect((min_x,min_y),(max_x-min_x,max_y-min_y)) + # corners + @property + def corners(self): + ''' List of actual (rotated) corners. ''' + return [self.topleft_rotated,self.topright_rotated,self.bottomright_rotated,self.bottomleft_rotated] + @property + def corners_relative(self): + return [(temp[0]-self.centerx,temp[1]-self.centery) for temp in self.corners] + + # circles related to the rect + def get_circumcircle(self): + Circle.get_circumcircle_from_rect(self.bbox) + def get_incircle(self): + Circle.get_incircle_from_rect(self.bbox) + + # collision events + def collide_point(self,*args): + ''' collidepoint(x,y) -> + collidepoint((x,y)) -> ''' + temp_len=len(args) + temp_x=None + temp_y=None + if temp_len==2: + temp_x=args[0] + temp_y=args[1] + elif temp_len==1: + if len(args[0])==2: + temp_x,temp_y=args[0] + else: + raise TypeError("Arguments are of incorrect type") + else: + raise TypeError("Arguments are of incorrect type") + if temp_x==None or temp_y==None: + raise TypeError("argument must contain two numbers") + return pygame.Rect((self.left,self.top),(self.width,self.height)).collidepoint(self.rotate_point_relative(temp_x,temp_y)) + + def collide_rect(self,rect): + ''' Tests if it collides with rect, which should be instance of rect class. ''' + # First test:whether bbox collides with rect. If not, no collision. + if not self.bbox.colliderect(rect): + return False + else: + # Todo: Else sure test: Need to do line class first. Test if any line of titedRect collides with any line of (instead, the whole) rect. + pass + + # draw functions + def draw(self,surface,(x,y)=(None,None),color=None,width=1,use_antialiasing=False,blend=True): + ''' Draws this tiltedRect on surf_to_draw_on. It must be of width and height greater or equal to its bbox. + (x,y)->co-ordinates whether the lefttop point of its bbox should be drawn on the surface. + width argument doesnt work with antialiasing. width=0 makes it filled. + blend argument works only with antialiasing. The boolean blend argument set to true will blend the shades with existing shades instead of overwriting them. + color falls back first to tiltedRect.color (if present) and then to blue color.''' + if (x,y)==(None,None): + (x,y)=(0,0) + if not color: + if self.color: + color=self.color + else: + color=pygame.Color("blue") + iter_corners=[(temp[0]+x,temp[1]+y) for temp in self.corners_relative] + if not use_antialiasing: + pygame.draw.polygon(surface, color, iter_corners, width) + else: + pygame.draw.aalines(surface, color, True, iter_corners, blend) + def get_surface(self,surface=None,flags=0): + ''' Returns a new surface with dimensions of (tiltedRect.bbox.width,tiltedRect.bbox.width) and other properties as surface. + This tiltedRect can be safely drawn on it.''' + if surface: + return pygame.Surface((self.bbox.width,self.bbox.height),flags,surface) + else: + return pygame.Surface((self.bbox.width,self.bbox.height),flags) + def get_surface_drawn(self,(x,y)=(None,None),color=None,width=1,use_antialiasing=False,blend=True,surface=None,flags=0): + ''' Returns a surface which has the tiltedRect drwan on it. ''' + temp_surf=self.get_surface(surface,flags) + self.draw(temp_surf,(x,y),color,width,use_antialiasing,blend) + return temp_surf + + def collide_tiltedRect(self,another_tiltedRect): + '''Checks whether this tiltedRect collides with another_titltedrect, which must be a instance of titledRect. ''' + # Rotates both tiltedRect by -another_tiltedRect.angle. So, another_tiltedRect becomes a Rect called temp_Rect. + # Then check if the rotated version of this tiltedRect (temp_tiltedRect) collides with the obtained Rect. + temp_tiltedRect=self.rotate(-another_tiltedRect.angle) + temp_Rect=another_tiltedRect.get_rect() + return temp_tiltedRect.colliderect(temp_Rect) + # functions outside all classes def get_event_list(event_types=None,further_check_variable_name=None,further_check_value=None,list_to_get=None):#list_to_get is attached to a constant list here ''' Get the required events from the event_list. @@ -520,9 +776,13 @@ def operation_on_lists(operation,list1,list2=None,variable_name=None): else: raise ValueError("Invalid operation name. Valid operations are UNION, INTERSECTION, DIFFERENCE.") # end of operation_on_lists() - +#Todo: see below +# 1. Allow rect-like and circle-like constructs wherever rect and circle are expected. +# 2. Do all sort of type-checking. # Design tips I will use later # IMP: always derive classes from object. Else, in python 2.x, they become old-style class which sucks. +# use tuples instead of lists wherver possible because they are usually faster to create +# learn about generators # named tuples, arrays are better. use more of them. named tuples can be accessed. # refactor code into proper modules as-well-as use better named functions and variables # use enumerate() for getting the index and value of elements while looping through lists. This mistake done atleast 1 time.