mirror of
https://github.com/bendtherules/pyFun.git
synced 2026-08-18 22:02:25 +00:00
444 lines
18 KiB
Python
444 lines
18 KiB
Python
import pygame
|
|
from math_utils import distance
|
|
import math
|
|
import base
|
|
import inspect
|
|
import sprite_types
|
|
from utils import remove_from_list, rect_to_pygame_rect
|
|
|
|
|
|
class Circle(object):
|
|
map_class = "Circle"
|
|
|
|
def __init__(self, center=(None, None), radius=None, color=None, container_obj=None):
|
|
if not (len(center) == 2):
|
|
raise AttributeError("Center must be (x,y)")
|
|
x = center[0]
|
|
y = center[1]
|
|
if (radius is None):
|
|
raise AttributeError("radius must be provided")
|
|
self.radius = radius
|
|
self.color = color
|
|
if not container_obj:
|
|
if (x is None) or (y is None):
|
|
raise AttributeError("Center=(x,y) must be provided")
|
|
else:
|
|
self.container_obj = None
|
|
self.center = (x, y)
|
|
else:
|
|
self.container_obj = container_obj # Use weakrefs
|
|
|
|
@property
|
|
def center(self):
|
|
if self.container_obj:
|
|
return (self.container_obj.x, self.container_obj.y)
|
|
else:
|
|
return self._center
|
|
|
|
@center.setter
|
|
def center(self, val):
|
|
if self.container_obj:
|
|
self.container_obj.x, self.container_obj.y = val
|
|
else:
|
|
self._center = val
|
|
|
|
@classmethod
|
|
def from_container_obj(cls, container_obj, radius, color=None):
|
|
return cls(radius=radius, color=color, container_obj=container_obj)
|
|
|
|
@property
|
|
def center_x(self):
|
|
return self.center[0]
|
|
|
|
@property
|
|
def center_y(self):
|
|
return self.center[1]
|
|
|
|
@center_x.setter
|
|
def center_x(self, new_x):
|
|
self.center[0] = new_x
|
|
|
|
@center_y.setter
|
|
def center_y(self, new_y):
|
|
self.center[1] = new_y
|
|
|
|
@property
|
|
def x(self):
|
|
return self.center_x
|
|
|
|
@x.setter
|
|
def x(self, val):
|
|
self.center_x = val
|
|
|
|
@property
|
|
def y(self):
|
|
return self.center_y
|
|
|
|
@y.setter
|
|
def y(self, val):
|
|
self.center_y = val
|
|
|
|
@property
|
|
def bbox(self):
|
|
# left = self.center_x - self.radius
|
|
# top = self.center_y - self.radius
|
|
width = height = self.radius * 2
|
|
return sprite_types.Rect(self.center, (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 self.__class__(self.center, self.radius, self.color)
|
|
|
|
def move(self, move_x, move_y):
|
|
return Circle((self.center_x + move_x, self.center_y + move_y), self.radius, self.color)
|
|
|
|
def move_ip(self, move_x, move_y):
|
|
self.center_x += move_x
|
|
self.center_y += move_y
|
|
return self
|
|
|
|
def inflate(self, inflate_radius):
|
|
# check if resultant radius is -ve
|
|
return Circle(self.center, self.radius + inflate_radius, self.color)
|
|
|
|
def inflate_ip(self, inflate_radius):
|
|
self.radius += inflate_radius
|
|
return self
|
|
|
|
def is_inside(self, another_circle):
|
|
''' Checks if this circle is fully inside another_circle '''
|
|
if self.radius >= another_circle.radius:
|
|
return None
|
|
else:
|
|
bigger_circle = another_circle
|
|
smaller_circle = self
|
|
return distance(bigger_circle.center, smaller_circle.center) + smaller_circle.radius < bigger_circle.radius
|
|
|
|
def is_inside_mutual(self, another_circle):
|
|
''' Checks if this circle is fully inside another_circle or the opposite.'''
|
|
radius_diff = self.radius - another_circle.radius
|
|
if radius_diff > 0:
|
|
bigger_circle = self
|
|
smaller_circle = another_circle
|
|
elif radius_diff == 0:
|
|
# None of them can be inside the other
|
|
return None
|
|
else:
|
|
smaller_circle = self
|
|
bigger_circle = another_circle
|
|
return distance(bigger_circle.center, smaller_circle.center) + smaller_circle.radius < bigger_circle.radius
|
|
|
|
def collide(self, other_obj, moved_center=(None, None), from_container_obj=False):
|
|
''' Collision handler for any type of obj'''
|
|
# todo: handle game_class=="all"
|
|
# todo: point class
|
|
if not hasattr(other_obj, "__class__"):
|
|
raise AttributeError(
|
|
"other_obj must be a class or instance")
|
|
|
|
# Handle instances
|
|
if isinstance(other_obj, Circle):
|
|
return self.collide_circle(other_obj, moved_center, from_container_obj)
|
|
elif isinstance(other_obj, sprite_types.Rect):
|
|
return self.collide_rect(other_obj, moved_center, from_container_obj)
|
|
elif isinstance(other_obj, sprite_types.Img):
|
|
return self.collide_img(other_obj, moved_center, from_container_obj)
|
|
else:
|
|
pass
|
|
|
|
# Handle classes
|
|
if inspect.isclass(other_obj):
|
|
if issubclass(other_obj, Circle):
|
|
return self.collide_list_circle(other_obj.list_instance, moved_center, from_container_obj)
|
|
elif issubclass(other_obj, sprite_types.Rect):
|
|
return self.collide_list_rect(other_obj.list_instance, moved_center, from_container_obj)
|
|
elif issubclass(other_obj, sprite_types.Img):
|
|
return self.collide_list_img(other_obj.list_instance, moved_center, from_container_obj)
|
|
else:
|
|
pass
|
|
|
|
# Handle fun_Game and fun_Class or their instances
|
|
if isinstance(other_obj, base.fun_Game) or (inspect.isclass(other_obj) and issubclass(other_obj, base.fun_Game)):
|
|
def instance_collide(other_obj):
|
|
return self.collide(other_obj.sprite, moved_center, from_container_obj=True)
|
|
to_return = other_obj.for_all_instance(
|
|
instance_collide, flatten="level-class")
|
|
remove_from_list(to_return, self.container_obj)
|
|
remove_from_list(to_return, None, recursive=True)
|
|
remove_from_list(to_return, False, recursive=True)
|
|
return to_return
|
|
if inspect.isclass(other_obj) and issubclass(other_obj, base.fun_Class):
|
|
def instance_collide(other_obj):
|
|
return self.collide(other_obj.sprite, moved_center, from_container_obj=True)
|
|
to_return = other_obj.for_all_instance(
|
|
instance_collide, flatten=False)
|
|
remove_from_list(to_return, self.container_obj)
|
|
remove_from_list(to_return, None, recursive=True)
|
|
remove_from_list(to_return, False, recursive=True)
|
|
return to_return
|
|
if isinstance(other_obj, base.fun_Class):
|
|
other_obj_sprite = other_obj.sprite
|
|
to_return = self.collide(
|
|
other_obj_sprite, moved_center, from_container_obj=True)
|
|
# doesnt return list, so prev. case operations not done
|
|
return to_return
|
|
|
|
def collide_img(self, another_img, moved_center=(None, None), from_container_obj=False):
|
|
''' Checks if this circle intersects with another_img. '''
|
|
|
|
does_collide = self.collide(
|
|
another_img.col_shape, moved_center, from_container_obj=False)
|
|
if does_collide:
|
|
if from_container_obj:
|
|
to_return = another_img.container_obj
|
|
else:
|
|
to_return = another_img
|
|
else:
|
|
to_return = False
|
|
return to_return
|
|
|
|
def collide_list_img(self, list_img, moved_center=(None, None), from_container_obj=False):
|
|
list_collided_img = []
|
|
for each_img in list_img:
|
|
if self.collide_img(each_img, moved_center, from_container_obj):
|
|
# Collide_img itself takes container_obj into account, so no
|
|
# need of post-processing
|
|
list_collided_img.append(each_img)
|
|
else: # else of the for loop
|
|
return False
|
|
return list_collided_img
|
|
|
|
def collide_circle(self, another_circle, moved_center=(None, None), from_container_obj=False):
|
|
''' Checks if this circle intersects with another_circle. '''
|
|
if not (len(moved_center) == 2):
|
|
raise AttributeError("moved_center must be of the form (x,y)")
|
|
_center = self.center
|
|
moved_center = list(moved_center)
|
|
if moved_center[0] is None:
|
|
moved_center[0] = _center[0]
|
|
if moved_center[1] is None:
|
|
moved_center[1] = _center[1]
|
|
# temporarily move self to moved_center and shift back later
|
|
self.center = moved_center
|
|
does_collide = (distance(self.center, another_circle.center) <= (
|
|
self.radius + another_circle.radius))
|
|
self.center = _center
|
|
if does_collide:
|
|
if from_container_obj:
|
|
to_return = another_circle.container_obj
|
|
else:
|
|
to_return = another_circle
|
|
else:
|
|
to_return = False
|
|
return to_return
|
|
|
|
def collide_point(self, (point_x, point_y)):
|
|
''' Checks if (point_x, point_y) is inside this circle. '''
|
|
does_collide = (
|
|
distance(self.center, (point_x, point_y)) <= self.radius)
|
|
if does_collide:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def collide_list_circle(self, list_circle, moved_center=(None, None), from_container_obj=False):
|
|
''' Returns list of circles in list_circle which intersects with this circle. '''
|
|
list_collided_circle = []
|
|
for circle in list_circle:
|
|
if self.collide_circle(circle, moved_center, from_container_obj):
|
|
# Collide_circle itself takes container_obj into account, so no
|
|
# need of post-processing
|
|
list_collided_circle.append(circle)
|
|
else: # else of the for loop
|
|
return False
|
|
return list_collided_circle
|
|
|
|
def collide_list_any_circle(self, list_circle, moved_center=(None, None), from_container_obj=False):
|
|
''' Checks if this circle intersects with any of the circles in circle_list. '''
|
|
for circle in list_circle:
|
|
if self.collide_circle(circle, moved_center, from_container_obj):
|
|
return True
|
|
else: # else of the for loop
|
|
return False
|
|
|
|
def collide_list_all_circle(self, list_circle, moved_center=(None, None), from_container_obj=False):
|
|
''' Checks if this circle intersects with all of the circles in circle_list. '''
|
|
for circle in list_circle:
|
|
if not self.collide_circle(circle, moved_center, from_container_obj):
|
|
return False
|
|
else: # else of the for loop
|
|
return True
|
|
|
|
def collide_rect(self, another_rect, moved_center=(None, None), from_container_obj=False):
|
|
''' Checks if this circle collides with another_rect. '''
|
|
if not (len(moved_center) == 2):
|
|
print "moved_center must be of the form (x,y)"
|
|
_center = self.center
|
|
moved_center = list(moved_center)
|
|
if moved_center[0] is None:
|
|
moved_center[0] = _center[0]
|
|
if moved_center[1] is None:
|
|
moved_center[1] = _center[1]
|
|
|
|
self.center = moved_center
|
|
does_collide = not ((self.center_x + self.radius) < another_rect.left or (self.center_x - self.radius) > another_rect.right or (
|
|
self.center_y + self.radius) < another_rect.top or (self.center_y - self.radius) > another_rect.bottom)
|
|
self.center = _center
|
|
if does_collide:
|
|
if from_container_obj:
|
|
to_return = another_rect.container_obj
|
|
else:
|
|
to_return = another_rect
|
|
else:
|
|
to_return = False
|
|
return to_return
|
|
|
|
def collide_list_rect(self, list_rect, moved_center=(None, None), from_container_obj=False):
|
|
''' Returns list of rect in list_rect which intersects with this circle. '''
|
|
list_collided_rect = []
|
|
for rect in list_rect:
|
|
if self.collide_rect(rect, moved_center, from_container_obj):
|
|
list_collided_rect.append(rect)
|
|
else: # else of the for loop
|
|
return False
|
|
return list_collided_rect
|
|
|
|
def collide_list_any_rect(self, list_rect, moved_center=(None, None), from_container_obj=False):
|
|
''' Returns True if collides with any of the rect in the list. '''
|
|
for temp_rect in list_rect:
|
|
if self.collide_rect(self, temp_rect, moved_center, from_container_obj):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def collide_list_all_rect(self, list_rect, moved_center=(None, None), from_container_obj=False):
|
|
''' Returns True if collides with all the rect in the list. '''
|
|
for temp_rect in list_rect:
|
|
if not self.collide_rect(self, temp_rect, moved_center, from_container_obj):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
@classmethod
|
|
def get_circumcircle_from_rect(cls, another_rect):
|
|
# create a circle with same center and radius=sqrt(w^2+h^2)/2
|
|
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)/2 (as it's not a
|
|
# square)
|
|
return cls(another_rect.center, min(another_rect.width, another_rect.height) / 2)
|
|
|
|
# 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
|
|
return self
|
|
|
|
# list_circle_params : [((x1,y1),radius_1), ((x2,y2),radius_2), ...]
|
|
def _unionall(self, circle_1, seq_circle):
|
|
''' 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
|
|
return self
|
|
|
|
# draw functions
|
|
def blit(self, surface, (x, y)=(None, None), color=None, clip_rect=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
|
|
# pygame.Rect does this, which is used in clear_rect
|
|
x, y = int(x), int(y)
|
|
if not color:
|
|
if self.color:
|
|
color = self.color
|
|
else:
|
|
color = pygame.Color("blue")
|
|
|
|
if clip_rect:
|
|
# Set clip region
|
|
surface.set_clip(clip_rect)
|
|
|
|
if not use_antialiasing:
|
|
pygame.draw.circle(surface, color, (x, y), self.radius, width)
|
|
else:
|
|
pygame.gfxdraw.aacircle(surface, x, y, self.radius, color)
|
|
|
|
if clip_rect:
|
|
# Remove clip region
|
|
surface.set_clip(None)
|
|
|
|
def draw(self):
|
|
tmp_dirty_sprite = {
|
|
"sprite": self,
|
|
# later shape may be used for matching insted of sprites, as sprite
|
|
# may be subclassed, changing "sprite" obj
|
|
"shape": "Circle",
|
|
# Very IMP: If using Rect for bbox, use rect_to_pygame_rect like
|
|
# below.
|
|
"bbox": rect_to_pygame_rect(self.bbox),
|
|
"config": {
|
|
"center": self.center,
|
|
"radius": self.radius,
|
|
"color": self.color
|
|
}
|
|
}
|
|
for tmp_old_sprite in self.container_obj.list_rendered_sprite:
|
|
# if any of them is eqv. to new dirty sprite, mark old one as
|
|
# still_valid
|
|
if tmp_old_sprite == tmp_dirty_sprite:
|
|
# puts "still_valid" entry in the old_dirty_sprite_dict
|
|
# quite good approach, wont match with anything again
|
|
# but "still_valid" should be removed, before next step (tick), to allow
|
|
# checks later
|
|
tmp_old_sprite["still_valid"] = True
|
|
break
|
|
else:
|
|
# i.e. if no match found, list it as dirty sprite
|
|
self.container_obj.list_dirty_sprite.append(tmp_dirty_sprite)
|
|
|
|
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
|