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.Rect;
25 
26 private {
27 	debug import std.stdio;
28 	import std.traits : isNumeric;
29 	
30 	import derelict.sdl2.sdl;
31 
32 	import Dgame.Math.Vector2;
33 	import Dgame.Internal.Unique;
34 }
35 
36 SDL_Rect* transfer(T)(const Rect!(T)* rect, SDL_Rect* to) pure nothrow
37 in {
38 	assert(to !is null);
39 } body {
40 	if (rect is null)
41 		return null;
42 
43 	rect.transferTo(to);
44 
45 	return to;
46 }
47 
48 /**
49  * Rect defines a rectangle structure that contains the left upper corner and the width/height.
50  *
51  * Author: rschuett
52  */
53 struct Rect(T) if (isNumeric!T) {
54 	/**
55 	 * The x
56 	 */
57 	T x = 0;
58 	/**
59 	 * and y coordinates
60 	 */
61 	T y = 0;
62 	/**
63 	 * The width
64 	 */
65 	T width = 0;
66 	/**
67 	 * and the height
68 	 */
69 	T height = 0;
70 	
71 	/**
72 	 * CTor
73 	 */
74 	this(T x, T y, T width, T height) pure nothrow {
75 		this.x = x;
76 		this.y = y;
77 		
78 		this.width  = width;
79 		this.height = height;
80 	}
81 	
82 	/**
83 	 * CTor
84 	 */
85 	this(ref const Vector2!(T) vec, T width, T height) pure nothrow {
86 		this(vec.x, vec.y, width, height);
87 	}
88 	
89 	/**
90 	 * CTor
91 	 */
92 	this(U)(ref const Rect!U rect) pure nothrow {
93 		static if (is(U : T)) {
94 			this(rect.x, rect.y, rect.width, rect.height);
95 		} else {
96 			this(cast(T) rect.x, cast(T) rect.y,
97 			     cast(T) rect.width, cast(T) rect.height);
98 		}
99 	}
100 
101 	this(ref const SDL_Rect rect) {
102 		this(cast(T) rect.x, cast(T) rect.y,
103 		     cast(T) rect.w, cast(T) rect.h);
104 	}
105 
106 //	debug(Dgame)
107 //	this(this) {
108 //		writeln("Postblit");
109 //	}
110 //	
111 //	debug(Dgame)
112 //	~this() {
113 //		debug writeln("DTor Rect");
114 //	}
115 	
116 	/**
117 	 * Transfer the internal data to the SDL_Rect.
118 	 */
119 	void transferTo(SDL_Rect* rect) const pure nothrow
120 	in {
121 		assert(rect !is null, "Cannot transfer anything to null.");
122 	} body {
123 		rect.x = cast(int) this.x;
124 		rect.y = cast(int) this.y;
125 		rect.w = cast(int) this.width;
126 		rect.h = cast(int) this.height;
127 	}
128 	
129 	/**
130 	 * Supported operations: +=, -=, *=, /=, %=
131 	 */
132 	Rect opBinary(string op)(ref const Rect rect) const pure nothrow {
133 		switch (op) {
134 			case "+":
135 				return Rect(this.x + rect.x,
136 				              this.y + rect.y,
137 				              this.width + rect.width,
138 				              this.height + rect.height);
139 			case "-":
140 				return Rect(this.x - rect.x,
141 				              this.y - rect.y,
142 				              this.width - rect.width,
143 				              this.height - rect.height);
144 			case "*":
145 				return Rect(this.x * rect.x,
146 				              this.y * rect.y,
147 				              this.width * rect.width,
148 				              this.height * rect.height);
149 			case "/":
150 				return Rect(this.x / rect.x,
151 				              this.y / rect.y,
152 				              this.width / rect.width,
153 				              this.height / rect.height);
154 			case "%":
155 				return Rect(this.x % rect.x,
156 				              this.y % rect.y,
157 				              this.width % rect.width,
158 				              this.height % rect.height);
159 			default:
160 				throw new Exception("Unsupported Operation: " ~ op);
161 		}
162 	}
163 	
164 	/**
165 	 * Collapse this Rect. Means that the coordinates and the size is set to 0.
166 	 */
167 	void collapse() pure nothrow {
168 		this.width = this.height = 0;
169 		this.x = this.y = 0;
170 	}
171 	
172 	/**
173 	 * Checks if this Rect is empty (if it's collapsed) with SDL_RectEmpty.
174 	 */
175 	bool isEmpty() const {
176 		SDL_Rect a = void;
177 		this.transferTo(&a);
178 
179 		return SDL_RectEmpty(&a) == SDL_TRUE;
180 	}
181 	
182 	/**
183 	 * Checks if this Rect is collapsed, which means
184 	 * that the width and/or the height are <= 0.
185 	 * This is a pure and nothrow variant of isEmpty.
186 	 */
187 	bool isCollapsed() const pure nothrow {
188 		return this.width <= 0 || this.height <= 0;
189 	}
190 	
191 	/**
192 	 * Checks if all corners are zero.
193 	 */
194 	bool isZero() const pure nothrow {
195 		return this.x == 0 && this.y == 0 && this.width == 0 && this.height == 0;
196 	}
197 	
198 	/**
199 	 * Returns an union of the given and this Rect.
200 	 */
201 	Rect getUnion(ref const Rect rect) const {
202 		SDL_Rect a = void;
203 		SDL_Rect b = void;
204 		SDL_Rect c = void;
205 
206 		this.transferTo(&a);
207 		rect.transferTo(&b);
208 
209 		SDL_UnionRect(&a, &b, &c);
210 
211 		return Rect(c);
212 	}
213 	
214 	/**
215 	 * Checks whether this Rect contains the given coordinates.
216 	 */
217 	bool opBinaryRight(string op : "in")(ref Vector2!(T) vec) const pure nothrow {
218 		return this.contains(vec);
219 	}
220 	
221 	/**
222 	 * Checks whether this Rect contains the given coordinates.
223 	 */
224 	bool contains(ref const Vector2!(T) vec) const pure nothrow {
225 		return this.contains(vec.x, vec.y);
226 	}
227 	
228 	/**
229 	 * Checks whether this Rect contains the given coordinates.
230 	 */
231 	bool contains(T x, T y) const pure nothrow {
232 		return (x >= this.x) && (x < this.x + this.width)
233 			&& (y >= this.y) && (y < this.y + this.height);
234 	}
235 	
236 	/**
237 	 * opEquals: compares two rectangles on their coordinates and their size (but not explicit type).
238 	 */
239 	bool opEquals(ref const Rect rect) const {
240 		SDL_Rect a = void;
241 		SDL_Rect b = void;
242 
243 		this.transferTo(&a);
244 		rect.transferTo(&b);
245 
246 		return SDL_RectEquals(&a, &b);
247 	}
248 	
249 	/**
250 	 * opCast to another Rect type.
251 	 */
252 	Rect!U opCast(V : Rect!U, U)() const pure nothrow {
253 		return Rect!U(cast(U) this.x, cast(U) this.y,
254 		              cast(U) this.width, cast(U) this.height);
255 	}
256 	
257 	/**
258 	 * Checks whether this Rect intersects with an other.
259 	 * If, and the parameter 'overlap' isn't null,
260 	 * the colliding rectangle is stored there.
261 	 */
262 	bool intersects(ref const Rect rect, Rect* overlap = null) const {
263 		SDL_Rect a = void;
264 		SDL_Rect b = void;
265 
266 		this.transferTo(&a);
267 		rect.transferTo(&b);
268 
269 		if (overlap is null)
270 			return SDL_HasIntersection(&a, &b) == SDL_TRUE;
271 
272 		SDL_Rect c = void;
273 
274 		const bool intersects = SDL_IntersectRect(&a, &b, &c) == SDL_TRUE;
275 		overlap.set(cast(T) c.x, cast(T) c.y,
276 		            cast(T) c.w, cast(T) c.h);
277 
278 		return intersects;
279 	}
280 	
281 	/**
282 	 * Use this function to calculate a minimal rectangle enclosing a set of points.
283 	 */
284 	static Rect enclosePoints(in Vector2!(T)[] points) {
285 		unique_ptr!(SDL_Point) sdl_points = allocate_unique!(SDL_Point)(points.length);
286 //		SDL_Point[] sdl_points = new SDL_Point[points.length];
287 
288 		foreach (i, ref const Vector2!(T) p; points) {
289 			sdl_points[i] = SDL_Point(cast(int) p.x, cast(int) p.y);
290 		}
291 
292 		SDL_Rect a = void;
293 		SDL_EnclosePoints(&sdl_points[0], cast(uint) points.length, null, &a);
294 
295 		return Rect(a);
296 	}
297 	
298 	/**
299 	 * Replace current size.
300 	 */
301 	void setSize(T width, T height) pure nothrow {
302 		this.width  = width;
303 		this.height = height;
304 	}
305 	
306 	/**
307 	 * Returns the current size as Vector2
308 	 */
309 	Vector2!(T) getSize() const pure nothrow {
310 		return Vector2!(T)(this.width, this.height);
311 	}
312 	
313 	/**
314 	 * Increase current size.
315 	 */
316 	void increase(T width, T height) pure nothrow {
317 		this.width += width;
318 		this.height += height;
319 	}
320 	
321 	/**
322 	 * Set a new position with coordinates.
323 	 */
324 	void setPosition(T x, T y) pure nothrow {
325 		this.x = x;
326 		this.y = y;
327 	}
328 	
329 	/**
330 	 * Set a new position with a vector.
331 	 */
332 	void setPosition(ref const Vector2!(T) position) pure nothrow {
333 		this.setPosition(position.x, position.y);
334 	}
335 	
336 	/**
337 	 * Returns the current position as Vector2
338 	 */
339 	Vector2!(T) getPosition() const pure nothrow {
340 		return Vector2!(T)(this.x, this.y);
341 	}
342 	
343 	/**
344 	 * Move the object.
345 	 */
346 	void move(ref const Vector2!(T) vec) pure nothrow {
347 		this.move(vec.x, vec.y);
348 	}
349 	
350 	/**
351 	 * Move the object.
352 	 */
353 	void move(T x, T y) pure nothrow {
354 		this.x += x;
355 		this.y += y;
356 	}
357 	
358 	/**
359 	 * The new coordinates <b>and</b> a new size.
360 	 */
361 	void set(T x, T y, T w, T h) pure nothrow {
362 		this.setPosition(x, y);
363 		this.setSize(w, h);
364 	}
365 
366 	/**
367 	 * Returns the coordinates as static array
368 	 */
369 	T[4] asArray() const pure nothrow {
370 		return [this.x, this.y, this.width, this.height];
371 	}
372 }
373 
374 /**
375  * alias for float
376  */
377 alias FloatRect = Rect!(float);
378 /**
379  * alias for short
380  */
381 alias ShortRect = Rect!(short);