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ı?
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:
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.
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.
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);
}
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 sqrt
hesaplamayı 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)));
}
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 .
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
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üş)
quat = diffVectors(v1, v2); assert quat * v1 == v2
.
angle
Değerini bir iç üründen aldığı için bunun işe yaraması pek olası değil .
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 toQuaternion
gibi 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
angle
kuaterniyonun radyan cinsinden ölçülen eksen-açı temsilinin bir parçasıdır.
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.
crossproduct
bu durumlarda geçerli olmayacaktır, bu nedenle öncedot(v1, v2) > 0.999999
vedot(v1, v2) < -0.999999
sı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.