1 /**
2 gl3n.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
13 License: MIT
14 */
15 
16 module gl3n.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 round roundEven;
24     alias floor fract;
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 gl3n.util : is_vector, is_quaternion, is_matrix;
38 
39     version(unittest) {
40         import gl3n.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(!is_vector!T && !is_quaternion!T && !is_matrix!T) {
60     return smath.abs(t);
61 }
62 
63 /// Calculates the absolute value per component.
64 T abs(T)(T vec) if(is_vector!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(is_quaternion!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(almost_equal(inversesqrt(1.0f), 1.0f));
119     assert(almost_equal(inversesqrt(10.0f), (1/sqrt(10.0f))));
120     assert(almost_equal(inversesqrt(2342342.0f), (1/sqrt(2342342.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 almost_equal(T, S)(T a, S b, float epsilon = 0.000001f) if(!is_vector!T && !is_quaternion!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 almost_equal(T, S)(T a, S b, float epsilon = 0.000001f) if(is_vector!T && is_vector!S && T.dimension == S.dimension) {
143     foreach(i; 0..T.dimension) {
144         if(!almost_equal(a.vector[i], b.vector[i], epsilon)) {
145             return false;
146         }
147     }
148     return true;
149 }
150 
151 bool almost_equal(T)(T a, T b, float epsilon = 0.000001f) if(is_quaternion!T) {
152     foreach(i; 0..4) {
153         if(!almost_equal(a.quaternion[i], b.quaternion[i], epsilon)) {
154             return false;
155         }
156     }
157     return true;
158 }
159 
160 unittest {
161     assert(almost_equal(0, 0));
162     assert(almost_equal(1, 1));
163     assert(almost_equal(-1, -1));    
164     assert(almost_equal(0f, 0.000001f, 0.000001f));
165     assert(almost_equal(1f, 1.1f, 0.1f));
166     assert(!almost_equal(1f, 1.1f, 0.01f));
167 
168     assert(almost_equal(vec2i(0, 0), vec2(0.0f, 0.0f)));
169     assert(almost_equal(vec2(0.0f, 0.0f), vec2(0.000001f, 0.000001f)));
170     assert(almost_equal(vec3(0.0f, 1.0f, 2.0f), vec3i(0, 1, 2)));
171 
172     assert(almost_equal(quat(0.0f, 0.0f, 0.0f, 0.0f), quat(0.0f, 0.0f, 0.0f, 0.0f)));
173     assert(almost_equal(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(almost_equal(radians(to!(real)(0)), 0));
198     assert(almost_equal(radians(to!(real)(90)), PI/2));
199     assert(almost_equal(radians(to!(real)(180)), PI));
200     assert(almost_equal(radians(to!(real)(360)), 2*PI));
201     
202     assert(almost_equal(degrees(to!(real)(0)), 0));
203     assert(almost_equal(degrees(to!(real)(PI/2)), 90));
204     assert(almost_equal(degrees(to!(real)(PI)), 180));
205     assert(almost_equal(degrees(to!(real)(2*PI)), 360));
206 
207     assert(almost_equal(degrees(radians(to!(real)(12))), 12));
208     assert(almost_equal(degrees(radians(to!(real)(100))), 100));
209     assert(almost_equal(degrees(radians(to!(real)(213))), 213));
210     assert(almost_equal(degrees(radians(to!(real)(399))), 399));
211     
212     /+static+/ assert(almost_equal(cdegrees!PI, 180));
213     /+static+/ assert(almost_equal(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(almost_equal(smoothstep(0.0, 2.0, 0.5), 0.15625, 0.00001));
253 }