I'm making a pool-like game and I'm having a bit of trouble programming the collision for the pool balls.
Initially I pieced together a weird approximation based off of this question, and it seemed to calculate angles somewhat alright, but it didn't preserve momentum.
So then I (just today) wrote some code directly following Wikipedia's instructions (although my code assumed equal pool ball mass):
It seems to calculate angles alright as well, but it still isn't preserving momentum, with every collision it adds energy to the system! However, I tested head-on ball collision with differing speeds, and it seemed to preserve momentum fine.
oldSelfVel = self.velocity
relPosVec = Vector2d(self.x - ball.x, self.y - ball.y)
self.velocity = self.velocity.minus(relPosVec.timesScalar(self.velocity.minus(ball.velocity).dot(relPosVec) / (relPosVec.magnitude ** 2)))
opRelPosVec = relPosVec.timesScalar(-1)
ball.velocity = ball.velocity.minus(opRelPosVec.timesScalar(ball.velocity.minus(oldSelfVel).dot(opRelPosVec) / (opRelPosVec.magnitude ** 2)))
If you need a reference for the methods my Vector class has - .minus subtracts, .timesScalar multiplies by a scalar, .dot calculates dot product, and .magnitude calculates magnitude. relPosVec is the vector representing the distance between the balls, and opRelPosVec is the opposite of relPosVec.
What's the problem here? Is Wikipedia's formula wrong? Or did I implement it wrong?
If you have any further questions just ask me in the comments.
Related
I wrote algorithm to calculate racing line and work fine for me. I have a problem with the car facing forward.
I use the function to determine the position of the car:
car_p = getSplinePoint()
I get x and y cords.
I can also use function
car_g = getSplineSlope()
to get slope of the track.
I transform my image by:
self.image = self.src_image
self.image = pygame.transform.rotate(self.src_image, ANGLE)
I start my car with angle 0.
I don't know how to calculate the angle at which the car should be turned so that it is directed in the appropriate direction.
Could you tell my how I should calculate it?
Ok, so I don't use pygame so there might be a better solution but I've come up with this method:
Keep track of a vector (which we'l call v) and make it point to the direction to which the car's facing. Then, make another vector (u) which will go from the car to the next waypoint.
Then use this formula to get the cosine of the angle between both vectors:
cosθ = (v·u)/(||v||·||u||)
||v|| is the length of the vector v.
After you've obtained the cosine you can simply get the angle by calculating the arcosine of the result. It's a bit messy, but if pygame doesn't have any command to do this automatically then al least it will do the job.
I'm currently trying to develop a to-scale model of the universe using pygame. At the moment, when I'm calculating the x, y positions of the planets w.r.t. the sun, the planets are slowly falling towards the sun, despite only using equations for position based on the distance and angle of the planet (no force).
Here is the code snippet for calculating distance from a given star currently:
def d_obj(self, reference):
x_diff_sq = pow(self.x - reference.pos[0], 2)
y_diff_sq = pow(self.y - reference.pos[1], 2)
return pow(x_diff_sq + y_diff_sq, 0.5)
And then I pass what this function returns into the next function for calculating the position
def move(self, d):
self.theta += self.d_theta
self.x = int(d * math.cos(self.theta)) + total_d/2
self.y = int(d * math.sin(self.theta)) + total_d/2
total_d/2 is a co-ordinate offset and self.d_theta is the rotational period for the given planet.
Each planet has its initial position hard coded and I'm using this to calculate the difference between initial distance and current distance for all of the planets, every tick it is apparent that the planet moves about 1km towards the sun. Is there any way I can attempt to offset this?
I understand that in the scale of things where I'm drawing things in terms of millions of km, I'm just curious what part of these equations is causing the error. I've tried using the '**' operator over pow and after some research online found that pow is better used for powers involving floats.
Should also mention that all calculations are in kilometers, then before drawing, the planets radius and x, y are mapped to the screen from a set distance that is currently around 4 AU.
You're trying to move your planets in circles, right?
In your code, you
Use x and y to calculate distance,
Use delta_theta to calculate new theta,
Use new theta and distance to calculate new x and y.
You don't have to do all that. Instead, you can keep a hardcoded distance and just
Use delta_theta to calculate new theta,
Use new theta and (known) distance to calculate x and y for drawing.
Then your distance will not drift at all.
Side note: If you're planning to keep the planets moving for long times, make sure you keep your theta between 0 and 2*pi, or rounding errors will start kicking in and your theta accuracy will deteriorate.
You're thinking this will make adding moons and asteroids difficult.
Not really!
You can do the same for moons, by noting which planet they belong to, the distance to that planet, delta_theta and initial theta (based on their parent planet).
If you want to start doing ellipses instead of circles, you can change your calculations (use convenient constant orbital elements instead of distance and delta_theta, which will not be constant anymore) to apply Kepler's laws.
You can also add asteroids later. You can keep the Cartesian positions and velocities of the asteroids, and calculate their motion separately, after calculating the motion of all the "trivially" moving objects.
I have wrote a class in python that will randomly generate a line with a curve at the end. I've added movement to the class using an two variable: xChange and yChange. I've tried to add collision detection to the curve by calculating a y value (testY) which I got by rearranging the equation of an ellipse ((x-h)^2/a^2 + (y-k)^2/b^2 = 1).
if playerC[0] >= self.x1 and playerC[0] <= self.x4:
#Tests if the player coords are the same as the curves Y
testY = self.k + (self.b*math.sqrt(self.a**2-self.h**2+2*self.h*playerC[0]-playerC[0]**2))/self.a
pygame.draw.line(gameDisplay, WHITE, [0, testY ], [1000, testY])
playerC[0] is the fixed x-coordinate I need collision detection on.
I've tried using the same code on another project and it worked fine, however using it in this format seems to break it.
self.h and self.k are the only variables which will change.
This answer is based on the idea that the curve is some randomly curving line-section.
I would first calculate a bounding box for the curve, using this to initially perform a fast and efficient does-it-collide-at-all test. If the player does not collide with a bounding box, no further tests are needed.
Now it's been determined that playerC is in the vicinity of the curve, the code can do further tests. Depending on the size / complexity / shape of your curve, it may be efficient to split the line into N sub-lines (say 8?), and then bounding-box test those sections against your player. If high accuracy is needed, then further test against the pixels (or further sub-sub-lines) of the curve.
This type of splitting and testing is often implemented with a quadtree data structure. Using a quadtree mimics the process above. It quickly finds the relevant part of the collision test, so that means the code is not spending a huge amount of time processing thousands of points.
Failing all this, generate the list of points for the line, and test these (plus th e movement offset) against the player's bounding box.
I have a set of approximately 10,000 vectors max (random directions) in 3d space and I'm looking for a new direction v_dev (vector) which deviates from all other directions in the set by e.g. a minimum of 5 degrees. My naive initial try is the following, which has of course bad runtime complexity but succeeds for some cases.
#!/usr/bin/env python
import numpy as np
numVecs = 10000
vecs = np.random.rand(numVecs, 3)
randVec = np.random.rand(1, 3)
notFound=True
foundVec=randVec
below=False
iter = 1
for vec in vecs:
angle = np.rad2deg(np.arccos(np.vdot(vec, foundVec)/(np.linalg.norm(vec) * np.linalg.norm(foundVec))))
print("angle: %f\n" % angle)
while notFound:
for vec in vecs:
angle = np.rad2deg(np.arccos(np.vdot(vec, randVec)/(np.linalg.norm(vec) * np.linalg.norm(randVec))))
if angle < 5:
below=True
if below:
randVec = np.random.rand(1, 3)
else:
notFound=False
print("iteration no. %i" % iter)
iter = iter + 1
Any hints how to approach this problem (language agnostic) would be appreciate.
Consider the vectors in a spherical coordinate system (u,w,r), where r is always 1 because vector length doesn't matter here. Any vector can be expressed as (u,w) and the "deadzone" around each vector x, in which the target vector t cannot fall, can be expressed as dist((u_x, w_x, 1), (u_x-u_t, w_x-w_t, 1)) < 5°. However calculating this distance can be a bit tricky, so converting back into cartesian coordinates might be easier. These deadzones are circular on the spherical shell around the origin and you're looking for a t that doesn't hit any on them.
For any fixed u_t you can iterate over all x and using the distance function can find the start and end point of a range of w_t, that are blocked because they fall into the deadzone of the vector x. The union of all 10000 ranges build the possible values of w_t for that given u_t. The same can be done for any fixed w_t, looking for a u_t.
Now comes the part that I'm not entirely sure of: Given that you have two unknows u_t and w_t and 20000 knowns, the system is just a tad overdetermined and if there's a solution, it should be possible to find it.
My suggestion: Set u_t fixed to a random value and check which w_t are possible. If you find a non-empty range, great, you're done. If all w_t are blocked, select a different u_t and try again. Now, selecting u_t at random will work eventually, yet a smarter iteration should be possible. Maybe u_t(n) = u_t(n-1)*phi % 360°, where phi is the golden ratio. That way the u_t never repeat and will cover the whole space with finer and finer granularity instead of starting from one end and going slowly to the other.
Edit: You might also have more luck on the mathematics stackexchange since this isn't so much a code question as it is a mathematics question. For example I'm not sure what I wrote is all that rigorous, so I don't even know it works.
One way would be two build a 2d manifold (area on the sphere) of forbidden areas. You start by adding a point, then, the forbidden area is a circle on the sphere surface.
While true, pick a point on the boundary of the area. If this is not close (within 5 degrees) to any other vector, then, you're done, return it. If not, you just found a new circle of forbidden area. Add it to your manifold of forbidden area. You'll need to chop the circle in line or arc segments and build the boundary as a list.
If the set of vector has no solution, you boundary will collapse to an empty point. Then you return failure.
It's not the easiest approach, and you'll have to deal with the boundaries of a complex shape over a sphere. But it's guaranteed to work and should have reasonable complexity.
I have a program written for simple 2D collision detection and handling on my current billiards simulation, however it is rubbish am looking for a solution that involves proper physics, i.e. newtons laws of motion for coliisions. what I have so far, which does not conserverve momentum and simplifies physics is
def collide(ball1,ball2):
dx = ball1.x - ball2.x
dy = ball1.y - ball2.y
dist = (dx)**2+(dy)**2
if dist < (ball1.radius + ball2.radius)**2:
tangent = atan2(dy, dx)
angle = 0.5 * pi + tangent
angle1 = 2*tangent - ball1.angle
angle2 = 2*tangent - ball2.angle
speed1 = ball2.speed*e
speed2 = ball1.speed*e
(ball1.angle, ball1.speed) = (angle1, speed1)
(ball2.angle, ball2.speed) = (angle2, speed2)
ball1.x += sin(angle)
ball1.y -= cos(angle)
ball2.x -= sin(angle)
ball2.y += cos(angle)
and what i have to run the collisions is this, where the bounce() is for hitting against the wall
running = True
while running:
background()
for i,ball in enumerate(balls,1):
ball.bounce()
ball.move()
for ball2 in balls[i:]:
collide(ball,ball2)
ball.display()
pygame.display.flip()
i'm still pretty new to this so please change whatever is useless/stupid
I recall I did a simple billiard simulator a while back. As you mentioned this is for educational purposes I will spare you from the whole code (and I don't have to dig around for it too :) )
But basically, I kept track of how long time has elapsed since the last frame. I used this time to find out new positions of each ball given a speed vector of each ball. For collisions in a frame I had to figure out at which exact time two balls would collide, and then apply each collision at that exact time. Pseudo code would look something like:
while running:
frame_time_ms = time elapsed since last frame
collisions = all collisions that will happen during this frame, with the exact time of collision. (1)
while collisions:
collision = first collision in collisions
collision_time_ms = time of collision (1)
move all balls to collision_time_ms
collide the two balls in the collision (2)
collisions = all remaining collisions after the time of collision (1)
move all balls to the end time of the frame
So, you will need to bring back your geometry and physics knowledge to find out the key formulas to:
Given the start and end position of two balls in a frame (or part of
a frame), do they collide, and at which point do they collide.
Remember to include the radius of the balls as well here. This will
give you the time of collision.
Given two balls at exact collision position, how will their new speed vectors look like afterwards. Some hints is to use elastic collisions, and experiment with how elastic it actually is:
http://en.wikipedia.org/wiki/Elastic_collision For bonus points you
could also include rotation of the balls :)
Good luck! =)
Circle on circle collision is simple. Take the center point co ordinance, subtract them to determine the distance between each circle.
Then, if the distance is greater than the sum of both circles radii, than they do not touch. If it is equal they are touching, if it's less they are overlapping.
Simply, if they are touching, have them repel each other. You can do this in a few ways. If you keep track of the directions they move in, have them move in the opposite direction.
As for walls, just use each wall with > < statements. So if the pos of a circle has x coordinates less than the west wall, than it has passed that wall. Again, just have them repel the walls.
Circle Collison is very simple if however you want to do other shapes it will be unbelievably difficult. Unless you just pit circles around those shapes or use pixel perfect collision (this is very high performance demand.)
If you want highly accurate collision of non circles, get a physics engine.