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 }