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