我知道两个四元数的点(或内部)乘积是旋转之间的角度(包括轴旋转)。这使得点积等于四元数超球面上两点之间的角度 但是,我无法找到如何实际计算点积。
任何帮助将不胜感激!
当前代码:
public static float dot(Quaternion left, Quaternion right){
float angle;
//compute
return angle;
}
定义为Quaternion.w,Quaternion.x,Quaternion.y和Quaternion.z。
注意:可以假设四元数是标准化的。
答案 0 :(得分:11)
四元数的点积只是4D中的标准欧几里德点积:
dot = left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w
然后您要寻找的角度是点积的arccos
(注意点积不是角度):acos(dot)
。
但是,如果要查找两个四元数之间的相对旋转,例如从q1
到q2
,则应计算相对四元数q = q1^-1 * q2
,然后找到与q
关联的旋转{1}}。
答案 1 :(得分:2)
注意:从数值的角度来看,acos(点)非常不稳定。
如前所述,q = q1 ^ -1 * q2且比angle = 2 * atan2(q.vec.length(),q.w)
答案 2 :(得分:2)
它应该是2 x acos(点)来获得四元数之间的角度。
答案 3 :(得分:2)
实际上没有两个四元数之间的夹角,只有四元数通过乘法将一个四元数带到另一个四元数。但是,您可以通过计算两个四元数之间的差异(例如qDiff = q1.mul(q2.inverse())
来测量该映射转换的总旋转角度,或者您的库也许能够使用诸如qDiff = q1.difference(q2)
这样的调用来直接计算该角度),然后测量围绕四元数轴的角度(您的四元数库可能对此有一个例程,例如ang = qDiff.angle()
)。
请注意,您可能需要固定该值,因为测量绕轴的角度并不一定会使旋转“短距离”,例如:
if (ang > Math.PI) {
ang -= 2.0 * Math.PI;
} else if (ang < -Math.PI) {
ang += 2.0 * Math.PI;
}
我认为,在原始问题中,将四元数视为4d向量的意图是提供一种简单的方法来测量两个四元数的相似性,同时还要记住四元数表示旋转。 (从一个四元数到另一个四元数的实际旋转映射本身就是一个四元数,而不是标量。)
有几个答案建议使用点积的acos
。 (首先要注意:四元数必须是单位四元数才能起作用。)但是,其他答案没有考虑“双重掩盖问题”:q
和-q
都代表完全相同的旋转。
acos(q1 . q2)
和acos(q1 . (-q2))
都应返回相同的值,因为q2
和-q2
代表相同的旋转度。但是,(x == 0
除外)acos(x)
和acos(-x)
不会返回相同的值。因此,平均而言(给定的随机四元数),acos(q1 . q2)
不会给您一半的期望值,这意味着它不会为您提供q1
和{{1}之间的夹角},假设您完全关心q2
和q1
代表旋转。因此,即使您仅打算将点积或点积的q2
用作相似性指标,也要测试acos
和q1
的相似度轮换,您得到的答案将有一半时间是错误的。
更具体地说,如果您尝试将四元数简单地视为4d向量,并计算q2
,则有时会得到您期望的ang = acos(q1 . q2)
的值,其余时间为您实际想要的价值(考虑到双重保障问题)将为ang
。 。您获得的这两个值中的哪一个将在这些值之间随机波动,具体取决于PI - acos(-q1 . q2)
和q1
的计算方式!。
要解决此问题,必须对四元数进行规范化,以使它们位于双重覆盖空间的同一“半球”中。有几种方法可以做到这一点,老实说,我什至不确定这些方法中的哪一种是“正确的”或最佳方法。在某些情况下,它们都会产生与其他方法不同的结果。任何对以上三种归一化形式中的哪一种是正确或最优的反馈都将不胜感激。
q2
还请注意:
import java.util.Random;
import org.joml.Quaterniond;
import org.joml.Vector3d;
public class TestQuatNorm {
private static Random random = new Random(1);
private static Quaterniond randomQuaternion() {
return new Quaterniond(
random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1,
random.nextDouble() * 2 - 1, random.nextDouble() * 2 - 1)
.normalize();
}
public static double normalizedDot0(Quaterniond q1, Quaterniond q2) {
return Math.abs(q1.dot(q2));
}
public static double normalizedDot1(Quaterniond q1, Quaterniond q2) {
return
(q1.w >= 0.0 ? q1 : new Quaterniond(-q1.x, -q1.y, -q1.z, -q1.w))
.dot(
q2.w >= 0.0 ? q2 : new Quaterniond(-q2.x, -q2.y, -q2.z, -q2.w));
}
public static double normalizedDot2(Quaterniond q1, Quaterniond q2) {
Vector3d v1 = new Vector3d(q1.x, q1.y, q1.z);
Vector3d v2 = new Vector3d(q2.x, q2.y, q2.z);
double dot = v1.dot(v2);
Quaterniond q2n = dot >= 0.0 ? q2
: new Quaterniond(-q2.x, -q2.y, -q2.z, -q2.w);
return q1.dot(q2n);
}
public static double acos(double val) {
return Math.toDegrees(Math.acos(Math.max(-1.0, Math.min(1.0, val))));
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
var q1 = randomQuaternion();
var q2 = randomQuaternion();
double dot = q1.dot(q2);
double dot0 = normalizedDot0(q1, q2);
double dot1 = normalizedDot1(q1, q2);
double dot2 = normalizedDot2(q1, q2);
System.out.println(acos(dot) + "\t" + acos(dot0) + "\t" + acos(dot1)
+ "\t" + acos(dot2));
}
}
}
在数值上不太准确(给定一些最坏的情况,最不重要的一半数字可能是错误的); acos
的实现在JDK标准库中非常慢; acos
的参数甚至位于[-1,1]的外面,则返回acos
,这对于偶数四元数的点积是很常见的-因此,您需要对点积在调用NaN
之前的范围内。请参见上面的代码中的这一行:acos
根据this cheatsheet式。 (42),有一种更健壮和准确的方法来计算两个向量之间的角度,该方法将 return Math.toDegrees(Math.acos(Math.max(-1.0, Math.min(1.0, val))));
替换为acos
(尽管请注意,这也不能解决双重覆盖问题,因此您需要在应用以下内容之前使用上述规范化形式之一):
atan2
我承认我不理解这种表述,因为四元数的减法和加法没有几何意义。