How to make Fluid Drag equation not framerate dependent - c#

I am trying to plan for a game I started coding. (Very much in the beginnings)
My problem is that I want the acceleration / movement portion of all game objects to be based on acceleration force vs drag (thus resulting in terminal velocity as the upper limit of speed available)
While I could go another route, I'd rather not if possible. Additionally, it has been suggested (by a friend) that I could use a physics library, but that seems overkill and besides, I'd like to learn and understand these concepts myself - I always feel like I better understand my own programs when I do.
I am making a 2D game and using Vector2 variables for position, heading and thrust force (the acceleration force applied). It's top-down so gravity is not a part of the equation.
Before I code it, I'm working out test cases in Excel - which is how I check my math before committing math to code. And I'm discovering that my use of the drag equation is making the object in question framerate dependent!! Specifically, the better the framerate, the lower the resultant terminal velocity.
I've been trying to modify the equations as necessary to account for framerate, but it eludes me.
If you want to work with the same spreadsheet I am, you can download the spreadsheet here.
But you don't have to - here are the specifics.
The drag equation as I understand it is:
Drag = 0.5 * FluidDensity * Velocity * Velocity * DragCoefficient * IncidenceArea
Using some numbers picked from thin air for calculations, if Fluid Density is 0.233 and the Drag Coefficient is 0.4 and the Incidental Area is 0.1 and the Acceleration force is 50 pixels per second, then here is what happens:
If I calculate that acceleration is applied every 0.25 seconds (once every quarter second) at 1/4 the Acceleration force (to match the timing) then we reach terminal velocity at about 39.3 pixels per second.
If I calculate acceleration instead at every second, we reach terminal velocity at about 53.6 pixels per second.
Specifically, every time I calculate for a given DeltaTime, the resultant speed is calculated as (code is from my head - not from an IDE - apologies if there's a bug in it):
//In globals / initialization:
Vector2 Position;
Vector2 Speed;
Vector2 ThrustForce;
float Density = 0.233f;
float DragCoefficient = 0.4f;
float IncidentalArea = 0.1f;
//In the update loop
//DeltaTime is a float based upon how much of a second passed
Vector2 AccelerationToApply = ThrustForce * DeltaTime;
Vector2 NewSpeed = Speed + AccelerationToApply;
Vector2 Drag = Speed * Speed * 0.5f * Density * DragCoefficient * IncidentalArea;
NewSpeed -= Drag;
Speed = NewSpeed;
That's the problem math. Here is the question:
How should this be expressed so that it's framerate independent?

The classic approach is to step the simulated physical time independent from the game loop frame rate, calculating multiple sub-iterations per frame if necessary to advance the physics. This allows you to control your time step (generally making it smaller than the main frame rate), which also helps to keep other potentially unstable calculations under control (such as oscillators.) This of course means that your physics has to compute faster than real time for the fixed time step chosen, as otherwise your world goes into slow motion.
Speaking of instability, I imagine that you'll see some oscillation effects in your current implementation, depending on whether you're overshooting the terminal velocity in a given time step. One way to resolve this is to compute the speed via analytical integration instead of approximating using a incremental step. To do that, express your formula as a differential equation and see if it is of a form that can be readily solved analytically.

There were two parts missing from the code above. While I had played with turning one part "on" and "off" to experimentally determine if it was needed, without the other I was having problems finding the right answer.
The two parts are this: The resultant drag does need to be multiplied by the time step in order to reduce its effect upon the acceleration, but also and perhaps more importantly - the acceleration force to be applied on this frame needs the drag subtracted from it before it is applied to the speed - not after like I had above.
The modified (and now framerate independent) code looks like this:
Also, I reduced having 4 "constant" coefficients to just one coefficient for the sake of simplicity.
//In globals / initialization:
Vector2 Position;
Vector2 Speed;
Vector2 ThrustForce;
float Coefficient = 0.009f;
float PreviousDrag = 0.000f;
//In the update loop
//DeltaTime is a float based upon how much of a second passed
Vector2 AccelerationToApply = ThrustForce * DeltaTime + PreviousDrag * DeltaTime;
Vector2 NewSpeed = Speed + AccelerationToApply;
PreviousDrag = Coefficient * NewSpeed * NewSpeed;
Speed = NewSpeed;
Running this logic through excel, I find that at approximately the same times I reach the same approximate terminal velocity no matter how often (or not) I calculate a change in velocity.

Related

Rotating an object to the movement direction is systematically inaccurate

This is in unity 2019.4.40f1. I'm trying to rotate an object toward it's movement direction. However, I noticed that this is inaccurate and in investigating I noticed that the inaccuracy comes from the actual movement amounts. What I mean is, in the code below, delta.y / delta.x prints out different values every loop iteration even though destination does not change. As a result, Atan2 of course also returns different results. The error every frame is on the 4th 3rd or 4th decimal place, however, over just a few seconds it adds up because it's not random variation but instead seems to be systematic.
Also note that at this stage I'm not actually applying the rotation to the object. My expectation is that the code below should move the object from its position to the destination a little bit every frame, in a straight line. This does appear to be the case visually, but numerically there is enough deviation to cause problems with the angle calculations.
IEnumerator TweenFunc (object myObject, Vector2 destination, float speed)
{
while (condition)
{
currentPosition = myObject.transform.position;
delta = destination - currentPosition;
Vector2 dir = delta.normalized;
frameTime = TimeManager.FrameTime
myObject.transform.Translate(dir.x * frameTime * speed, dir.y * frameTime * speed, 0);
Debug.Log(delta.y / delta.x);
Debug.Log(Mathf.Rad2Deg* Mathf.Atan2(delta.y, delta.x));
yield return true;
}
}
I would like to keep the angle computation consistent. It is possible to pre-compute the angle once, outside of the loop by computing delta first outside of the loop and then getting the angle before entering the loop. However, this solution is not usable in my use-case because the actual angle computation needs to take place in a different part of the code base that is only aware of dir and speed and not destination.
Edit for additional info: When traveling from (1.2, -0.2) to (0.2, -3.2) the angle changes from ~108.43352 to 108.3664. The true angle should be 108.435. While this is only a 10th of a degree, if I actually apply the rotation to the object it becomes visible, especially because most of the change appears to happen toward the end of the movement. I wonder if as dir.x and dir.y go to zero accuracy becomes worse in a systematic way. Also, there is no angle drift when traveling in any of the cardinal directions. There, the accuracy of dir.x and dir.y don't require decimal places since they are either 0 or 1, so that could be what's going on.
I wonder if there's a way to deal with this since the ultimate goal is actually a pretty common thing - to rotate the object toward its movement direction.
Even though the code you show here only does translation an not rotation, in general have in mind that Transform.Translate by default works in local space.
So if (from your title I just assume) you object is rotated this is actually inaccurate since you take a world space delta and then move on it but in local space
=> You want to explicitly pass in
myObject.transform.Translate(dir.x * frameTime * speed, dir.y * frameTime * speed, 0, Space.World);
And then you probably want to rather operate on the actually normalized movement vector and afaik you flipped the order of arguments:
Debug.Log(Mathf.Rad2Deg * Mathf.Atan2(dir.x, dir.y));
I'll add my partial solution answers since I haven't figured out how to counteract the issue directly.
It's possible to cast the x and y movement direction and all subsequent calculations leading to the angle computation as doubles by computing destination - currentPosition normally, casting that to double, and doing the rest of the vector computations (e.g. normalization, Atan2) manually with double precision and using Math rather than Mathf functions. Since object transform positions are still stored as floats there is still error, but this results in a reduction in error in the example of 0.05 of a degree, so it reduces the error in that example by half.
Since the function TweenFunc does not update destination, and it's not expected that game objects should be moved outside of the function while it is active, I can move the computation of dir.x and dir.y outside of the loop, so they are only computed once, at the beginning, at maximum accuracy, which in the example returns -108.4349 and no additional error accumulates further.
This works for producing stable movement directions, but the major downside of this is if somehow the game object is moved in some way outside of this function, then it may never reach its destination and we might therefore never exit the loop in TweenFunc. This can be addressed by adding complexity like checking if movement direction changed by more than 1 degree or some other threshold, and resetting it then.

Unity 2D scripted friction equation

I am working on the friction for a square object for after it is done being pushed by the player. The block's movements are controlled purely through script using transform.Translate(velocity) each frame and only a boxcollider2D. I am speculating that because i am controlling the block's movements each frame manually is the reason i can't seem to get the boxcolliders2D's physics material 2D friction to work.
Basically, I am trying to recreate the built in physics similar to rigidbodies by using transform.translate, or use the built in physics if possible. So far, i've landed on something like this
newVelocity.x -= ( 1 / (newVelocity.x * newVelocity.x));
In an attempt to reduce the velocity.x each frame until it hits 0 and shorts out. Obviously, this falls apart when the velocity.x becomes a decimal number. I'm stumped on what i should be doing to achieve a friction similar to rigid bodies where it slows down a little at first and then much faster towards the end as it stops. Any and all help appreciated, thanks in advance!
You could compare the signs of the velocity before and after calculating the change in velocity, and set the velocity to zero when the sign changes, like so:
newVelocity.x -= ( 1 / (newVelocity.x * newVelocity.x));
if(Mathf.Sign(previousVelocity.x) != Mathf.Sign(newVelocity.x)) {
newVelocity.x = 0f;
}
One thing, though, is that normal friction is not calculated using the above equation, it's calculated as follows:
newVelocity.x -= frictionCoefficient*Time.fixedDeltaTime;
And with this, you wouldn't need to compare the velocity to the previous velocity as you can just say if the velocity is low, it's zero. You could still compare the previous velocity sign stuff if you wanted to though, but this is probably more efficient
if(newVelocity.x < 0.01f) {
newVelocty.x = 0f;
}

Continuous aim to target in Malmo

Malmo is Microsoft's AI framework for Minecraft, consisting of a mod for the game itself, and a multi-platform framework for sending inputs and receiving data about the world.
Minecraft's aiming is cylindrical. It's stored in a yaw (left and right) and pitch (up and down) instead of a full-on rotation quaternion. Yaw goes from -180 degrees at the far left and wraps to 180 at the far right. Pitch goes from -90 pointing directly up (zenith) to 90 directly down (nadir). In my code I store these as a Vector2 (recreated to work much like XNA's) such that X represents Yaw, and Y represents Pitch.
I'm having trouble creating a continuous aiming to target algorithm so that the AI is able to aim its camera to a given target yaw and pitch. Because the only way to do so while still allowing continuous movement is through continuous aiming (setting yaw and pitch velocities, rather than values), I need to repeatedly increment the yaw and pitch towards the target direction.
I do this by storing the target direction in a nullable property. If the property is null, that means not to change the aim. Otherwise, subtract the distance between the stored value (target aim) and the current aim (supplied via parameter) each time the update method is called. It then scales the difference so that either yaw or pitch is 1 (max speed), and the other is correctly proportioned for that. This proportioned Vector2 velocity is split into its yaw and pitch, and then sent to the client via the turn and pitch commands. Once within 10 degrees of the target direction, the target is set to null.
On paper, it seems like this would make the camera's aim go directly towards the target direction (excluding yaw wraparounds). However, the client's pitch usually goes straight past the target direction, despite the the update method sending a pitch command that says to go the opposite direction. This means the pitch somehow gets "stuck" at the zenith and nadir, but fixes itself and "turns around" and gets stuck at the opposite pole after a couple seconds. The amount of time spent stuck before turning around seems to increase exponentially (or maybe quadratically) each time.
Here's the source code of my aim update method:
public void UpdateAim(Observations obs)
{
// AimTarget is a Vector2? property
if (AimTarget == null || obs == null)
{
return;
}
if (AimTarget.Value.Distance(obs.Aim) < AIM_CLOSE_ENOUGH) // < 10
{
Logger.LogInfo("Reached {0}", AimTarget.Value);
Look(Vector2.Zero);
AimTarget = null;
return;
}
double deltaYaw = AimTarget.Value.X - obs.Aim.X;
double deltaPitch = AimTarget.Value.Y - obs.Aim.Y;
// These two are stored as private Vector2 fields
deltaAim = new Vector2(deltaYaw, deltaPitch);
scaledAim = deltaAim / Math.Max(Math.Abs(deltaAim.X), Math.Abs(deltaAim.Y));
Look(scaledAim); // sets continuous aim velocity
}
And the (simplified) source code of Look(Vector2):
public void Look(Vector2 direction)
{
// Agent is an AgentHost property
Agent.sendCommand("turn " + velocity);
Agent.sendCommand("pitch " + velocity);
}
UpdateAim() is called 20 times a second (although I have tried as high as 50 times a second and as low as 5) during the main game loop.
At the end of the last time I ran the AI (which got stuck at the nadir), my aiming debug data looked like this:
// Format: (yaw, pitch)
Target Aim: (134.75, 27.90)
Actual In-Game Aim: (-6.50, 90.00) // Lines up with MC's debug screen
Delta Aim : (145.17, -62.10) // Total degrees needed to go in yaw and pitch
Scaled Aim Velocity: (1.00, -0.43)
The scaled aim velocity is what is supplied to Look(). As you can see, the pitch velocity was negative as it was supposed to be, but the actual in-game aim remains at 90 and for some reason doesn't get corrected. Am I doing the math right?
From all what I can see, the math is elegant and checks out. If the pitch velocity is negative at the nadir and it doesn't move down, to me it looks like Agent.sendCommand not doing its job properly.
When setting the speed, does it remain at the speed you set it to until another one is set? Or do speeds get added to each other? What happens if the pitch is out of bounds?
You probably managed to fix this ages ago, but just in case, here are a few thoughts:
In your Look() method, you have the following:
Agent.sendCommand("turn " + velocity);
Agent.sendCommand("pitch " + velocity);
I assume the repeated use of velocity is a typo made when you simplified the code for SO use? Otherwise this would certainly explain the behaviour.
Your scaling code is interesting - is there any reason why you need to keep the ratio of yaw velocity to delta velocity the same? i.e do you really need the Math.Max(Math.Abs(deltaAim.X), Math.Abs(deltaAim.Y)) term? The two movements (yaw and pitch) are totally independent, so there's no reason to scale them dependently, unless it improves performance in some clever way I've not spotted.
You may need to take oscillations / damping into account. Imagine your yaw is correct (deltaYaw == 0). Your scaling means that your pitch delta velocity is always going to be at the maximum value (1, or -1 depending on direction). In other words, even if the delta pitch is only 0.0001, you'll still be adjusting at maximum speed and will significantly overshoot. (Obviously the use of AIM_CLOSE_ENOUGH will help with this, but I image it's still possible to get oscillations - especially if you have a high turnSpeedDegs set - see http://microsoft.github.io/malmo/0.17.0/Schemas/MissionHandlers.html#element_ContinuousMovementCommands)
For an example of this sort of thing working, take a look at the cart_test.py sample - https://github.com/Microsoft/malmo/blob/master/Malmo/samples/Python_examples/cart_test.py
Here's the relevant code snippet. yaw_to_mob is the target yaw, and yaw is the player's current yaw.
# Find shortest angular distance between the two yaws, preserving sign:
deltaYaw = yaw_to_mob - yaw
while deltaYaw < -180:
deltaYaw += 360;
while deltaYaw > 180:
deltaYaw -= 360;
deltaYaw /= 180.0;
# And turn:
agent_host.sendCommand("turn " + str(deltaYaw))
If you want to see the oscillation problem in action, by the way, take a look at the MazeRunner.py sample (same location as cart_test.py) and increase the turnSpeedDegs by a factor of two or three. Minecraft updates the pitch/yaw at render tick time, not world tick time, so slower render speeds will create bigger oscillation problems.

XNA 2D game - behaves differently on friend's computer

I made a simple networking xna game using c# and lidgren. But velocity of objects are different on friend's computer - resolution of window is the same, fps are the same (both 60), application is the same, but he's character is just slower for some reason. For some time his velocity is equal to mine, but then it slows down again.
Other friends do have equal velocities as I do. What could be a problem? The program adjusts movement according to fps, so there is probably no problem in fps.
Without seeing your code its hard to say for sure.
The program adjusts movement according to fps
Well it's generally better to think of animation as a function elapsed since the last update rather than frames per second. That way, the animation will take the same amount of real world time to complete regardless of FPS whether it be fixed or variable update.
Movement deltas should take into account time elapsed since last game update not last/current FPS.
e.g.
public Vector3 Velocity;
public Vector3 Position;
public void Update(GameTime gameTime)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Apply acceleration
Vector3 acceleration = force / Mass;
Velocity += acceleration * elapsed;
// Apply psuedo drag
Velocity *= DragFactor;
// Apply velocity
Position += Velocity * elapsed;
.
.
.
You said you're using lindgren to make it multiplayer? Maybe it's rubberbanding due to latency. Try checking it on 2 computers on lan to see if it resolves the issue.
Possible there's different settings to lindgren to specify how to handle syncing or prediction.

Simple 2D rocket dynamics

I am currently experimenting with some physics toys in XNA using the Farseer Physics library, however my question isn't specific to XNA or Farseer - but to any 2D physics library.
I would like to add "rocket"-like movement (I say rocket-like in the sense that it doesn't have to be a rocket - it could be a plane or a boat on the water or any number of similar situations) for certain objects in my 2D scene. I know how to implement this using a kinematic simulation, but I want to implement it using a dynamic simulation (i.e. applying forces over time). I'm sort of lost on how to implement this.
To simplify things, I don't need the dynamics to rotate the geometry, just to affect the velocity of the body. I'm using a circle geometry that is set to not rotate in Farseer, so I am only concerned with the velocity of the object.
I'm not even sure what the best abstraction should be. Conceptually, I have the direction the body is currently moving (unit vector), a direction I want it to go, and a value representing how fast I want it to change direction, while keeping speed relatively constant (small variations are acceptable).
I could use this abstraction directly, or use something like a "rudder" value which controls how fast the object changes directions (either clockwise or counter clockwise).
What kind of forces should I apply to the body to simulate the movement I'm looking for? Keep in mind that I would also like to be able to adjust the "thrust" of the rocket on the fly.
Edit:
The way I see it, and correct me if I'm wrong, you have two forces (ignoring the main thrust force for now):
1) You have a static "fin" that is always pointed in the same direction as the body. If the body rotates such that the fin is not aligned with the direction of movement, air resistance will apply forces to along the length of the fin, proportional to the angle between the direction of movement and the fin.
2) You have a "rudder", which can rotate freely within a specified range, which is attached some distance from the body's center of mass (in this case we have a circle). Again, when this plane is not parallel to the direction of movement, air resistance causes proportional forces along the length of the rudder.
My question is, differently stated, how do I calculate these proportional forces from air resistance against the fin and rudder?
Edit:
For reference, here is some code I wrote to test the accepted answer:
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
float dc = 0.001f;
float lc = 0.025f;
float angle = MathHelper.ToRadians(45);
Vector2 vel = new Vector2(1, 0);
Vector2 pos = new Vector2(0, 0);
for (int i = 0; i < 200; i++)
{
Vector2 drag = vel * angle * dc;
Vector2 sideForce = angle * lc * vel;
//sideForce = new Vector2(sideForce.Y, -sideForce.X); // rotate 90 degrees CW
sideForce = new Vector2(-sideForce.Y, sideForce.X); // rotate 90 degrees CCW
vel = vel + (-drag) + sideForce;
pos = pos + vel;
if(i % 10 == 0)
System.Console.WriteLine("{0}\t{1}\t{2}", pos.X, pos.Y, vel.Length());
}
}
When you graph the output of this program, you'll see a nice smooth circular curve, which is exactly what I was looking for!
If you already have code to integrate force and mass to acceleration and velocity, then you just need to calculate the individual part of each of the two elements you're talking about.
Keeping it simple, I'd forget about the fin for a moment and just say that anytime the body of your rocket is at an angle to it's velocity, it will generate a linearly increasing side-force and drag. Just play around with the coefficients until it looks and feels how you want.
Drag = angle*drag_coefficient*velocity + base_drag
SideForce = angle*lift_coefficent*velocity
For the rudder, the effect generated is a moment, but unless your game absolutely needs to go into angular dynamics, the simpler thing to do is let the rudder control put in a fixed amount of change to your rocket body angle per time tick in your game.
I suddenly "get" it.
You want to simulate a rocket powered missile flying in air, OK. That's a different problem than the one I have detailed below, and imposes different limits. You need an aerospace geek. Or you could just punt.
To do it "right" (for space):
The simulated body should be provided with a moment of inertia around its center of mass, and must also have a pointing direction and an angular velocity. Then you compute the angular acceleration from the applied impulse and distance from the CoM, and add that to the angular velocity. This allows you to compute the current "pointing" of the craft (if you don't use gyros or paired attitude jets, you also get a (typically very small) linear acceleration).
To generate a turn, you point the craft off the current direction of movement and apply the main drive.
And if you are serious about this you also need to subtract the mass of burned fuel from the total mass and make the appropriate corrections to the moment of inertia at each time increment.
BTW--This may be more trouble than it is worth: maneuvering a rocket in free-fall is tricky (You may recall that the Russians bungled a docking maneuver at the ISS a few years ago; well, that's not because they are stupid.). Unless you tell us your use case we can't really advise you on that.
A little pseudocode to hint at what you're getting into here:
rocket {
float structuralMass;
float fuelMass;
point position;
point velocity;
float heading;
float omega; // Angular velocity
float structuralI; // moment of inertia from craft
float fuelI; // moemnt of inertia from the fuel load
float Mass(){return struturalMass + fuelMass};
float I(){return struturalI + fuelI};
float Thrust(float t);
float AdjustAttitude(float a);
}
The upshot is: maybe you want a "game physics" version.
For reason I won't both to go into here, the most efficient way to run a "real" rocket is generally not to make gradual turns and slow acceleration, but to push hard when ever you want to change direction. In this case you get the angle to thrust by subtracting the desired vector (full vector, not the unit) from the current one. Then you pointing in that direction, and trusting all out until the desired course is reached.
Imagine your in floating in empty space... And you have a big rock in your hand... If you throw the rock, a small impulse will be applied to you in the exact opposite direction you throw the rock. You can model your rocket as something that rapidly converts quantum's of fuel into some amount of force (a vector quantity) that you can add to your direction vector.

Categories