Creating multiple interactive polygon buttons in Pygame - python

My goal is to create a lot of polygon button. For example, if I input a number of 6, I want to have 6 polygons.
For example:
import pygame
pygame.init()
SIZE_X = SIZE_Y = 600
screen = pygame.display.set_mode((SIZE_X, SIZE_Y), pygame.RESIZABLE )
red = (255, 0 , 0)
NB_POLYGONS = 6
for i in range(NB_POLYGONS):
polygons = [ ( x1 + i * 50,y1) , ( x2 + i * 50,y2), ( x3 + i * 50,y3)]#With xn and yn some random cords
pygame.draw.polygon(screen,red, polygons)
This example works and creates 6 polygons. But how can I make them responsive? For example by clicking on one of them to switch it to blue. In the code I provided I've put 3 points in the polygon, but I want to know if it's possible to do this with an arbitrary polygon.

pygame.draw.polygon() returns a pygame.Rect bounding the changed pixels. Test if the mouse is in the rectangle and draw the polygon again with a different color:
for i in range(NB_POLYGONS):
points = [(x1 + i*50, y1), (x2 + i*50, y2), (x3 + i*50, y3)]
bounding_rect = pygame.draw.polygon(screen, 'red', points)
if bounding_rect.collidepoint(pygame.mouse.get_pos()):
pygame.draw.polygon(screen, 'blue', points)

If you restrict it to only convex polygons, you can get pixel perfect precision by checking whether the mouse-click intersects it. This can be done by creating triangles between each consecutive vertex in the polygon and checking whether any of them intersects the point.
Checking for intersection between a point and a triangle can be done with Heron's formula.
Here's a minimal example:
def collide(point, polygon):
assert len(polygon) >= 3, "A polygon must have at least 3 vertices."
px, py = point
n = len(polygon)
for i in range(n):
x0, y0 = polygon[i]
x1, y1 = polygon[(i+1) % n]
x2, y2 = polygon[(i+2) % n]
area1 = abs((x0-px)*(y1-py) - (x1-px)*(y0-py));
area2 = abs((x1-px)*(y2-py) - (x2-px)*(y1-py));
area3 = abs((x2-px)*(y0-py) - (x0-px)*(y2-py));
area = abs((x1-x0)*(y2-y0) - (x2-x0)*(y1-y0));
if area1 + area2 + area3 == area:
return True
return False
print(collide(point=(10, 10), polygon=[(5, 5), (5, 10), (10, 10), (10, 5)])) # True
print(collide(point=(20, 10), polygon=[(5, 5), (5, 10), (10, 10), (10, 5)])) # False

Related

Wrong coefficients for the line equation

