1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  * 
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  * 
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  * 
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  * 
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  * 
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Math.Vector2;
25 
26 private:
27 
28 import std.traits : isNumeric;
29 static import std.math;
30 
31 public:
32 
33 /**
34  * Vector2 is a structure that defines a two-dimensional point.
35  *
36  * Author: Randy Schuett (rswhite4@googlemail.com)
37  */
38 struct Vector2(T) if (isNumeric!(T)) {
39     /**
40      * The x coordinate
41      */
42     T x = 0;
43     /**
44      * The y coordinate
45      */
46     T y = 0;
47     
48     /**
49      * CTor
50      */
51     @nogc
52     this(T x, T y) pure nothrow {
53         this.x = x;
54         this.y = y;
55     }
56     
57     /**
58      * CTor
59      */
60     @nogc
61     this(U)(U x, U y) pure nothrow if (isNumeric!(U) && !is(U == T)) {
62         this(cast(T) x, cast(T) y);
63     }
64     
65     /**
66      * CTor
67      */
68     @nogc
69     this(U)(const Vector2!(U) vec) pure nothrow if (!is(U == T)) {
70         this(vec.x, vec.y);
71     }
72     
73     /**
74      * Supported operation: +=, -=, *=, /= and %=
75      */
76     @nogc
77     ref Vector2!(T) opOpAssign(string op)(const Vector2!(T) vec) pure nothrow {
78         switch (op) {
79             case "+":
80             case "-":
81             case "*":
82             case "/":
83             case "%":
84                 mixin("this.x " ~ op ~ "= vec.x;");
85                 mixin("this.y " ~ op ~ "= vec.y;");
86             break;
87             default:
88                 assert(0, "Unsupported operator " ~ op);
89         }
90         
91         return this;
92     }
93     
94     /**
95      * Supported operation: +=, -=, *=, /= and %=
96      */
97     @nogc
98     ref Vector2!(T) opOpAssign(string op)(float num) pure nothrow {
99         switch (op) {
100             case "+":
101             case "-":
102             case "*":
103             case "/":
104             case "%":
105                 mixin("this.x = cast(T)(this.x " ~ op ~ " num);");
106                 mixin("this.y = cast(T)(this.y " ~ op ~ " num);");
107             break;
108             default:
109                 assert(0, "Unsupported operator " ~ op);
110         }
111         
112         return this;
113     }
114     
115     /**
116      * Supported operation: +, -, *, / and %
117      */
118     @nogc
119     Vector2!(T) opBinary(string op)(const Vector2!(T) vec) const pure nothrow {
120         switch (op) {
121             case "+":
122             case "-":
123             case "*":
124             case "/":
125             case "%":
126                 mixin("return Vector2!(T)(this.x " ~ op ~ " vec.x, this.y " ~ op ~ " vec.y);");
127             default:
128                 assert(0, "Unsupported operator " ~ op);
129         }
130     }
131     
132     /**
133      * Supported operation: +, -, *, / and %
134      */
135     @nogc
136     Vector2!(T) opBinary(string op)(float num) const pure {
137         switch (op) {
138             case "+":
139             case "-":
140             case "*":
141             case "/":
142             case "%":
143                 mixin("return Vector2!(T)(cast(T)(this.x " ~ op ~ " num), cast(T)(this.y " ~ op ~ " num));");
144             default:
145                 assert(0, "Unsupported operator " ~ op);
146         }
147     }
148     
149     /**
150      * Returns a negated copy of this Vector.
151      */
152     @nogc
153     Vector2!(T) opNeg() const pure nothrow {
154         return Vector2!(T)(-this.x, -this.y);
155     }
156     
157     /**
158      * Compares two vectors by checking whether the coordinates are equals.
159      */
160     @nogc
161     bool opEquals(const Vector2!(T) vec) const pure nothrow {
162         return vec.x == this.x && vec.y == this.y;
163     }
164 
165     /**
166      * Checks if this vector is empty. This means that his coordinates are 0.
167      */
168     @nogc
169     bool isEmpty() const pure nothrow {
170         return this.x == 0 && this.y == 0;
171     }
172     
173     /**
174      * Calculate the scalar product.
175      */
176     @nogc
177     float scalar(const Vector2!(T) vec) const pure nothrow {
178         return this.x * vec.x + this.y * vec.y;
179     }
180     
181     /**
182      * alias for scalar
183      */
184     alias dot = scalar;
185     
186     /**
187      * Calculate the length.
188      */
189     @nogc
190     @property
191     float length() const pure nothrow {
192         if (this.isEmpty())
193             return 0f;
194         return std.math.sqrt(std.math.pow(this.x, 2f) + std.math.pow(this.y, 2f));
195     }
196     
197     /**
198      * Calculate the angle between two vectors.
199      * If the second paramter is true, the return value is converted to degrees.
200      * Otherwise, radiant is used.
201      */
202     @nogc
203     float angle(const Vector2!(T) vec, bool degrees = true) const pure nothrow {
204         immutable float angle = std.math.acos(this.scalar(vec) / (this.length * vec.length));
205         if (degrees)
206             return angle * 180f / std.math.PI;
207         
208         return angle;
209     }
210     
211     /**
212      * Calculate the diff between two vectors.
213      */
214     @nogc
215     float diff(const Vector2!(T) vec) const pure nothrow {
216         return std.math.sqrt(std.math.pow(this.x - vec.x, 2f) + std.math.pow(this.y - vec.y, 2f));
217     }
218     
219     /**
220      * Normalize the vector in which the coordinates are divided by the length.
221      */
222     @nogc
223     ref Vector2!(T) normalize() pure nothrow {
224         immutable float len = this.length;
225         if (len != 0) {
226             this.x = cast(T)(this.x / len);
227             this.y = cast(T)(this.y / len);
228         }
229         
230         return this;
231     }
232 }
233 
234 alias Vector2f = Vector2!(float); /// A float representation
235 alias Vector2i = Vector2!(int);  /// An int representation
236 
237 @nogc
238 unittest {
239     Vector2i vec;
240 
241     assert(vec.x == 0);
242     assert(vec.y == 0);
243     assert(vec.isEmpty());
244 
245     vec = Vector2i(20, 30);
246 
247     assert(vec.x == 20);
248     assert(vec.y == 30);
249     assert(!vec.isEmpty());
250 
251     vec += 42;
252 
253     assert(vec.x == 62);
254     assert(vec.y == 72);
255     assert(!vec.isEmpty());
256 
257     const Vector2i vec2 = vec * 3;
258 
259     assert(vec2.x == 3 * vec.x);
260     assert(vec2.y == 3 * vec.y);
261 
262     const Vector2i vec3 = vec2 + vec;
263 
264     assert(vec3.x == vec.x + vec2.x);
265     assert(vec3.y == vec.y + vec2.y);
266 
267     assert(vec == vec);
268     assert(vec2 != vec3);
269 
270     const Vector2i vec4 = -vec;
271 
272     assert(vec4.x == -62);
273     assert(vec4.y == -72);
274 
275     const Vector2f vconv = vec4;
276 
277     assert(vec4.x == vconv.x && vec4.y == vconv.y);
278 
279     Vector2f v1 = Vector2f(2.3, 4.2);
280     immutable float l1 = v1.length;
281     const Vector2f v1n = v1.normalize();
282 
283     Vector2i v2 = Vector2i(2.3, 4.2);
284     immutable float l2 = v2.length;
285     const Vector2i v2n = v2.normalize();
286 
287     const Vector2f vec5 = Vector2f(80, 64);
288     const Vector2f vec6 = Vector2f(32, 32);
289     const Vector2f vec7 = Vector2f(2.5, 2);
290 
291     assert(vec5 / vec6 == vec7);
292     assert(vec5 / vec6.x == vec7);
293 
294     const Vector2i vec8 = Vector2i(32, 32);
295     const Vector2f vec9 = (vec8 / 32) * 32;
296 
297     assert(vec9.x == vec8.x && vec9.y == vec8.y);
298 }