围绕另一个向量旋转一个向量

发布于 2024-12-06 19:30:38 字数 57 浏览 0 评论 0原文

我正在为 OpenGL 编写一个 3d 矢量类。如何将向量 v1 绕另一个向量 v2 旋转角度 A?

I am writing a 3d vector class for OpenGL. How do I rotate a vector v1 about another vector v2 by an angle A?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

回首观望 2024-12-13 19:30:38

您可能会发现四元数是一种更优雅、更高效的解决方案。


最近看到这个答案发生冲突后,我想我会提供一个更可靠的答案。无需理解四元数的完整数学含义即可使用的一种。我假设(给定 C++ 标签)您有一个类似 Vector3 类,具有“明显”函数,例如 innercross 、*= 标量运算符等...

#include <cfloat>
#include <cmath>

...

void make_quat (float quat[4], const Vector3 & v2, float angle)
{
    // BTW: there's no reason you can't use 'doubles' for angle, etc.
    // there's not much point in applying a rotation outside of [-PI, +PI];
    // as that covers the practical 2.PI range.

    // any time graphics / floating point overlap, we have to think hard
    // about degenerate cases that can arise quite naturally (think of
    // pathological cancellation errors that are *possible* in seemingly
    // benign operations like inner products - and other running sums).

    Vector3 axis (v2);

    float rl = sqrt(inner(axis, axis));
    if (rl < FLT_EPSILON) // we'll handle this as no rotation:
    {
        quat[0] = 0.0, quat[1] = 0.0, quat[2] = 0.0, quat[3] = 1.0;
        return; // the 'identity' unit quaternion.
    }

    float ca = cos(angle);

    // we know a maths library is never going to yield a value outside
    // of [-1.0, +1.0] right? Well, maybe we're using something else -
    // like an approximating polynomial, or a faster hack that's a little
    // rough 'around the edge' cases? let's *ensure* a clamped range:
    ca = (ca < -1.0f) ? -1.0f : ((ca > +1.0f) ? +1.0f : ca);

    // now we find cos / sin of a half-angle. we can use a faster identity
    // for this, secure in the knowledge that 'sqrt' will be valid....

    float cq = sqrt((1.0f + ca) / 2.0f); // cos(acos(ca) / 2.0);
    float sq = sqrt((1.0f - ca) / 2.0f); // sin(acos(ca) / 2.0);

    axis *= sq / rl; // i.e., scaling each element, and finally:

    quat[0] = axis[0], quat[1] = axis[1], quat[2] = axis[2], quat[3] = cq;
}

因此,在给定原始参数的情况下,float quat[4] 保存一个表示轴和旋转角度的单位四元数(, v2, A)

这是四元数乘法的例程。 SSE/SIMD 或许可以加速这一过程,但复杂的变换和复杂的操作可能会增加速度。在大多数场景中,照明通常由 GPU 驱动。如果您还记得复数乘法有点奇怪,那么四元数乘法就更奇怪了。复数乘法是一种交换运算:a*b = b*a。四元数甚至不保留此属性,即 q*p != p*q

