1 // TODO: get rid of instantiators in favor of aliases to reduce template bloat
2 module nxt.geometry;
3 
4 import std.traits: isFloatingPoint, isNumeric, isSigned, isDynamicArray, isAssignable, isArray, CommonType, isInstanceOf;
5 
6 version = use_cconv;            ///< Use cconv for faster compilation.
7 version = unittestAllInstances;
8 
9 version(unittestAllInstances)
10 {
11     static immutable defaultElementTypes = ["float", "double", "real"];
12 }
13 else
14 {
15     static immutable defaultElementTypes = ["double"];
16 }
17 
18 enum Orient { column, row } // Vector Orientation.
19 
20 /** `D`-Dimensional Cartesian Point with Coordinate Type (Precision) `E`.
21  */
22 struct Point(E, uint D)
23 if (D >= 1 /* && TODO: extend trait : isNumeric!E */)
24 {
25     alias ElementType = E;
26 
27     this(T...)(T args)
28     {
29         foreach (immutable ix, arg; args)
30         {
31             _point[ix] = arg;
32         }
33     }
34 
35     /** Element data. */
36     E[D] _point;
37     enum dimension = D;
38 
39     @property void toString(scope void delegate(scope const(char)[]) @safe sink) const
40     {
41         sink(`Point(`);
42         foreach (const ix, const e; _point)
43         {
44             if (ix != 0) { sink(","); }
45             version(use_cconv)
46             {
47                 import nxt.cconv : toStringInSink;
48                 toStringInSink(e, sink);
49             }
50             else
51             {
52                 import std.conv : to;
53                 sink(to!string(e));
54             }
55         }
56         sink(`)`);
57     }
58 
59     @property string toMathML()() const
60     {
61         // opening
62         string str = `<math><mrow>
63   <mo>(</mo>
64   <mtable>`;
65 
66         static foreach (i; 0 .. D)
67         {
68             str ~= `
69     <mtr>
70       <mtd>
71         <mn>` ~ _point[i].toMathML ~ `</mn>
72       </mtd>
73     </mtr>`;
74         }
75 
76         // closing
77         str ~= `
78   </mtable>
79   <mo>)</mo>
80 </mrow></math>
81 `;
82         return str;
83     }
84 
85     /** Returns: Area 0 */
86     @property E area() const { return 0; }
87 
88     inout(E)[] opSlice() inout { return _point[]; }
89 
90     /** Points +/- Vector => Point */
91     auto opBinary(string op, F)(Vector!(F, D) r) const
92         if ((op == "+") ||
93             (op == "-"))
94     {
95         Point!(CommonType!(E, F), D) y;
96         static foreach (i; 0 .. D)
97         {
98             y._point[i] = mixin("_point[i]" ~ op ~ "r._vector[i]");
99         }
100         return y;
101     }
102 }
103 
104 /** Instantiator for `Point`. */
105 auto point(Ts...)(Ts args)
106 if (!is(CommonType!Ts == void))
107 {
108     return Point!(CommonType!Ts, args.length)(args);
109 }
110 
111 version(unittest)
112 {
113     alias vec2f = Vector!(float, 2, true);
114     alias vec3f = Vector!(float, 3, true);
115 
116     alias vec2d = Vector!(float, 2, true);
117 
118     alias nvec2f = Vector!(float, 2, true);
119 }
120 
121 /** `D`-Dimensional Vector with Coordinate/Element (Component) Type `E`.
122  *
123  * See_Also: http://physics.stackexchange.com/questions/16850/is-0-0-0-an-undefined-vector
124  */
125 struct Vector(E, uint D,
126               bool normalizedFlag = false, // `true` for unit vectors
127               Orient orient = Orient.column)
128 if (D >= 1 &&
129     (!normalizedFlag ||
130      isFloatingPoint!E)) // only normalize fp for now
131 {
132     // Construct from vector.
133     this(V)(V vec)
134     if (isVector!V &&
135         // TOREVIEW: is(T.E : E) &&
136         (V.dimension >= dimension))
137     {
138         static if (normalizedFlag)
139         {
140             if (vec.normalized)
141             {
142                 immutable vec_norm = vec.magnitude;
143                 static foreach (i; 0 .. D)
144                 {
145                     _vector[i] = vec._vector[i] / vec_norm;
146                 }
147                 return;
148             }
149         }
150         static foreach (i; 0 .. D)
151         {
152             _vector[i] = vec._vector[i];
153         }
154     }
155 
156     /** Construct from Scalar `value`. */
157     this(S)(S scalar)
158     if (isAssignable!(E, S))
159     {
160         static if (normalizedFlag)
161         {
162             import std.math : sqrt;
163             clear(1/sqrt(cast(E)D)); // TODO: costly
164         }
165         else
166         {
167             clear(scalar);
168         }
169     }
170 
171     /** Construct from combination of arguments. */
172     this(Args...)(Args args) { construct!(0)(args); }
173 
174     enum dimension = D;
175 
176     @property const
177     {
178         string orientationString()() { return orient == Orient.column ? `Column` : `Row`; }
179         string joinString()() { return orient == Orient.column ? ` \\ ` : ` & `; }
180     }
181 
182     @property void toString(scope void delegate(scope const(char)[]) @safe sink) const
183     {
184         sink(orientationString);
185         sink(`Vector(`);
186         foreach (const ix, const e; _vector)
187         {
188             if (ix != 0) { sink(","); }
189             version(use_cconv)
190             {
191                 import nxt.cconv : toStringInSink;
192                 toStringInSink(e, sink);
193             }
194             else
195             {
196                 import std.conv : to;
197                 sink(to!string(e));
198             }
199         }
200         sink(`)`);
201     }
202 
203     /** Returns: LaTeX Encoding of Vector. http://www.thestudentroom.co.uk/wiki/LaTex#Matrices_and_Vectors */
204     @property string toLaTeX()() const
205     {
206         return `\begin{pmatrix} ` ~ map!(to!string)(_vector[]).join(joinString) ~ ` \end{pmatrix}` ;
207     }
208 
209     @property string toMathML()() const
210     {
211         // opening
212         string str = `<math><mrow>
213   <mo>⟨</mo>
214   <mtable>`;
215 
216         if (orient == Orient.row)
217         {
218             str ~=  `
219     <mtr>`;
220         }
221 
222         static foreach (i; 0 .. D)
223         {
224             final switch (orient)
225             {
226             case Orient.column:
227                 str ~= `
228     <mtr>
229       <mtd>
230         <mn>` ~ _vector[i].toMathML ~ `</mn>
231       </mtd>
232     </mtr>`;
233                 break;
234             case Orient.row:
235                 str ~= `
236       <mtd>
237         <mn>` ~ _vector[i].toMathML ~ `</mn>
238       </mtd>`;
239                 break;
240             }
241         }
242 
243         if (orient == Orient.row)
244         {
245             str ~=  `
246     </mtr>`;
247         }
248 
249         // closing
250         str ~= `
251   </mtable>
252   <mo>⟩</mo>
253 </mrow></math>
254 `;
255         return str;
256     }
257 
258     auto randInPlace()(E scaling = 1)
259     {
260         import nxt.random_ex: randInPlace;
261         static if (normalizedFlag &&
262                    isFloatingPoint!E) // cannot use normalized() here (yet)
263         {
264             static if (D == 2)  // randomize on unit circle
265             {
266                 alias P = real; // precision
267                 immutable angle = uniform(0, 2*cast(P)PI);
268                 _vector[0] = scaling*sin(angle);
269                 _vector[1] = scaling*cos(angle);
270             }
271             static if (D == 3)  // randomize on unit sphere: See_Also: http://mathworld.wolfram.com/SpherePointPicking.html
272             {
273                 alias P = real; // precision
274                 immutable u = uniform(0, cast(P)1);
275                 immutable v = uniform(0, cast(P)1);
276                 immutable theta = 2*PI*u;
277                 immutable phi = acos(2*v-1);
278                 _vector[0] = scaling*cos(theta)*sin(phi);
279                 _vector[1] = scaling*sin(theta)*sin(phi);
280                 _vector[2] = scaling*cos(phi);
281             }
282             else
283             {
284                 _vector.randInPlace();
285                 normalize(); // TODO: Turn this into D data restriction instead?
286             }
287         }
288         else
289         {
290             _vector.randInPlace();
291         }
292     }
293 
294     /// Returns: `true` if all values are not `nan` nor `infinite`, otherwise `false`.
295     @property bool ok()() const
296     {
297         static if (isFloatingPoint!E)
298         {
299             foreach (const ref v; _vector)
300             {
301                 if (isNaN(v) ||
302                     isInfinity(v))
303                 {
304                     return false;
305                 }
306             }
307         }
308         return true;
309     }
310     // NOTE: Disabled this because I want same behaviour as MATLAB: bool opCast(T : bool)() const { return ok; }
311     bool opCast(T : bool)() const
312     {
313         return all!"a"(_vector[]);
314     }
315 
316     /// Returns: Pointer to the coordinates.
317     // @property auto value_ptr() { return _vector.ptr; }
318 
319     /// Sets all values to `value`.
320     void clear(V)(V value)
321     if (isAssignable!(E, V))
322     {
323         static foreach (i; 0 .. D)
324         {
325             _vector[i] = value;
326         }
327     }
328 
329     /** Returns: Whole Internal Array of E. */
330     auto opSlice()
331     {
332         return _vector[];
333     }
334     /** Returns: Slice of Internal Array of E. */
335     auto opSlice(uint off, uint len)
336     {
337         return _vector[off .. len];
338     }
339     /** Returns: Reference to Internal Vector Element. */
340     ref inout(E) opIndex(uint i) inout
341     {
342         return _vector[i];
343     }
344 
345     bool opEquals(S)(in S scalar) const
346     if (isAssignable!(E, S)) // TODO: is(typeof(E.init != S.init))
347     {
348         static foreach (i; 0 .. D)
349         {
350             if (_vector[i] != scalar)
351             {
352                 return false;
353             }
354         }
355         return true;
356     }
357 
358     bool opEquals(F)(in F vec) const
359     if (isVector!F &&
360         dimension == F.dimension) // TOREVIEW: Use isEquable instead?
361     {
362         return _vector == vec._vector;
363     }
364 
365     bool opEquals(F)(const F[] array) const
366     if (isAssignable!(E, F) &&
367         !isArray!F &&
368         !isVector!F) // TOREVIEW: Use isNotEquable instead?
369     {
370         if (array.length != dimension)
371         {
372             return false;
373         }
374         static foreach (i; 0 .. D)
375         {
376             if (_vector[i] != array[i])
377             {
378                 return false;
379             }
380         }
381         return true;
382     }
383 
384     static void isCompatibleVectorImpl(uint d)(Vector!(E, d) vec) if (d <= dimension) {}
385     static void isCompatibleMatrixImpl(uint r, uint c)(Matrix!(E, r, c) m) {}
386 
387     enum isCompatibleVector(T) = is(typeof(isCompatibleVectorImpl(T.init)));
388     enum isCompatibleMatrix(T) = is(typeof(isCompatibleMatrixImpl(T.init)));
389 
390     private void construct(uint i)()
391     {
392         static assert(i == D, "Not enough arguments passed to constructor");
393     }
394     private void construct(uint i, T, Tail...)(T head, Tail tail)
395     {
396         static        if (i >= D)
397         {
398             static assert(0, "Too many arguments passed to constructor");
399         }
400         else static if (is(T : E))
401         {
402             _vector[i] = head;
403             construct!(i + 1)(tail);
404         }
405         else static if (isDynamicArray!T)
406         {
407             static assert((Tail.length == 0) && (i == 0), "Dynamic array can not be passed together with other arguments");
408             _vector[] = head[];
409         }
410         else static if (__traits(isStaticArray, T))
411         {
412             _vector[i .. i + T.length] = head[];
413             construct!(i + T.length)(tail);
414         }
415         else static if (isCompatibleVector!T)
416         {
417             _vector[i .. i + T.dimension] = head._vector[];
418             construct!(i + T.dimension)(tail);
419         }
420         else
421         {
422             static assert(0, "Vector constructor argument must be of type " ~ E.stringof ~ " or Vector, not " ~ T.stringof);
423         }
424     }
425 
426     // private void dispatchImpl(int i, string s, int size)(ref E[size] result) const {
427     //     static if (s.length > 0) {
428     //         result[i] = _vector[coordToIndex!(s[0])];
429     //         dispatchImpl!(i + 1, s[1..$])(result);
430     //     }
431     // }
432 
433     // /// Implements dynamic swizzling.
434     // /// Returns: a Vector
435     // @property Vector!(E, s.length) opDispatch(string s)() const {
436     //     E[s.length] ret;
437     //     dispatchImpl!(0, s)(ret);
438     //     Vector!(E, s.length) ret_vec;
439     //     ret_vec._vector = ret;
440     //     return ret_vec;
441     // }
442 
443     ref inout(Vector) opUnary(string op : "+")() inout { return this; }
444 
445     Vector opUnary(string op : "-")() const
446     if (isSigned!(E))
447     {
448         Vector y;
449         static foreach (i; 0 .. D)
450         {
451             y._vector[i] = - _vector[i];
452         }
453         return y;
454     }
455 
456     auto opBinary(string op, T)(in T rhs) const
457     if (op == "+" || op == "-" &&
458         isInstanceOf!(Vector, T) &&
459         dimension && T.dimension)
460     {
461         Vector!(CommonType!(E, F), D) y;
462         static foreach (i; 0 .. D)
463         {
464             y._vector[i] = mixin("_vector[i]" ~ op ~ "rhs._vector[i]");
465         }
466         return y;
467     }
468 
469     Vector opBinary(string op : "*", F)(in F r) const
470     {
471         Vector!(CommonType!(E, F), D, normalizedFlag) y;
472         static foreach (i; 0 .. dimension)
473         {
474             y._vector[i] = _vector[i] * r;
475         }
476         return y;
477     }
478 
479     Vector!(CommonType!(E, F), D) opBinary(string op : "*", F)(in Vector!(F, D) r) const
480     {
481         // MATLAB-style Product Behaviour
482         static if (orient == Orient.column &&
483                    r.orient == Orient.row)
484         {
485             return outer(this, r);
486         }
487         else static if (orient == Orient.row &&
488                         r.orient == Orient.column)
489         {
490             return dot(this, r);
491         }
492         else
493         {
494             static assert(0, "Incompatible vector dimensions.");
495         }
496     }
497 
498     /** Multiply with right-hand-side `rhs`. */
499     Vector!(E, T.rows) opBinary(string op : "*", T)(in T rhs) const
500     if (isCompatibleMatrix!T &&
501         (T.cols == dimension))
502     {
503         Vector!(E, T.rows) ret;
504         ret.clear(0);
505         static foreach (c; 0 .. T.cols)
506         {
507             static foreach (r; 0 ..  T.rows)
508             {
509                 ret._vector[r] += _vector[c] * rhs.at(r,c);
510             }
511         }
512         return ret;
513     }
514 
515     /** Multiply with left-hand-side `lhs`. */
516     version(none)               // TODO: activate
517     auto opBinaryRight(string op, T)(in T lhs) const
518     if (!isInstanceOf!(Vector, T) &&
519         !isMatrix!T &&
520         !isQuaternion!T)
521     {
522         return this.opBinary!(op)(lhs);
523     }
524 
525     /** TODO: Suitable Restrictions on F. */
526     void opOpAssign(string op, F)(in F r)
527         /* if ((op == "+") || (op == "-") || (op == "*") || (op == "%") || (op == "/") || (op == "^^")) */
528     {
529         static foreach (i; 0 .. dimension)
530         {
531             mixin("_vector[i]" ~ op ~ "= r;");
532         }
533     }
534     unittest
535     {
536         auto v2 = vec2f(1, 3);
537         v2 *= 5.0f; assert(v2[] == [5, 15].s);
538         v2 ^^= 2; assert(v2[] == [25, 225].s);
539         v2 /= 5; assert(v2[] == [5, 45].s);
540     }
541 
542     void opOpAssign(string op)(in Vector r)
543     if ((op == "+") ||
544         (op == "-"))
545     {
546         static foreach (i; 0 .. dimension)
547         {
548             mixin("_vector[i]" ~ op ~ "= r._vector[i];");
549         }
550     }
551 
552     /// Returns: Non-Rooted `N` - Norm of `x`.
553     auto nrnNorm(uint N)() const
554     if (isNumeric!E && N >= 1)
555     {
556         static if (isFloatingPoint!E)
557         {
558             real y = 0;                 // TOREVIEW: Use maximum precision for now
559         }
560         else
561         {
562             E y = 0;                // TOREVIEW: Use other precision for now
563         }
564         static foreach (i; 0 .. D)
565         {
566             y += _vector[i] ^^ N;
567         }
568         return y;
569     }
570 
571     /// Returns: Squared Magnitude of x.
572     @property real magnitudeSquared()() const
573     if (isNumeric!E)
574     {
575         static if (normalizedFlag) // cannot use normalized() here (yet)
576         {
577             return 1;
578         }
579         else
580         {
581             return nrnNorm!2;
582         }
583     }
584 
585     /// Returns: Magnitude of x.
586     @property real magnitude()() const
587     if (isNumeric!E)
588     {
589         static if (normalizedFlag) // cannot use normalized() here (yet)
590         {
591             return 1;
592         }
593         else
594         {
595             import std.math : sqrt;
596             return sqrt(magnitudeSquared);
597         }
598     }
599     alias norm = magnitude;
600 
601     static if (isFloatingPoint!E)
602     {
603         /// Normalize `this`.
604         void normalize()()
605         {
606             if (this != 0)         // zero vector have zero magnitude
607             {
608                 immutable m = this.magnitude;
609                 static foreach (i; 0 .. D)
610                 {
611                     _vector[i] /= m;
612                 }
613             }
614         }
615 
616         /// Returns: normalizedFlag Copy of this Vector.
617         @property Vector normalized()() const
618         {
619             Vector y = this;
620             y.normalize();
621             return y;
622         }
623 
624         unittest
625         {
626             static if (D == 2 && !normalizedFlag)
627             {
628                 assert(Vector(3, 4).magnitude == 5);
629             }
630         }
631     }
632 
633     /// Returns: Vector Index at Character Coordinate `coord`.
634     private @property ref inout(E) get_(char coord)() inout
635     {
636         return _vector[coordToIndex!coord];
637     }
638 
639     /// Coordinate Character c to Index
640     template coordToIndex(char c)
641     {
642         static if ((c == 'x'))
643         {
644             enum coordToIndex = 0;
645         }
646         else static if ((c == 'y'))
647         {
648             enum coordToIndex = 1;
649         }
650         else static if ((c == 'z'))
651         {
652             static assert(D >= 3, "The " ~ c ~ " property is only available on vectors with a third dimension.");
653             enum coordToIndex = 2;
654         }
655         else static if ((c == 'w'))
656         {
657             static assert(D >= 4, "The " ~ c ~ " property is only available on vectors with a fourth dimension.");
658             enum coordToIndex = 3;
659         }
660         else
661         {
662             static assert(0, "Accepted coordinates are x, s, r, u, y, g, t, v, z, p, b, w, q and a not " ~ c ~ ".");
663         }
664     }
665 
666     /// Updates the vector with the values from other.
667     void update(in Vector!(E, D) other) { _vector = other._vector; }
668 
669     static if (D == 2)
670     {
671         void set(E x, E y)
672         {
673             _vector[0] = x;
674             _vector[1] = y;
675         }
676     }
677     else static if (D == 3)
678     {
679         void set(E x, E y, E z)
680         {
681             _vector[0] = x;
682             _vector[1] = y;
683             _vector[2] = z;
684         }
685     }
686     else static if (D == 4)
687     {
688         void set(E x, E y, E z, E w)
689         {
690             _vector[0] = x;
691             _vector[1] = y;
692             _vector[2] = z;
693             _vector[3] = w;
694         }
695     }
696 
697     static if (D >= 1) { alias x = get_!'x'; }
698     static if (D >= 2) { alias y = get_!'y'; }
699     static if (D >= 3) { alias z = get_!'z'; }
700     static if (D >= 4) { alias w = get_!'w'; }
701 
702     static if (isNumeric!E)
703     {
704         /* Need these conversions when E is for instance ubyte.
705            See this commit: https://github.com/Dav1dde/gl3n/commit/2504003df4f8a091e58a3d041831dc2522377f95 */
706         static immutable E0 = E(0);
707         static immutable E1 = E(1);
708         static if (dimension == 2)
709         {
710             static immutable Vector e1 = Vector(E1, E0); /// canonical basis for Euclidian space
711             static immutable Vector e2 = Vector(E0, E1); /// ditto
712         }
713         else static if (dimension == 3)
714         {
715             static immutable Vector e1 = Vector(E1, E0, E0); /// canonical basis for Euclidian space
716             static immutable Vector e2 = Vector(E0, E1, E0); /// ditto
717             static immutable Vector e3 = Vector(E0, E0, E1); /// ditto
718         }
719         else static if (dimension == 4)
720         {
721             static immutable Vector e1 = Vector(E1, E0, E0, E0); /// canonical basis for Euclidian space
722             static immutable Vector e2 = Vector(E0, E1, E0, E0); /// ditto
723             static immutable Vector e3 = Vector(E0, E0, E1, E0); /// ditto
724             static immutable Vector e4 = Vector(E0, E0, E0, E1); /// ditto
725         }
726     }
727 
728     version(none)
729     @safe pure nothrow @nogc unittest
730     {
731         static if (isNumeric!E)
732         {
733             assert(vec2.e1[] == [1, 0].s);
734             assert(vec2.e2[] == [0, 1].s);
735 
736             assert(vec3.e1[] == [1, 0, 0].s);
737             assert(vec3.e2[] == [0, 1, 0].s);
738             assert(vec3.e3[] == [0, 0, 1].s);
739 
740             assert(vec4.e1[] == [1, 0, 0, 0].s);
741             assert(vec4.e2[] == [0, 1, 0, 0].s);
742             assert(vec4.e3[] == [0, 0, 1, 0].s);
743             assert(vec4.e4[] == [0, 0, 0, 1].s);
744         }
745     }
746 
747     /** Element data. */
748     E[D] _vector;
749 
750     unittest
751     {
752         // static if (isSigned!(E)) { assert(-Vector!(E,D)(+2),
753         //                                   +Vector!(E,D)(-2)); }
754     }
755 
756 }
757 
758 auto rowVector(Ts...)(Ts args)
759 if (!is(CommonType!Ts == void))
760 {
761     return Vector!(CommonType!Ts, args.length)(args);
762 }
763 alias vector = rowVector; // TODO: Should rowVector or columnVector be default?
764 
765 auto columnVector(Ts...)(Ts args)
766 if (!is(CommonType!Ts == void))
767 {
768     return Vector!(CommonType!Ts, args.length, false, Orient.column)(args);
769 }
770 
771 ///
772 @safe pure nothrow @nogc unittest
773 {
774     assert(point(1, 2) + vector(1, 2) == point(2, 4));
775     assert(point(1, 2) - vector(1, 2) == point(0, 0));
776 }
777 
778 version(unittest)
779 {
780     import std.conv : to;
781     import nxt.array_help : s;
782 }