How to make rect from the intersection of two? - python

I'm working on a breakout clone and I've been trying to figure out how to get the intersection rect of two colliding rects so I can measure how deep the ball entered the block in both x and y axis and decide which component of the velocity I'll reverse.
I figured I could calculate the depth for each case like this:
But if I had the intersection rect than I woudn't have to worry if the ball hits the block from the left/right or top/bottom (since I would be only reversing the x and y axis respectively), thus saving me a lot of typing.
I've looked on Pygame's docs but seems it doesn't have a function for that. How would I go about solving this problem?

Assuming you have rectangles r1 and r2, with .left, .right, .top, and .bottom edges, then
left = max(r1.left, r2.left);
right = min(r1.right, r2.right);
top = max(r1.top, r2.top);
bottom = min(r1.bottom, r2.bottom);
(with the usual convention that coordinates increase top to bottom and left to right). Finally, check that left<right and top<bottom, and compute the area:
Area = (right - left) * (top - bottom);
Alternatively, you can use the clip() function. From the docs you linked in your question:
clip(Rect) -> Rect Returns a new rectangle that is cropped to be
completely inside the argument Rect. If the two rectangles do not
overlap to begin with, a Rect with 0 size is returned.

Related

How to convert screen x,y (cartesian coordinates) to 3D world space crosshair movement angles (screenToWorld)?

