- Home /
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
Answer by aldonaletto · Apr 06, 2012 at 01:55 PM
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.
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.
Oh my God. PLEASE put your code in code tags its not hard! You're making this 10x harder than it should be for someone looking for help. I seriously thought this was the one answer I needed. Now I have to sort this crap out.
Seriously if you can't just use code tags then how am I able to trust that your code is actually reliable? I don't believe using the physics engine produces "weird" values for eulerAngles when used. Perhaps you just don't have a frickin' clue?
God I feel like this is a troll answer. >:(
EDIT: Also Idk how a physics engine can have "weird" or "unreliable" output. Physics, at least the conventional kind (as opposed to the quantum kind) is NOT unreliable or unpredictable. Its quite predictable that's what physics (and science) is all about. $$anonymous$$ystery solved. This guy doesn't have a frickin' clue.
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.
Answer by Chybis · Oct 27, 2013 at 10:18 PM
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;
}
Could you please post the complete code for this? Your incomplete code makes me have more questions than answers.
I have a working controller that is intended for mouse axis lock. However, I am trying to achieve a double click in space to turn, just like here
What I have so far:
float roll = directionTurn.x - rigidbody.transform.up.x;
float pitch = directionTurn.y - rigidbody.transform.right.y;
rigidbody.AddRelativeTorque(Vector3.up * roll * rollConstant * Time.deltaTime);
rigidbody.AddRelativeTorque(Vector3.left * pitch * pitchConstant * Time.deltaTime);
And then I try to stabilize it without success =(
I think I made the Chybis code working, but I don't think it does what it is supposed to. Or maybe I did something wrong?
Answer by garner · Apr 06, 2012 at 04:20 AM
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.
Answer by toniepeli · Dec 05, 2013 at 03:16 AM
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?
Answer by Map-Builder · Jan 11, 2017 at 12:10 AM
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
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 Force$$anonymous$$ode.Acceleration to Force$$anonymous$$ode.Force for both AddTorque calls, remedies this problem. FANTASTIC script you have there, though! Completely saved my sanity!
$$anonymous$$y 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.
Your answer
Follow this Question
Related Questions
How may I observe expected physical interactions while using Rigidbody.MoveRotation()? 1 Answer
Rotating a Rigidbody with Physics 1 Answer
How to keep rotation from switching between - and + after a 360? 0 Answers
How do i keep a rigidbody upright? Looking for best way to freeze rotation 1 Answer
Freezing Rotation and Joints (Swing / Rope) Physics Issues 1 Answer