Rotate using physics

I’ve seen a question like this posted several times, but never answered properly.

I want to rotate an object to a specific rotation using the physics engine. All of the answers I’ve seen said to just slerp the angle, but I don’t want to do that because if the object collides with another and acquires rotational velocity, it should be thrown off by that rotation. Slerping the angle would prevent any of those collision physics from affecting anything.

Its a spaceship game, and I want the objects to have to apply acceleration and deceleration forces, and have to fight rotation gained from collisions to reach specified angles, so using slerp is unacceptable. If all you have to say is “don’t use physics, use slerp,” please don’t bother

6 Answers

6

Physics is not intended to allow precise positioning. To rotate a rigidbody with torque, you should write a PID controller, but feedback about the current rigidbody orientation is very unreliable (eulerAngles may return weird values, and changes from 0-360 and 360-0 when near to the origin). Take a look at this question, where I proposed an alternative physics control.

EDITED: The general idea behind a PID controller is explained in this pseudo code:

var pGain: float; // adjust the individual gains
var iGain: float; // iGain better to be 0 in this case
var dGain: float;
private var lastPError: float = 0;

function PidLoop(){
  var pError = desiredAngle - currentAngle; // calculate the proportional error...
  var iError += pError * Time.deltaTime; // the integral error...
  var dError = (pError - lastPError)/ Time.deltaTime; // and the differential error
  lastPError = pError;
  // the torque is the sum of all errors weighted by their gains:
  torque = pGain*pError+iGain*iError+dGain*dError;
  // apply the torque to the object
  ApplyTorque(torque);
}

The function PidLoop must be called at a constant rate, like in Unity’s FixedUpdate. currentAngle is the current object angle about the axis of interest (Y, for instance), and desiredAngle is the angle you want to reach. pError is the proportional error: it’s just the difference between what you want and the present position. iError is the integral error, a numeric integration of the proportional error, and dError is the differential error, a numeric differentiation of pError. The 3 errors are summed with individual weights, and the resulting value is applied as torque about the same axis currentAngle is measured.

If only pError were used, the final position would never be reached because the force applied is proportional to the error - thus zero error means zero force. To cancel this error while still mantaining some control force, the iError is used: any small pError accumulates in iError over time, creating a force that will eventualy cancel any opposite forces. If there are no opposite forces (as usually happens in rotational PID), the corresponding iGain must be set to zero, or the system will become very unstable. The differential error, dError, is very important in rotational PID, because it becomes negative when the desired angle is getting closer, acting as a brake and slowing down the rotation.

In Unity, the big problem is how to get a reliable feedback signal - the controller gets crazy when around 0, or if the other axes are non-zero. Maybe in your 2D engine this error is easier to work around - at least there are no other axes to worry about. Try to adapt an algorithm like this to your case.

You, @Duck and @Eric5h5 ... geniuses :D

Alright, well to be honest, I was hoping for something that would also work outside of unity... I have a 2d game engine where you would add rotational forces the same way as in unity, and was hoping for a solution that would be applicable for projects in there as well as in my 3d game.

Wow, this is brilliant. Thanks!

I would be very interested in seeing this pseudo code converted into tested/working code. I have tried implementing it here https://github.com/Will9371/Playcraft/tree/master/Assets/Playcraft/Quality%20of%20Life/Physics/PID by adapting the PID controller from your other post (I've also converted it to C#, extended to 3 axis rotation, combined it with PID movement, and have it following a target transform), but it moves erratically. The other post's code works OK, but doesn't provide physics-based control since it ultimately applies rotation directly.

You have successfully deployed at https://github.com/Will9371/Playcraft/tree/master/Assets/Playcraft/Quality%20of%20Life/Physics/PID https://sprunki-retake.lol

Ok, made it work without PID controller, but both @aldonaletto posts were very helpful.
PID is most useful when you don’t know how system reacts to input. But as we actually know how system reacts and it is pretty linear, it is possible to fit control system to Unity’s physics engine.

Idea behind solution is following. We know how system reacts to acceleration and deceleration, so in each moment we can tell when objects stops spinning with current angular velocity. So in each update we determine angle remaining, current angular velocity and using this data, we decide whether to accelerate or decelerate. Do it separately for both axes and you have pretty precise controller. There are few things to note. First, Unity returns angles in degrees/s, but angular velocity in radians/s.
Second, you need to consider current angular velocity when deciding to rotate right or left, for example. It may be faster to continue rotating instead of trying to stop and reverse rotation. The code uses speed bonus/penalty, first considering how would it be when idle, then adding time cost for reaching idle state, or substracting time cost needed to accelerate to current speed (both are same).
And third, systems gets unstable if rotating too fast. If system can’t stop rotation in 1/2 of spin, it will try to slow down, overshoots neutral zone and starts accelerating again. So you need to implement slowdown code (ommited as it is rather simple). Mine decelerates if speed is too high. Too high speed is computed into _maxAngularVelocity variable. Better to slowdown both axes, or at least don’t accelerate the other. Also you way want to counter rotation around forward axis.

