1 module sbylib.math.vector; 2 3 import std.traits; 4 import std.algorithm; 5 import std.range : isInputRange, RangeElementType = ElementType; 6 7 alias vec2 = Vector!(float, 2); 8 alias vec3 = Vector!(float, 3); 9 alias vec4 = Vector!(float, 4); 10 alias ivec2 = Vector!(int, 2); 11 alias ivec3 = Vector!(int, 3); 12 alias ivec4 = Vector!(int, 4); 13 14 enum isVector(T) = isInstanceOf!(Vector, T); 15 16 /** 17 Vector type 18 */ 19 struct Vector(T, uint S) 20 if (__traits(isArithmetic, T)) 21 { 22 23 private T[S] elements; 24 25 /** 26 Dimension of this vector type 27 */ 28 enum Dimension = S; 29 30 /** 31 Element type of this vector type 32 */ 33 alias ElementType = T; 34 35 alias array this; 36 37 /** 38 Constructor by element type. 39 This vector's each element is filled by given value. 40 41 Params: 42 e = a value which fills the vector 43 */ 44 this(T e) { 45 assignSingle(e); 46 } 47 48 unittest { 49 import std.random : uniform; 50 51 foreach (k; 0..100) { 52 const e = uniform(-1.0f, +1.0f); 53 const v = vec3(e); 54 static foreach (i; 0..3) { 55 assert(v[i] == e); 56 } 57 } 58 } 59 60 /** 61 Constructor by InputRange. 62 This vector's all elements are filled by given range. 63 64 Params: 65 elements = values which fills the vector 66 */ 67 this(Range)(Range r) 68 if (isInputRange!(Range) && is(RangeElementType!(Range) : T)) 69 { 70 import std.range : front, popFront, empty; 71 72 static foreach (i; 0..S) { 73 this.elements[i] = r.front; 74 r.popFront; 75 } 76 assert(r.empty); 77 } 78 79 unittest { 80 import std.range : iota; 81 assert(vec3(iota(3)) == vec3(0,1,2)); 82 } 83 84 /** 85 Constructor by combination of scalar, array, or vector. 86 87 Params: 88 args = combination of values, whose total element count must be the same as S 89 */ 90 this(Args...)(Args args) { 91 assignAll(args); 92 } 93 94 unittest { 95 const v = Vector!(float,5)(1,2,3, vec2(4,5)); 96 assert(v == Vector!(float,5)(1,2,3,4,5)); 97 } 98 99 /** 100 Unary operation for "+". 101 This function is identity. 102 103 Returns: a vector equals to this vector. 104 */ 105 Vector opUnary(string op)() const 106 if (op == "+") 107 { 108 return this; 109 } 110 111 unittest { 112 import std.algorithm : map; 113 import std.range : iota; 114 import std.random : uniform; 115 116 foreach (i; 0..100) { 117 const v = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 118 assert(approxEqual(v * +1, +v)); 119 } 120 } 121 122 /** 123 Unary operation for "-". 124 125 Returns: an inverted vector 126 */ 127 Vector opUnary(string op)() const 128 if (op == "-") 129 { 130 return makeVector!(op ~ "elements[]"); 131 } 132 133 unittest { 134 import std.algorithm : map; 135 import std.range : iota; 136 import std.random : uniform; 137 138 foreach (i; 0..100) { 139 const v = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 140 assert(approxEqual(v * -1, -v)); 141 } 142 } 143 144 /** 145 Binary operation between scalar, array or vector type. 146 147 Params: 148 value = operation target 149 150 Returns: calculated vector 151 */ 152 Vector opBinary(string op, AnotherType)(auto ref AnotherType value) const 153 { 154 return makeVector!("elements[]" ~ op ~ Expression!(AnotherType))(value); 155 } 156 157 unittest { 158 import std.algorithm : map; 159 import std.range : iota; 160 import std.array : array; 161 import std.random : uniform; 162 import std.math : approxEqual; 163 164 foreach (k; 0..100) { 165 const a = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 166 const b = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 167 168 const s = uniform(-1.0f, +1.0f); 169 const r = iota(4).map!(_ => uniform(-1.0f, +1.0f)).array; 170 171 static foreach (i; 0..4) { 172 assert(approxEqual(a[i] + b[i], (a + b)[i])); 173 assert(approxEqual(a[i] - b[i], (a - b)[i])); 174 assert(approxEqual(a[i] * b[i], (a * b)[i])); 175 assert(approxEqual(a[i] / b[i], (a / b)[i])); 176 177 assert(approxEqual(a[i] + s, (a + s)[i])); 178 assert(approxEqual(a[i] - s, (a - s)[i])); 179 assert(approxEqual(a[i] * s, (a * s)[i])); 180 assert(approxEqual(a[i] / s, (a / s)[i])); 181 182 assert(approxEqual(a[i] + r[i], (a + r)[i])); 183 assert(approxEqual(a[i] - r[i], (a - r)[i])); 184 assert(approxEqual(a[i] * r[i], (a * r)[i])); 185 assert(approxEqual(a[i] / r[i], (a / r)[i])); 186 } 187 } 188 } 189 190 /** 191 Binary operation between matrix, scalar, array or vector type. 192 193 Params: 194 value = operation target 195 196 Returns: calculated vector 197 */ 198 Vector opBinaryRight(string op, AnotherType)(auto ref AnotherType value) const 199 { 200 import sbylib.math.matrix : isMatrix; 201 202 static if (isPotentially!(AnotherType, isMatrix)) { 203 return mixin("value"~PotentialExpression!(AnotherType, isMatrix) ~ ".opBinary!(\""~op~"\")(this)"); 204 } else { 205 return makeVector!(Expression!(AnotherType) ~ op ~ "elements[]")(value); 206 } 207 } 208 209 unittest { 210 import std.algorithm : map; 211 import std.range : iota; 212 import std.array : array; 213 import std.random : uniform; 214 import std.math : approxEqual; 215 216 foreach (k; 0..100) { 217 const a = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 218 219 const s = uniform(-1.0f, +1.0f); 220 const r = iota(4).map!(_ => uniform(-1.0f, +1.0f)).array; 221 222 static foreach (i; 0..4) { 223 224 assert(approxEqual(s + a[i], (s + a)[i])); 225 assert(approxEqual(s - a[i], (s - a)[i])); 226 assert(approxEqual(s * a[i], (s * a)[i])); 227 assert(approxEqual(s / a[i], (s / a)[i])); 228 229 assert(approxEqual(r[i] + a[i], (r + a)[i])); 230 assert(approxEqual(r[i] - a[i], (r - a)[i])); 231 assert(approxEqual(r[i] * a[i], (r * a)[i])); 232 assert(approxEqual(r[i] / a[i], (r / a)[i])); 233 } 234 } 235 } 236 237 /** 238 Assign operation for vector or array. 239 240 Params: 241 value = assigned value 242 243 Returns: this vector after assigned. 244 */ 245 Vector opAssign(AnotherType)(const AnotherType value) 246 if (isVector!(AnotherType) || isArray!(AnotherType)) 247 { 248 this.assignAll(value); 249 return this; 250 } 251 252 /** 253 Assign operation for scalar. 254 The value fills each element of this vector. 255 256 Params: 257 value = assigned value 258 259 Returns: this vector after assigned. 260 */ 261 Vector opAssign(AnotherType)(const AnotherType value) 262 if (!isVector!(AnotherType) && !isArray!(AnotherType) && is(typeof({ T v; v = AnotherType.init; }))) 263 { 264 this.assignSingle(value); 265 return this; 266 } 267 268 unittest { 269 vec3 v; 270 271 v = 3; 272 assert(approxEqual(v, vec3(3))); 273 274 int[3] v2 = [1,2,3]; 275 v = v2; 276 assert(approxEqual(v, vec3(1,2,3))); 277 278 v = vec3(3,2,1); 279 assert(approxEqual(v, vec3(3,2,1))); 280 } 281 282 /** 283 Operator assign for vector, array, or scalar type. 284 If the argument type is scalar, the value affects each element of this vector. 285 286 Params: 287 value = assigned value 288 289 Returns: this vector after assigned. 290 */ 291 Vector opOpAssign(string op, AnotherType)(AnotherType value) 292 { 293 mixin("elements[] " ~ op ~ "=" ~ Expression!(AnotherType) ~ ";"); 294 return this; 295 } 296 297 unittest { 298 import std.algorithm : map; 299 import std.range : iota; 300 import std.random : uniform; 301 302 foreach (i; 0..100) { 303 auto v = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 304 const v2 = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 305 const v3 = v + v2; 306 v += v2; 307 308 assert(approxEqual(v, v3)); 309 } 310 } 311 312 /** 313 Cast operation. 314 315 Returns: a vector whose elements are indivisually casted. 316 */ 317 V2 opCast(V2)() const if (isVector!V2 && Dimension == V2.Dimension) { 318 import std : to; 319 320 return V2(this.elements[].map!(to!(V2.ElementType))); 321 } 322 323 unittest { 324 import std.algorithm : map; 325 import std.range : iota; 326 import std.random : uniform; 327 import std.array : array; 328 329 foreach (k; 0..100) { 330 float[4] e; 331 static foreach (i; 0..4) { 332 e[i] = uniform(-1.0f, +1.0f); 333 } 334 const v = vec4(e.array); 335 336 const v2 = cast(ivec4)v; 337 338 static foreach (i; 0..4) { 339 assert(cast(int)v[i] == v2[i]); 340 } 341 } 342 } 343 344 /** 345 Indexing operation. 346 347 Params: 348 idx = index 349 350 Returns: i-th element of this vector. 351 */ 352 T opIndex(size_t i) const 353 in (i < S) 354 { 355 return this.elements[i]; 356 } 357 358 unittest { 359 import std.algorithm : map; 360 import std.range : iota; 361 import std.random : uniform; 362 import std.array : array; 363 364 foreach (k; 0..100) { 365 float[4] e; 366 static foreach (i; 0..4) { 367 e[i] = uniform(-1.0f, +1.0f); 368 } 369 const v = vec4(e.array); 370 371 static foreach (i; 0..4) { 372 assert(v[i] == e[i]); 373 } 374 } 375 } 376 377 /** 378 Indexing operation. 379 380 Params: 381 idx = index 382 383 Returns: i-th element of this vector. 384 */ 385 ref T opIndex(size_t i) 386 in (i < S) 387 { 388 return this.elements[i]; 389 } 390 391 unittest { 392 import std.algorithm : map; 393 import std.range : iota; 394 import std.random : uniform; 395 396 foreach (k; 0..100) { 397 vec4 v; 398 399 static foreach (i; 0..4) {{ 400 const e = uniform(-1.0f, +1.0f); 401 v[i] = e; 402 assert(v[i] == e); 403 }} 404 } 405 } 406 407 /** 408 Get raw array of this vector. 409 410 Returns: raw array of this vector. 411 */ 412 auto array() inout { 413 return elements; 414 } 415 416 private enum XYZW = "xyzw"; 417 private enum RGBA = "rgba"; 418 419 private static size_t[] createIndex(string propertyString, string s) { 420 size_t[] index = new size_t[s.length]; 421 foreach (i; 0..s.length) { 422 index[i] = propertyString.countUntil(s[i]); 423 } 424 return index; 425 } 426 427 private mixin template Gen(string s) { 428 enum isXYZW = s.all!(a => XYZW.canFind(a)); 429 enum isRGBA = s.all!(a => RGBA.canFind(a)); 430 static assert(isXYZW || isRGBA); 431 enum propertyString = isXYZW ? XYZW : isRGBA ? RGBA : ""; 432 enum index = createIndex(propertyString, s); 433 } 434 435 /** 436 Single element access by name. 437 The property is 'x', 'y', 'z', 'w', 'r', 'g', 'b' or 'a'. 438 439 Returns: selected element 440 */ 441 auto ref opDispatch(string s)() inout 442 if (s.length == 1) 443 { 444 mixin Gen!(s); 445 return elements[index[0]]; 446 } 447 448 /** 449 Multiple element access by name. 450 The property is combination of 'x', 'y', 'z', 'w', 'r', 'g', 'b' or 'a'. 451 452 Returns: a vector made by selected elements 453 */ 454 const(Vector!(T,s.length)) opDispatch(string s)() inout 455 if (s.length > 1) 456 { 457 mixin Gen!(s); 458 Vector!(T, s.length) result; 459 static foreach (i,idx; index) { 460 result[i] = elements[idx]; 461 } 462 return result; 463 } 464 465 /** 466 Property assign for scalar value. 467 This function supports both single and multiple property reference. 468 469 Params: 470 value = assigned scalar value 471 472 Returns: assigned value 473 */ 474 T opDispatch(string s)(T value) 475 if (s.all!(a =>XYZW.canFind(a)) || s.all!(a => RGBA.canFind(a))) 476 { 477 mixin Gen!(s); 478 static if(s.length == 1) { 479 return this[index[0]] = value; 480 } else { 481 static foreach (i,idx; index) { 482 this[idx] = value; 483 } 484 return value; 485 } 486 } 487 488 /** 489 Property assign for vector value. 490 This function supports both single and multiple property reference. 491 492 Params: 493 value = assigned vector value 494 495 Returns: assigned value 496 */ 497 const(Vector!(T,s.length)) opDispatch(string s)(Vector!(T,s.length) value) 498 if (s.all!(a =>XYZW.canFind(a)) || s.all!(a => RGBA.canFind(a))) 499 { 500 mixin Gen!(s); 501 static foreach (i,idx; index) { 502 this[idx] = value[i]; 503 } 504 return value; 505 } 506 507 string toString() const { 508 import std.format : format; 509 import std.array : array, join; 510 import std.algorithm : map; 511 import std.conv : to; 512 513 return format!"(%s)"(this.elements.array.map!(to!string).join(",")); 514 } 515 516 static if (isFloatingPoint!T) { 517 /** 518 Returns whether this vector contains NaN. 519 520 Returns: true if this vector contains NaN 521 */ 522 bool hasNaN() const { 523 import std.algorithm : any; 524 import std.array : array; 525 import std.math : isNaN; 526 527 return this.elements.array.any!(isNaN); 528 } 529 530 unittest { 531 assert(vec3().hasNaN); 532 assert(!vec3(0).hasNaN); 533 } 534 } 535 536 private void assignAll(Args...)(Args args) { 537 assignAll!(0)(args); 538 } 539 540 private void assignAll(size_t offset, Args...)(Args args) 541 if (Args.length > 0 && offset + Count!(Args) == Dimension) 542 { 543 assign!(offset)(args[0]); 544 assignAll!(offset + Count!(Args[0]))(args[1..$]); 545 } 546 547 private void assignAll(size_t offset)() { 548 static assert(offset == Dimension); 549 } 550 551 private void assign(size_t offset, AnotherType)(AnotherType value) 552 if (isVector!(AnotherType) && offset + Count!(AnotherType) <= Dimension) 553 { 554 assign!(offset)(value.elements); 555 } 556 557 private void assign(size_t offset, AnotherType)(AnotherType value) 558 if (isStaticArray!(AnotherType) && offset + Count!(AnotherType) <= Dimension) 559 { 560 static foreach (i; 0..Count!(AnotherType)) { 561 assign!(offset+i)(value[i]); 562 } 563 } 564 565 private void assign(size_t index, AnotherType : T)(AnotherType value) { 566 this[index] = value; 567 } 568 569 private void assignSingle(AnotherType)(AnotherType value) { 570 this.elements[] = value; 571 } 572 573 private template Count(Types...) 574 if (Types.length > 1) 575 { 576 enum Count = Count!(Types[0]) + Count!(Types[1..$]); 577 } 578 579 private template Count(Type) { 580 static if (isVector!(Type)) { 581 enum Count = Count!(typeof(Type.elements)); 582 } else static if (isStaticArray!(Type)) { 583 enum Count = Type.length * Count!(ForeachType!(Type)); 584 } else static if (is(Unqual!Type : T)) { 585 enum Count = 1; 586 } else { 587 static assert(false, "Invalid type: " ~ Type.stringof); 588 } 589 } 590 591 private template Expression(AnotherType) { 592 static if (isPotentially!(AnotherType, isArray)) 593 enum Expression = "value" ~ PotentialExpression!(AnotherType, isArray) ~ "[]"; 594 else 595 enum Expression = "value"; 596 } 597 598 private Vector makeVector(string expr, AnotherType...)(auto ref AnotherType values) const { 599 static if (AnotherType.length > 0) alias value = values[0]; 600 else alias value = values; 601 602 Vector result; 603 result.elements[] = mixin(expr); 604 return result; 605 } 606 } 607 608 609 /** 610 Get a vector whose each element is minimum value of the same indexed element of two vectors. 611 612 Params: 613 v = left hand value of calculation 614 v2 = right hand value of calculation 615 616 Returns: minimum vector of two vectors 617 */ 618 Vector!(CommonType!(T,S),U) min(T,S,uint U)(const Vector!(T,U) v, const Vector!(S,U) v2) { 619 import std.algorithm : fmin = min; 620 return reduceV!(fmin)(v, v2); 621 } 622 623 unittest { 624 import std.algorithm : map, fmin = min; 625 import std.range : iota; 626 import std.random : uniform; 627 628 foreach (k; 0..100) { 629 const a = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 630 const b = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 631 static foreach (i; 0..4) { 632 assert(fmin(a[i], b[i]) == min(a,b)[i]); 633 } 634 } 635 } 636 637 /** 638 Get a vector whose each element is maximum value of the same indexed element of two vectors. 639 640 Params: 641 v = left hand value of calculation 642 v2 = right hand value of calculation 643 644 Returns: maximum vector of two vectors 645 */ 646 Vector!(CommonType!(T,S),U) max(T,S,uint U)(const Vector!(T,U) v, const Vector!(S,U) v2) { 647 import std.algorithm : fmax = max; 648 return reduceV!(fmax)(v, v2); 649 } 650 651 unittest { 652 import std.algorithm : map, fmax = max; 653 import std.range : iota; 654 import std.random : uniform; 655 656 foreach (k; 0..100) { 657 const a = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 658 const b = vec4(iota(4).map!(_ => uniform(-1.0f, +1.0f))); 659 static foreach (i; 0..4) { 660 assert(fmax(a[i], b[i]) == max(a,b)[i]); 661 } 662 } 663 } 664 665 /** 666 Calculates dot product. 667 668 Params: 669 v = left hand value of dot product 670 v2 = right hand value of dot product 671 672 Returns: dot product of two vectors 673 */ 674 CommonType!(T,S) dot(T,S,uint U)(const Vector!(T,U) v, const Vector!(S,U) v2) { 675 CommonType!(T,S) result = 0; 676 static foreach (i; 0..U) { 677 result += v[i] * v2[i]; 678 } 679 return result; 680 } 681 682 unittest { 683 import std.algorithm : map; 684 import std.random : uniform; 685 import std.range : iota; 686 import std.math : abs, approxEqual; 687 import sbylib.math.angle : deg, cos; 688 import sbylib.math.matrix : mat3; 689 690 foreach (i; 0..100) { 691 const point = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 692 const axis = point.ortho.normalize; 693 const angle = uniform(0,180.0f).deg; 694 const r = mat3.axisAngle(axis, angle); 695 const point2 = r * point; 696 assert(approxEqual(dot(point, point2), length(point) * length(point2) * cos(angle))); 697 } 698 } 699 700 /** 701 Calclates cross product of two vectors in 2D. 702 Returned value is |v||v2| sin a 703 704 Params: 705 v = left hand value of calculation 706 v2 = right hand value of calculation 707 708 Returns: cross product of two vectors 709 */ 710 CommonType!(T,S) cross(T,S)(const Vector!(T,2) v, const Vector!(S,2) v2) { 711 return v.x * v2.y - v.y * v2.x; 712 } 713 714 unittest { 715 import std.algorithm : map; 716 import std.random : uniform; 717 import std.range : iota; 718 import std.math : approxEqual, abs; 719 import sbylib.math.angle : deg, acos, sin; 720 721 foreach (i; 0..100) { 722 const a = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 723 const b = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 724 725 const la = length(a); 726 const lb = length(b); 727 728 const angle = acos(dot(a,b) / (la * lb)); 729 730 const c = cross(a,b); 731 732 assert(approxEqual(abs(c), la * lb * sin(angle), 1e-3)); 733 } 734 } 735 736 /** 737 Calclates cross product of two vectors in 3D. 738 739 Params: 740 v = left hand value of calculation 741 v2 = right hand value of calculation 742 743 Returns: cross product of two vectors 744 */ 745 Vector!(CommonType!(T,S),3) cross(T,S)(const Vector!(T,3) v, const Vector!(S,3) v2) { 746 Vector!(CommonType!(T,S),3) result; 747 static foreach (i; 0..3) { 748 result[i] = v[(i+1)%3] * v2[(i+2)%3] - v[(i+2)%3] * v2[(i+1)%3]; 749 } 750 return result; 751 } 752 753 unittest { 754 import std.algorithm : map; 755 import std.random : uniform; 756 import std.range : iota; 757 import std.math : approxEqual; 758 import sbylib.math.angle : deg, acos, sin; 759 760 foreach (i; 0..100) { 761 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 762 const b = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 763 764 const la = length(a); 765 const lb = length(b); 766 767 const angle = acos(dot(a,b) / (la * lb)); 768 769 const c = cross(a,b); 770 771 assert(approxEqual(dot(a,c), 0)); 772 assert(approxEqual(dot(b,c), 0)); 773 assert(approxEqual(c.length, la * lb * sin(angle))); 774 } 775 } 776 777 /** 778 Returns a vector which is orthognal to the given vector. 779 780 Params: 781 v = target vector 782 783 Returns: orthognal vector 784 */ 785 Vector!(T,2) ortho(T)(const Vector!(T,2) v) { 786 return Vector!(T,2)(-v.y, v.x); 787 } 788 789 unittest { 790 import std.algorithm : map; 791 import std.random : uniform; 792 import std.range : iota; 793 import std.math : approxEqual; 794 795 foreach (i; 0..100) { 796 const a = vec2(2.iota.map!(_ => uniform(-1.0f, +1.0f))); 797 assert(approxEqual(dot(a, a.ortho), 0)); 798 } 799 } 800 801 /** 802 Returns a vector which is orthognal to the given vector. 803 804 Params: 805 v = target vector 806 807 Returns: orthognal vector 808 */ 809 Vector!(T,3) ortho(T)(const Vector!(T,3) v) 810 if (__traits(isFloating, T)) 811 { 812 if (v.x == 0 && v.z == 0) { 813 return normalize(cross(v, Vector!(T,3)(1,0,0))); 814 } else { 815 return normalize(cross(v, Vector!(T,3)(0,1,0))); 816 } 817 } 818 819 unittest { 820 import std.algorithm : map; 821 import std.random : uniform; 822 import std.range : iota; 823 import std.math : approxEqual; 824 825 foreach (i; 0..100) { 826 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 827 assert(approxEqual(dot(a, a.ortho), 0)); 828 } 829 } 830 831 /** 832 Returns squared length of the given vector. 833 834 Params: 835 v = target vector 836 837 Returns: squared length of the given vector 838 */ 839 T lengthSq(T,uint U)(const Vector!(T,U) v) { 840 T result = 0; 841 static foreach (i; 0..U) { 842 result += v[i] * v[i]; 843 } 844 return result; 845 } 846 847 /** 848 Returns length of the given vector. 849 850 Params: 851 v = target vector 852 853 Returns: length of the given vector 854 */ 855 T length(T,uint U)(const Vector!(T,U) v) 856 if (__traits(isFloating, T)) 857 { 858 import std.math : sqrt; 859 return sqrt(lengthSq(v)); 860 } 861 862 /** 863 Returns a vector whose length 1 from target vector. 864 865 Params: 866 v = target vector 867 868 Returns: normalized target vector 869 */ 870 Vector!(T,U) normalize(T,uint U)(const Vector!(T,U) v) 871 if (__traits(isFloating, T)) 872 { 873 return v / length(v); 874 } 875 876 /** 877 Returns a vector whose length 1 from target vector. 878 When the length is 0, returns zero vector. 879 880 Params: 881 v = target vector 882 883 Returns: normalized target vector if the length is greater than 0, otherwise zero vector 884 */ 885 Vector!(T,U) safeNormalize(T,uint U)(const Vector!(T,U) v) 886 if (__traits(isFloating, T)) 887 { 888 const l = length(v); 889 if (l == 0) return v; 890 return v / l; 891 } 892 893 unittest { 894 import std.algorithm : map; 895 import std.random : uniform; 896 import std.range : iota; 897 import std.math : approxEqual; 898 899 foreach (i; 0..100) { 900 const v = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 901 assert(v == vec4(0) || safeNormalize(v).length.approxEqual(1)); 902 } 903 } 904 905 /** 906 Returns a linear interpolated vector 907 908 Params: 909 v1 = target vector 910 v2 = target vector 911 t = interpolation coefficient 912 913 Returns: interpolated vector 914 */ 915 Vector!(T,U) mix(T,uint U)(const Vector!(T,U) v1, const Vector!(T,U) v2, T t) { 916 return (1-t) * v1 + t * v2; 917 } 918 919 unittest { 920 import std.algorithm : map; 921 import std.random : uniform; 922 import std.range : iota; 923 import std.math : approxEqual; 924 925 foreach (i; 0..100) { 926 const v1 = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 927 const v2 = vec4(4.iota.map!(_ => uniform(-1.0f, +1.0f))); 928 foreach (j; 1..100) { 929 const t = j * 0.01; 930 const v = mix(v1, v2, t); 931 assert(dot(normalize(v1-v), normalize(v2-v)).approxEqual(-1)); 932 } 933 } 934 } 935 936 /** 937 Reduces an array of vector by the given function. 938 939 Params: 940 v = array of vector reduced 941 942 Returns: reduced vector 943 */ 944 Vector!(T,U) reduceV(alias pred, T, int U)(const Vector!(T,U)[] v...) { 945 Vector!(T,U) result = v[0]; 946 static foreach (i; 0..U) { 947 foreach (j; 1..v.length) { 948 result[i] = pred(result[i], v[j][i]); 949 } 950 } 951 return result; 952 } 953 954 /** 955 Calculates an unsigned area of a triangle which is composed of the given points. 956 957 Params: 958 positions = array of position vectors which compose a triangle 959 960 Returns: unsigned area of triangle 961 */ 962 T computeUnSignedArea(T)(const Vector!(T,3)[3] positions...) { 963 return length(cross(positions[2] - positions[0], positions[1] - positions[0])) / 2; 964 } 965 966 unittest { 967 import std.algorithm : map; 968 import std.random : uniform; 969 import std.range : iota; 970 import std.math : approxEqual; 971 972 foreach (i; 0..100) { 973 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 974 const b = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 975 976 const area = computeUnSignedArea(a,b,vec3(0)); 977 978 const base = length(a); 979 const height = length(b-normalize(a) * dot(normalize(a),b)); 980 981 assert(approxEqual(area, base * height / 2)); 982 } 983 } 984 985 /** 986 Calculates a signed volume of a tetrahedron which is composed of the given points. 987 988 Params: 989 positions = array of position vectors which compose a tetrahedron 990 991 Returns: signed volume of tetrahedron 992 */ 993 T computeSignedVolume(T)(const Vector!(T,3)[4] positions...) { 994 Vector!(T,3)[3] v; 995 static foreach (i; 0..3) { 996 v[i] = positions[i+1] - positions[0]; 997 } 998 T result = 0; 999 static foreach (i; 0..3) { 1000 result += v[i].x * v[(i+1)%3].y * v[(i+2)%3].z; 1001 result -= v[i].x * v[(i+2)%3].y * v[(i+1)%3].z; 1002 } 1003 return result / 6; 1004 } 1005 1006 unittest { 1007 import std.algorithm : map; 1008 import std.random : uniform; 1009 import std.range : iota; 1010 import std.math : approxEqual, abs; 1011 1012 foreach (i; 0..100) { 1013 const a = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1014 const b = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1015 const c = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1016 1017 const volume = computeSignedVolume(a,b,c,vec3(0)); 1018 1019 const area = computeUnSignedArea(a,b,vec3(0)); 1020 const height = dot(normalize(cross(a,b)), c); 1021 1022 assert(approxEqual(abs(volume), abs(area * height / 3))); 1023 } 1024 } 1025 1026 //与えられた点列に対し、分散が最大化するベクトルを含む正規直交基底を返す 1027 /** 1028 Returns normalized orthognal basis which maximize the dispersion of the given points 1029 1030 Params: 1031 vertices = array of points 1032 1033 Returns: 3 normalized orthognal basis 1034 */ 1035 Vector!(T,3)[3] mostDispersionBasis(T)(const Vector!(T,3)[] vertices...) 1036 in(vertices.length > 0) 1037 { 1038 import std.algorithm : sum; 1039 import sbylib.math.matrix : mat3, Matrix, diagonalizeForRealSym; 1040 1041 const c = vertices.sum / vertices.length; 1042 mat3 vcm = mat3(0); 1043 foreach (ref v; vertices) { 1044 const r = v - c; 1045 vcm += Matrix!(float,3,1)(r.array) * Matrix!(float,1,3)(r.array); 1046 } 1047 vcm /= vertices.length; 1048 const diagonal = diagonalizeForRealSym(vcm); 1049 Vector!(T,3)[3] result; 1050 static foreach (i; 0..3) { 1051 result[i] = diagonal.column[i].normalize; 1052 } 1053 return result; 1054 } 1055 1056 unittest { 1057 import std.algorithm : map, sum, max; 1058 import std.random : uniform; 1059 import std.range : iota; 1060 import std.math : abs, approxEqual; 1061 import sbylib.math.angle : deg, cos; 1062 import sbylib.math.matrix : mat3; 1063 1064 vec3[] points; 1065 foreach (i; 0..100) { 1066 points ~= vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))); 1067 } 1068 const center = points.sum / 100; 1069 foreach (i; 0..100) { 1070 points[i] -= center; 1071 } 1072 1073 float maxDispersion = 0; 1074 1075 foreach (i; 0..10) { 1076 const axis = vec3(3.iota.map!(_ => uniform(-1.0f, +1.0f))).normalize; 1077 const angle = uniform(0,180.0f).deg; 1078 const r = mat3.axisAngle(axis, angle); 1079 static foreach (j; 0..3) { 1080 maxDispersion = max(maxDispersion, points.map!(p => (r * p)[j]^^2).sum); 1081 } 1082 } 1083 1084 const basis = mostDispersionBasis(points); 1085 float idealMaxDispersion = 0; 1086 static foreach (j; 0..3) { 1087 idealMaxDispersion = max(idealMaxDispersion, points.map!(p => dot(basis[j],p)^^2).sum); 1088 } 1089 1090 assert(idealMaxDispersion >= maxDispersion); 1091 } 1092 1093 /** 1094 Returns true if the distance between the given two point is less than eps. 1095 1096 Params: 1097 a = target point 1098 b = target point 1099 eps = criteria of approximation 1100 1101 Returns: true if 2 vectors are approximately equal. 1102 */ 1103 bool approxEqual(T,uint N)(const Vector!(T,N) a, const Vector!(T,N) b, T eps = 1e-5) { 1104 return length(a-b) < eps; 1105 } 1106 1107 private template PotentialExpression(T, alias pred) { 1108 static if (pred!(T)) { 1109 enum PotentialExpression = ""; 1110 } else static if (isAggregateType!(T)) { 1111 enum AliasThis = __traits(getAliasThis, T); 1112 static assert(AliasThis.length > 0); 1113 enum Expr = "T."~AliasThis[0]; 1114 static if (isFunction!(mixin(Expr))) { 1115 alias Type = ReturnType!(mixin(Expr)); 1116 } else { 1117 alias Type = typeof(mixin(Expr)); 1118 } 1119 enum PotentialExpression = "." ~ AliasThis[0] ~ PotentialExpression!(Type, pred); 1120 } 1121 } 1122 1123 private template isPotentially(T, alias pred) { 1124 static if (pred!(T)) { 1125 enum isPotentially = true; 1126 } else static if (isAggregateType!(T)) { 1127 enum AliasThis = __traits(getAliasThis, T); 1128 static if (AliasThis.length == 0) { 1129 enum isPotentially = false; 1130 } else { 1131 enum Expr = "T."~AliasThis[0]; 1132 static if (isFunction!(mixin(Expr))) { 1133 alias Type = ReturnType!(mixin(Expr)); 1134 } else { 1135 alias Type = typeof(mixin(Expr)); 1136 } 1137 enum isPotentially = isPotentially!(Type, pred); 1138 } 1139 } else { 1140 enum isPotentially = false; 1141 } 1142 }