围绕枢轴点旋转刚体

Posted

技术标签:

【中文标题】围绕枢轴点旋转刚体【英文标题】:Rotating a RigidBody around a pivot point 【发布时间】:2015-09-09 22:26:00 【问题描述】:

我正在尝试围绕枢轴点(在本例中为原点)而不是其质心旋转刚体。

我有一个建议应用三个转换:

    将刚体转换为原点

    围绕质心旋转刚体

    从原点变换刚体。

这是我的代码:

btMatrix3x3 orn = btPhys->getWorldTransform().getBasis();   
btQuaternion quat;
orn.getRotation(quat);
btVector3 axis = quat.getAxis();

//Move rigidbody 2 units along its axis to the origin
btPhys->translate(btVector3(-2.0 * axis.getX(), 0.0, -2.0 * axis.getZ()));

//Rotate the rigidbody by 1 degree on its center of mass
orn *= btMatrix3x3(btQuaternion( btVector3(1, 0, 0), btScalar(degreesToRads(-1))));
btPhys->getWorldTransform().setBasis(orn);  

//Update axis variable to apply transform on
orn.getRotation(quat);
axis = quat.getAxis();

//Move the rigidbody 2 units along new axis
btPhys->translate(btVector3(2.0 * axis.getX(), 0.0, 2.0 * axis.getZ())); 

但是,枢轴点似乎在四处移动,而不是停留在一个地方(原点)。有没有更好的方法(实际上可行)围​​绕枢轴点旋转刚体?

编辑: 我为旋转功能添加了一些完整性检查代码:

//Code that doesn't work
btVector3 invTrans = btPhys->offsetToPivot.rotate(btVector3(1.0, 0.0, 0.0), btScalar(degreesToRads(-1)));
//Values printed out are identical to offsetToPivot
printf("invTrans: %f %f %f\n", invTrans.getX(), invTrans.getY(), invTrans.getZ());

//Sanity code that DOES work
//Arbitrary vector
btVector3 temp = btVector3(0.0, 2.0, 0.0);
temp = temp.rotate(btVector3(1.0, 0.0, 0.0), btScalar(degreesToRads(-1)));
printf("temp %f %f %f\n", temp.getX(), temp.getY(), temp.getZ());

【问题讨论】:

您应该将枢轴点转换为原点,然后旋转,然后应用反向转换。 我已经编辑了我的答案。 对我来说,假设 offsetToPivot 最初设置为 btVector3(0.0, 2.0, 0.0),这两个代码片段的工作方式完全相同。如果它被设置为btVector3(-2.0, 0.0, 0.0); 它显然不会工作,因为你试图围绕它所在的轴旋转点,这当然会把它留在同一个地方。 【参考方案1】:

这个方法确实有效,只是你应用不正确。您的第二次平移是沿世界轴执行的,但您已经旋转了对象,因此您必须将其沿旋转的矢量平移回来。

正确的代码应该差不多是这样的:

btMatrix3x3 orn = btPhys->getWorldTransform().getBasis();   
btQuaternion quat;
orn.getRotation(quat);
btVector3 axis = quat.getAxis();

//Move rigidbody 2 units along its axis to the origin
btPhys->translate(btVector3(-2.0 * axis.getX(), 0.0, -2.0 * axis.getZ()));

//Rotate the rigidbody by 1 degree on its center of mass
orn *= btMatrix3x3(btQuaternion( btVector3(1, 0, 0), btScalar(degreesToRads(-1))));
btPhys->getWorldTransform().setBasis(orn);  

//Get rotation matrix
btTransform invRot(btQuaternion(btVector3(1, 0, 0), btScalar(degreesToRads(-1))),btVector3(0,0,0));
//Rotate your first translation vector with the matrix
btVector3 invTrans(-2.0 * axis.getX(), 0.0, -2.0 * axis.getZ());
invTrans = invRot * invTrans;

//Update axis variable to apply transform on
orn.getRotation(quat);
axis = quat.getAxis();

//Translate back by rotated vector
btPhys->translate(-invTrans); 

我不确定旋转是否不应该带有减号(我现在无法检查),但您可以轻松地尝试两者。

编辑。

好的,所以您忘了提到您执行的是连续旋转而不是单个旋转。此过程适用于围绕枢轴的单次旋转(例如 30 度旋转)。我再次查看了您的代码,我了解到您尝试沿本地 x 轴和 z 轴执行第一次平移。然而,事实并非如此。在这一行:

btVector3 axis = quat.getAxis();

变量轴是一个单位向量,表示您的对象围绕其旋转的轴。它不是它的坐标系。我以前没有注意到这部分。四元数很棘手,您应该阅读更多关于它们的信息,因为很多人都误用了它们。

在连续情况下可行的解决方案是将最后一次平移(从质心到枢轴 - 在我的示例中由 invTrans 表示)存储在您的对象中,并使用它来执行第一次平移,然后旋转它以同样的方式完成,并使用它移动到正确的位置。

更正后的代码如下所示:

btMatrix3x3 orn = btPhys->getWorldTransform().getBasis();   
btQuaternion quat;
orn.getRotation(quat);

//Move rigidbody 2 units along its axis to the origin
btPhys->translate(btPhys->offsetToPivot);

