1 /**
2 Flexible vector template with some math utils
3 */
4 module sily.vector;
5 
6 import std.math;
7 import std.numeric;
8 import std.conv;
9 import std.traits;
10 import std.typetuple;
11 import std.algorithm;
12 import std.stdio;
13 import std.string;
14 import std.format;
15 
16 import sily.meta.swizzle;
17 import sily.math;
18 import sily.array;
19 
20 /// Alias to vector with set size
21 alias Vector2(T) = Vector!(T, 2);
22 /// Ditto
23 alias Vector3(T) = Vector!(T, 3);
24 /// Ditto
25 alias Vector4(T) = Vector!(T, 4);
26 
27 /// Alias to vector with set size and type
28 alias Vector2f = Vector2!float;
29 /// Ditto
30 alias Vector2d = Vector2!double;
31 /// Ditto
32 alias Vector2i = Vector2!int;
33 /// Ditto
34 alias Vector2u = Vector2!uint;
35 
36 /// Ditto
37 alias Vector3f = Vector3!float;
38 /// Ditto
39 alias Vector3d = Vector3!double;
40 /// Ditto
41 alias Vector3i = Vector3!int;
42 /// Ditto
43 alias Vector3u = Vector3!uint;
44 
45 /// Ditto
46 alias Vector4f = Vector4!float;
47 /// Ditto
48 alias Vector4d = Vector4!double;
49 /// Ditto
50 alias Vector4i = Vector4!int;
51 /// Ditto
52 alias Vector4u = Vector4!uint;
53 
54 /// GLSL style alias
55 alias vec2 = Vector2f;
56 /// Ditto
57 alias vec3 = Vector3f;
58 /// Ditto
59 alias vec4 = Vector4f;
60 
61 /// Ditto
62 alias dvec2 = Vector2d;
63 /// Ditto
64 alias dvec3 = Vector3d;
65 /// Ditto
66 alias dvec4 = Vector4d;
67 
68 /// Ditto
69 alias ivec2 = Vector2i;
70 /// Ditto
71 alias ivec3 = Vector3i;
72 /// Ditto
73 alias ivec4 = Vector4i;
74 
75 /// Ditto
76 alias uvec2 = Vector2u;
77 /// Ditto
78 alias uvec3 = Vector3u;
79 /// Ditto
80 alias uvec4 = Vector4u;
81 
82 /// Vector structure with data accesible with `[N]` or swizzling
83 struct Vector(T, size_t N) if (isNumeric!T && N > 0)  {
84     /// Vector data
85     public T[N] data = [ 0 ];
86 
87     /// Alias to allow easy `data` access
88     alias data this;
89     /// Alias to data type (e.g. float, int)
90     alias dataType = T;
91     /** 
92     Alias to vector type. Can be used to contruct vectors
93     of same type
94     ---
95     auto rvec7 = Vector!(real, 7)(10);
96     auto rvec7s = rvec7.VecType(20);
97     ---
98     */
99     alias VecType = Vector!(T, N);
100     /// Alias to vector size
101     enum size_t size = N;
102 
103     /**
104     Constructs Vector from components. If no components present
105     vector will be filled with 0
106     Example:
107     ---
108     // Vector can be constructed manually or with aliases
109     auto v1 = Vector!(int, 2)(10, 20);
110     auto v2 = ivec2(10, 20);
111     auto v3 = Vector2i(10, 20);
112     auto v4 = Vector2!int(10, 20);
113     // Also vector can be given only one value,
114     // in that case it'll be filled with that value
115     auto v5 = ivec4(13);
116     auto v6 = vec4(0.3f);
117     // Vector values can be accessed with array slicing,
118     // by using color symbols or swizzling
119     float v6x = v6.x;
120     float v6z = v6.z;
121     float[] v6yzx = v6.yzx;
122     float v6y = v6[1];
123     // Valid vector accessors are:
124     // Vector2 - [x, y], [w, h], [u, v]
125     // Vector3 - [x, y, z], [w, h, d], [u, v, t], [r, g, b]
126     // Vector4 - [x, y, z, w], [r, g, b, a]
127     // Other sizes must be accessed with index
128     ---
129     */
130     this(in T val) {
131         foreach (i; 0 .. size) { data[i] = val; }
132     }
133     /// Ditto
134     this(in T[N] vals...) {
135         data = vals;
136     }
137 
138     /* -------------------------------------------------------------------------- */
139     /*                         UNARY OPERATIONS OVERRIDES                         */
140     /* -------------------------------------------------------------------------- */
141     
142     /// opBinary x [+, -, *, /, %] y
143     auto opBinary(string op, R)(in Vector!(R, N) b) const if ( isNumeric!R ) {
144         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
145         VecType ret = VecType();
146         foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); }
147         return ret;
148     }
149 
150     /// Ditto
151     auto opBinaryRight(string op, R)(in Vector!(R, N) b) const if ( isNumeric!R ) {
152         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
153         VecType ret = VecType();
154         foreach (i; 0 .. size) { mixin( "ret[i] = b.data[i] " ~ op ~ " data[i];" ); }
155         return ret;
156     }
157 
158     /// Ditto
159     auto opBinary(string op, R)(in R b) const if ( isNumeric!R ) {
160         // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
161         VecType ret = VecType();
162         foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); }
163         return ret;
164     }
165 
166     /// Ditto
167     auto opBinaryRight(string op, R)(in R b) const if ( isNumeric!R ) {
168         // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
169         VecType ret = VecType();
170         foreach (i; 0 .. size) { mixin( "ret[i] = b " ~ op ~ " data[i];" ); }
171         return ret;
172     }
173 
174     /// opEquals x == y
175     bool opEquals(R)(in Vector!(R, size) b) const if ( isNumeric!R ) {
176         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
177         bool eq = true;
178         foreach (i; 0 .. size) { eq = eq && data[i] == b.data[i]; }
179         return eq;
180     }
181 
182     /// opCmp x [< > <= >=] y
183     int opCmp(R)(in Vector!(R, N) b) const if ( isNumeric!R ) {
184         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
185         T al = length;
186         T bl = b.length;
187         if (al == bl) return 0;
188         if (al < bl) return -1;
189         return 1;
190     }
191 
192     /// opUnary [-, +, --, ++] x
193     auto opUnary(string op)() if(op == "-"){
194         // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
195         VecType ret = VecType();
196         if (op == "-")
197             foreach (i; 0 .. size) { data[i] = -data[i]; }
198         return ret;
199     }
200     
201     /// opOpAssign x [+, -, *, /, %]= y
202     auto opOpAssign(string op, R)( in Vector!(R, N) b ) if ( isNumeric!R ) { 
203         // assert(/* this !is null && */ b !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
204         foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b.data[i];" ); }
205         return this;
206     }
207     
208     /// Ditto
209     auto opOpAssign(string op, R)( in R b ) if ( isNumeric!R ) { 
210         // assert(this !is null, "\nOP::ERROR nullptr Vector!" ~ size.to!string ~ ".");
211         foreach (i; 0 .. size) { mixin( "data[i] = data[i] " ~ op ~ " b;" ); }
212         return this;
213     }
214 
215     /// Returns hash 
216     size_t toHash() const @safe nothrow {
217         return typeid(data).getHash(&data);
218     }
219     
220     static if (N == 2 || N == 3 || N == 4) {
221         static if (N == 2) private enum AS = "x y|w h|u v"; 
222         else
223         static if (N == 3) private enum AS = "x y z|w h d|u v t|r g b"; 
224         else
225         static if (N == 4) private enum AS = "x y z w|r g b a";
226         /// Mixes in swizzle
227         mixin accessByString!(T, N, "data", AS);
228     }
229 
230     /// Returns copy of vector
231     public VecType copyof() {
232         return VecType(data);
233     }
234 
235     /// Returns string representation of vector: `[1.00, 1.00,... , 1.00]`
236     public string toString() const {
237         import std.conv : to;
238         string s;
239         s ~= "[";
240         foreach (i; 0 .. size) {
241             s ~= isFloatingPoint!T ? format("%.2f", data[i]) : format("%d", data[i]);
242             if (i != size - 1) s ~= ", ";
243         }
244         s ~= "]";
245         return s;
246     }
247 
248     /// Returns pointer to data
249     T* ptr() return {
250         return data.ptr;
251     }
252 
253     /* -------------------------------------------------------------------------- */
254     /*                         STATIC GETTERS AND SETTERS                         */
255     /* -------------------------------------------------------------------------- */
256     
257     /// Constructs predefined vector
258     static alias zero  = () => VecType(0);
259     /// Ditto
260     static alias one   = () => VecType(1);
261 
262     static if(isFloatingPoint!T) {
263         /// Ditto
264         static alias inf   = () => VecType(float.infinity);
265     }
266 
267     static if(N == 2) {
268         /// Ditto
269         static alias left  = () => VecType(-1, 0);
270         /// Ditto
271         static alias right = () => VecType(1, 0);
272         /// Ditto
273         static alias up    = () => VecType(0, -1);
274         /// Ditto
275         static alias down  = () => VecType(0, 1);
276     }
277 
278     static if(N == 3) {
279         static alias forward = () => VecType(0, 0, -1);
280         /// Ditto
281         static alias back    = () => VecType(0, 0, 1);
282         /// Ditto
283         static alias left    = () => VecType(-1, 0, 0);
284         /// Ditto
285         static alias right   = () => VecType(1, 0, 0);
286         /// Ditto
287         static alias up      = () => VecType(0, 1, 0);
288         /// Ditto
289         static alias down    = () => VecType(0, -1, 0);
290     }
291 
292     /* -------------------------------------------------------------------------- */
293     /*                                    MATH                                    */
294     /* -------------------------------------------------------------------------- */
295 
296     /// Returns squared vector length
297     public T lengthSquared() {
298         T l = 0;
299         foreach (i; 0 .. size) { l += data[i] * data[i]; }
300         return l;
301     }
302 
303     /** 
304     Returns squared distance from vector to `b`
305     Params:
306       b = Vector to calculate distance to
307     Returns: Distance
308     */
309     public T distanceSquaredTo(VecType b) {
310         T dist = 0;
311         foreach (i; 0 .. size) { dist += (data[i] - b.data[i]) * (data[i] - b.data[i]); }
312         return dist;
313     }
314 
315     /* -------------------------------------------------------------------------- */
316     /*                             FLOATING POINT MATH                            */
317     /* -------------------------------------------------------------------------- */
318     // Int math is still might be accessible
319     // if I'll need it. All I'd need to do
320     // Is to add another `static if` 
321 
322     // FLOAT VECTORS
323     static if(isFloatingPoint!T) {
324         /** 
325         Is vector approximately close to `v`
326         Params:
327           v = Vector to compare
328         Returns: 
329         */
330         public bool isClose(VecType v) {
331             bool eq = true;
332             foreach (i; 0 .. size) { eq = eq && data[i].isClose(v[i], float.epsilon); }
333             return eq;
334         }
335 
336         /// Returns vector length
337         public T length() {
338             return sqrt(lengthSquared);
339         }
340 
341         /// Normalises vector
342         public void normalize() {
343             T l = lengthSquared;
344             if (l != 0) {
345                 l = sqrt(lengthSquared);
346                 foreach (i; 0 .. size) { data[i] /= l; }
347             }
348         }
349         /// Ditto
350         alias normalise = normalize;
351         
352         /// Returns true if vector is normalised
353         public bool isNormalized() {
354             return lengthSquared.isClose(1, float.epsilon);
355         }
356         /// Ditto
357         alias isNormalised = isNormalized;
358 
359         /** 
360         Calculates distance to vector `b`
361         Params:
362           b = Vector
363         Returns: Distance
364         */
365         public T distanceTo(VecType b) {
366             return sqrt(distanceSquaredTo(b));
367         }
368 
369         /** 
370         Performs dot product
371         Params:
372           b = Vector
373         Returns: dot product
374         */
375         public float dot(VecType b) {
376             T d = 0;
377             foreach (i; 0 .. size) { d += data[i] * b.data[i]; }
378             return d;
379         }
380 
381         /// Signs current vector
382         public void sign() {
383             foreach (i; 0 .. size) { data[i] = data[i].sgn(); }
384         }
385 
386         /// Floors vector values
387         public void floor() {
388             foreach (i; 0 .. size) { data[i] = data[i].floor(); }
389         }
390 
391         /// Ceils vector values
392         public void ceil() {
393             foreach (i; 0 .. size) { data[i] = data[i].ceil(); }
394         }
395 
396         /// Rounds vector values
397         public void round() {
398             foreach (i; 0 .. size) { data[i] = data[i].round(); }
399         }
400 
401         /// Abs vector values
402         public void abs() {
403             foreach (i; 0 .. size) { data[i] = data[i].abs(); }
404         }
405 
406         /** 
407         Clamps vector values to min
408         Params:
409           b = Minimal Vector
410         */
411         public void min(VecType b) {
412             foreach (i; 0 .. size) { data[i] = data[i].min(b.data[i]); }
413         }
414 
415         /** 
416         Clamps vector values to max
417         Params:
418           b = Maximal Vector
419         */
420         public void max(VecType b) {
421             foreach (i; 0 .. size) { data[i] = data[i].max(b.data[i]); }
422         }
423 
424         /** 
425         Clamps vector values
426         Params:
427           b = Minimal Vector
428           b = Maximal Vector
429         */
430         public void clamp(VecType p_min, VecType p_max) {
431             foreach (i; 0 .. size) { data[i] = data[i].clamp(p_min.data[i], p_max.data[i]); }
432         }
433 
434         /** 
435         Snaps vector values
436         Params:
437           p_step = Vector to snap to
438         */
439         public void snap(VecType p_step) {
440             foreach (i; 0 .. size) { 
441                 data[i] = data[i].snap(p_step[i]);
442             }
443         }
444 
445         /** 
446         Limits vector length
447         Params:
448           p_len = Max length
449         */
450         public void limitLength(T p_len) {
451             T l = length();
452             if (l > 0 && p_len < l) {
453                 for (int i = 0; i < size; ++i) {
454                     data[i] /= l;
455                     data[i] *= p_len;
456                 }
457             }
458         }
459 
460         /** 
461         Linear interpolates vector
462         Params:
463           to = Vector to interpolate to
464           weight = Interpolation weight in range [0.0, 1.0]
465         */
466         public void lerp(VecType to, T weight) {
467             foreach (i; 0 .. size) { data[i] = (weight * (to.data[i] - data[i])); }
468         }
469 
470         // FIXME
471         // TODO
472         // it's in math
473         // public Vector2!T cubicInterpolate(Vector2!T b, Vector2!T prea, Vector2!T postb, float weight) {
474         //     Vector2 res = *this;
475         //     res.x = Math::cubic_interpolate(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight);
476         //     res.y = Math::cubic_interpolate(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight);
477         //     return res;
478         // }
479     }
480 
481     /* -------------------------------------------------------------------------- */
482     /*                                  VECTOR2F                                  */
483     /* -------------------------------------------------------------------------- */
484     static if(isFloatingPoint!T && N == 2) {
485         // public static Vector2!T fromAngle(float p_angle) {
486         //     return Vector2!T(cos(p_angle), sin(p_angle));
487         // }
488 
489         // public float cross(VecType b) {
490         //     return this.x * b.y - this.y * b.x;
491         // }
492         
493         // public float angle() {
494         //     return atan2(this.y, this.x);
495         // }
496 
497         // public float angleTo(Vector2!T b) {
498         //     return atan2(cross(b), dot(b));
499         // }
500 
501         // public float angleToPoint(Vector2!T b) {
502         //     return (b - data).angle();
503         // }
504 
505         // public float aspect() {
506         //     return this.x / this.y;
507         // }
508 
509         // public Vector2!T project(Vector2!T b) {
510         //     return b * (dot(b) / b.lengthSquared());
511         // }
512 
513         // public Vector2!T moveToward(Vector2!T p_to, const T p_delta) {
514         //     Vector2!T v = copyof();
515         //     Vector2!T vd = p_to - v;
516         //     T len = vd.length;
517         //     return len <= p_delta || len < float.epsilon ? p_to : v + vd / len * p_delta;
518         // }
519 
520         // public Vector2!T slide(Vector2!T p_normal) {
521         //     if (!p_normal.isNormalized) {
522         //         writeln("Normal vector must be normalized");
523         //         // throw new Error("MATH::ERROR::VECTOR2");
524         //         return copyof();
525         //     }
526         //     return copyof() - p_normal * dot(p_normal);
527         // }
528 
529         // public Vector2!T bounce(Vector2!T p_normal) {
530         //     return -reflect(p_normal);
531         // }
532 
533         // public Vector2!T reflect(Vector2!T p_normal) {
534         //     if (!p_normal.isNormalized) {
535         //         writeln("Normal vector must be normalized");
536         //         // throw new Error("MATH::ERROR::VECTOR2");
537         //         return copyof();
538         //     }
539         //     return  to!T(2) * p_normal * dot(p_normal) - copyof();
540         // }
541 
542         // public Vector2!T orthogonal() {
543         //     return Vector2!T(this.y, -this.x);
544         // }
545 
546         // public Vector2!T rotated(float phi) {
547         //     T sine = sin(phi);
548         //     T cosi = cos(phi);
549         //     return Vector2!T(
550         //         this.x * cosi - this.y * sine,
551         //         this.x * sine + this.y * cosi);
552         // }
553 
554         // public VecType slerp(VecType to, T weight) {
555         //     T stLensq = lengthSquared;
556         //     T enLensq = to.lengthSquared;
557         //     if (stLensq == 0.0f || enLensq == 0.0f) {
558         //         // Zero length vectors have no angle, so the best we can do is either lerp or throw an error.
559         //         return lerp(to, weight);
560         //     }
561         //     T stLen = sqrt(stLensq);
562         //     T rsLen = stLen.lerp(sqrt(enLensq), weight);
563         //     T angle = angleTo(to);
564         //     return rotated(angle * weight) * (rsLen / stLen);
565         // }
566 
567         // LINK https://glmatrix.net/docs/vec2.js
568         // LINK https://github.com/godotengine/godot/blob/master/core/math/vector2.cpp
569     }
570 
571     /* -------------------------------------------------------------------------- */
572     /*                                  VECTOR3F                                  */
573     /* -------------------------------------------------------------------------- */
574     static if(isFloatingPoint!T && N == 3) {
575         // TODO
576         // VecType cross(VecType b) {
577         //     VecType cr = VecType();            
578         //     T ax = data[0],
579         //       ay = data[1],
580         //       az = data[2];
581         //     T bx = b.data[0],
582         //       by = b.data[1],
583         //       bz = b.data[2];
584         //     cr.data[0] = ay * bz - az * by;
585         //     cr.data[1] = az * bx - ax * bz;
586         //     cr.data[2] = ax * by - ay * bx;
587         //     return cr;
588         // }
589 
590         // TODO
591 
592         // LINK https://glmatrix.net/docs/vec3.js.html
593         // LINK https://github.com/godotengine/godot/blob/master/core/math/vector3.cpp
594     }
595 
596     /* -------------------------------------------------------------------------- */
597     /*                                  VECTOR4F                                  */
598     /* -------------------------------------------------------------------------- */
599     // here probably gonna be almost nothing
600     // here be dragons?
601     static if(isFloatingPoint!T && N == 4) {
602         // TODO ?
603     }
604     
605     // Vector2 Vector2::posmod(const real_t p_mod) const {
606     //     return Vector2(Math::fposmod(x, p_mod), Math::fposmod(y, p_mod));
607     // }
608 
609     // Vector2 Vector2::posmodv(const Vector2 &p_modv) const {
610     //     return Vector2(Math::fposmod(x, p_modv.x), Math::fposmod(y, p_modv.y));
611     // }
612 }