ฉันมีเวกเตอร์สองตัว u และ v มีวิธีการหาควอเทอร์เนียนแทนการหมุนจาก u ถึง v หรือไม่?
ฉันมีเวกเตอร์สองตัว 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 พูดถูกว่าไม่มีการหมุนที่ไม่ซ้ำใคร แต่ข้างต้นควรให้ "ส่วนโค้งที่สั้นที่สุด" ซึ่งอาจเป็นสิ่งที่คุณต้องการ
sqrt((v1.Length ^ 2) * (v2.Length ^ 2))
v1.Length * v2.Length
ฉันไม่สามารถใช้รูปแบบนี้เพื่อให้ได้ผลลัพธ์ที่สมเหตุสมผล
ฉันคิดวิธีแก้ปัญหาที่ฉันเชื่อว่า Imbrondir พยายามนำเสนอ (แม้ว่าจะมีข้อผิดพลาดเล็กน้อยซึ่งอาจเป็นสาเหตุที่ทำให้ sinisterchipmunk มีปัญหาในการยืนยัน)
เนื่องจากเราสามารถสร้างควอเทอร์เนียนแทนการหมุนรอบแกนได้ดังนี้:
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
การเห็นการหมุนจากuถึงvสามารถทำได้โดยการหมุนโดยทีต้า (มุมระหว่างเวกเตอร์) รอบ ๆ เวกเตอร์ที่ตั้งฉากดูเหมือนว่าเราสามารถสร้างควอเทอร์เนียนแทนการหมุนดังกล่าวได้โดยตรงจากผลลัพธ์ของจุดและผลิตภัณฑ์ข้าม ; อย่างไรก็ตามในขณะที่มันหมายถึงtheta = มุม / 2ซึ่งหมายความว่าการทำเช่นนั้นจะทำให้เกิดการหมุนที่ต้องการเป็นสองเท่า
วิธีแก้ปัญหาหนึ่งคือการคำนวณเวกเตอร์ครึ่งทางระหว่างuและvและใช้จุดและผลคูณไขว้ของuและเวกเตอร์ครึ่งทางเพื่อสร้างควอเทอร์เนียนแทนการหมุนสองเท่าของมุมระหว่างuกับเวกเตอร์ครึ่งทางซึ่งพาเราไปสู่v !
มีกรณีพิเศษที่u == -vและเวกเตอร์ครึ่งทางที่ไม่ซ้ำกันจะไม่สามารถคำนวณได้ คาดว่าจะเป็นเช่นนี้เนื่องจากการหมุน "ส่วนโค้งที่สั้นที่สุด" จำนวนมากซึ่งสามารถพาเราจากuไปยังvได้และเราต้องหมุน 180 องศารอบเวกเตอร์ที่ตั้งฉากกับu (หรือv ) เป็นวิธีแก้ปัญหากรณีพิเศษของเรา นี้จะกระทำโดยการใช้ผลิตภัณฑ์ข้ามปกติของยูกับเวกเตอร์อื่น ๆไม่ขนานกับยู
รหัสเทียมตามมา (เห็นได้ชัดว่าในความเป็นจริงกรณีพิเศษจะต้องพิจารณาถึงความไม่ถูกต้องของจุดลอยตัว - อาจโดยการตรวจสอบผลิตภัณฑ์จุดเทียบกับเกณฑ์บางอย่างแทนที่จะเป็นค่าสัมบูรณ์)
โปรดทราบว่าไม่มีกรณีพิเศษเมื่อu == v (มีการสร้าง identity quaternion - ตรวจสอบและดูด้วยตัวคุณเอง)
// 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);
}
นี่เป็นคำตอบที่นำเสนอในคำตอบที่ยอมรับและดูเหมือนว่าจะเร็วกว่าโซลูชันเวกเตอร์ครึ่งทางเล็กน้อย (เร็วขึ้นประมาณ 20% จากการวัดของฉัน แต่อย่าใช้คำของฉันเลย) ฉันกำลังเพิ่มที่นี่เผื่อว่าคนอื่น ๆ เช่นฉันสนใจคำอธิบาย
โดยพื้นฐานแล้วแทนที่จะคำนวณควอเทอร์เนียนโดยใช้เวกเตอร์ครึ่งทางคุณสามารถคำนวณควอเทอร์เนียนซึ่งส่งผลให้เกิดการหมุนที่ต้องการเป็นสองเท่า (ตามรายละเอียดในโซลูชันอื่น) และค้นหาควอเทอร์เนียนครึ่งทางระหว่างนั้นถึงศูนย์องศา
ดังที่ฉันได้อธิบายไว้ก่อนหน้านี้ quaternion สำหรับการหมุนสองครั้งที่ต้องการคือ:
q.w == dot(u, v)
q.xyz == cross(u, v)
และควอเทอร์เนียนสำหรับการหมุนเป็นศูนย์คือ:
q.w == 1
q.xyz == (0, 0, 0)
การคำนวณควอเทอร์เนียนแบบครึ่งทางเป็นเพียงเรื่องของการหาผลรวมควอเทอร์เนียนและทำให้ผลลัพธ์เป็นปกติเช่นเดียวกับเวกเตอร์ อย่างไรก็ตามเช่นเดียวกับกรณีของเวกเตอร์ควอเทอร์เนียนจะต้องมีขนาดเท่ากันมิฉะนั้นผลลัพธ์จะเบ้ไปทางควอเทอร์เนียนที่มีขนาดใหญ่กว่า
quaternion length(u) * length(v)
สร้างขึ้นมาจากจุดและสินค้าข้ามของสองเวกเตอร์จะมีความสำคัญเช่นเดียวกับผลิตภัณฑ์เหล่านั้น แทนที่จะหารทั้งสี่องค์ประกอบด้วยปัจจัยนี้เราสามารถปรับขนาดควอเทอร์เนียนของเอกลักษณ์แทนได้ และหากคุณสงสัยว่าเหตุใดคำตอบที่ยอมรับจึงมีความซับซ้อนในการใช้sqrt(length(u) ^ 2 * length(v) ^ 2)
เนื่องจากความยาวกำลังสองของเวกเตอร์คำนวณได้เร็วกว่าความยาวดังนั้นเราจึงสามารถบันทึกการsqrt
คำนวณได้หนึ่งรายการ ผลลัพธ์คือ:
q.w = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)
จากนั้นปรับผลลัพธ์ให้เป็นปกติ รหัสหลอกมีดังนี้:
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)));
}
ปัญหาตามที่ระบุไว้ไม่ได้กำหนดไว้อย่างชัดเจน: ไม่มีการหมุนที่ไม่ซ้ำกันสำหรับเวกเตอร์คู่ที่ระบุ พิจารณากรณีเช่นที่ U = <1, 0, 0>และ v = <0, 1, 0> การหมุนหนึ่งครั้งจาก u ถึง v จะเป็นการหมุนpi / 2รอบแกน z หมุนอีกจาก u เพื่อโวลต์จะเป็นปี่หมุนรอบเวกเตอร์<1, 1, 0>
ทำไมไม่แสดงเวกเตอร์โดยใช้ควอเทอร์เนียนบริสุทธิ์ จะดีกว่าถ้าคุณทำให้ปกติก่อน
q 1 = (0 u x u y u z ) '
q 2 = (0 v x v y v z )'
q 1 q rot = q 2
คูณล่วงหน้าด้วย q 1 -1
q rot = q 1 -1 q 2
โดยที่ q 1 -1 = q 1 conj / q norm
อาจคิดได้ว่าเป็น "การแบ่งด้านซ้าย" การหารขวาซึ่งไม่ใช่สิ่งที่คุณต้องการคือ:
q rot, right = q 2 -1 q 1
ฉันไม่ค่อยเก่งเรื่อง Quaternion อย่างไรก็ตามฉันพยายามดิ้นรนเป็นเวลาหลายชั่วโมงในเรื่องนี้และไม่สามารถทำให้โซลูชัน Polaris878 ทำงานได้ ฉันได้ลอง pre-normalizing v1 และ v2 แล้ว การปรับมาตรฐาน q การทำให้ q.xyz เป็นปกติ แต่ฉันก็ยังไม่เข้าใจ ผลยังไม่ให้ผลลัพธ์ที่ถูกต้อง
ในที่สุดแม้ว่าฉันจะพบวิธีแก้ปัญหาที่ทำได้ ถ้ามันช่วยคนอื่นนี่คือรหัสที่ใช้งานได้ (python) ของฉัน:
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 )
ต้องทำกรณีพิเศษถ้า v1 และ v2 เป็นพาราเรลล์เช่น v1 == v2 หรือ v1 == -v2 (มีความอดทน) โดยที่ฉันเชื่อว่าโซลูชันควรเป็น Quaternion (1, 0,0,0) (ไม่มีการหมุน) หรือ Quaternion (0, * v1) (หมุน 180 องศา)
quat = diffVectors(v1, v2); assert quat * v1 == v2
ฉันจะทดสอบอะไรบางอย่างที่ทุกคนชอบดู
angle
ได้รับคุณค่าจากผลิตภัณฑ์จุด
คำตอบบางคำดูเหมือนจะไม่ได้พิจารณาถึงความเป็นไปได้ที่ผลคูณไขว้อาจเป็น 0 ตัวอย่างด้านล่างใช้การแสดงแกนมุม:
//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);
toQuaternion
สามารถดำเนินการดังต่อไปนี้:
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);
}
หากคุณใช้ไลบรารี Eigen คุณสามารถทำได้ดังนี้
Quaternion::FromTwoVectors(from, to)
toQuaternion(axis, ang)
-> คุณลืมระบุว่าคืออะไรang
angle
เป็นส่วนหนึ่งของการแสดงมุมแกนของควอเทอร์เนียนซึ่งวัดเป็นเรเดียน
จากมุมมองของอัลกอริทึมวิธีแก้ปัญหาที่เร็วที่สุดจะปรากฏใน 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
}
ตรวจสอบให้แน่ใจว่าคุณต้องการหน่วยควอเทอร์เนียน (โดยปกติจำเป็นสำหรับการแก้ไข)
หมายเหตุ: ควอร์เทอร์เนียนที่ไม่ใช่ยูนิตสามารถใช้ได้กับการทำงานบางอย่างที่เร็วกว่ายูนิต
crossproduct
จะใช้ไม่ได้ในกรณีเหล่านี้ดังนั้นก่อนอื่นคุณต้องตรวจสอบdot(v1, v2) > 0.999999
และdot(v1, v2) < -0.999999
ตามลำดับและส่งคืนค่าเอกลักษณ์สำหรับเวกเตอร์ขนานหรือคืนค่าการหมุน 180 องศา (เกี่ยวกับแกนใด ๆ ) สำหรับเวกเตอร์ที่ตรงกันข้าม