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