External Publication
Visit Post

Jolt Vehicle Tuning Guide

jMonkeyEngine Hub May 4, 2026
Source

I went trough git history to find the root cause: Initially i had radians, then i added different cars and a json loader, the definitions have been in degrees, then i removed parts of the loader for debugging and copied over the degrees and i actually have written the doc wrong.

Additionally when using a curve instead of a fixed value, jolt expects degree and converts internally.

github.com/jrouwe/JoltPhysics

Jolt/Physics/Vehicle/WheeledVehicleController.cpp

3c1f4baad

    120. 		mLongitudinalSlip = abs((mAngularVelocity * settings->mRadius - relative_longitudinal_velocity) / relative_longitudinal_velocity_denom);


    121. 		float longitudinal_slip_friction = settings->mLongitudinalFriction.GetValue(mLongitudinalSlip);


    122.

    123. 		// Calculate lateral friction based on slip angle


    124. 		float relative_velocity_len = relative_velocity.Length();


    125. 		mLateralSlip = relative_velocity_len < 1.0e-3f ? 0.0f : ACos(abs(relative_longitudinal_velocity) / relative_velocity_len);


    126. 		float lateral_slip_angle = RadiansToDegrees(mLateralSlip);


    127. 		float lateral_slip_friction = settings->mLateralFriction.GetValue(lateral_slip_angle);


    128.

    129. 		// Tire friction


    130. 		VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();


    131. 		mCombinedLongitudinalFriction = longitudinal_slip_friction;


    132. 		mCombinedLateralFriction = lateral_slip_friction;


    133. 		combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID);


    134. 	}


    135. 	else

Here is the current pacejka simplification i use:

    /**
     * Pacejka "Magic Formula" — slip-angle only. Sampled at boot and fed
     * to Jolt as a piecewise-linear lookup.
     *
     * B controls how quickly grip builds with slip — higher B means the
     * tyre snaps into peak faster. Street tyres are soft (low B), slicks
     * are sharp (high B).
     *
     * C controls the post-peak shape — how much grip the tyre keeps
     * once it's past its best slip angle and starting to slide.
     *
     * D is the peak grip itself — the maximum μ the tyre can deliver.
     *
     * E controls how gently the curve approaches its peak — higher E
     * gives a wider plateau where the tyre stays near peak grip across a
     * range of slip angles.
     */
    static float pacejka(float slipAngleDeg, float b, float c, float d, float e) {
        double alpha = Math.toRadians(slipAngleDeg);
        double ba = b * alpha;
        double inner = ba - e * (ba - Math.atan(ba));
        return (float) (d * Math.sin(c * Math.atan(inner)));
    }

and the logitudinalfriction curve is used to make RWD working better:

            // Longitudinal friction curve — peak μ at 6 % slip ratio, falling
            // off past peak to model kinetic-friction loss in deep wheelspin
            // and locked-wheel skids. Jolt's default (3 points capped at slip
            // 0.2) clamps flat at μ=1.0 past peak (LinearCurve.GetValue holds
            // the last Y), which keeps unrealistic grip during sustained
            // wheelspin — the extended tail down to μ=0.55 at slip 100 makes
            // sliding tyres actually feel like they've given up.
            var lon = w.getLongitudinalFriction();
            lon.clear();
            lon.addPoint(0f, 0f);
            lon.addPoint(0.06f, 1.8f);
            lon.addPoint(0.20f, 1.7f);
            lon.addPoint(0.50f, 1.4f);
            lon.addPoint(1.00f, 1.2f);
            lon.addPoint(3.00f, 0.85f);
            lon.addPoint(10.0f, 0.65f);
            lon.addPoint(100.0f, 0.55f);

The remaining issue is that even with a controller input, lateral grip is marginal when steering at higher speeds.

In the current state, MAX_SAT_LAT in the FrictionCircleTireCallback is a key configuration value. Physically correct would be 1. A burning out wheel has 0 lateral grip. There are other physics aspects (not simulated) that make the this not usable in game. 0.95 is a nice value for my current setup, with 0.6 for example you have arcade like car and with 0.9 it is slightly drift capable, but it does not breakout by accident or too much throttle.

Discussion in the ATmosphere

Loading comments...