Tìm quaternion đại diện cho phép quay từ vectơ này sang vectơ khác


105

Tôi có hai vectơ u và v. Có cách nào để tìm một quaternion đại diện cho phép quay từ u sang v không?

Câu trả lời:


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

Đừng quên chuẩn hóa q.

Richard nói đúng về việc không có một vòng quay duy nhất, nhưng phần trên sẽ cung cấp cho "vòng cung ngắn nhất", có lẽ là những gì bạn cần.


30
Cần biết rằng điều này không xử lý trường hợp các vectơ song song (cả hai cùng hướng hoặc hướng ngược lại). crossproductsẽ không hợp lệ trong những trường hợp này, vì vậy trước tiên bạn cần phải kiểm tra dot(v1, v2) > 0.999999dot(v1, v2) < -0.999999tương ứng và trả về một định danh cho các vectơ song song hoặc trả về một phép quay 180 độ (về bất kỳ trục nào) cho các vectơ đối diện.
sinisterchipmunk

11
Một thực hiện tốt những điều này có thể được tìm thấy trong mã nguồn Ogre3D
João Portela

4
@sinisterchipmunk Trên thực tế, nếu v1 = v2, crossproduct sẽ là (0,0,0) và w sẽ là số dương, điều này chuẩn hóa thành danh tính. Theo gamedev.net/topic/…, nó sẽ hoạt động tốt đối với v1 = -v2 và trong vùng lân cận của chúng.
jpa

3
Làm thế nào có ai có kỹ thuật này để làm việc? Đối với một, sqrt((v1.Length ^ 2) * (v2.Length ^ 2))đơn giản hóa thành v1.Length * v2.Length. Tôi không thể nhận được bất kỳ biến thể nào của điều này để tạo ra kết quả hợp lý.
Joseph Thomson,

2
Vâng, điều này hoạt động. Xem mã nguồn . L61 xử lý nếu các vectơ đối mặt với các hướng ngược nhau (trả về PI, nếu không nó sẽ trả về danh tính theo nhận xét của @ jpa). L67 xử lý các vectơ song song: không cần thiết về mặt toán học, nhưng nhanh hơn. L72 là câu trả lời của Polaris878, giả sử cả hai vectơ đều có độ dài đơn vị (tránh sqrt). Xem thêm các bài kiểm tra đơn vị .
sinisterchipmunk

63

Giải pháp vectơ nửa chiều

Tôi đã đưa ra giải pháp mà tôi tin rằng Imbrondir đang cố gắng đưa ra (mặc dù có một sai sót nhỏ, đó có thể là lý do tại sao sinisterchipmunk gặp khó khăn khi xác minh nó).

Cho rằng chúng ta có thể tạo một quaternion đại diện cho một phép quay quanh một trục như sau:

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

Và dấu chấm và tích chéo của hai vectơ chuẩn hóa là:

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

Xem như một phép quay từ u sang v có thể đạt được bằng cách quay theta (góc giữa các vectơ) xung quanh vectơ vuông góc, có vẻ như chúng ta có thể trực tiếp xây dựng một quaternion đại diện cho một phép quay như vậy từ các kết quả của dấu chấm và tích chéo. ; tuy nhiên, theo nghĩa của nó, theta = angle / 2 , có nghĩa là làm như vậy sẽ dẫn đến hai lần quay mong muốn.

Một giải pháp là tính nửa vectơ giữa uv , đồng thời sử dụng dấu chấm và tích chéo của u và vectơ nửa đường để tạo một tứ phương đại diện cho chuyển động quay hai lần góc giữa unửa vectơ, đưa chúng ta đến tận v !

Có một trường hợp đặc biệt, trong đó u == -v và một vectơ nửa đường duy nhất trở nên không thể tính được. Điều này được mong đợi, với vô số phép quay "cung ngắn nhất" có thể đưa chúng ta từ u đến v , và chúng ta chỉ cần quay 180 độ xung quanh bất kỳ vectơ nào trực giao với u (hoặc v ) như là lời giải trường hợp đặc biệt của chúng ta. Điều này được thực hiện bằng cách lấy tích chéo chuẩn hóa của u với bất kỳ vectơ nào khác không song song với u .

