การค้นหาควอเทอร์เนียนแทนการหมุนจากเวกเตอร์หนึ่งไปยังอีกเวกเตอร์หนึ่ง


105

ฉันมีเวกเตอร์สองตัว u และ v มีวิธีการหาควอเทอร์เนียนแทนการหมุนจาก u ถึง v หรือไม่?

คำตอบ:


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

อย่าลืมทำให้ q เป็นปกติ

Richard พูดถูกว่าไม่มีการหมุนที่ไม่ซ้ำใคร แต่ข้างต้นควรให้ "ส่วนโค้งที่สั้นที่สุด" ซึ่งอาจเป็นสิ่งที่คุณต้องการ


31
โปรดทราบว่าสิ่งนี้ไม่ได้จัดการกับกรณีของเวกเตอร์ขนาน (ทั้งในทิศทางเดียวกันหรือชี้ไปในทิศทางตรงกันข้าม) crossproductจะใช้ไม่ได้ในกรณีเหล่านี้ดังนั้นก่อนอื่นคุณต้องตรวจสอบdot(v1, v2) > 0.999999และdot(v1, v2) < -0.999999ตามลำดับและส่งคืนค่าเอกลักษณ์สำหรับเวกเตอร์ขนานหรือคืนค่าการหมุน 180 องศา (เกี่ยวกับแกนใด ๆ ) สำหรับเวกเตอร์ที่ตรงกันข้าม
sinisterchipmunk

11
การใช้งานที่ดีสามารถพบได้ในซอร์สโค้ด ogre3d
João Portela

4
@sinisterchipmunk อันที่จริงถ้า v1 = v2 crossproduct จะเป็น (0,0,0) และ w จะเป็นค่าบวกซึ่งทำให้เป็นอัตลักษณ์ปกติ ตามgamedev.net/topic/…ควรใช้งานได้ดีสำหรับ v1 = -v2 และในบริเวณใกล้เคียง
jpa

3
ใครมีเทคนิคนี้ไปใช้ได้ผลอย่างไร สำหรับหนึ่งช่วยลดความยุ่งยากในการsqrt((v1.Length ^ 2) * (v2.Length ^ 2)) v1.Length * v2.Lengthฉันไม่สามารถใช้รูปแบบนี้เพื่อให้ได้ผลลัพธ์ที่สมเหตุสมผล
Joseph Thomson

2
ใช่มันใช้งานได้ ดูรหัสที่มา L61 จะจัดการหากเวกเตอร์หันหน้าไปทางตรงกันข้าม (ส่งคืน PI มิฉะนั้นจะส่งคืนข้อมูลประจำตัวตามคำพูดของ @ jpa) L67 จัดการเวกเตอร์แบบขนาน: ไม่จำเป็นทางคณิตศาสตร์ แต่เร็วกว่า L72 คือคำตอบของ Polaris878 โดยสมมติว่าเวกเตอร์ทั้งสองเป็นความยาวหน่วย (หลีกเลี่ยง sqrt) ดูการทดสอบหน่วยด้วย
sinisterchipmunk

64

โซลูชันเวกเตอร์ครึ่งทาง

ฉันคิดวิธีแก้ปัญหาที่ฉันเชื่อว่า 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);
}

โซลูชัน Half-Way Quaternion

นี่เป็นคำตอบที่นำเสนอในคำตอบที่ยอมรับและดูเหมือนว่าจะเร็วกว่าโซลูชันเวกเตอร์ครึ่งทางเล็กน้อย (เร็วขึ้นประมาณ 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)));
}

12
+1: เยี่ยมมาก! สิ่งนี้ทำงานเป็นเสน่ห์ ควรเป็นคำตอบที่ได้รับการยอมรับ
Rekin

1
ไวยากรณ์ Quaternion ถูกเปิดในบางตัวอย่าง (Quaternion (xyz, w) และ Quaternion (w, xyz)) ดูเหมือนว่าในเรเดียนบล็อกโค้ดสุดท้ายและองศาจะผสมกันเป็นมุมแสดง (180 เทียบกับ k_cos_theta + k)
Guillermo Blasco