//Rotate the rigidbody by 1 degree on its center of mass
orn *= btMatrix3x3(btQuaternion( btVector3(1, 0, 0), btScalar(degreesToRads(-1))));
btPhys->getWorldTransform().setBasis(orn);  

//Get rotation matrix
btTransform invRot(btQuaternion(btVector3(1, 0, 0), btScalar(degreesToRads(-1))),btVector3(0,0,0));
//Rotate your first translation vector with the matrix
btVector3 invTrans = invRot * btPhys->offsetToPivot;

//Update axis variable to apply transform on
orn.getRotation(quat);
axis = quat.getAxis();

//Translate back by rotated vector
btPhys->translate(-invTrans); 
btPhys->offsetToPivot = invTrans;

但是,在开始整个过程​​之前,您必须将 offsetToPivot 设置为相对于质心的位置。

我的印象是,您的问题的主要根源是缺乏对线性代数和基本空间变换的理解。如果您打算继续从事该领域的工作,我强烈建议您阅读该主题。把你的问题写在纸上也很有帮助。

EDIT2.

好的,我已经试过你的代码了:

btVector3 temp = vec3(0,2,0);
btTransform invRot(btQuaternion(btVector3(1, 0, 0), btScalar(-0.017453f)),btVector3(0,0,0));
temp = invRot * temp;

在此之后,temp 等于 0.000000000, 1.99969542, -0.0349042267

【讨论】:

感谢您的回复,但我不完全确定您的意思。 invRot 不应该与应用于刚体的旋转相同(现在有一个 btVector3)?我尝试使用此代码,但它导致刚体在上下移动时在其质心上旋转。刚体的高度是否必须为 0 才能正常工作? 感谢您的更新。我了解您现在在做什么,但我仍然认为代码不太正确。我打印出 invTrans ,它似乎和 bt->offsetToPivot; 一样所以由于某种原因 invRot 没有得到正确应用。 @Jaitnium 你知道......我无法在任何地方运行此代码,所以它是从我的脑海中写出来的,我无法检查它。如果您了解该方法,您至少可以尝试自己修复它。你在哪一行打印这些值?你看到btPhys->offsetToPivot = invTrans;这行了吗?在这条线之后,它们应该是相等的。 RigidBody 的行为是什么?您为offsetToPivot 分配了什么初始值?当您开始旋转时,最初的 WorldTransform 是什么?您是否按照我的建议尝试过使用调试器? bt->offsetToPivot 与 invTrans 后的行相同:“btVector3 invTrans = invRot * btPhys->offsetToPivot;”行为是刚体仍在其质心上旋转。 @Jaitnium 那么btPhys->offsetToPivot的初始值是多少?你甚至把它放在任何地方吗?和职位?如果您不提供关键信息,您如何想象有人可以帮助您?实际上我已经检查了这一行,它肯定将invTrans 设置为offsetToPivot 以外的其他值。还有一个问题......你的函数degreeToRads返回什么?【参考方案2】:

在以下函数中,这些转换执行您描述的三个步骤:

int x = cos(angRads) * (initial.x - axisOfRotation.x) - sin(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.x;      
int y = sin(angRads) * (initial.x - axisOfRotation.x) + cos(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.y;

即:

第一步:将刚体转换为原点。

    initial.x - axisOfRotation.x
    initial.y - axisOfRotation.y

第 2 步:围绕其质心旋转刚体。

    cos(angRads) * initial.x - sin(angRads) * initial.y
    sin(angRads) * initial.x + cos(angRads) * initial.y 

第 3 步:将刚体从原点转换。

    +axisOfRotation.x;
    +axisOfRotation.y;

这是一个递归函数,它完全执行您需要的操作并返回 向量中所有连续旋转的点:(以它为基准)

rotateCoordinate(vector<Point>& rotated, Point& axisOfRotation, Point initial, 
                            float angRads, int numberOfRotations)
    // base case: when all rotations performed return vector holding the rotated points
    if(numberOfRotations <= 0) return;
    else
        // apply transformation on the initial point
        int x = cos(angRads) * (initial.x - axisOfRotation.x) - sin(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.x;
        int y = sin(angRads) * (initial.x - axisOfRotation.x) + cos(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.y;
        // save the result
        rotated.push_back(Point(x, y));
        // call the same function this time on the rotated point and decremented number of rotations
        rotateCoordinate(rotated, axisOfRotation, Point(x,y), angRads, numberOfRotations -1);
    

Point 在哪里:

struct Point 
    int x, y;
    Point(int xx, int yy) : x(xx), y(yy)  
    Point() :x(0), y(0)  
;

进一步阅读解释其背后的数学原理here。

【讨论】:

那么使用 Bullet 有什么意义呢?顺便提一句。您的转换仅适用于在 2D 平面中旋转 Point,而问题是围绕 3D 轴旋转 Rigid Body。它比这复杂一点。即使你将它缩放到 3D 空间(这很痛苦),你仍然只计算身体的位置,但是方向在哪里? @Estiny 你是对的。我会更新答案以符合问题要求,抱歉!

以上是关于围绕枢轴点旋转刚体的主要内容,如果未能解决你的问题,请参考以下文章

描述三维刚体旋转

视觉SLAM三维空间刚体运动的描述

视觉SLAM三维空间刚体运动的描述

关于Unity中关节的使用

使用 OpenGL 围绕枢轴点旋转

Unity 对象不围绕枢轴点旋转