Suppose I have a list of line segments [[[x1,y1],[x2,y2],...] detected with cv2.HoughLinesP. This list represent only the endpoints of each line segment.
Segments were sorted by It's x2, x2 value i.e. ordered "from left to right" with respect to the image, from which the segments were taken from.
segments_sorted = [[(0, 797), (46, 769)], [(2, 766), (138, 690)], [(220, 644), (399, 541)], [(427, 523), (615, 414)], [(460, 513), (615, 419)], [(495, 491), (614, 419)], [(753, 368), (843, 518)], [(958, 708), (1099, 706)], [(1047, 681), (1088, 729)], [(1047, 706), (1095, 761)]]
For better understanding, they were drawn on the image that is shown below:
I need to grab the left-most and the right-most lines end extend them to image boundaries (left-most and the right-most segment endpoint respectively).
left_most_segment = [(0, 797), (46, 769)]
right_most_segment = [(1047, 706), (1095, 761)]
def get_line_coefficients(line):
(x1, y1), (x2, y2) = line
a = y1 - y2,
b = x2 - x1,
c = (x1 - x2) * y1 + (y2 - y1) * x1
return a, b, c
# using the Ax + By + C = 0 equation - get the coefficients and update the endpoints
r_a, r_b, r_c = get_line_coefficients(right_most_segment)
# l_a = -55
# l_b = 48
# l_c = 23697
The problem is that, for all the segments that I fit into get_line_coefficients()
the c coefficient value is too big, like -36662 or 23697.
I thought previously to update the x,y coordinate of the endpoint.
# e.g. for the right-most segment, x1,y1 should now be updated
new_x = 0
new_y = -(r_a*new_x + r_c) / r_b
new_x = image.shape[1]
new_y = r_a * new_x + r_c / r_b
sorted_lines[-1][1] = [new_x, new_y]
cv2.polylines(original, np.array(sorted_lines), False, RGB_BLUE, thickness=3)
cv2.polylines breakes at
...
cv2.error: OpenCV(4.1.1) /io/opencv/modules/imgproc/src/drawing.cpp:2435: error: (-215:Assertion failed) p.checkVector(2, CV_32S) >= 0 in function 'polylines'
А dummy problem was caused by type mismatch.
new_y = int(-(r_a * new_x + r_c) / r_b) # should be an int
After several tests It becomes apparent that c value was computed in the right way.
the image with right-most line extended

Shrink polygon using corner coordinates

I'm trying to figure out how to shrink a polygon using only the coordinates of its corners. For example, if I have the following shape with corners at [(0, 0), (0, 100), (20, 100), (30, 60), (40, 100), (60, 100), (60, 0), (40, 10), (40, 40), (20, 40), (20, 10)] so the shape looks like this:
And I want to find the corner coordinates for if I shrink this polygon by some width and height factor. For example, If I want to shrink its width by 10% and height by 20% then this could be shown as something like this:
I was trying to do this using cv2.resize() by could not get the corners after resizing. I have been trying to find an algorithm for polygon resizing or polygon shrinking, but cannot find anything about how to do this. Do any algorithms or packages for doing something like this exist?
I have tested this solution on more than 1200 polygons of real life building in california and it works like charm.
One more thing is that this same approach works find for enlarging polygons as well equally well.
This below method can be used as it is:
def shrink_or_swell_shapely_polygon(my_polygon, factor=0.10, swell=False):
''' returns the shapely polygon which is smaller or bigger by passed factor.
If swell = True , then it returns bigger polygon, else smaller '''
from shapely import geometry
#my_polygon = mask2poly['geometry'][120]
shrink_factor = 0.10 #Shrink by 10%
xs = list(my_polygon.exterior.coords.xy[0])
ys = list(my_polygon.exterior.coords.xy[1])
x_center = 0.5 * min(xs) + 0.5 * max(xs)
y_center = 0.5 * min(ys) + 0.5 * max(ys)
min_corner = geometry.Point(min(xs), min(ys))
max_corner = geometry.Point(max(xs), max(ys))
center = geometry.Point(x_center, y_center)
shrink_distance = center.distance(min_corner)*0.10
if swell:
my_polygon_resized = my_polygon.buffer(shrink_distance) #expand
else:
my_polygon_resized = my_polygon.buffer(-shrink_distance) #shrink
#visualize for debugging
#x, y = my_polygon.exterior.xy
#plt.plot(x,y)
#x, y = my_polygon_shrunken.exterior.xy
#plt.plot(x,y)
## to net let the image be distorted along the axis
#plt.axis('equal')
#plt.show()
return my_polygon_resized
As far as I have understood you are searching the functionality of ST_Buffer from postgis, but with separated factors.
This is unfortunately not easy to achieve (see one question in the qgis-stack for more on it).
But if it already helps to do it with the same factor of x and y (or as a start for a more elaborate algorithm) here you go:
One library that makes ST_Buffer function accessible within python is shapely.
(If you need more geo-data specific power geoalchemy2 might be the better option. Beware the crs/srid changes in that case)
from shapely import geometry
import matplotlib.pyplot as plt
# your variables
coords = [(0, 0), (0, 100), (20, 100), (30, 60), (40, 100), (60, 100), (60, 0), (40, 10), (40, 40), (20, 40), (20, 10)]
lines = [[coords[i-1], coords[i]] for i in range(len(coords))]
# your factor of 10%
# Note: with 20% the polygon becomes a multi-polygon, so a loop for plotting would be needed.
factor = 0.1
# code from nathan
xs = [i[0] for i in coords]
ys = [i[1] for i in coords]
x_center = 0.5 * min(xs) + 0.5 * max(xs)
y_center = 0.5 * min(ys) + 0.5 * max(ys)
min_corner = geometry.Point(min(xs), min(ys))
max_corner = geometry.Point(max(xs), max(ys))
center = geometry.Point(x_center, y_center)
shrink_distance = center.distance(min_corner)*factor
assert abs(shrink_distance - center.distance(max_corner)) < 0.0001
my_polygon = geometry.Polygon(coords)
my_polygon_shrunken = my_polygon.buffer(-shrink_distance)
x, y = my_polygon.exterior.xy
plt.plot(x,y)
x, y = my_polygon_shrunken.exterior.xy
plt.plot(x,y)
# to net let the image be distorted along the axis
plt.axis('equal')
plt.show()
I misread the question, I'm leaving up the anwer because it might help someone, but I realize the final output is not the desired one
To get the new coordinates of the polygon after shrinking you can multiply all coordinates (position vectors) with the shrinkage factor like this:
x_shrink = 0.1
y_shrink = 0.2
coords = [(0, 0), (0, 100), (20, 100), (30, 60), (40, 100), (60, 100), (60, 0), (40, 10), (40, 40), (20, 40), (20, 10)]
xs = [i[0] for i in coords]
ys = [i[1] for i in coords]
# simplistic way of calculating a center of the graph, you can choose your own system
x_center = 0.5 * min(xs) + 0.5 * max(xs)
y_center = 0.5 * min(ys) + 0.5 * max(ys)
# shrink figure
new_xs = [(i - x_center) * (1 - x_shrink) + x_center for i in xs]
new_ys = [(i - y_center) * (1 - y_shrink) + y_center for i in ys]
# create list of new coordinates
new_coords = zip(new_xs, new_ys)
This outputs the following (blue is original, green is the shrunk polygon)
I do not think it is mathematically possible to have a percentage shrink in x, a percentage shrink in y and never have the layout move outside the original layout. That is however just a hunch.
This code shifts all the lines a certain amount closer to the center and then finds the new intersection points of all the lines:
import matplotlib.pyplot as plt
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
def line_intersection(line1, line2):
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1]) #Typo was here
div = det(xdiff, ydiff)
if div == 0:
raise Exception('lines do not intersect')
d = (det(*line1), det(*line2))
x = det(d, xdiff) / div
y = det(d, ydiff) / div
return x, y
# how much the coordinates are moved as an absolute value
shrink_value_x = 3
shrink_value_y = 1.5
# coords must be clockwise
coords = [(0, 0), (0, 100), (20, 100), (30, 60), (40, 100), (60, 100), (60, 0), (40, 10), (40, 40), (20, 40), (20, 10)]
lines = [[coords[i-1], coords[i]] for i in range(len(coords))]
new_lines = []
for i in lines:
dx = i[1][0] - i[0][0]
dy = i[1][1] - i[0][1]
# this is to take into account slopes
factor = 1 / (dx*dx + dy*dy)**0.5
new_dx = dy*shrink_value_x * factor
new_dy = dx*shrink_value_y * factor
new_lines.append([(i[0][0] + new_dx, i[0][1] - new_dy),
(i[1][0] + new_dx, i[1][1] - new_dy)])
# find position of intersection of all the lines
new_coords = []
for i in range(len(new_lines)):
new_coords.append((line_intersection(new_lines[i-1], new_lines[i])))
I got the line intersection code from this answer #Paul Draper.
This outputs

