🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Engine-Driveshaft-Wheel closed loop dependency

Started by
5 comments, last by Samith 2 years, 4 months ago

Another one of my questions with regards to vehicle physics. This is more of a theorethical question and less about implementation details. So I'm looking at implementing an vehicle engine, driveshaft and wheels into the simulation.

The engine spins at a given RPM, produces torque which is then applied onto the driveshaft through the transmission (gear ratio) which is then applied onto the wheels via the differential. (differential ratio.)

So the calculation (from the engine RPM to the wheels) looks like this:

float engineRPM = ... //current RPM
//engine stats
float gearRatio = 2.3f;
float differentialRatio = 1.5f;
float finalDriveRatio = 1.7f;
float transmissionEfficiency = 0.7f;
float wheelRadius = 0.3f;

float engineTorque = lookupCurve(engineRPM) * throttle;//lookup torque of engine to the given RPM
float wheelTorque = engineTorque * gearRatio * differentialRatio * finalDriveRatio * transmissionEfficiency;

I'm specifically talking about this line here:

//direction: engine > driveshaft > wheel
float wheelTorque = engineTorque * gearRatio * differentialRatio * finalDriveRatio * transmissionEfficiency;

by going from the engine through the transmission, driveshaft and differential to the wheel we multiply the given ratios with the engine torque.

