1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module atelier.core.vec2;
7 
8 import std.math;
9 
10 import atelier.core.vec3;
11 import atelier.core.vec4;
12 
13 /// Ratio to multiply with to get a value in radians from a value in degrees.
14 enum double degToRad = std.math.PI / 180.0;
15 /// Ratio to multiply with to get a value in degrees from a value in radians.
16 enum double radToDeg = 180.0 / std.math.PI;
17 
18 /// Represent a mathematical 2-dimensional vector.
19 struct Vec2(T) {
20     static assert(__traits(isArithmetic, T));
21 
22     static if (__traits(isUnsigned, T)) {
23         /// {1, 1} vector. Its length is not one !
24         enum one = Vec2!T(1u, 1u);
25         /// Null vector.
26         enum zero = Vec2!T(0u, 0u);
27     }
28     else {
29         static if (__traits(isFloating, T)) {
30             /// {1, 1} vector. Its length is not one !
31             enum one = Vec2!T(1f, 1f);
32             /// {0.5, 0.5} vector. Its length is not 0.5 !
33             enum half = Vec2!T(.5f, .5f);
34             /// Null vector.
35             enum zero = Vec2!T(0f, 0f);
36             /// {-1, 0} vector.
37             enum left = Vec2!T(-1f, 0f);
38             /// {1, 0} vector.
39             enum right = Vec2!T(1f, 0f);
40             /// {0, -1} vector.
41             enum down = Vec2!T(0f, -1f);
42             /// {0, 1} vector.
43             enum up = Vec2!T(0f, 1f);
44         }
45         else {
46             /// {1, 1} vector. Its length is not one !
47             enum one = Vec2!T(1, 1);
48             /// Null vector.
49             enum zero = Vec2!T(0, 0);
50             /// {-1, 0} vector.
51             enum left = Vec2!T(-1, 0);
52             /// {1, 0} vector.
53             enum right = Vec2!T(1, 0);
54             /// {0, -1} vector.
55             enum down = Vec2!T(0, -1);
56             /// {0, 1} vector.
57             enum up = Vec2!T(0, 1);
58         }
59     }
60 
61     /// x-axis coordinate.
62     T x;
63     /// y-axis coordinate.
64     T y;
65 
66     /// Changes {x, y}
67     void set(T x_, T y_) {
68         x = x_;
69         y = y_;
70     }
71 
72     /// The distance between this point and the other one.
73     T distance(Vec2!T v) const {
74         static if (__traits(isUnsigned, T))
75             alias V = int;
76         else
77             alias V = T;
78 
79         V px = x - v.x, py = y - v.y;
80 
81         static if (__traits(isFloating, T))
82             return std.math.sqrt(px * px + py * py);
83         else
84             return cast(T) std.math.sqrt(cast(float)(px * px + py * py));
85     }
86 
87     /// The distance between this point and the other one squared.
88     /// Not square rooted, so no crash and more efficient.
89     T distanceSquared(Vec2!T v) const {
90         static if (__traits(isUnsigned, T))
91             alias V = int;
92         else
93             alias V = T;
94 
95         V px = x - v.x, py = y - v.y;
96 
97         return px * px + py * py;
98     }
99 
100     /// Dot product of the 2 vectors.
101     T dot(const Vec2!T v) const {
102         return x * v.x + y * v.y;
103     }
104 
105     /// Cross product of the 2 vectors.
106     T cross(const Vec2!T v) const {
107         static if (__traits(isUnsigned, T))
108             return cast(int)(x * v.y) - cast(int)(y * v.x);
109         else
110             return (x * v.y) - (y * v.x);
111     }
112 
113     /// The normal axis of this vector.
114     Vec2!T normal() const {
115         return Vec2!T(-y, x);
116     }
117 
118     /// Reflect this vector inside another one that represents the area change.
119     Vec2!T reflect(const Vec2!T v) const {
120         static if (__traits(isFloating, T)) {
121             const T dotNI2 = 2.0 * x * v.x + y * v.y;
122             return Vec2!T(cast(T)(x - dotNI2 * v.x), cast(T)(y - dotNI2 * v.y));
123         }
124         else {
125             const T dotNI2 = 2 * x * v.x + y * v.y;
126             static if (__traits(isUnsigned, T))
127                 return Vec2!T(cast(int)(x) - cast(int)(dotNI2 * v.x),
128                         cast(int)(y) - cast(int)(dotNI2 * v.y));
129             else
130                 return Vec2!T(x - dotNI2 * v.x, y - dotNI2 * v.y);
131         }
132     }
133 
134     /// Refracts this vector onto another one that represents the area change.
135     Vec2!T refract(const Vec2!T v, float eta) const {
136         static if (__traits(isFloating, T)) {
137             const T dotNI = (x * v.x + y * v.y);
138             T k = 1.0 - eta * eta * (1.0 - dotNI * dotNI);
139             if (k < .0)
140                 return Vec2!T(T.init, T.init);
141             else {
142                 const double s = (eta * dotNI + sqrt(k));
143                 return Vec2!T(eta * x - s * v.x, eta * y - s * v.y);
144             }
145         }
146         else {
147             const float dotNI = cast(float)(x * v.x + y * v.y);
148             float k = 1.0f - eta * eta * (1.0f - dotNI * dotNI);
149             if (k < 0f)
150                 return Vec2!T(T.init, T.init);
151             else {
152                 const float s = (eta * dotNI + sqrt(k));
153                 return Vec2!T(cast(T)(eta * x - s * v.x), cast(T)(eta * y - s * v.y));
154             }
155         }
156     }
157 
158     /// The smaller vector possible between the two.
159     Vec2!T min(const Vec2!T v) const {
160         return Vec2!T(x < v.x ? x : v.x, y < v.y ? y : v.y);
161     }
162 
163     /// The larger vector possible between the two.
164     Vec2!T max(const Vec2!T v) const {
165         return Vec2!T(x > v.x ? x : v.x, y > v.y ? y : v.y);
166     }
167 
168     /// Remove negative components.
169     Vec2!T abs() const {
170         static if (__traits(isFloating, T))
171             return Vec2!T(x < .0 ? -x : x, y < .0 ? -y : y);
172         else static if (__traits(isUnsigned, T))
173             return Vec2!T(x < 0U ? -x : x, y < 0U ? -y : y);
174         else
175             return Vec2!T(x < 0 ? -x : x, y < 0 ? -y : y);
176     }
177 
178     /// Truncate the values.
179     Vec2!T floor() const {
180         static if (__traits(isFloating, T))
181             return Vec2!T(std.math.floor(x), std.math.floor(y));
182         else
183             return this;
184     }
185 
186     /// Round up the values.
187     Vec2!T ceil() const {
188         static if (__traits(isFloating, T))
189             return Vec2!T(std.math.ceil(x), std.math.ceil(y));
190         else
191             return this;
192     }
193 
194     /// Round the values to the nearest integer.
195     Vec2!T round() const {
196         static if (__traits(isFloating, T))
197             return Vec2!T(std.math.round(x), std.math.round(y));
198         else
199             return this;
200     }
201 
202     static if (__traits(isFloating, T)) {
203         /// Returns the vector actual angle.
204         T angle() const {
205             return std.math.atan2(y, x) * radToDeg;
206         }
207 
208         /// Add an angle to this vector.
209         Vec2!T rotate(T angle) {
210             const T radians = angle * degToRad;
211             const T px = x, py = y;
212             const T c = std.math.cos(radians);
213             const T s = std.math.sin(radians);
214             x = px * c - py * s;
215             y = px * s + py * c;
216             return this;
217         }
218 
219         /// Returns a vector with an added angle without modifying this one.
220         Vec2!T rotated(T angle) const {
221             const T radians = angle * degToRad;
222             const T c = std.math.cos(radians);
223             const T s = std.math.sin(radians);
224             return Vec2f(x * c - y * s, x * s + y * c);
225         }
226 
227         /// Returns a unit vector with an angle.
228         static Vec2!T angled(T angle) {
229             const T radians = angle * degToRad;
230             return Vec2f(std.math.cos(radians), std.math.sin(radians));
231         }
232     }
233 
234     /// Adds x and y.
235     T sum() const {
236         return x + y;
237     }
238 
239     /// The total length of this vector.
240     T length() const {
241         static if (__traits(isFloating, T))
242             return std.math.sqrt(x * x + y * y);
243         else
244             return cast(T) std.math.sqrt(cast(float)(x * x + y * y));
245     }
246 
247     /// The squared length of this vector.
248     /// Can be null, and more efficiant than length.
249     T lengthSquared() const {
250         return x * x + y * y;
251     }
252 
253     /// Transform this vector in a unit vector.
254     void normalize() {
255         static if (__traits(isFloating, T))
256             const T len = std.math.sqrt(x * x + y * y);
257         else
258             const T len = cast(T) std.math.sqrt(cast(float)(x * x + y * y));
259 
260         if (len == 0) {
261             x = len;
262             y = len;
263             return;
264         }
265         x /= len;
266         y /= len;
267     }
268 
269     /// Returns a unit vector from this one without modifying this one.
270     Vec2!T normalized() const {
271         static if (__traits(isFloating, T))
272             const T len = std.math.sqrt(x * x + y * y);
273         else
274             const T len = cast(T) std.math.sqrt(cast(float)(x * x + y * y));
275         if (len == 0) {
276             return zero;
277         }
278         return Vec2!T(x / len, y / len);
279     }
280 
281     /// Bounds the vector between those two.
282     Vec2!T clamp(const Vec2!T min, const Vec2!T max) const {
283         Vec2!T v = {x, y};
284         if (v.x < min.x)
285             v.x = min.x;
286         else if (v.x > max.x)
287             v.x = max.x;
288         if (v.y < min.y)
289             v.y = min.y;
290         else if (v.y > max.y)
291             v.y = max.y;
292         return v;
293     }
294 
295     /// Bounds the vector between those boundaries.
296     Vec2!T clamp(const Vec4!T clip) const {
297         Vec2!T v = {x, y};
298         if (v.x < clip.x)
299             v.x = clip.x;
300         else if (v.x > clip.z)
301             v.x = clip.z;
302         if (v.y < clip.y)
303             v.y = clip.y;
304         else if (v.y > clip.w)
305             v.y = clip.w;
306         return v;
307     }
308 
309     /// Is this vector between those boundaries ?
310     bool isBetween(const Vec2!T min, const Vec2!T max) const {
311         if (x < min.x)
312             return false;
313         else if (x > max.x)
314             return false;
315         if (y < min.y)
316             return false;
317         else if (y > max.y)
318             return false;
319         return true;
320     }
321 
322     static if (__traits(isFloating, T)) {
323         /// Returns an interpolated vector from this vector to the end vector by a factor. \
324         /// Does not modify this vector.
325         Vec2!T lerp(Vec2!T end, float t) const {
326             return (this * (1.0 - t)) + (end * t);
327         }
328     }
329 
330     /// While conserving the x/y ratio, returns the largest vector possible that fits inside the other vector. (like a size) \
331     /// Does not modify this vector.
332     Vec2!T fit(const Vec2!T v) const {
333         if (v == Vec2!T.zero)
334             return v;
335         return (x / y) < (v.x / v.y) ? Vec2!T(x * v.y / y, v.y) : Vec2!T(v.x, y * v.x / x);
336     }
337 
338     /// While conserving the x/y ratio, returns the smallest vector possible that can contain the other vector. (like a size) \
339     /// Does not modify this vector.
340     Vec2!T contain(const Vec2!T v) const {
341         if (v == Vec2!T.zero)
342             return v;
343         return (x / y) < (v.x / v.y) ? Vec2!T(v.x, y * v.x / x) : Vec2!T(x * v.y / y, v.y);
344     }
345 
346     /// Linear interpolation to approach a target
347     Vec2!T approach(const Vec2!T target, const Vec2!T step) {
348         import std.algorithm.comparison : min, max;
349 
350         return Vec2!T(x > target.x ? max(x - step.x, target.x) : min(x + step.x,
351                 target.x), y > target.y ? max(y - step.y, target.y) : min(y + step.y, target.y));
352     }
353 
354     /// Equality operations
355     bool opEquals(const Vec2!T v) const @safe pure nothrow {
356         return (x == v.x) && (y == v.y);
357     }
358 
359     /// Ditto
360     int opCmp(const Vec2!T v) const @safe pure nothrow {
361         const T a = x - v.x;
362         const T b = y - v.y;
363         if (a && b)
364             return 1;
365         if (a < 0 && b < 0)
366             return -1;
367         return 0;
368     }
369 
370     /// Unary operations
371     Vec2!T opUnary(string op)() const @safe pure nothrow {
372         return mixin("Vec2!T(" ~ op ~ " x, " ~ op ~ " y)");
373     }
374 
375     /// Binary operations
376     Vec2!T opBinary(string op)(const Vec2!T v) const @safe pure nothrow {
377         return mixin("Vec2!T(x " ~ op ~ " v.x, y " ~ op ~ " v.y)");
378     }
379 
380     /// Binary operations
381     Vec2!T opBinary(string op)(T s) const @safe pure nothrow {
382         return mixin("Vec2!T(x " ~ op ~ " s, y " ~ op ~ " s)");
383     }
384 
385     /// Binary operations
386     Vec2!T opBinaryRight(string op)(T s) const @safe pure nothrow {
387         return mixin("Vec2!T(s " ~ op ~ " x, s " ~ op ~ " y)");
388     }
389 
390     /// Assignment
391     Vec2!T opOpAssign(string op)(Vec2!T v) @safe pure nothrow {
392         mixin("x = x" ~ op ~ "v.x;y = y" ~ op ~ "v.y;");
393         return this;
394     }
395 
396     /// Assignment
397     Vec2!T opOpAssign(string op)(T s) @safe pure nothrow {
398         mixin("x = x" ~ op ~ "s;y = y" ~ op ~ "s;");
399         return this;
400     }
401 
402     /// Conversion
403     Vec2!U opCast(V : Vec2!U, U)() const @safe pure nothrow {
404         return V(cast(U) x, cast(U) y);
405     }
406 
407     /// Hash value.
408     size_t toHash() const @safe pure nothrow {
409         import std.typecons : tuple;
410 
411         return tuple(x, y).toHash();
412     }
413 }
414 
415 alias Vec2f = Vec2!(float);
416 alias Vec2i = Vec2!(int);
417 alias Vec2u = Vec2!(uint);