diff --git a/docs/source/quat.rst b/docs/source/quat.rst index 4f424df..a8ca028 100644 --- a/docs/source/quat.rst +++ b/docs/source/quat.rst @@ -52,6 +52,7 @@ Functions: #. :c:func:`glm_quat_mat3` #. :c:func:`glm_quat_mat3t` #. :c:func:`glm_quat_lerp` +#. :c:func:`glm_quat_nlerp` #. :c:func:`glm_quat_slerp` #. :c:func:`glm_quat_look` #. :c:func:`glm_quat_for` @@ -304,6 +305,25 @@ Functions documentation | *[in]* **t** interpolant (amount) clamped between 0 and 1 | *[out]* **dest** result quaternion +.. c:function:: void glm_quat_nlerp(versor q, versor r, float t, versor dest) + + | interpolates between two quaternions + | taking the shortest rotation path using + | normalized linear interpolation (NLERP) + + | This is a cheaper alternative to slerp; most games use nlerp + | for animations as it visually makes little difference. + + References: + * `Understanding Slerp, Then Not Using it `_ + * `Lerp, Slerp and Nlerp `_ + + 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_slerp(versor q, versor r, float t, versor dest) | interpolates between two quaternions diff --git a/include/cglm/call/quat.h b/include/cglm/call/quat.h index 32e503c..dfd380d 100644 --- a/include/cglm/call/quat.h +++ b/include/cglm/call/quat.h @@ -116,11 +116,15 @@ glmc_quat_mat3t(versor q, mat3 dest); CGLM_EXPORT void glmc_quat_lerp(versor from, versor to, float t, versor dest); - + CGLM_EXPORT void glmc_quat_lerpc(versor from, versor to, float t, versor dest); +CGLM_EXPORT +void +glmc_quat_nlerp(versor q, versor r, float t, versor dest); + CGLM_EXPORT void glmc_quat_slerp(versor q, versor r, float t, versor dest); diff --git a/include/cglm/call/vec4.h b/include/cglm/call/vec4.h index 28a3734..f56f599 100644 --- a/include/cglm/call/vec4.h +++ b/include/cglm/call/vec4.h @@ -99,7 +99,7 @@ glmc_vec4_scale(vec4 v, float s, vec4 dest); CGLM_EXPORT void -glmc_vec4_scale_as(vec3 v, float s, vec3 dest); +glmc_vec4_scale_as(vec4 v, float s, vec4 dest); CGLM_EXPORT void diff --git a/include/cglm/quat.h b/include/cglm/quat.h index 8560ec8..ece4a78 100644 --- a/include/cglm/quat.h +++ b/include/cglm/quat.h @@ -38,6 +38,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_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); CGLM_INLINE void glm_quat_forp(vec3 from, @@ -628,6 +629,26 @@ glm_quat_lerpc(versor from, versor to, float t, versor dest) { glm_vec4_lerpc(from, to, t, dest); } +/*! + * @brief interpolates between two quaternions + * taking the shortest rotation path using + * normalized linear interpolation (NLERP) + * + * @param[in] from from + * @param[in] to to + * @param[in] t interpolant (amount) + * @param[out] dest result quaternion + */ +CGLM_INLINE +void +glm_quat_nlerp(versor from, versor to, float t, versor dest) { + float dot = glm_vec4_dot(from, to); + versor target; + glm_vec4_scale(to, (dot >= 0) ? 1 : -1, target); + glm_quat_lerp(from, target, t, dest); + glm_quat_normalize(dest); +} + /*! * @brief interpolates between two quaternions * using spherical linear interpolation (SLERP) diff --git a/include/cglm/struct/quat.h b/include/cglm/struct/quat.h index 8b3774a..aefddd6 100644 --- a/include/cglm/struct/quat.h +++ b/include/cglm/struct/quat.h @@ -34,6 +34,7 @@ CGLM_INLINE mat3s glms_quat_mat3t(versors q) CGLM_INLINE versors glms_quat_lerp(versors from, versors to, float t) 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 mat4s. glms_quat_look(vec3s eye, versors ori) CGLM_INLINE versors glms_quat_for(vec3s dir, vec3s fwd, vec3s up) @@ -401,6 +402,24 @@ glms_quat_lerpc(versors from, versors to, float t) { return dest; } +/*! + * @brief interpolates between two quaternions + * taking the shortest rotation path using + * normalized linear interpolation (NLERP) + * + * @param[in] from from + * @param[in] to to + * @param[in] t interpolant (amount) + * @param[out] dest result quaternion + */ +CGLM_INLINE +versors +glms_quat_nlerp(versors from, versors to, float t) { + versors dest; + glm_quat_nlerp(from.raw, to.raw, t, dest.raw); + return dest; +} + /*! * @brief interpolates between two quaternions * using spherical linear interpolation (SLERP) diff --git a/src/quat.c b/src/quat.c index f992f7c..716bb88 100644 --- a/src/quat.c +++ b/src/quat.c @@ -170,6 +170,12 @@ glmc_quat_lerpc(versor from, versor to, float t, versor dest) { glm_quat_lerpc(from, to, t, dest); } +CGLM_EXPORT +void +glmc_quat_nlerp(versor from, versor to, float t, versor dest) { + glm_quat_nlerp(from, to, t, dest); +} + CGLM_EXPORT void glmc_quat_slerp(versor from, versor to, float t, versor dest) { diff --git a/test/src/test_quat.h b/test/src/test_quat.h index 0ef7ca7..ee9e7dc 100644 --- a/test/src/test_quat.h +++ b/test/src/test_quat.h @@ -708,6 +708,38 @@ TEST_IMPL(GLM_PREFIX, quat_lerpc) { TEST_SUCCESS } +TEST_IMPL(GLM_PREFIX, quat_nlerp) { + versor q1, q2, q3, q4; + vec3 v1 = {10.0f, 0.0f, 0.0f}, v2; + + glm_quatv(q1, glm_rad(30.0f), v1); + glm_quatv(q2, glm_rad(90.0f), v1); + + GLM(quat_nlerp)(q1, q2, 1.0f, q3); + glm_quat_normalize(q2); + ASSERTIFY(test_assert_quat_eq(q2, q3)); + + glm_quatv(q1, glm_rad(30.001f), v1); + glm_quatv(q2, glm_rad(30.002f), v1); + GLM(quat_nlerp)(q1, q2, 0.7f, q3); + glm_quat_lerp(q1, q2, 0.7f, q4); + ASSERTIFY(test_assert_quat_eq(q3, q4)); + + glm_quatv(q1, glm_rad(30.0f), v1); + glm_quatv(q2, glm_rad(90.0f), v1); + GLM(quat_nlerp)(q1, q2, 0.5f, q3); + + glm_quat_axis(q3, v2); + glm_vec3_normalize(v1); + glm_vec3_normalize(v2); + + ASSERT(glm_quat_angle(q3) > glm_rad(30.0f)); + ASSERT(glm_quat_angle(q3) < glm_rad(90.0f)); + ASSERTIFY(test_assert_vec3_eq(v1, v2)) + + TEST_SUCCESS +} + TEST_IMPL(GLM_PREFIX, quat_slerp) { versor q1, q2, q3, q4; vec3 v1 = {10.0f, 0.0f, 0.0f}, v2; diff --git a/test/tests.h b/test/tests.h index 2fe9fc6..be8d5d1 100644 --- a/test/tests.h +++ b/test/tests.h @@ -284,6 +284,7 @@ TEST_DECLARE(glm_quat_mat3) TEST_DECLARE(glm_quat_mat3t) TEST_DECLARE(glm_quat_lerp) TEST_DECLARE(glm_quat_lerpc) +TEST_DECLARE(glm_quat_nlerp) TEST_DECLARE(glm_quat_slerp) TEST_DECLARE(glm_quat_look) TEST_DECLARE(glm_quat_for) @@ -320,6 +321,7 @@ TEST_DECLARE(glmc_quat_mat3) TEST_DECLARE(glmc_quat_mat3t) TEST_DECLARE(glmc_quat_lerp) TEST_DECLARE(glmc_quat_lerpc) +TEST_DECLARE(glmc_quat_nlerp) TEST_DECLARE(glmc_quat_slerp) TEST_DECLARE(glmc_quat_look) TEST_DECLARE(glmc_quat_for) @@ -1006,6 +1008,7 @@ TEST_LIST { TEST_ENTRY(glm_quat_mat3t) TEST_ENTRY(glm_quat_lerp) TEST_ENTRY(glm_quat_lerpc) + TEST_ENTRY(glm_quat_nlerp) TEST_ENTRY(glm_quat_slerp) TEST_ENTRY(glm_quat_look) TEST_ENTRY(glm_quat_for) @@ -1042,6 +1045,7 @@ TEST_LIST { TEST_ENTRY(glmc_quat_mat3t) TEST_ENTRY(glmc_quat_lerp) TEST_ENTRY(glmc_quat_lerpc) + TEST_ENTRY(glmc_quat_nlerp) TEST_ENTRY(glmc_quat_slerp) TEST_ENTRY(glmc_quat_look) TEST_ENTRY(glmc_quat_for)