diff options
| author | 3gg <3gg@shellblade.net> | 2023-02-04 11:07:21 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2023-02-04 11:07:21 -0800 |
| commit | fc175dbb72f80764431c1fe82ab8996b114831da (patch) | |
| tree | 306c54ac2f43e00b56161199de6caf3cb244fe13 | |
| parent | 9f39e29b40ce952773ff97c468384475d1fdcc53 (diff) | |
Add slerp and qslerp.
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | include/math/defs.h | 17 | ||||
| -rw-r--r-- | include/math/quat.h | 87 | ||||
| -rw-r--r-- | include/math/spatial3.h | 2 | ||||
| -rw-r--r-- | include/math/vec3.h | 44 | ||||
| -rw-r--r-- | test/quat_test.c | 85 | ||||
| -rw-r--r-- | test/vec3_test.c | 68 |
7 files changed, 292 insertions, 15 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a3e4638..916dbfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -15,7 +15,9 @@ target_link_libraries(math INTERFACE | |||
| 15 | # Test. | 15 | # Test. |
| 16 | 16 | ||
| 17 | add_executable(math_test | 17 | add_executable(math_test |
| 18 | test/mat4_test.c) | 18 | test/mat4_test.c |
| 19 | test/quat_test.c | ||
| 20 | test/vec3_test.c) | ||
| 19 | 21 | ||
| 20 | target_link_libraries(math_test | 22 | target_link_libraries(math_test |
| 21 | math) | 23 | math) |
diff --git a/include/math/defs.h b/include/math/defs.h index f67ee97..f572d8f 100644 --- a/include/math/defs.h +++ b/include/math/defs.h | |||
| @@ -14,7 +14,7 @@ typedef double R; | |||
| 14 | #define R_MIN DBL_MIN | 14 | #define R_MIN DBL_MIN |
| 15 | #define R_MAX DBL_MAX | 15 | #define R_MAX DBL_MAX |
| 16 | #else // floats | 16 | #else // floats |
| 17 | typedef float R; | 17 | typedef float R; |
| 18 | #define R_MIN FLT_MIN | 18 | #define R_MIN FLT_MIN |
| 19 | #define R_MAX FLT_MAX | 19 | #define R_MAX FLT_MAX |
| 20 | #endif | 20 | #endif |
| @@ -29,13 +29,15 @@ typedef float R; | |||
| 29 | #define TO_DEG (180.0 / PI) | 29 | #define TO_DEG (180.0 / PI) |
| 30 | 30 | ||
| 31 | #ifdef MATH_USE_DOUBLE | 31 | #ifdef MATH_USE_DOUBLE |
| 32 | static inline double min(R a, R b) { return fmin(a, b); } | 32 | static inline R min(R a, R b) { return fmin(a, b); } |
| 33 | static inline double max(R a, R b) { return fmax(a, b); } | 33 | static inline R max(R a, R b) { return fmax(a, b); } |
| 34 | static inline double rlog2(R a) { return log2(a); } | 34 | static inline R rlog2(R a) { return log2(a); } |
| 35 | static inline R rmod(R a, R m) { return fmodf(a, m); } | ||
| 35 | #else // floats | 36 | #else // floats |
| 36 | static inline float min(R a, R b) { return fminf(a, b); } | 37 | static inline R min(R a, R b) { return fminf(a, b); } |
| 37 | static inline float max(R a, R b) { return fmaxf(a, b); } | 38 | static inline R max(R a, R b) { return fmaxf(a, b); } |
| 38 | static inline float rlog2(R a) { return log2f(a); } | 39 | static inline R rlog2(R a) { return log2f(a); } |
| 40 | static inline R rmod(R a, R m) { return fmod(a, m); } | ||
| 39 | #endif | 41 | #endif |
| 40 | 42 | ||
| 41 | static inline R rabs(R x) { return x >= 0.0 ? x : -x; } | 43 | static inline R rabs(R x) { return x >= 0.0 ? x : -x; } |
| @@ -51,3 +53,4 @@ static inline R sign(R x) { | |||
| 51 | } | 53 | } |
| 52 | } | 54 | } |
| 53 | static inline R lerp(R a, R b, R t) { return a + (b - a) * t; } | 55 | static inline R lerp(R a, R b, R t) { return a + (b - a) * t; } |
| 56 | static inline R R_eq(R a, R b, R eps) { return rabs(a - b) <= eps; } | ||
diff --git a/include/math/quat.h b/include/math/quat.h index b284567..a154044 100644 --- a/include/math/quat.h +++ b/include/math/quat.h | |||
| @@ -4,23 +4,38 @@ | |||
| 4 | #include "mat4.h" | 4 | #include "mat4.h" |
| 5 | #include "vec3.h" | 5 | #include "vec3.h" |
| 6 | 6 | ||
| 7 | #include <assert.h> | ||
| 8 | |||
| 7 | /// A quaternion. | 9 | /// A quaternion. |
| 8 | typedef struct quat { | 10 | typedef struct quat { |
| 9 | R w, x, y, z; | 11 | R x, y, z, w; |
| 10 | } quat; | 12 | } quat; |
| 11 | 13 | ||
| 14 | /// Return the unit/identity quaternion. | ||
| 12 | static inline quat qunit() { | 15 | static inline quat qunit() { |
| 13 | return (quat){.x = 0.0, .y = 0.0, .z = 0.0, .w = 0.0}; | 16 | return (quat){.x = 0.0, .y = 0.0, .z = 0.0, .w = 1.0}; |
| 14 | } | 17 | } |
| 15 | 18 | ||
| 19 | /// Construct a quaternion. | ||
| 16 | static inline quat qmake(R x, R y, R z, R w) { | 20 | static inline quat qmake(R x, R y, R z, R w) { |
| 17 | return (quat){.x = x, .y = y, .z = z, .w = w}; | 21 | return (quat){.x = x, .y = y, .z = z, .w = w}; |
| 18 | } | 22 | } |
| 19 | 23 | ||
| 24 | /// Construct a quaternion from an array. | ||
| 20 | static inline quat quat_from_array(const R xyzw[4]) { | 25 | static inline quat quat_from_array(const R xyzw[4]) { |
| 21 | return (quat){.x = xyzw[0], .y = xyzw[1], .z = xyzw[2], .w = xyzw[3]}; | 26 | return (quat){.x = xyzw[0], .y = xyzw[1], .z = xyzw[2], .w = xyzw[3]}; |
| 22 | } | 27 | } |
| 23 | 28 | ||
| 29 | /// Construct a quaternion from a 3D point. | ||
| 30 | static inline quat quat_from_vec3(vec3 p) { | ||
| 31 | return (quat){.x = p.x, .y = p.y, .z = p.z, .w = 0.0}; | ||
| 32 | } | ||
| 33 | |||
| 34 | /// Get a 3D point back from a quaternion. | ||
| 35 | static inline vec3 vec3_from_quat(quat q) { | ||
| 36 | return (vec3){.x = q.x, .y = q.y, .z = q.z}; | ||
| 37 | } | ||
| 38 | |||
| 24 | /// Construct a rotation quaternion. | 39 | /// Construct a rotation quaternion. |
| 25 | static inline quat qmake_rot(R angle, R x, R y, R z) { | 40 | static inline quat qmake_rot(R angle, R x, R y, R z) { |
| 26 | const R a = angle * 0.5; | 41 | const R a = angle * 0.5; |
| @@ -34,6 +49,11 @@ static inline quat qmake_rot(R angle, R x, R y, R z) { | |||
| 34 | return (quat){x / mag, y / mag, z / mag, w}; | 49 | return (quat){x / mag, y / mag, z / mag, w}; |
| 35 | } | 50 | } |
| 36 | 51 | ||
| 52 | /// Add two quaternions. | ||
| 53 | static inline quat qadd(quat a, quat b) { | ||
| 54 | return (quat){.x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z, .w = a.w + b.w}; | ||
| 55 | } | ||
| 56 | |||
| 37 | /// Multiply two quaternions. | 57 | /// Multiply two quaternions. |
| 38 | static inline quat qmul(quat q1, quat q2) { | 58 | static inline quat qmul(quat q1, quat q2) { |
| 39 | const R x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; | 59 | const R x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; |
| @@ -61,6 +81,37 @@ static inline vec3 qrot(quat q, vec3 v) { | |||
| 61 | return vec3_make(u.x, u.y, u.z); | 81 | return vec3_make(u.x, u.y, u.z); |
| 62 | } | 82 | } |
| 63 | 83 | ||
| 84 | /// Negate the quaternion. | ||
| 85 | static inline quat qneg(quat q) { | ||
| 86 | return (quat){.x = -q.x, .y = -q.y, .z = -q.z, .w = -q.w}; | ||
| 87 | } | ||
| 88 | |||
| 89 | /// Return the dot product of two quaternions. | ||
| 90 | static inline R qdot(quat a, quat b) { | ||
| 91 | return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; | ||
| 92 | } | ||
| 93 | |||
| 94 | /// Return the quaternion's magnitude. | ||
| 95 | static inline R qnorm(quat q) { | ||
| 96 | return sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Return the quaternion's squared magnitude. | ||
| 100 | static inline R qnorm2(quat q) { | ||
| 101 | return q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w; | ||
| 102 | } | ||
| 103 | |||
| 104 | /// Normalize the quaternion. | ||
| 105 | static inline quat qnormalize(quat q) { | ||
| 106 | const R n = qnorm(q); | ||
| 107 | return (quat){.x = q.x / n, .y = q.y / n, .z = q.z / n, .w = q.w / n}; | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Scale the quaternion. | ||
| 111 | static inline quat qscale(quat q, R s) { | ||
| 112 | return (quat){.x = s * q.x, .y = s * q.y, .z = s * q.z, .w = s * q.w}; | ||
| 113 | } | ||
| 114 | |||
| 64 | /// Get a 4x4 rotation matrix from a quaternion. | 115 | /// Get a 4x4 rotation matrix from a quaternion. |
| 65 | static inline mat4 mat4_from_quat(quat q) { | 116 | static inline mat4 mat4_from_quat(quat q) { |
| 66 | const R xx = q.x * q.x; | 117 | const R xx = q.x * q.x; |
| @@ -77,3 +128,35 @@ static inline mat4 mat4_from_quat(quat q) { | |||
| 77 | 1 - 2 * xx - 2 * zz, 2 * yz - 2 * wx, 0, 2 * xz - 2 * wy, 2 * yz + 2 * wx, | 128 | 1 - 2 * xx - 2 * zz, 2 * yz - 2 * wx, 0, 2 * xz - 2 * wy, 2 * yz + 2 * wx, |
| 78 | 1 - 2 * xx - 2 * yy, 0, 0, 0, 0, 1); | 129 | 1 - 2 * xx - 2 * yy, 0, 0, 0, 0, 1); |
| 79 | } | 130 | } |
| 131 | |||
| 132 | /// Interpolate two unit quaternions using spherical linear interpolation. | ||
| 133 | /// | ||
| 134 | /// Note: You might want to normalize the result. | ||
| 135 | static inline quat qslerp(quat a, quat b, R t) { | ||
| 136 | assert(0.0 <= t); | ||
| 137 | assert(t <= 1.0); | ||
| 138 | const R eps = 1e-5; | ||
| 139 | (void)eps; | ||
| 140 | assert(R_eq(qnorm2(a), 1.0, eps)); | ||
| 141 | assert(R_eq(qnorm2(b), 1.0, eps)); | ||
| 142 | R dot = qdot(a, b); | ||
| 143 | // Make the rotation path follow the "short way", i.e., ensure that: | ||
| 144 | // -90 <= angle <= 90 | ||
| 145 | if (dot < 0.0) { | ||
| 146 | dot = -dot; | ||
| 147 | b = qneg(b); | ||
| 148 | } | ||
| 149 | // For numerical stability, perform linear interpolation when the two | ||
| 150 | // quaternions are close to each other. | ||
| 151 | R ta, tb; | ||
| 152 | if (1.0 - dot > 1e-6) { | ||
| 153 | const R theta = acos(dot); | ||
| 154 | const R sin_theta = sqrt(1 - dot * dot); | ||
| 155 | ta = sin(theta * (1.0 - t)) / sin_theta; | ||
| 156 | tb = sin(theta * t) / sin_theta; | ||
| 157 | } else { // Linear interpolation. | ||
| 158 | ta = 1.0 - t; | ||
| 159 | tb = t; | ||
| 160 | } | ||
| 161 | return qadd(qscale(a, ta), qscale(b, tb)); | ||
| 162 | } | ||
diff --git a/include/math/spatial3.h b/include/math/spatial3.h index 8de38bf..9065972 100644 --- a/include/math/spatial3.h +++ b/include/math/spatial3.h | |||
| @@ -118,7 +118,7 @@ static inline void spatial3_set_transform(Spatial3* spatial, mat4 transform) { | |||
| 118 | static inline void spatial3_set_forward(Spatial3* spatial, vec3 forward) { | 118 | static inline void spatial3_set_forward(Spatial3* spatial, vec3 forward) { |
| 119 | spatial->f = vec3_normalize(forward); | 119 | spatial->f = vec3_normalize(forward); |
| 120 | // Use aux vector to define right vector orthogonal to forward. | 120 | // Use aux vector to define right vector orthogonal to forward. |
| 121 | if (vec3_eq(vec3_abs(spatial->f), up3())) { | 121 | if (vec3_eq(vec3_abs(spatial->f), up3(), 1e-9)) { |
| 122 | spatial->r = vec3_normalize(vec3_cross(spatial->f, forward3())); | 122 | spatial->r = vec3_normalize(vec3_cross(spatial->f, forward3())); |
| 123 | } else { | 123 | } else { |
| 124 | spatial->r = vec3_normalize(vec3_cross(spatial->f, up3())); | 124 | spatial->r = vec3_normalize(vec3_cross(spatial->f, up3())); |
diff --git a/include/math/vec3.h b/include/math/vec3.h index 641c02f..d8e1248 100644 --- a/include/math/vec3.h +++ b/include/math/vec3.h | |||
| @@ -51,14 +51,14 @@ static inline vec3 vec3_div(vec3 a, vec3 b) { | |||
| 51 | return (vec3){a.x / b.x, a.y / b.y, a.z / b.z}; | 51 | return (vec3){a.x / b.x, a.y / b.y, a.z / b.z}; |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | /// Scale a vector by a scalar value. | 54 | /// Scale the vector. |
| 55 | static inline vec3 vec3_scale(vec3 v, R s) { | 55 | static inline vec3 vec3_scale(vec3 v, R s) { |
| 56 | return (vec3){v.x * s, v.y * s, v.z * s}; | 56 | return (vec3){v.x * s, v.y * s, v.z * s}; |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | /// Compare two vectors for equality. | 59 | /// Compare two vectors for equality. |
| 60 | static inline bool vec3_eq(vec3 a, vec3 b) { | 60 | static inline bool vec3_eq(vec3 a, vec3 b, R eps) { |
| 61 | return a.x == b.x && a.y == b.y && a.z == b.z; | 61 | return R_eq(a.x, b.x, eps) && R_eq(a.y, b.y, eps) && R_eq(a.z, b.z, eps); |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | /// Return the absolute value of the vector. | 64 | /// Return the absolute value of the vector. |
| @@ -67,7 +67,9 @@ static inline vec3 vec3_abs(vec3 v) { | |||
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | /// Compare two vectors for inequality. | 69 | /// Compare two vectors for inequality. |
| 70 | static inline bool vec3_ne(vec3 a, vec3 b) { return !(vec3_eq(a, b)); } | 70 | static inline bool vec3_ne(vec3 a, vec3 b, R eps) { |
| 71 | return !(vec3_eq(a, b, eps)); | ||
| 72 | } | ||
| 71 | 73 | ||
| 72 | /// Return the vector's squared magnitude. | 74 | /// Return the vector's squared magnitude. |
| 73 | static inline R vec3_norm2(vec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } | 75 | static inline R vec3_norm2(vec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } |
| @@ -123,6 +125,40 @@ static inline vec3 vec3_pow(vec3 v, R p) { | |||
| 123 | return (vec3){pow(v.x, p), pow(v.y, p), pow(v.z, p)}; | 125 | return (vec3){pow(v.x, p), pow(v.y, p), pow(v.z, p)}; |
| 124 | } | 126 | } |
| 125 | 127 | ||
| 128 | /// Linearly interpolate two vectors. | ||
| 129 | static inline vec3 vec3_lerp(vec3 a, vec3 b, R t) { | ||
| 130 | return (vec3){ | ||
| 131 | .x = a.x + t * (b.x - a.x), | ||
| 132 | .y = a.y + t * (b.y - a.y), | ||
| 133 | .z = a.z + t * (b.z - a.z)}; | ||
| 134 | } | ||
| 135 | |||
| 136 | /// Interpolate two unit vectors using spherical linear interpolation. | ||
| 137 | /// | ||
| 138 | /// Note: You might want to normalize the result. | ||
| 139 | static inline vec3 vec3_slerp(vec3 a, vec3 b, R t) { | ||
| 140 | assert(0.0 <= t); | ||
| 141 | assert(t <= 1.0); | ||
| 142 | const R eps = 1e-5; | ||
| 143 | (void)eps; | ||
| 144 | assert(R_eq(vec3_norm2(a), 1.0, eps)); | ||
| 145 | assert(R_eq(vec3_norm2(b), 1.0, eps)); | ||
| 146 | R dot = vec3_dot(a, b); | ||
| 147 | // For numerical stability, perform linear interpolation when the two vectors | ||
| 148 | // are close to each other. | ||
| 149 | R ta, tb; | ||
| 150 | if (1.0 - dot > 1e-6) { | ||
| 151 | const R theta = acos(dot); | ||
| 152 | const R sin_theta = sqrt(1.0 - dot * dot); | ||
| 153 | ta = sin((1.0 - t) * theta) / sin_theta; | ||
| 154 | tb = sin(t * theta) / sin_theta; | ||
| 155 | } else { // Linear interpolation. | ||
| 156 | ta = 1.0 - t; | ||
| 157 | tb = t; | ||
| 158 | } | ||
| 159 | return vec3_add(vec3_scale(a, ta), vec3_scale(b, tb)); | ||
| 160 | } | ||
| 161 | |||
| 126 | /// The (1, 0, 0) vector. | 162 | /// The (1, 0, 0) vector. |
| 127 | static inline vec3 right3() { return (vec3){1.0, 0.0, 0.0}; } | 163 | static inline vec3 right3() { return (vec3){1.0, 0.0, 0.0}; } |
| 128 | 164 | ||
diff --git a/test/quat_test.c b/test/quat_test.c new file mode 100644 index 0000000..83519c3 --- /dev/null +++ b/test/quat_test.c | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | #include <math/quat.h> | ||
| 2 | |||
| 3 | #include <math/float.h> | ||
| 4 | |||
| 5 | #include "test.h" | ||
| 6 | |||
| 7 | #include <stdio.h> | ||
| 8 | |||
| 9 | static const float eps = 1e-7; | ||
| 10 | |||
| 11 | static inline void print_quat(quat q) { | ||
| 12 | printf("{ %f, %f, %f, %f }\n", q.x, q.y, q.z, q.w); | ||
| 13 | } | ||
| 14 | |||
| 15 | static inline void print_vec3(vec3 v) { | ||
| 16 | printf("{ %f, %f, %f }\n", v.x, v.y, v.z); | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Slerp between two vectors forming an acute angle. | ||
| 20 | TEST_CASE(quat_slerp_acute_angle) { | ||
| 21 | const R angle1 = 0; | ||
| 22 | const R angle2 = PI / 4; | ||
| 23 | const R t = 0.5; | ||
| 24 | |||
| 25 | const quat a = qmake_rot(angle1, 0, 0, 1); | ||
| 26 | const quat b = qmake_rot(angle2, 0, 0, 1); | ||
| 27 | |||
| 28 | const quat c = qslerp(a, b, t); | ||
| 29 | const vec3 result = qrot(c, vec3_make(1, 0, 0)); | ||
| 30 | |||
| 31 | const R angle3 = lerp(angle1, angle2, t); | ||
| 32 | const vec3 expected = vec3_make(cos(angle3), sin(angle3), 0.0); | ||
| 33 | TEST_TRUE(vec3_eq(result, expected, eps)); | ||
| 34 | } | ||
| 35 | |||
| 36 | /// Slerp between two vectors forming an obtuse angle (negative dot product). | ||
| 37 | /// | ||
| 38 | /// The interpolation must follow the shortest path between both vectors. | ||
| 39 | TEST_CASE(quat_slerp_obtuse_angle) { | ||
| 40 | const R angle1 = 0; | ||
| 41 | const R angle2 = 3 * PI / 4; | ||
| 42 | const R t = 0.5; | ||
| 43 | |||
| 44 | const quat a = qmake_rot(angle1, 0, 0, 1); | ||
| 45 | const quat b = qmake_rot(angle2, 0, 0, 1); | ||
| 46 | |||
| 47 | const quat c = qslerp(a, b, t); | ||
| 48 | const vec3 result = qrot(c, vec3_make(1, 0, 0)); | ||
| 49 | |||
| 50 | const R angle3 = lerp(angle1, angle2, t); | ||
| 51 | const vec3 expected = vec3_make(cos(angle3), sin(angle3), 0.0); | ||
| 52 | TEST_TRUE(vec3_eq(result, expected, eps)); | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Slerp between two vectors forming a reflex angle. | ||
| 56 | /// | ||
| 57 | /// The interpolation must follow the shortest path between both vectors. | ||
| 58 | TEST_CASE(quat_slerp_reflex_angle) { | ||
| 59 | const R angle1 = 0; | ||
| 60 | const R angle2 = 5 * PI / 4; | ||
| 61 | const R t = 0.5; | ||
| 62 | |||
| 63 | const quat a = qmake_rot(angle1, 0, 0, 1); | ||
| 64 | const quat b = qmake_rot(angle2, 0, 0, 1); | ||
| 65 | |||
| 66 | const quat c = qslerp(a, b, t); | ||
| 67 | const vec3 result = qrot(c, vec3_make(1, 0, 0)); | ||
| 68 | |||
| 69 | // Because it's a reflex angle, we expect the rotation to follow the short | ||
| 70 | // path from 'a' down clockwise to 'b'. Could add +PI to the result of lerp(), | ||
| 71 | // but that adds more error than negating cos and sin. | ||
| 72 | const R angle3 = lerp(angle1, angle2, t); | ||
| 73 | const vec3 expected = vec3_make(-cos(angle3), -sin(angle3), 0.0); | ||
| 74 | TEST_TRUE(vec3_eq(result, expected, eps)); | ||
| 75 | } | ||
| 76 | |||
| 77 | TEST_CASE(quat_mat4_from_quat) { | ||
| 78 | const R angle = PI / 8; | ||
| 79 | const quat q = qmake_rot(angle, 0, 0, 1); | ||
| 80 | |||
| 81 | const mat4 m = mat4_from_quat(q); | ||
| 82 | const vec3 p = mat4_mul_vec3(m, vec3_make(1, 0, 0), /*w=*/1); | ||
| 83 | |||
| 84 | TEST_TRUE(vec3_eq(p, vec3_make(cos(angle), sin(angle), 0), eps)); | ||
| 85 | } | ||
diff --git a/test/vec3_test.c b/test/vec3_test.c new file mode 100644 index 0000000..886fee3 --- /dev/null +++ b/test/vec3_test.c | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | #include <math/vec3.h> | ||
| 2 | |||
| 3 | #include <math/float.h> | ||
| 4 | |||
| 5 | #include "test.h" | ||
| 6 | |||
| 7 | #include <stdio.h> | ||
| 8 | |||
| 9 | static const float eps = 1e-7; | ||
| 10 | |||
| 11 | static inline void print_vec3(vec3 v) { | ||
| 12 | printf("{ %f, %f, %f }\n", v.x, v.y, v.z); | ||
| 13 | } | ||
| 14 | |||
| 15 | /// Slerp between two vectors forming an acute angle. | ||
| 16 | TEST_CASE(vec3_slerp_acute_angle) { | ||
| 17 | const R angle1 = 0; | ||
| 18 | const R angle2 = PI / 4; | ||
| 19 | const R t = 0.5; | ||
| 20 | |||
| 21 | const vec3 a = vec3_make(cos(angle1), sin(angle1), 0); | ||
| 22 | const vec3 b = vec3_make(cos(angle2), sin(angle2), 0); | ||
| 23 | |||
| 24 | const vec3 result = vec3_slerp(a, b, t); | ||
| 25 | |||
| 26 | const R angle3 = lerp(angle1, angle2, t); | ||
| 27 | const vec3 expected = vec3_make(cos(angle3), sin(angle3), 0.0); | ||
| 28 | TEST_TRUE(vec3_eq(result, expected, eps)); | ||
| 29 | } | ||
| 30 | |||
| 31 | /// Slerp between two vectors forming an obtuse angle (negative dot product). | ||
| 32 | /// | ||
| 33 | /// The interpolation must follow the shortest path between both vectors. | ||
| 34 | TEST_CASE(vec3_slerp_obtuse_angle) { | ||
| 35 | const R angle1 = 0; | ||
| 36 | const R angle2 = 3 * PI / 4; | ||
| 37 | const R t = 0.5; | ||
| 38 | |||
| 39 | const vec3 a = vec3_make(cos(angle1), sin(angle1), 0); | ||
| 40 | const vec3 b = vec3_make(cos(angle2), sin(angle2), 0); | ||
| 41 | |||
| 42 | const vec3 result = vec3_slerp(a, b, t); | ||
| 43 | |||
| 44 | const R angle3 = lerp(angle1, angle2, t); | ||
| 45 | const vec3 expected = vec3_make(cos(angle3), sin(angle3), 0.0); | ||
| 46 | TEST_TRUE(vec3_eq(result, expected, eps)); | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Slerp between two vectors forming a reflex angle. | ||
| 50 | /// | ||
| 51 | /// The interpolation must follow the shortest path between both vectors. | ||
| 52 | TEST_CASE(vec3_slerp_reflex_angle) { | ||
| 53 | const R angle1 = 0; | ||
| 54 | const R angle2 = 5 * PI / 4; | ||
| 55 | const R t = 0.5; | ||
| 56 | |||
| 57 | const vec3 a = vec3_make(cos(angle1), sin(angle1), 0); | ||
| 58 | const vec3 b = vec3_make(cos(angle2), sin(angle2), 0); | ||
| 59 | |||
| 60 | const vec3 result = vec3_slerp(a, b, t); | ||
| 61 | |||
| 62 | // slerp goes from a to b following the shortest path, which is down from a | ||
| 63 | // towards b. The resulting angle is therefore +PI of the angle we get from | ||
| 64 | // lerping the two input angles. | ||
| 65 | const R angle3 = lerp(angle1, angle2, t) + PI; | ||
| 66 | const vec3 expected = vec3_make(cos(angle3), sin(angle3), 0.0); | ||
| 67 | TEST_TRUE(vec3_eq(result, expected, 1e-5)); | ||
| 68 | } | ||
