1

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.

RealAnyOne
  • 125
  • 3
  • 3
  • 17
  • Why don't you just transform.rotate your ship. Then you can make it as precise as you want. Instead of trying to calculate some kind of tertiary value. – Tyler S. Loeper Jun 06 '18 at 18:13
  • 1
    A force gives an *acceleration*, not a velocity, so the end result also depends on the time period over which to force was applied. The mass dependence is in the `body.inertiaTensor` property. – meowgoesthedog Jun 06 '18 at 19:05
  • 2
    "I'm getting 36 degrees of rotation." Note RealAnyOne, that when you give **rotational speed** you give it as **"degrees per second"**. (Just like your speed in a car is "miles per hour" - if you say "miles" it doesn't make sense!) So, it's important to take care about these little things when you're just learning. – Fattie Jun 06 '18 at 20:17
  • @TylerS.Loeper I dunno :P I'm using forces to move the ship forward and sideways, figured I should make it rotate with forces too. How would a real life ship rotate precisely? :P – RealAnyOne Jun 07 '18 at 13:16

1 Answers1

7

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:

enter image description here

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):

enter image description here

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:

enter image description here

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:

enter image description here

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);
meowgoesthedog
  • 14,670
  • 4
  • 27
  • 40
  • _"Applying a torque leads to angular acceleration (radians per second per second)"._ So the `test` is saying the ship will rotate 36 degrees faster every second? So it gets faster and faster over time (because it gave me acceleration and not velocity). 1) Why is the rotation being done by `AddForceAtPosition` way less than 36 degrees per second and 2) not speeding up. And even weirder is that it's double the force as you rightly pointed out. --- Oh ok Unity bodies have a "maximum angular velocity", and I had capped it at 0.1. I changed it to 7 and the ship went bayblade. Now it makes sense – RealAnyOne Jun 07 '18 at 13:48
  • In `AngularvelocityToImpulse` it says _"The Out Parameter must be assigned before control leaves the current method"._ Also it says that it can't convert Vector3 to bool. Why is it `bool AngularvelocityToImpulse` and not `Vector3 AngularvelocityToImpulse` – RealAnyOne Jun 07 '18 at 14:45
  • 1
    Just got back; apologies as I am rusty with C# and wasn't paying attention to what I was writing - now fixed. (That said, you should have been able to spot the source of those errors yourself). – meowgoesthedog Jun 07 '18 at 15:53
  • And yes the cap would explain why it only rotated to 90 degrees; with an acceleration of 36.1 deg / s^2 you would have exceeded the target speed in 0.1 seconds (1% of the test time). – meowgoesthedog Jun 07 '18 at 16:19
  • I'm newbie at coding in general, let alone knowing how to use `ref` and `out` :P. [Here](https://stackoverflow.com/questions/1683394/using-ref-out-keywords-with-passing-by-reference-passing-by-value-in-c-sharp) it says you can't use `ref` and `out` in the same parameter. `out = 0.5...` gives _invalid term expression_ below the **=** sign. I'm guessing u're not testing your code as you write hehe. I removed the `ref`, and made it like so `out Vector3 foo`, and put `foo = 0.5f...` before the `return false;` because it was saying _"The parameter out must be assigned before it leaves the control"_ – RealAnyOne Jun 08 '18 at 16:05
  • Haven't tested the final result but like that it doesn't give any immediate errors :P – RealAnyOne Jun 08 '18 at 16:05
  • @RealAnyOne so is the result correct or still out of whack? – meowgoesthedog Jun 08 '18 at 17:17
  • In your method `AngularvelocityToImpulse (out Vector3 test, Vector3 vel, Vector3 position)` what do I put in Vector3 vel? Force and direction? Something like `shipFront.right * force`? – RealAnyOne Jun 09 '18 at 11:57
  • @RealAnyOne the (change in) angular velocity you want. – meowgoesthedog Jun 09 '18 at 12:21
  • Ok thanks. Maybe you should edit your answer if anyone ever comes by. `AngularvelocityToImpulse(out Vector3 t, Vector3 vel, Vector3 position)`, and the line `t = 0.5f * Vector3.Cross(Q, R) / R.sqrMagnitude;` should come before `if (Mathf.Abs(Vector3.Dot(Q, R)) > 1e-5) return false;` and for desired velocity `var desiredVel = new Vector3 (0, 0.01, 0);` – RealAnyOne Jun 09 '18 at 16:57
  • @RealAnyOne that's incorrect. The value returned by `out` will not be valid if the condition `if (Mathf.Abs(Vector3.Dot(Q, R)) > 1e-5)` is satisfied, since this is the required condition as stated in my answer above. And `var desiredVel = new Vector3 (0, 0.01, 0);` is just your own test value for your own specific case, no need to put it in my answer. – meowgoesthedog Jun 09 '18 at 17:09
  • Ah yes about my specific case. But the `out` after the `if (Math.Abs...` was giving error, saying _"out parameter must be assigned to before it leaves the control"_ so I put it before and it worked fine. And `out = ...` was also giving error, so I put in the parameter `AngularvelocityToImpulse (out Vector3 variable...)` and then `variable = 0.5f * Vector3.Cross(Q, R) / R.sqrMagnitude;` – RealAnyOne Jun 10 '18 at 11:44
  • 1
    @RealAnyOne you know what let's just work around all this pointless debugging and return a `Vector3` instead, see edit. – meowgoesthedog Jun 10 '18 at 12:13
  • Can I use the spaceship's speed to its own right (how fast the ship is moving in the x axis relative to itself, not a global x) to pass it to the function `AngularvelocityToImpulse` in order to stop the ships sideways movement? (by applying that speed divided 2, to the ships back and front). Because I'm trying to do so but ships side movement never reaches 0. – RealAnyOne Jul 15 '18 at 12:52
  • Wait, I'm guessing above is a dumb question. What I want to know is the equation to get acceleration given force at position :P not rotational speed. – RealAnyOne Jul 15 '18 at 12:55
  • Noo, the angular thing worked fine and with precision. This last question is unrelated to this threads question. // The spaceship is going forward at its maximum velocity (this maximum velocity is for better flight control), if I turn the ship 30º to any side for example, I first need to add some "drag" so it slows down and I can move forward in the new direction. I can already do that but it's unprecise, i.e: the spaceship is always moving to one of its sides at 0.0xx speed. I want to add an impulse so it stops moving in its own X axis completely. (**Not rotational**) – RealAnyOne Jul 15 '18 at 13:41
  • @RealAnyOne please post a separate question complete with diagrams for illustration. It looks like you only need to apply the required *linear* impulse to the center of mass, equal to the linear momentum along that direction (component of velocity x mass) – meowgoesthedog Jul 15 '18 at 13:44
  • I think that's it yes. I found this: _force = mass (velocity - initial velocity) / time_, so putting in my numbers, _force = 10.000 (0 - 0.05) / 1_, which gave me force = -500. I think that's about right. Have to test it. I didn't make a new thread because I thought the problem was similar, just needed the new equation to know impulse needed to stop the spaceship. – RealAnyOne Jul 15 '18 at 14:00