1
Quaternion (float, Vector3) เป็นการสร้างจากสเกลาร์ - เวกเตอร์ในขณะที่ Quaternion (Vector3, float) เป็นการสร้างจากมุมแกน บางทีอาจทำให้สับสน แต่ฉันคิดว่าถูกต้อง แก้ไขฉันถ้าคุณยังคิดว่าผิด!
Joseph Thomson

มันได้ผล! ขอบคุณ! อย่างไรก็ตามฉันพบลิงค์อื่นที่คล้ายกันและอธิบายได้ดีเพื่อดำเนินการข้างต้น คิดว่าฉันควรแบ่งปันเพื่อบันทึก;)
คนบาป

1
@JosephThomson ครึ่งทางแก้ปัญหา quaternion ดูเหมือนว่าจะมาจากที่นี่
legends2k

6

ปัญหาตามที่ระบุไว้ไม่ได้กำหนดไว้อย่างชัดเจน: ไม่มีการหมุนที่ไม่ซ้ำกันสำหรับเวกเตอร์คู่ที่ระบุ พิจารณากรณีเช่นที่ U = <1, 0, 0>และ v = <0, 1, 0> การหมุนหนึ่งครั้งจาก u ถึง v จะเป็นการหมุนpi / 2รอบแกน z หมุนอีกจาก u เพื่อโวลต์จะเป็นปี่หมุนรอบเวกเตอร์<1, 1, 0>


1
ในความเป็นจริงไม่มีคำตอบที่เป็นไปได้จำนวนอนันต์หรือไม่? เพราะหลังจากที่คุณจัดแนวเวกเตอร์ "จาก" ด้วยเวกเตอร์ "ถึง" แล้วคุณยังสามารถหมุนผลลัพธ์รอบแกนของมันได้อย่างอิสระ? คุณรู้หรือไม่ว่าโดยทั่วไปแล้วข้อมูลเพิ่มเติมใดที่สามารถใช้เพื่อ จำกัด ทางเลือกนี้และกำหนดปัญหาได้ดี
Doug McClean

5

ทำไมไม่แสดงเวกเตอร์โดยใช้ควอเทอร์เนียนบริสุทธิ์ จะดีกว่าถ้าคุณทำให้ปกติก่อน
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


2
ฉันหลงทางไม่ใช่การหมุนจาก q1 ถึง q2 คำนวณเป็น q_2 = q_rot q_1 q_rot ^ -1 หรือ
yota

4

ฉันไม่ค่อยเก่งเรื่อง 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ฉันจะทดสอบอะไรบางอย่างที่ทุกคนชอบดู
sinisterchipmunk

ไม่น่าจะได้ผลเลยเนื่องจากangleได้รับคุณค่าจากผลิตภัณฑ์จุด
sam hocevar

ฟังก์ชัน Quaternion () อยู่ที่ไหน
มิ.ย. วัง

3

คำตอบบางคำดูเหมือนจะไม่ได้พิจารณาถึงความเป็นไปได้ที่ผลคูณไขว้อาจเป็น 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
Maksym Ganenko

พารามิเตอร์ตัวที่ 2 angleเป็นส่วนหนึ่งของการแสดงมุมแกนของควอเทอร์เนียนซึ่งวัดเป็นเรเดียน
Shital Shah

คุณถูกขอให้ใช้ quaternion เพื่อหมุนจากเวกเตอร์หนึ่งไปยังอีกเวกเตอร์หนึ่ง คุณไม่มีมุมคุณต้องคำนวณก่อน คำตอบของคุณควรมีการคำนวณมุม ไชโย!
Maksym Ganenko

นี่คือ c ++? ux () คืออะไร?
มิถุนายนวัง

ใช่นี่คือ C ++ u เป็นประเภทเวกเตอร์จากไลบรารี Eigen (หากคุณใช้อยู่)
Shital Shah

2

จากมุมมองของอัลกอริทึมวิธีแก้ปัญหาที่เร็วที่สุดจะปรากฏใน 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
 }

ตรวจสอบให้แน่ใจว่าคุณต้องการหน่วยควอเทอร์เนียน (โดยปกติจำเป็นสำหรับการแก้ไข)

หมายเหตุ: ควอร์เทอร์เนียนที่ไม่ใช่ยูนิตสามารถใช้ได้กับการทำงานบางอย่างที่เร็วกว่ายูนิต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.