如何缩放四元数的旋转

Posted

技术标签:

【中文标题】如何缩放四元数的旋转【英文标题】:How to scale the rotation of a quaternion 【发布时间】:2015-10-17 02:58:39 【问题描述】:

我正在尝试将速度乘以帧之间的时间。我想为四元数做这件事是通过将它们提升到一个幂来完成的。我有代码可以根据鼠标移动来旋转对象。它有一个以一个帧速率运行的主循环和一个以固定帧速率运行的物理循环。这是主循环的相关部分:

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

这是物理循环的相关部分:

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();

这很好用,但是当我尝试使用 glm::pow 将角速度提高到幂时,对象会随机旋转并且不会跟随我的鼠标。我意识到我可以用这样的一行代码来做到这一点

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

但这对于这项任务来说似乎是不必要的复杂。是什么导致了这个问题,我该如何解决?

【问题讨论】:

四元数没有唯一的平方根,也没有旋转的唯一平方根。一个 10 度的旋转可以是两个 5 度的旋转,或者两个 185 度的旋转。两者在组合时都会产生 10 度的旋转。一般来说,k 度旋转的第 n 个根是 x 从 0 到 n-1 的集合 x 360/n + k/n。我不知道你的图书馆是做什么的,我只是指出数学是模棱两可的。 【参考方案1】:

不确定如何使用您正在使用的 API 来执行此操作,但基本上,您会使用 Quaternion::Slerp()。 Slerp的意思是“球面线性插值”。

这样的东西(伪代码)应该可以工作:

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

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

编辑: 我应该注意,这不是我解决这个问题的方式。我只是将围绕 X 和 Y 轴的旋转存储为标量,并在每一帧中从它们构造一个新的四元数。

请原谅草率的伪代码:

// 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);

【讨论】:

我很抱歉没有说明我使用的是什么 API。时间是我实现的一个类,变换也是如此。我正在使用 GLM 数学库,这对我来说很重要。我想避免 x 和 y 旋转,因为 Euler Angles 因为这应该适用于所有物理对象。 所以使用第一个解决方案 该解决方案在一定程度上有效,但是当我超过某个角速度时,它也会变得不稳定 这实际上并没有解决问题,而是试图规避它。原始问题末尾的“不必要的复杂”解决方案是正确的解决方案。【参考方案2】:

原来角速度通常表示为一个 3d 矢量,其中方向是轴,大小是角速度。替换这行代码:

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

用这个:

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

物理系统按预期工作。 if 检查确保角速度不为 0。

【讨论】:

将角轴值视为欧拉角是不正确的,即使它们有时会产生相似的四元数。原始问题末尾的“不必要的复杂”解决方案是正确的解决方案。

以上是关于如何缩放四元数的旋转的主要内容,如果未能解决你的问题,请参考以下文章

unityvector3和四元数的区别

如何在Unity中获得2个四元数的相对旋转

如何在不使用 transform.Rotate 的情况下在其本地或世界轴上旋转带有第二个四元数的四元数?

四元数与轴+角度

四元数运动学笔记旋转的雅克比矩阵

四元数法