Bir vektörden diğerine dönüşü temsil eden kuaterniyon bulma


105

U ve v olmak üzere iki vektörüm var. U'dan v'ye dönüşü temsil eden bir kuaterniyon bulmanın bir yolu var mı?

Yanıtlar:


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

Q 'yu normalleştirmeyi unutmayın.

Richard, benzersiz bir rotasyon olmadığı konusunda haklı, ancak yukarıdakiler "en kısa yay" ı vermeli, ki bu muhtemelen ihtiyacınız olan şey.


30
Bunun paralel vektörlerin durumunu ele almadığını unutmayın (her ikisi de aynı yönde veya zıt yönlerde). crossproductbu durumlarda geçerli olmayacaktır, bu nedenle önce dot(v1, v2) > 0.999999ve dot(v1, v2) < -0.999999sırasıyla işaretlemeniz ve paralel vektörler için bir kimlik kuatı döndürmeniz veya zıt vektörler için 180 derecelik bir dönüş (herhangi bir eksen etrafında) döndürmeniz gerekir.
sinisterchipmunk

11
Bunun iyi bir uygulaması ogre3d kaynak kodunda bulunabilir
João Portela

4
@sinisterchipmunk Aslında v1 = v2 olsaydı crossproduct (0,0,0) olurdu ve w pozitif olurdu, bu da özdeşliğe normalleşir. Gamedev.net/topic/… 'e göre, v1 = -v2 için ve yakın çevresinde de gayet iyi çalışmalıdır.
jpa

3
Bu tekniği işe yarayan biri nasıl oldu? Birincisi, sqrt((v1.Length ^ 2) * (v2.Length ^ 2))basitleştiriyor v1.Length * v2.Length. Mantıklı sonuçlar üretmek için bunun herhangi bir varyasyonunu bulamadım.
Joseph Thomson

2
Evet, bu çalışıyor. Kaynak koduna bakın . L61, vektörler zıt yönlere bakarsa işler (PI'yı döndür, aksi takdirde @ jpa'nın açıklamasına göre kimliği döndürür). L67 paralel vektörleri işler: matematiksel olarak gereksiz, ancak daha hızlı. L72, Polaris878'in cevabıdır, her iki vektörün de birim uzunluk olduğunu varsayar (bir sqrt'den kaçınır). Ayrıca birim testlerine bakın .
sinisterchipmunk

63

Yarım Yol Vektör Çözümü

Imbrondir'in sunmaya çalıştığına inandığım çözümü buldum (küçük bir hatayla da olsa, muhtemelen bu yüzden uğursuzçipmunk bunu doğrulamakta zorlandı).

Bunun gibi bir eksen etrafında bir dönüşü temsil eden bir kuaterniyon oluşturabileceğimize göre:

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

Ve iki normalleştirilmiş vektörün nokta ve çapraz çarpımı:

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

Bir dönme hareketi olarak gören u için v doğrudan nokta ve çapraz ürünlerinin sonuçlarından bir dönüşü temsil eder bir Dördey gerçekleştirebilmesi gibi dik bir vektör çevresinde teta (vektör arasındaki açı) ile döndürülerek elde edilebilir, görünüşe ; ancak, olduğu haliyle, teta = açı / 2 , bu, bunu yapmanın istenen dönüşün iki katı ile sonuçlanacağı anlamına gelir.

Bir çözüm arasında bir vektör yarı yolda hesaplamak için u ve v ve nokta ve çapraz ürün kullanmak u ve yarım bir dönüşü temsil eder bir Dördey oluşturmak için vektör , iki kez arasındaki açı u ve yarım vektör bu bizi v !

U == -v ve benzersiz bir yarı yol vektörünün hesaplanmasının imkansız hale geldiği özel bir durum vardır . Bu bizi alabilir sonsuz sayıda "en kısa yay" rotasyonlar göz önüne alındığında, beklenen u için v ve biz sadece herhangi bir vektör dik etrafında 180 derece döndürmek gerekir u (veya v ) olarak özel durum çözümü. Bu normalleştirilmiş çapraz ürününü alarak yapılır u başka vektörle değil paralel u .

