Programing

한 벡터에서 다른 벡터로의 회전을 나타내는 쿼터니언 찾기

lottogame 2020. 8. 20. 19:23
반응형

한 벡터에서 다른 벡터로의 회전을 나타내는 쿼터니언 찾기


두 개의 벡터 u와 v가 있습니다. u에서 v 로의 회전을 나타내는 쿼터니언을 찾는 방법이 있습니까?


Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

q를 정규화하는 것을 잊지 마십시오.

Richard는 고유 한 회전이 없다는 점에 대해 옳지 만 위의 내용은 "가장 짧은 원호"를 제공해야합니다. 이는 아마도 필요한 것입니다.


반 방향 벡터 솔루션

나는 임 브론디 르가 제시하려한다고 생각하는 해결책을 생각해 냈습니다. (사소한 실수가 있었지만, 아마도 불길한 다람쥐가 그것을 확인하는 데 어려움을 겪었을 것입니다).

다음과 같이 축을 중심으로 한 회전을 나타내는 쿼터니언을 구성 할 수 있다고 가정합니다.

q.w == cos(angle / 2)
q.x == sin(angle / 2) * axis.x
q.y == sin(angle / 2) * axis.y
q.z == sin(angle / 2) * axis.z

그리고 두 정규화 된 벡터의 내적과 외적은 다음과 같습니다.

dot     == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z

에서 회전으로 보는 UV 것은 우리가 직접 도트 및 벡터 곱의 결과와 같은 회전을 나타내는 쿼터니언을 생성 할 수있는 것처럼 수직 벡터 주위 세타 (벡터 사이의 각도)로 회전시킴으로써 달성 될 수 있고, 그것은 본다 ; 그러나 그것이 의미하는 바는 theta = angle / 2 입니다. 즉, 그렇게하면 원하는 회전의 두 배가됩니다.

한가지 해결책 사이 벡터 반쯤를 계산하는 UV 와 도트의 외적 사용 U반 방향 의 회전 나타내는 쿼터니언 구성하는 벡터를 두 번 사이의 각도 U반 방향 벡터를, 그것은 우리를 v 까지 데려다줍니다 !

u == -v 이고 고유 한 중간 벡터를 계산할 수 없게 되는 특별한 경우 가 있습니다. 이것은 우리를 u 에서 v로 데려 갈 수있는 무한히 많은 "가장 짧은 호"회전을 감안할 때 예상되며, 우리의 특별한 경우 솔루션으로 u (또는 v )에 직교하는 벡터 주위로 180도 회전해야합니다 . 이것은 표준화의 외적 취함으로써 이루어진다 U를 다른 벡터로 하지 평행 U .

의사 코드는 다음과 같습니다 (실제로 특별한 경우는 부동 소수점 부정확성을 설명해야합니다. 아마도 절대 값이 아닌 임계 값에 대해 내적을 확인하여).

또한 u == v 일 때 특별한 경우 없다는 점에 유의하십시오 (아이덴티티 쿼터니언이 생성됨-직접 확인하고 확인하십시오).

// N.B. the arguments are _not_ axis and angle, but rather the
// raw scalar-vector components.
Quaternion(float w, Vector3 xyz);

Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
  // It is important that the inputs are of equal length when
  // calculating the half-way vector.
  u = normalized(u);
  v = normalized(v);

  // Unfortunately, we have to check for when u == -v, as u + v
  // in this case will be (0, 0, 0), which cannot be normalized.
  if (u == -v)
  {
    // 180 degree rotation around any orthogonal vector
    return Quaternion(0, normalized(orthogonal(u)));
  }

  Vector3 half = normalized(u + v);
  return Quaternion(dot(u, half), cross(u, half));
}

orthogonal함수는 주어진 벡터에 직교하는 벡터를 반환합니다. 이 구현에서는 가장 직교하는 기저 벡터가있는 외적을 사용합니다.

Vector3 orthogonal(Vector3 v)
{
    float x = abs(v.x);
    float y = abs(v.y);
    float z = abs(v.z);

    Vector3 other = x < y ? (x < z ? X_AXIS : Z_AXIS) : (y < z ? Y_AXIS : Z_AXIS);
    return cross(v, other);
}

Half-Way Quaternion 솔루션

이것은 실제로 받아 들여진 답변에 제시된 솔루션이며 중간 벡터 솔루션보다 약간 더 빠른 것 같습니다 (내 측정에 의해 ~ 20 % 더 빠르지 만 내 말을 받아들이지는 않습니다). 저와 같은 다른 사람들이 설명에 관심이있을 경우를 대비하여 여기에 추가합니다.

