Simple Euler Physics, Weird Behaviour - c++

I have this simple euler integrator. For finding precise collision times it should handle negative dt's as well (I divide the frame time and simulate back when I detect a collision).
For some reason
someBody.update(1.0);
someBody.update(-0.3);
someBody.update(-0.3);
someBody.update(0.6);
gives different results than:
someBody.update(1.0);
It might be because I use euler instead of RK4 or verlet?
Here's the code for the integrator:
void Body::update(double dt)
{
if (dt > 0) velocity += acceleration * (dt*dt);
else velocity -= acceleration * (dt*dt);
pos += velocity * dt;
rotation += angularVelocity * dt;
}
Thanks a lot!
Maarten

The reason is math. Let's focus on velocity variable:
If you call:
someBody.update(1.0)
you'll get:
velocity += acceleration * 1
But if you call:
someBody.update(1.0);
someBody.update(-0.3);
someBody.update(-0.3);
someBody.update(0.6);
you will get:
velocity += acceleration * (1 - 0.3^2 - 0.32^2 + 0.6^2)
(which gives velocity += acceleration * 1.18)
You should have simply velocity += acceleration * dt;

Suspect floating point error accumulation in Body::update.
Example: 1 + 2 + 3 + 4 = 10 but 1.0 + 2.0 + 3.0 + 4.0 = something else.
The easiest fix is to round the results after each major computation step.

Thanks, the acceleration was indeed wrong.
I use a fixed timestep for each frame. This function is to run the simulation for partial frames. So I ignore the accaleraion completely now. This way everything within a frame happens linearly and the acceleration is only added in between frames.
The problem persists though. I think it might be the floating point errors as poited out by Magicianeer.
Although I think these errors would not add up so dramatically. In around 50 frames a bouncing object goes from it's full height (100) to about half that height. In around 200 frames it's still on the floor. When I don't call multiple the updates within the frame it's all fine.
I'll try keeping the position at the end of the frame and putting it back after doing the partial frame simulations. That way the errors won't add up over multiple frames.

Related

Rotating vectors in space and high precision in C++

This is my function to compute 3D rotation in C++ defined by an angle in radiant around axis.
Vector rotate(const Vector& axis, const Vector& input, const double angle) {
double norm = 1/axis.norm();
if(norm != 1)
axis *= norm;
double cos = std::cos(angle);
double mcos = 1 - cos;
double sin = std::sin(angle);
double r1[3];
double r2[3];
double r3[3];
double t_x, t_ym t_z;
r1[0] = cos + std::pow(axis.x, 2)*mcos;
r1[1] = axis.x*axis.y*mcos - axis.z * sin;
r1[2] = axis.x*axis.z*mcos - axis.y * sin;
r2[0] = axis.x*axis.y*mcos + axis.z*sin;
r2[1] = cos + std::pow(axis.y, 2)*mcos;
r2[2] = axis.x*axis.z*mcos - axis.x * sin;
r3[0] = axis.x*axis.z*mcos - axis.y * sin;
r3[1] = axis.z*axis.y*mcos - axis.x * sin;
r3[2] = cos - std::pow(axis.z, 2) * mcos;
return Vector(t_x, t_y, t_z);
}
The thing is if you try yourself to rotate the vector a n times pi/4 where n multiple of 4 (so you do complete revolution around the axis by doing four quarter of rotations) the error will propagate pretty fast.
Example (where err = input-output):
input: (1.265, 3.398, 3.333)
rotation axis: (2.33, 0.568, 2.689)
n: 8 (so two completes revolutions)
output: (1.301967, 1.533389, 4.138940)
error: (0.038697, -0.864611, 0.805940)
n: 400 (so 100 completes revolutions)
error: (472..., 166..., 673...)
What can I do ?
Constraints:
Rotations are not predictable so not possible to do something like angle = pi/4 *n % 2*pi like #molbdnilo suggests. Because I have to chain translations and rotations to test if there's a collision.
This is one of the typical problems that you can run into with floats.
Floating point numbers are pretty exact on singular operations. In fact, for many operations you are guaranteed to get the most exact result that can be represented in the format, so any rounding errors that you get are solely due to fitting it into the representation.
However, as soon as you start chaining floating point operations, even those errors can accumulate. If you are lucky, you can make your algorithm numerically stable, so that the rounding errors cancel each other out in the end and you always stay in the ballpark of the correct result. Getting this right can be quite a challenge though, especially for complex computations. For instance, in your particular implementation, there is lots of potential for catastrophic cancellation introducing large rounding errors into the computation chain.
The easier solution is: Avoid chaining the float operations in the first place! Or to be more precise: Only chain those parts which you can keep numerically stable. Since you mentioned this is for a computer game: In a game you transform the geometry according to the camera matrix each frame. You never touch the geometry in memory, instead you simply adjust the camera matrix. That way, your source geometry is always fresh and the rounding error in each frame is simply the error from that single transformation.
Similarly, you usually don't update the camera matrix incrementally. Instead, you read the player's position and view and build the complete matrix from scratch from those vectors. Now the only challenge that you have left is make sure that you don't accumulate errors into the player position and view, but this is much easier than ensuring stability at the other end of the transformation pipeline.
Just store the original base vector and the rotation angle together, and do the calculation every time you need the current rotated value. You can cache this and invalidate every time the angle changes, but always work from the original base vector and total aggregate rotation.
Presto! No cumulative errors, because no chained calculations.
Also, if you're concerned about cumulative errors in the angle itself, store that in degrees and convert to radians when required. Again, pi is touched once in the degree->radian conversion, and you don't have a chain of approximate pi/n values contributing more errors.
You could try and discretize your rotations so that they add up to multiples of pi/2. You clamp your rotation value to the closest discretized value and then do the calculation. If you find one small enough it will be still perceived as smooth and you should not accumulate an error.
In the end I used Rodrigues' rotation formula and the performances are way better.
Though this is not the solution as #ComicSansMs points it out:
Avoid chaining the float operations in the first place!
So combining a new algorithm and using Rodrigues' formula was ok.
I suspect mistakes in my first matrix implementation.
Thanks everyone for your answers and insights.

