Gravity strength and jump height somehow dependant on framerate - Pygame - python

I'm currently in the early stages of making a classic 2D platformer using pygame for a school project, and I was experimenting with the jump mechanic, when I ran into this rather strange problem. Even though I have factored in the timedelta between updates, the jump length and height both get shorter when the amount of time between frames increases.
Here I've let 3 instances of the Player object jump across the screen, moving right with a constant speed of 700 pixels/second, and an initial upwards speed of 700 pixels/second. They each had an artificially increased minimal delta_time of 0.001s, 0.017s and 0.1s respectively.
This is how the new speed and position vectors are calculated inside of the Player object's update function, which is called once every frame (the delta_time is passed in from the main update loop):
self.speed.y += 1000.0 * delta_time
self.position += self.speed * delta_time
And this is how delta_time is calculated at the end of every cycle of the main update loop (it is initialized with a value of zero for the first frame):
delta_time = time.time() - pre_time
pre_time = time.time()
if delta_time < min_delta_time:
time.sleep(min_delta_time - delta_time)
delta_time += time.time() - pre_time
pre_time = time.time()
The min_delta_time represents the minimal time between frame updates.
After many attempts at fixing this, I'm pretty certain that the flaw lies in the line that updates the speed, but even after double checking the math, I still can't figure out what the problem is. At first I thought that it could be the delta_time being imprecise, but if that was the case the horizontal speed would also be affected, and the trails would still line up.
So can i change anything to make the jump/gravity more consistent?

This isn't an error in coding so much as a logical error in the model: by modeling an acceleration as taking place instantaneously at update points rather than continuously across the full time span, inaccuracies are introduced which will get larger the sparser the updates.
To illustrate this, consider an object accelerating at 1m/s^2 from stop. If we model this as above with a 1-second interval, after six seconds our model will put the object at 21m. Repeating with a 2-second interval, we'll put it at 24m, and a 3-second interval will put it at 27m. However, the real position should be 18m.
The solution is to consider average velocity over the simulated time span rather than the instantaneous velocity at the sample points. In other words, rather than counting the full span's worth of acceleration, then using the result as the velocity for the whole span, add half of the span's acceleration, use that velocity to calculate distance traveled in that span, then add the remaining half of the acceleration.
So your update logic would become something like:
self.speed.y += 500.0 * delta_time
self.position += self.speed * delta_time
self.speed.y += 500.0 * delta_time

Related

Calculating the speed a variable increases (python)

I have a small problem that I really cant figure out how to solve after some time googling.
My project consists of a rasberry pi and a rotary encoder that I will use as a sensor to see how fast a shaft is spinning. This is then coded in Python. My problem then is that I have no clue how to calculate this. The rotary encoder is connected directly to the shaft and will spin at the same speed as the shaft, and if I use this code the variable "counter" will increment with 1 per "click". Assuming thats what I have to go on, I need to know how I can calculate how fast counter increases so I can get the speed of the shaft. This is because the shaft will be spinning at diffrent speeds at all times.
The reason I need to know how fast the shaft is spinning is because I have a UI that will display the speed.
I would also appreciate if you have any other approuches that I have missed.
Thank you in advance.
This is how I would calculate the speed :
first add this at the top
import time
then a start time variable
clk = 17
dt = 18
starttime = time.time() ## used to calculate a duration
if you subtract starttime from time.time() you will get how long it has been since you defined the start time variable
( time.time() - starttime )
therefore you then add this
if dtState != clkState:
counter += 1
timetakentospinshaft = time.time() - starttime ## this is how long the shaft took to spin 360 degrees
starttime = time.time() # resetting the duartion
now you have the time taken, so you can calculate the speed by dividing the distance by the time
( speed = distance / time )
the distance can be calculated by multiplying the length of the shaft by 2π (calculation for circumference)
( distance = length of shaft * 2π )

Particle collisions with walls inside box

