0
\$\begingroup\$

I have this code for my linear acceleration and deceleration.

var currentSpeed = velocity.translation.z;
var decelerationDistance = ((currentSpeed * currentSpeed) / movementSettings.acceleration) * 0.5f;
if (distanceFromTarget > decelerationDistance)
{
    velocity.translation = float3(0f, 0f, min(currentSpeed + (movementSettings.acceleration * deltaTime), movementSettings.maxSpeed));

}
else
{
    velocity.translation = float3(0f, 0f, max(currentSpeed - (movementSettings.acceleration * deltaTime), 0f));
}

Assuming the object is already pointed at the target, this accelerates an object up to its maximum speed at its acceleration rate, and then slows it down by the acceleration rate so that it comes to a stop exactly on the target.

I'd like to have the same behaviour apply to rotation (which is a quaternion). i.e. Assuming the object is stationary, smoothly rotate to a target rotation, obeying acceleration and max rotation speed limits.

I have made a start of converting this code to work on quaternions:

private static float Magnitude(Unity.Mathematics.quaternion quat)
{
    return CalculateQuaternionDifference(Unity.Mathematics.quaternion.identity, quat);
}

private static float CalculateQuaternionDifference(Unity.Mathematics.quaternion a, Unity.Mathematics.quaternion b)
{
    float dotProduct = dot(a, b);
    return dotProduct > 0.99999f ? 0f : acos(min(abs(dotProduct), 1f)) * 2f;
}

...

var currentRotSpeed = Magnitude(velocity.rotation);
var rotDecelerationDistance = ((currentRotSpeed * currentRotSpeed) / movementSettings.acceleration) * 0.5f;
if (distance > rotDecelerationDistance)
{
    velocity.rotation = slerp(Unity.Mathematics.quaternion.identity, mul(rotationDiff, velocity.rotation), movementSettings.acceleration);
}
else
{
    velocity.rotation = slerp(Unity.Mathematics.quaternion.identity, mul(inverse(rotationDiff), velocity.rotation), movementSettings.acceleration);
}

My questions are:

  1. Unity.Mathematics.math.slerp() doesn't seem to play well with values of t over 1. When acceleration and deltaTime are low it works, but if they increase too much it breaks in ways I don't understand. How can I multiply a Quaternion by a scalar the same way I multiply the linear acceleration by deltaTime.
  2. This doesn't follow any max rotational speed limits or stop at 0. What is the equivalent of min/max() for a quaternion?
  3. rotationDiff is the rotational difference between the current orientation and the target orientation. As the current orientation gets closer to the target orientation, this distance gets smaller. I know I should be using some static value instead, but I'm not sure what. Is there a way to normalize rotationDiff so it always has a magnitude of 1 radian?
\$\endgroup\$
1
\$\begingroup\$

For an angular velocity, I think you'll have an easier time expressing it in angle-axis form:

Vector3 rotationAxis;
float rotationSpeed;

You're still storing just 4 floats, so it's the same weight as a quaternion, just easier to reason about. I'll assume you've chosen your axis so that rotationSpeed is non-negative, and that we're working in units of degrees, since that's what Unity's quaternion methods expose.

Then your translation speed code converts quite simply to angular speed:

var currentSpeed = velocity.rotationSpeed;
var decelerationDistance = ((currentSpeed * currentSpeed) / movementSettings.acceleration) * 0.5f;

float distanceFromTarget = Quaternion.Angle(currentRotation, targetRotation);

if (distanceFromTarget > decelerationDistance)
{
    velocity.rotationSpeed = min(currentSpeed + (movementSettings.acceleration * deltaTime), movementSettings.maxSpeed);    
}
else
{
    velocity.rotationSpeed = max(currentSpeed - (movementSettings.acceleration * deltaTime), 0f);
}

You can apply this angular velocity like so:

Quaternion travel = Quaternion.AngleAxis(velocity.rotationSpeed * deltaTime, velocity.rotationAxis);

Quaternion newRotation = travel * currentRotation;
\$\endgroup\$
2
  • \$\begingroup\$ This is the more or less solution I went with. You optimize it further by making the magnitude of the rotationAxis vector the rotationSpeed. \$\endgroup\$ – Omegastick Mar 10 at 8:21
  • \$\begingroup\$ I wouldn't call that an optimization. Ostensibly it saves you a float, but due to alignment you probably don't get that memory back. And it means more expensive operations are used to read-out the speed or update it. \$\endgroup\$ – DMGregory Mar 10 at 12:25
0
\$\begingroup\$
  1. Multiplying a quaternion with a scalar doesn't do anything. To get the double quaternion you will need to multiply it with itself. (then you can slerp with adjusted t to get a quaternion at other locations).

  2. the w component follows the cosine of the half angle of the rotation. This means that going from the identity rotation of 0 degrees to full 360 degree rotation the w component changes from 1 to -1 monotonically. So for 2 quaternions rotating along the same axis you can use min on w to select the greater rotation.

  3. you can extract the axis out of the quaternion and then pass back into the angle/axis constructor with fixed angle.

    Vector3 axis = Vector3(rotationDiff.x, rotationDiff.y, rotationDiff.z);
    rotationDiff = Quaternion.AngleAxis(1/PI*180, axis);
    
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.