1 /**
2     inmath.lerpolate
3 
4     Authors: David Herberth, Inochi2D Project
5     License: MIT
6 */
7 module inmath.interpolate;
8 
9 private {
10     import inmath.linalg : Vector, dot, vec2, vec3, vec4, quat;
11     import inmath.util : isVector, isQuaternion;
12     import inmath.math : almostEqual, acos, sin, sqrt, clamp, PI;
13     import std.conv : to;
14 }
15 
16 @safe pure nothrow:
17 
18 /// lerpolates linear between two points, also known as lerp.
19 T lerp(T)(T a, T b, float t) {
20     return a * (1 - t) + b * t;
21 }
22 
23 /// lerpolates spherical between to vectors or quaternions, also known as slerp.
24 T slerp(T)(T a, T b, float t) if(isVector!T || isQuaternion!T) {
25     static if(isVector!T) {
26         real theta = acos(dot(a, b));
27     } else {
28         real theta = acos(
29             // this is a workaround, acos returning -nan on certain values near +/-1
30             clamp(a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z, -1, 1)
31         );
32     }
33     
34     if(almostEqual(theta, 0)) {
35         return a;
36     } else if(almostEqual(theta, PI)) { // 180°?
37         return lerp(a, b, t);
38     } else { // slerp
39         real sintheta = sin(theta);
40         return (sin((1.0-t)*theta)/sintheta)*a + (sin(t*theta)/sintheta)*b;
41     }
42 }
43 
44 
45 /// Normalized quaternion linear lerpolation.
46 quat nlerp(quat a, quat b, float t) {
47     // TODO: tests
48     float dot = a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
49 
50     quat result;
51     if(dot < 0) { // Determine the "shortest route"...
52         result = a - (b + a) * t; // use -b instead of b
53     } else {
54         result = a + (b - a) * t;
55     }
56     result.normalize();
57 
58     return result;
59 }
60 
61 @("vector lerp, slerp")
62 unittest {
63     vec2 v2_1 = vec2(1.0f);
64     vec2 v2_2 = vec2(0.0f);
65     vec3 v3_1 = vec3(1.0f);
66     vec3 v3_2 = vec3(0.0f);
67     vec4 v4_1 = vec4(1.0f);
68     vec4 v4_2 = vec4(0.0f);
69     
70     assert(lerp(v2_1, v2_2, 0.5f).vector == [0.5f, 0.5f]);
71     assert(lerp(v2_1, v2_2, 0.0f) == v2_1);
72     assert(lerp(v2_1, v2_2, 1.0f) == v2_2);
73     assert(lerp(v3_1, v3_2, 0.5f).vector == [0.5f, 0.5f, 0.5f]);
74     assert(lerp(v3_1, v3_2, 0.0f) == v3_1);
75     assert(lerp(v3_1, v3_2, 1.0f) == v3_2);
76     assert(lerp(v4_1, v4_2, 0.5f).vector == [0.5f, 0.5f, 0.5f, 0.5f]);
77     assert(lerp(v4_1, v4_2, 0.0f) == v4_1);
78     assert(lerp(v4_1, v4_2, 1.0f) == v4_2);
79     
80     assert(slerp(v2_1, v2_2, 0.0).vector == v2_1.vector);
81     assert(slerp(v2_1, v2_2, 1.0).vector == v2_2.vector);
82     assert(slerp(v3_1, v3_2, 0.0).vector == v3_1.vector);
83     assert(slerp(v3_1, v3_2, 1.0).vector == v3_2.vector);
84     assert(slerp(v4_1, v4_2, 0.0).vector == v4_1.vector);
85     assert(slerp(v4_1, v4_2, 1.0).vector == v4_2.vector);
86 }
87 
88 @("real lerp")
89 unittest {
90     real r1 = 0.0;
91     real r2 = 1.0;
92     assert(lerp(r1, r2, 0.5f) == 0.5);
93     assert(lerp(r1, r2, 0.0f) == r1);
94     assert(lerp(r1, r2, 1.0f) == r2);
95     
96     assert(lerp(0.0, 1.0, 0.5f) == 0.5);
97     assert(lerp(0.0, 1.0, 0.0f) == 0.0);
98     assert(lerp(0.0, 1.0, 1.0f) == 1.0);
99     
100     assert(lerp(0.0f, 1.0f, 0.5f) == 0.5f);
101     assert(lerp(0.0f, 1.0f, 0.0f) == 0.0f);
102     assert(lerp(0.0f, 1.0f, 1.0f) == 1.0f);
103 }
104 
105 @("quat lerp")
106 unittest {
107     quat q1 = quat(1.0f, 1.0f, 1.0f, 1.0f);
108     quat q2 = quat(0.0f, 0.0f, 0.0f, 0.0f);
109 
110     assert(lerp(q1, q2, 0.0f).quaternion == q1.quaternion);
111     assert(lerp(q1, q2, 0.5f).quaternion == [0.5f, 0.5f, 0.5f, 0.5f]);
112     assert(lerp(q1, q2, 1.0f).quaternion == q2.quaternion);
113     
114     assert(slerp(q1, q2, 0.0f).quaternion == q1.quaternion);
115     assert(slerp(q1, q2, 1.0f).quaternion == q2.quaternion);
116 }
117 
118 /// Nearest lerpolation of two points.
119 T nearest(T)(T x, T y, float t) {
120     if(t < 0.5f) { return x; }
121     else { return y; } 
122 }
123 
124 @("nearest lerp")
125 unittest {
126     assert(nearest(0.0, 1.0, 0.5f) == 1.0);
127     assert(nearest(0.0, 1.0, 0.4f) == 0.0);
128     assert(nearest(0.0, 1.0, 0.6f) == 1.0);
129 }
130 
131 /// Catmull-rom lerpolation between four points.
132 T catmullrom(T)(T p0, T p1, T p2, T p3, float t) {
133     return 0.5f * ((2 * p1) + 
134                    (-p0 + p2) * t +
135                    (2 * p0 - 5 * p1 + 4 * p2 - p3) * t^^2 +
136                    (-p0 + 3 * p1 - 3 * p2 + p3) * t^^3);
137 }
138 
139 /// Catmull-derivatives of the lerpolation between four points.
140 T dcatmullrom(T)(T p0, T p1, T p2, T p3, float t) {
141     return 0.5f * ((2 * p1) +
142                    (-p0 + p2) +
143                    2 * (2 * p0 - 5 * p1 + 4 * p2 - p3) * t +
144                    3 * (-p0 + 3 * p1 - 3 * p2 + p3) * t^^2);
145 }
146 
147 /// Hermite lerpolation (cubic hermite spline).
148 T hermite(T)(T x, T tx, T y, T ty, float t) {
149     float h1 = 2 * t^^3 - 3 * t^^2 + 1;
150     float h2 = -2* t^^3 + 3 * t^^2;
151     float h3 = t^^3 - 2 * t^^2 + t;
152     float h4 = t^^3 - t^^2;
153     return h1 * x + h3 * tx + h2 * y + h4 * ty;
154 }