diff --git a/docs/source/quat.rst b/docs/source/quat.rst index c52a235..8497099 100644 --- a/docs/source/quat.rst +++ b/docs/source/quat.rst @@ -55,6 +55,7 @@ Functions: #. :c:func:`glm_quat_lerp` #. :c:func:`glm_quat_nlerp` #. :c:func:`glm_quat_slerp` +#. :c:func:`glm_quat_slerp_longest` #. :c:func:`glm_quat_look` #. :c:func:`glm_quat_for` #. :c:func:`glm_quat_forp` @@ -351,6 +352,17 @@ Functions documentation | *[in]* **t** interpolant (amount) clamped between 0 and 1 | *[out]* **dest** result quaternion +.. c:function:: void glm_quat_slerp_longest(versor q, versor r, float t, versor dest) + + | interpolates between two quaternions + | using spherical linear interpolation (SLERP) and always takes the longest path + + Parameters: + | *[in]* **from** from + | *[in]* **to** to + | *[in]* **t** interpolant (amount) clamped between 0 and 1 + | *[out]* **dest** result quaternion + .. c:function:: void glm_quat_look(vec3 eye, versor ori, mat4 dest) | creates view matrix using quaternion as camera orientation diff --git a/include/cglm/call/quat.h b/include/cglm/call/quat.h index acf4313..4244d36 100644 --- a/include/cglm/call/quat.h +++ b/include/cglm/call/quat.h @@ -133,6 +133,10 @@ CGLM_EXPORT void glmc_quat_slerp(versor q, versor r, float t, versor dest); +CGLM_EXPORT +void +glmc_quat_slerp_longest(versor q, versor r, float t, versor dest); + CGLM_EXPORT void glmc_quat_look(vec3 eye, versor ori, mat4 dest); diff --git a/include/cglm/quat.h b/include/cglm/quat.h index 18892d3..777e793 100644 --- a/include/cglm/quat.h +++ b/include/cglm/quat.h @@ -39,6 +39,7 @@ CGLM_INLINE void glm_quat_lerp(versor from, versor to, float t, versor dest); CGLM_INLINE void glm_quat_lerpc(versor from, versor to, float t, versor dest); CGLM_INLINE void glm_quat_slerp(versor q, versor r, float t, versor dest); + CGLM_INLINE void glm_quat_slerp_longest(versor q, versor r, float t, versor dest); CGLM_INLINE void glm_quat_nlerp(versor q, versor r, float t, versor dest); CGLM_INLINE void glm_quat_look(vec3 eye, versor ori, mat4 dest); CGLM_INLINE void glm_quat_for(vec3 dir, vec3 fwd, vec3 up, versor dest); @@ -742,6 +743,52 @@ glm_quat_slerp(versor from, versor to, float t, versor dest) { glm_vec4_scale(q1, 1.0f / sinTheta, dest); } +/*! + * @brief interpolates between two quaternions + * using spherical linear interpolation (SLERP) and always takes the long path + * + * @param[in] from from + * @param[in] to to + * @param[in] t amout + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_slerp_longest(versor from, versor to, float t, versor dest) { + CGLM_ALIGN(16) vec4 q1, q2; + float cosTheta, sinTheta, angle; + + cosTheta = glm_quat_dot(from, to); + glm_quat_copy(from, q1); + + if (fabsf(cosTheta) >= 1.0f) { + glm_quat_copy(q1, dest); + return; + } + + /* longest path */ + if (!(cosTheta < 0.0f)) { + glm_vec4_negate(q1); + cosTheta = -cosTheta; + } + + sinTheta = sqrtf(1.0f - cosTheta * cosTheta); + + /* LERP to avoid zero division */ + if (fabsf(sinTheta) < 0.001f) { + glm_quat_lerp(from, to, t, dest); + return; + } + + /* SLERP */ + angle = acosf(cosTheta); + glm_vec4_scale(q1, sinf((1.0f - t) * angle), q1); + glm_vec4_scale(to, sinf(t * angle), q2); + + glm_vec4_add(q1, q2, q1); + glm_vec4_scale(q1, 1.0f / sinTheta, dest); +} + /*! * @brief creates view matrix using quaternion as camera orientation * diff --git a/include/cglm/struct/quat.h b/include/cglm/struct/quat.h index 859b664..ff87740 100644 --- a/include/cglm/struct/quat.h +++ b/include/cglm/struct/quat.h @@ -37,6 +37,7 @@ CGLM_INLINE versors glms_quat_lerpc(versors from, versors to, float t) CGLM_INLINE versors glms_quat_nlerp(versors from, versors to, float t) CGLM_INLINE versors glms_quat_slerp(versors from, versors to, float t) + CGLM_INLINE versors glms_quat_slerp_longest(versors from, versors to, float t) CGLM_INLINE mat4s. glms_quat_look(vec3s eye, versors ori) CGLM_INLINE versors glms_quat_for(vec3s dir, vec3s fwd, vec3s up) CGLM_INLINE versors glms_quat_forp(vec3s from, vec3s to, vec3s fwd, vec3s up) @@ -457,6 +458,23 @@ glms_quat_(slerp)(versors from, versors to, float t) { return dest; } +/*! + * @brief interpolates between two quaternions + * using spherical linear interpolation (SLERP) and always takes the longest path + * + * @param[in] from from + * @param[in] to to + * @param[in] t amout + * @returns result quaternion + */ +CGLM_INLINE +versors +glms_quat_(slerp_longest)(versors from, versors to, float t) { + versors dest; + glm_quat_slerp_longest(from.raw, to.raw, t, dest.raw); + return dest; +} + /*! * @brief creates view matrix using quaternion as camera orientation * diff --git a/src/quat.c b/src/quat.c index 56cda30..3f41286 100644 --- a/src/quat.c +++ b/src/quat.c @@ -188,6 +188,12 @@ glmc_quat_slerp(versor from, versor to, float t, versor dest) { glm_quat_slerp(from, to, t, dest); } +CGLM_EXPORT +void +glmc_quat_slerp_longest(versor from, versor to, float t, versor dest) { + glm_quat_slerp_longest(from, to, t, dest); +} + CGLM_EXPORT void glmc_quat_look(vec3 eye, versor ori, mat4 dest) {