Slerp
#クォータニオン #回転 #lerp #補間
Spherical Lerpの略
回転表現としてのクォータニオンにおいて、球上で等速で回転するような補間を指す
2回転間の補間に最適なアルゴリズム
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
実装リンク集
Three.js r139
Babylon.js v5.0.0
cglm v0.8.5
TypeScript実装
code:ts
export function quatSlerp(a: Quaternion, b: Quaternion, t: number): Quaternion {
// early abort if t is exactly zero or one
if (t === 0.0) { return a; }
if (t === 1.0) { return b; }
let cosHalfTheta = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; // dot product
if (cosHalfTheta < 0.0) {
b = new Quaternion(-b.x, -b.y, -b.z, -b.w);
cosHalfTheta = -cosHalfTheta;
}
// I think you two are the same
if (cosHalfTheta >= 1.0) {
return a;
}
const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
// very small diff. fallback to simple lerp
if (sqrSinHalfTheta <= Number.EPSILON) {
const s = 1.0 - t;
return new Quaternion(
s * a.x + t * b.x,
s * a.y + t * b.y,
s * a.z + t * b.z,
s * a.w + t * b.w,
).normalized;
}
// welcome
const sinHalfTheta = Math.sqrt(sqrSinHalfTheta);
const halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta);
const ratioA = Math.sin((1.0 - t) * halfTheta) / sinHalfTheta;
const ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
return new Quaternion(
a.x * ratioA + b.x * ratioB,
a.y * ratioA + b.y * ratioB,
a.z * ratioA + b.z * ratioB,
a.w * ratioA + b.w * ratioB,
);
}
GLSL実装
正確にはSlerpではなく三次元方向ベクトル間の補間ですが……
code:glsl
vec3 slerpUniformVec3(vec3 a, vec3 b, float t) {
// early abort if t is exactly zero or one
if (t == 0.0) { return a; }
if (t == 1.0) { return b; }
float ratioA;
float ratioB;
float cosv = dot(a, b);
if (cosv > 1.0 - 1E-4) {
// avoid precision funny, just lerp it
ratioB = t;
ratioA = 1.0 - t;
} else {
// do the proper slerp
float sinv = sqrt(1.0 - cosv * cosv);
float theta = acos(cosv);
ratioA = sin((1.0 - t) * theta) / sinv;
ratioB = sin(t * theta) / sinv;
}
return ratioA * a + ratioB * b;
}