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?
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:
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.
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ý.
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 u và v , đồ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 u và nử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 orthogonal
hà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);
}
Đâ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 sqrt
phé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)));
}
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> .
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
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 độ)
quat = diffVectors(v1, v2); assert quat * v1 == v2
.
angle
nó có được giá trị từ một sản phẩm chấm.
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);
Có toQuaternion
thể đượ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
angle
là một phần của biểu diễn góc trục của quaternion, được đo bằng radian.
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ị.
crossproduct
sẽ 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 tradot(v1, v2) > 0.999999
vàdot(v1, v2) < -0.999999
tươ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.