I want to simulate particle movement inside a box. When the particles hit one of the 4 walls in the box I want them to bounce back. Elastic collision [Drawing of velocity changes after collision][1]
EDIT
Added more code. Changed if test for (x,y,z) limit. I know that when particles hit the wall the velocity parallell to the wall will stay the same, but the velocity perpendicular to the wall will change to -v_perpendicular_vel_before
Full code
fig0 = plt.figure()
fig1 = plt.figure()
n = 1000 # time steps
M = 1000. #[kg]
N = 1000 #10**5
a = 0
There's no way I can reproduce the run, since you haven't posted full code; I'm doing this from visual inspection.
Among other things, you've left variables k, T, m undefined, so I have no idea of the magnitude of the velocities. Is the upper limit high enough for a particle to skip totally past a collision? If v*dt can be > tol*2, you can totally miss collisions. When I code things like this, I make sure that tol is bounded by the maximum change.
Other than that, your position update is correct.
What happens when you run this with a single particle? Can you get it to bounce off all three walls? What happened when you hand-coded two particles headed straight at one another along a single dimension? I would like to think that you tested these basic functions before you loaded in 10000 particles.
I'm not sure what L is in your code; the only rational idea I have is that it's the upper limit of the box dimensions, which I'm reading as 1.2^10-6 on your plot.
The biggest problem that I see is that your only collision check is against the x+ side of the box. You haven't check the other dimensions or the lower limit (0?) for anything, and there's no comparison of one particle to another. As a result, the only change you'll get is to have roughly half your particles hit the right wall (x+) and reverse direction. Other than that, everything will drift forever, with ever-decreasing x values.
HANDLE A BOUNCE
First of all, get your velocity and position expressions in synch with one another: each of these should be a triple (tuple or list), so you can index them together. You currently have them as the second index of your particle list (good idea), but three separate variables elsewhere (poor idea). Instead, make newVel a list, so you can simply loop through the particles and update:
for dim in range(3):
if r[i, dim] < tol or # hit the lower wall
r[i, dim] > b - tol: # hit the upper wall
v[i, dim] = -v[i, dim]
Also, please just update the positions appropriately; no need to involve the local, temporary variables:
for dim in range(3):
r[i, dim] += v[i, dim] * dt
WRITE SOME SERVICE FUNCTIONS
It often helps to write some general-use functions, just to get them out of the way when you're writing your main code. At the moment, you're handling a lot of details, when you should be worrying about only one technique at a time. For instance, since you're going to be handling particle collisions, you'll want a distance computation. Don't keep this in the middle of your active code; just write a function to do it. For instance:
def dist(a, b): # a, b are positions: triples of coordinates
return math.sqrt(sum([(a[dim] - b[dim])**2 for dim in range(3)]))
I won't comment on how to implement the particle collisions, because you haven't shown any effort to solve the problem yet, and you're not yet at a point to attack that extension. First, get this part working: one particle bouncing around, then two particles bouncing around, then maybe 4 particles to show that you can do it for an arbitrary quantity of particles.
Once you have that, you can worry about particle collisions. When you get to that point, make a good attempt; if you get stuck, post a new question.

Code to resemble a rowing stroke

