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