1 /** 2 Defines a Vector of float point typefrom 2 to 4 dimension 3 */ 4 module zmath.vector; 5 6 alias Vec2r = Vector!(real, 2); /// Alias of a 2d Vector with reals 7 alias Vec3r = Vector!(real, 3); /// Alias of a 3d Vector with reals 8 alias Vec4r = Vector!(real, 4); /// Alias of a 4d Vector with reals 9 10 alias Vec2d = Vector!(double, 2); /// Alias of a 2d Vector with doubles 11 alias Vec3d = Vector!(double, 3); /// Alias of a 3d Vector with doubles 12 alias Vec4d = Vector!(double, 4); /// Alias of a 4d Vector with doubles 13 14 alias Vec2f = Vector!(float, 2); /// Alias of a 2d Vector with floats 15 alias Vec3f = Vector!(float, 3); /// Alias of a 3d Vector with floats 16 alias Vec4f = Vector!(float, 4); /// Alias of a 4d Vector with floats 17 18 /** 19 * N-Dimensional Vector over a FloatPoint type, where N must be 2,3 or 4 20 */ 21 public struct Vector(T, size_t dim_) if (__traits(isFloating, T)) { 22 nothrow: 23 static enum size_t dim = dim_; /// Vector Dimension 24 25 static assert(dim >= 2 && dim <= 4, "Not valid dimension size."); 26 static assert(__traits(isFloating, T), "Type not is a Float Point type."); 27 static assert(is(T : real), "Type not is like a Float Point type."); 28 29 union { 30 package T[dim] coor; /// Vector coords like Array 31 32 struct { 33 static if (dim >= 1) { 34 T x; /// X coord 35 alias r = x; /// R component 36 alias roll = x; /// Euler roll angle 37 alias bank = x; /// Euler roll angle 38 } 39 static if (dim >= 2) { 40 T y; /// Y coord 41 alias g = y; /// G component 42 alias pitch = y; /// Euler pith angle 43 alias attidue = y; /// Euler pith angle 44 } 45 static if (dim >= 3) { 46 T z; /// Z coord 47 alias b = z; /// B component 48 alias yaw = z; /// Euler yaw angle 49 alias heading = z; /// Euler yaw angle 50 } 51 static if (dim >= 4) { 52 T w; /// W coord 53 alias a = w; /// Alpha component 54 } 55 } 56 } 57 // Consts 58 static if (dim == 2) { // for R2 59 public static enum Vector!(T, 2) ZERO = Vector!(T, 2)(0, 0); /// Origin 60 public static enum Vector!(T, 2) X_AXIS = Vector!(T, 2)(1, 0); /// X Axis in R2 61 public static enum Vector!(T, 2) Y_AXIS = Vector!(T, 2)(0, 1); /// Y Axis in R2 62 63 } else static if (dim == 3) { // for R3 64 public static enum Vector!(T, 3) ZERO = Vector!(T, 3)(0, 0, 0); /// Origin 65 public static enum Vector!(T, 3) X_AXIS = Vector!(T, 3)(1, 0, 0); /// X Axis in R3 66 public static enum Vector!(T, 3) Y_AXIS = Vector!(T, 3)(0, 1, 0); /// Y Axis in R3 67 public static enum Vector!(T, 3) Z_AXIS = Vector!(T, 3)(0, 0, 1); /// Z Axis in R3 68 69 } else static if (dim == 4) { // for R4 70 public static enum Vector!(T, 4) ZERO = Vector!(T, 4)(0, 0, 0, 0); /// Origin 71 public static enum Vector!(T, 4) X_AXIS = Vector!(T, 4)(1, 0, 0, 0); /// X Axis in R4 72 public static enum Vector!(T, 4) Y_AXIS = Vector!(T, 4)(0, 1, 0, 0); /// Y Axis in R4 73 public static enum Vector!(T, 4) Z_AXIS = Vector!(T, 4)(0, 0, 1, 0); /// Z Axis in R4 74 /** W Axis in R4 (used like a scale factor with 3d Maths with 4d Matrixes) */ 75 public static enum Vector!(T, 4) W_AXIS = Vector!(T, 4)(0, 0, 0, 1); 76 } 77 78 /** 79 * Build a new Vector from a set of initial values 80 * If no there values for z and w, will be set to 0 81 * Params: 82 * x = X coord 83 * y = Y coord 84 * z = Z coord 85 * w = W coord (scale factor in 3d math) 86 */ 87 @nogc this(in T x, in T y = 0, in T z = 0, in T w = 1) pure { 88 this.x = x; 89 this.y = y; 90 static if (dim >= 3) 91 this.z = z; 92 static if (dim >= 4) 93 this.w = w; 94 } 95 96 /** 97 * Build a new Vector from a array 98 * If no there values for y and z, will be set to 0. w sill beset to 1 99 * Params: 100 * xs = Array with coords 101 */ 102 this(in T[] xs) pure { 103 size_t l = xs.length > dim ? dim : xs.length; 104 this.coor[0 .. l] = xs[0 .. l].dup; 105 if (l < dim) { 106 static if (dim < 4) { 107 this.coor[l .. dim] = 0; 108 } else { 109 this.coor[l .. dim - 1] = 0; 110 w = 1; 111 } 112 } 113 } 114 115 // Basic Properties ********************************************************** 116 117 /** 118 * Returns i coord of this vector 119 */ 120 @nogc T opIndex(size_t i) pure const { 121 return coor[i]; 122 } 123 124 /** 125 * Assigns a value to a i coord 126 */ 127 void opIndexAssign(T c, size_t i) { 128 coor[i] = c; 129 } 130 131 /** 132 * Returns the actual length of this Vector 133 */ 134 @property @nogc T length() pure const { 135 import std.math : sqrt; 136 137 return sqrt(sq_length); 138 } 139 140 /** 141 * Returns the actual squared length of this Vector 142 */ 143 @property @nogc T sq_length() pure const { 144 return this * this; 145 } 146 147 // Operations **************************************************************** 148 149 /** 150 * Define Equality 151 * Params: 152 * rhs = Vector at rigth of '==' 153 */ 154 @nogc bool opEquals()(auto ref const Vector rhs) pure const { 155 if (x != rhs.x) 156 return false; 157 static if (dim >= 2) { 158 if (y != rhs.y) { 159 return false; 160 } 161 } 162 static if (dim >= 3) { 163 if (z != rhs.z) { 164 return false; 165 } 166 } 167 static if (dim == 4) { 168 if (w != rhs.w) { 169 return false; 170 } 171 } 172 return true; 173 } 174 175 /** 176 * Approximated equality with controlable precision 177 * Params: 178 * rhs = Vector to compare with this vector 179 * maxRelDiff = Maximun relative difference 180 * maxAbsDiff = Maximun absolute difference 181 * 182 * See: std.math : approxEqual 183 */ 184 @nogc bool approxEqual()(auto ref const Vector rhs, T maxRelDiff = 1e-2, T maxAbsDiff = 1e-05) pure const { 185 import std.math : approxEqual; 186 187 static if (dim == 2) { 188 return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y, 189 rhs.y, maxRelDiff, maxAbsDiff); 190 } else static if (dim == 3) { 191 return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y, rhs.y, 192 maxRelDiff, maxAbsDiff) && approxEqual(z, rhs.z, maxRelDiff, maxAbsDiff); 193 } else static if (dim == 4) { 194 return approxEqual(x, rhs.x, maxRelDiff, maxAbsDiff) && approxEqual(y, rhs.y, maxRelDiff, 195 maxAbsDiff) && approxEqual(z, rhs.z, maxRelDiff, maxAbsDiff) 196 && approxEqual(w, rhs.w, maxRelDiff, maxAbsDiff); 197 } 198 } 199 200 /** 201 * Define unary operators + and - 202 */ 203 @nogc Vector opUnary(string op)() pure const if (op == "+" || op == "-") { 204 static if (dim == 2) { 205 return Vector(mixin(op ~ "x"), mixin(op ~ "y")); 206 } else static if (dim == 3) { 207 return Vector(mixin(op ~ "x"), mixin(op ~ "y"), mixin(op ~ "z")); 208 } else static if (dim == 4) { 209 return Vector(mixin(op ~ "x"), mixin(op ~ "y"), mixin(op ~ "z"), mixin(op ~ "w")); 210 } 211 } 212 213 /** 214 * Define binary operator + and - 215 */ 216 @nogc Vector opBinary(string op)(auto ref const Vector rhs) pure const if (op == "+" || op == "-") { 217 static if (dim == 2) { 218 return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y")); 219 } else static if (dim == 3) { 220 return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"), mixin("z" ~ op ~ "rhs.z")); 221 } else static if (dim == 4) { 222 return Vector(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"), 223 mixin("z" ~ op ~ "rhs.z"), mixin("w" ~ op ~ "rhs.w")); 224 } 225 } 226 227 /** 228 * Define Scalar multiplication 229 */ 230 @nogc Vector opBinary(string op)(in T rhs) pure const if (op == "*" || op == "/") { 231 static if (dim == 2) { 232 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs")); 233 } else static if (dim == 3) { 234 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), mixin("z" ~ op ~ "rhs")); 235 } else static if (dim == 4) { 236 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), 237 mixin("z" ~ op ~ "rhs"), mixin("w" ~ op ~ "rhs")); 238 } 239 } 240 241 /** 242 * Define Scalar multiplication 243 */ 244 @nogc Vector opBinaryRight(string op)(in T rhs) pure const if(op == "*" || op == "/") { 245 static if (dim == 2) { 246 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs")); 247 } else static if (dim == 3) { 248 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), mixin("z" ~ op ~ "rhs")); 249 } else static if (dim == 4) { 250 return Vector(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"), 251 mixin("z" ~ op ~ "rhs"), mixin("w" ~ op ~ "rhs")); 252 } 253 } 254 255 /** 256 * Define Dot Product 257 */ 258 @nogc T opBinary(string op)(auto ref const Vector rhs) pure const if (op == "*") { 259 T tmp = 0; 260 foreach (i, x; this.coor) { 261 tmp += x * rhs.coor[i]; 262 } 263 return tmp; 264 } 265 266 /** 267 * Define Cross Product for R3 (operation c = a & b ) 268 */ 269 @nogc Vector opBinary(string op)(auto ref const Vector rhs) pure const 270 if (op == "&" && dim == 3) { 271 // dfmt off 272 return Vector( 273 coor[1] * rhs.coor[2] - coor[2] * rhs.coor[1], 274 coor[2] * rhs.coor[0] - coor[0] * rhs.coor[2], 275 coor[0] * rhs.coor[1] - coor[1] * rhs.coor[0] 276 ); 277 // dfmt on 278 } 279 280 /** 281 * It's a unitary vector (length == 1) 282 * Returns : True if length approxEqual to 1.0 283 */ 284 @property @nogc bool isUnit() pure const { 285 import std.math : approxEqual, abs; 286 287 return approxEqual(abs(this.sq_length - 1.0), 0); 288 } 289 290 /** 291 * Normalize this vector 292 */ 293 @nogc void normalize() { 294 if (!isUnit()) { 295 const T invL = 1 / this.length; 296 static if (dim >= 2) { 297 x = x * invL; 298 y = y * invL; 299 } 300 static if (dim >= 3) 301 z = z * invL; 302 static if (dim == 4) 303 w = w * invL; 304 } 305 } 306 307 /** 308 * Returns the unit vector of this vector 309 */ 310 @property @nogc Vector unit() pure const { 311 Vector ret = this; 312 ret.normalize; 313 return ret; 314 } 315 316 /** 317 * Obtains the projection of two vectors 318 * Params: 319 * a = Vector to project 320 * b = Vector where project vector a 321 * Returns : A Vector that it's projection of Vector a over Vector b 322 */ 323 @nogc static Vector projectOnTo()(auto ref const Vector a, auto ref const Vector b) pure { 324 const bNormalized = b.unit(); 325 Vector ret = b * (a * bNormalized); 326 return ret; 327 } 328 329 /** 330 * Obtains the projection of this vector over other vector 331 * Params: 332 * b = Vector where project this vector 333 * Returns : A Vector that it's projection of this Vector over Vector b 334 */ 335 @nogc Vector projectOnTo()(auto ref const Vector b) pure { 336 return this.projectOnTo(this, b); 337 } 338 339 /** 340 * Calculate the distance between two points pointed by this vector and other 341 * Params : 342 * b = Vector B 343 * Returns : Distance between the point pointed by this vector and other point 344 */ 345 @nogc T distance()(auto ref const Vector b) pure { 346 auto d = this - b; 347 return d.length; 348 } 349 350 /** 351 * Calculate the squared distance between two points pointed by this vector 352 * and other 353 * Params : 354 * b = Vector B 355 * Returns : Squared distance between the point pointed by this vector and 356 * other point 357 */ 358 @nogc T sq_distance()(auto ref const Vector b) pure { 359 auto d = this - b; 360 return d.sq_length; 361 } 362 363 /** 364 * Rotation in R2 365 * Params: 366 * angle = Rotation angle 367 * Returns : A new vector that is the rotation of this vector 368 */ 369 static if (dim == 2) { 370 @nogc Vector rotate(real angle) pure const { 371 import std.math : sin, cos; 372 373 return Vector(x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle)); 374 } 375 } 376 377 // Misc ********************************************************************** 378 379 /** 380 * Return a pointer of the internal array 381 */ 382 @property @nogc T* ptr() pure { 383 return this.coor.ptr; 384 } 385 386 /** 387 * Checks that the vector not have a weird NaN value 388 * Returns : True if this vector not have a NaN value 389 */ 390 @property @nogc bool isOk() pure const { 391 import std.math : isNaN; 392 393 if (isNaN(x) || isNaN(y)) 394 return false; 395 static if (dim >= 3) 396 if (isNaN(z)) 397 return false; 398 static if (dim >= 4) 399 if (isNaN(w)) 400 return false; 401 return true; 402 } 403 404 /** 405 * Checks that the vector have finite values 406 * Returns : True if this vector have finite values (not infinite value or 407 * NaNs) 408 */ 409 @property @nogc bool isFinite() pure const { 410 import std.math : isFinite; 411 412 if (isFinite(x) && isFinite(y)) { 413 static if (dim >= 3) 414 if (!isFinite(z)) 415 return false; 416 static if (dim >= 4) 417 if (!isFinite(w)) 418 return false; 419 return true; 420 } else { 421 return false; 422 } 423 } 424 425 /** 426 * Casting method to convert to other vector types 427 */ 428 @nogc Tout opCast(Tout)() pure const if (isVector!(Tout)) { 429 import std.conv : to; 430 431 Tout newVector = void; 432 auto i = 0; 433 static if (is(typeof(newVector.x) == typeof(this.x))) { 434 for (; i < dim && i < Tout.dim; i++) 435 newVector.coor[i] = coor[i]; 436 } else { 437 for (; i < dim && i < Tout.dim; i++) 438 newVector.coor[i] = to!(typeof(newVector.x))(coor[i]); 439 } 440 441 static if (Tout.dim >= 3 && dim < 3) // Expands a small vector to a bigger 442 newVector.z = 0; // Z by default to 0 443 444 static if (Tout.dim == 4 && dim < 4) { 445 newVector.w = 1; 446 // w is usually a scale factor used in 3d math with 4d matrixes 447 } 448 449 return newVector; 450 } 451 452 /** 453 * Returns a string representation of this vector 454 */ 455 string toString() const { 456 import std.conv : to; 457 try { 458 string ret = "[" ~ to!string(x) ~ ", " ~ to!string(y); 459 static if (dim >= 3) 460 ret ~= ", " ~ to!string(z); 461 static if (dim >= 4) 462 ret ~= ", " ~ to!string(w); 463 ret ~= "]"; 464 return ret; 465 } catch (Exception ex) { 466 assert(false); // This never should happen 467 } 468 } 469 } 470 471 /** 472 * Say if a thing it's a Vector 473 */ 474 template isVector(T) { 475 immutable bool isVector = __traits(compiles, () { 476 T t; 477 static assert(T.dim >= 2 && T.dim <= 4); 478 auto coor = t.coor; 479 auto x = t.x; 480 auto y = t.y; 481 static if (t.dim >= 3) 482 auto z = t.z; 483 static if (t.dim >= 4) 484 auto w = t.w; 485 // TODO : Should test for methods ? 486 }); 487 } 488 489 static assert(Vec2f.sizeof == 8); 490 static assert(Vec3d.sizeof == 24); 491 static assert(Vec4f.sizeof == 16); 492 493 unittest { 494 Vec2r v1 = Vec2r(0, 2); 495 auto v2 = Vec2r(2, 0); 496 auto v3 = Vec2r([1, 1]); 497 float[] arr = [5, 20.1, 0, 10, 20]; 498 auto v4 = Vec4f(arr); 499 500 assert(v2.x == 2); // By Coordinate name 501 assert(v2.coor[0] == 2); // By direct access to array (only internal use) 502 } 503 504 unittest { 505 auto v = Vec3f(10, 0, 0); 506 auto v2 = Vec4d(1, 1, 1, 1); 507 assert(v2[0] == 1); // By opIndex 508 v2[0] = 123; 509 assert(v2[0] == 123); // Assign by opIndex 510 assert(v.length == 10.0); 511 assert(Vec4d(1, 1, 1, 1).sq_length == 4); 512 } 513 514 unittest { 515 // Check equality 516 auto v1 = Vec4f(4, 2, 1, 0); 517 auto v2 = Vec4f(4, 2, 3, 1); 518 auto v3 = Vec4f(4, 2, 1, 0); 519 auto v4 = Vec4f(4, 2, 1.00001, 0); 520 assert(v2 != v1); 521 assert(v1 == v1); 522 assert(v1 == Vec4f(4, 2, 1, 0)); 523 assert(v3 == v1); 524 assert(v1 != v4); 525 assert(v1.approxEqual(v1)); 526 assert(!v1.approxEqual(v2)); 527 assert(v1.approxEqual(v4)); 528 assert(v1.approxEqual(Vec4f(4, 2, 1, 0))); 529 } 530 531 unittest { 532 // Check change of sign 533 auto v = Vec2r([1, 1]); 534 auto n = -v; 535 assert(n.x == -1); 536 assert(n.y == -1); 537 } 538 539 unittest { 540 // Check addition 541 auto v1 = Vec4d(1, 2, 3, 4); 542 auto v2 = Vec4d(1, 1, 1, 1); 543 auto v12 = v1 + v2; 544 auto v21 = v2 + v1; 545 546 // Symetry 547 assert(v12 == v21); 548 // Value 549 assert(v12 == Vec4d(2, 3, 4, 5)); 550 551 // Check subtraction 552 auto v1m2 = v1 - v2; 553 assert(v1m2 == Vec4d(0, 1, 2, 3)); 554 // Subtraction asimetry 555 auto v2m1 = v2 - v1; 556 assert(v2m1 == Vec4d(0, -1, -2, -3)); 557 } 558 559 unittest { 560 // Product by Scalar 561 auto vten = Vec4r(0, 1, 2, 3) * 10.0L; 562 assert(vten == Vec4r(0, 10, 20, 30)); 563 vten = vten / 2.0L; 564 assert(vten == Vec4r(0, 5, 10, 15)); 565 vten = 10 * vten ; 566 assert(vten == Vec4r(0, 50, 100, 150)); 567 } 568 569 unittest { 570 // Dot Product and length 571 auto v1 = Vec4f(10, 1, 0, 0); 572 auto v2 = Vec4f(1, 1, 1, 0); 573 auto v2dotv1 = v2 * v1; 574 assert(v2dotv1 == 11.0); 575 v2 = Vec4f(0, 1, 1, 0); 576 v2dotv1 = v2 * Vec4f(10, 0, 1, 0); 577 assert(v2dotv1 == 1.0); 578 } 579 580 unittest { 581 // Cross product 582 auto r3v1 = Vec3r(0, 0, 10); 583 auto r3v2 = Vec3r(2, 0, 2); 584 auto cross0 = r3v1 & r3v1; 585 auto cross12 = r3v1 & r3v2; 586 auto cross21 = r3v2 & r3v1; 587 588 assert(cross0.length == 0); 589 assert(cross12.length == 20.0); 590 assert(cross21.length == 20.0); 591 592 assert(cross12 == Vec3r(0, 20.0L, 0)); 593 assert(cross21 == Vec3r(0, -20.0L, 0)); 594 } 595 596 unittest { 597 // Unitary Vector 598 auto v = Vec4f(10, 10, 10, 10); 599 Vec4f uv = v; 600 uv.normalize(); 601 auto uv2 = Vec4f(3, 3, 3, 3); 602 uv2.normalize(); 603 assert(uv == uv2); 604 assert(uv == Vec4f(.5, .5, .5, .5)); 605 assert(!v.isUnit()); 606 assert(uv.isUnit()); 607 } 608 609 unittest { 610 // Check projection 611 auto b = Vec3f.X_AXIS; 612 auto a = Vec3f(1, 20, -30); 613 auto proy = a.projectOnTo(b); 614 assert(proy == Vec3f.X_AXIS); 615 } 616 617 unittest { 618 // Test Distance 619 auto v1 = Vec4d(1, 1, 1, 1); 620 auto v2 = Vec4d(-1, -1, -1, -1); 621 auto dis = v1.distance(v2); 622 auto dis_sq = v1.sq_distance(v2); 623 assert(dis == 4); 624 assert(dis_sq == 16); 625 626 dis = v1.distance(Vec4d(-1, -1, -1, -1)); 627 assert(dis == 4); 628 } 629 630 unittest { 631 import std.math : PI_2; 632 633 // Check Rotation in R2 634 auto v = Vec2f(10, 0); 635 auto rot90 = v.rotate(PI_2); 636 auto rotn90 = v.rotate(-PI_2); 637 assert(rot90.length == v.length); 638 assert(rotn90.length == v.length); 639 assert(rot90.approxEqual(Vec2f(0, 10))); 640 assert(rotn90.approxEqual(Vec2f(0, -10))); 641 } 642 643 unittest { 644 // Check isOk() and isFinite() 645 Vec4d vNaN; 646 assert(!vNaN.isOk); 647 assert(!vNaN.isFinite); 648 Vec4d vOK = Vec4d(1, 2, 3, 4); 649 assert(vOK.isOk); 650 assert(vOK.isFinite); 651 Vec4d vInf = Vec4d(1.0 / 0.0, 10, 0, 1); 652 assert(!vInf.isFinite); 653 assert(vInf.isOk); 654 } 655 656 unittest { 657 // Check casting 658 auto v2 = Vec2r(10, -10); 659 auto v2f = cast(Vec2f) v2; 660 auto v2d = cast(Vec2d) v2; 661 auto vecf = Vec2f(10, 10); 662 //auto vec4d = toImpl!(Vec4d, Vec2f) (vecf); 663 //auto vec4d = to!Vec4d (vecf); // Not yet 664 auto vec4d = cast(Vec4d) vecf; 665 assert(is(typeof(v2f) == Vec2f)); 666 assert(is(typeof(v2d) == Vec2d)); 667 assert(is(typeof(vec4d) == Vec4d)); 668 auto r2 = Vec2d(5, 4); 669 auto r4 = cast(Vec4d) r2; 670 assert(is(typeof(r4) == Vec4d)); 671 assert(r4 == Vec4d(5, 4, 0, 1)); 672 r2 = cast(Vec2d) r4; 673 assert(r2 == Vec2d(5, 4)); 674 } 675 676 unittest { 677 const v1 = Vec2f(5, 7); 678 assert(isVector!(typeof(v1))); // Yep it's a Vector 679 assert(!isVector!(int)); 680 } 681