Optimisation of Shadow Casting Python

I have been working on a Shadow Caster for a small RPG I'm doing.
The trouble I have is that when I use it in my game it is just way way way to slow and induces a horrible lag.
Please do not be too frighten by the lenght of the post. It is fairly straightforward but so that you can run the code I included all the Bresenham's algorithms as well.
The principle is as follow:
- make a black surface
- define a light source with a position and a radius.
- get all the points on the circumference of the circle defined by this position and radius using Bresenham's Circle Algorithm.
- for each point along the circumference draw a ligne from the position of the light source using Bresenham's Line Algorithm.
- then iterate over the points of the line and check if they collide with every obstacle displayed on the screen.
- If there is no collision draw a WHITE circle centered on that point with a radius of 10 pixels or so.
- If there is a collision move on to the next point along the circle circumference.
- finally blit the surface with all the white circles on a surface which has a transparency value of 100 for the black color and a full transparency for the WHITE color.
So far I have attempted the following:
Which did reduce the lag:
- restrict the obstacle list to the ones displayed on the screen
- consider the screen edges as obstacles to reduce the iteration of area not visible.
- iterate only over every 3 points around the circle and 12 points along the lines.
Which didn't change anything:
- using ellipses going from the light source to the edge of the range or the obstacle instead of lots of circles along the line. The problem was that I had to redraw surface for each ellipse and then rotate the whole lot.
If you have any suggestions on how to make this more efficient I would be happy to here then.
Bresenham's Line Algo:
def get_line(start, end):
"""Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
Bresenham's Circle Algo:
def get_circle((dx,dy),radius):
"Bresenham complete circle algorithm in Python"
# init vars
switch = 3 - (2 * radius)
points = set()
x = 0
y = radius
# first quarter/octant starts clockwise at 12 o'clock
while x <= y:
# first quarter first octant
points.add((x,-y))
# first quarter 2nd octant
points.add((y,-x))
# second quarter 3rd octant
points.add((y,x))
# second quarter 4.octant
points.add((x,y))
# third quarter 5.octant
points.add((-x,y))
# third quarter 6.octant
points.add((-y,x))
# fourth quarter 7.octant
points.add((-y,-x))
# fourth quarter 8.octant
points.add((-x,-y))
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
x = x + 1
offset_points = set()
for pt in points:
offset_points.add((pt[0]+dx,pt[1]+dy))
return offset_points
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
My Classes for the light sources, obstacles and shadow mask:
class Obstacle(object):
def __init__(self,x,y):
self.surf = pygame.Surface((150,150))
self.rect = pygame.Rect((x,y),(150,150))
self.surf.fill(pygame.color.Color('blue'))
class Light_Source(object):
def __init__(self,x,y,range_):
self.range = range_
self.pos = (x,y)
class Night_Mask(object):
def __init__(self):
self.surf = pygame.Surface((500,500)) #Screenwidth and height
self.alpha = 100
self.light_sources = []
'''setting initial alpha and colorkey'''
self.surf.set_colorkey(WHITE)
self.surf.set_alpha(self.alpha)
def apply_shadows(self, obstacles):
shadow_surf = pygame.Surface((500,500))
for source in self.light_sources:
circle_pts = list(get_circle(source.pos,source.range))
for cir_pt in circle_pts[0::3]:
shadow_gen(shadow_surf,source,cir_pt,obstacles)
self.surf.blit(shadow_surf, (0, 0))
The shadow generation functions which allows me to break out of both line and obstacle loop without using an exception in my apply_shadows method of the Night_Mask class:
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
And finally the main pygame example loop to run all of the above:
pygame.init()
screen = pygame.display.set_mode((500, 500))
bg = pygame.Surface((500,500))
bg.fill(pygame.color.Color('yellow'))
ob_a = Obstacle(75,80)
ls = Light_Source(75,75,300)
night_m = Night_Mask()
night_m.light_sources.extend([ls])
while True:
screen.fill(pygame.color.Color('black'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ls.pos = pygame.mouse.get_pos()
night_m.apply_shadows([ob_a])
screen.blit(bg, (0, 0))
screen.blit(ob_a.surf,ob_a.rect)
screen.blit(night_m.surf, (0, 0))
pygame.display.flip()
Here is the entire code from start to finish for an easy copy paste:
import pygame
import sys
WHITE = (255,255,255)
'''FUNCTIONS'''
def get_line(start, end):
"""Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
def get_circle((dx,dy),radius):
"Bresenham complete circle algorithm in Python"
# init vars
switch = 3 - (2 * radius)
points = set()
x = 0
y = radius
# first quarter/octant starts clockwise at 12 o'clock
while x <= y:
# first quarter first octant
points.add((x,-y))
# first quarter 2nd octant
points.add((y,-x))
# second quarter 3rd octant
points.add((y,x))
# second quarter 4.octant
points.add((x,y))
# third quarter 5.octant
points.add((-x,y))
# third quarter 6.octant
points.add((-y,x))
# fourth quarter 7.octant
points.add((-y,-x))
# fourth quarter 8.octant
points.add((-x,-y))
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
x = x + 1
offset_points = set()
for pt in points:
offset_points.add((pt[0]+dx,pt[1]+dy))
return offset_points
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
'''CLASSES'''
class Obstacle(object):
def __init__(self,x,y):
self.surf = pygame.Surface((150,150))
self.rect = pygame.Rect((x,y),(150,150))
self.surf.fill(pygame.color.Color('blue'))
class Light_Source(object):
def __init__(self,x,y,range_):
self.range = range_
self.pos = (x,y)
class Night_Mask(object):
def __init__(self):
self.surf = pygame.Surface((500,500)) #Screenwidth and height
self.alpha = 100
self.light_sources = []
'''setting initial alpha and colorkey'''
self.surf.set_colorkey(WHITE)
self.surf.set_alpha(self.alpha)
def apply_shadows(self, obstacles):
shadow_surf = pygame.Surface((500,500))
for source in self.light_sources:
circle_pts = list(get_circle(source.pos,source.range))
for cir_pt in circle_pts[0::3]:
shadow_gen(shadow_surf,source,cir_pt,obstacles)
self.surf.blit(shadow_surf, (0, 0))
'''MAIN GAME'''
pygame.init()
screen = pygame.display.set_mode((500, 500))
bg = pygame.Surface((500,500))
bg.fill(pygame.color.Color('yellow'))
ob_a = Obstacle(75,80)
ls = Light_Source(75,75,300)
night_m = Night_Mask()
night_m.light_sources.extend([ls])
while True:
screen.fill(pygame.color.Color('black'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ls.pos = pygame.mouse.get_pos()
night_m.apply_shadows([ob_a])
screen.blit(bg, (0, 0))
screen.blit(ob_a.surf,ob_a.rect)
screen.blit(night_m.surf, (0, 0))
pygame.display.flip()
Your lag issue appears to be coming from the method Night_Mask.apply_shadows(self, obstacles). This appears to be due to the pure amount of iterations the nested for loop needs to go through.
Reducing the value of range_ in the constructor of Light_Source(x, y, range_) reduces the lag by reducing the aforementioned methods iterations, but the visual effect is worse. I found that the fps started to really drop for me after setting the variable past ~65-70.
There is a Pygame graphics library, that handles shadows very well.
Link to the page:http://pygame.org/project-Pygame+Advanced+Graphics+Library-660-4586.html
Direct download for version 8.1.1 from site:link
This is the description of the library from the site:
This is an all purpose graphics library for easily creating complicated effects quickly, and with a minimum of code. Run the very well commented examples, each less than a page long (not counting comments), and learn how to make complicated effects like shadows and antialiasing.
Here is an image from the page showing an example of shadows.
I downloaded and tested the library, and it works very well. I tested on Pygame1.9.2a0 for python 3.4
I believe this to be the easiest fix for your problem, and should help you with future projects as well. I hope this helps.

Detecting Rectangle collision with a Circle

I have a Circle with a center point (Center_X, Center_Y) and I am detecting if a rectangle falls into it's Radius (Radius). How would I be able to perform this task? I have tried using
if (X - Center_X)^2 + (Y - Center_Y)^2 < Radius^2:
print(1)
Then I try to draw a circle to fit over this area:
Circle = pygame.draw.circle(Window, Blue, (Center_X, Center_Y), Radius, 0)
But it doesn't seem to line up. Is there something I am doing wrong?
Here's what I was describing in my comments, plus changes to correct handling of the case of a circle inside a larger rectangle which Michael Anderson pointed out in a comment:
import math
def collision(rleft, rtop, width, height, # rectangle definition
center_x, center_y, radius): # circle definition
""" Detect collision between a rectangle and circle. """
# complete boundbox of the rectangle
rright, rbottom = rleft + width/2, rtop + height/2
# bounding box of the circle
cleft, ctop = center_x-radius, center_y-radius
cright, cbottom = center_x+radius, center_y+radius
# trivial reject if bounding boxes do not intersect
if rright < cleft or rleft > cright or rbottom < ctop or rtop > cbottom:
return False # no collision possible
# check whether any point of rectangle is inside circle's radius
for x in (rleft, rleft+width):
for y in (rtop, rtop+height):
# compare distance between circle's center point and each point of
# the rectangle with the circle's radius
if math.hypot(x-center_x, y-center_y) <= radius:
return True # collision detected
# check if center of circle is inside rectangle
if rleft <= center_x <= rright and rtop <= center_y <= rbottom:
return True # overlaid
return False # no collision detected
You have two common options for this kind of collision detection.
The first is to understand the ways two 2D objects can collide.
A vertex of one can be inside the other
Their sides can cross (even thought no verice is inside)
One can be completely interior to the other.
Technically case 1. can only occur if case 2. also occurs, but it is often a cheaper check.
Also case 3 is checked by case 1, in the case where both objects vertices are checked.
I would proceed like this. (as it is in order of cheapness)
Check that their bounding boxes intersect.
Check whether any vertex of the square is inside the
Check if the center of the circle is inside the rectangle
Check for circle - edge intersections.
The second and more general method is based on the notion of the product / expansion of shapes.
This operation allows you to convert the intersection question into a point containment question.
In this case the circle / rectangle box intersection can be replaced with a check for a point in a rounded rectangle.
Use the dist function from Shortest distance between a point and a line segment
import math
def dist(p1, p2, c):
x1,y1 = p1
x2,y2 = p2
x3,y3 = c
px = x2-x1
py = y2-y1
something = px*px + py*py
u = ((x3 - x1) * px + (y3 - y1) * py) / float(something)
if u > 1:
u = 1
elif u < 0:
u = 0
x = x1 + u * px
y = y1 + u * py
dx = x - x3
dy = y - y3
dist = math.sqrt(dx*dx + dy*dy)
return dist
Here is a test:
rect = [[0. , 0. ],
[ 0.2, 1. ],
[ 2.2, 0.6],
[ 2. , -0.4]]
c = 0.5, 2.0
r = 1.0
distances = [dist(rect[i], rect[j], c) for i, j in zip([0, 1, 2, 3], [1, 2, 3, 0])]
print distances
print any(d < r for d in distances)
output:
[1.044030650891055, 1.0394155162323753, 2.202271554554524, 2.0592194189509323]
False
Here is the plot:

Drawing diagonal lines on an image

Hi im trying to draw diagonal lines across an image top right to bottom left here is my code so far.
width = getWidth(picture)
height = getHeight(picture)
for x in range(0, width):
for y in range(0, height):
pixel = getPixel(picture, x, y)
setColor(pixel, black)
Thanks
Most graphic libraries have some way to draw a line directly.
In JES there is the addLine function, so you could do
addLine(picture, 0, 0, width, height)
If you're stuck with setting single pixels, you should have a look at Bresenham Line Algorithm, which is one of the most efficient algorithms to draw lines.
A note to your code: What you're doing with two nested loops is the following
for each column in the picture
for each row in the current column
set the pixel in the current column and current row to black
so basically youre filling the entire image with black pixels.
EDIT
To draw multiple diagonal lines across the whole image (leaving a space between them), you could use the following loop
width = getWidth(picture)
height = getHeight(picture)
space = 10
for x in range(0, 2*width, space):
addLine(picture, x, 0, x-width, height)
This gives you an image like (the example is hand-drawn ...)
This makes use of the clipping functionality, most graphics libraries provide, i.e. parts of the line that are not within the image are simply ignored. Note that without 2*width (i.e. if x goes only up to with), only the upper left half of the lines would be drawn...
I would like to add some math considerations to the discussion...
(Just because it is sad that JES's addLine function draws black lines only and is quite limited...)
Note : The following code uses the Bresenham's Line Algorithm pointed out by MartinStettner (so thanks to him).
The Bresenham's line algorithm is an algorithm which determines which order to form a close approximation to a straight line between two given points. Since a pixel is an atomic entity, a line can only be drawn on a computer screen by using some kind of approximation.
Note : To understand the following code, you will need to remember a little bit of your basic school math courses (line equation & trigonometry).
Code :
# The following is fast implementation and contains side effects...
import random
# Draw point, with check if the point is in the image area
def drawPoint(pic, col, x, y):
if (x >= 0) and (x < getWidth(pic)) and (y >= 0) and (y < getHeight(pic)):
px = getPixel(pic, x, y)
setColor(px, col)
# Draw line segment, given two points
# From Bresenham's line algorithm
# http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
def drawLine(pic, col, x0, y0, x1, y1):
dx = abs(x1-x0)
dy = abs(y1-y0)
sx = sy = 0
#sx = 1 if x0 < x1 else -1
#sy = 1 if y0 < y1 else -1
if (x0 < x1):
sx = 1
else:
sx = -1
if (y0 < y1):
sy = 1
else:
sy = -1
err = dx - dy
while (True):
drawPoint(pic, col, x0, y0)
if (x0 == x1) and (y0 == y1):
break
e2 = 2 * err
if (e2 > -dy):
err = err - dy
x0 = x0 + sx
if (x0 == x1) and (y0 == y1):
drawPoint(pic, col, x0, y0)
break
if (e2 < dx):
err = err + dx
y0 = y0 + sy
# Draw infinite line from segment
def drawInfiniteLine(pic, col, x0, y0, x1, y1):
# y = m * x + b
m = (y0-y1) / (x0-x1)
# y0 = m * x0 + b => b = y0 - m * x0
b = y0 - m * x0
x0 = 0
y0 = int(m*x0 + b)
# get a 2nd point far away from the 1st one
x1 = getWidth(pic)
y1 = int(m*x1 + b)
drawLine(pic, col, x0, y0, x1, y1)
# Draw infinite line from origin point and angle
# Angle 'theta' expressed in degres
def drawInfiniteLineA(pic, col, x, y, theta):
# y = m * x + b
dx = y * tan(theta * pi / 180.0) # (need radians)
dy = y
if (dx == 0):
dx += 0.000000001 # Avoid to divide by zero
m = dy / dx
# y = m * x + b => b = y - m * x
b = y - m * x
# get a 2nd point far away from the 1st one
x1 = 2 * getWidth(pic)
y1 = m*x1 + b
drawInfiniteLine(pic, col, x, y, x1, y1)
# Draw multiple parallele lines, given offset and angle
def multiLines(pic, col, offset, theta, randOffset = 0):
# Range is [-2*width, 2*width] to cover the whole surface
for i in xrange(-2*getWidth(pic), 2*getWidth(pic), offset):
drawInfiniteLineA(pic, col, i + random.randint(0, randOffset), 1, theta)
# Draw multiple lines, given offset, angle and angle offset
def multiLinesA(pic, col, offsetX, offsetY, theta, offsetA):
j = 0
# Range is [-2*width, 2*width] to cover the whole surface
for i in xrange(-2*getWidth(pic), 2*getWidth(pic), offsetX):
drawInfiniteLineA(pic, col, i, j, theta)
j += offsetY
theta += offsetA
file = pickAFile()
picture = makePicture(file)
color = makeColor(0, 65, 65) #pickAColor()
#drawline(picture, color, 10, 10, 100, 100)
#drawInfiniteLine(picture, color, 10, 10, 100, 100)
#drawInfiniteLineA(picture, color, 50, 50, 135.0)
#multiLines(picture, color, 20, 56.0)
#multiLines(picture, color, 10, 56.0, 15)
multiLinesA(picture, color, 10, 2, 1.0, 1.7)
show(picture)
Output (Painting by Pierre Soulages) :
Hope this gave some fun and ideas to JES students... And to others as well...
Where does your picture object comes from? What is it? What is not working so far? And what library for image access are you trying to use? (I mean, where do you get, or intend to get "getWidth, getHeight, getPixel, setColor) from?
I think no library that gives you a "pixel" as a whole object which can be used in a setColor call exists, and if it does, it would be the slowest thing in the World - maybe in the galaxy.
On the other hand, if these methods did exist and your Picture, the code above would cover all the image in black - you are getting all possible "y" values (from 0 to height) inside all possible x values (from 0 to width) of the image, and coloring each Black.
Drawing a line would require you to change x, and y at the same time, more like:
(using another "imaginary library", but one more plausible:
for x, y in zip(range(0, width), range(0, height)):
picture.setPixel((x,y), Black) )
This would sort of work, but the line would not be perfect unless the image was perfectly square - else it would skip pixels in the widest direction of the image. To solve that a more refined algorithm is needed - but that is second to you have a real way to access pixels on an image - like using Python's Imaging Library (PIL or Pillow), or pygame, or some other library.

Categories

Resources