Mã giả theo sau (rõ ràng, trong thực tế, trường hợp đặc biệt sẽ phải tính đến độ không chính xác của dấu chấm động - có thể bằng cách kiểm tra các sản phẩm dấu chấm so với một số ngưỡng thay vì một giá trị tuyệt đối).

Cũng lưu ý rằng không có trường hợp đặc biệt nào khi u == v (mã định danh được tạo ra - hãy tự mình kiểm tra và xem).

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

Các orthogonalhàm trả về bất kỳ trực giao vector vào vector nhất định. Việc triển khai này sử dụng tích chéo với vectơ cơ sở trực giao nhất.

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

Giải pháp Half-Way Quaternion

Đây thực sự là giải pháp được trình bày trong câu trả lời được chấp nhận, và nó dường như nhanh hơn một chút so với giải pháp vectơ nửa chiều (nhanh hơn ~ 20% theo các phép đo của tôi, mặc dù tôi không nói gì về nó). Tôi thêm nó vào đây trong trường hợp những người khác như tôi quan tâm đến lời giải thích.

Về cơ bản, thay vì tính toán một quaternion bằng cách sử dụng vectơ nửa chiều, bạn có thể tính toán quaternion dẫn đến hai lần quay cần thiết (như được trình bày chi tiết trong giải pháp khác) và tìm nửa đường của quaternion giữa nó và 0 độ.

Như tôi đã giải thích trước đây, quaternion để tăng gấp đôi vòng quay cần thiết là:

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

Và quaternion cho phép quay bằng không là:

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

Tính toán nửa quaternion chỉ đơn giản là một vấn đề tổng hợp các quaternion và chuẩn hóa kết quả, giống như với vectơ. Tuy nhiên, đối với vectơ cũng vậy, các quaternion phải có cùng độ lớn, nếu không kết quả sẽ bị lệch về phía quaternion có độ lớn lớn hơn.

Một quaternion xây dựng từ các dấu chấm và sản phẩm chéo của hai vectơ sẽ có tầm quan trọng tương tự như những sản phẩm: length(u) * length(v). Thay vì phân chia tất cả bốn thành phần cho yếu tố này, thay vào đó chúng ta có thể mở rộng quy mô tứ phân vị. Và nếu bạn đang thắc mắc tại sao câu trả lời được chấp nhận dường như làm phức tạp vấn đề khi sử dụng sqrt(length(u) ^ 2 * length(v) ^ 2), đó là bởi vì độ dài bình phương của vectơ được tính nhanh hơn độ dài, vì vậy chúng ta có thể tiết kiệm một sqrtphép tính. Kết quả là:

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

Và sau đó chuẩn hóa kết quả. Mã giả sau:

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

12
+1: Tuyệt vời! Điều này hoạt động như một sự quyến rũ. Nên là câu trả lời được chấp nhận.
Rekin

1
Cú pháp Quaternion được bật trên một số ví dụ (Quaternion (xyz, w) và Quaternion (w, xyz)). Cũng có vẻ như trong khối mã cuối cùng, radian và độ được trộn lẫn để thể hiện góc (180 so với k_cos_theta + k).
Guillermo Blasco

1
Quaternion (float, Vector3) được xây dựng từ vector vô hướng, trong khi Quaternion (Vector3, float) được xây dựng từ góc trục. Có lẽ có khả năng gây nhầm lẫn, nhưng tôi nghĩ nó là chính xác. Hãy sửa cho tôi nếu bạn vẫn nghĩ nó là sai!
Joseph Thomson,

Nó đã làm việc! Cảm ơn! Tuy nhiên, tôi đã tìm thấy một liên kết tương tự và được giải thích rõ ràng khác để thực hiện thao tác trên. Tôi nghĩ rằng tôi nên chia sẻ để ghi lại;)
tội nhân

1
@JosephThomson Giải pháp nửa đường quaternion dường như đến từ đây .
Legends2k

6

Vấn đề như đã nêu chưa được xác định rõ ràng: không có phép quay duy nhất cho một cặp vectơ đã cho. Hãy xem xét trường hợp, ví dụ, trong đó u = <1, 0, 0> và v = <0, 1, 0> . Một phép quay từ u sang v sẽ là một phép quay pi / 2 quanh trục z. Một phép quay khác từ u sang v sẽ là một phép quay pi xung quanh vectơ <1, 1, 0> .


