1 module gfm.math.vector; 2 3 import std.traits, 4 std.math, 5 std.conv, 6 std.array, 7 std.string; 8 9 import gfm.math.funcs; 10 11 /** 12 * Generic 1D small vector. 13 * Params: 14 * N = number of elements 15 * T = type of elements 16 */ 17 struct Vector(T, int N) 18 { 19 nothrow: 20 public 21 { 22 static assert(N >= 1); 23 24 // fields definition 25 union 26 { 27 T[N] v; 28 struct 29 { 30 static if (N >= 1) 31 { 32 T x; 33 alias x r; 34 } 35 static if (N >= 2) 36 { 37 T y; 38 alias y g; 39 } 40 static if (N >= 3) 41 { 42 T z; 43 alias z b; 44 } 45 static if (N >= 4) 46 { 47 T w; 48 alias w a; 49 } 50 } 51 } 52 53 @nogc this(Args...)(Args args) pure nothrow 54 { 55 static if (args.length == 1) 56 { 57 // Construct a Vector from a single value. 58 opAssign!(Args[0])(args[0]); 59 } 60 else 61 { 62 // validate the total argument count across scalars and vectors 63 template argCount(T...) { 64 static if(T.length == 0) 65 enum argCount = 0; // done recursing 66 else static if(isVector!(T[0])) 67 enum argCount = T[0]._N + argCount!(T[1..$]); 68 else 69 enum argCount = 1 + argCount!(T[1..$]); 70 } 71 72 static assert(argCount!Args <= N, "Too many arguments in vector constructor"); 73 74 int index = 0; 75 foreach(arg; args) 76 { 77 static if (isAssignable!(T, typeof(arg))) 78 { 79 v[index] = arg; 80 index++; // has to be on its own line (DMD 2.068) 81 } 82 else static if (isVector!(typeof(arg)) && isAssignable!(T, arg._T)) 83 { 84 mixin(generateLoopCode!("v[index + @] = arg[@];", arg._N)()); 85 index += arg._N; 86 } 87 else 88 static assert(false, "Unrecognized argument in Vector constructor"); 89 } 90 assert(index == N, "Bad arguments in Vector constructor"); 91 } 92 } 93 94 /// Assign a Vector from a compatible type. 95 @nogc ref Vector opAssign(U)(U x) pure nothrow if (isAssignable!(T, U)) 96 { 97 mixin(generateLoopCode!("v[@] = x;", N)()); // copy to each component 98 return this; 99 } 100 101 /// Assign a Vector with a static array type. 102 @nogc ref Vector opAssign(U)(U arr) pure nothrow if ((isStaticArray!(U) && isAssignable!(T, typeof(arr[0])) && (arr.length == N))) 103 { 104 mixin(generateLoopCode!("v[@] = arr[@];", N)()); 105 return this; 106 } 107 108 /// Assign with a dynamic array. 109 /// Size is checked in debug-mode. 110 @nogc ref Vector opAssign(U)(U arr) pure nothrow if (isDynamicArray!(U) && isAssignable!(T, typeof(arr[0]))) 111 { 112 assert(arr.length == N); 113 mixin(generateLoopCode!("v[@] = arr[@];", N)()); 114 return this; 115 } 116 117 /// Assign from a samey Vector. 118 @nogc ref Vector opAssign(U)(U u) pure nothrow if (is(U : Vector)) 119 { 120 v[] = u.v[]; 121 return this; 122 } 123 124 /// Assign from other vectors types (same size, compatible type). 125 @nogc ref Vector opAssign(U)(U x) pure nothrow if (isVector!U 126 && isAssignable!(T, U._T) 127 && (!is(U: Vector)) 128 && (U._N == _N)) 129 { 130 mixin(generateLoopCode!("v[@] = x.v[@];", N)()); 131 return this; 132 } 133 134 /// Returns: a pointer to content. 135 @nogc inout(T)* ptr() pure inout nothrow @property 136 { 137 return v.ptr; 138 } 139 140 /// Converts to a pretty string. 141 string toString() const nothrow 142 { 143 try 144 return format("%s", v); 145 catch (Exception e) 146 assert(false); // should not happen since format is right 147 } 148 149 @nogc bool opEquals(U)(U other) pure const nothrow 150 if (is(U : Vector)) 151 { 152 for (int i = 0; i < N; ++i) 153 { 154 if (v[i] != other.v[i]) 155 { 156 return false; 157 } 158 } 159 return true; 160 } 161 162 @nogc bool opEquals(U)(U other) pure const nothrow 163 if (isConvertible!U) 164 { 165 Vector conv = other; 166 return opEquals(conv); 167 } 168 169 @nogc Vector opUnary(string op)() pure const nothrow 170 if (op == "+" || op == "-" || op == "~" || op == "!") 171 { 172 Vector res = void; 173 mixin(generateLoopCode!("res.v[@] = " ~ op ~ " v[@];", N)()); 174 return res; 175 } 176 177 @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow 178 if (is(U : Vector)) 179 { 180 mixin(generateLoopCode!("v[@] " ~ op ~ "= operand.v[@];", N)()); 181 return this; 182 } 183 184 @nogc ref Vector opOpAssign(string op, U)(U operand) pure nothrow if (isConvertible!U) 185 { 186 Vector conv = operand; 187 return opOpAssign!op(conv); 188 } 189 190 @nogc Vector opBinary(string op, U)(U operand) pure const nothrow 191 if (is(U: Vector) || (isConvertible!U)) 192 { 193 Vector result = void; 194 static if (is(U: T)) 195 mixin(generateLoopCode!("result.v[@] = cast(T)(v[@] " ~ op ~ " operand);", N)()); 196 else 197 { 198 Vector other = operand; 199 mixin(generateLoopCode!("result.v[@] = cast(T)(v[@] " ~ op ~ " other.v[@]);", N)()); 200 } 201 return result; 202 } 203 204 @nogc Vector opBinaryRight(string op, U)(U operand) pure const nothrow if (isConvertible!U) 205 { 206 Vector result = void; 207 static if (is(U: T)) 208 mixin(generateLoopCode!("result.v[@] = cast(T)(operand " ~ op ~ " v[@]);", N)()); 209 else 210 { 211 Vector other = operand; 212 mixin(generateLoopCode!("result.v[@] = cast(T)(other.v[@] " ~ op ~ " v[@]);", N)()); 213 } 214 return result; 215 } 216 217 @nogc ref T opIndex(size_t i) pure nothrow 218 { 219 return v[i]; 220 } 221 222 @nogc ref const(T) opIndex(size_t i) pure const nothrow 223 { 224 return v[i]; 225 } 226 227 @nogc T opIndexAssign(U : T)(U x, size_t i) pure nothrow 228 { 229 return v[i] = x; 230 } 231 232 233 /// Implements swizzling. 234 /// 235 /// Example: 236 /// --- 237 /// vec4i vi = [4, 1, 83, 10]; 238 /// assert(vi.zxxyw == [83, 4, 4, 1, 10]); 239 /// --- 240 @nogc @property auto opDispatch(string op, U = void)() pure const nothrow if (isValidSwizzle!(op)) 241 { 242 alias Vector!(T, op.length) returnType; 243 returnType res = void; 244 enum indexTuple = swizzleTuple!op; 245 foreach(i, index; indexTuple) 246 res.v[i] = v[index]; 247 return res; 248 } 249 250 /// Support swizzling assignment like in shader languages. 251 /// 252 /// Example: 253 /// --- 254 /// vec3f v = [0, 1, 2]; 255 /// v.yz = v.zx; 256 /// assert(v == [0, 2, 0]); 257 /// --- 258 @nogc @property void opDispatch(string op, U)(U x) pure 259 if ((op.length >= 2) 260 && (isValidSwizzleUnique!op) // v.xyy will be rejected 261 && is(typeof(Vector!(T, op.length)(x)))) // can be converted to a small vector of the right size 262 { 263 Vector!(T, op.length) conv = x; 264 enum indexTuple = swizzleTuple!op; 265 foreach(i, index; indexTuple) 266 v[index] = conv[i]; 267 } 268 269 /// Casting to small vectors of the same size. 270 /// Example: 271 /// --- 272 /// vec4f vf; 273 /// vec4d vd = cast!(vec4d)vf; 274 /// --- 275 @nogc U opCast(U)() pure const nothrow if (isVector!U && (U._N == _N)) 276 { 277 U res = void; 278 mixin(generateLoopCode!("res.v[@] = cast(U._T)v[@];", N)()); 279 return res; 280 } 281 282 /// Implement slices operator overloading. 283 /// Allows to go back to slice world. 284 /// Returns: length. 285 @nogc int opDollar() pure const nothrow 286 { 287 return N; 288 } 289 290 /// Returns: a slice which covers the whole Vector. 291 @nogc T[] opSlice() pure nothrow 292 { 293 return v[]; 294 } 295 296 // vec[a..b] 297 @nogc T[] opSlice(int a, int b) pure nothrow 298 { 299 return v[a..b]; 300 } 301 302 /// Returns: squared length. 303 @nogc T squaredLength() pure const nothrow 304 { 305 T sumSquares = 0; 306 mixin(generateLoopCode!("sumSquares += v[@] * v[@];", N)()); 307 return sumSquares; 308 } 309 310 // Returns: squared Euclidean distance. 311 @nogc T squaredDistanceTo(Vector v) pure const nothrow 312 { 313 return (v - this).squaredLength(); 314 } 315 316 static if (isFloatingPoint!T) 317 { 318 /// Returns: Euclidean length 319 @nogc T length() pure const nothrow 320 { 321 return sqrt(squaredLength()); 322 } 323 324 /// Returns: Inverse of Euclidean length. 325 @nogc T inverseLength() pure const nothrow 326 { 327 return 1 / sqrt(squaredLength()); 328 } 329 330 /// Faster but less accurate inverse of Euclidean length. 331 /// Returns: Inverse of Euclidean length. 332 @nogc T fastInverseLength() pure const nothrow 333 { 334 return inverseSqrt(squaredLength()); 335 } 336 337 /// Returns: Euclidean distance between this and other. 338 @nogc T distanceTo(Vector other) pure const nothrow 339 { 340 return (other - this).length(); 341 } 342 343 /// In-place normalization. 344 @nogc void normalize() pure nothrow 345 { 346 auto invLength = inverseLength(); 347 mixin(generateLoopCode!("v[@] *= invLength;", N)()); 348 } 349 350 /// Returns: Normalized vector. 351 @nogc Vector normalized() pure const nothrow 352 { 353 Vector res = this; 354 res.normalize(); 355 return res; 356 } 357 358 /// Faster but less accurate in-place normalization. 359 @nogc void fastNormalize() pure nothrow 360 { 361 auto invLength = fastInverseLength(); 362 mixin(generateLoopCode!("v[@] *= invLength;", N)()); 363 } 364 365 /// Faster but less accurate vector normalization. 366 /// Returns: Normalized vector. 367 @nogc Vector fastNormalized() pure const nothrow 368 { 369 Vector res = this; 370 res.fastNormalize(); 371 return res; 372 } 373 374 static if (N == 3) 375 { 376 /// Gets an orthogonal vector from a 3-dimensional vector. 377 /// Doesn’t normalize the output. 378 /// Authors: Sam Hocevar 379 /// See_also: Source at $(WEB lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts). 380 @nogc Vector getOrthogonalVector() pure const nothrow 381 { 382 return abs(x) > abs(z) ? Vector(-y, x, 0.0) : Vector(0.0, -z, y); 383 } 384 } 385 } 386 } 387 388 private 389 { 390 enum _N = N; 391 alias T _T; 392 393 // define types that can be converted to this, but are not the same type 394 template isConvertible(T) 395 { 396 enum bool isConvertible = (!is(T : Vector)) 397 && is(typeof( 398 { 399 T x; 400 Vector v = x; 401 }())); 402 } 403 404 // define types that can't be converted to this 405 template isForeign(T) 406 { 407 enum bool isForeign = (!isConvertible!T) && (!is(T: Vector)); 408 } 409 410 template isValidSwizzle(string op, int lastSwizzleClass = -1) 411 { 412 static if (op.length == 0) 413 enum bool isValidSwizzle = true; 414 else 415 { 416 enum len = op.length; 417 enum int swizzleClass = swizzleClassify!(op[0]); 418 enum bool swizzleClassValid = (lastSwizzleClass == -1 || (swizzleClass == lastSwizzleClass)); 419 enum bool isValidSwizzle = (swizzleIndex!(op[0]) != -1) 420 && swizzleClassValid 421 && isValidSwizzle!(op[1..len], swizzleClass); 422 } 423 } 424 425 template searchElement(char c, string s) 426 { 427 static if (s.length == 0) 428 { 429 enum bool result = false; 430 } 431 else 432 { 433 enum string tail = s[1..s.length]; 434 enum bool result = (s[0] == c) || searchElement!(c, tail).result; 435 } 436 } 437 438 template hasNoDuplicates(string s) 439 { 440 static if (s.length == 1) 441 { 442 enum bool result = true; 443 } 444 else 445 { 446 enum tail = s[1..s.length]; 447 enum bool result = !(searchElement!(s[0], tail).result) && hasNoDuplicates!(tail).result; 448 } 449 } 450 451 // true if the swizzle has at the maximum one time each letter 452 template isValidSwizzleUnique(string op) 453 { 454 static if (isValidSwizzle!op) 455 enum isValidSwizzleUnique = hasNoDuplicates!op.result; 456 else 457 enum bool isValidSwizzleUnique = false; 458 } 459 460 template swizzleIndex(char c) 461 { 462 static if((c == 'x' || c == 'r') && N >= 1) 463 enum swizzleIndex = 0; 464 else static if((c == 'y' || c == 'g') && N >= 2) 465 enum swizzleIndex = 1; 466 else static if((c == 'z' || c == 'b') && N >= 3) 467 enum swizzleIndex = 2; 468 else static if ((c == 'w' || c == 'a') && N >= 4) 469 enum swizzleIndex = 3; 470 else 471 enum swizzleIndex = -1; 472 } 473 474 template swizzleClassify(char c) 475 { 476 static if(c == 'x' || c == 'y' || c == 'z' || c == 'w') 477 enum swizzleClassify = 0; 478 else static if(c == 'r' || c == 'g' || c == 'b' || c == 'a') 479 enum swizzleClassify = 1; 480 else 481 enum swizzleClassify = -1; 482 } 483 484 template swizzleTuple(string op) 485 { 486 enum opLength = op.length; 487 static if (op.length == 0) 488 enum swizzleTuple = []; 489 else 490 enum swizzleTuple = [ swizzleIndex!(op[0]) ] ~ swizzleTuple!(op[1..op.length]); 491 } 492 } 493 } 494 495 /// True if `T` is some kind of `Vector` 496 enum isVector(T) = is(T : Vector!U, U...); 497 498 // Previous name, but the alias doesn't seem to show deprecation messages 499 deprecated("Use isVector instead") alias isVectorInstantiation(T) = isVector!T; 500 501 /// 502 unittest 503 { 504 static assert(isVector!vec2f); 505 static assert(isVector!vec3d); 506 static assert(isVector!(vec4!real)); 507 static assert(!isVector!float); 508 } 509 510 /// Get the numeric type used to measure a vectors's coordinates. 511 alias DimensionType(T : Vector!U, U...) = U[0]; 512 513 /// 514 unittest 515 { 516 static assert(is(DimensionType!vec2f == float)); 517 static assert(is(DimensionType!vec3d == double)); 518 } 519 520 template vec2(T) { alias Vector!(T, 2) vec2; } 521 template vec3(T) { alias Vector!(T, 3) vec3; } 522 template vec4(T) { alias Vector!(T, 4) vec4; } 523 524 alias vec2!byte vec2b; 525 alias vec2!ubyte vec2ub; 526 alias vec2!short vec2s; 527 alias vec2!ushort vec2us; 528 alias vec2!int vec2i; 529 alias vec2!uint vec2ui; 530 alias vec2!long vec2l; 531 alias vec2!ulong vec2ul; 532 alias vec2!float vec2f; 533 alias vec2!double vec2d; 534 535 alias vec3!byte vec3b; 536 alias vec3!ubyte vec3ub; 537 alias vec3!short vec3s; 538 alias vec3!ushort vec3us; 539 alias vec3!int vec3i; 540 alias vec3!uint vec3ui; 541 alias vec3!long vec3l; 542 alias vec3!ulong vec3ul; 543 alias vec3!float vec3f; 544 alias vec3!double vec3d; 545 546 alias vec4!byte vec4b; 547 alias vec4!ubyte vec4ub; 548 alias vec4!short vec4s; 549 alias vec4!ushort vec4us; 550 alias vec4!int vec4i; 551 alias vec4!uint vec4ui; 552 alias vec4!long vec4l; 553 alias vec4!ulong vec4ul; 554 alias vec4!float vec4f; 555 alias vec4!double vec4d; 556 557 private 558 { 559 static string generateLoopCode(string formatString, int N)() pure nothrow 560 { 561 string result; 562 for (int i = 0; i < N; ++i) 563 { 564 string index = ctIntToString(i); 565 // replace all @ by indices 566 result ~= formatString.replace("@", index); 567 } 568 return result; 569 } 570 571 // Speed-up CTFE conversions 572 static string ctIntToString(int n) pure nothrow 573 { 574 static immutable string[16] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; 575 if (n < 10) 576 return table[n]; 577 else 578 return to!string(n); 579 } 580 } 581 582 583 /// Element-wise minimum. 584 @nogc Vector!(T, N) minByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 585 { 586 import std.algorithm: min; 587 Vector!(T, N) res = void; 588 mixin(generateLoopCode!("res.v[@] = min(a.v[@], b.v[@]);", N)()); 589 return res; 590 } 591 592 /// Element-wise maximum. 593 @nogc Vector!(T, N) maxByElem(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 594 { 595 import std.algorithm: max; 596 Vector!(T, N) res = void; 597 mixin(generateLoopCode!("res.v[@] = max(a.v[@], b.v[@]);", N)()); 598 return res; 599 } 600 601 /// Element-wise absolute value. 602 @nogc Vector!(T, N) absByElem(T, int N)(const Vector!(T, N) a) pure nothrow 603 { 604 Vector!(T, N) res = void; 605 mixin(generateLoopCode!("res.v[@] = abs(a.v[@]);", N)()); 606 return res; 607 } 608 609 /// Returns: Dot product. 610 @nogc T dot(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 611 { 612 T sum = 0; 613 mixin(generateLoopCode!("sum += a.v[@] * b.v[@];", N)()); 614 return sum; 615 } 616 617 /// Returns: 3D cross product. 618 /// Thanks to vuaru for corrections. 619 @nogc Vector!(T, 3) cross(T)(const Vector!(T, 3) a, const Vector!(T, 3) b) pure nothrow 620 { 621 return Vector!(T, 3)(a.y * b.z - a.z * b.y, 622 a.z * b.x - a.x * b.z, 623 a.x * b.y - a.y * b.x); 624 } 625 626 /// 3D reflect, like the GLSL function. 627 /// Returns: a reflected by normal b. 628 @nogc Vector!(T, N) reflect(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 629 { 630 return a - (2 * dot(b, a)) * b; 631 } 632 633 /// 634 @nogc unittest 635 { 636 // reflect a 2D vector across the x axis (the normal points along the y axis) 637 assert(vec2f(1,1).reflect(vec2f(0,1)) == vec2f(1,-1)); 638 assert(vec2f(1,1).reflect(vec2f(0,-1)) == vec2f(1,-1)); 639 640 // note that the normal must be, well, normalized: 641 assert(vec2f(1,1).reflect(vec2f(0,20)) != vec2f(1,-1)); 642 643 // think of this like a ball hitting a flat floor at an angle. 644 // the x and y components remain unchanged, and the z inverts 645 assert(vec3f(2,3,-0.5).reflect(vec3f(0,0,1)) == vec3f(2,3,0.5)); 646 } 647 648 /// Returns: angle between vectors. 649 /// See_also: "The Right Way to Calculate Stuff" at $(WEB www.plunk.org/~hatch/rightway.php) 650 @nogc T angleBetween(T, int N)(const Vector!(T, N) a, const Vector!(T, N) b) pure nothrow 651 { 652 auto aN = a.normalized(); 653 auto bN = b.normalized(); 654 auto dp = dot(aN, bN); 655 656 if (dp < 0) 657 return PI - 2 * asin((-bN-aN).length / 2); 658 else 659 return 2 * asin((bN-aN).length / 2); 660 } 661 662 static assert(vec2f.sizeof == 8); 663 static assert(vec3d.sizeof == 24); 664 static assert(vec4i.sizeof == 16); 665 666 667 unittest 668 { 669 static assert(vec2i.isValidSwizzle!"xyx"); 670 static assert(!vec2i.isValidSwizzle!"xyz"); 671 static assert(vec4i.isValidSwizzle!"brra"); 672 static assert(!vec4i.isValidSwizzle!"rgyz"); 673 static assert(vec2i.isValidSwizzleUnique!"xy"); 674 static assert(vec2i.isValidSwizzleUnique!"yx"); 675 static assert(!vec2i.isValidSwizzleUnique!"xx"); 676 677 assert(vec2l(0, 1) == vec2i(0, 1)); 678 679 int[2] arr = [0, 1]; 680 int[] arr2 = new int[2]; 681 arr2[] = arr[]; 682 vec2i a = vec2i([0, 1]); 683 vec2i a2 = vec2i(0, 1); 684 immutable vec2i b = vec2i(0); 685 assert(b[0] == 0 && b[1] == 0); 686 vec2i c = arr; 687 vec2l d = arr2; 688 assert(a == a2); 689 assert(a == c); 690 assert(vec2l(a) == vec2l(a)); 691 assert(vec2l(a) == d); 692 693 vec4i x = [4, 5, 6, 7]; 694 assert(x == x); 695 --x[0]; 696 assert(x[0] == 3); 697 ++x[0]; 698 assert(x[0] == 4); 699 x[1] &= 1; 700 x[2] = 77 + x[2]; 701 x[3] += 3; 702 assert(x == [4, 1, 83, 10]); 703 assert(x.xxywz == [4, 4, 1, 10, 83]); 704 assert(x.xxxxxxx == [4, 4, 4, 4, 4, 4, 4]); 705 assert(x.abgr == [10, 83, 1, 4]); 706 assert(a != b); 707 x = vec4i(x.xyz, 166); 708 assert(x == [4, 1, 83, 166]); 709 710 vec2l e = a; 711 vec2l f = a + b; 712 assert(f == vec2l(a)); 713 714 vec3ui g = vec3i(78,9,4); 715 g ^= vec3i(78,9,4); 716 assert(g == vec3ui(0)); 717 //g[0..2] = 1u; 718 //assert(g == [2, 1, 0]); 719 720 assert(vec2i(4, 5) + 1 == vec2i(5,6)); 721 assert(vec2i(4, 5) - 1 == vec2i(3,4)); 722 assert(1 + vec2i(4, 5) == vec2i(5,6)); 723 assert(vec3f(1,1,1) * 0 == 0); 724 assert(1.0 * vec3d(4,5,6) == vec3f(4,5.0f,6.0)); 725 726 auto dx = vec2i(1,2); 727 auto dy = vec2i(4,5); 728 auto dp = dot(dx, dy); 729 assert(dp == 14 ); 730 731 vec3i h = cast(vec3i)(vec3d(0.5, 1.1, -2.2)); 732 assert(h == [0, 1, -2]); 733 assert(h[] == [0, 1, -2]); 734 assert(h[1..3] == [1, -2]); 735 assert(h.zyx == [-2, 1, 0]); 736 737 h.yx = vec2i(5, 2); // swizzle assignment 738 739 assert(h.xy == [2, 5]); 740 assert(-h[1] == -5); 741 assert(++h[0] == 3); 742 743 //assert(h == [-2, 1, 0]); 744 assert(!__traits(compiles, h.xx = h.yy)); 745 vec4ub j; 746 747 assert(lerp(vec2f(-10, -1), vec2f(10, 1), 0.5) == vec2f(0, 0)); 748 749 // larger vectors 750 alias Vector!(float, 5) vec5f; 751 vec5f l = vec5f(1, 2.0f, 3.0, 4u, 5.0L); 752 l = vec5f(l.xyz, vec2i(1, 2)); 753 754 // the ctor should not compile if given too many arguments 755 static assert(!is(typeof(vec2f(1, 2, 3)))); 756 static assert(!is(typeof(vec2f(vec2f(1, 2), 3)))); 757 static assert( is(typeof(vec3f(vec2f(1, 2), 3)))); 758 static assert( is(typeof(vec3f(1, 2, 3)))); 759 760 assert(absByElem(vec3i(-1, 0, 2)) == vec3i(1, 0, 2)); 761 } 762