Ken*_*ent 6 java math rotation quaternions dot-product
我知道两个四元数的点(或内部)乘积是旋转之间的角度(包括轴旋转).这使得点积等于四元数超球面上两点之间的角度.
但是,我无法找到如何实际计算点积.
任何帮助,将不胜感激!
当前代码:
public static float dot(Quaternion left, Quaternion right){
float angle;
//compute
return angle;
}
Run Code Online (Sandbox Code Playgroud)
定义为Quaternion.w,Quaternion.x,Quaternion.y和Quaternion.z.
注意:可以假设四元数是标准化的.
use*_*587 13
四元数的点积只是4D中的标准欧几里德点积:
dot = left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w
Run Code Online (Sandbox Code Playgroud)
然后你正在寻找的角度是arccos
点积(注意点积不是角): acos(dot)
.
但是,如果你正在寻找两个四元之间的相对旋转,从说q1
来q2
,您应该计算相对四元数q = q1^-1 * q2
,然后找到相关的旋转q
.
实际上不存在两个四元数之间的角度这样的东西,只有通过乘法将一个四元数带到另一个四元数的四元数。但是,您可以通过计算两个四元数之间的差来测量该映射变换的总旋转角度(例如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;
}
Run Code Online (Sandbox Code Playgroud)
更新: 请参阅此答案。
我假设在最初的问题中,将四元数视为 4d 向量的目的是提供一种简单的方法来测量两个四元数的相似性,同时仍然记住四元数代表旋转。(从一个四元数到另一个四元数的实际旋转映射本身就是一个四元数,而不是标量。)
有几个答案建议使用acos
点积的 。(首先要注意的是:四元数必须是单位四元数才能工作。)但是,其他答案没有考虑“双重覆盖问题”: 和 都q
代表-q
完全相同的旋转。
和acos(q1 . q2)
都acos(q1 . (-q2))
应该返回相同的值,因为q2
和-q2
代表相同的旋转。但是(除了x == 0
),acos(x)
和acos(-x)
不返回相同的值。因此,平均而言(给定随机四元数),acos(q1 . q2)
在一半的时间里不会给你所期望的结果,这意味着它不会给你 和 之间的角度的测量q1
,q2
假设你关心所有这些q1
并q2
代表旋转。因此,即使您只打算使用点积或acos
点积作为相似性度量,来测试它们作为轮换的效果有多相似q1
和相似,您得到的答案有一半的时间都是错误的。q2
更具体地说,如果您尝试简单地将四元数视为 4d 向量,并且计算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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
另请注意:
acos
已知在数值上不太准确(给定一些最坏情况的输入,最多有一半的最低有效数字可能是错误的);acos
JDK 标准库中的实现异常缓慢;acos
NaN
如果其参数稍稍超出 [-1,1],则返回,这是偶数单位四元数的点积的常见情况 - 因此您需要在调用之前将点积的值绑定到该范围acos
。请参阅上面代码中的这一行: return Math.toDegrees(Math.acos(Math.max(-1.0, Math.min(1.0, val))));
Run Code Online (Sandbox Code Playgroud)
根据这个备忘单方程。(42),有一种更稳健和更准确的方法来计算替换为的两个向量之间的角度acos
(atan2
尽管请注意,这也不能解决双重覆盖问题,因此您需要在应用之前使用上述归一化形式之一下列):
ang(q1, q2) = 2 * atan2(|q1 - q2|, |q1 + q2|)
Run Code Online (Sandbox Code Playgroud)
我承认我不理解这个公式,因为四元数减法和加法没有几何意义。
归档时间: |
|
查看次数: |
12449 次 |
最近记录: |