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 	debug import std.stdio : writeln;
28 	import std.math : pow, sqrt, acos, PI;
29 	import std.traits : isNumeric;
30 }
31 
32 /**
33  * Vector2 is a structure that defines a two-dimensional point.
34  *
35  * Author: rschuett
36  */
37 struct Vector2(T) if (isNumeric!T) {
38 	/**
39 	 * x coordinate
40 	 */
41 	T x = 0;
42 	/**
43 	 * y coordinate
44 	 */
45 	T y = 0;
46 	
47 	/**
48 	 * CTor
49 	 */
50 	this(T x, T y) pure nothrow {
51 		this.x = x;
52 		this.y = y;
53 	}
54 	
55 	/**
56 	 * CTor
57 	 */
58 	this(U)(U x, U y) pure nothrow if (isNumeric!U && !is(U : T)) {
59 		this(cast(T) x, cast(T) y);
60 	}
61 	
62 	/**
63 	 * CTor
64 	 */
65 	this(U)(U[2] pos) pure nothrow if (isNumeric!U) {
66 		this(pos[0], pos[1]);
67 	}
68 	
69 	/**
70 	 * CTor
71 	 */
72 	this(U)(ref const Vector2!U vec) pure nothrow {
73 		this(vec.x, vec.y);
74 	}
75 	
76 //	debug(Dgame)
77 //	this(this) {
78 //		writeln("Postblit Vector2");
79 //	}
80 	
81 	/**
82 	 * Supported operation: +=, -=, *=, /= and %=
83 	 */
84 	ref Vector2 opOpAssign(string op)(ref const Vector2 vec) pure {
85 		switch (op) {
86 			case "+":
87 				this.x += vec.x;
88 				this.y += vec.y;
89 				break;
90 			case "-":
91 				this.x -= vec.x;
92 				this.y -= vec.y;
93 				break;
94 			case "*":
95 				this.x *= vec.x;
96 				this.y *= vec.y;
97 				break;
98 			case "/":
99 				this.x /= vec.x;
100 				this.y /= vec.y;
101 				break;
102 			case "%":
103 				this.x %= vec.x;
104 				this.y %= vec.y;
105 				break;
106 			default: throw new Exception("Unsupported operator " ~ op);
107 		}
108 		
109 		return this;
110 	}
111 	
112 	/**
113 	 * Supported operation: +=, -=, *=, /= and %=
114 	 */
115 	ref Vector2 opOpAssign(string op)(T number) pure {
116 		switch (op) {
117 			case "+":
118 				this.x += number;
119 				this.y += number;
120 				break;
121 			case "-":
122 				this.x -= number;
123 				this.y -= number;
124 				break;
125 			case "*":
126 				this.x *= number;
127 				this.y *= number;
128 				break;
129 			case "/":
130 				this.x /= number;
131 				this.y /= number;
132 				break;
133 			case "%":
134 				this.x %= number;
135 				this.y %= number;
136 				break;
137 			default: throw new Exception("Unsupported operator " ~ op);
138 		}
139 		
140 		return this;
141 	}
142 	
143 	/**
144 	 * Supported operation: +, -, *, / and %
145 	 */
146 	Vector2 opBinary(string op)(ref const Vector2 vec) const pure {
147 		switch (op) {
148 			case "+": return Vector2(vec.x + this.x, vec.y + this.y);
149 			case "-": return Vector2(vec.x - this.x, vec.y - this.y);
150 			case "*": return Vector2(vec.x * this.x, vec.y * this.y);
151 			case "/": return Vector2(vec.x / this.x, vec.y / this.y);
152 			case "%": return Vector2(vec.x % this.x, vec.y % this.y);
153 			default: throw new Exception("Unsupported operator " ~ op);
154 		}
155 	}
156 	
157 	/**
158 	 * Supported operation: +, -, *, / and %
159 	 */
160 	Vector2 opBinary(string op)(T number) const pure {
161 		switch (op) {
162 			case "+": return Vector2(number + this.x, number + this.y);
163 			case "-": return Vector2(number - this.x, number - this.y);
164 			case "*": return Vector2(number * this.x, number * this.y);
165 			case "/": return Vector2(number / this.x, number / this.y);
166 			case "%": return Vector2(number % this.x, number % this.y);
167 			default: throw new Exception("Unsupported operator " ~ op);
168 		}
169 	}
170 	
171 	/**
172 	 * Returns a negated copy of this Vector.
173 	 */
174 	Vector2 opNeg() const pure nothrow {
175 		return Vector2(-this.x, -this.y);
176 	}
177 	
178 	/**
179 	 * Negate this Vector
180 	 */
181 	void negate() pure nothrow {
182 		this.x = -this.x;
183 		this.y = -this.y;
184 	}
185 	
186 	/**
187 	 * Compares two vectors by checking whether the coordinates are equals.
188 	 */
189 	bool opEquals(ref const Vector2 vec) const pure nothrow {
190 		return vec.x == this.x && vec.y == this.y;
191 	}
192 
193 	/**
194 	 * Checks if this vector is empty. This means that his coordinates are 0.
195 	 */
196 	bool isEmpty() const pure nothrow {
197 		return this.x == 0 && this.y == 0;
198 	}
199 	
200 	/**
201 	 * Calculate the scalar product.
202 	 */
203 	float scalar(ref const Vector2 vec) const pure nothrow {
204 		return this.x * vec.x + this.y * vec.y;
205 	}
206 	
207 	/**
208 	 * alias for scalar
209 	 */
210 	alias dot = scalar;
211 	
212 	/**
213 	 * Calculate the length.
214 	 */
215 	@property
216 	float length() const pure nothrow {
217 		if (this.isEmpty())
218 			return 0f;
219 		
220 		return sqrt(pow(this.x, 2f) + pow(this.y, 2f));
221 	}
222 	
223 	/**
224 	 * Calculate the angle between two vectors.
225 	 * If the second paramter is true, the return value is converted to degrees.
226 	 * Otherwise, radiant is used.
227 	 */
228 	float angle(ref const Vector2 vec, bool degrees = true) const pure nothrow {
229 		float angle = acos(this.scalar(vec) / (this.length * vec.length));
230 		
231 		if (degrees)
232 			return angle * 180f / PI;
233 		
234 		return angle;
235 	}
236 	
237 	/**
238 	 * Calculate the diff between two vectors.
239 	 */
240 	float diff(ref const Vector2 vec) const pure nothrow {
241 		return sqrt(pow(this.x - vec.x, 2f) + pow(this.y - vec.y, 2f));
242 	}
243 	
244 	/**
245 	 * Normalize the vector in which the coordinates are divided by the length.
246 	 */
247 	ref Vector2 normalize() pure nothrow {
248 		const float len = this.length;
249 		if (len != 0) {
250 			this.x /= len;
251 			this.y /= len;
252 		}
253 		
254 		return this;
255 	}
256 	
257 	/**
258 	 * Set new coordinates.
259 	 */
260 	void set(T x, T y) pure nothrow {
261 		this.x = x;
262 		this.y = y;
263 	}
264 	
265 	/**
266 	 * Move the current coordinates.
267 	 */
268 	void move(T x, T y) pure nothrow {
269 		this.x += x;
270 		this.y += y;
271 	}
272 	
273 	/**
274 	 * Returns the Vector as static array.
275 	 */
276 	T[2] asArray() const pure nothrow {
277 		return [this.x, this.y];
278 	}
279 } unittest {
280 	Vector2f vec;
281 	assert(vec.isEmpty());
282 	vec += 1;
283 	assert(vec.x == 1f && vec.y == 1f);
284 	assert(!vec.isEmpty());
285 }
286 
287 /**
288  * Alias for short Vector
289  */
290 alias Vector2s = Vector2!(short);
291 /**
292  * Alias for float Vector
293  */
294 alias Vector2f = Vector2!(float);
295 /**
296  * Alias for byte Vector
297  */
298 alias Vector2b = Vector2!(byte);