Recently I've been playing around with computer vision and neural networks.
And came across experimental object detection within a 3D application.
But, surprisingly to me - I've faced an issue of converting one coordinates system to another (AFAIK cartesian to polar/sphere).
Let me explain.
For example, we have a screenshot of a 3D application window (some 3D game):
Now, using Open-CV or neural network I'm able to detect the round spheres (in-game targets).
As well as their X, Y coordinates within the game window (x, y offsets).
And if I will programmatically move a mouse cursor within the given X, Y coordinates in order to aim one of the targets.
It will work only when I'm in desktop environment (moving the cursor in desktop).
But when I switch to the 3D game and thus, my mouse cursor is now within 3D game world environment - it does not work and does not aim the target.
So, I did a decent research on the topic.
And what I came across, is that the mouse cursor is locked inside 3D game.
Because of this, we cannot move the cursor using MOUSEEVENTF_MOVE (0x0001) + MOUSEEVENTF_ABSOLUTE (0x8000) flags within the mouse_event win32 call.
We are only able to move the mouse programmatically using relative movement.
And, theoretically, in order to get this relative mouse movement offsets, we can calculate the offset of detections from the middle of the 3D game window.
In such case, relative movement vector would be something like (x=-100, y=0) if the target point is 100px left from the middle of the screen.
The thing is, that the crosshair inside a 3D game will not move 100px to the left as expected.
And will not aim the given target.
But it will move a bit in a given direction.
After that, I've made more research on the topic.
And as I understand, the crosshair inside a 3D game is moving using angles in 3D space.
Specifically, there are only two of them: horizontal movement angles and vertical movement angles.
So the game engine takes our mouse movement and converts it to the movement angles within a given 3D world space.
And that's how the crosshair movement is done inside a 3D game.
But we don't have access to that, all we can is move the mouse with win32 calls externally.
Then I've decided to somehow calculate pixels per degree (amount of pixels we need to use with win32 relative mouse movement in order to move the crosshair by 1 degrees inside the game).
In order to do this, I've wrote down a simple calculation algorithm.
Here it is:
As you can see, we need to move our mouse relatively with win32 by 16400 pixels horizontally, in order to move the crosshair inside our game by 360 degrees.
And indeed, it works.
16400/2 will move the crosshair by 180 degrees respectively.
What I did next, is I tried to convert our screen X, Y target offset coordinates to percentages (from the middle of the screen).
And then convert them to degrees.
The overall formula looked like (example for horizontal movement only):
w = 100 # screen width
x_offset = 10 # target x offset
hor_fov = 106.26
degs = (hor_fov/2) * (x_offset /w) # 5.313 degrees
And indeed, it worked!
But not quite as expected.
The overall aiming precision was different, depending on how far the target is from the middle of the screen.
I'm not that great with trigonometry, but as I can say - there's something to do with polar/sphere coordinates.
Because we can see only some part of the game world both horizontally & vertically.
It's also called the FOV (Field of view).
Because of this, in the given 3D game we are only able to view 106.26 degrees horizontally.
And 73.74 degrees vertically.
My guess, is that I'm trying to convert coordinates from linear system to something non-linear.
As a result, the overall accuracy is not good enough.
I've also tried to use math.atan in Python.
And it works, but still - not accurate.
Here is the code:
def point_get_difference(source_point, dest_point):
# 1000, 1000
# source_point = (960, 540)
# dest_point = (833, 645)
# result = (100, 100)
x = dest_point[0]-source_point[0]
y = dest_point[1]-source_point[1]
return x, y
def get_move_angle__new(aim_target, gwr, pixels_per_degree, fov):
game_window_rect__center = (gwr[2]/2, gwr[3]/2)
rel_diff = list(point_get_difference(game_window_rect__center, aim_target))
x_degs = degrees(atan(rel_diff[0]/game_window_rect__center[0])) * ((fov[0]/2)/45)
y_degs = degrees(atan(rel_diff[1] / game_window_rect__center[0])) * ((fov[1]/2)/45)
rel_diff[0] = pixels_per_degree * x_degs
rel_diff[1] = pixels_per_degree * y_degs
return rel_diff, (x_degs+y_degs)
get_move_angle__new((900, 540), (0, 0, 1920, 1080), 16364/360, (106.26, 73.74))
# Output will be: ([-191.93420990140876, 0.0], -4.222458785413539)
# But it's not accurate, overall x_degs must be more or less than -4.22...
Is there a way to precisely convert 2D screen X, Y coordinates into 3D game crosshair movement degrees?
There must be a way, I just can't figure it out ...
The half-way point between the center and the edge of the screen is not equal to the field of view divided by four. As you noticed, the relationship is nonlinear.
The angle between a fractional position on the screen (0-1) and the middle of the screen can be calculated as follows. This is for the horizontal rotation (i.e, around the vertical axis), so we're only considering the X position on the screen.
# angle is the angle in radians that the camera needs to
# rotate to aim at the point
# px is the point x position on the screen, normalised by
# the resolution (so 0.0 for the left-most pixel, 0.5 for
# the centre and 1.0 for the right-most
# FOV is the field of view in the x dimension in radians
angle = math.atan((x-0.5)*2*math.tan(FOV/2))
For a field of view of 100 degrees and an x of zero, that gives us -50 degrees of rotation (exactly half the field of view). For an x of 0.25 (half-way between the edge and middle), we get a rotation of around -31 degrees.
Note that the 2*math.tan(FOV/2) part is constant for any given field of view, so you can calculate it in advance and store it. Then it just becomes (assuming we named it z):
angle = math.atan((x-0.5)*z)
Just do that for both x and y and it should work.
Edit / update:
Here is a complete function. I've tested it, and it seems to work.
import math
def get_angles(aim_target, window_size, fov):
"""
Get (x, y) angles from center of image to aim_target.
Args:
aim_target: pair of numbers (x, y) where to aim
window_size: size of area (x, y)
fov: field of view in degrees, (horizontal, vertical)
Returns:
Pair of floating point angles (x, y) in degrees
"""
fov = (math.radians(fov[0]), math.radians(fov[1]))
x_pos = aim_target[0]/(window_size[0]-1)
y_pos = aim_target[1]/(window_size[1]-1)
x_angle = math.atan((x_pos-0.5)*2*math.tan(fov[0]/2))
y_angle = math.atan((y_pos-0.5)*2*math.tan(fov[1]/2))
return (math.degrees(x_angle), math.degrees(y_angle))
print(get_angles(
(0, 0), (1920, 1080), (100, 67.67)
), "should be around -50, -33.835")
print(get_angles(
(1919, 1079), (1920, 1080), (100, 67.67)
), "should be around 50, 33.835")
print(get_angles(
(959.5, 539.5), (1920, 1080), (100, 67.67)
), "should be around 0, 0")
print(get_angles(
(479.75, 269.75), (1920, 1080), (100, 67.67)
), "should be around 30.79, 18.53")

