Calculating direction after a 2d circle collision - python

I know how to calculate the scalar of the velocity vector after a collision with 2 circles
(as per this link: https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331)
These circles cannot rotate and do not have friction but can have different masses, however I cannot seem to find out any way to find the unit vector that I need to multiply the scalar of velocity by to get the new velocity of the particles after the collision.
I also know how to check if 2 circles are colliding.
Also, I am only dealing with this in a purely "maths-sense" (ie. the circles have a center and a radius), and would like to know how I can represent these circles on the screen in python 3.0.
The vector class:
class Vector():
def __init__(self,x,y):
self.x = x
self.y = y
def add(self, newVector):
return Vector(self.x+newVector.x, self.y+newVector.y)
def subtract(self,newVector):
return Vector(self.x-newVector.x, self.y-newVector.y)
def equals(self, newVector):
return Vector(newVector.x,newVector.y)
def scalarMult(self, scalar):
return Vector(self.x*scalar, self.y*scalar)
def dotProduct(self, newVector):
return (self.x*newVector.x)+(self.y*newVector.y
def distance(self):
return math.sqrt((self.x)**2 +(self.y)**2)
The circle class:
class Particles():
def __init__(self,currentPos, oldPos, accel, dt,mass, center, radius):
self.currentPos = currentPos
self.oldPos = oldPos
self.accel = accel
self.dt = dt
self.mass = mass
self.center = center
self.radius = radius
def doVerletPosition(currentPos, oldPos, accel, dt):
a = currentPos.subtract(oldPos)
b = currentPos.add(a)
c = accel.scalarMult(dt)
d = c.scalarMult(dt)
return d.add(b)
def doVerletVelocity(currentPos, oldPos, dt):
deltaD = (currentPos.subtract(oldPos))
return deltaD.scalarMult(1/dt)
def collisionDetection(self, center, radius):
xCenter = (self.radius).xComponent()
yCenter = (self.radius).yComponent()
xOther = radius.xComponent()
yOther = radius.yComponent()
if ((xCenter - xOther)**2 + (yCenter-yOther)**2 < (self.radius + radius)**2):
return True
else:
return False
I do know about AABBs, but I am only using around 10 particles for now, and AABBs are not necessary now.

You know that the force transmitted between the two discs has to go along the "normal vector" for this collision, which is easy to get - it's just the vector along the line connecting the centers of the two discs.
You have four constraints: conservation of momentum (which counts for two constraints since it applies in x and y), conservation of energy, and this "force along normal" constraint. And you have four unknowns, namely the x and y components of the final velocities. Four equations and four unknowns, you can solve for your answer. Due to my background in physics, I've written this out in terms of momentum instead of velocity, but hopefully that's not too hard to parse. (Note, for instance, that kinetic energy is equal to p**2/2m or 1/2 mv**2.)
## conservation of momentum
p_1_x_i + p_2_x_i = p_1_x_f + p_2_x_f ## p_1_x_i := momentum of disc _1_ in _x_ axis intially _i
p_1_y_i + p_2_x_i = p_1_y_f + p_2_y_f
## conservation of energy
(p_1_x_i**2 + p_1_y_i**2)/(2*m_1) + (p_2_x_i**2 + p_2_y_i**2)/(2*m_2) = (p_1_x_f**2 + p_1_y_f**2)/(2*m_1) + (p_2_x_f**2 + p_2_y_f**2)/(2*m_2)
## impulse/force goes along the normal vector
tan(th) := (x_2-x_1)/(y_2-y_1) # tangent of the angle of the collision
j_1_x := p_1_x_i - p_1_x_f # change in momentum aka impulse
j_1_y := p_1_y_i - p_1_y_f
tan(th) = -j_1_x/j_1_y
(I hope the notation is clear. It would be much clearer if I could use latex, but stackoverflow doesn't support it.)
Hope this helps!

Related

Why is the tower defense path all messy when I try to eliminate the tower placment on the path?

I want to finish my tower defense game as fast as I can.
I have trouble with if the tower is on the path with a given path list.
It will never work even is I try as hard as I can.
I already tried a lot of times to solve that problem like tech with tim's tower defense youtube tutorial. It made almost perfect sense why it was not working.
But no matter how hard I try, It seems to never work properly.
Find the link to the tutorial here.
WARNING:The video is pretty long.
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if dis < 130:
return False
return True
You might be thinking 'Why did I do this' so I changed it a bit:
import numpy as np
closest = []
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if len(closest) <= 2:
if dis < 130:
closest.append(point)
p1 = np.array([x, y])
p2 = np.array(closest[0])
p3 = np.array(closest[1])
dis = np.cross(p2-p1,p3-p1)/np.linalg.norm(p2-p1)
if dis < 90:
return False
return True
I did not recive any error messages, but you can still place towers on some spots on the path,
and you can't place towers on some points not on the path, and I was expecting it to be pretty neat.
As #ImperishableNight stated, the issue is that your function only compares each point on the path and checks if the distance is less than a certain threshold (130 pixels in your case). But this is not enough, since we are not only interested in the end points in each segment of the path, but also all of the points in between. For that, we need to calculate the distance between a point and a line segment.
I have written and commented on the following code using a simple Point class to replace whatever pygame provides. I break the problem up into a bunch of tiny functions to solve your problem. Please let me know if any of this is unclear.
import math
import random
class Point:
def __init__(self, x=0, y=0):
"""
A really simple Point class.
Replaces whatever pygame provides for this example.
"""
self.x = x
self.y = y
def __repr__(self):
"""
A convenient representation of a Point class.
So we see the x and y values when printing these objects.
"""
return "({0}, {1})".format(self.x, self.y)
def gen_rand_point(width, height):
"""
Creates random x and y values for a Point object
"""
rand_x = random.randint(0, width)
rand_y = random.randint(0, height)
point = Point(x=rand_x, y=rand_y)
return point
def gen_rand_point_list(width, height, num_points):
"""
Creates a list of random points using the previous function.
"""
points = []
for i in range(num_points):
point = gen_rand_point(width, height)
points.append(point)
return points
def points_to_segments(points, loop=False):
"""
Converts a list of points into a list of segments.
Offsets the point list and zips it to create "segments".
A segment is just a tuple containing two Point objects.
"""
starts = points
ends = points[1:] + [points[0]]
segments = list(zip(starts, ends))
if loop:
return segments
else:
return segments[:-1]
def calc_sqr_dist(point_a, point_b):
"""
Calculates the square distance between two points.
Can be useful to save a wasteful math.sqrt call.
"""
delta_x = point_b.x - point_a.x
delta_y = point_b.y - point_a.y
sqr_dist = (delta_x ** 2) + (delta_y ** 2)
return sqr_dist
def calc_dist(point_a, point_b):
"""
Calculates the distance between two points.
When you need a wasteful math.sqrt call.
"""
sqr_dist = calc_sqr_dist(point_a, point_b)
dist = math.sqrt(sqr_dist)
return dist
def calc_dot_product(segment_a, segment_b):
"""
Calculates the dot product of two segments.
Info about what the dot product represents can be found here:
https://math.stackexchange.com/q/805954
"""
a0, a1 = segment_a
b0, b1 = segment_b
ax = a1.x - a0.x
ay = a1.y - a0.y
bx = b1.x - b0.x
by = b1.y - b0.y
dot = (ax * bx) + (ay * by)
return dot
def calc_point_segment_dist(point, segment):
"""
Gets the distance between a point and a line segment.
Some explanation can be found here:
https://stackoverflow.com/a/1501725/2588654
"""
start, end = segment
sqr_dist = calc_sqr_dist(start, end)
#what if the segment's start and end are the same?
if sqr_dist == 0:
dist = calc_dist(point, start)
return dist
#what if it is not that easy?
else:
segment_a = (start, point)
segment_b = (start, end)#really is just segment...
dot = calc_dot_product(segment_a, segment_b)
t = float(dot) / sqr_dist
clamped_t = max(0, min(1, t))#clamps t to be just within the segment
#the interpolation is basically like a lerp (linear interpolation)
projection = Point(
x = start.x + (t * (end.x - start.x)),
y = start.y + (t * (end.y - start.y)),
)
dist = calc_dist(point, projection)
return dist
def calc_point_path_dist(point, path):
"""
Gets the distances between the point and each segment.
Then returns the minimum distance of all of these distances.
"""
dists = [calc_point_segment_dist(point, segment) for segment in path]
min_dist = min(dists)
return min_dist
if __name__ == "__main__":
"""
A fun example!
"""
width = 800
height = 600
num_path_points = 5
tower_range = 50
tower = gen_rand_point(width, height)
path_points = gen_rand_point_list(width, height, num_path_points)
path = points_to_segments(path_points)
dist = calc_point_path_dist(tower, path)
in_range = dist <= tower_range
print(dist, in_range)

new position of creature without using a library

Given the position of the creature, and the position of it's target, I'm trying to calculate the path to follow, limited by movement and rotation speed.
The creature has a look-orientation, and can only move forward along that orientation. It can not sidestep.
I am able to calculate the angle of difference between the creature's look-orientation and the target's position with the following code
def calculateAngleDifference(self):
deltaX = self.position_x - self.target[0]
deltaY = self.position_y - self.target[1]
try:
#atan is inverse tan
delta = degrees(atan(deltaY / deltaX)) - self.orientation
except:
delta = degrees(atan(deltaY / 1)) - self.orientation
return delta//1
this gives me a correct result, and stops when the creature is looking directly to the target.
However, I would like to move it during the same time, and thus the creature should walk a curve. It is impossible to have a target that is more than 45° different.
for the position calculation I use
def pointOnCircle(origin, distance=50, degree = 45, method=cos):
return origin + distance * method(radians(degree))
newX = pointOnCircle(self.position_x, distance=1, degree = 1, method=cos)
newY = pointOnCircle(self.position_y, distance=1, degree = 1, method=sin)
# input()
self.position_x = newX
self.position_y = newY
However, this never gets me to the target.

Getting a camera to Yaw, Pitch and Roll in a proper manner with PyOpenGL?

I have been struggling for quite a while now, trying to implement a camera which would rotate freely in any of its own axis (namely when increasing yaw, pitch and roll). I have managed to setup the camera to change its position properly depending on the current forward, right and up vectors, which usually do not mess up (and by usually I mean when I omit roll). Simply put, the camera moves forward, but the problem is when I try rotating the camera, which I do not know how to set up properly.
I spawn a camera to use with this class:
import math
import numpy
class Camera:
def __init__(self, position=(0, 0, 0), pitch=0, yaw=0, roll=0):
self.position = list(position)
self.yaw = yaw
self.pitch = pitch
self.roll = roll
self.direction = [0.0, 0.0, -1.0]
self.right = [1.0, 0.0, 0.0]
self.up = [0.0, 1.0, 0.0]
# Calculates where each camera vector (front, right, up) is facing given the current pitch, yaw, roll.
# The derived vectors are the camera's own coordinate system, while the pitch, yaw and roll are world-based so far.
def cameraVectors(self):
x = math.sin(math.radians(self.yaw))*math.cos(math.radians(self.pitch))
y = math.sin(math.radians(self.pitch))
z = math.cos(math.radians(self.yaw))*math.cos(math.radians(self.pitch))
self.direction = [x, y, z]
x = math.sin(math.radians(self.yaw + 90))*math.cos(math.radians(self.roll))
y = math.sin(math.radians(self.roll))
z = math.cos(math.radians(self.yaw + 90))*math.cos(math.radians(self.roll))
self.right = [x, y, z]
self.up = numpy.cross(self.direction, self.right)
# Simply increases the rotation when told to. Stuff below function is irrelevant (maybe) as it works alright.
def cameraRotate(self, rotX, rotY, rotZ):
self.yaw += rotX
self.pitch += rotY
self.roll += rotZ
self.cameraVectors()
def cameraMoveF(self, value):
self.position[0] += value * self.direction[0]
self.position[1] += value * self.direction[1]
self.position[2] += value * self.direction[2]
self.cameraVectors()
def cameraMoveB(self, value):
self.position[0] -= value * self.direction[0]
self.position[1] -= value * self.direction[1]
self.position[2] -= value * self.direction[2]
self.cameraVectors()
def cameraMoveL(self, value):
self.position[0] += value * self.right[0]
self.position[1] += value * self.right[1]
self.position[2] += value * self.right[2]
self.cameraVectors()
def cameraMoveR(self, value):
self.position[0] -= value * self.right[0]
self.position[1] -= value * self.right[1]
self.position[2] -= value * self.right[2]
self.cameraVectors()
def cameraMoveU(self, value):
self.position[0] += value * self.up[0]
self.position[1] += value * self.up[1]
self.position[2] += value * self.up[2]
self.cameraVectors()
def cameraMoveD(self, value):
self.position[0] -= value * self.up[0]
self.position[1] -= value * self.up[1]
self.position[2] -= value * self.up[2]
self.cameraVectors()
def getPosition(self):
return self.position
def getDirection(self):
return self.direction
def getRight(self):
return self.right
def getUp(self):
return self.up
def getPitch(self):
return self.pitch
def getYaw(self):
return self.yaw
def getRoll(self):
return self.roll
And I use this ViewMatrix which I create every frame:
def createViewMatrix(eyePos, eyeForward, eyeUp):
E = numpy.array(eyePos)
F = numpy.array(eyeForward)
U = numpy.array(eyeUp)
F = F/numpy.linalg.norm(F)
S = numpy.cross(F, U)
S = S/numpy.linalg.norm(S)
Uprim = numpy.cross(S, F)
mat = numpy.zeros(shape=(4, 4))
mat[0][0] = S[0]
mat[1][0] = S[1]
mat[2][0] = S[2]
mat[0][1] = Uprim[0]
mat[1][1] = Uprim[1]
mat[2][1] = Uprim[2]
mat[0][2] = -F[0]
mat[1][2] = -F[1]
mat[2][2] = -F[2]
mat[3][0] = -numpy.dot(S, E)
mat[3][1] = -numpy.dot(Uprim, E)
mat[3][2] = numpy.dot(F, E)
mat[0][3] = 0.0
mat[1][3] = 0.0
mat[2][3] = 0.0
mat[3][3] = 1.0
return mat
The transformation matrix, projection and view matrix work flawlessly when it comes to rendering the view - all objects render properly, but the problem is that I am struggling with setting up the camera's own coordinate system and to make it roll, pitch and yaw according to its own coordinate system. I am obviously wrong somewhere in the code, but I really do not know where the problem may be. I have put a short video showing how the camera renders the objects in the different camera rotations (Ex. when looking down and I turn right, the camera and its target both rotate according to the world Y-axis, not the camera's as I want and when I roll more weird stuff happens). The axis are represented as squares that are rotated to face the camera.
https://www.youtube.com/watch?v=aBaLkE1cUSo
At first I show normal rotations with yaw and pitch. You can notice how the more I pitch and yaw, the more the rotation becomes into a roll (because the yaw is related to the world coordinate system. Also inverts when the camera flips, obviously). Then I show the roll at around 0:48. At 1:00 I show you how the rolling messes up and the whole scene just weirds out.
Please, if anyone can find where and what I am missing, I would be really happy if you could share your thoughts! I am pulling my hair out and I just cannot find what I am missing. Some people mentioned transforming the camera or something, but I really cannot understand how to do all this and I did refer to a lot of resources...
Thank you in advance!
In the end of the day there is no way to make such rotations using the simple Euler rotations I was using. Even by trying to rotate the up, right and forward vector in numerous ways, eventually I resorted to working with Quaternions to achieve my goal. Now the rotations work like a charm!

I need to create a variable(velocity) with magnitude and direction. How can this be done?

Then with this velocity and acceleration and initial position find the next position(2D). The only tricky part is the creation of the vector!
Just use standard vector math. Distance is the Pythagorean theorem and magnitude is trigonometry:
from math import *
class Vector2D:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def direction(self):
return degrees(atan(self.y / self.x))
def magnitude(self):
return sqrt(self.x ** 2 + self.y ** 2)
You can create a class , then each instance (object) of that class would be a velocity object (vector) . A very simple example is -
class Velocity:
def __init__(self, mag, direction):
self.mag = mag
self.direction = direction
Then you can create velocity objects like -
v1 = Velocity(5,5)
v2 = Velocity(10,15)
You can access the magnitude and direction of each velocity as -
print(v1.mag)
>> 5
print(v1.direction)
>> 5
The above is a minimalistic example , you will need to add whatever operations (read functions) you want our velocity object to support.
You could create a velocity tuple, where magnitude is at index 0 and direction is at index 1. Then define acceleration as a float and a startingPos where x is at index 0 and y is at index 1.
#0: magnitude 1: direction (degrees above x axis)
velocity = (2.3, 55)
acceleration = 3.2
#0: x 1: y
startingPos = (-10, 0)

Physics simulator in water python

I'm having issues understanding how to simulate a situation like this: http://phet.colorado.edu/sims/density-and-buoyancy/buoyancy_en.html
The point of the program is to make a simulator - like the one in the link. I want to keep it realistic and use Python. I want to draw the simulation in Pygame.
In my program I ask for a couple of variables; the mass and the radius. The radius will be used to calculate the volume of a sphere and the mass will be used to calculate the buoyancy, gravity force and acceleration.
But the thing is, to keep everything in SI-units I ask for the radius in metre. Doing this while keeping my radius under 10cm, makes for a really small number. And when I use the Pygame module to draw a sphere at the size of 0.1m, it fails. So instead of doing that, I needed to use a bigger scale.
So here comes my main problem. How exacly should I scale the sphere? Say I wanted to define 100 pixels to be 1 metre. I would then have to multiply my radius by 100, since that would be the scale, but now that the sphere is bigger should the velocity also be multiplied by 100?
I've gotten really confused over this! Thanks for your time.
Don't know if you need to see this, anyhow.
Calculations.py
import math
class Formulas():
def __init__(self):
self.pi = 3.1415926535
self.gravity = 9.82 #m/s^2
self.density_water = 1000.0 #kg/m^3
self.density_air = 1.29 #kg/m^3
self.drag_sphere = 0.47
def object_buoyancy(self, volume, medium_density):
buoyancy = volume * medium_density * self.gravity #N
return buoyancy
def object_gravity(self, mass):
gravity_force = mass * self.gravity #N
return gravity_force
def object_volume_sphere(self, radius):
volume = 1.3333333 * self.pi * math.pow(radius, 3) #m^3
return volume
def object_mass(self, density, volume):
mass = volume * density #kg
return mass
def object_acceleration(self, gravity_force, buoyancy, mass):
total_force = gravity_force - buoyancy #N
acceleration = total_force / mass #m/s^2
return acceleration
def object_speed(self, acceleration, time, scale):
speed = acceleration * (float(time)/1000.0) #m/s
return speed
def surface_area(self, radius):
area = 4 * self.pi * math.pow(radius, 2)
return area
Like so many problems in physics, this one can be solved using dimensional analysis.
Let us take a look at those constants you defined:
self.pi = 3.1415926535
self.gravity = 9.82 #m/s^2
self.density_water = 1000.0 #kg/m^3
self.density_air = 1.29 #kg/m^3
self.drag_sphere = 0.47
In order to be consistent and scale all of your distances so that 1 m = 100 px, we need to scale your constants:
self.pi = 3.1415926535
self.gravity = 982.0 #px/s^2
self.density_water = 0.001 #kg/px^3
self.density_air = 0.00000129 #kg/px^3
self.drag_sphere = 0.47
The only other thing you have to do is increase your radius variable by a factor of 100. After that, the rest of your calculations will fall in line:
Your volume calculation will be correct, so
Your mass and surface area calculations will change, so
Your gravity and bouyancy will change, so
Your acceleration will change, so
Finally, your velocity will be correct.
In your equations/methods, I do not see where your medium_density is set, so that my have to change as well. But, in the end, all you have to do is scale all of your inputs that have a unit of "distance" and your output variable will be scaled correctly.

Categories