Essentially, instead of calculating a quaternion using a half-way vector, you can calculate the quaternion which results in twice the required rotation (as detailed in the other solution), and find the quaternion half-way between that and zero degrees.

As I explained before, the quaternion for double the required rotation is:

q.w   == dot(u, v)
q.xyz == cross(u, v)

And the quaternion for zero rotation is:

q.w   == 1
q.xyz == (0, 0, 0)

Calculating the half-way quaternion is simply a matter of summing the quaternions and normalizing the result, just like with vectors. However, as is also the case with vectors, the quaternions must have the same magnitude, otherwise the result will be skewed towards the quaternion with the larger magnitude.

A quaternion constructed from the dot and cross product of two vectors will have the same magnitude as those products: length(u) * length(v). Rather than dividing all four components by this factor, we can instead scale up the identity quaternion. And if you were wondering why the accepted answer seemingly complicates matters by using sqrt(length(u) ^ 2 * length(v) ^ 2), it's because the squared length of a vector is quicker to calculate than the length, so we can save one sqrt calculation. The result is:

q.w   = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)

And then normalize the result. Pseudo code follows:

Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
  float k_cos_theta = dot(u, v);
  float k = sqrt(length_2(u) * length_2(v));

  if (k_cos_theta / k == -1)
  {
    // 180 degree rotation around any orthogonal vector
    return Quaternion(0, normalized(orthogonal(u)));
  }

  return normalized(Quaternion(k_cos_theta + k, cross(u, v)));
}

The problem as stated is not well-defined: there is not a unique rotation for a given pair of vectors. Consider the case, for example, where u = <1, 0, 0> and v = <0, 1, 0>. One rotation from u to v would be a pi / 2 rotation around the z-axis. Another rotation from u to v would be a pi rotation around the vector <1, 1, 0>.


Why not represent the vector using pure quaternions? It's better if you normalize them first perhaps.
q1 = (0 ux uy uz)'
q2 = (0 vx vy vz)'
q1 qrot = q2
Pre-multiply with q1-1
qrot = q1-1 q2
where q1-1 = q1conj / qnorm
This is can be thought of as "left division". Right division, which is not what you want is:
qrot,right = q2-1 q1


Some of the answers don't seem to consider possibility that cross product could be 0. Below snippet uses angle-axis representation:

//v1, v2 are assumed to be normalized
Vector3 axis = v1.cross(v2);
if (axis == Vector3::Zero())
    axis = up();
else
    axis = axis.normalized();

return toQuaternion(axis, ang);

The toQuaternion can be implemented as follows:

static Quaternion toQuaternion(const Vector3& axis, float angle)
{
    auto s = std::sin(angle / 2);
    auto u = axis.normalized();
    return Quaternion(std::cos(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}

If you are using Eigen library, you can also just do:

Quaternion::FromTwoVectors(from, to)

I'm not much good on Quaternion. However I struggled for hours on this, and could not make Polaris878 solution work. I've tried pre-normalizing v1 and v2. Normalizing q. Normalizing q.xyz. Yet still I don't get it. The result still didn't give me the right result.

In the end though I found a solution that did. If it helps anyone else, here's my working (python) code:

def diffVectors(v1, v2):
    """ Get rotation Quaternion between 2 vectors """
    v1.normalize(), v2.normalize()
    v = v1+v2
    v.normalize()
    angle = v.dot(v2)
    axis = v.cross(v2)
    return Quaternion( angle, *axis )

A special case must be made if v1 and v2 are paralell like v1 == v2 or v1 == -v2 (with some tolerance), where I believe the solutions should be Quaternion(1, 0,0,0) (no rotation) or Quaternion(0, *v1) (180 degree rotation)


From algorithm point of view , the fastest solution looks in pseudocode

 Quaternion shortest_arc(const vector3& v1, const vector3& v2 ) 
 {
     // input vectors NOT unit
     Quaternion q( cross(v1, v2), dot(v1, v2) );
     // reducing to half angle
     q.w += q.magnitude(); // 4 multiplication instead of 6 and more numerical stable

     // handling close to 180 degree case
     //... code skipped 

        return q.normalized(); // normalize if you need UNIT quaternion
 }

Be sure that you need unit quaternions (usualy, it is required for interpolation).

NOTE: Nonunit quaternions can be used with some operations faster than unit.

참고URL : https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another

반응형