How to smoothly re-orient an object after a collision?

I have a vehicle traveling in a forward motion, and for the most part I always want its rotation to be 0,0,0 (it can “strafe” but not turn).

There are some stationary obstacles with standard colliders. When the vehicle hits one, it knocks the vehicle around a bit but I want the vehicle to then correct its orientation and continue moving forward at a rotation of 0,0,0.

Here are a few things I’ve tried that aren’t working:

First I’ve tried just doing a continual correction with some smoothing, like this:

_rb.rotation = Quaternion.Lerp(_rb.rotation, Quaternion.Euler(0, 0, 0), smoothing * Time.deltaTime);

Where _rb is the vehicle’s RigidBody and smoothing is a float (set around 10). This almost works, because it does correct the orientation a bit, but of course the problem with this kind of smoothing is that you never reach the target value of the lerp. It just makes exponentially smaller increments towards it. At some point I have to reach 0,0,0 or the vehicle drifts off to a side rather than moving straight.

I also tried setting up a time-stepped lerp. I gave a total time and then on collision I set a timer to that value and then calculate the steps like this:

    if (_timer > 0)
    {
        float step = 1 - (_timer / forwardCorrectionTime);
        _rb.rotation = Quaternion.Lerp(_rb.rotation, Quaternion.Euler(0, 0, 0), step);
        _timer -= Time.deltaTime;
    }

This sort of works if I set the correction time to something reasonably long, like greater than 5 seconds, but I want the correction to happen more quickly. If I lower the time then it will perform the correction, but then the ship jukes back to an incorrect rotation - I’m guessing that perhaps the physics engine is still performing the effects of the collision after the corrective rotation.

The only reliable method I’ve found is simply forcing the rotation instead of using a lerp, but this of course kills the effect of the collision and is not what I’m going for.

Before attempting to modify position or rotation of a rigidbody, you should make it kinematic. Did you try that ?
Something like:

if (mReorientationEnabled)
{
    _rb.isKinematic = true;
    _rb.rotation = Quaternion.Lerp(_rb.rotation, Quaternion.Euler(0, 0, 0), smoothing * Time.deltaTime);
}
else
{
    _rb.isKinematic = false;
}

@toromano Mentioned lerp wil work but, technically it will never reach it’s target, you will be creating zeno’s paradox. You will (if ever) reach the target because of the float tail rounding. But this is important if getting to target is a thing and it has to be correct. For start Id use Vector3.MoveTowards() and edit localEulerAngles or rotation directly (converting my vector to Quaternion.Euler() first).

Here’s a thought on an approach to this… Let’s say you want a slight delay after an impact, followed by a recovery time based on how far you were turned by the physics interaction:

enum DrivingState {Driving, Crashing, Recovering}
DrivingState state = DrivingState.Driving;

// This can serve as a baseline multiplier for how long it takes to recover to drive again.
// This can be further scaled if desired based on the severity of the impact (i.e. dot product of vehicle velocity and normal of face impacted)
public float crashDelayMod; // in seconds
// This, instead, is a multiplier for degrees-per-second to rotate
public float recoveryDelayMod;
float recoveryTimer; // Can be reused for the actual time spent on both states
Quaternion crashRotation; // Where were you looking at the very last moment of the crash?
float recoveryAngle; // The angle to correct for after impact, scaled on recovery speed by recoveryDelayMod

// ...

void OnCollisionEnter(Collision other)
{
	if(!other.CompareTag("ground") // faster than (other.tag != "ground")
	{
		state = DrivingState.Crashing;
		recoveryTimer = 0.0f;
	}
}

void Update()
{
	if(state != DrivingState.Crashing)
	{
		// Driving Controls handled here
		
		if(state == DrivingState.Recovering)
		{
			recoveryTimer += Time.deltaTime;
			float recoveryPercent = recoveryTimer * (recoveryDelayMod / recoveryAngle);
			_rb.rotation = Quaternion.Slerp(crashRotation, Quaternion.identity, recoveryPercent);
			if(recoveryPercent >= 1.0f)
			{
				state = DrivingState.Driving;
			}
		}
	}
	else //if(state == DrivingState.Crashing) // The implied result
	{
		recoveryTimer += Time.deltaTime;
		if(recoveryTimer >= crashDelayMod)
		{
			crashRotation = _rb.rotation;
			recoveryTimer = 0;
			recoveryAngle = Quaternion.Angle(crashRotation, Quaternion.identity);
			if(recoveryAngle > 0) // Need to straighten out
			{
				state = DrivingState.Recovering;
			}
			else // Didn't turn whatsoever from the impact
			{
				state = DrivingState.Driving;
			}
		}
	}
}

Just a heads up that I did write this without testing it, but hopefully this at least offers an idea of how this can be approached.

Edit: In the end, I forgot to turn the state back to “Driving” – Whoops!