1 /** 2 * Type and dimension generic Vector backed by a static array. 3 */ 4 module bettercmath.vector; 5 6 import std.algorithm : among, copy, max, min, sum; 7 import std.range; 8 import std.traits : isFloatingPoint; 9 10 import bettercmath.cmath; 11 import bettercmath.misc : FloatType; 12 public import bettercmath.misc : lerp; 13 14 @safe @nogc nothrow: 15 16 version (unittest) 17 { 18 private alias Vec1 = Vector!(float, 1); 19 private alias Vec2 = Vector!(float, 2); 20 private alias Vec2i = Vector!(int, 2); 21 private alias Vec3 = Vector!(float, 3); 22 private alias Vec4 = Vector!(float, 4); 23 } 24 25 26 /** 27 * Generic Vector backed by a static array. 28 * 29 * Params: 30 * T = Element type 31 * N = Vector dimension, must be positive 32 */ 33 struct Vector(T, uint N) 34 if (N > 0) 35 { 36 pure: 37 /// Alias for Vector element type. 38 alias ElementType = T; 39 /// Vector dimension. 40 enum dimension = N; 41 /// Element array. 42 T[N] elements = 0; 43 alias elements this; 44 45 private ref inout(T) _get(size_t i)() inout 46 in { assert(i < N, "Index out of bounds"); } 47 do 48 { 49 return elements[i]; 50 } 51 private ref inout(T[to - from]) _slice(size_t from, size_t to)() inout 52 in { assert(from <= N - 1 && to <= N, "Index out of bounds"); } 53 do 54 { 55 return elements[from .. to]; 56 } 57 58 /// Get a reference to first element. 59 alias x = _get!(0); 60 alias r = x; /// ditto 61 alias u = x; /// ditto 62 alias s = x; /// ditto 63 64 static if (N > 1) 65 { 66 /// Get a reference to second element. 67 alias y = _get!(1); 68 alias g = y; /// ditto 69 alias v = y; /// ditto 70 alias t = y; /// ditto 71 } 72 static if (N > 2) 73 { 74 /// Get a reference to third element. 75 alias z = _get!(2); 76 alias b = z; /// ditto 77 alias p = z; /// ditto 78 79 /// Get a reference to the first and second elements. 80 alias xy = _slice!(0, 2); 81 alias rg = xy; /// ditto 82 alias uv = xy; /// ditto 83 alias st = xy; /// ditto 84 85 /// Get a reference to the second and third elements. 86 alias yz = _slice!(1, 3); 87 alias gb = yz; /// ditto 88 alias tp = yz; /// ditto 89 } 90 static if (N > 3) 91 { 92 /// Get a reference to fourth element. 93 alias w = _get!(3); 94 alias a = w; /// ditto 95 alias q = w; /// ditto 96 97 /// Get a reference to the third and fourth elements. 98 alias zw = _slice!(2, 4); 99 alias ba = zw; /// ditto 100 alias pq = zw; /// ditto 101 102 /// Get a reference to the first, second and third elements. 103 alias xyz = _slice!(0, 3); 104 alias rgb = xyz; /// ditto 105 alias stp = xyz; /// ditto 106 107 /// Get a reference to the second, third and fourth elements. 108 alias yzw = _slice!(1, 4); 109 alias gba = yzw; /// ditto 110 alias tpq = yzw; /// ditto 111 } 112 113 unittest 114 { 115 Vec2 v = [1, 2]; 116 assert(v.x == 1); 117 assert(v.x == v[0]); 118 assert(v.y == 2); 119 assert(v.y == v[1]); 120 v.x = 2; 121 assert(v.r == 2); 122 123 Vec4 v2 = [1, 2, 3, 4]; 124 assert(v2.xy == [1, 2]); 125 assert(v2.yz == [2, 3]); 126 assert(v2.zw == [3, 4]); 127 v2.xyz = 0; 128 assert(v2 == [0, 0, 0, 4]); 129 } 130 131 /// Constructs a Vector with all elements equal to `scalar` 132 this(const T scalar) 133 { 134 elements[] = scalar; 135 } 136 unittest 137 { 138 Vec2 v; 139 v = Vec2(1); 140 v = Vec2(1.0); 141 v = Vec2(1.0f); 142 } 143 /// Constructs a Vector from static array. 144 this()(const auto ref T[N] values) 145 { 146 elements = values; 147 } 148 unittest 149 { 150 Vec2 v; 151 v = Vec2([1, 2]); 152 v = Vec2([1.0, 2.0]); 153 v = Vec2([1.0f, 2.0f]); 154 } 155 /// Constructs a Vector with elements from an Input Range 156 this(R)(auto ref R range) 157 if (isInputRange!R) 158 { 159 auto remainder = range.copy(elements[]); 160 remainder[] = 0; 161 } 162 unittest 163 { 164 assert(Vec4(iota(4)) == [0, 1, 2, 3]); 165 assert(Vec4(iota(8).stride(2)) == [0, 2, 4, 6]); 166 assert(Vec4(iota(3)) == [0, 1, 2, 0]); 167 } 168 /// Constructs a Vector with all elements initialized separetely 169 this(Args...)(const auto ref Args args) 170 if (args.length <= N) 171 { 172 this(only(args)); 173 } 174 unittest 175 { 176 Vec2 v; 177 v = Vec2(1, 2); 178 v = Vec2(1, 2.0); 179 v = Vec2(1.0, 2); 180 v = Vec2(1.0, 2.0); 181 v = Vec2(1.0f, 2.0f); 182 } 183 184 /// Vector with all zeros 185 enum Vector zeros = 0; 186 alias zeroes = zeros; 187 /// Vector with all ones 188 enum Vector ones = 1; 189 190 /// Returns a new vector with unary operator applied to all elements 191 Vector opUnary(string op)() const 192 if (op.among("-", "+", "~")) 193 { 194 Vector result; 195 mixin(q{result =} ~ op ~ q{elements[];}); 196 return result; 197 } 198 unittest 199 { 200 assert(-Vec2(1, -2) == [-1, 2]); 201 } 202 203 /// Returns a new vector with binary operator applied to all elements and `scalar` 204 Vector opBinary(string op)(const T scalar) const 205 if (!op.among("~", "<<", ">>", ">>>")) 206 { 207 Vector result; 208 mixin(q{result = elements[]} ~ op ~ q{scalar;}); 209 return result; 210 } 211 unittest 212 { 213 Vec2 a = [1, 2]; 214 assert(a + 1 == [1f + 1f, 2f + 1f]); 215 assert(a - 1 == [1f - 1f, 2f - 1f]); 216 assert(a * 2 == [1f * 2f, 2f * 2f]); 217 assert(a / 2 == [1f / 2f, 2f / 2f]); 218 assert(a % 2 == [1f % 2f, 2f % 2f]); 219 assert(a ^^ 2 == [1f ^^ 2f, 2f ^^ 2f]); 220 221 Vec2i b = [1, 2]; 222 assert((b & 1) == [1 & 1, 2 & 1]); 223 assert((b | 1) == [1 | 1, 2 | 1]); 224 assert((b ^ 1) == [1 ^ 1, 2 ^ 1]); 225 } 226 // TODO: shift operations 227 228 /// Ditto 229 Vector opBinaryRight(string op)(const T scalar) const 230 if (!op.among("~", "<<", ">>", ">>>")) 231 { 232 Vector result; 233 mixin(q{result = scalar} ~ op ~ q{elements[];}); 234 return result; 235 } 236 unittest 237 { 238 Vec2 a = [1, 2]; 239 assert(1 + a == [1f + 1f, 1f + 2f]); 240 assert(1 - a == [1f - 1f, 1f - 2f]); 241 assert(2 * a == [2f * 1f, 2f * 2f]); 242 assert(2 / a == [2f / 1f, 2f / 2f]); 243 assert(2 % a == [2f % 1f, 2f % 2f]); 244 assert(2 ^^ a == [2f ^^ 1f, 2f ^^ 2f]); 245 246 Vec2i b = [1, 2]; 247 assert((1 & b) == [1 & 1, 1 & 2]); 248 assert((1 | b) == [1 | 1, 1 | 2]); 249 assert((1 ^ b) == [1 ^ 1, 1 ^ 2]); 250 } 251 252 /// Returns a new vector with the results of applying operator against elements of `other`. 253 /// If operands dimensions are unequal, copies the values from greater dimension vector. 254 Vector!(T, max(N, M)) opBinary(string op, uint M)(const auto ref T[M] other) const 255 if (op != "~") 256 { 257 enum minDimension = min(N, M); 258 typeof(return) result; 259 mixin(q{result[0 .. minDimension] = elements[0 .. minDimension]} ~ op ~ q{other[0 .. minDimension];}); 260 static if (M < N) 261 { 262 result[minDimension .. N] = elements[minDimension .. N]; 263 } 264 else static if (N < M) 265 { 266 result[minDimension .. M] = other[minDimension .. M]; 267 } 268 return result; 269 } 270 unittest 271 { 272 assert(Vec2(1, 2) + Vec2(3, 4) == [1f+3f, 2f+4f]); 273 assert(Vec2(1, 2) - Vec2(3, 4) == [1f-3f, 2f-4f]); 274 assert(Vec2(1, 2) * Vec2(3, 4) == [1f*3f, 2f*4f]); 275 assert(Vec2(1, 2) / Vec2(3, 4) == [1f/3f, 2f/4f]); 276 assert(__traits(compiles, Vec2(1, 2) + [3, 4])); 277 278 assert(Vec2(1, 2) + Vec1(3) == [1f+3f, 2f]); 279 assert(Vec2(1, 2) - Vec1(3) == [1f-3f, 2f]); 280 assert(Vec2(1, 2) * Vec1(3) == [1f*3f, 2f]); 281 assert(Vec2(1, 2) / Vec1(3) == [1f/3f, 2f]); 282 283 assert(Vec2(1, 2) + Vec3(3, 4, 5) == [1f+3f, 2f+4f, 5f]); 284 assert(Vec2(1, 2) - Vec3(3, 4, 5) == [1f-3f, 2f-4f, 5f]); 285 assert(Vec2(1, 2) * Vec3(3, 4, 5) == [1f*3f, 2f*4f, 5f]); 286 assert(Vec2(1, 2) / Vec3(3, 4, 5) == [1f/3f, 2f/4f, 5f]); 287 } 288 289 /// Returns a new vector of greater dimension by copying elements and appending `scalar`. 290 Vector!(T, N + 1) opBinary(string op : "~")(const T scalar) const 291 { 292 typeof(return) result; 293 result[0 .. N] = elements[]; 294 result[N] = scalar; 295 return result; 296 } 297 unittest 298 { 299 Vec2 v = [1, 2]; 300 assert(v ~ 3 == Vec3(1, 2, 3)); 301 } 302 /// Returns a new vector of greater dimension by copying elements and prepending `scalar`. 303 Vector!(T, N + 1) opBinaryRight(string op : "~")(const T scalar) const 304 { 305 typeof(return) result; 306 result[0] = scalar; 307 result[1 .. N + 1] = elements[]; 308 return result; 309 } 310 unittest 311 { 312 Vec2 v = [1, 2]; 313 Vec3 v2 = 0f ~ v; 314 assert(0 ~ v == Vec3(0, 1, 2)); 315 } 316 317 /// Returns a new vector of greater dimension by copying elements and appending values from `other`. 318 Vector!(T, N + M) opBinary(string op : "~", M)(const auto ref T[M] other) const 319 { 320 typeof(return) result = elements ~ other; 321 return result; 322 } 323 unittest 324 { 325 Vec2 v1 = [1, 2]; 326 assert(v1 ~ [3f, 4f] == Vec4(1, 2, 3, 4)); 327 assert(v1 ~ Vec2(3f, 4f) == Vec4(1, 2, 3, 4)); 328 } 329 /// Returns a new vector of greater dimension by copying elements and prepending values from `other`. 330 Vector!(T, N + M) opBinaryRight(string op : "~", M)(const auto ref T[M] other) const 331 { 332 typeof(return) result = other ~ elements; 333 return result; 334 } 335 unittest 336 { 337 Vec2 v1 = [1, 2]; 338 assert([3f, 4f] ~ v1 == Vec4(3, 4, 1, 2)); 339 assert(Vec2(3f, 4f) ~ v1 == Vec4(3, 4, 1, 2)); 340 } 341 342 /// Cast to a static array of same dimension, but different element type. 343 U opCast(U : T2[N], T2)() const 344 { 345 typeof(return) result; 346 foreach (i; 0 .. N) 347 { 348 result[i] = cast(T2) elements[i]; 349 } 350 return result; 351 } 352 unittest 353 { 354 Vec2i intVec = [1, 2]; 355 auto floatVec = cast(Vec2) intVec; 356 assert(floatVec == Vec2(1f, 2f)); 357 assert(cast(Vec2i) floatVec == intVec); 358 359 auto floatArray = cast(float[2]) intVec; 360 assert(floatArray == [1f, 2f]); 361 } 362 363 /// Assign result of applying operator with `scalar` to elements. 364 ref Vector opOpAssign(string op)(const T scalar) return 365 { 366 mixin(q{elements[] } ~ op ~ q{= scalar;}); 367 return this; 368 } 369 unittest 370 { 371 Vec2 v = [1, 2]; 372 v += 5; 373 assert(v == [6, 7]); 374 } 375 /// Assign result of applying operator with `other` to elements. 376 ref Vector opOpAssign(string op, uint M)(const auto ref T[M] other) return 377 { 378 enum minDimension = min(N, M); 379 mixin(q{elements[0 .. minDimension] } ~ op ~ q{= other[0 .. minDimension];}); 380 return this; 381 } 382 unittest 383 { 384 Vec3 v = [1, 2, 3]; 385 v += Vec2(1, 2); 386 assert(v == [2, 4, 3]); 387 388 v += Vec4(1, 2, 3, 4); 389 assert(v == [3, 6, 6]); 390 } 391 392 unittest 393 { 394 assert(Vec2.sizeof == Vec2.elements.sizeof); 395 assert(Vec3.sizeof == Vec3.elements.sizeof); 396 assert(Vec4.sizeof == Vec4.elements.sizeof); 397 398 alias Vec2_100 = Vec2[100]; 399 assert(Vec2_100.sizeof == 100 * Vec2.elements.sizeof); 400 401 auto v = Vec2(5); 402 v += 3; 403 assert(v == Vec2(8)); 404 } 405 } 406 407 /// True if `T` is some kind of Vector 408 enum isVector(T) = is(T : Vector!U, U...); 409 410 /// Construct Vector directly from static array, inferring element type. 411 Vector!(T, N) vector(T, uint N)(const auto ref T[N] elements) 412 { 413 return typeof(return)(elements); 414 } 415 unittest 416 { 417 auto v = [1, 2, 3].vector; 418 assert(v.elements == [1, 2, 3]); 419 assert(is(typeof(v) == Vector!(int, 3))); 420 } 421 422 /// Returns the dot product between two Vectors. 423 pure T dot(T, uint N)(const auto ref Vector!(T, N) a, const auto ref Vector!(T, N) b) 424 { 425 auto multiplied = a * b; 426 return multiplied[].sum; 427 } 428 429 /// Returns the cross product between two 3D Vectors. 430 pure Vector!(T, 3) cross(T)(const auto ref Vector!(T, 3) a, const auto ref Vector!(T, 3) b) 431 { 432 typeof(return) result = [ 433 a.y * b.z - a.z * b.y, 434 a.z * b.x - a.x * b.z, 435 a.x * b.y - a.y * b.x, 436 ]; 437 return result; 438 } 439 440 /// Returns a Vector that is the reflection of `vec` against `normal`. 441 pure Vector!(T, N) reflect(T, uint N)(const auto ref Vector!(T, N) vec, const auto ref Vector!(T, N) normal) 442 { 443 return vec - (2 * normal * dot(vec, normal)); 444 } 445 446 /// Returns the squared magnitude (Euclidean length) of a Vector. 447 pure T magnitudeSquared(T, uint N)(const auto ref Vector!(T, N) vec) 448 out (r) { assert(r >= 0, "Vector squared magnitude should be non-negative!"); } 449 do 450 { 451 return dot(vec, vec); 452 } 453 unittest 454 { 455 assert(Vec2(0, 0).magnitudeSquared() == 0); 456 assert(Vec2(1, 0).magnitudeSquared() == 1); 457 assert(Vec2(0, 1).magnitudeSquared() == 1); 458 assert(Vec2(1, 1).magnitudeSquared() == 2); 459 assert(Vec2(2, 0).magnitudeSquared() == 4); 460 assert(Vec2(1, 2).magnitudeSquared() == 5); 461 } 462 463 /// Returns the magnitude (Euclidean length) of a Vector. 464 auto magnitude(T, uint N)(const auto ref Vector!(T, N) vec) 465 out (r) { assert(r >= 0, "Vector magnitude should be non-negative!"); } 466 do 467 { 468 return sqrt(vec.magnitudeSquared()); 469 } 470 unittest 471 { 472 assert(Vec2(0, 0).magnitude() == 0); 473 assert(Vec2(1, 0).magnitude() == 1); 474 assert(Vec2(0, 1).magnitude() == 1); 475 assert(Vec2(1, 1).magnitude() == sqrt(2f)); 476 assert(Vec2(2, 0).magnitude() == 2); 477 } 478 479 Vector!(typeof(mapfunc(cast(T) 0)), N) map(alias mapfunc, T, uint N)(const auto ref Vector!(T, N) vec) 480 { 481 typeof(return) result; 482 foreach (i; 0 .. N) 483 { 484 result[i] = mapfunc(vec[i]); 485 } 486 return result; 487 } 488 unittest 489 { 490 Vec3 v = [-1, 2.5, -3]; 491 import std.math : abs, floor; 492 assert(v.map!(abs) == Vec3(1, 2.5, 3)); 493 assert(v.map!(floor) == Vec3(-1, 2, -3)); 494 assert(v.map!(x => x + 1) == Vec3(-1 + 1, 2.5 + 1, -3 + 1)); 495 } 496 497 /// Normalize a Vector inplace. 498 ref Vector!(T, N) normalize(T, uint N)(ref return Vector!(T, N) vec) 499 { 500 auto sqMag = vec.magnitudeSquared(); 501 if (sqMag != 0) 502 { 503 enum FloatType!T one = 1; 504 const auto inverseMag = one / sqrt(sqMag); 505 vec *= inverseMag; 506 } 507 return vec; 508 } 509 unittest 510 { 511 Vec2 v = [5, 0]; 512 v.normalize(); 513 assert(v == Vec2(1, 0)); 514 } 515 516 /// Returns a normalized copy of Vector. 517 Vector!(T, N) normalized(T, uint N)(const auto ref Vector!(T, N) vec) 518 { 519 typeof(return) copy = vec; 520 return copy.normalize(); 521 } 522 unittest 523 { 524 Vec2 v = [200, 0]; 525 assert(v.normalized() == Vec2(1, 0)); 526 assert(v == Vec2(200, 0)); 527 } 528 529 unittest 530 { 531 Vec2 a = [1, 1]; 532 Vec2 b = [2, 3]; 533 assert(lerp(a, b, 0) == a); 534 assert(lerp(a, b, 0.5) == Vec2(1.5, 2)); 535 assert(lerp(a, b, 1) == b); 536 }