1
Trong thực tế, không có vô số câu trả lời có thể có? Bởi vì sau khi bạn căn chỉnh vectơ "from" với vectơ "tới", bạn vẫn có thể tự do quay kết quả quanh trục của nó? Bạn có biết thông tin bổ sung nào thường có thể được sử dụng để hạn chế sự lựa chọn này và làm cho vấn đề được xác định rõ ràng không?
Doug McClean

5

Tại sao không biểu diễn vectơ bằng cách sử dụng các quaternion thuần túy? Sẽ tốt hơn nếu bạn bình thường hóa chúng trước.
q 1 = (0 u x u y u z ) '
q 2 = (0 v x v y v z )'
q 1 q rot = q 2 Nhân
trước với q 1 -1
q rot = q 1 -1 q 2
trong đó q 1 -1 = q 1 conj / q chuẩn mực
Đây có thể được coi là "sự phân chia trái". Phép chia bên phải, không như ý bạn muốn là:
q rot, right = q 2 -1 q 1


2
Tôi bị lạc, không phải vòng quay từ q1 đến q2 được tính là q_2 = q_rot q_1 q_rot ^ -1?
yota

4

Tôi không giỏi về Quaternion. Tuy nhiên, tôi đã vật lộn hàng giờ về vấn đề này và không thể làm cho giải pháp Polaris878 hoạt động. Tôi đã thử chuẩn hóa trước v1 và v2. Chuẩn hóa q. Chuẩn hóa q.xyz. Tuy nhiên, tôi vẫn không hiểu nó. Kết quả vẫn không cho tôi kết quả đúng.

Cuối cùng, mặc dù tôi đã tìm thấy một giải pháp đã làm được. Nếu nó giúp ích cho bất kỳ ai khác, đây là mã (python) đang hoạt động của tôi:

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 )

Một trường hợp đặc biệt phải được thực hiện nếu v1 và v2 là paralell như v1 == v2 hoặc v1 == -v2 (với một số dung sai), trong đó tôi tin rằng các giải pháp phải là Quaternion (1, 0,0,0) (không xoay) hoặc Quaternion (0, * v1) (xoay 180 độ)


Tôi có một triển khai đang hoạt động, nhưng cái này của bạn đẹp hơn, vì vậy tôi thực sự muốn nó hoạt động. Thật không may, nó không thành công tất cả các trường hợp thử nghiệm của tôi. Tất cả các bài kiểm tra của tôi đều trông giống như thế quat = diffVectors(v1, v2); assert quat * v1 == v2.
sinisterchipmunk

Nó không chắc rằng điều này sẽ hiệu quả vì anglenó có được giá trị từ một sản phẩm chấm.
sam hocevar

Hàm Quaternion () ở đâu?
Tháng sáu Wang

3

Một số câu trả lời dường như không xem xét đến khả năng sản phẩm chéo có thể là 0. Đoạn mã dưới đây sử dụng biểu diễn trục góc:

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

toQuaternionthể được thực hiện như sau:

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

Nếu bạn đang sử dụng thư viện Eigen, bạn cũng có thể thực hiện:

Quaternion::FromTwoVectors(from, to)

toQuaternion(axis, ang)-> bạn quên để xác định những gì làang
Maksym Ganenko

Tham số thứ 2 anglelà một phần của biểu diễn góc trục của quaternion, được đo bằng radian.
Shital Shah

Bạn được yêu cầu lấy quaternion để xoay từ vectơ này sang vectơ khác. Bạn không có góc, bạn phải tính toán nó trước. Câu trả lời của bạn nên chứa phép tính góc. Chúc mừng!
Maksym Ganenko

Đây là c ++? ux () là gì?
Tháng sáu Wang

Vâng, đây là C ++. u là kiểu vectơ từ thư viện Eigen (nếu bạn đang sử dụng).
Shital Shah

2

Từ quan điểm thuật toán, giải pháp nhanh nhất là mã giả

 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
 }

Hãy chắc chắn rằng bạn cần quaternion đơn vị (thông thường, nó được yêu cầu cho phép nội suy).

LƯU Ý: Các quaternion phi đơn vị có thể được sử dụng với một số hoạt động nhanh hơn đơn vị.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.