Randomly placing N circles in rectangle without overlapping

I want to place N circles with given, common radius in the rectangle of given size, such that circles are not overlapping in Python. My current solutions are:
1) to create a set of every point in the space and remove from it points that will cause overlapping before generating next circle (but it's slow when the rectangle is big).
2) to draw the center of balls from the set of not-overlapping points (e.g. every 2r + const) (but the positions are not random enough here).
Do you have other, more efficient ideas?
so the most efficient packing in 2D is hexagonal packing and you can just hard code your program to give that packing for circles
read more about it here : https://en.wikipedia.org/wiki/Circle_packing

checking collision between rectangle and a sector of circle efficiently

hey I wanted to know if someone knew a code for checking collision between a rect and a sector of a circle, I know how to do it but the way I would do it is unefficient, here is the basic theory:
def is_inside_sector(rect, circle_center, radius, sector_point_A, sector_point_B):
#the rect is a tuple with the coordinate of the 4 corners
#the sector_point_A is the point on the edge of the sector and the circle
#the sector_point_B is the other one
angle_A = sector_point_A.angle_to(vec(radius, 0)) % 360
#calculate the angle of the first sector point
angle_B = sector_point_B.angle_to(vec(radius, 0)) % 360
#same for the second one
for corner in rect:
if circle_center.distance_to(corner) <= radius:
if corner.angle_to(vec(radius, 0)) <= angle_A and corner.angle_to(vec(radius, 0)) >= angle_B:
return True
else:
return False
#this checks if any corner is in the sector, if yes, the rect and the sector collide
aight now I know the code has many flaws but I didnt complete it.
there are possibility that the rect and the sector collide without any corner of the rect being inside the sector, I would fix that by checking if any of the 3 corners of the sector (circle_center, sector_point_A, sector_point_B) would be inside the rect.
simple stuff and it would be a really good collision checking BUT! you gotta check 7 point! this is insanly slow, you gotta check if any of the corners of the rect is inside the sector and you gotta check if any of the corners of the sector is inside the rect, it will give you 100% accuracy but it is so slow.
is there any way I can check the collision of a sector and a rect without making an absurde function?
thanks alot if you help me on that, if you need I will make a real function testing the 7 points as I explained, the one I wrote was made on the go
Presumably you mean, by sector S of a circle, the Wikipedia definition: ⌔.
You need three functions:
Is a given point p in the sector S?
Do two segments intersect?
Does a segment intersect a circular arc?
I don't see how you can cover all cases without these three. All three have been
explored extensively, and code can be found all over the web.

With Pygame, is there a faster way to draw hundreds of small squares?

