1 /** 2 * Copyright: Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved 3 * Authors: $(Gabriel Gheorghe) 4 * License: $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007) 5 * Source: $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/math/vector.d, _vector.d) 6 * Documentation: 7 * Coverage: 8 */ 9 module liberty.math.vector; 10 import std.traits : isFloatingPoint; 11 /// T = type of elements. 12 /// N = number of elements (2, 3, 4). 13 struct Vector(T, ubyte N) if (N >= 2 && N <= 4) { 14 /// 15 alias size = N; 16 /// 17 alias type = T; 18 /// Fields definition. 19 union { 20 /// 21 T[N] v; 22 /// 23 struct { 24 /// 25 T x; 26 /// 27 alias r = x; 28 /// 29 alias p = y; 30 /// 31 T y; 32 /// 33 alias g = y; 34 /// 35 alias q = y; 36 static if (N >= 3) { 37 /// 38 T z; 39 /// 40 alias b = z; 41 /// 42 alias s = z; 43 } 44 static if (N == 4) { 45 /// 46 T w; 47 /// 48 alias a = w; 49 /// 50 alias t = w; 51 } 52 } 53 } 54 static if (N == 2 && is(T == float)) { 55 /// Vector2 with values of 0. 56 static const Vector!(T, 2) zero = Vector!(T, 2)(0, 0); 57 /// Vector2 with values of 1. 58 static const Vector!(T, 2) one = Vector!(T, 2)(1, 1); 59 /// Vector2 pointing up. 60 static const Vector!(T, 2) up = Vector!(T, 2)(0, 1); 61 /// Vector2 pointing right. 62 static const Vector!(T, 2) right = Vector!(T, 2)(1, 0); 63 /// Vector2 pointing down. 64 static const Vector!(T, 2) down = Vector!(T, 2)(0, -1); 65 /// Vector2 pointing left. 66 static const Vector!(T, 2) left = Vector!(T, 2)(-1, 0); 67 /// 68 unittest { 69 assert(Vector!(T, 2).zero == Vector!(T, 2)(0, 0)); 70 assert(Vector!(T, 2).one == Vector!(T, 2)(1, 1)); 71 assert(Vector!(T, 2).up == Vector!(T, 2)(0, 1)); 72 assert(Vector!(T, 2).right == Vector!(T, 2)(1, 0)); 73 assert(Vector!(T, 2).down == Vector!(T, 2)(0, -1)); 74 assert(Vector!(T, 2).left == Vector!(T, 2)(-1, 0)); 75 } 76 } else static if (N == 3 && is(T == float)) { 77 /// Vector3 with values of 0. 78 static const Vector!(T, 3) zero = Vector!(T, 3)(0, 0, 0); 79 /// Vector3 with values of 1. 80 static const Vector!(T, 3) one = Vector!(T, 3)(1, 1, 1); 81 /// Vector3 pointing up. 82 static const Vector!(T, 3) up = Vector!(T, 3)(0, 1, 0); 83 /// Vector3 pointing right. 84 static const Vector!(T, 3) right = Vector!(T, 3)(1, 0, 0); 85 /// Vector3 pointing down. 86 static const Vector!(T, 3) down = Vector!(T, 3)(0, -1, 0); 87 /// Vector3 pointing left. 88 static const Vector!(T, 3) left = Vector!(T, 3)(-1, 0, 0); 89 /// Vector3 pointing forward. 90 static const Vector!(T, 3) forward = Vector!(T, 3)(0, 0, 1); 91 /// Vector3 pointing backward. 92 static const Vector!(T, 3) backward = Vector!(T, 3)(0, 0, -1); 93 /// 94 unittest { 95 assert(Vector!(T, 3).zero == Vector!(T, 3)(0, 0, 0)); 96 assert(Vector!(T, 3).one == Vector!(T, 3)(1, 1, 1)); 97 assert(Vector!(T, 3).up == Vector!(T, 3)(0, 1, 0)); 98 assert(Vector!(T, 3).right == Vector!(T, 3)(1, 0, 0)); 99 assert(Vector!(T, 3).down == Vector!(T, 3)(0, -1, 0)); 100 assert(Vector!(T, 3).left == Vector!(T, 3)(-1, 0, 0)); 101 assert(Vector!(T, 3).forward == Vector!(T, 3)(0, 0, 1)); 102 assert(Vector!(T, 3).backward == Vector!(T, 3)(0, 0, -1)); 103 } 104 } 105 /// 106 this(U...)(U values) pure nothrow @safe @nogc { 107 import std.meta : allSatisfy; 108 import std.traits : isAssignable; 109 enum bool isAsgn(U) = isAssignable!(T, U); 110 static if ((U.length == N) && allSatisfy!(isAsgn, U)) { 111 static foreach (i, x; values) { 112 v[i] = x; 113 } 114 } /*else static if ((U.length == 2) && is(U[0] : Vector2!T) && is(U[1] : T) && is(this == Vector3!T)) { 115 v[0] = U[0].x; 116 v[1] = U[0].y; 117 v[2] = U[1]; 118 }*/ 119 else static if ((U.length == 1) && isAsgn!U && (!is(U[0] : Vector))) { 120 v[] = values[0]; 121 } else static if (U.length == 1 && is(U[0] : T[])) { 122 v = values[0]; 123 } else static if (U.length == 0) { 124 v[] = cast(T)0; 125 } else { 126 static assert(false, "Cannot create a vector from given arguments!"); 127 } 128 } 129 /// 130 pure nothrow @safe unittest { 131 auto v1 = Vector2I(4, 2); 132 assert (v1.v == [4, 2]); 133 auto v2 = Vector2I(5); 134 assert (v2.v == [5, 5]); 135 auto v3 = Vector3I([1, 2, 3]); 136 assert (v3.v == [1, 2, 3]); 137 auto v4 = Vector4I(); 138 assert (v4.v == [0, 0, 0, 0]); 139 auto v5 = Vector3I(Vector2I(1, 3), 5); 140 assert (v5.v == [1, 3, 5]); 141 auto v6 = Vector4I(Vector3I(-2, 1, 3), 5); 142 assert (v6.v == [-2, 1, 3, 5]); 143 auto v7 = Vector4I(Vector2I(1, 3), Vector2I(5, 4)); 144 assert (v7.v == [1, 3, 5, 4]); 145 } 146 // TODO: remove this constructors 147 static if (N == 3) { 148 this(Vector2!T xy, T z) { v[0] = xy.x; v[1] = xy.y; v[2] = z; } 149 } else static if (N == 4) { 150 this(Vector2!T xy, Vector2!T zw) { v[0] = xy.x; v[1] = xy.y; v[2] = zw.x; v[3] = zw.y; } 151 this(Vector3!T xyz, T w) { v[0] = xyz.x; v[1] = xyz.y; v[2] = xyz.z; v[3] = w; } 152 } 153 /// Assign a Vector from a compatible type. 154 ref Vector opAssign(U)(U rhs) pure nothrow @safe @nogc { 155 static if (is(U : Vector)) { 156 static foreach (i; 0..N) { 157 v[i] = rhs.v[i]; 158 } 159 } else static if (is(U : T)) { 160 v[] = rhs; 161 } else static if (is(U : T[])) { 162 assert (rhs.length == N, "Static array's lenght must be equal to vector's length!"); 163 v = rhs; 164 } else { 165 static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof); 166 } 167 return this; 168 } 169 /// 170 pure nothrow @safe unittest { 171 auto v1 = Vector3I(); 172 v1 = Vector3I(7, 8, 9); 173 assert (v1.v == [7, 8, 9]); 174 auto v2 = Vector2F(); 175 v2 = 3.4f; 176 assert (v2.v == [3.4f, 3.4f]); 177 auto v3 = Vector4I(); 178 v3 = [1, 3, 5, -6]; 179 assert (v3.v == [1, 3, 5, -6]); 180 } 181 /// 182 bool opEquals(U)(U rhs) pure nothrow const @safe @nogc { 183 static if (is(U : Vector)) { 184 static foreach (i; 0..N) { 185 if (v[i] != rhs.v[i]) { 186 return false; 187 } 188 } 189 } else static if (is(U : T)) { 190 static foreach (i; 0..N) { 191 if (v[i] != rhs) { 192 return false; 193 } 194 } 195 } else { 196 static assert (0, "Cannot compare a variable of type " ~ U.stringof ~ " with a variable of type " ~ Vector.stringof); 197 } 198 return true; 199 } 200 /// 201 pure nothrow @safe unittest { 202 auto v1 = Vector2F(4.5f, 6.0f); 203 assert (v1 == Vector2F(4.5f, 6.0f)); 204 assert (v1 != Vector2F(4.5f, 8.0f)); 205 auto v2 = Vector3I(-1, -1, -1); 206 assert (v2 == -1); 207 assert (v2 != 0); 208 } 209 /// 210 Vector opUnary(string op)() pure const nothrow @safe @nogc if (op == "+" || op == "-" || op == "~" || op == "!") { 211 Vector ret = void; 212 static foreach (i; 0..N) { 213 mixin("ret.v[i] = " ~ op ~ "v[i];"); 214 } 215 return ret; 216 } 217 /// 218 pure nothrow @safe @nogc unittest { 219 auto v1 = Vector2I(2, -5); 220 assert ((+v1).v == [2, -5]); 221 assert ((-v1).v == [-2, 5]); 222 assert ((~v1).v == [-3, 4]); 223 // *** BUG *** 224 //auto v2 = Vector!(bool, 2)(true, false); 225 //assert ((!v2).v == [false, true]); 226 } 227 /// 228 Vector opBinary(string op, U)(U rhs) pure nothrow const @safe @nogc { 229 Vector ret = void; 230 static if (is(U : Vector)) { 231 static foreach (i; 0..N) { 232 mixin("ret.v[i] = cast(T)(v[i] " ~ op ~ " rhs.v[i]);"); 233 } 234 } else static if (is(U : T)) { 235 static foreach (i; 0..N) { 236 mixin("ret.v[i] = cast(T)(v[i] " ~ op ~ " rhs);"); 237 } 238 } else { 239 static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof); 240 } 241 return ret; 242 } 243 /// 244 pure nothrow @safe @nogc unittest { 245 auto v1 = Vector2I(4, -5); 246 auto v2 = Vector2I(7, 2); 247 auto v3 = v1 + v2; 248 assert (v3.v == [11, -3]); 249 v3 = v2 - 2; 250 assert (v3.v == [5, 0]); 251 } 252 /// 253 Vector opBinaryRight(string op, U)(U lhs) pure nothrow const @safe @nogc { 254 Vector ret = void; 255 static if (is(U : Vector)) { 256 static foreach (i; 0..N) { 257 mixin("ret.v[i] = cast(T)(lhs.v[i] " ~ op ~ " v[i]);"); 258 } 259 } else static if (is(U : T)) { 260 static foreach (i; 0..N) { 261 mixin("ret.v[i] = cast(T)(lhs " ~ op ~ " v[i]);"); 262 } 263 } else { 264 static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Vector.stringof); 265 } 266 return ret; 267 } 268 /// 269 pure nothrow @safe @nogc unittest { 270 auto v1 = Vector2I(4, -5); 271 auto v2 = 3 + v1; 272 assert (v2.v == [7, -2]); 273 } 274 /// 275 ref Vector opOpAssign(string op, U)(U rhs) pure nothrow @safe @nogc { 276 static if (is(U : Vector) || is(U : T)) { 277 mixin("this = this " ~ op ~ " rhs;"); 278 } else { 279 static assert (0, "Cannot assign a variable of type " ~ U.stringof ~ " within a variable of type " ~ Matrix.stringof); 280 } 281 return this; 282 } 283 /// 284 pure nothrow @safe @nogc unittest { 285 auto v1 = Vector2I(2, -8); 286 v1 += 3; 287 assert (v1.v == [5, -5]); 288 auto v2 = Vector2I(1, 2); 289 v1 -= v2; 290 assert (v1.v == [4, -7]); 291 } 292 /// 293 ref T opIndex(size_t i) pure nothrow @safe @nogc { 294 return v[i]; 295 } 296 /// 297 ref const(T) opIndex(size_t i) pure nothrow const @safe @nogc { 298 return v[i]; 299 } 300 /// 301 T opIndexAssign(U : T)(U x, size_t i) pure nothrow @safe @nogc { 302 return v[i] = x; 303 } 304 /// 305 U opCast(U)() pure nothrow const @safe @nogc if (isVector!U && U.size == size) { 306 U ret = void; 307 static foreach (i; 0..N) { 308 mixin("ret.v[i] = cast(U.type)v[i];"); 309 } 310 return ret; 311 } 312 /// 313 pure nothrow @safe @nogc unittest { 314 auto v1 = Vector2F(1.3f, -5.7f); 315 auto v2 = cast(Vector2I)v1; 316 assert (v2.v == [1, -5]); 317 } 318 /// 319 int opDollar() pure nothrow const @safe @nogc { 320 return N; 321 } 322 /// 323 T[] opSlice() pure nothrow @safe @nogc { 324 return v[]; 325 } 326 /// 327 T[] opSlice(int a, int b) pure nothrow @safe @nogc { 328 return v[a..b]; 329 } 330 /// Returns a pointer to content. 331 inout(T)* ptr() pure nothrow inout @nogc @property { 332 return v.ptr; 333 } 334 /// Converts current vector to a string. 335 string toString() nothrow const @safe { 336 try { 337 import std.string : format; 338 return format("%s", v); 339 } catch (Exception e) { 340 assert (0); 341 } 342 } 343 /// 344 pure nothrow @safe unittest { 345 auto v = Vector!(uint, 2)(2, 4); 346 assert (v.toString() == "[2, 4]"); 347 } 348 /// 349 T squaredMagnitude() pure nothrow const @safe @nogc { 350 T sumSquares = 0; 351 static foreach (i; 0..N) { 352 mixin("sumSquares += v[i] * v[i];"); 353 } 354 return sumSquares; 355 } 356 /// 357 T squaredDistanceTo(Vector v) pure nothrow const @safe @nogc { 358 return (v - this).squaredMagnitude(); 359 } 360 static if (isFloatingPoint!T) { 361 /// Returns Euclidean length. 362 T magnitude() pure nothrow const @safe @nogc { 363 import liberty.math.functions : sqrt; 364 return sqrt(squaredMagnitude()); 365 } 366 /// Returns inverse of Euclidean length. 367 T inverseMagnitude() pure nothrow const @safe @nogc { 368 import liberty.math.functions : sqrt; 369 return 1 / sqrt(squaredMagnitude()); 370 } 371 /// Faster but less accurate inverse of Euclidean length. Returns inverse of Euclidean length. 372 T fastInverseMagnitude() pure nothrow const @safe @nogc { 373 import liberty.math.functions : inverseSqrt; 374 return inverseSqrt(squaredMagnitude()); 375 } 376 /// Returns Euclidean distance between this and other. 377 T distanceTo(Vector other) pure nothrow const @safe @nogc { 378 return (other - this).magnitude(); 379 } 380 /// In-place normalization. 381 void normalize() pure nothrow @safe @nogc { 382 auto invMag = inverseMagnitude(); 383 v[] *= invMag; 384 } 385 /// Returns normalized vector. 386 Vector normalized() pure nothrow const @safe @nogc { 387 Vector res = this; 388 res.normalize(); 389 return res; 390 } 391 /// Faster but less accurate in-place normalization. 392 void fastNormalize() pure nothrow @safe @nogc { 393 auto invLength = fastInverseMagnitude(); 394 v[] *= invLength; 395 } 396 /// Faster but less accurate vector normalization. Returns normalized vector. 397 Vector fastNormalized() pure nothrow const @safe @nogc { 398 Vector ret = this; 399 ret.fastNormalize(); 400 return ret; 401 } 402 static if (N == 3) { 403 /// Gets an orthogonal vector from a 3D vector. 404 Vector getOrthogonalVector() pure nothrow const @safe @nogc { 405 import liberty.math.functions : abs; 406 return abs(x) > abs(z) ? Vector(-y, x, 0.0) : Vector(0.0, -z, y); 407 } 408 } 409 } 410 } 411 /// True if T is some kind of Vector. 412 enum isVector(T) = is(T : Vector!U, U...); 413 /// 414 pure nothrow @safe @nogc unittest { 415 static assert (isVector!Vector2F); 416 static assert (isVector!Vector3I); 417 static assert (isVector!(Vector4!uint)); 418 static assert (!isVector!real); 419 } 420 /// The numeric type used to measure coordinates of a vector. 421 alias DimensionType(T : Vector!U, U...) = U[0]; 422 /// 423 pure nothrow @safe @nogc unittest { 424 static assert (is(DimensionType!Vector2F == float)); 425 static assert (is(DimensionType!Vector3D == double)); 426 static assert (is(DimensionType!Vector4U == uint)); 427 } 428 /// 429 template Vector2(T) { 430 alias Vector2 = Vector!(T, 2); 431 } 432 /// 433 template Vector3(T) { 434 alias Vector3 = Vector!(T, 3); 435 } 436 /// 437 template Vector4(T) { 438 alias Vector4 = Vector!(T, 4); 439 } 440 /// 441 alias Vector2I = Vector2!int ; 442 /// 443 alias Vector2U = Vector2!uint; 444 /// 445 alias Vector2F = Vector2!float; 446 /// 447 alias Vector2D = Vector2!double; 448 /// 449 alias Vector3I = Vector3!int; 450 /// 451 alias Vector3U = Vector3!uint; 452 /// 453 alias Vector3F = Vector3!float; 454 /// 455 alias Vector3D = Vector3!double; 456 /// 457 alias Vector4I = Vector4!int; 458 /// 459 alias Vector4U = Vector4!uint; 460 /// 461 alias Vector4F = Vector4!float; 462 /// 463 alias Vector4D = Vector4!double; 464 /// Element-wise minimum. 465 Vector!(T, N) minByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc { 466 import std.algorithm : min; 467 Vector!(T, N) ret = void; 468 static foreach (i; 0..N) { 469 ret.v[i] = min(a.v[i], b.v[i]); 470 } 471 return ret; 472 } 473 /// Element-wise maximum. 474 Vector!(T, N) maxByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc { 475 import std.algorithm : max; 476 Vector!(T, N) ret = void; 477 static foreach (i; 0..N) { 478 ret.v[i] = max(a.v[i], b.v[i]); 479 } 480 return ret; 481 } 482 /// Element-wise absolute value. 483 Vector!(T, N) absByElem(T, int N)(const Vector!(T, N) a) pure nothrow @safe @nogc { 484 import liberty.math.functions : abs; 485 Vector!(T, N) ret = void; 486 static foreach (i; 0..N) { 487 ret.v[i] = abs(a.v[i]); 488 } 489 return ret; 490 } 491 /// Returns dot product. 492 T dot(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc { 493 T sum = 0; 494 static foreach (i; 0..N) { 495 sum += a.v[i] * b.v[i]; 496 } 497 return sum; 498 } 499 /// Returns 3D cross product. 500 Vector!(T, 3) cross(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow @safe @nogc { 501 return Vector!(T, 3)(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); 502 } 503 /// Returns 3D reflect. 504 Vector!(T, N) reflect(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow @safe @nogc { 505 return a - (2 * dot(b, a)) * b; 506 } 507 /// 508 static assert (Vector2F.sizeof == 8); 509 /// 510 static assert (Vector3D.sizeof == 24); 511 /// 512 static assert (Vector4I.sizeof == 16);