1 /++
2 A module that describes how to work with a vector, as well as functions for 
3 processing a vector for other needs.
4 
5 Please note that the vector works only with two-dimensional space, therefore, 
6 the vector is calculated only along two axes.
7 
8 Macros:
9     LREF = <a href="#$1">$1</a>
10     HREF = <a href="$1">$2</a>
11 
12 Authors: $(HREF https://github.com/TodNaz, TodNaz)
13 Copyright: Copyright (c) 2020 - 2021, TodNaz.
14 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE, MIT)
15 +/
16 module tida.vector;
17 
18 import std.traits;
19 import std.math;
20 
21 /++
22 Vector structure. May include any numeric data type available for vector arithmetic.
23 
24 Example:
25 ---
26 assert(vec!float(32, 32) + vecf(32, 32) == Vector!float[64, 64]);
27 ---
28 +/
29 struct Vector(T)
30 if (isNumeric!T && isSigned!T)
31 {
32     import core.exception;
33 
34 public:
35     alias Type = T;
36 
37     T   x, /// X-axis position.
38         y; /// Y-axis position. 
39 
40 @safe nothrow pure:
41     /++
42     Any numeric type that can be freely converted to a template vector type 
43     can be included in the vector's constructor.
44     +/
45     this(R)(R x, R y)
46     if (isImplicitlyConvertible!(R, T))
47     {
48         this.x = cast(T) x;
49         this.y = cast(T) y;
50     }
51 
52     /++
53     Any numeric type that can be freely converted to a template vector type 
54     can be included in the vector's constructor.
55     +/
56     this(R)(R[2] arrvec)
57     if (isImplicitlyConvertible!(R, T))
58     {
59         this.x = cast(T) arrvec[0];
60         this.y = cast(T) arrvec[1];
61     }
62 
63     void opIndexAssign(R)(R value, size_t index)
64     if (isImplicitlyConvertible!(R, T))
65     {
66         if (index == 0)
67         {
68             this.x = cast(R) value;
69         } else
70         if (index == 1)
71         {
72             this.y = cast(R) value;
73         } else
74             throw new RangeError();
75     }
76 
77     void opOpAssign(string op)(Vector rhs)
78     {
79         static if (op == "+")
80         {
81             this.x += rhs.x;
82             this.y += rhs.y;
83         }else 
84         static if (op == "-")
85         {
86             this.x = x - rhs.x;
87             this.y = y - rhs.y;
88         }else
89         static if (op == "*")
90         {
91             this.x = x * rhs.x;
92             this.y = y * rhs.y;
93         }else
94         static if (op == "/")
95         {
96             this.x = x / rhs.x;
97             this.y = y / rhs.y;
98         }else
99             static assert(null, "The `" ~ op ~ "` operator is not implemented.");
100     }
101 
102     void opOpAssign(string op)(T num)
103     {
104         static if (op == "+")
105         {
106             this.x = this.x + num;
107             this.y = this.y + num;
108         }else
109         static if (op == "-")
110         {
111             this.x = this.x - num;
112             this.y = this.y - num;
113         }else
114         static if (op == "*")
115         {
116             this.x = this.x * num;
117             this.y = this.y * num;
118         }else 
119         static if (op == "/")
120         {
121             this.x = this.x / num;
122             this.y = this.y / num;
123         }else
124             static assert(null, "The `" ~ op ~ "` operator is not implemented.");
125     }
126 
127     /++
128     Normalizes the vector.
129     +/
130     void normalize()
131     {
132         immutable d = 1 / length;
133 
134         this.x = x * d;
135         this.y = y * d;
136     }
137     
138 inout:
139     T opIndex(size_t index)
140     {
141         if (index == 0)
142         {
143             return this.x;
144         } else
145         if (index == 1)
146         {
147             return this.y;
148         } else
149             throw new RangeError();
150     }
151 
152     /++
153     Converts a vector to an array.
154     +/
155     T[] array()
156     {
157         return [x, y];
158     }
159 
160     bool opEquals(Vector a, Vector b)
161     {
162         return a is b || (a.x == b.x && a.y == b.y);
163     }
164 
165     bool opEquals(Vector other)
166     {
167         return this is other || (this.x == other.x && this.y == other.y);
168     }
169 
170     int opCmp(Vector rhs)
171     {
172         return (x > rhs.x && y > rhs.y) ? 1 : -1;
173     }
174 
175     Vector!T opBinary(string op)(Vector rhs)
176     {
177         static if (op == "+")
178         {
179             return Vector!T(this.x + rhs.x, this.y + rhs.y);
180         }
181         else 
182         static if (op == "-")
183         {
184             return Vector!T(this.x - rhs.x, this.y - rhs.y);
185         }
186         else
187         static if (op == "*")
188         {
189             return Vector!T(this.x * rhs.x, this.y * rhs.y);
190         }else
191         static if (op == "/")
192         {
193             return Vector!T(this.x / rhs.x, this.y / rhs.y);
194         }else
195         static if (op == "%")
196         {
197             return Vector!T(this.x % rhs.x, this.y % rhs.y);
198         }else
199             static assert(null, "The `" ~ op ~ "` operator is not implemented.");
200     }
201 
202     Vector!T opBinary(string op, R)(R num)
203     if (isImplicitlyConvertible!(R, T))
204     {
205         static if (op == "+")
206         {
207             return Vector!T(this.x + num, this.y + num);
208         }else
209         static if (op == "-")
210         {
211             return Vector!T(this.x - num, this.y - num);
212         }else
213         static if (op == "*")
214         {
215             return Vector!T(this.x * num, this.y * num);
216         }
217         else 
218         static if (op == "/")
219         {
220             return Vector!T(this.x / num, this.y / num);
221         }else
222         static if (op == "%")
223         {
224             return Vector!T(this.x % num, this.y % num);
225         }else
226         static if (op == "^^")
227         {
228             return Vector!T(this.x ^^ num, this.y ^^ num);
229         }else
230             static assert(null, "The `" ~ op ~ "` operator is not implemented.");
231     }
232 
233     /++
234     Vector length.
235     +/
236     T length()
237     {
238         static if(isIntegral!T) 
239         {
240             return cast(T) (sqrt(cast(float) ((this.x * this.x) + (this.y * this.y))));
241         }else
242         {
243             return sqrt((this.x * this.x) + (this.y * this.y));
244         }
245     }
246 
247     /++
248     Returns a normalized vector.
249     +/
250     Vector!T normalized()
251     {
252         immutable d = 1 / length;
253 
254         return Vector!T(this.x * d, this.y * d);
255     }
256 
257     Vector!T negatived()
258     {
259         return Vector!T(-this.x, -this.y);
260     }
261 
262     Vector!T positived()
263     {
264         return Vector!T(+this.x, +this.y);
265     }
266 
267     template opUnary(string op)
268     if (op == "-" || op == "+")
269     {
270         static if (op == "+")
271             alias opUnary = positived;
272         else
273         static if (op == "-")
274             alias opUnary = negatived;
275     }
276 }
277 
278 unittest
279 {
280     assert(vec!real([32, 64]) == (vec!real(32.0, 64.0)));
281     assert(-vec!real(32, 32) == vec!real(-32, -32));
282 }
283 
284 /++
285 Checks if this type is a vector.
286 +/
287 template isVector(T)
288 {
289     enum isVector = __traits(hasMember, T, "x") && __traits(hasMember, T, "y");
290 }
291 
292 /++
293 Checks if the vector is integers.
294 +/
295 template isVectorIntegral(T)
296 if (isVector!T)
297 {
298     enum isVectorIntegral = isIntegral!(T.Type); 
299 }
300 
301 /++
302 Checks if the vector is float.
303 +/
304 template isVectorFloatingPoint(T)
305 if (isVector!T)
306 {
307     enum isVectorFloatingPoint = isFloatingPoint!(T.Type);
308 }
309 
310 unittest
311 {
312     static assert(isVector!(Vector!int));
313     static assert(!isVector!int);
314 
315     static assert(isVectorIntegral!(Vector!int));
316     static assert(!isVectorIntegral!(Vector!float));
317 
318     static assert(isVectorFloatingPoint!(Vector!double));
319     static assert(!isVectorFloatingPoint!(Vector!long));
320 }
321 
322 alias Vecf = Vector!float; /// Vector float.
323 alias vec(T) = Vector!T; /// Vector.
324 alias vecf = vec!float; /// Vector float.
325 
326 /++
327 Not a numeric vector.
328 +/
329 template vecNaN(T)
330 if (isVectorFloatingPoint!(Vector!T))
331 {
332     enum vecNaN = Vector!T(T.nan, T.nan);
333 }
334 
335 /// ditto
336 enum vecfNaN = vecNaN!float;
337 
338 /// Zero vector
339 enum vecZero(T) = vec!T(0, 0);
340 
341 /// ditto
342 enum vecfZero = vecZero!float;
343 
344 /++
345 Checks if the vector is non-numeric.
346 +/
347 bool isVectorNaN(T)(Vector!T vector)
348 if (isVectorFloatingPoint!(Vector!T))
349 {
350     import std.math : isNaN;
351 
352     return vector.x.isNaN && vector.y.isNaN;
353 }
354 
355 /// ditto
356 alias isVecfNaN = isVectorNaN!float;
357 
358 unittest
359 {
360     assert(vecfNaN.isVecfNaN);
361     assert(!vecf(0.0f, 0.0f).isVecfNaN);
362 }
363 
364 /++
365 Generates a buffer from vectors.
366 
367 Params:
368     vectors = Array vector.
369 +/
370 T[] generateArray(T)(Vector!T[] vectors) @safe nothrow pure
371 {
372     import std.algorithm : map, joiner;
373     import std.range : array;
374 
375     return vectors
376         .map!(e => e.array)
377         .joiner
378         .array;
379 }
380 
381 unittest
382 {
383     assert([vec!int(16, 16), vec!int(32, 48), vec!int(48, 8)].generateArray == ([16, 16, 32, 48, 48, 8]));
384 }
385 
386 inout(T) sqr(T)(inout(T) value) @safe nothrow pure
387 {
388     return value * value;
389 }
390 
391 /++
392 Construct the vector modulo.
393 
394 Params:
395     vec = Vector.
396 +/
397 inout(Vector!T) abs(T)(inout(Vector!T) vec) @safe nothrow pure
398 {
399     import std.math : abs;
400 
401     return Vector!T(abs(vec.x), abs(vec.y));
402 }
403 
404 unittest
405 {
406     assert(abs(vec!long(-64, -32)) == (vec!long(64, 32)));
407     assert(abs(vec!float(-128.5f, 19.0f)) == (vec!float(128.5f, 19.0f)));
408 }
409 
410 /++
411 Distance between two points.
412 
413 Params:
414     a = First point.
415     b = Second point.
416 +/
417 inout(T) distance(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure
418 {
419     import std.math : sqrt;
420 
421     return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y));
422 }
423 
424 /++
425 Average distance between vectors.
426 +/
427 inout(Vector!T) averateVectors(T)(inout(Vector!T) a, inout(Vector!T) b) @safe nothrow pure
428 {
429     return ((b - a) / 2) + ((a > b) ? b : a);
430 }
431 
432 unittest
433 {
434     assert(vec!long(32, 32).averateVectors(vec!long(64, 64)) == (vec!long(48, 48)));
435 
436     assert(vec!real(48.0, 48.0).averateVectors(vec!real(128.0, 128.0)) == (vec!real(88.0, 88.0)));
437 }
438 
439 /++
440 Creates a random vector.
441 
442 Params:
443     begin = Begin.
444     end = End.
445 
446 Example:
447 ---
448 Vecf rnd = uniform(vecf(32, 16), vecf(64, 48));
449 // vec.x = random: 32 .. 64
450 // vec.y = random: 16 .. 48
451 ---
452 +/
453 inout(Vector!T) uniform(T)(inout(Vector!T) begin, inout(Vector!T) end) @safe
454 {
455     import std.random : uniform;
456 
457     return Vector!T(uniform(begin.x, end.x), uniform(begin.y, end.y));
458 }
459 
460 /++
461 Rounds the vector up.
462 
463 Params:
464     vec = Rounded vector.
465 
466 Example:
467 ---
468 assert(Vecf(32.5,32.5) == Vecf(33, 33));
469 ---
470 +/
471 inout(Vector!T) round(T)(inout(Vector!T) vec) @safe nothrow pure
472 if (isVectorFloatingPoint!(Vector!T))
473 {
474     import core.stdc.math : roundl;
475 
476     return Vector!T(roundl(vec.x), roundl(vec.y));
477 }
478 
479 unittest
480 {
481     assert(vec!real(31.4, 33.51).round == (vec!real(31.0, 34.0)));
482 }
483 
484 /++
485 Floors the vector down.
486 
487 Params:
488     vec = Floored vector.
489 
490 Example:
491 ---
492 assert(vecf(32.5, 32.5) == vecf(32, 32));
493 ---
494 +/
495 inout(Vector!T) floor(T)(inout(Vector!T) vec) @safe nothrow pure
496 if (isVectorFloatingPoint!(Vector!T))
497 {
498     import std.math : floor;
499 
500     return Vector!T(floor(vec.x), floor(vec.y));
501 }
502 
503 unittest
504 {
505     assert(vec!double(31.4, 33.51).floor == (vec!double(31.0, 33.0)));
506 }