Physics-based Rotation - Arriving at a Target Value

Good day, everyone!

Okay, so here’s the deal: I’ve been fiddling with some code over the past few days, and I’m currently not seeing a solution that I’m pretty sure is right in front of me. It’s kind of like having a massive tool bag, but not being sure which is the right tool for the job.

I’m working on a rotation system that will allow the user to input a target rotation rate in degrees per second, according to each of the local axis of rotation. Effectively, pitch yaw and roll on a spaceship. The physics system then applies appropriate local angular torque to said object.

I’ve managed to get the code written that will apply forces appropriately based on the values put in, but I’m running into a problem where, between fixed update steps, the values will momentarily exceed the target. The result is a nasty back-and-forth between applying opposing forces on the model when it approaches the target values, as it never arrives at the exact value specified. I’m hoping to find a way to force the vector to be the target value once the numbers are close enough, but I’m out of ideas on where/how to do so atm. Which is ridiculous, since mathf.clamp seems to work fine just about everywhere else.

Any assistance would be appreciated. Here is the current hacked together code. It works with a test cube that has a rigidbody mass of 300 and 0 angular drag.

using UnityEngine;
using System.Collections;

public class ShipRotation : MonoBehaviour {
    public float rotationSpeed = 300f;
    public float maxTurn = 6f;  //maximum turn in degrees per second before structural damage or crew injury is possible
    public float maxPitch = 6f;  //maximum pitch in degrees per second before structural damage or crew injury is possible
    public float maxRoll = 6f;  //maximum roll in degrees per second before structural damage or crew injury is possible
    public Vector3 setRotation = Vector3.zero;  //set rotation in degrees per second
    public Vector3 naturalTargetRotation = Vector3.zero;  //current rotation in radians
    public Vector3 currentRotation = Vector3.zero;  //current rotation in degrees per second
    public Vector3 forceToApply = Vector3.zero;  //calculated vector information for relative torque

    void FixedUpdate () {
        Vector3 tmpVector = transform.InverseTransformDirection(GetComponent<Rigidbody>().angularVelocity);
        currentRotation = new Vector3 (tmpVector.x * Mathf.Rad2Deg, tmpVector.y * Mathf.Rad2Deg,tmpVector.z * Mathf.Rad2Deg);


        naturalTargetRotation = new Vector3 (setRotation.x * Mathf.Deg2Rad, setRotation.y * Mathf.Deg2Rad, setRotation.z * Mathf.Deg2Rad);
        ProcessTurn ();
    }

    void ProcessTurn()
    {
        CapTurns ();
        if (currentRotation.x == setRotation.x){
            forceToApply.x = 0f;
        } else if (currentRotation.x > setRotation.x) {
            forceToApply.x = -rotationSpeed * Time.fixedDeltaTime;
        } else if (currentRotation.x < setRotation.x) {
            forceToApply.x = rotationSpeed * Time.fixedDeltaTime;
        }

        if (currentRotation.y == setRotation.y){
            forceToApply.y = 0f;
        } else if (currentRotation.y > setRotation.y) {
            forceToApply.y = -rotationSpeed*Time.fixedDeltaTime;
        }
        else if (currentRotation.y < setRotation.y) {
            forceToApply.y = rotationSpeed*Time.fixedDeltaTime;
        }

        if (currentRotation.z == setRotation.z){
            forceToApply.z = 0f;
        } else if (currentRotation.z > setRotation.z) {
            forceToApply.z = -rotationSpeed*Time.fixedDeltaTime;
        }
        else if (currentRotation.z < setRotation.z) {
            forceToApply.z = rotationSpeed*Time.fixedDeltaTime;
        }

        GetComponent<Rigidbody> ().AddRelativeTorque (forceToApply);

    }

    void CapTurns()
    {
        if (currentRotation.magnitude <= 0.08f) {
            //currentRotation = setRotation;
        }
        Debug.Log (currentRotation.x - setRotation.x);
    }
}

First thing to realize with floating values is that you can’t use == comparison. The 0.00000001 precision always comes from somewhere, and then ruins the math. Or angle being 0.01 and you then apply a -0.0007 force or something, it will skip 0.0. So you can use Mathf.Abs(currentRotation.z - setRotation.z) < 0.01 or other value that works better in testing.

Or you can rotate based on the angular difference, something like:

forceToApply.x = Mathf.Clamp((setRotation.x - currentRotation.x) * SomeMultiplier, -rotationSpeed, rotationSpeed) * Time.fixedDeltaTime;

With this you don’t need any of those IF’s, and it will slow the rotation speed down when it’s closing the target.