I'm attempting to make a tile based game, not done anything like this before so I'm learning as I go along. However, I've got a big problem with speed, and I was wondering if anyone had any solutions/advice. I tried to separate recalculating bits and the actual drawing, though as you can only move the camera currently, it's got to do both at once, and it's very noticeable how slow it runs if you have a small tilesize and large resolution.
I thought an idea would be to split it into chunks, so you calculate an x*x area, and instead of checking each tile if it's within the screen bounds, you only check the group of tiles, then somehow cache it the first time it's drawn so you then end up drawing a single image from memory. However I didn't find anything on that when googling it.
As to the drawing part, it runs to the effect of:
for tile in tile_dict:
pygame.draw.rect(precalculated stuff)
With the same tilesize as the image below, at 720p it runs at 100fps, and at 1080p it runs at 75fps. This is with literally nothing but drawing squares. Each block is a slightly different colour, so I can't just draw a bigger square. I know not to redraw every frame by the way.
As to the recalculation part, it's a bit longer but still quite easy to understand. I calculate which coordinates would be at the edge of the screen, and use that to build a list of all on screen tiles. I then delete any tiles that are outside of this area, move the cooordinate to the new location if the tile has moved on screen, and calculate any tiles that have just appeared. This runs at about 90fps at 720p, or 45fps at 1080p, which is really not good.
def recalculate(self):
overflow = 2
x_min = self.cam.x_int + 1 - overflow
y_min = self.cam.y_int + 1 - overflow
x_max = self.cam.x_int + int(self.WIDTH / self.tilesize) + overflow
y_max = self.cam.y_int + int(self.HEIGHT / self.tilesize) + overflow
self.screen_coordinates = [(x, y)
for x in range(x_min, x_max)
for y in range(y_min, y_max)]
#Delete the keys that have gone off screen
del_keys = []
for key in self.screen_block_data:
if not x_min < key[0] < x_max or not y_min < key[1] < y_max:
del_keys.append(key)
for key in del_keys:
del self.screen_block_data[key]
#Rebuild the new list of blocks
block_data_copy = self.screen_block_data.copy()
for coordinate in self.screen_coordinates:
tile_origin = ((coordinate[0] - self.cam.x_int) - self.cam.x_float,
(coordinate[1] - self.cam.y_int) - self.cam.y_float)
tile_location = tuple(i * self.tilesize for i in tile_origin)
#Update existing point with new location
if coordinate in self.screen_block_data:
self.screen_block_data[coordinate][2] = tile_location
continue
block_type = get_tile(coordinate)
#Generate new point info
block_hash = quick_hash(*coordinate, offset=self.noise_level)
#Get colour
if coordinate in self.game_data.BLOCK_TAG:
main_colour = CYAN #in the future, mix this with the main colour
else:
main_colour = TILECOLOURS[block_type]
block_colour = [min(255, max(0, c + block_hash)) for c in main_colour]
self.screen_block_data[coordinate] = [block_type,
block_colour,
tile_location]
I realised in what I wrote above, I probably could cache the info for a 10x10 area or something to cut down on what needs to be done when moving the camera, but that still doesn't get around the problem with drawing.
I can upload the full code if anyone wants to try stuff with it (it's split over a few files so probably easier to not paste everything here), but here's a screenshot of how it looks currently for a bit of reference:
To increase the speed of drawing the small squares, you can draw them onto non-screen surface (any pygame surface that will be big enough to hold all the squares) and then blit this surface on the screen with correct coordinates.
This way you won't need to check if any squares are outside the screen and it will be only necessary to provide inverted camera (viewpoint) coordinates (If camera position is [50,20] then you should blit the surface with tiles onto [-50,-20]).

How can I make my circles fly off the screen in pygame?

I am a begginner at python and I'm trying to make a circle game. So far it draws a circle at your mouse with a random color and radius when you click.
Next, I would like the circle to fly off the screen in a random direction. How would I go about doing this? This is the main chunk of my code so far:
check1 = None
check2 = None
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit
if event.type == MOUSEBUTTONDOWN:
last_mouse_pos = pygame.mouse.get_pos()
if last_mouse_pos:
global check
color1 = random.randint(0,255)
color2 = random.randint(0,255)
color3 = random.randint(0,255)
color = (color1,color2,color3)
radius = random.randint (5,40)
posx,posy = last_mouse_pos
if posx != check1 and posy != check2:
global check1, check2
screen.lock()
pygame.draw.circle(screen, color, (posx,posy), radius)
screen.unlock()
check1,check2 = posx,posy
pygame.display.update()
Again, I want the circle to fly off the screen in a random direction.
I have made a few attempts but no successes yet.
Also, thanks to jdi who helped me s
When you create the circle (on click), generate 2 random numbers. These will be your (x,y) components for a two dimensional Euclidean velocity vector:
# interval -1.0 to 1.0, adjust as necessary
vx, vy = ( (random.random()*2) -1, (random.random()*2) - 1 )
Then after the ball has been created, on each iteration of the game loop, increment your ball's position by the velocity vector:
posx, posy = posx + vx, posy + vy
Note that the overall speed might be variable. If you want to always have a speed of 1.0 per seconds, normalize the vector:
To normalize the vector, you divide it by its magnitude:
So in your case:
And hence:
So in Python, after importing math with import math:
mag = math.sqrt(vx*vx + vy*vy)
v_norm = vx/mag, vy/mag
# use v_norm instead of your (vx, vy) tuple
Then you can multiply this with some speed variable, to get reliable velocity.
Once you progress to having multiple objects moving around and potentially off screen, it is useful to remove the offscreen objects which have no chance of coming back, and have nothing to do with your program anymore. Otherwise, if you keep tracking all those offscreen objects while creating more, you get essentially a memory leak, and will run out of memory given enough time/actions.
While what you are doing right now is quite simple, assuming you haven't already, learning some basic vector math will pay itself off very soon. Eventually you may need to foray into some matrix math to do certain transformations. If you are new to it, its not as hard as it first looks. You can probably get away with not studying it, but you will save yourself effort later if you start attempting to do more ambitious things.
Right now, you are doing the following (drastically simplifying your code)...
while True:
if the mouse was clicked:
draw a circle on the screen where the mouse was clicked
Let's make things a little easier, and build up gradually.
Start with the circle without the user clicking
To keep things simple, let's make the circle near the top left of the screen, that way we can always assume there will be a circle (making some of the logic easier)
circle_x, circle_y = 10,10
while True:
draw the circle at circle_x, circle_y
pygame.display.update()
Animate the circle
Before going into too much about "random directions", let's just make it easy and go in one direction (let's say, always down and to the right).
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
draw the circle at circle_x, circle_y
update the display
Now, every time through the loop, the center of the circle moves a bit, and then you draw it in its new position. Note that you might need to reduce the values that you add to circle_x and y (in my code, 0.1) in case the circle moves too fast.
However, you'll notice that your screen is now filling up with circles! Rather than one circle that is "moving", you're just drawing the circle many times! To fix this, we're going to "clear" the screen before each draw...
screen = ....
BLACK = (0,0,0) # Defines the "black" color
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
screen.fill(BLACK)
draw the circle at circle_x, circle_y
update the display
Notice that we are "clearing" the screen by painting the entire thing black right before we draw our circle.
Now, you can start work the rest of what you want back into your code.
Keep track of multiple circles
You can do this by using a list of circles, rather than two circle variables
circles = [...list of circle positions...]
while True:
# Update
for circle in circles:
... Update the circle position...
# Draw
screen.fill(BLACK)
for circle in circles:
draw the circle at circle position # This will occur once for each circle
update the display
One thing to note is that if you keep track of the circle positions in a tuple, you won't be able to change their values. If you're familiar with Object Oriented Programming, you could create a Circle class, and use that to keep track of the data relating to your circles. Otherwise, you can every loop create a list of updated coordinates for each circle.
Add circle when the user clicks
circles = []
while True:
# event handling
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
circles.append( pos ) # Add a new circle to the list
# Update all the circles
# ....
# Draw
clear the screen
for circle_position in circles:
draw the circle at circle_position # This will occur once for each circle
update the display
Have the circle move in a random direction
This is where a good helping of math comes into play. Basically, you'll need a way to determine what to update the x and y coordinate of the circle by each loop. Keep in mind it's completely possible to just say that you want it to move somewhere between -1 and 1 for each axis (X, y), but that isn't necessarily right. It's possible that you get both X and Y to be zero, in which case the circle won't move at all! The next Circle could be 1 and 1, which will go faster than the other circles.
I'm not sure what your math background is, so you might have a bit of learning to do in order to understand some math behind how to store a "direction" (sometimes referred to as a "vector") in a program. You can try Preet's answer to see if that helps. True understanding is easier with a background in geometry and trigonometry (although you might be able to get by without it if you find a good resource).
Some other thoughts
Some other things you'll want to keep in mind:
Right now, the code that we're playing with "frame rate dependent". That is, the speed in which the circles move across the screen is entirely dependent on how fast the computer can run; a slower computer will see the circles move like snails, while a faster computer will barely see the circles before they fly off the screen! There are ways of fixing this, which you can look up on your own (do a search for "frame rate dependence" or other terms in your favorite search engine).
Right now, you have screen.lock() and screen.unlock(). You don't need these. You only need to lock/unlock the screen's surface if the surface requires it (some surfaces do not) and if you are going to manually access the pixel data. Doing things like drawing circles to the screen, pygame in lock/unlock the surfaces for you automatically. In short, you don't need to deal with lock/unlock right now.

Categories

Resources