Sözde kod izler (açıkçası, gerçekte özel durumun kayan nokta yanlışlıklarını hesaba katması gerekir - muhtemelen nokta ürünleri mutlak bir değer yerine bazı eşiklere göre kontrol ederek).

Ayrıca u == v (kimlik kuaterniyonu üretilir - kendiniz kontrol edin ve görün) özel bir durum olmadığını unutmayın .

// 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İşlev, bir vektör herhangi bir vektör ortogonal döner. Bu uygulama, en ortogonal taban vektörü ile çapraz çarpımı kullanır.

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

Yarım Yönlü Kuaterniyon Çözümü

Bu aslında kabul edilen cevapta sunulan çözümdür ve yarı yollu vektör çözümünden marjinal olarak daha hızlı görünmektedir (ölçümlerime göre ~% 20 daha hızlı, yine de sözüme güvenmeyin). Benim gibi başkalarının bir açıklama ile ilgilenmesi durumunda buraya ekliyorum.

Esasen, bir yarı yol vektörü kullanarak bir kuaterniyon hesaplamak yerine, gerekli dönüşün iki katı (diğer çözümde ayrıntılı olarak belirtildiği gibi) ile sonuçlanan kuaterniyonu hesaplayabilir ve bu ile sıfır derece arasındaki kuaterniyonu yarı yolda bulabilirsiniz.

Daha önce açıkladığım gibi, gerekli dönüşün iki katı için kuaterniyon:

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

Ve sıfır dönüş için kuaterniyon:

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

Yarı-yol kuaterniyonu hesaplamak, tıpkı vektörlerde olduğu gibi, basitçe kuaterniyonları toplamak ve sonucu normalleştirmek meselesidir. Bununla birlikte, vektörlerde de olduğu gibi, kuaterniyonların aynı büyüklükte olması gerekir, aksi takdirde sonuç daha büyük büyüklükteki kuaterniyona doğru eğilir.

Nokta ve iki vektörün çapraz ürününden yapılmış bir Dördey bu ürünlerin aynı büyüklüğe sahip olacaktır: length(u) * length(v). Dört bileşeni de bu faktöre bölmek yerine, özdeşlik kuaterniyonunu ölçeklendirebiliriz. Ve eğer kabul edilen cevabın kullanımı ile neden görünüşte işleri karmaşıklaştırdığını merak ediyorsanız sqrt(length(u) ^ 2 * length(v) ^ 2), bunun nedeni bir vektörün kare uzunluğunun hesaplanmasının uzunluktan daha hızlı olmasıdır, böylece bir sqrthesaplamayı kaydedebiliriz . Sonuç:

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

Ve sonra sonucu normalleştirin. Sözde kod şu şekildedir:

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: Harika! Bu bir çekicilik oldu. Kabul edilen cevap olmalı.
Rekin

1
Kuaterniyon sözdizimi bazı örneklerde değiştirilmiştir (Kuaterniyon (xyz, w) ve Kuaterniyon (w, xyz)). Ayrıca, son kod bloğunda radyan ve derecelerin açıları ifade etmek için karıştırıldığı görülmektedir (180'e karşı k_cos_theta + k).
Guillermo Blasco

1
Kuaterniyon (float, Vector3) skaler vektörden inşa edilirken, Quaternion (Vector3, float) eksen açısından inşa edilir. Belki kafa karıştırıcı olabilir, ama bence doğru. Hala yanlış olduğunu düşünüyorsan beni düzelt!
Joseph Thomson

İşe yaradı! Teşekkürler! Ancak, yukarıdaki işlemi gerçekleştirmek için benzer ve iyi açıklanmış başka bir bağlantı buldum . Kayıt için paylaşmam gerektiğini düşündüm;)
günahkar

1
@JosephThomson Yarım yol kuaterniyon çözümü buradan geliyor gibi görünüyor .
legends2k

6

Belirtildiği gibi sorun iyi tanımlanmamıştır: belirli bir vektör çifti için benzersiz bir dönüş yoktur. Örneğin u = <1, 0, 0> ve v = <0, 1, 0> olduğu durumu ele alalım . U'dan v'ye bir dönüş, z ekseni etrafında bir pi / 2 dönüşü olacaktır . U'dan v'ye başka bir dönüş, <1, 1, 0> vektörü etrafında bir pi dönüşü olacaktır .


