Colliding shapes in python - python

My plan is to create a "simple" 2-player minigame game (for ex. sumo and race).
My goal would be to efficiently implement collisions (my current code can deal only with simple wall physics and movement of the objects) with square, circle (and triangle?) shaped objects that can be either part of the environment (for ex. "rocks" or immovable obstacles) or part of the user controlled items (for ex. "cars" or pushable obstacles). It would be also nice to know how mass could also be accounted for in the collisions.
There are two aspects I need help with:
Different types of dynamic collisions between two moving objects (that have a mass and 2D vector)
(the physics part not the detection).
Making sure everything that needs to collide, collides fast enough (so that my slow computer could still render more than 40-60 frames per second) , and according to the specific rules (or if it would be possible then according to one rule?). So that it would not also be hard to manage the objects that need to be collided (add, remove, modify and so on).
Or should I just implement two types of collision for ex. static + dynamic circle and dynamic + dynamic circle?
def checkcollisions(object1, object2):
# x is the current x position
# y is the current y position
# angle is the current vector angle (calculated from x and y with pythagoros
# speed is the length of the vector
dx = object1.x - object2.x
dy = object1.y - object2.y
dist = hypot(dx, dy)
if dist < object1.radius + object2.radius:
angle = atan2(dy, dx) + 0.5 * pi
total_mass = object1.mass + object2.mass
'''''http://www.petercollingridge.co.uk/pygame-physics-simulation/mass'''''
if (0.79 <= object1.angle < 2.36 or 0.79-2*pi <= object1.angle < 2.36-2*pi) or (3.93 <= object1.angle < 5.5 or 3.93-2*pi <= object1.angle < 5.5-2*pi) and ((0.79 <= object2.angle < 2.36 or 0.79-2*pi <= object2.angle < 2.36-2*pi) or (3.93 <= object2.angle < 5.5 or 3.93-2*pi <= object2.angle < 5.5-2*pi)):
(object2angle, object2speed) = vectorsum((object2.angle, object2.speed*(object2.mass-object1.mass)/total_mass), (angle+pi, 2*object1.speed*object1.mass/total_mass))
(object1angle, object1speed) = vectorsum((object1.angle, object1.speed*(object1.mass-object2.mass)/total_mass), (angle, 2*object2.speed*object2.mass/total_mass))
else:
'''''https://en.wikipedia.org/wiki/Elastic_collision'''''
CONTACT_ANGLE = angle
x = (((object1.speed * cos(object1.angle - CONTACT_ANGLE) * (object1.mass-object2.mass)+ 2*object2.mass*object2.speed*cos(object2.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object1.speed*sin(object1.angle - CONTACT_ANGLE)*cos(CONTACT_ANGLE + 0.5 * pi)
y = (((object1.speed * cos(object1.angle - CONTACT_ANGLE) * (object1.mass-object2.mass)+ 2*object2.mass*object2.speed*cos(object2.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object1.speed*sin(object1.angle - CONTACT_ANGLE)*sin(CONTACT_ANGLE + 0.5 * pi)
object1angle = pi/2 - atan2(y, x)
object1speed = hypot(x, y)
x = (((object2.speed * cos(object2.angle - CONTACT_ANGLE)*(object2.mass-object1.mass)+2*object1.mass*object1.speed*cos(object1.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object2.speed*sin(object2.angle - CONTACT_ANGLE)*cos(CONTACT_ANGLE + 0.5 * pi)
y = (((object2.speed * cos(object2.angle - CONTACT_ANGLE)*(object2.mass-object1.mass)+2*object1.mass*object1.speed*cos(object1.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object2.speed*sin(object2.angle - CONTACT_ANGLE)*sin(CONTACT_ANGLE + 0.5 * pi)
object2angle = pi/2 - atan2(y, x)
object2speed = hypot(x, y)
(object2.angle, object2.speed) = (object2angle, object2speed)
(object1.angle, object1.speed) = (object1angle, object1speed)
object1.speed *= 0.999
object2.speed *= 0.999
overlap = 0.5*(object1.radius + object2.radius - dist+1)
object1.x += sin(angle)*overlap
object1.y -= cos(angle)*overlap
object2.x -= sin(angle)*overlap
object2.y += cos(angle)*overlap
'''''http://www.petercollingridge.co.uk/pygame-physics-simulation/mass'''''
def vectorsum(vectorx, vectory): # Every array's first number is the degree from 0, the second is speed
x = sin(vectory[0]) * vectory[1] + sin(vectorx[0]) * vectorx[1]
y = cos(vectory[0]) * vectory[1] + cos(vectorx[0]) * vectorx[1] # Calculating new vectors from anle and lenght
angle = pi / 2 - atan2(y, x) # Calculating the degree
speed = hypot(x, y) # Calculating the speed
return angle, speed
(I'm just a beginner with Python (or English) so keep that in mind please.)

Collision detection is very easy in pygame. Take a look at using pygame.sprite. They have several functions to detect collisions. (spritecollide, groupcollide, etc) If you have some complex collision interaction generally you use the rect or circle to see if they collide, then only do your complex calculations on those. Though for most games you do not need to have the cost of perfect collision detection, close enough is good enough.
As far was what happens when you collide, that is more physics then programming. Some concepts to keep in mind are: momentum conservation, elastic vs inelastic collisions, deflection angle. "How to building a 2D physics engine" is a bit too broad for SO question. Maybe look at how-to-create-a-custom-2d-physics-engine-oriented-rigid-bodies

Related

How to predict trajectory of ball in a ping pong game, for AI paddle prediction?

This might be a more mathematical question, but I'm trying to get my head around how I can program an unbeatable AI for a ping pong game. From what I have read so far, it would be to simulate the trajectory of a ball when it is moving in the direction towards the AI Paddle.
In this game I have a ball and I can read its x and y position on the board, and then read it again in the next iteration which will allow me to calculate the velocity in the x and y direction.
But I'm not sure how to program how and where the ball will reach the AI paddle's goal position, and consider how many times the ball will bounce off the walls will requires me to use some geometry. But I can't get my head around it and how I will be programming it.
So far what I have thought of is the variables I've been given: the size of the table in x and y direction, the position of the ball "currently" and before in order to get its velocity in x and y direction. My first assumption is to find out a way to calculate whether the ball will hit the walls or the AI goal side instead?
There is a more direct way of doing this instead of repeated "raycasting":
def predict(x, y, vx, vy, h, b):
"""
:param x: ball x position
:param y: ball y position
:param vx: ball x velocity
:param vy: ball y velocity
:param h: the field height
:param b: the y position the prediction is for
:return: ball x position at y = b
"""
m = vy / vx # slope
c = -x * m + y # y-intercept
val = (m * b + c) % (2 * h)
return min(val, 2 * h - val)
Now, step by step
m = vy / vx # slope
c = -x * m + y # y-intercept
val = (m * b + c)
A simple linear function showing the ball's current path.
This works, but only if the ball never hits a side wall.
A Model
Imagine there were fields with the same height on both sides of the original one, stretching into infinity.
Now 'the number of bounces' has become 'the number of cells the ball travels'.
Additionally, if the number of bounces is even, the distance from the lower border of the cell it hits to the point of impact is the same as the height the actual ball would hit at in the real cell.
Therefore
(m * b + c) % (2 * h)
To cover odd bounces as well, you need to mirror the graph around h.
Here is a graphic explanation:
And since the irrelevant graph is the one with values above h, you take the minimum.
Possible Problems
In some languages, the % is a remainder operator, though not python.
If the predictions are negative in some cases add this.
val = ((m * b + c) % (2 * h) + 2 * h) % (2 * h)
This function depends on 'accurate' collision.
So if the bounces are handled in a way similar to this,
if y not in range(0, y_max):
vy *= -1
the predictions will be slightly off.
If you can change the core game, use
if y < 0:
y *= -1
vy *= -1
elif y > y_max:
y = 2 * y_max - y
vy *= -1
A divide by zero exception will be thrown if vx is 0, but since the ball will never hit a wall in this case, this should be handled by the ball movement logic.
Snippets are cool, but functions are better. I can't prove this works, but it seems to.
float pong_get_ball_endpoint(float xpos, float ypos, float xspeed, float yspeed)
{
// In the following, the fabs() mirrors it over the bottom wall. The fmod wraps it when it exceeds twice
// the top wall. If the ball ends up in the top half of the double height section, we reflect it back
auto deltaX = (xspeed > 0) ? (BAT2_X - xpos) : -(xpos - BAT1_X); // How far from ball to opponent bat
auto slope = yspeed / xspeed; // Rise over run, ie: deltaY per X
float newY = fmod(fabs(ypos + deltaX * slope), (2 * MATRIX_HEIGHT)); // New Y, but wrappped every 2*height
if (newY > MATRIX_HEIGHT) // If in top half, reflect to bottom
newY = 2 * MATRIX_HEIGHT - newY;
return newY;
}

Path-finding in 2D space

I am creating a game where I want an enemy to path track onto the player - who can move in any direction on the 2D plane. At first, I tried...
self.bat_x += (player_rect.centerx - self.rect.centerx) / 60
self.bat_y += (player_rect.centery - self.rect.centery) / 60
Here the path-tracking works fine. I divide each value by 60 so that the enemy doesn't just appear and stick on to my player / to slow the movement of the enemy down. However, the further away the enemy is, the faster it is. The closer the bat gets, the slower the bat gets. This is because, using the x-axis for example, when the distance between the player and the enemy is smaller, player_rect.centerx - self.rect.centerxis smaller so less gets added to self.bat_x. Is there a way so that the path-finding still works but the speed is constant? Or does anyone know a different path-finding method and how to implement it?
Pythagoras is your friend
x = player_rect.centerx - self.rect.centerx
y = player_rect.centery - self.rect.centery
norm = (x**2 + y**2)**0.5
const = 1/60
self.bat_x += const * x / norm
self.bat_y += const * y / norm
One way would be using the locations of the player and enemy to find the slope/angle of the line connecting them.
Considering that enemy is at (x1, y1) and player is at (x2, y2).
Then
angle = arctan((y2 - y1)/x2-x1))
Note that x2 - x1 could be zero, so take care of that case.
After finding the line, you could use polar coordinates to find the next position
For eg
x += speed * sin(angle)
Y += speed * cos(angle)

Create random pattern gradient maps?

So I have been working on some procedural generating and i managed to create a circular monochrome gradient map which i use to generate other maps.
def create_circular_gradient(self, world):
center_x, center_y = self.mapSize[1] // 2, self.mapSize[0] // 2
circle_grad = np.zeros_like(world)
for y in range(world.shape[0]):
for x in range(world.shape[1]):
distx = abs(x - center_x)
disty = abs(y - center_y)
dist = math.sqrt(distx * distx + disty * disty)
circle_grad[y][x] = dist
# get it between -1 and 1
max_grad = np.max(circle_grad)
circle_grad = circle_grad / max_grad
circle_grad -= 0.5
circle_grad *= 2.0
circle_grad = -circle_grad
# shrink gradient
for y in range(world.shape[0]):
for x in range(world.shape[1]):
if circle_grad[y][x] > 0:
circle_grad[y][x] *= 20
# get it between 0 and 1
max_grad = np.max(circle_grad)
circle_grad = circle_grad / max_grad
grad_world = self.apply_gradient_noise(world, circle_grad)
return grad_world
Now my question is, how exactly would i need to modify this to create a swirly gradient in a random way? and even maybe a box or a diverse linear gradients?
Note that world is a randomly generated 2d array (and can be any shape like 100x100 or even 100x50 but of course i use it a whole lot bigger) and to the circle gradient, I add a perlin noise to randomize the generation. The code that randomizes it will work for any map but i'm not sure where to start creating different shapes of gradient.
Edit
Ignore the magic numbers, that was the only way i found how to create a circular monochrome gradient

Calculate LookAt like function in 2 dimensions

I'm trying to create a lookAt function in 2 dimensions using Python, so here's my code right now.
from math import *
def lookAt(segment, originPoint):
segmentCenterPoint = getSegmentCenter(segment)
for i in range(2):
vtx = getVertex(segment, i)
x, y = getVertexCoord(vtx)
# Calculate the rotation angle already applied on the polygon
offsetAngle = atan2(y - segmentCenterPoint.y, x - segmentCenterPoint.x)
# Calculate the rotation angle to orient the polygon to an origin point
orientAngle = atan2(segmentCenterPoint.y - originPoint.y, segmentCenterPoint.x - originPoint.x)
# Get the final angle
finalAngle = orientAngle - (pi / 2)
if offsetAngle >= pi:
offsetAngle -= pi
elif offsetAngle < 0:
offsetAngle += pi
finalAngle += offsetAngle
# Temporary move the point to have its rotation pivot to (0,0)
tempX = x - segmentCenterPoint.x
tempY = y - segmentCenterPoint.y
# Calculate coords of the point with the rotation applied
s = sin(finalAngle)
c = cos(finalAngle)
newX = tempX * c - tempY * s
newY = tempX * s + tempY * c
# Move the point to the initial pivot
x = newX + segmentCenterPoint.x
y = newY + segmentCenterPoint.y
# Apply new coords to the vertex
setVertexCoord(vtx, x, y)
I tried some examples manually and worked well, but when I tried to apply the function on thousands of segments, it seems some segment are not well oriented.
I probably missed something but i don't know what it is. Also, maybe there's a faster way to calculate it ?
Thank you for your help.
EDIT
Here is a visualization to understand better the goal of the lookAt.
The goal is to find A' and B' coordinates, assuming we already know O, A and B ones. ([AB] is the segment we need to orient perpendicularly to the point O)
To find positions of A' and B', you don't need rotate points (and dealt with angles at all).
Find vector OC = segmentCenterPoint - originPoint
Make normalized (unit) vector oc = OC / Length(OC)
Make perpendicular vector P = (-oc.Y, oc.X)
Find CB length lCB
Find A' = C + lCB * P
B' = C - lCB * P

Python gravity simulator behaving strangely

I'm making a gravity simulation in Python (in 3D with VPython, to be exact) and I'm sure there's nothing wrong with the code, but it behaves strangely when two objects get close to each other.
My inspiration is http://testtubegames.com/gravity.html. Note how you can place two planets with no velocity, they move towards each other, overtake, decelerate and turn back. In my program, they overtake, and decelerate, but only proportionately to the distance, so technically it should never turn back anyway.
I realize that the law f=G*(m1*m2)/r**2 wouldn't work if r (the distance) is or gets too close to 0, so I have included a max-out, so if it is less than 1 it is set to 1 (units not in pixels, by the way), but it still does not work.
Simple logic also suggests that the objects should not react in this way, so the next thing that follows is that I must be missing something.
Here is an extract of the code:
from visual import *
a = sphere(x=-10,mass=10, vel=vector())
b = sphere(x=10, mass=10, vel=vector())
while 1:
rate(20)
#distance between the two objects, a and b, where a.r.mag would be the magnitude of the vector
a.r = b.pos - a.pos
b.r = a.pos - b.pos
a.force = a.r
if a.r.mag > 1:
a.force.mag = (a.mass * b.mass) / a.r.mag**2
else:
a.force.mag = (a.mass * b.mass) / 1
a.vel = a.vel + a.force / a.mass
b.force = b.r
if b.r.mag > 1:
b.force.mag = (a.mass * b.mass) / b.r.mag**2
else:
b.force.mag = (a.mass * b.mass) / 1
b.vel = b.vel + b.force / b.mass
a.pos = a.pos + a.vel
b.pos = b.pos + b.vel
EDIT: Code re-written in response to shockburner:
from visual import *
import sys
limit2 = sys.float_info.min
limit = limit2**0.5
timestep = 0.0005
a = sphere(x=-5,mass=10, vel=vector())
b = sphere(x=5, mass=10, vel=vector())
def force(ob1, ob2):
ob1.r = ob2.pos - ob1.pos
ob1.force = ob1.r + vector()
if ob1.r.mag > limit:
ob1.force.mag = (ob1.mass * ob2.mass) / ob1.r.mag2
else:
ob1.force.mag = (ob1.mass * ob2.mass) / limit2
return ob1.force
while 1:
rt = int(1/timestep)
rate(rt)
a.acc = force(a, b) / a.mass
b.acc = force(b, a) / b.mass
a.pos = a.pos + timestep * (a.vel + timestep * a.acc / 2)
b.pos = b.pos + timestep * (b.vel + timestep * b.acc / 2)
a.acc1 = force(a,b) / a.mass
b.acc1 = force(b,a) / b.mass
a.vel = a.vel + timestep * (a.acc + a.acc1) / 2
b.vel = b.vel + timestep * (b.acc + b.acc1) / 2
Any help or pointer in the right direction would be greatly appreciated, and if the answer turns out to be idiotically simple (which with me is usually the case) remember I am quite an idiot anyway.
My guess is that your problem stems from numerical errors in your integration method. It seems you are using the Euler method which is prone to large numerical errors as it is a first-order integration method. I would recommend velocity verlet for numerically integrating orbits as it is a second-order method that also preserves total energy (kinetic + gravitational potential) to machine precision. This energy conservation generally makes velocity verlet more stable than 4th-order Runge–Kutta, because bound orbits stay bound.
Also you might want to consider having a dynamic time step as opposes to a static one. When your particles are closed together velocities and positions change faster. Thus in order to reduce your numerical errors you need to take a smaller time step.
Finally, I would make your limiter (if a.r.mag > 1:) as small as possible/practical. I'd try the following:
import sys
limit2 = sys.float_info.min
limit = limit2**.5
...
if a.r.mag > limit:
a.force.mag = (a.mass * b.mass) / a.r.mag**2
else:
a.force.mag = (a.mass * b.mass) / limit2
...
I've had this problem before too. If you just go directly to Runge-Kutta, everything will sort itself out. This pdf will explain how to incorporate the method: http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf. Good luck!

Categories

Resources