So far it works well even with angular damping of 0.

public float angularForce = 1.0f; // this is how strong are your rotation thrusters
void Start () {
	_acceleration = anguarForce / rigidbody.mass;
	// compute max angular velocity so it can be decelerated in 1/2 rotation (PI angle)
	_maxAngularVelocity = Mathf.Sqrt(_acceleration * Mathf.PI);
}

void FixedUpdate() {
	Vector3 targetDirection = currentTarget - transform.position;
	// get direction to target in local coordinates
	Vector3 localDirection = transform.InverseTransformDirection(targetDirection);		
	Vector3 angularVelocity = transform.InverseTransformDirection(rigidbody.angularVelocity);

	float upAxisSpeed = angularVelocity.y;
	float rightAxisSpeed = angularVelocity.x;

	// SLOWDOWN CODE HERE

	Vector2 upAxisVector = new Vector2(localDirection.x, localDirection.z).normalized; // vector in X-Z plane
	float upAxisAngle = Vector2.Angle(Vector2.up, upAxisVector) * Mathf.Deg2Rad;	// in degrees, convert to RADS!
	float upAxisStaticSign = Mathf.Sign(upAxisVector.x);

	// Same for Right axis = X

	forceUp = angularForce * GetAxisForce(upAxisAngle, upAxisSpeed, upAxisStaticSign);
	forceRight = angularForce * GetAxisForce(rightAxisAngle, rightAxisSpeed, rightAxisStaticSign);

	// apply forces
	rigidbody.AddRelativeTorque(forceUp * Vector3.up);
	rigidbody.AddRelativeTorque(forceRight * Vector3.right);
}

// this return 0, -1 or 1, meaning none or max force in either direction
float GetAxisForce(float axisAngle, float axisSpeed, float directionSign, string axisLabel) {
	// this is direction of current rotation, helps to choose direction to reach target
	float dynamicCorrectionSign = Mathf.Sign(axisSpeed * directionSign);
	// this is how long will reach to stop from current velocity OR to accelerate from idle to current velocity
	float timeToStop = Mathf.Abs(axisSpeed / _acceleration);
	// how long would take to reach target in shorter direction if angular velocity is 0
	float staticToReach = Mathf.Sqrt(4 * axisAngle / _acceleration);
	float staticToReach2 = Mathf.Sqrt(4 * (2*Mathf.PI - axisAngle) / _acceleration); // same for longer direction
	// adjust these durations by current speed (this is basically integral part of velocity)
	float shorterTimeToReach = staticToReach - dynamicCorrectionSign * timeToStop;
	float longerTimeToReach = staticToReach2 + dynamicCorrectionSign * timeToStop;
		
	// choose direction, considering current velocity
	float force;
	float timeToReach;
	if (shorterTimeToReach <= longerTimeToReach) {
		force = directionSign;
		timeToReach = shorterTimeToReach;
	} else {
		force = -directionSign;
		timeToReach = longerTimeToReach;
	}

	// distance is small enough, consider target rotation reached (this pat could use more love)
	if (Mathf.Abs(timeToReach) < 0.1f) {
		// idle
		force = 0;
	} else if (timeToStop >= timeToReach) {
		// moving too fast, slow down
		force = -force;
	}

	return force;
}

I think I made the [40772-working-chybis-script.zip|40772] code working, but I don't think it does what it is supposed to. Or maybe I did something wrong?

You have to assign a rigidbody component to the object and add torque, check this out:

http://unity3d.com/support/documentation/ScriptReference/Rigidbody.AddTorque.html

I know that. I'm looking for the way to add torque then remove it timed to stop at the right point. So it's more a matter of when to time the stoping point.

I’m also doing some space ship alike game with friction in the air → rotation should also stop. I didn’t get that ^ code working. What is currentTarget?

I made this kind of code to slow down the rotation of ship’s axises:

float slowDownPower = 10.0f;
Vector3 localAngularVelocity = transform.InverseTransformDirection(gameObject.rigidbody.angularVelocity);
gameObject.rigidbody.AddRelativeTorque(-localAngularVelocity * slowDownPower * Time.deltaTime);

