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 }