I'm working on a small program displaying moving rowing boats. The following shows a simple sample code (Python 2.x):
import time
class Boat:
def __init__(self, pace, spm):
self.pace = pace #velocity of the boat in m/s
self.spm = spm #strokes per minute
self.distance = 0 #distance travelled
def move(self, deltaT):
self.distance = self.distance + (self.pace * deltaT)
boat1 = Boat(3.33, 20)
while True:
boat1.move(0.1)
print boat1.distance
time.sleep(0.1)
As you can see a boat has a pace and rows with a number of strokes per minute. Everytime the method move(deltaT) is called it moves a certain distance according to the pace.
The above boat just travels at a constant pace which is not realistic. A real rowing boat accelerates at the beginning of a stroke and then decelerates after the rowing blades left the water. There are many graphs online which show a typical rowing curve (force shown here, velocity looks similar):
Source: highperformancerowing.net
The pace should be constant over time, but it should change during the stroke.
What is the best way to change the constant velocity into a curve which (at least basically) resembles a more realistic rowing stroke?
Note: Any ideas on how to tag this question better? Is it an algorithm-problem?
If your goal is to simply come up with something visually plausible and not to do a full physical simulation, you can simply add a sine wave to the position.
class Boat:
def __init__(self, pace, spm, var=0.5):
self.pace = pace #average velocity of the boat in m/s
self.sps = spm/60.0 #strokes per second
self.var = var #variation in speed from 0-1
self.totalT = 0 #total time
self.distance = 0 #distance traveled
def move(self, deltaT):
self.totalT += deltaT
self.distance = self.pace * (self.totalT + self.var * math.sin(self.totalT * self.sps * 2*math.pi)
You need to be careful with the variation var, if it gets too high the boat might go backwards and destroy the illusion.
You can convert a curve like this into a polynomial equation for velocity.
A description/example of how to do this can be found at:
python numpy/scipy curve fitting
This shows you how to take a set of x,y coordinates (which you can get by inspection of your existing plot or from actual data) and create a polynomial function.
If you are using the same curve for each Boat object, you could just hard code it into your program. But you could also have a separate polynomial equation for each Boat object as well, assuming each rower or boat has a different profile.
You can perform simple integration of the differential equation of motion. (This is what you are already doing to get space as a function of time, with constant speed, x' = x + V.dt.)
Assume a simple model with a constant force during the stroke and no force during the glide, and drag proportional to the speed.
So the acceleration is a = P - D.v during stroke, and - D.v during glide (deceleration).
The speed is approximated with v' = v + a.dt.
The space is approximated with x' = x + v.dt.
If dt is sufficiently small, this motion should look realistic. You can refine the model with a more accurate force law and better integration techniques like Runge-Kutta, but I am not sure it is worth it.
Below an example plot of speed and space vs time using this technique. It shows speed oscillations quickly establishing a periodic regime, and quasi-linear displacement with undulations.

How to detect the point of collision between two circles and then create a vector to update the circle's position?

i'm writing a simple air hockey style game in CodeSkulptor (python 2). 3 circles, basically two paddles and a ball. I understand how to detect collisions between circles, but I have found it difficult to then accurately model how that circle would bounce off a user controlled circle. The user controlled circle (paddle) tends to consume the ball and the ball gets stuck. I think my code for the ball bouncing off a wall is pretty much the same as the code I wrote for the paddle collision.
I read a bit around some other posts and i've figured out that I need to get the point of collision and then somehow turn that into a vector to update the ball's position. Can anyone help me with that? Also, does the fact that the paddle is user controlled complicate things? I'm guessing the impact on the ball has to take into account the vector of the paddle as well?
Thanks for any responses, and please keep the maths as simple as you can.
Chris
Solution 1
The algorithm that works well for me and arbitrary shapes is
move the circle
test if it overlaps
if it overlaps: move it back
make it change the direction.
This way you should not get stuck if the other part is not moving in the mean time. Because by moving back you do not overlap afterwards and do mot trigger the bounce and change-direction twice.
Solution 2
Using vectors and only circles:
circle1x, circle1y, circle1radius, circle2x, circle2y, circle2radius
if (circle1x - circle2x) ** 2 + (circle1y - circle2y) ** 2 < (circle1radius + circle2radius) ** 2:
# they overlap using http://en.wikipedia.org/wiki/Pythagorean_theorem
# compute new direction
Changing Direction
computing the new direction can just ignore the speed of both circles but that does not look quite natural.
old_direction1 = (v1x, v1y)
velocity1 = (v1x ** 2 + v1y ** 2 ) ** 0.5
old_direction2 = (v2x, v2y)
distance = ((circle1x - circle2x) ** 2 + (circle1y - circle2y) ** 2) ** 0.5
new_direction1 = ((circle1x - circle2x) / distance * velocity1,
(circle1y - circle2y) / distance * velocity1)
new_direction2 = ((circle2x - circle1x), ...)
But you can use this new direction and the old direction to create a new direction that looks more natural. I did not test this out but it should look something like this:
combined_direction = old_direction + 2 * (old_direction dot-product new_direction) * new_direction
The dot product gives you the direction that is projected onto some vector have a read.
Also once you got this you can create elastic collisions considering how heavy the circles are: http://en.wikipedia.org/wiki/Collision

What is wrong with my gravity simulation?

As per advice given to me in this answer, I have implemented a Runge-Kutta integrator in my gravity simulator.
However, after I simulate one year of the solar system, the positions are still off by cca 110 000 kilometers, which isn't acceptable.
My initial data was provided by NASA's HORIZONS system. Through it, I obtained position and velocity vectors of the planets, Pluto, the Moon, Deimos and Phobos at a specific point in time.
These vectors were 3D, however, some people told me that I could ignore the third dimension as the planets aligned themselves in a plate around the Sun, and so I did. I merely copied the x-y coordinates into my files.
This is the code of my improved update method:
"""
Measurement units:
[time] = s
[distance] = m
[mass] = kg
[velocity] = ms^-1
[acceleration] = ms^-2
"""
class Uni:
def Fg(self, b1, b2):
"""Returns the gravitational force acting between two bodies as a Vector2."""
a = abs(b1.position.x - b2.position.x) #Distance on the x axis
b = abs(b1.position.y - b2.position.y) #Distance on the y axis
r = math.sqrt(a*a + b*b)
fg = (self.G * b1.m * b2.m) / pow(r, 2)
return Vector2(a/r * fg, b/r * fg)
#After this is ran, all bodies have the correct accelerations:
def updateAccel(self):
#For every combination of two bodies (b1 and b2) out of all bodies:
for b1, b2 in combinations(self.bodies.values(), 2):
fg = self.Fg(b1, b2) #Calculate the gravitational force between them
#Add this force to the current force vector of the body:
if b1.position.x > b2.position.x:
b1.force.x -= fg.x
b2.force.x += fg.x
else:
b1.force.x += fg.x
b2.force.x -= fg.x
if b1.position.y > b2.position.y:
b1.force.y -= fg.y
b2.force.y += fg.y
else:
b1.force.y += fg.y
b2.force.y -= fg.y
#For body (b) in all bodies (self.bodies.itervalues()):
for b in self.bodies.itervalues():
b.acceleration.x = b.force.x/b.m
b.acceleration.y = b.force.y/b.m
b.force.null() #Reset the force as it's not needed anymore.
def RK4(self, dt, stage):
#For body (b) in all bodies (self.bodies.itervalues()):
for b in self.bodies.itervalues():
rd = b.rk4data #rk4data is an object where the integrator stores its intermediate data
if stage == 1:
rd.px[0] = b.position.x
rd.py[0] = b.position.y
rd.vx[0] = b.velocity.x
rd.vy[0] = b.velocity.y
rd.ax[0] = b.acceleration.x
rd.ay[0] = b.acceleration.y
if stage == 2:
rd.px[1] = rd.px[0] + 0.5*rd.vx[0]*dt
rd.py[1] = rd.py[0] + 0.5*rd.vy[0]*dt
rd.vx[1] = rd.vx[0] + 0.5*rd.ax[0]*dt
rd.vy[1] = rd.vy[0] + 0.5*rd.ay[0]*dt
rd.ax[1] = b.acceleration.x
rd.ay[1] = b.acceleration.y
if stage == 3:
rd.px[2] = rd.px[0] + 0.5*rd.vx[1]*dt
rd.py[2] = rd.py[0] + 0.5*rd.vy[1]*dt
rd.vx[2] = rd.vx[0] + 0.5*rd.ax[1]*dt
rd.vy[2] = rd.vy[0] + 0.5*rd.ay[1]*dt
rd.ax[2] = b.acceleration.x
rd.ay[2] = b.acceleration.y
if stage == 4:
rd.px[3] = rd.px[0] + rd.vx[2]*dt
rd.py[3] = rd.py[0] + rd.vy[2]*dt
rd.vx[3] = rd.vx[0] + rd.ax[2]*dt
rd.vy[3] = rd.vy[0] + rd.ay[2]*dt
rd.ax[3] = b.acceleration.x
rd.ay[3] = b.acceleration.y
b.position.x = rd.px[stage-1]
b.position.y = rd.py[stage-1]
def update (self, dt):
"""Pushes the uni 'dt' seconds forward in time."""
#Repeat four times:
for i in range(1, 5, 1):
self.updateAccel() #Calculate the current acceleration of all bodies
self.RK4(dt, i) #ith Runge-Kutta step
#Set the results of the Runge-Kutta algorithm to the bodies:
for b in self.bodies.itervalues():
rd = b.rk4data
b.position.x = b.rk4data.px[0] + (dt/6.0)*(rd.vx[0] + 2*rd.vx[1] + 2*rd.vx[2] + rd.vx[3]) #original_x + delta_x
b.position.y = b.rk4data.py[0] + (dt/6.0)*(rd.vy[0] + 2*rd.vy[1] + 2*rd.vy[2] + rd.vy[3])
b.velocity.x = b.rk4data.vx[0] + (dt/6.0)*(rd.ax[0] + 2*rd.ax[1] + 2*rd.ax[2] + rd.ax[3])
b.velocity.y = b.rk4data.vy[0] + (dt/6.0)*(rd.ay[0] + 2*rd.ay[1] + 2*rd.ay[2] + rd.ay[3])
self.time += dt #Internal time variable
The algorithm is as follows:
Update the accelerations of all bodies in the system
RK4(first step)
goto 1
RK4(second)
goto 1
RK4(third)
goto 1
RK4(fourth)
Did I mess something up with my RK4 implementation? Or did I just start with corrupted data (too few important bodies and ignoring the 3rd dimension)?
How can this be fixed?
Explanation of my data etc...
All of my coordinates are relative to the Sun (i.e. the Sun is at (0, 0)).
./my_simulator 1yr
Earth position: (-1.47589927462e+11, 18668756050.4)
HORIZONS (NASA):
Earth position: (-1.474760457316177E+11, 1.900200786726017E+10)
I got the 110 000 km error by subtracting the Earth's x coordinate given by NASA from the one predicted by my simulator.
relative error = (my_x_coordinate - nasa_x_coordinate) / nasa_x_coordinate * 100
= (-1.47589927462e+11 + 1.474760457316177E+11) / -1.474760457316177E+11 * 100
= 0.077%
The relative error seems miniscule, but that's simply because Earth is really far away from the Sun both in my simulation and in NASA's. The distance is still huge and renders my simulator useless.
110 000 km absolute error means what relative error?
I got the 110 000 km value by subtracting my predicted Earth's x
coordinate with NASA's Earth x coordinate.
I'm not sure what you're calculating here or what you mean by "NASA's Earth x coordinate". That's a distance from what origin, in what coordinate system, at what time? (As far as I know, the earth moves in orbit around the sun, so its x-coordinate w.r.t. a coordinate system centered at the sun is changing all the time.)
In any case, you calculated an absolute error of 110,000 km by subtracting your calculated value from "NASA's Earth x coordinate". You seem to think this is a bad answer. What's your expectation? To hit it spot on? To be within a meter? One km? What's acceptable to you and why?
You get a relative error by dividing your error difference by "NASA's Earth x coordinate". Think of it as a percentage. What value do you get? If it's 1% or less, congratulate yourself. That would be quite good.
You should know that floating point numbers aren't exact on computers. (You can't represent 0.1 exactly in binary any more than you can represent 1/3 exactly in decimal.) There are going to be errors. Your job as a simulator is to understand the errors and minimize them as best you can.
You could have a stepsize problem. Try reducing your time step size by half and see if you do better. If you do, it says that your results have not converged. Reduce by half again until you achieve acceptable error.
Your equations might be poorly conditioned. Small initial errors will be amplified over time if that's true.
I'd suggest that you non-dimensionalize your equations and calculate the stability limit step size. Your intuition about what a "small enough" step size should be might surprise you.
I'd also read a bit more about the many body problem. It's subtle.
You might also try a numerical integration library instead of your integration scheme. You'll program your equations and give them to an industrial strength integrator. It could give some insight into whether or not it's your implementation or the physics that causes the problem.
Personally, I don't like your implementation. It'd be a better solution if you'd done it with mathematical vectors in mind. The "if" test for the relative positions leaves me cold. Vector mechanics would make the signs come out naturally.
UPDATE:
OK, your relative errors are pretty small.
Of course the absolute error does matter - depending on your requirements. If you're landing a vehicle on a planet you don't want to be off by that much.
So you need to stop making assumptions about what constitutes too small a step size and do what you must to drive the errors to an acceptable level.
Are all the quantities in your calculation 64-bit IEEE floating point numbers? If not, you'll never get there.
A 64 bit floating point number has about 16 digits of accuracy. If you need more than that, you'll have to use an infinite precision object like Java's BigDecimal or - wait for it - rescale your equations to use a length unit other than kilometers. If you scale all your distances by something meaningful for your problem (e.g., the diameter of the earth or the length of the major/minor axis of the earth's orbit) you might do better.
To do a RK4 integration of the solar system you need a very good precision or your solution will diverge quickly. Assuming you have implemented everything correctly, you may be seeing the drawbacks with RK for this sort of simulation.
To verify if this is the case: try a different integration algorithm. I found that using Verlet integration a solar system simulation will be much less chaotic. Verlet is much simpler to implement than RK4 as well.
The reason Verlet (and derived methods) are often better than RK4 for long term prediction (like full orbits) is that they are symplectic, that is, conserve momentum which RK4 does not. Thus Verlet will give a better behavior even after it diverges (a realistic simulation but with an error in it) whereas RK will give a non-physical behavior once it diverges.
Also: make sure you are using floating point as good as you can. Don't use distances in meters in the solar system, since the precision of floating point numbers is much better in the 0..1 interval. Using AU or some other normalized scale is much better than using meters. As suggested on the other topic, ensure you use an epoch for the time to avoid accumulating errors when adding time steps.
Such simulations are notoriously unreliable. Rounding errors accumulate and introduce instability. Increasing precision doesn't help much; the problem is that you (have to) use a finite step size and nature uses a zero step size.
You can reduce the problem by reducing the step size, so it takes longer for the errors to become apparent. If you are not doing this in real time, you can use a dynamic step size which reduces if two or more bodies are very close.
One thing I do with these kinds of simulations is "re-normalise" after each step to make the total energy the same. The sum of gravitational plus kinetic energy for the system as a whole should be a constant (conservation of energy). Work out the total energy after each step, and then scale all the object speeds by a constant amount to keep the total energy a constant. This at least keeps the output looking more plausible. Without this scaling, a tiny amount of energy is either added to or removed from the system after each step, and orbits tend to blow up to infinity or spiral into the sun.
Very simple changes that will improve things (proper usage of floating point values)
Change the unit system, to use as much mantissa bits as possible. Using meters, you're doing it wrong... Use AU, as suggested above. Even better, scale things so that the solar system fits in a 1x1x1 box
Already said in an other post : your time, compute it as time = epoch_count * time_step, not by adding ! This way, you avoid accumulating errors.
When doing a summation of several values, use a high precision sum algorithm, like Kahan summmation. In python, math.fsum does it for you.
Shouldn't the force decomposition be
th = atan(b, a);
return Vector2(cos(th) * fg, sin(th) * fg)
(see http://www.physicsclassroom.com/class/vectors/Lesson-3/Resolution-of-Forces or https://fiftyexamples.readthedocs.org/en/latest/gravity.html#solution)
BTW: you take the square root to calculate the distance, but you actually need the squared distance...
Why not simplify
r = math.sqrt(a*a + b*b)
fg = (self.G * b1.m * b2.m) / pow(r, 2)
to
r2 = a*a + b*b
fg = (self.G * b1.m * b2.m) / r2
I am not sure about python, but in some cases you get more precise calculations for intermediate results (Intel CPUs support 80 bit floats, but when assigning to variables, they get truncated to 64 bit):
Floating point computation changes if stored in intermediate "double" variable
It is not quite clear in what frame of reference you have your planet coordinates and velocities. If it is in heliocentric frame (the frame of reference is tied to the Sun), then you have two options: (i) perform calculations in non-inertial frame of reference (the Sun is not motionless), (ii) convert positions and velocities into the inertial barycentric frame of reference. If your coordinates and velocities are in barycentric frame of reference, you must have coordinates and velocities for the Sun as well.

Categories