1 /** 2 * This module contains the `Vec` templated struct representing vectors and 3 * their operations. 4 */ 5 module dvec.vector; 6 7 import std.traits : isNumeric, isFloatingPoint; 8 import dvec.vector_types; 9 10 /** 11 * Generic struct that represents a vector holding `size` elements of type `T`. 12 * A vector must contain at least 1 element, and has no upper-bound on the size 13 * beyond restrictions imposed by the system. 14 */ 15 struct Vec(T, size_t size) if (isNumeric!T && size > 0) { 16 /** 17 * A static contstant that can be used to get the size of the vector. 18 */ 19 public static const size_t SIZE = size; 20 21 /** 22 * The internal static array storing the elements of this vector. 23 */ 24 public T[size] data; 25 26 /** 27 * Internal alias to make template methods more readable. We can just say 28 * `MY_TYPE` instead of `Vec!(T, size)` everywhere. 29 */ 30 private alias MY_TYPE = Vec!(T, size); 31 32 /** 33 * Constructs a vector from an array of elements. 34 * Params: 35 * elements = The elements to put in the vector. 36 */ 37 public this(T[size] elements) @nogc { 38 static foreach (i; 0 .. size) data[i] = elements[i]; 39 } 40 unittest { 41 Vec3f v = Vec3f([1f, 2f, 3f]); 42 assert(v.data == [1f, 2f, 3f]); 43 } 44 45 /** 46 * Constructs a vector from a variadic list of elements. 47 * Params: 48 * elements = The elements to put in the vector. 49 */ 50 public this(T[] elements...) @nogc { 51 if (elements.length != size) assert(false, "Invalid number of elements provided to Vec constructor."); 52 static foreach (i; 0 .. size) data[i] = elements[i]; 53 } 54 unittest { 55 Vec3i v = Vec3i(5, 4, 3); 56 assert(v.data == [5, 4, 3]); 57 } 58 59 /** 60 * Constructs a vector where all elements have the given value. 61 * Params: 62 * value = The value to assign to all elements in the vector. 63 */ 64 public this(T value) @nogc { 65 static foreach (i; 0 .. size) data[i] = value; 66 } 67 unittest { 68 Vec2f v = Vec2f(1f); 69 assert(v.data == [1f, 1f]); 70 Vec!(float, 25) v2 = Vec!(float, 25)(3f); 71 foreach (value; v2.data) { 72 assert(value == 3f); 73 } 74 } 75 76 /** 77 * Constructs a vector as a copy of the other. 78 * Params: 79 * other = The vector to copy. 80 */ 81 public this(MY_TYPE other) @nogc { 82 this(other.data); 83 } 84 unittest { 85 Vec2f vOriginal = Vec2f(5f, 16f); 86 Vec2f vCopy = Vec2f(vOriginal); 87 assert(vCopy.data == [5f, 16f]); 88 } 89 90 /** 91 * Constructs a vector containing all 0's. 92 * Returns: An vector containing all 0's. 93 */ 94 public static MY_TYPE empty() @nogc { 95 MY_TYPE v; 96 static foreach (i; 0 .. size) v[i] = 0; 97 return v; 98 } 99 unittest { 100 Vec4f v = Vec4f.empty(); 101 assert(v.data == [0f, 0f, 0f, 0f]); 102 } 103 104 /** 105 * Computes the sum of a given array of vectors. 106 * Params: 107 * vectors = The list of vectors to compute the sum of. 108 * Returns: The sum of all vectors. 109 */ 110 public static MY_TYPE sum(MY_TYPE[] vectors) @nogc { 111 MY_TYPE v = MY_TYPE(0); 112 foreach (vector; vectors) v.add(vector); 113 return v; 114 } 115 unittest { 116 Vec2i v1 = Vec2i(1, 1); 117 Vec2i v2 = Vec2i(2, 2); 118 Vec2i v3 = Vec2i(3, 3); 119 Vec2i v4 = Vec2i(-1, -4); 120 assert(Vec2i.sum([v1, v2]) == v3); 121 assert(Vec2i.sum([v1, v2, v3]) == Vec2i(6, 6)); 122 assert(Vec2i.sum([v3, v4]) == Vec2i(2, -1)); 123 } 124 125 /** 126 * Gets a copy of this vector. 127 * Returns: A copy of this vector. 128 */ 129 public MY_TYPE copy() const @nogc { 130 return MY_TYPE(this); 131 } 132 unittest { 133 Vec3f v = Vec3f(0.5f, 1.0f, 0.75f); 134 Vec3f vCopy = v.copy(); 135 assert(v.data == vCopy.data); 136 v.data[0] = -0.5f; 137 assert(vCopy.data[0] == 0.5f); 138 } 139 140 /** 141 * Gets the element at a specified index. 142 * Params: 143 * i = The index of the element. 144 * Returns: The element at the specified index. 145 */ 146 public T opIndex(size_t i) const @nogc { 147 return data[i]; 148 } 149 unittest { 150 Vec3f v = Vec3f(1f, 2f, 3f); 151 assert(v[0] == 1f); 152 assert(v[1] == 2f); 153 assert(v[2] == 3f); 154 } 155 156 /** 157 * Inserts an element at the specified index. 158 * Params: 159 * value = The value to assign. 160 * i = The index of the element. 161 */ 162 public void opIndexAssign(T value, size_t i) @nogc { 163 data[i] = value; 164 } 165 unittest { 166 Vec3i v; 167 assert(v[0] == 0); 168 v[0] = 42; 169 assert(v[0] == 42); 170 } 171 172 /** 173 * Adds the given vector to this one. 174 * Params: 175 * other = The vector to add to this one. 176 * Returns: A reference to this vector, for method chaining. 177 */ 178 public ref MY_TYPE add(V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 179 static foreach (i; 0 .. size) data[i] += other[i]; 180 return this; 181 } 182 unittest { 183 Vec3i v1 = Vec3i(1); 184 Vec3i v2 = Vec3i(2); 185 v1.add(v2); 186 assert(v1.data == [3, 3, 3]); 187 assert(v2.data == [2, 2, 2]); 188 } 189 190 /** 191 * Subtracts the given vector from this one. 192 * Params: 193 * other = The vector to subtract from this one. 194 * Returns: A reference to this vector, for method chaining. 195 */ 196 public ref MY_TYPE sub(V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 197 static foreach (i; 0 .. size) data[i] -= other[i]; 198 return this; 199 } 200 unittest { 201 Vec3i v1 = Vec3i(1); 202 Vec3i v2 = Vec3i(2); 203 v1.sub(v2); 204 assert(v1.data == [-1, -1, -1]); 205 assert(v2.data == [2, 2, 2]); 206 } 207 208 /** 209 * Multiplies this vector by a factor, element-wise. 210 * Params: 211 * factor = The factor to multiply by. 212 * Returns: A reference to this vector, for method chaining. 213 */ 214 public ref MY_TYPE mul(V)(V factor) @nogc if (isNumeric!V) { 215 static foreach (i; 0 .. size) data[i] *= factor; 216 return this; 217 } 218 unittest { 219 assert(Vec2i(2).mul(2).data == [4, 4]); 220 } 221 222 /** 223 * Multiplies this vector by another vector, element-wise. 224 * Params: 225 * other = The other vector to multiply with. 226 * Returns: A reference to this vector, for method chaining. 227 */ 228 public ref MY_TYPE mul(V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 229 static foreach (i; 0 .. size) data[i] *= other[i]; 230 return this; 231 } 232 unittest { 233 assert(Vec2i(1, 2).mul(Vec2i(3, 2)).data == [3, 4]); 234 } 235 236 /** 237 * Divides this vector by a factor, element-wise. 238 * Params: 239 * factor = The factor to divide by. 240 * Returns: A reference to this vector, for method chaining. 241 */ 242 public ref MY_TYPE div(V)(V factor) @nogc if (isNumeric!V) { 243 static foreach (i; 0 .. size) data[i] /= factor; 244 return this; 245 } 246 unittest { 247 assert(Vec2i(8).div(2).data == [4, 4]); 248 } 249 250 /** 251 * Divides this vector by another vector, element-wise. 252 * Params: 253 * other = The other vector to divide with. 254 * Returns: A reference to this vector, for method chaining. 255 */ 256 public ref MY_TYPE div(V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 257 static foreach (i; 0 .. size) data[i] /= other[i]; 258 return this; 259 } 260 unittest { 261 assert(Vec2i(8).div(Vec2i(2, 4)).data == [4, 2]); 262 } 263 264 /** 265 * Determines the squared magnitude of this vector. 266 * Returns: The squared magnitude of this vector. 267 */ 268 public double mag2() const @nogc { 269 double sum = 0; 270 static foreach (i; 0 .. size) sum += data[i] * data[i]; 271 return sum; 272 } 273 unittest { 274 assert(Vec2i(2).mag2() == 8); 275 assert(Vec2f(3f, 4f).mag2() == 5f * 5f); 276 } 277 278 /** 279 * Determines the magnitude of this vector. 280 * Returns: The magnitude of this vector. 281 */ 282 public double mag() const @nogc { 283 import std.math : sqrt; 284 return sqrt(mag2()); 285 } 286 unittest { 287 import std.math : sqrt; 288 assert(Vec2i(1, 0).mag() == 1); 289 assert(Vec2f(3f, 4f).mag() == 5f); 290 assert(Vec2i(1, 1).mag() == sqrt(2.0)); 291 } 292 293 /** 294 * Determines the [dot product](https://en.wikipedia.org/wiki/Dot_product) 295 * of this vector and another vector. 296 * Params: 297 * other = The other vector. 298 * Returns: The dot product of the vectors. 299 */ 300 public T dot(MY_TYPE other) const @nogc { 301 T sum = 0; 302 static foreach (i; 0 .. size) sum += data[i] * other[i]; 303 return sum; 304 } 305 unittest { 306 assert(Vec3i(1, 3, -5).dot(Vec3i(4, -2, -1)) == 3); 307 assert(Vec2f(1.0f, 0.0f).dot(Vec2f(0.0f, 1.0f)) == 0f); 308 assert(Vec2f(1.0f, 0.0f).dot(Vec2f(1.0f, 0.0f)) == 1f); 309 assert(Vec2f(1.0f, 0.0f).dot(Vec2f(-1.0f, 0.0f)) == -1f); 310 } 311 312 /** 313 * Adds two vectors. 314 * Params: 315 * other = The other vector. 316 * Returns: A vector representing the sum of this and the other. 317 */ 318 public MY_TYPE opBinary(string op : "+", V)(Vec!(V, size) other) const @nogc if (isNumeric!V) { 319 auto result = copy(); 320 result.add(other); 321 return result; 322 } 323 unittest { 324 assert(Vec2i(1) + Vec2i(3) == Vec2i(4)); 325 } 326 327 /** 328 * Adds a scalar value to a vector, element-wise. 329 * Params: 330 * scalar = The scalar value to add to the vector. 331 * Returns: A vector representing the sum of this and the scalar value 332 * applied to all elements. 333 */ 334 public MY_TYPE opBinary(string op : "+", V)(V scalar) const @nogc if (isNumeric!V) { 335 auto result = copy(); 336 result.add(Vec!(V, size)(scalar)); 337 return result; 338 } 339 unittest { 340 assert(Vec2i(1) + 3 == Vec2i(4)); 341 } 342 343 /** 344 * Adds a vector to this one. 345 * Params: 346 * other = The other vector. 347 * Returns: A reference to this vector. 348 */ 349 public ref MY_TYPE opOpAssign(string op : "+", V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 350 this.add(other); 351 return this; 352 } 353 unittest { 354 Vec3i v = Vec3i(1, 2, 3); 355 v += Vec3i(1); 356 assert(v.data == [2, 3, 4]); 357 } 358 359 /** 360 * Adds a scalar value to this vector. 361 * Params: 362 * scalar = The scalar value to add to the vector. 363 * Returns: A reference to this vector. 364 */ 365 public ref MY_TYPE opOpAssign(string op : "+", V)(V scalar) @nogc if (isNumeric!V) { 366 this.add(Vec!(V, size)(scalar)); 367 return this; 368 } 369 unittest { 370 Vec3i v = Vec3i(1, 2, 3); 371 v += 2; 372 assert(v.data == [3, 4, 5]); 373 } 374 375 // TODO: Add scalar operators for: 376 // sub 377 // mul 378 // div 379 // and non-operator overload methods for add, sub 380 381 /** 382 * Subtracts two vectors. 383 * Params: 384 * other = The other vector. 385 * Returns: A vector representing the difference of this and the other. 386 */ 387 public MY_TYPE opBinary(string op : "-", V)(Vec!(V, size) other) const @nogc if (isNumeric!V) { 388 auto result = copy(); 389 result.sub(other); 390 return result; 391 } 392 unittest { 393 assert(Vec2i(4) - Vec2i(1) == Vec2i(3)); 394 } 395 396 /** 397 * Subtracts a vector from this one. 398 * Params: 399 * other = The other vector. 400 * Returns: A reference to this vector. 401 */ 402 public ref MY_TYPE opOpAssign(string op : "-", V)(Vec!(V, size) other) @nogc if (isNumeric!V) { 403 this.sub(other); 404 return this; 405 } 406 unittest { 407 Vec3i v = Vec3i(1, 2, 3); 408 v -= Vec3i(2); 409 assert(v.data == [-1, 0, 1]); 410 } 411 412 /** 413 * Multiplies a vector by a factor. 414 * Params: 415 * factor = The factor to multiply by. 416 * Returns: The resultant vector. 417 */ 418 public MY_TYPE opBinary(string op : "*", V)(V factor) const @nogc if (isNumeric!V) { 419 auto result = copy(); 420 result.mul(factor); 421 return result; 422 } 423 unittest { 424 assert(Vec2f(0.5f) * 2f == Vec2f(1.0f)); 425 } 426 427 /** 428 * Multiplies this vector by a factor. 429 * Params: 430 * factor = The factor to multiply by. 431 * Returns: A reference to this vector. 432 */ 433 public ref MY_TYPE opOpAssign(string op : "*", V)(V factor) @nogc if (isNumeric!V) { 434 this.mul(factor); 435 return this; 436 } 437 unittest { 438 Vec2f v = Vec2f(2f); 439 v *= 2f; 440 assert(v.data == [4f, 4f]); 441 } 442 443 /** 444 * Divides a vector by a factor. 445 * Params: 446 * factor = The factor to divide by. 447 * Returns: The resultant vector. 448 */ 449 public MY_TYPE opBinary(string op : "/", V)(V factor) const @nogc if (isNumeric!V) { 450 auto result = copy(); 451 result.div(factor); 452 return result; 453 } 454 unittest { 455 assert(Vec2f(8f) / 4f == Vec2f(2f)); 456 } 457 458 /** 459 * Divides this vector by a factor. 460 * Params: 461 * factor = The factor to divide by. 462 * Returns: A reference to this vector. 463 */ 464 public ref MY_TYPE opOpAssign(string op : "/", V)(V factor) @nogc if (isNumeric!V) { 465 this.div(factor); 466 return this; 467 } 468 unittest { 469 Vec2f v = Vec2f(2f, 4f); 470 v /= 4f; 471 assert(v.data == [0.5f, 1f]); 472 } 473 474 /** 475 * Compares this vector to another, based on their magnitudes. 476 * Params: 477 * other = The vector to compare to. 478 * Returns: 0 if the vectors have equal magnitude, 1 if this vector's 479 * magnitude is bigger, and -1 if the other's is bigger. 480 */ 481 public int opCmp(MY_TYPE other) const @nogc { 482 double a = this.mag2(); 483 double b = other.mag2(); 484 if (a == b) return 0; 485 if (a < b) return -1; 486 return 1; 487 } 488 unittest { 489 Vec3i v1 = Vec3i(1); 490 Vec3i v2 = Vec3i(2); 491 Vec3i v3 = Vec3i(3); 492 assert(v1 == v1); 493 assert(v1 != v2); 494 assert(v1 < v2); 495 assert(v2 > v1); 496 assert(v3 > v2); 497 } 498 499 /** 500 * Gets a simple string representation of this vector. 501 * Returns: The string representation of this vector. 502 */ 503 public string toString() const { 504 import std.array : appender; 505 import std.conv : to; 506 auto s = appender!string; 507 s ~= "["; 508 static foreach (i; 0 .. size - 1) { 509 s ~= data[i].to!string; 510 s ~= ", "; 511 } 512 s ~= data[size - 1].to!string; 513 s ~= "]"; 514 return s[]; 515 } 516 517 static if (isFloatingPoint!T) { 518 /** 519 * [Normalizes](https://en.wikipedia.org/wiki/Unit_vector) this vector, 520 * such that it will have a magnitude of 1. 521 * Returns: A reference to this vector, for method chaining. 522 */ 523 public ref MY_TYPE norm() @nogc { 524 const double mag = mag(); 525 static foreach (i; 0 .. size) { 526 data[i] /= mag; 527 } 528 return this; 529 } 530 } 531 532 static if (isFloatingPoint!T && size == 2) { 533 /** 534 * Converts this 2-dimensional vector from [Cartesian](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) 535 * to [Polar](https://en.wikipedia.org/wiki/Polar_coordinate_system) 536 * coordinates. It is assumed that the first element is the **x** 537 * coordinate, and the second element is the **y** coordinate. The 538 * first element becomes the **radius** and the second becomes the 539 * angle **theta**. 540 * Returns: A reference to this vector, for method chaining. 541 */ 542 public ref MY_TYPE toPolar() @nogc { 543 import std.math : atan2; 544 T radius = mag(); 545 T angle = atan2(data[1], data[0]); 546 data[0] = radius; 547 data[1] = angle; 548 return this; 549 } 550 551 /** 552 * Converts this 2-dimensional vector from [Polar](https://en.wikipedia.org/wiki/Polar_coordinate_system) 553 * to [Cartesian](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) 554 * coordinates. It is assumed that the first element is the **radius** 555 * and the second element is the angle **theta**. The first element 556 * becomes the **x** coordinate, and the second becomes the **y** 557 * coordinate. 558 * Returns: A reference to this vector, for method chaining. 559 */ 560 public ref MY_TYPE toCartesian() @nogc { 561 import std.math : cos, sin; 562 T x = data[0] * cos(data[1]); 563 T y = data[0] * sin(data[1]); 564 data[0] = x; 565 data[1] = y; 566 return this; 567 } 568 } 569 570 static if (isFloatingPoint!T && size == 3) { 571 /** 572 * Computes the [cross product](https://en.wikipedia.org/wiki/Cross_product) of this vector and another, and stores 573 * the result in this vector. 574 * Params: 575 * other = The other vector. 576 * Returns: A reference to this vector, for method chaining. 577 */ 578 public ref MY_TYPE cross(MY_TYPE other) @nogc { 579 MY_TYPE tmp; 580 tmp[0] = data[1] * other[2] - data[2] * other[1]; 581 tmp[1] = data[2] * other[0] - data[0] * other[2]; 582 tmp[2] = data[0] * other[1] - data[1] * other[0]; 583 data[0] = tmp[0]; 584 data[1] = tmp[1]; 585 data[2] = tmp[2]; 586 return this; 587 } 588 } 589 } 590 591 unittest { 592 import std.stdio; 593 import std.math; 594 595 void assertFP(double actual, double expected, double delta = 1e-06, string msg = "Assertion failed.") { 596 double lowBound = expected - delta; 597 double highBound = expected + delta; 598 assert(actual > lowBound && actual < highBound, msg); 599 } 600 601 // Test floating-point specific methods. 602 auto v6 = Vec2f(3, 3); 603 v6.norm(); 604 assertFP(v6.mag, 1); 605 assertFP(v6[0], sqrt(2f) / 2f); 606 v6 = Vec2f(1, 0); 607 auto v6Copy = Vec2f(v6); 608 v6.norm(); 609 assert(v6.mag == 1); 610 assert(v6.data == v6Copy.data); 611 612 // Test toPolar 613 auto vCart = Vec2d(1, 0); 614 vCart.toPolar(); 615 assert(vCart.data == [1.0, 0.0]); 616 vCart.toCartesian(); 617 assert(vCart.data == [1.0, 0.0]); 618 vCart = Vec2d(1, 1); 619 vCart.toPolar(); 620 assertFP(vCart[0], sqrt(2f)); 621 assertFP(vCart[1], PI_4); 622 vCart.toCartesian(); 623 assertFP(vCart[0], 1); 624 assertFP(vCart[1], 1); 625 vCart = Vec2d(-1, 1); 626 vCart.toPolar(); 627 assertFP(vCart[0], sqrt(2f)); 628 assertFP(vCart[1], 3 * PI_4); 629 }