static inline void
qmul (float r[4], const float q[4], const float p[4])
{
    // quaternion multiplication: r = q * p

    float w0 = q[3], w1 = p[3];
    float x0 = q[0], x1 = p[0];
    float y0 = q[1], y1 = p[1];
    float z0 = q[2], z1 = p[2];

    r[3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
    r[0] = w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1;
    r[1] = w0 * y1 + y0 * w1 + z0 * x1 - x0 * z1;
    r[2] = w0 * z1 + z0 * w1 + x0 * y1 - y0 * x1;
}

最后,旋转 3D“向量”v (或者,如果您愿意,也可以将'point' v 问题已命名为 v1,表示为向量),使用四元数:float q[4] 有一点奇怪的公式: v' = q * v * 共轭(q)。四元数具有共轭,类似于复数。这是例程:

static inline void
qrot (float v[3], const float q[4])
{
    // 3D vector rotation: v = q * v * conj(q)

    float r[4], p[4];

    r[0] = + v[0], r[1] = + v[1], r[2] = + v[2], r[3] = +0.0;
    glView__qmul(r, q, r);

    p[0] = - q[0], p[1] = - q[1], p[2] = - q[2], p[3] = q[3];
    glView__qmul(r, r, p);

    v[0] = r[0], v[1] = r[1], v[2] = r[2];
}

将它们放在一起。显然,您可以在适当的情况下使用static关键字。现代优化编译器可能会根据自己的代码生成启发式忽略inline提示。但现在我们只关注正确性:

如何将向量 v1 绕另一个向量 v2 旋转角度 A?

假设某种 Vector3 类,以及以弧度表示的 (A),我们希望四元数表示围绕轴 v2,我们希望将四元数旋转应用于 v1 以获得结果:

float q[4]; // we want to find the unit quaternion for `v2` and `A`...

make_quat(q, v2, A);

// what about `v1`? can we access elements with `operator [] (int)` (?)
// if so, let's assume the memory: `v1[0] .. v1[2]` is contiguous.
// you can figure out how you want to store and manage your Vector3 class.

qrot(& v1[0], q);

// `v1` has been rotated by `(A)` radians about the direction vector `v2` ...

这是人们希望在 Beta 文档站点中看到的扩展内容吗? ?我不太清楚它的要求、预期的严格性等。

You may find quaternions to be a more elegant and efficient solution.


After seeing this answer bumped recently, I though I'd provide a more robust answer. One that can be used without necessarily understanding the full mathematical implications of quaternions. I'm going to assume (given the C++ tag) that you have something like a Vector3 class with 'obvious' functions like inner, cross, and *= scalar operators, etc...

#include <cfloat>
#include <cmath>

...

void make_quat (float quat[4], const Vector3 & v2, float angle)
{
    // BTW: there's no reason you can't use 'doubles' for angle, etc.
    // there's not much point in applying a rotation outside of [-PI, +PI];
    // as that covers the practical 2.PI range.

    // any time graphics / floating point overlap, we have to think hard
    // about degenerate cases that can arise quite naturally (think of
    // pathological cancellation errors that are *possible* in seemingly
    // benign operations like inner products - and other running sums).

    Vector3 axis (v2);

    float rl = sqrt(inner(axis, axis));
    if (rl < FLT_EPSILON) // we'll handle this as no rotation:
    {
        quat[0] = 0.0, quat[1] = 0.0, quat[2] = 0.0, quat[3] = 1.0;
        return; // the 'identity' unit quaternion.
    }

    float ca = cos(angle);

    // we know a maths library is never going to yield a value outside
    // of [-1.0, +1.0] right? Well, maybe we're using something else -
    // like an approximating polynomial, or a faster hack that's a little
    // rough 'around the edge' cases? let's *ensure* a clamped range:
    ca = (ca < -1.0f) ? -1.0f : ((ca > +1.0f) ? +1.0f : ca);

    // now we find cos / sin of a half-angle. we can use a faster identity
    // for this, secure in the knowledge that 'sqrt' will be valid....

    float cq = sqrt((1.0f + ca) / 2.0f); // cos(acos(ca) / 2.0);
    float sq = sqrt((1.0f - ca) / 2.0f); // sin(acos(ca) / 2.0);

    axis *= sq / rl; // i.e., scaling each element, and finally:

    quat[0] = axis[0], quat[1] = axis[1], quat[2] = axis[2], quat[3] = cq;
}

Thus float quat[4] holds a unit quaternion that represents the axis and angle of rotation, given the original arguments (, v2, A).

Here's a routine for quaternion multiplication. SSE/SIMD can probably speed this up, but complicated transform & lighting are typically GPU-driven in most scenarios. If you remember complex number multiplication as a little weird, quaternion multiplication is more so. Complex number multiplication is a commutative operation: a*b = b*a. Quaternions don't even preserve this property, i.e., q*p != p*q :

static inline void
qmul (float r[4], const float q[4], const float p[4])
{
    // quaternion multiplication: r = q * p

    float w0 = q[3], w1 = p[3];
    float x0 = q[0], x1 = p[0];
    float y0 = q[1], y1 = p[1];
    float z0 = q[2], z1 = p[2];

    r[3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
    r[0] = w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1;
    r[1] = w0 * y1 + y0 * w1 + z0 * x1 - x0 * z1;
    r[2] = w0 * z1 + z0 * w1 + x0 * y1 - y0 * x1;
}

Finally, rotating a 3D 'vector' v (or if you prefer, the 'point' v that the question has named v1, represented as a vector), using the quaternion: float q[4] has a somewhat strange formula: v' = q * v * conjugate(q). Quaternions have conjugates, similar to complex numbers. Here's the routine:

static inline void
qrot (float v[3], const float q[4])
{
    // 3D vector rotation: v = q * v * conj(q)

    float r[4], p[4];

    r[0] = + v[0], r[1] = + v[1], r[2] = + v[2], r[3] = +0.0;
    glView__qmul(r, q, r);

    p[0] = - q[0], p[1] = - q[1], p[2] = - q[2], p[3] = q[3];
    glView__qmul(r, r, p);

    v[0] = r[0], v[1] = r[1], v[2] = r[2];
}

Putting it all together. Obviously you can make use of the static keyword where appropriate. Modern optimising compilers may ignore the inline hint depending on their own code generation heuristics. But let's just concentrate on correctness for now:

How do I rotate a vector v1 about another vector v2 by an angle A?

Assuming some sort of Vector3 class, and (A) in radians, we want the quaternion representing the rotation by the angle (A) about the axis v2, and we want to apply that quaternion rotation to v1 for the result:

float q[4]; // we want to find the unit quaternion for `v2` and `A`...

make_quat(q, v2, A);

// what about `v1`? can we access elements with `operator [] (int)` (?)
// if so, let's assume the memory: `v1[0] .. v1[2]` is contiguous.
// you can figure out how you want to store and manage your Vector3 class.

qrot(& v1[0], q);

// `v1` has been rotated by `(A)` radians about the direction vector `v2` ...

Is this the sort of thing that folks would like to see expanded upon in the Beta Documentation site? I'm not altogether clear on its requirements, expected rigour, etc.

┾廆蒐ゝ 2024-12-13 19:30:38

这可能有用:

double c = cos(A);
double s = sin(A);
double C = 1.0 - c;

double Q[3][3];
Q[0][0] = v2[0] * v2[0] * C + c;
Q[0][1] = v2[1] * v2[0] * C + v2[2] * s;
Q[0][2] = v2[2] * v2[0] * C - v2[1] * s;

Q[1][0] = v2[1] * v2[0] * C - v2[2] * s;
Q[1][1] = v2[1] * v2[1] * C + c;
Q[1][2] = v2[2] * v2[1] * C + v2[0] * s;

Q[2][0] = v2[0] * v2[2] * C + v2[1] * s;
Q[2][1] = v2[2] * v2[1] * C - v2[0] * s;
Q[2][2] = v2[2] * v2[2] * C + c;

v1[0] = v1[0] * Q[0][0] + v1[0] * Q[0][1] + v1[0] * Q[0][2];
v1[1] = v1[1] * Q[1][0] + v1[1] * Q[1][1] + v1[1] * Q[1][2];
v1[2] = v1[2] * Q[2][0] + v1[2] * Q[2][1] + v1[2] * Q[2][2];

This may prove useful:

double c = cos(A);
double s = sin(A);
double C = 1.0 - c;

double Q[3][3];
Q[0][0] = v2[0] * v2[0] * C + c;
Q[0][1] = v2[1] * v2[0] * C + v2[2] * s;
Q[0][2] = v2[2] * v2[0] * C - v2[1] * s;

Q[1][0] = v2[1] * v2[0] * C - v2[2] * s;
Q[1][1] = v2[1] * v2[1] * C + c;
Q[1][2] = v2[2] * v2[1] * C + v2[0] * s;

Q[2][0] = v2[0] * v2[2] * C + v2[1] * s;
Q[2][1] = v2[2] * v2[1] * C - v2[0] * s;
Q[2][2] = v2[2] * v2[2] * C + c;

v1[0] = v1[0] * Q[0][0] + v1[0] * Q[0][1] + v1[0] * Q[0][2];
v1[1] = v1[1] * Q[1][0] + v1[1] * Q[1][1] + v1[1] * Q[1][2];
v1[2] = v1[2] * Q[2][0] + v1[2] * Q[2][1] + v1[2] * Q[2][2];
泛泛之交 2024-12-13 19:30:38

最容易理解的方法是旋转坐标轴,使向量 v2 与 Z 轴对齐,然后绕 Z 轴旋转 A,然后再旋转回来,使 Z 轴与 v2 对齐。

当您写下这三个运算的旋转矩阵时,您可能会注意到您依次应用了三个矩阵。为了达到相同的效果,您可以将三个矩阵相乘。

The easiest-to-understand way would be rotating the coordinate axis so that vector v2 aligns with the Z axis, then rotate by A around the Z axis, and rotate back so that the Z axis aligns with v2.

When you have written down the rotation matrices for the three operations, you'll probably notice that you apply three matrices after each other. To reach the same effect, you can multiply the three matrices.

兔小萌 2024-12-13 19:30:38

我在这里找到了这个:
http://steve.hollasch.net/cgindex/math/rotvec.html

let
    [v] = [vx, vy, vz]      the vector to be rotated.
    [l] = [lx, ly, lz]      the vector about rotation
          | 1  0  0|
    [i] = | 0  1  0|           the identity matrix        
          | 0  0  1|

          |   0  lz -ly |
    [L] = | -lz   0  lx |
          |  ly -lx   0 |

    d = sqrt(lx*lx + ly*ly + lz*lz)
    a                       the angle of rotation

then

矩阵运算给出:

[v] = [v]x{[i] + sin(a)/d*[L] + ((1 - cos(a))/(d*d)*([L]x[L]))} 

我编写了自己的 Matrix3 类和 Vector3Library 来实现此向量旋转。它工作得绝对完美。我用它来避免在相机视野之外绘制模型。

我想这就是“使用 3d 旋转矩阵”方法。我快速浏览了四元数,但从未使用过它们,所以坚持使用我可以理解的东西。

I found this here:
http://steve.hollasch.net/cgindex/math/rotvec.html

let
    [v] = [vx, vy, vz]      the vector to be rotated.
    [l] = [lx, ly, lz]      the vector about rotation
          | 1  0  0|
    [i] = | 0  1  0|           the identity matrix        
          | 0  0  1|

          |   0  lz -ly |
    [L] = | -lz   0  lx |
          |  ly -lx   0 |

    d = sqrt(lx*lx + ly*ly + lz*lz)
    a                       the angle of rotation

then

matrix operations gives:

[v] = [v]x{[i] + sin(a)/d*[L] + ((1 - cos(a))/(d*d)*([L]x[L]))} 

I wrote my own Matrix3 class and Vector3Library that implemented this vector rotation. It works absolutely perfectly. I use it to avoid drawing models outside the field of view of the camera.

I suppose this is the "use a 3d rotation matrix" approach. I took a quick look at quaternions, but have never used them, so stuck to something I could wrap my head around.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文