1 /++
2  + Authors: Avaxar <avaxar@nekkl.org>
3  + Copyright: Copyright © 2023, Avaxar
4  + License: $(LINK2 https://mit-license.org, MIT License)
5  +/
6 
7 module dsdl2.frect;
8 @safe:
9 
10 // dfmt off
11 import bindbc.sdl;
12 static if (sdlSupport >= SDLSupport.v2_0_10):
13 // dfmt on
14 
15 import dsdl2.sdl;
16 import dsdl2.rect;
17 
18 import std.format : format;
19 import std.typecons : Nullable, nullable;
20 
21 /++
22  + D struct that wraps `SDL_FPoint` (from SDL 2.0.10) containing 2D floating point coordinate pair
23  +
24  + `dsdl2.FPoint` stores `float`ing point `x` and `y` coordinate points. This wrapper also implements
25  + vector-like operator overloading.
26  +/
27 struct FPoint {
28     SDL_FPoint sdlFPoint; /// Internal `SDL_FPoint` struct
29 
30     this() @disable;
31 
32     /++
33      + Constructs a `dsdl2.FPoint` from a vanilla `SDL_FPoint` from bindbc-sdl
34      +
35      + Params:
36      +   sdlFPoint = the `dsdl2.FPoint` struct
37      +/
38     this(SDL_FPoint sdlFPoint) {
39         this.sdlFPoint = sdlFPoint;
40     }
41 
42     /++
43      + Constructs a `dsdl2.FPoint` by feeding in an `x` and `y` pair
44      +
45      + Params:
46      +   x = x coordinate point
47      +   y = y coordinate point
48      +/
49     this(float x, float y) {
50         this.x = x;
51         this.y = y;
52     }
53 
54     /++
55      + Constructs a `dsdl2.FPoint` by feeding in an array of `x` and `y`
56      +
57      + Params:
58      +   xy = x and y coordinate point array
59      +/
60     this(float[2] xy) {
61         this.sdlFPoint.x = xy[0];
62         this.sdlFPoint.y = xy[1];
63     }
64 
65     /++
66      + Constructs a `dsdl2.FPoint` from a `dsdl2.Point`
67      +
68      + Params:
69      +   point = `dsdl2.Point` whose attributes are to be copied
70      +/
71     this(Point point) {
72         this.sdlFPoint.x = point.x;
73         this.sdlFPoint.y = point.y;
74     }
75 
76     /++
77      + Unary element-wise operation overload template
78      +/
79     FPoint opUnary(string op)() const {
80         return FPoint(mixin(op ~ "this.x"), mixin(op ~ "this.y"));
81     }
82 
83     /++
84      + Binary element-wise operation overload template
85      +/
86     FPoint opBinary(string op)(const FPoint other) const {
87         return FPoint(mixin("this.x" ~ op ~ "other.x"), mixin("this.y" ~ op ~ "other.y"));
88     }
89 
90     /++
91      + Binary operation overload template with scalars
92      +/
93     FPoint opBinary(string op)(float scalar) const if (op == "*" || op == "/") {
94         return FPoint(mixin("this.x" ~ op ~ "scalar"), mixin("this.y" ~ op ~ "scalar"));
95     }
96 
97     /++
98      + Element-wise operator assignment overload
99      +/
100     ref inout(FPoint) opOpAssign(string op)(const FPoint other) return inout {
101         mixin("this.x" ~ op ~ "=other.x");
102         mixin("this.y" ~ op ~ "=other.y");
103         return this;
104     }
105 
106     /++
107      + Operator assignment overload with scalars
108      +/
109     ref inout(FPoint) opOpAssign(string op)(const float scalar) return inout
110     if (op == "*" || op == "/") {
111         mixin("this.x" ~ op ~ "=scalar");
112         mixin("this.y" ~ op ~ "=scalar");
113         return this;
114     }
115 
116     /++
117      + Formats the `dsdl2.FPoint` into its construction representation: `"dsdl2.FPoint(<x>, <y>)"`
118      +
119      + Returns: the formatted `string`
120      +/
121     string toString() const {
122         return "dsdl2.FPoint(%f, %f)".format(this.x, this.y);
123     }
124 
125     /++
126      + Proxy to the X value of the `dsdl2.FPoint`
127      +
128      + Returns: X value of the `dsdl2.FPoint`
129      +/
130     ref inout(float) x() return inout @property {
131         return this.sdlFPoint.x;
132     }
133 
134     /++
135      + Proxy to the Y value of the `dsdl2.FPoint`
136      +
137      + Returns: Y value of the `dsdl2.FPoint`
138      +/
139     ref inout(float) y() return inout @property {
140         return this.sdlFPoint.y;
141     }
142 
143     /++
144      + Static array proxy of the `dsdl2.FPoint`
145      +
146      + Returns: array of `x` and `y`
147      +/
148     ref inout(float[2]) array() return inout @property @trusted {
149         return *cast(inout(float[2]*))&this.sdlFPoint;
150     }
151 }
152 ///
153 unittest {
154     auto a = dsdl2.FPoint(1.0, 2.0);
155     auto b = a + a;
156     assert(b == dsdl2.FPoint(2.0, 4.0));
157 
158     auto c = a * 2.0;
159     assert(b == c);
160 }
161 
162 /++
163  + D struct that wraps `SDL_FRect` (from SDL 2.0.10) representing a rectangle of floating point 2D
164  + coordinate and dimension
165  +
166  + `dsdl2.FRect` stores `float`ing point `x` and `y` coordinate points, as well as `w`idth and `h`eight which
167  + specifies the rectangle's dimension. `x` and `y` symbolize the top-left coordinate of the rectangle, and
168  + the `w`idth and `h`eight extend to the positive plane of both axes.
169  +/
170 struct FRect {
171     SDL_FRect sdlFRect; /// Internal `SDL_FRect` struct
172 
173     this() @disable;
174 
175     /++
176      + Constructs a `dsdl2.FRect` from a vanilla `SDL_FRect` from bindbc-sdl
177      +
178      + Params:
179      +   sdlFRect = the `SDL_FRect` struct
180      +/
181     this(SDL_FRect sdlFRect) {
182         this.sdlFRect = sdlFRect;
183     }
184 
185     /++
186      + Constructs a `dsdl2.FRect` by feeding in the `x`, `y`, `width`, and `height` of the rectangle
187      +
188      + Params:
189      +   x = top-left x coordinate point of the rectangle
190      +   y = top-left y coordinate point of the rectangle
191      +   width = rectangle width
192      +   height = rectangle height
193      +/
194     this(float x, float y, float width, float height) {
195         this.sdlFRect.x = x;
196         this.sdlFRect.y = y;
197         this.sdlFRect.w = width;
198         this.sdlFRect.h = height;
199     }
200 
201     /++
202      + Constructs a `dsdl2.FRect` by feeding in a `dsdl2.FPoint` as the `xy`, then `width` and `height` of
203      + the rectangle
204      +
205      + Params:
206      +   point = top-left point of the rectangle
207      +   width = rectangle width
208      +   height = rectangle height
209      +/
210     this(FPoint point, float width, float height) {
211         this.sdlFRect.x = point.x;
212         this.sdlFRect.y = point.y;
213         this.sdlFRect.w = width;
214         this.sdlFRect.h = height;
215     }
216 
217     /++
218      + Constructs a `dsdl2.FRect` from a `dsdl2.Rect`
219      +
220      + Params:
221      +   rect = `dsdl2.Rect` whose attributes are to be copied
222      +/
223     this(Rect rect) {
224         this.sdlFRect.x = rect.x;
225         this.sdlFRect.y = rect.y;
226         this.sdlFRect.w = rect.width;
227         this.sdlFRect.h = rect.height;
228     }
229 
230     /++
231      + Binary operation overload template to move rectangle's position by an `offset` as a `dsdl2.FPoint`
232      +/
233     FRect opBinary(string op)(const FPoint offset) const
234     if (op == "+" || op == "-") {
235         return FRect(Foint(mixin("this.x" ~ op ~ "offset.x"), mixin("this.y" ~ op ~ "offset.y")),
236             this.width, this.height);
237     }
238 
239     /++
240      + Operator assignment overload template to move rectangle's position in-place by an `offset` as a
241      + `dsdl2.FPoint`
242      +/
243     ref inout(FPoint) opOpAssign(string op)(const FPoint offset) return inout
244     if (op == "+" || op == "-") {
245         mixin("this.x" ~ op ~ "=offset.x");
246         mixin("this.y" ~ op ~ "=offset.y");
247         return this;
248     }
249 
250     /++
251      + Formats the `dsdl2.FRect` into its construction representation: `"dsdl2.FRect(<x>, <y>, <w>, <h>)"`
252      +
253      + Returns: the formatted `string`
254      +/
255     string toString() const {
256         return "dsdl2.FRect(%f, %f, %f, %f)".format(this.x, this.y, this.width, this.height);
257     }
258 
259     /++
260      + Proxy to the X value of the `dsdl2.FRect`
261      +
262      + Returns: X value of the `dsdl2.FRect`
263      +/
264     ref inout(float) x() return inout @property {
265         return this.sdlFRect.x;
266     }
267 
268     /++
269      + Proxy to the Y value of the `dsdl2.FRect`
270      +
271      + Returns: Y value of the `dsdl2.FRect`
272      +/
273     ref inout(float) y() return inout @property {
274         return this.sdlFRect.y;
275     }
276 
277     /++
278      + Proxy to the `dsdl2.FPoint` containing the `x` and `y` value of the `dsdl2.FRect`
279      +
280      + Returns: reference to the `dsdl2.FPoint` structure
281      +/
282     ref inout(FPoint) point() return inout @property @trusted {
283         return *cast(inout(FPoint*))&this.sdlFRect.x;
284     }
285 
286     /++
287      + Proxy to the width of the `dsdl2.FRect`
288      +
289      + Returns: width of the `dsdl2.FRect`
290      +/
291     ref inout(float) width() return inout @property {
292         return this.sdlFRect.w;
293     }
294 
295     /++
296      + Proxy to the height of the `dsdl2.FRect`
297      +
298      + Returns: height of the `dsdl2.FRect`
299      +/
300     ref inout(float) height() return inout @property {
301         return this.sdlFRect.h;
302     }
303 
304     /++
305      + Proxy to the size array containing the `width` and `height` of the `dsdl2.FRect`
306      +
307      + Returns: reference to the static `float[2]` array
308      +/
309     ref inout(float[2]) size() return inout @property @trusted {
310         return *cast(inout(float[2]*))&this.sdlFRect.w;
311     }
312 
313     static if (sdlSupport >= SDLSupport.v2_0_22) {
314         /++
315          + Wraps `SDL_FRectEmpty` (from SDL 2.0.22) which checks if the `dsdl2.FRect` is an empty rectangle
316          +
317          + Returns: `true` if it is empty, otherwise `false`
318          +/
319         bool empty() const @trusted
320         in {
321             assert(getVersion() >= Version(2, 0, 22));
322         }
323         do {
324             return SDL_FRectEmpty(&this.sdlFRect);
325         }
326 
327         /++
328          + Wraps `SDL_PointInFRect` (from SDL 2.0.22) which sees whether the coordinate of a `dsdl2.FPoint`
329          + is inside the `dsdl2.FRect`
330          +
331          + Params:
332          +   point = the `dsdl2.FPoint` to check its collision of with the `dsdl2.FRect` instance
333          + Returns: `true` if it is within, otherwise `false`
334          +/
335         bool pointInRect(FPoint point) const @trusted
336         in {
337             assert(getVersion() >= Version(2, 0, 22));
338         }
339         do {
340             return SDL_PointInFRect(&point.sdlFPoint, &this.sdlFRect);
341         }
342 
343         /++
344          + Wraps `SDL_HasIntersectionF` (from SDL 2.0.22) which sees whether two `dsdl2.FRect`s intersect
345          + each other
346          +
347          + Params:
348          +   rect = other `dsdl2.FRect` to check its intersection of with the `dsdl2.FRect`
349          + Returns: `true` if both have intersection with each other, otherwise `false`
350          +/
351         bool hasIntersection(FRect rect) const @trusted
352         in {
353             assert(getVersion() >= Version(2, 0, 22));
354         }
355         do {
356             return SDL_HasIntersectionF(&this.sdlFRect, &rect.sdlFRect) == SDL_TRUE;
357         }
358 
359         /++
360          + Wraps `SDL_IntersectFRectAndLine` (from SDL 2.0.22) which sees whether a line intersects with the
361          + `dsdl2.FRect`
362          +
363          + Params:
364          +   line = set of two `dsdl2.FPoint`s denoting the start and end coordinates of the line to check its
365          +          intersection of with the `dsdl2.FRect`
366          + Returns: `true` if it intersects, otherwise `false`
367          +/
368         bool hasLineIntersection(FPoint[2] line) const @trusted
369         in {
370             assert(getVersion() >= Version(2, 0, 22));
371         }
372         do {
373             return SDL_IntersectFRectAndLine(&this.sdlFRect, &line[0].sdlFPoint.x, &line[0].sdlFPoint.y,
374                 &line[1].sdlFPoint.x, &line[1].sdlFPoint.y) == SDL_TRUE;
375         }
376 
377         /++
378          + Wraps `SDL_IntersectFRect` (from SDL 2.0.22) which attempts to get the rectangle of intersection
379          + between two `dsdl2.FRect`s
380          +
381          + Params:
382          +   other = other `dsdl2.FRect` with which the `dsdl2.FRect` is intersected
383          + Returns: non-null `Nullable!FRect` instance if intersection is present, otherwise a null one
384          +/
385         Nullable!FRect intersectRect(FRect other) const @trusted
386         in {
387             assert(getVersion() >= Version(2, 0, 22));
388         }
389         do {
390             FRect intersection = void;
391             if (SDL_IntersectFRect(&this.sdlFRect, &other.sdlFRect, &intersection.sdlFRect) == SDL_TRUE) {
392                 return intersection.nullable;
393             }
394             else {
395                 return Nullable!FRect.init;
396             }
397         }
398 
399         /++
400          + Wraps `SDL_IntersectFRectAndLine` (from SDL 2.0.22) which attempts to clip a line segment in the
401          + boundaries of the `dsdl2.FRect`
402          +
403          + Params:
404          +   line = set of two `dsdl2.FPoint`s denoting the start and end coordinates of the line to clip from its
405          +          intersection with the `dsdl2.FRect`
406          + Returns: non-null `Nullable!(FPoint[2])` as the clipped line if there's an intersection, otherwise a null one
407          +/
408         Nullable!(FPoint[2]) intersectLine(FPoint[2] line) const @trusted
409         in {
410             assert(getVersion() >= Version(2, 0, 22));
411         }
412         do {
413             if (SDL_IntersectFRectAndLine(&this.sdlFRect, &line[0].sdlFPoint.x, &line[0].sdlFPoint.y,
414                     &line[1].sdlFPoint.x, &line[1].sdlFPoint.y) == SDL_TRUE) {
415                 FPoint[2] intersection = [line[0], line[1]];
416                 return intersection.nullable;
417             }
418             else {
419                 return Nullable!(FPoint[2]).init;
420             }
421         }
422 
423         /++
424          + Wraps `SDL_UnionFRect` which creates a `dsdl2.FRect` (from SDL 2.0.22) of the minimum size to
425          + enclose two given `dsdl2.FRect`s
426          +
427          + Params:
428          +   other = other `dsdl2.FRect` to unify with the `dsdl2.FRect`
429          + Returns: `dsdl2.FRect` of the minimum size to enclose the `dsdl2.FRect` and `other`
430          +/
431         FRect unify(FRect other) const @trusted
432         in {
433             assert(getVersion() >= Version(2, 0, 22));
434         }
435         do {
436             FRect union_ = void;
437             SDL_UnionFRect(&this.sdlFRect, &other.sdlFRect, &union_.sdlFRect);
438             return union_;
439         }
440     }
441 }
442 ///
443 unittest {
444     auto rect1 = dsdl2.FRect(-2.0, -2.0, 3.0, 3.0);
445     auto rect2 = dsdl2.FRect(-1.0, -1.0, 3.0, 3.0);
446 
447     assert(rect1.hasIntersection(rect2));
448     assert(rect1.intersectRect(rect2).get == dsdl2.FRect(-1.0, -1.0, 2.0, 2.0));
449 }