1
Aslında sonsuz sayıda olası cevap yok mu? Çünkü "başlangıç" vektörünü "ila" vektörü ile hizaladıktan sonra sonucu yine de kendi ekseni etrafında serbestçe döndürebilirsiniz? Bu seçimi kısıtlamak ve sorunu iyi tanımlayabilmek için tipik olarak hangi ekstra bilgilerin kullanılabileceğini biliyor musunuz?
Doug McClean

5

Vektörü neden saf kuaterniyonlar kullanarak temsil etmiyoruz? Belki önce onları normalleştirmen daha iyi.
q, 1 = (0 u x u y u Z ) '
q 2 = (0 V x v y v z )'
q 1 q rot = q 2
Ön çarpma q 1 -1
q rot = q 1 -1 q 2
burada q 1 -1 = q 1 conj / q norm
Bu "sol bölünme" olarak düşünülebilir. Ne istediğini değil Sağ bölme vardır:
q çürüklüğü, sağ = q 2 -1 q 1


2
Kayboldum, q1'den q2'ye dönüş q_2 = q_rot q_1 q_rot ^ -1 olarak hesaplanmadı mı?
yota

4

Quaternion konusunda pek iyi değilim. Ancak bunun üzerinde saatlerce uğraştım ve Polaris878 çözümünü çalıştıramadım. V1 ve v2'yi önceden normalleştirmeyi denedim. Normalleştirme q. Q.xyz normalleştiriliyor. Yine de anlamıyorum. Sonuç yine de bana doğru sonucu vermedi.

Sonunda yine de bunu yapan bir çözüm buldum. Başkasına yardımcı oluyorsa, işte benim çalışma (python) kodum:

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 )

Eğer v1 ve v2, v1 == v2 veya v1 == -v2 (biraz toleransla) gibi paralel ise, çözümlerin Kuaterniyon (1, 0,0,0) olması gerektiğine inanıyorum (döndürme yok) özel bir durum yapılmalıdır. veya Kuaterniyon (0, * v1) (180 derece dönüş)


Çalışan bir uygulamam var, ama bu seninki daha güzel, bu yüzden gerçekten çalışmasını istedim. Maalesef tüm test durumlarım başarısız oldu. Testlerimin hepsi bir şeye benziyor quat = diffVectors(v1, v2); assert quat * v1 == v2.
sinisterchipmunk

angleDeğerini bir iç üründen aldığı için bunun işe yaraması pek olası değil .
sam hocevar

Quaternion () işlevi nerede?
Haziran Wang,

3

Bazı yanıtlar, çapraz çarpımın 0 olabileceği olasılığını dikkate almıyor gibi görünüyor.

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

Aşağıdaki toQuaterniongibi uygulanabilir:

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 kitaplığını kullanıyorsanız, şunları da yapabilirsiniz:

Quaternion::FromTwoVectors(from, to)

toQuaternion(axis, ang)-> ne olduğunu belirtmeyi unuttunuzang
Maksym Ganenko

2. parametre, anglekuaterniyonun radyan cinsinden ölçülen eksen-açı temsilinin bir parçasıdır.
Shital Shah

Kuaterniyonun bir vektörden diğerine dönmesi istendi. Açınız yok, önce onu hesaplamalısınız. Cevabınız açı hesaplamasını içermelidir. Şerefe!
Maksym Ganenko

Bu c ++ mı? ux () nedir?
Haziran Wang,

Evet, bu C ++. u, Eigen kitaplığındaki vektör türüdür (birini kullanıyorsanız).
Shital Shah

2

Algoritma açısından en hızlı çözüm sözde kodda görünüyor

 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
 }

Birim kuaterniyonlarına ihtiyacınız olduğundan emin olun (genellikle enterpolasyon için gereklidir).

NOT: Birim olmayan kuaterniyonlar, bazı işlemlerde birimden daha hızlı kullanılabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.