Now, because those components are a closed loop system, the engine RPM (float engineRPM = ... //current RPM from the first codeblock) depends on the wheels angular velocity (which got accelerated by the engines torque).

The angular velocity can be calculated by dividing the torque by the wheels inertia:

float wheelInertia = 1;//for simplicity purposes
float wheelAngularVelocity = wheelTorque / wheelInertia;

//convert angular velocity from radians/s to rpm for later use
float wheelRPM = wheelAngularVelocity*60.0f/2.0f*pi; //from angular velocity to revolutions per minute

So far so good. Here comes the part that i don't understand.

We now want to use wheelRPM (which is essentially the angular velocity) an input for the engine RPM from the first codeblock. (float engineRPM = ... //current RPM)

According to this source: https://asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html​ as well as a reply i've found on this site: https://gamedev.net/forums/topic/677344-engine-rpm-and-wheel-angular-velocity/5283248/?page=1

The RPMs have to be calculated by multiplying the wheelRPM with the ratios (again).

//from wheelRPM to engineRPM
float engineRPM = wheelRPM  * gearRatio * differentialRatio * finalDriveRatio;

But shouldn't the reverse be the case? Instead of multiplying the wheel with the rations shouldn't we perform a division like this: ?

//from wheelRPM to engineRPM (?)
float engineRPM = wheelRPM  / gearRatio / differentialRatio / finalDriveRatio;

My thinking is that (for a very simple example) if we have an engine spinning at 1000 RPM with a gearRatio of 2 and attach this directly to the wheels then the wheels will spin twice as fast (2000 RPM). So if we want to calculate the engine RPM from the wheelRPM (going through the transmission) we should basically reverse the equation and perform a division instead of multiplication?

So in the end the closed loop would look something like this:

float wheelInertia = 1;//for simplicity purposes
float wheelAngularVelocity = wheelTorque / wheelInertia;
float wheelRPM = wheelAngularVelocity*60.0f/2.0f*pi;

float engineRPM = wheelRPM  / gearRatio / differentialRatio / finalDriveRatio;
//or? float engineRPM = wheelRPM  * gearRatio * differentialRatio * finalDriveRatio;

//engine stats
float gearRatio = 2.3f;
float differentialRatio = 1.5f;
float finalDriveRatio = 1.7f;
float transmissionEfficiency = 0.7f;
float wheelRadius = 0.3f;

float engineTorque = lookupCurve(engineRPM) * throttle;//lookup torque of engine to the given RPM

wheelTorque = engineTorque * gearRatio * differentialRatio * finalDriveRatio * transmissionEfficiency;

//wheelTorque is then used at the top of the codeblock as an input

I'm probably overlooking something crucial here so any insight would be greatly appreciated.

Advertisement

My thinking is that (for a very simple example) if we have an engine spinning at 1000 RPM with a gearRatio of 2 and attach this directly to the wheels then the wheels will spin twice as fast (2000 RPM). So if we want to calculate the engine RPM from the wheelRPM (going through the transmission) we should basically reverse the equation and perform a division instead of multiplication?

A gear ratio is typically the ratio of the output gear radius to the input gear radius. For a gear ratio of 2, that would mean the output gear has twice the radius, and thus the output gear would spin half as fast as the input gear. See the mechanical advantage section here: https://en.wikipedia.org/wiki/Gear_train​.​ This means that in your example, an engine spinning at 1000 RPM with a gear ratio of 2 would mean that the wheels are actually spinning at 500 RPM, not 2000 RPM. The torque on the wheel, however, would be the engine torque times two. As the mechanical torque advantage goes up, the speed goes down, in order for the total work done to be the same.

The torque on the output is the input torque multiplied by the gear ratio. Meanwhile, the speed of the output is the speed of the input divided by the gear ratio. In order to get the speed of the input (ie the engine RPM) from the output, we do the inverse and multiply the speed of the output by the gear ratio.

Alright so if i understand this correctly: (Torque goes up by multiplication, rpm goes down by division)

float engineTorque = lookupCurve(engineRPM) * throttle;

float wheelTorque = engineTorque * gearRatio * differentialRatio * finalDriveRatio * transmissionEfficiency;
float engineAngularVelocity = (engineRPM * 2.0f * Pi)/60.0f);
float wheelAngularVelocity = (engineAngularVelocity  / (gearRatio * differentialRatio * finalDriveRatio * transmissionEfficiency); //not sure about transmissionefficiency there

Is this correct?

I just wonder why then the angular velocity of the wheel is calculated as

float wheelAngularVelocity = wheelTorque / wheelInertia;

and then multiplied with:

float wheelRPM = wheelAngularVelocity*60.0f/2.0f*pi;
float engineRPM = wheelRPM * gearRatio * differentialRatio * finalDriveRatio;

if we assume that the inertia is a constant (which it is AFAIK) then wheelAngularVelocity will increase with the torque (as the input in the engine RPM) even though it should decrease? (So is this calculation even correct?) In the Macro Monster article about vehicle physics the gear/differential ratios dont seem to be taken into account for that. (in this case the engine RPM would quickly explode to very high values.)

The only thing i can imagine is that we have to keep track of the angular velocity of the wheels as well as the torque simultaneously (as shown in the first codeblock above) and operate on those two variables to calculate the force on the wheel via the torque as well as the engineRPM via the wheels angularVelocity.

/edit: i'm very likely confusing a lot of stuff here. (have to read up on it more.)
From what i've gathered wheelTorque / wheelInertia; is actually the angular acceleration instead of the tires velocity. (so the acceleration is added to the wheels angular velocity which is in turn passed to the engines input.)

From what i've gathered wheelTorque / wheelInertia; is actually the angular acceleration instead of the tires velocity. (so the acceleration is added to the wheels angular velocity which is in turn passed to the engines input.)

This is exactly right. The wheelTorque affects the acceleration. And in fact, the wheelTorque from the engine is only one component of the final angular acceleration of the wheel: the final angular acceleration will be the sum of all the torques acting on the wheel divided by the wheel inertia. The other torques acting on the wheel will be the friction from the road (probably calculated from the longitudinal pacejka for the wheel's current slip ratio), the braking torque (depends on how much the user is braking), and the rolling resistance of the wheel.

You'll add up all those torques to get the total torque acting on the wheel, and divide by the inertia to get the total acceleration. Then, you'll use the acceleration to update the wheel angular velocity. The new wheel angular velocity will be the old angular velocity plus the angular acceleration times the timestep. If you read the Chicken and Egg section of the tutorial you linked, you'll see the author talks about how this is actually a form of numerical integration.

Here is a bit of pseudo-code that might help: (EDIT: I've ignored rolling resistance in this code because it only has a small effect)

void update(float deltaTime)
{
	// these prev values are derived from the wheelAngularVelocity at the last tick
	float prevWheelAngularVelocity = wheelAngularVelocity;
	float prevWheelRpm = prevWheelAngularVelocity * 60.0f * 2.0 / PI;
	float prevEngineRpm = prevWheelRpm * gearRatio * finalDifferentialRatio;	// calculate what the engine rpm would be, given the angular velocity of the wheels

	float longitudinalPacejkaTorque = calcLongitudinalPacejka(prevWheelAngularVelocity);	// this function presumably calculates the slip ratio and then passes that into the Pacejka formula to get the longitudinal friction force
	float brakeTorque = calcBrakeTorque(userBrakeAmount);		// this function calculates the friction torque from the brakes given a user input
	float engineTorque = lookupCurve(prevEngineRpm) * throttle;		// as you've already written
	float wheelTorque = engineTorque * gearRatio * finalDifferentialRatio * transmissionEfficiency;
	float wheelAngularAcceleration = (wheelTorque + longitudinalPacejkaTorque + brakeTorque) / wheelInertia;

	// now update the wheelAngularVelocity for our new timestep, using a simple Euler integration method
	// next time we update, we'll use this new angular velocity to calculate what our engine rpm is and use that in the throttle calculations
	wheelAngularVelocity += wheelAngularAcceleration * deltaTime;
}

Alright got it. (I hope. There is so much stuff to take into consideration.)

One thing that i'm wondering though (it's not directly related to the engine torque question)

float longitudinalPacejkaTorque = calcLongitudinalPacejka(prevWheelAngularVelocity);

For the pacejka curve we need AFAIK the angular velocity of the

float longitudinalVelocity = dot(rigidbody.getVelocityInLocalPoint(wheelOrigin), forwardAxisWheel);//in m/s
float slipRatio = ((wheelAngularVelocity * wheelRadius) - longitudinalVelocity) / std::abs(longitudinalVelocity);

(have yet to figure out how to handle the case where the slipratio gets near 0 due to longitudinalVelocity being close to 0.)

and from what i've seen we have to multiply the slipratio with the tireload (which is approximately (mass * gravity) / number of wheels )

so we get

float tractionForce = pacejka(slipRatio) * tireload;

and i presume we use that as the force to apply to the rigidbody as well as the torque to feed back to the gearing system?

//apply tractionforce to move the vehicle
rigidbody.applyForce(forwardAxisNormalWheel * tractionForce , wheelOrigin);

//calculate torque from tractionForce
float tractionTorque = tractionForce * wheelInfo.tireRadius; 

//...
//feed back to wheel
float wheelAngularAcceleration = (wheelTorque + tractionTorque + brakeTorque) / wheelInertia;

Issue here is that the tractionforce ( float tractionForce = pacejka(slipRatio) * tireload; ) doesn't seem to take the wheelRadius and it's angular velocity directly into consideration? (We feed that data into the pacejka curve but as a return we get a normalized pacejkavalue which we then multiply by the tireload.)

The issue with that is that with high traction values they not only don't align properly with the rotation of the wheels (they still slip like on ice) if we feed the calculated tractionTorque back to the wheel it “overshoots” and starts for example accelerating the wheel either forwards or backwards.

In my case i have as an example set the mass of the vehicle to 600.

vehicleMass = 600;//kg
tireLoad = (600 * 9.81)/4 = 1.471,5 newtonmeters ((mass * gravity) / wheelcount)

//per tire:
tractionForce = pacejka(slipRatio) * 1.471,5; //return of pacejka(slipRatio) is a normalized value between -1 and +1)
tractionTorque = tractionForce * tireRadius; //of the tireradius is 1 meter we get exactly 1.471,5 in torque.

Are those calculations somewhat correct? (I feel that the way i calculate the force and torque is as the added tractionTorque quickly explodes in range.) Hence my suspicion that somewhere here we need to take the actual distance traveled into account.

float distanceTraveled = (wheelInfo.angularVelocity * wheelInfo.tireRadius) - longitudinalVelocity;

(to atleast clamp it to not cause the value to shift into the reverse sign?

/Edit:
So scratch most of what that i said above.
The tractionForce which is the pacejkaValue multiplied by the tireload (= (mass * gravity) / number of wheels) is the force which has to be overcome by the vehicles engine/wheeltorque in order to accelerate the car. (if we assume there is no slip, the engine would have to output more force than the tractionforce in order to get the car moving.). To take tireslipping into account we multiply the tireload with the (normalized) pacejka value.
The only thing that we have to take into account is that if the tractionForce (which gets added to the engines torque as an opposing force) is larger then the current engine/vehicle torque then the values start to oscillate. (at best.) So this has to be handled manually.

Lewa said:
The only thing that we have to take into account is that if the tractionForce (which gets added to the engines torque as an opposing force) is larger then the current engine/vehicle torque then the values start to oscillate. (at best.) So this has to be handled manually.

There are plenty of natural cases where traction torque will be greater than engine torque: for example when the car is coasting or when the wheel isn't driven (ie a front wheel in a rear wheel drive car). So there's nothing abnormal or special about that situation. The traction force is what will cause the non-driven wheels to start rotating to match the motion of the car. At moderate and high speeds I wouldn't expect oscillations to occur. That said, at very low speeds oscillations will occur and do have to be manually handled. This is just the nature of the Pacejka formula and the finite time-step integration. I don't know if there's some “correct” or best strategy to deal with oscillations, but I deal with it by averaging out the traction force over a couple frames and that seems to damp the oscillations quite a bit. Something like this:

float scaleFactor = 0.5f;	// scaleFactor can be adjusted. A value of 1 will do no blending, 0 will use only the last frame's traction. 0.5 will mix
float tractionForce = previousFrameTractionForce * (1 - scaleFactor) + calculateLongitudinalTractionForce(etc, etc) * scaleFactor;

Seems to work okay for me. There are almost certainly smarter/better solutions than this but for my purposes this works well enough.

This topic is closed to new replies.

Advertisement