1 /**
2     inmath.math
3 
4     Provides nearly all GLSL functions, according to spec 4.1,
5     it also publically imports other useful functions (from std.math, core.stdc.math, std.alogrithm) 
6     so you only have to import this file to get all mathematical functions you need.
7 
8     Publically imports: PI, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, 
9     asinh, acosh, atanh, pow, exp, log, exp2, log2, sqrt, abs, floor, trunc, round, ceil, modf,
10     fmodf, min, max.
11 
12     Authors: David Herberth, Inochi2D Project
13     License: MIT
14 */
15 
16 module inmath.math;
17 
18 public {
19     import std.math : PI, sin, cos, tan, asin, acos, atan, atan2,
20                       sinh, cosh, tanh, asinh, acosh, atanh,
21                       pow, exp, log, exp2, log2, sqrt,
22                       floor, trunc, round, ceil, modf;
23     alias roundEven = round;
24     alias fract = floor;
25     //import core.stdc.math : fmodf;
26     import std.algorithm : min, max;
27 }
28 
29 private {
30     import std.conv : to;
31     import std.algorithm : all;
32     import std.range : zip;
33     import std.traits : CommonType, Unqual;
34     import std.range : ElementType;
35     import smath = std.math;
36     
37     import inmath.util : isVector, isQuaternion, isMatrix;
38 
39     version(unittest) {
40         import inmath.linalg : vec2, vec2i, vec3, vec3i, quat;
41     }
42 }
43 
44 /// PI / 180 at compiletime, used for degrees/radians conversion.
45 public enum real PI_180 = PI / 180;
46 /// 180 / PI at compiletime, used for degrees/radians conversion.
47 public enum real _180_PI = 180 / PI;
48 
49 /// Modulus. Returns x - y * floor(x/y).
50 T mod(T)(T x, T y) { // std.math.floor is not pure
51     return x - y * floor(x/y);
52 }
53 
54 @safe pure nothrow:
55 
56 extern (C) { float fmodf(float x, float y); }
57 
58 /// Calculates the absolute value.
59 T abs(T)(T t) if(!isVector!T && !isQuaternion!T && !isMatrix!T) {
60     return smath.abs(t);
61 }
62 
63 /// Calculates the absolute value per component.
64 T abs(T)(T vec) if(isVector!T) {
65     Unqual!T ret;
66 
67     foreach(i, element; vec.vector) {
68         ret.vector[i] = abs(element);
69     }
70     
71     return ret;
72 }
73 
74 /// ditto
75 T abs(T)(T quat) if(isQuaternion!T) {
76     Unqual!T ret;
77 
78     ret.quaternion[0] = abs(quat.quaternion[0]);
79     ret.quaternion[1] = abs(quat.quaternion[1]);
80     ret.quaternion[2] = abs(quat.quaternion[2]);
81     ret.quaternion[3] = abs(quat.quaternion[3]);
82 
83     return ret;
84 }
85 
86 unittest {
87     assert(abs(0) == 0);
88     assert(abs(-1) == 1);
89     assert(abs(1) == 1);
90     assert(abs(0.0) == 0.0);
91     assert(abs(-1.0) == 1.0);
92     assert(abs(1.0) == 1.0);
93     
94     assert(abs(vec3i(-1, 0, -12)) == vec3(1, 0, 12));
95     assert(abs(vec3(-1, 0, -12)) == vec3(1, 0, 12));
96     assert(abs(vec3i(12, 12, 12)) == vec3(12, 12, 12));
97 
98     assert(abs(quat(-1.0f, 0.0f, 1.0f, -12.0f)) == quat(1.0f, 0.0f, 1.0f, 12.0f));
99 }
100 
101 /// Returns 1/sqrt(x), results are undefined if x <= 0.
102 real inversesqrt(real x) {
103     return 1 / sqrt(x);
104 }
105 
106 /// Returns 1.0 if x > 0, 0.0 if x = 0, or -1.0 if x < 0.
107 float sign(T)(T x) {
108     if(x > 0) {
109         return 1.0f;
110     } else if(x == 0) {
111         return 0.0f;
112     } else { // if x < 0
113         return -1.0f;
114     }
115 }
116 
117 unittest {
118     assert(almostEqual(inversesqrt(1.0f), 1.0f));
119     assert(almostEqual(inversesqrt(10.0f), (1/sqrt(10.0f))));
120     assert(almostEqual(inversesqrt(2_342_342.0f), (1/sqrt(2_342_342.0f))));
121     
122     assert(sign(-1) == -1.0f);
123     assert(sign(0) == 0.0f);
124     assert(sign(1) == 1.0f);
125     assert(sign(0.5) == 1.0f);
126     assert(sign(-0.5) == -1.0f);
127     
128     assert(mod(12.0, 27.5) == 12.0);
129     assert(mod(-12.0, 27.5) == 15.5);
130     assert(mod(12.0, -27.5) == -15.5);
131 }
132 
133 /// Compares to values and returns true if the difference is epsilon or smaller.
134 bool almostEqual(T, S)(T a, S b, float epsilon = 0.000001f) if(!isVector!T && !isQuaternion!T) {
135     if(abs(a-b) <= epsilon) {
136         return true;
137     }
138     return abs(a-b) <= epsilon * abs(b);
139 }
140 
141 /// ditto
142 bool almostEqual(T, S)(T a, S b, float epsilon = 0.000001f) if(isVector!T && isVector!S && T.dimension == S.dimension) {
143     foreach(i; 0..T.dimension) {
144         if(!almostEqual(a.vector[i], b.vector[i], epsilon)) {
145             return false;
146         }
147     }
148     return true;
149 }
150 
151 bool almostEqual(T)(T a, T b, float epsilon = 0.000001f) if(isQuaternion!T) {
152     foreach(i; 0..4) {
153         if(!almostEqual(a.quaternion[i], b.quaternion[i], epsilon)) {
154             return false;
155         }
156     }
157     return true;
158 }
159 
160 unittest {
161     assert(almostEqual(0, 0));
162     assert(almostEqual(1, 1));
163     assert(almostEqual(-1, -1));    
164     assert(almostEqual(0f, 0.000001f, 0.000001f));
165     assert(almostEqual(1f, 1.1f, 0.1f));
166     assert(!almostEqual(1f, 1.1f, 0.01f));
167 
168     assert(almostEqual(vec2i(0, 0), vec2(0.0f, 0.0f)));
169     assert(almostEqual(vec2(0.0f, 0.0f), vec2(0.000001f, 0.000001f)));
170     assert(almostEqual(vec3(0.0f, 1.0f, 2.0f), vec3i(0, 1, 2)));
171 
172     assert(almostEqual(quat(0.0f, 0.0f, 0.0f, 0.0f), quat(0.0f, 0.0f, 0.0f, 0.0f)));
173     assert(almostEqual(quat(0.0f, 0.0f, 0.0f, 0.0f), quat(0.000001f, 0.000001f, 0.000001f, 0.000001f)));
174 }
175 
176 /// Converts degrees to radians.
177 real radians(real degrees) {
178     return PI_180 * degrees;
179 }
180 
181 /// Compiletime version of $(I radians).
182 real cradians(real degrees)() {
183     return radians(degrees);
184 }
185 
186 /// Converts radians to degrees.
187 real degrees(real radians) {
188     return _180_PI * radians;
189 }
190 
191 /// Compiletime version of $(I degrees).
192 real cdegrees(real radians)() {
193     return degrees(radians);
194 }
195 
196 unittest {
197     assert(almostEqual(radians(to!(real)(0)), 0));
198     assert(almostEqual(radians(to!(real)(90)), PI/2));
199     assert(almostEqual(radians(to!(real)(180)), PI));
200     assert(almostEqual(radians(to!(real)(360)), 2*PI));
201     
202     assert(almostEqual(degrees(to!(real)(0)), 0));
203     assert(almostEqual(degrees(to!(real)(PI/2)), 90));
204     assert(almostEqual(degrees(to!(real)(PI)), 180));
205     assert(almostEqual(degrees(to!(real)(2*PI)), 360));
206 
207     assert(almostEqual(degrees(radians(to!(real)(12))), 12));
208     assert(almostEqual(degrees(radians(to!(real)(100))), 100));
209     assert(almostEqual(degrees(radians(to!(real)(213))), 213));
210     assert(almostEqual(degrees(radians(to!(real)(399))), 399));
211     
212     /+static+/ assert(almostEqual(cdegrees!PI, 180));
213     /+static+/ assert(almostEqual(cradians!180, PI));
214 }
215 
216 /// Returns min(max(x, min_val), max_val), Results are undefined if min_val > max_val.
217 CommonType!(T1, T2, T3) clamp(T1, T2, T3)(T1 x, T2 min_val, T3 max_val) {
218     return min(max(x, min_val), max_val);
219 }
220 
221 unittest {
222     assert(clamp(-1, 0, 2) == 0);
223     assert(clamp(0, 0, 2) == 0);
224     assert(clamp(1, 0, 2) == 1);
225     assert(clamp(2, 0, 2) == 2);
226     assert(clamp(3, 0, 2) == 2);
227 }
228 
229 /// Returns 0.0 if x < edge, otherwise it returns 1.0.
230 float step(T1, T2)(T1 edge, T2 x) {
231     return x < edge ? 0.0f:1.0f;
232 }
233 
234 /// Returns 0.0 if x <= edge0 and 1.0 if x >= edge1 and performs smooth 
235 /// hermite interpolation between 0 and 1 when edge0 < x < edge1. 
236 /// This is useful in cases where you would want a threshold function with a smooth transition.
237 CommonType!(T1, T2, T3) smoothstep(T1, T2, T3)(T1 edge0, T2 edge1, T3 x) {
238     auto t = clamp((x - edge0) / (edge1 - edge0), 0, 1);
239     return t * t * (3 - 2 * t);
240 }
241 
242 unittest {
243     assert(step(0, 1) == 1.0f);
244     assert(step(0, 10) == 1.0f);
245     assert(step(1, 0) == 0.0f);
246     assert(step(10, 0) == 0.0f);
247     assert(step(1, 1) == 1.0f);
248     
249     assert(smoothstep(1, 0, 2) == 0);
250     assert(smoothstep(1.0, 0.0, 2.0) == 0);
251     assert(smoothstep(1.0, 0.0, 0.5) == 0.5);
252     assert(almostEqual(smoothstep(0.0, 2.0, 0.5), 0.15625, 0.00001));
253 }