How can work time between ticks into this movement function?

I have a function that moves a planet around a star. This function takes a parameter t, which is the time in milliseconds since the last update. In other movement functions I've written, I like to use time to dictate movement so the movement will always be the same on all computers and instances instead of based on processing power. However, all methods I have tried for including time in this physics equation have resulted in erratic results. Any ideas?
void Planet::update(int t){
double grav_const = 6.6742e-11;
double earth_mass = 5.975e24;
double starX = 1920/2 * 10000;
double starY = 1080/2 * 10000;
double diffX = xPos - starX;
double diffY = yPos - starY;
double radius = sqrt(pow(diffX,2) + pow(diffY,2));
double grav_accel = (grav_const * (earth_mass / pow(radius,2)));
double angle = atan2(diffX, diffY);
xVel += (sin(angle) * grav_accel);
yVel += (cos(angle) * grav_accel);
xPos -= xVel;
yPos -= yVel;
}
It's been a while since I dealt with physics at this level, but I think you can go back to fundamental reasoning about the units involved.
Acceleration is distance over time squared (m/s^2 or whatever your units are). So to get velocity (distance over time) then you need to multiply by time.
m/s = (m/s^2) * s
And then after that you want to turn your velocity into a specific change in distance. So multiply it by the time again and there you go.
m = (m/s) * s
If things still don't seem right afterwards, then you may need to check over the rest of your equations and constants. Make sure the units match up (seconds vs minutes, metere vs kilometers, etc). Make sure you aren't suffering rounding in places you didn't intend. And so on.
In the worst case, work the math yourself for a few iterations (perhaps with larger time values) and maybe even plot the results on a piece of paper to make sure it looks sensible.
When you describes the results as "erratic" exactly what do you mean?
If you mean:
A. "t changes by a varying amount between each call". Then you need to look at the architecture of the calling application since that will vary with processing power and other work going on in the system (assuming a preemptive multitasking OS).
B. "the floating point values have strange rounding characteristics". Then welcome to using floating point numbers. The representations of double, float and the like are simply imperfect and exhibit rounding areas in certain circumstances and you may have problems if you are taking deltas that are too small relative to the size of the other values you are combining.
C. "t has no effect on my results". I don't see any references to the input parameter in your example.
You should post the entire Planet class, or at least more of it.
EDIT: The best way to calculate position based on times like this is to come up with an absolute function that returns position based on time and NOT accumulate position, but only accumulate time. For example:
timeAbsolute += tDelta;
xPos = fxPos(timeAbsolute);
yPos = fyPos(timeAbsolute);
xVel = fxVel(timeAbsolute);
yVel = fyVel(timeAbsolute);
My orbital mechanics fu is not strong enough to give you those functions in general, but in your case (where you seem to be assuming a circular orbit), you can simply take the arc angle instead. So, assuming 1 orbit every 360 seconds (and using degrees), you would get
angle = (timeAbsolute % 360);
then calc velocity and position from angle.
P.S. Be careful with fmod ...
How to use fmod and avoid precision issues