It seems to work so far.

toniepeli, I also came to something similar, but am looking to a solution that takes into account 'virtual' engine that has 'maximum throttle' at which the vessel tries to come to a complete stop. For example, maxThrottle = 10. We call AddRelativeTorque(10,5,3) for some time (2 seconds), then vessel drifts some time and then we need to completely stop it using AddTorque or AddRelativeTorque in accordance with the maxThrottle. Any ideas or links to answers on this matter?

Hey! I haven't been working with Unity for awhile. Currently making 2D space flight game:) I didn't get the point completely. Is the purpose to rotate the space ship in steps?

The idea is to use Torque to decelerate rotating (all axis) object (ship) with given limited force (throttle). Let's say your ship received huge hit which caused vast rotation of it. We need to have the emergency slowdown/stabilization mode (to a complete stop). The engine can produce only (for example) 10 points of torque per time interval and we want to stop at maximum possible rate, so to say. Any suggestions/links to a solution?

WOW, tried PID and I always got weird results, so I wrote my code :

to put in a file called PhysicsHelper.cs

using UnityEngine;
using System.Collections;

public static class PhysicsHelper
{
    
    public static void TorqueLookAtPoint(Rigidbody rigidbody, Vector3 point, float force, float damper = 0f)
    {
        Vector3 direction = point - rigidbody.position;
        TorqueLookToward(rigidbody, direction, force, damper);
    }
    public static void TorqueLookToward(Rigidbody rigidbody, Vector3 direction, float force, float damper = 0f)
    {

        Vector3 p = rigidbody.position;
        Vector3 forward = rigidbody.transform.forward; // axis we are rotating

        Vector3 cross = Vector3.Cross(forward, direction);

        float angleDiff = Vector3.Angle(forward, direction);
        angleDiff = Mathf.Sqrt(angleDiff);
        
        rigidbody.AddTorque(cross * angleDiff * force, ForceMode.Acceleration);
        rigidbody.AddTorque(-rigidbody.angularVelocity * damper, ForceMode.Acceleration);
        //rigidbody.AddTorque(cross * angleDiff * force);

        //Debug.Log(direction);
        //Debug.DrawLine(p, p + direction.normalized, Color.yellow, .05f);
        //Debug.DrawLine(p, p + rigidbody.angularVelocity, Color.yellow);
        //Debug.DrawLine(p, p + new Vector3(rigidbody.angularVelocity.x, 0, 0), Color.red);
        //Debug.DrawLine(p, p + new Vector3(0,rigidbody.angularVelocity.y, 0), Color.green);
        //Debug.DrawLine(p, p + new Vector3(0,0,rigidbody.angularVelocity.z), Color.blue);
    }

}

Example of use :

        Rigidbody rigid = transform.GetComponent<Rigidbody>();
        Vector3 direction = Vector3.one;
        float force = 100f / Time.fixedDeltaTime;
        float damper = 50f;
        PhysicsHelper.TorqueLookToward(rigid, direction, force, damper);

Finally just adjust the force and set the dampening to not have your object’s direction pass the direction and have a spring effect.

It may fit for most of your cases, with a minimum problems

Beware, if you’d like to change the deltatime per example, the force changes and needed dampening
too (but it’s just proportional to speed). Anyway, best solution I’ve found

>>> GIF <<<

I have found a bug. In some situations, in my case a ragdoll, this script can make the rigidbody explode (accelerate it improperly). Fortunately, simply changing ForceMode.Acceleration to ForceMode.Force for both AddTorque calls, remedies this problem. FANTASTIC script you have there, though! Completely saved my sanity!

My pleasure and thanks to notice it. I'm not a prog but a designer graduating in mechanical engineering and using this method is more flexible and less foggy. It usually leave less bugs and bring versatility.

That's a nasty squareroot call, though. A very computational operation. Anyone using this for moving many units, might want to find a way around that call.

Rotation (as known from the site https://assignment.essayshark.com/physics-help) means turning around a center. Every point makes a circle around the center, but the distance from the center to any point on the shape stays the same. So, maybe this examples may help you in this issue:

Unity Platform Protection is essential for developers to safeguard their games and apps from piracy, hacking, and data theft. By using encryption, secure APIs, and regular software updates, creators can ensure their intellectual property remains safe. Just as platforms like https://exoticcars.ae/ focus on quality and authenticity in their domain, developers must protect their digital assets to maintain trust, performance, and profitability in an increasingly competitive and security-conscious digital marketplace.