msik314
msik314

Reputation: 51

How to scale the rotation of a quaternion

I am trying to do the equivalent of multiplying the velocity by the time between frames. I would imagine that doing this for quaternions would be done by raising them to a power. I have code to rotate an object based on my mouse movements. It has a main loop running at one frame rate and a physics loop running at a fixed frame rate. Here is the relevant part of the main loop:

glfwPollEvents();
Input::update();
window.clear(0,0,0,1);

rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().x, glm::vec3(0,1,0));
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().y, glm::vec3(1,0,0));

if(Input::getKey(Input::KEY_A))
{
    rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_D))
{
    rigidBody.velocity += float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_W))
{
    rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_S))
{
    rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_LCONTROL))
{
    rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,1,0);
}
if(Input::getKey(Input::KEY_LSHIFT))
{
    rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,1,0);
}

Here is the relevant part of the physics loop:

for(int i = 0; i < *numRigidBodies; i++)
{
    rigidBodies[i].transform->getPos() += rigidBodies[i].velocity;
    rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
}
rigidBodies[0].angularVelocity = glm::quat();
rigidBodies[0].velocity = glm::vec3();

This works fine, but when I try raising angular velocity to a power with glm::pow, the object rotates randomly and does not follow my mouse. I realize I could do this with a line of code like

rigidBodies[i].transform->getRot() *= glm::angleAxis((float)Time::getFixedDelta() * glm::angle(rigidBodies[i].angularVelocity), glm::axis(rigidBodies[i].angularVelocity));

but this seems needlessly complicated for the task. What is causing this issue, and how can I fix it?

Upvotes: 4

Views: 9456

Answers (2)

msik314
msik314

Reputation: 51

Turns out angular velocity is usually represented as a 3d vector where the direction is the axis and the magnitude is the angular speed. Replace this line of code:

rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;

with this:

if(rigidBodies[i].angularVelocity != glm::vec3())
    rigidBodies[i].transform->getRot() *= glm::quat(rigidBodies[i].angularVelocity * float(Time::getFixedDelta()));

and the physics system works as expected. The if check makes sure that angular speed is not 0.

Upvotes: 1

CuriousGeorge
CuriousGeorge

Reputation: 7400

Not sure exactly how to do it with the API you're using, but basically, you would use Quaternion::Slerp(). Slerp means "spherical linear interpolation".

Something like this(pseudocode) should work:

auto& rot = rigidBodies[i].transform->getRot();

auto goal = rigidBodies[i].angularVelocity * rot;
rot = rot.slerp(rot, goal, Time::deltaTime);


Edit:
I should note that this is not how I would approach this problem. I would just store the rotation around the X and Y axis as scalars and construct a new quaternion from them each frame.

Please excuse the sloppy pseudo code:

// previous x and y positions, could probably be set in MouseDown event
float lastX = ...; 
float lastY = ...;

float xRotation = 0;
float yRotation = 0;

float rotationSpeed = 1.0;

void OnMouseMove(float x, float y) {
    float dx = x - lastX;
    float dy = y - lastY;
    lastX = x;
    lastY = y;

    xRotation += dy * rotationSpeed * Time::deltaTime;
    yRotation += dx * rotationSpeed * Time::deltaTime;

    rigidBodies[i].transform->getRot() = eulerQuat(xRotation, yRotation, 0);
}

Upvotes: 1

Related Questions