Creating acceleration of an object (tank) in a game

I'm having a trouble with making my Tank accelerate when I press a move button and decelerate when I let go of it. Don't be too harsh on me for not being a pro because I'm still learnig and Thanks in advance!
I also have variables called 'speed' and 'maxspeed' which I played around with and it didn't turn out to well, and the code below basicaly makes my tank move in an update function
if(TestName->Tank[0].up == true){
TestName->Tank[0].position_y = TestName->Tank[0].position_y + (TestName->Tank[0].speed + 0.06f);
}
if(TestName->Tank[0].down == true){
TestName->Tank[0].position_y = TestName->Tank[0].position_y - 0.06f;
}
if(TestName->Tank[0].right == true){
TestName->Tank[0].rotation = TestName->Tank[0].rotation + 0.6f;
}
if(TestName->Tank[0].left == true){
TestName->Tank[0].rotation = TestName->Tank[0].rotation - 0.6f;
}
}
In this bit of code:
TestName->Tank[0].position_y = TestName->Tank[0].position_y + (TestName->Tank[0].speed + 0.06f);
it looks horribly like you are trying to add an acceleration (0.06) to your speed while at the same time adding the speed to the position. The plus sign won't change the speed, it will just calculate the result and use it in the equation.
Do it in two steps:
TestName->Tank[0].speed += 0.06f;
TestName->Tank[0].position_y += TankName->Tank[0].speed;
When you have that bit working your next issue will be inability to steer. I think you would be well advised to google vectors (maths, not C++) and the difference between velocity and speed.
Logically,.....Supposing your defaultspeed is 1 and maxspeed is 2. You probably want another variable, lets call it deltaSpeed.
Then your totalSpeed(Change in Tank's Position in a direction) is defaultspeed * deltaSpeed.
You would also need to check if the user is pressing down on the button, if so increase(either by a fixed amount or exponentially) the deltaSpeed. When the user release the button then decrease the deltaSpeed. This would give the illusion of tank speeding up and slowing down.
Then, when the user presses another direction you could reset the deltaSpeed.
some useful Newtonian equations:
f = ma
v^2 = u^2 + 2as
s = ut + 0.5 * a * t^2
where:
s = distance travelled (m)
u = initial velocity (m/s)
t = time (seconds)
a = acceleration
f = force
m = mass
Rearranging these equations allows you to compute the change in acceleration, and therefore velocity, and therefore distance for a given time period.
Games will model these rules with varying degrees of simplification (e.g. you could code that pressing 'W' will generate a Force of X Newtons in the direction the tank is facing, or you could choose to limit the maximum speed, for make force inversely proportional to current speed (to model for the power equation).
Or you could calculate F as the integral of the power equation over the time period between each simulation pass (which is what a flight sim might do).
Hopefully some ideas to guide you to some useful literature.

Rigid Body Simulation Friction

I'm currently working on a 3D Rigid Body simulation program. I have currently managed to get the rigid bodies colliding with the floor and bouncing correctly using the impulse. However, my problem is once they have bounced they accelerate constantly despite using a friction vector to try and slow them.
This is the code when you hit the ground
Rvector fDirection(m_Bodies[i].Vel.x,0.0,m_Bodies[i].Vel.z);
Rvector relativeVelocities = m_Bodies[i].Vel - floorVelocity;
fDirection.normalize();
Real impulse = -(1+e) * (Rvector::dotProduct(relativeVelocities,floorNormal))
/ (1/m_Bodies[i].mass + floorMass);
Rvector friction = fDirection*mu*gravity.length()*m_Bodies[i].mass;
Rvector collision_forces = Rvector(0,1,0)*impulse;
collision_forces += friction ;
m_Bodies[i].Vel += (collision_forces/m_Bodies[i].mass);
Thanks
Edit:
Here is the integration code.
void RigidBodySimulation::eulerIntegration(float dTime)
{
Rvector newVel;
Rvector newPos;
Rvector zero(0.0, 0.0, 0.0);
Real one_over_mass;
Rvector accel;
for( unsigned int i = 0 ; i < m_Bodies.size(); i++)
{
one_over_mass = 1/m_Bodies[i].mass;
newVel = m_Bodies[i].Vel + m_Bodies[i].force*one_over_mass*dTime;
newPos = m_Bodies[i].Pos + m_Bodies[i].Vel*dTime;
accel = m_Bodies[i].force / m_Bodies[i].mass;
m_Bodies[i].acceleration = accel;
m_Bodies[i].newPos = newPos;
m_Bodies[i].Vel = newVel;
m_Bodies[i].Pos = newPos;
}
}
I have to say, this is a pretty terrible piece of code you have there, and I've been doing this for more than 10 years. You should get a basic textbook on dynamics (like Hibbeler).
Real impulse = -(1+e) * (Rvector::dotProduct(relativeVelocities,floorNormal))
/ (1/m_Bodies[i].mass + floorMass);
This equation sort of looks like you are trying to calculate the restitution impulse from the impact (although the calculation is wrong). First, you must understand that an impulse is not the same thing as a force. An impulse is the integral of the force over a certain interval of time. During an impact, you can assume that period of time to be really small and that's why you perform an instantaneous velocity change. And that's why you should have specified that the integration code has nothing to do with the collision calculation, because it is by-passed for that instant, or at least, it should be if you do an impulse-based calculation. This is what the actual calculation should look like:
Real momentum_before = Rvector::dotProduct(m_Bodies[i].Vel * m_Bodies[i].mass + floorVelocity * floorMass, floorNormal);
Real rel_vel_after = -e * Rvector::dotProduct(relativeVelocities,floorNormal);
// conservation of momentum in normal direction gives this:
Real body_vel_after = (momentum_before + floorMass * rel_vel_after) / (m_Bodies[i].mass + floorMass);
Real floor_vel_after = body_vel_after - rel_vel_after;
Which actually simplifies to one line as follows:
Real body_vel_after = ( (m_Bodies[i].mass - e * floorMass) * Rvector::dotProduct(m_Bodies[i].Vel, floorNormal)
+ (1.0 + e) * floorMass * Rvector::dotProduct(floorVelocity, floorNormal)
) / (m_Bodies[i].mass + floorMass);
However, if you assume the floor to have infinite mass (or much larger than that of the body), then you would simply have:
Real body_rel_vel_after = -e * Rvector::dotProduct(relativeVelocities, floorNormal);
Real body_vel_after = Rvector::dotProduct(floorVelocity, floorNormal) + body_rel_vel_after;
It's that simple. But, under that assumption, you do not have conservation of momentum. But in any case, the restitution impulse from the impact can be calculated as:
Real impulse = m_Bodies[i].mass * (body_vel_after - Rvector::dotProduct(m_Bodies[i].Vel, floorNormal));
Now, because the restitution impulse is the integral of the normal force over the small time period of impact, the impulse from the friction during the impact can be calculated from that restitution impact. The friction force is equal to "mu" times the normal force, i.e., |Ff| = mu * |Fn|, this is also valid for the impulse, i.e., |If| = mu * |In|. So, you can compute it directly:
Real friction_impulse = mu * fabs(impulse);
But that's just the magnitude of the friction impulse. It's direction is opposite from the relative tangential velocity, which is:
Rvector tangent_rel_vel = relativeVelocities - Rvector::dotProduct(relativeVelocities, floorNormal) * floorNormal;
And it's direction is:
Rvector dir_rel_vel = tangent_rel_vel;
dir_rel_vel.normalize();
(Notice that I need to keep the tangential velocity intact, because it will be needed later)
At this point, you could compute the tangential velocity after impact as follows (again, under the assumption of an infinite-mass floor, otherwise, it is more complicated than that):
Rvector tangent_rel_vel_after = tangent_rel_vel - dir_rel_vel * friction_impulse / m_Bodies[i].mass;
However, what if the friction impulse causes the tangential relative velocity to get to zero? That's a problem, because, with the above formula, part of the friction impulse could wind up reversing the direction of the tangential relative velocity, which would mean that during the latter part of the impact, the friction force is actually acting in the direction of the velocity (not good). The most friction can do is stop the relative motion. So, you need to check for that condition:
Real tang_rel_vel_change = friction_impulse / mBodies[i].mass;
Rvector tangent_rel_vel_after = tangent_rel_vel - dir_rel_vel * tang_rel_vel_change;
if ( tang_rel_vel_change > tangent_rel_vel.length() )
tangent_rel_vel_after = Rvector(0.0, 0.0, 0.0); // stop relative motion.
At this point, all you need to do is combine the two final velocities:
m_Bodies[i].Vel = floorVelocity + tangent_rel_vel_after + body_rel_vel_after * floorNormal;
And that's it, at least, for this very simple problem (infinite mass of the floor). In reality, this impulse-based approach become increasingly difficult to deal with as you complicate things: two finite-mass objects, multiple objects, and actual rigid-body dynamics (because you are just doing particle dynamics here). The impulse-based approach is rarely seen anywhere beyond simple schoolyard examples of balls bouncing on the floor. Btw, you shouldn't really call this a "rigid-body" simulator since all you are really doing a particle dynamics (and 3D rigid-body dynamics is way more complicated than this). Also, your integration law is terrible, but that's a whole different story.
Friction is backwards, isn't it?
Rvector fDirection(m_Bodies[i].Vel.x,0.0,m_Bodies[i].Vel.z);
This is in the direction of the velocity. You then multiply it by some constants then add it to the velocity (along with the impulse)
collision_forces += friction ;
m_Bodies[i].Vel += (collision_forces/m_Bodies[i].mass);
So that will act to increase the velocity along the floor.
There are some other odd things:
your impulse term has: (1/m_Bodies[i].mass + floorMass) This is adding 1/mass to mass. Shouldn't it be (1/(m_Bodies[i].mass + floorMass))
And then in the integrator, you calculate acceleration from force, but then never integrate it, you also apply force to velocity directly. So what's the acceleration member for?

C++ gamedev: truncating float to int

I'm making a game in C++ where is a tank that can moves on a stage. The tank has an angle (float, in degrees, and i'm supposing the tank is at 0º when his cannon points to the right), a speed (float), and there is a time constant called "deltaT" (float).
When the player moves the tank forward, I use trigonometry and the physic equation of position in function of time (I mean X(t), I don't know how it says in English) in order to calculate the new tank's coordinates in the stage.
This is my problem: due to the passage from float to int, the values closest to zero are not taken into account. So, at certain angles, the tank appears rotated, but moves in a different direction.
This is what my code does:
1 - first, I separate the speed in its components X and Y, by using the angle in which the tank is moving:
float speedX = this->speed * cos(this->angle);
float speedY = this->speed * sin(this->angle);
2 - then use the equation I mentioned above to get the new coordinates:
this->x = (int) ceil(this->x + (speedX * deltaT));
this->y = (int) ceil(this->y - (speedY * deltaT));
The problem begins at the first step: at certain angles, the value of the cos or the sin is very close to zero.
So, when I multiply it for speed to obtain, say, speedX, I still got a very low number, and then when I multiply it for deltaT it is still too low, and finally when apply the ceil, that amount is totally lost.
For example, at 94º, with deltaT = 1.5, and speed = 2, and assuming initial value of X is 400, we have:
speedX = -0.1395...
this->x = 400 //it must be 399.86..., but stays in 400 due to the ceiling
So, in my game the tank appears rotated, but moves straight forward. Also, sometimes it moves well backwards but wrong forward, and viceversa.
How can I do to make the direction of the tank more accurate? To raise the speed or the value of deltaT are not options since it's about a tank, not a formula 1.
How can I do to make the direction of the tank more accurate? To raise the speed or the value of deltaT are not options since it's about a tank, not a formula 1 :P
You should store your position values as floats, even though they are ultimately used as ints for on-screen positioning. That way, you won't lose the non-integer portion of your position. Just cast to int right at the end when you do your drawing.
Keep the location of the tank in floats all the time. Alternatively, only let the tank rotate in increments of 45 degrees. Decide on whether your game will use approximate positions and headings or exact ones and stick to that decision all the way through.
Your problem is not accuracy! Floating-point math has 24-bit accuracy, that's plus/minus 1/16,777,216. No problem there.
It's your rounding mode.
ceil rounds up.
Try:
int x = this->x + (speedX * deltaT) +.5f;
ceil creates a rounding error (E) of 0<E<1, casting as above gives -.5<E<+.5, which has half the absolute error.
Also, avoid using double math. ceil is the double version, you mean ceilf. Technically, your code casts float->double->int. You gain nothing from this, but it takes time.
And finally... The real problem:
Your accuracy problem really is because you are accumulating it!
So 0<E<1 ... PER FRAME!
at 60Hz => 0<E<60*seconds. Thus after a minute 0<E<3600
If X/Y are pixel coordinates, that's a whole screen! No wonder you are struggling.
No matter how you round, this is always going to be a problem.
Instead, you to store the floating point result before rounding. Thus the absolute error is always 0<E<1 ... or -.5f < E < .5f for mid-point rounding. Try adding a new floating point position - XPos/YPos
this->XPos += speedX * deltaT;
this->YPos += speedY * deltaT;
this->x = static_cast<int>(this->XPos+.5f);
this->y = static_cast<int>(this->YPos+.5f);
You should now move around smoothly.
PS: The word in English is "parametric", https://en.wikipedia.org/wiki/Parametric_equation