1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2017 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  */
24 
25 /**
26  * A rectangle is defined by its top-left corner and its size. It is a very
27  * simple class defined for convenience, so its member variables (`left`, `top`,
28  * `width`, and `height`) are public and can be accessed directly, just like the
29  * vector classes ($(VECTOR2_LINK) and $(VECTOR3_LINK)).
30  *
31  * To keep things simple, $(U Rect) doesn't define functions to emulate the
32  * properties that are not directly members (such as right, bottom, center,
33  * etc.), it rather only provides intersection functions.
34  *
35  * Rect uses the usual rules for its boundaries:
36  * $(UL
37  * $(LI The let and top edges are included in the rectangle's area)
38  * $(LI The right (left + width) and bottom (top + height) edges are excluded
39  * from the rectangle's area))
40  *
41  * $(PARA This means that `IntRect(0, 0, 1, 1)` and `IntRect(1, 1, 1, 1)` don't
42  * intersect.
43  *
44  * $(U Rect) is a template and may be used with any numeric type, but for
45  * simplicity the instanciations used by SFML are aliased:)
46  * $(UL
47  * $(LI Rect!(int) is IntRect)
48  * $(LI Rect!(float) is FloatRect))
49  *
50  * $(PARA This is so you don't have to care about the template syntax.)
51  *
52  * Example:
53  * ---
54  * // Define a rectangle, located at (0, 0) with a size of 20x5
55  * auto r1 = IntRect(0, 0, 20, 5);
56  *
57  * // Define another rectangle, located at (4, 2) with a size of 18x10
58  * auto position = Vector2i(4, 2);
59  * auto size = Vector2i(18, 10);
60  * auto r2 = IntRect(position, size);
61  *
62  * // Test intersections with the point (3, 1)
63  * bool b1 = r1.contains(3, 1); // true
64  * bool b2 = r2.contains(3, 1); // false
65  *
66  * // Test the intersection between r1 and r2
67  * IntRect result;
68  * bool b3 = r1.intersects(r2, result); // true
69  * // result == IntRect(4, 2, 16, 3)
70  * ---
71  */
72 module dsfml.graphics.rect;
73 
74 import std.traits;
75 
76 import dsfml.system.vector2;
77 
78 /**
79  * Utility class for manipulating 2D axis aligned rectangles.
80  */
81 struct Rect(T)
82     if(isNumeric!(T))
83 {
84     /// Left coordinate of the rectangle.
85     T left = 0;
86     /// Top coordinate of the rectangle.
87     T top = 0;
88     /// Width of the rectangle.
89     T width= 0;
90     /// Height of the rectangle.
91     T height = 0;
92 
93     /**
94      * Construct the rectangle from its coordinates
95      *
96      * Be careful, the last two parameters are the width
97      * and height, not the right and bottom coordinates!
98      *
99      * Params:
100      *	rectLeft   = Left coordinate of the rectangle
101      *  rectTop    = Top coordinate of the rectangle
102      *  rectWidth  = Width of the rectangle
103      *  rectHeight = Height of the rectangle
104      */
105     this(T rectLeft, T rectTop, T rectWidth, T rectHeight)
106     {
107         left = rectLeft;
108         top = rectTop;
109         width = rectWidth;
110         height = rectHeight;
111     }
112 
113     /**
114      * Construct the rectangle from position and size
115      *
116      * Be careful, the last parameter is the size,
117      * not the bottom-right corner!
118      *
119      * Params:
120      *  position = Position of the top-left corner of the rectangle
121      *  size     = Size of the rectangle
122      */
123     this(Vector2!(T) position, Vector2!(T) size)
124     {
125         left = position.x;
126         top = position.y;
127         width = size.x;
128         height = size.y;
129     }
130 
131     /**
132      * Check if a point is inside the rectangle's area.
133      *
134      * Params:
135      * 		x	= X coordinate of the point to test
136      * 		y	= Y coordinate of the point to test
137      *
138      * Returns: true if the point is inside, false otherwise.
139      */
140     bool contains(E)(E X, E Y) const
141         if(isNumeric!(E))
142     {
143         if(left <= X && X<= (left + width))
144         {
145             if(top <= Y && Y <= (top + height))
146             {
147                 return true;
148             }
149             else
150             {
151                 return false;
152             }
153         }
154         else
155         {
156             return false;
157         }
158     }
159 
160     /**
161      * Check if a point is inside the rectangle's area.
162      *
163      * Params:
164      * 		point	= Point to test
165      *
166      * Returns: true if the point is inside, false otherwise.
167      */
168     bool contains(E)(Vector2!(E) point) const
169         if(isNumeric!(E))
170     {
171         if(left <= point.x && point.x<= (left + width))
172         {
173             if(top <= point.y && point.y <= (top + height))
174             {
175                 return true;
176             }
177             else
178             {
179                 return false;
180             }
181         }
182         else
183         {
184             return false;
185         }
186     }
187 
188     /**
189      * Check the intersection between two rectangles.
190      *
191      * Params:
192      * 		rectangle	= Rectangle to test
193      *
194      * Returns: true if rectangles overlap, false otherwise.
195      */
196     bool intersects(E)(Rect!(E) rectangle) const
197     if(isNumeric!(E))
198     {
199         Rect!(T) rect;
200 
201         return intersects(rectangle, rect);
202     }
203 
204     /**
205      * Check the intersection between two rectangles.
206      *
207      * This overload returns the overlapped rectangle in the intersection
208      * parameter.
209      *
210      * Params:
211      * 		rectangle		= Rectangle to test
212      * 		intersection	= Rectangle to be filled with the intersection
213      *
214      * Returns: true if rectangles overlap, false otherwise.
215      */
216     bool intersects(E,O)(Rect!(E) rectangle, out Rect!(O) intersection) const
217         if(isNumeric!(E) && isNumeric!(O))
218     {
219         O interLeft = intersection.max(left, rectangle.left);
220         O interTop = intersection.max(top, rectangle.top);
221         O interRight = intersection.min(left + width, rectangle.left + rectangle.width);
222         O interBottom = intersection.min(top + height, rectangle.top + rectangle.height);
223 
224         if ((interLeft < interRight) && (interTop < interBottom))
225         {
226             intersection = Rect!(O)(interLeft, interTop, interRight - interLeft, interBottom - interTop);
227             return true;
228         }
229         else
230         {
231             intersection = Rect!(O)(0, 0, 0, 0);
232             return false;
233         }
234     }
235 
236     /// Compare two rectangles for equality.
237     bool opEquals(E)(const Rect!(E) otherRect) const
238         if(isNumeric!(E))
239     {
240         return ((left == otherRect.left) && (top == otherRect.top) && (width == otherRect.width) && (height == otherRect.height) );
241     }
242 
243     /// Output the string representation of the Rect.
244     string toString()
245     {
246         import std.conv;
247         return "Left: " ~ text(left) ~ " Top: " ~ text(top) ~ " Width: " ~ text(width) ~ " Height: " ~ text(height);
248     }
249 
250     private T max(T a, T b)
251     {
252         return a>b?a:b;
253     }
254 
255     private T min(T a, T b)
256     {
257         return a<b?a:b;
258     }
259 }
260 
261 unittest
262 {
263     version(DSFML_Unittest_Graphics)
264     {
265         import std.stdio;
266 
267         writeln("Unit test for Rect");
268 
269         auto rect1 = IntRect(0,0,100,100);
270         auto rect2 = IntRect(10,10,100,100);
271         auto rect3 = IntRect(10,10,10,10);
272         auto point = Vector2f(-20,-20);
273 
274         assert(rect1.intersects(rect2));
275 
276         FloatRect interRect;
277 
278         rect1.intersects(rect2, interRect);
279 
280         assert(interRect == IntRect(10,10, 90, 90));
281 
282         assert(rect1.contains(10,10));
283 
284         assert(!rect1.contains(point));
285 
286         writeln();
287     }
288 }
289 
290 /// Definition of a Rect using integers.
291 alias Rect!(int) IntRect;
292 /// Definition of a Rect using floats.
293 alias Rect!(float) FloatRect;