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