Reputation: 235
I found this script that returns rotational speed (taking into account where force is applied and distance from center of mass).
public Vector3 ForceToTorque(Vector3 force, Vector3 position, ForceMode forceMode = ForceMode.Force)
{
Vector3 t = Vector3.Cross(position - body.worldCenterOfMass, force);
ToDeltaTorque(ref t, forceMode);
return t;
}
private void ToDeltaTorque(ref Vector3 torque, ForceMode forceMode)
{
bool continuous = forceMode == ForceMode.VelocityChange || forceMode == ForceMode.Acceleration;
bool useMass = forceMode == ForceMode.Force || forceMode == ForceMode.Impulse;
if (continuous) torque *= Time.fixedDeltaTime;
if (useMass) ApplyInertiaTensor(ref torque);
}
private void ApplyInertiaTensor(ref Vector3 v)
{
v = body.rotation * Div(Quaternion.Inverse(body.rotation) * v, body.inertiaTensor);
}
private static Vector3 Div(Vector3 v, Vector3 v2)
{
return new Vector3(v.x / v2.x, v.y / v2.y, v.z / v2.z);
}
With 2100 newtons I'm getting 0.6 radians (36 degrees) of rotation.
var test = rotationScript.ForceToTorque(shipFront.right * 2100, shipFront.position, ForceMode.Force);
Debug.Log(test + " " + test * Mathf.Rad2Deg);
// Above gives (0, 0.6, 0) and (0, 36.1, 0)
But using AddForceAtPosition
to rotate the ship with the same force I don't get the same result
if (currTurn > 0) {
body.AddForceAtPosition(shipFront.right * 2100, shipFront.position, ForceMode.Force);
body.AddForceAtPosition(-shipBack.right * 2100, shipBack.position, ForceMode.Force);
} else if (currTurn < 0) {
body.AddForceAtPosition(-shipFront.right * 2100, shipFront.position, ForceMode.Force);
body.AddForceAtPosition(shipBack.right * 2100, shipBack.position, ForceMode.Force);
}
It's not giving me 36 degrees per second - I tested by counting how long it took to do a full 360 spin, supposedly it should've been done in 10s but it took 10s to rotate only ~90º.
There's a lot I don't understand in the first script, like most of the physics part, but I don't see it taking into consideration my ships mass (body.worldCenterOfMass
?), could that be it?
I need this so I can rotate my ship more precisely.
Upvotes: 1
Views: 2124
Reputation: 15045
The major mistake was a confusion between acceleration and velocity. Applying a torque leads to angular acceleration (radians per second per second), which is given by your test
(36.1 deg / s^2
around the Y-axis). This is not the angular velocity, but the rate-of-change, so you should not expect the same result.
(Also the force passed to ForceToTorque
is only half of the required force.)
Quick physics notes - torque equation:
I
is the moment-of-inertia tensor, a 3x3 matrix given by the integral above, over all mass elements of the body. It is obviously symmetric in its indices i
and j
, so it is diagonalizable (any decent linear algebra book):
D
is the M-of-I tensor in the body's principal axes basis, and R
is the rotation matrix from the principal to the current basis. The diagonal elements of D
are the values of the vector body.inertiaTensor
, which means that Unity always aligns the object's principal axes with the world axes, and that we always have I = D
.
Therefore to obtain the angular acceleration arising from a torque:
Where the last line is performed by Div
.
A better way to ensure accurate rotation is to apply an angular impulse, which directly changes the angular velocity. Q
and the corresponding linear impulse P
required both satisfy:
This directly changes the angular velocity of the body. (*)
is a condition that the input parameters must satisfy. You can still use AddForceAtPosition
with ForceMode.Impulse
. Code:
Vector3 AngularvelocityToImpulse(Vector3 vel, Vector3 position)
{
Vector3 R = position - body.worldCenterOfMass;
Vector3 Q = MultiplyByInertiaTensor(vel);
// condition (*)
if (Math.Abs(Vector3.Dot(Q, R)) > 1e-5)
return new Vector3();
// one solution
// multiply by 0.5 because you need to apply this to both sides
// fixes the factor-of-2 issue from before
return 0.5 * Vector3.Cross(Q, R) / R.sqrMagnitude;
}
Vector3 MultiplyByInertiaTensor(Vector3 v)
{
return body.rotation * Mul(Quaternion.Inverse(body.rotation) * v, body.inertiaTensor);
}
Vector3 Mul(Vector3 v, Vector3 a)
{
return new Vector3(v.x * a.x, v.y * a.y, v.z * a.z);
}
To apply:
var test = AngularvelocityToImpulse(...);
body.AddForceAtPosition(test, shipFront.position, ForceMode.Impulse);
body.AddForceAtPosition(-test, shipBack.position, ForceMode.Impulse);
Upvotes: 7