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