Circle class and tiltedRect class updated

This commit is contained in:
2013-06-14 07:59:48 +05:30
parent 5284821617
commit d1f4cc61cc
2 changed files with 293 additions and 32 deletions
+2 -1
View File
@@ -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,6 +24,7 @@ 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
+290 -30
View File
@@ -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 )<self.radius
def is_inside_mutual(self,another_circle):
''' Checks if this circle is inside another_circle and also the opposite.
''' Checks if this circle is fully inside another_circle and also the opposite.
2x Faster than using is_inside for both of them. '''
return (distance( self.center, another_circle.center ) < max( self.radius, another_circle.radius) )
@@ -392,27 +390,285 @@ class Circle(object):
else:
return True
# union - do later. needs to be a circle *within* which the union of the bounding box of all the circles can be fit.
## def union(self,new_circle):
## ''' Returns a new circle which is the union of these two circles '''
## temp_var=self.__union((self.center,self.radius),(new_circle.center,new_circle.radius))
## return Circle(temp_var[0],temp_var[1])
##
## def union_ip(self,new_circle):
## ''' Turns this circle into a circle which is a union of these two circles. '''
## self.center,self.radius=self.__union((self.center,self.radius),(new_circle.center,new_circle.radius))
##
## def __unionall(self,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. '''
## temp_union= (self.center,self.radius)
## for temp_circle in seq_circle:
## temp_union = self.__union(temp_union,(temp_circle.center,temp_circle.radius))
## return temp_union
## def unionall(self,seq_circle): #seq_circle: sequence of circles
## temp_union=self.__unionall(seq_circle)
## return Circle(temp_union[0],temp_union[1])
@classmethod
def get_circumcircle_from_rect(cls,another_rect):# create a circle with same center and radius=sqrt(w^2+h^2)
import math
return cls(another_rect.center, math.sqrt(another_rect.width**2+another_rect.height**2)/2)
@classmethod
def get_incircle_from_rect(cls,another_rect): # radius of incircle is same as the min of width and height (as it's not a square)
import math
return cls(another_rect.center,min(another_rect.width,another_rect.height))
# union -needs to be a circle *within* which the union of the bounding box of all the circles can be fit.
# warning about all the union functions: The circles returned are not necessarily the most optimum union circle, but it will surely contain the others.
def _union(self,circle_1,circle_2):
''' self.__union((new_center_x,new_center_y),new_radius) -> ([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.