1 /**
2 2D Axis-aligned bounding box.
3 
4 Copyright:
5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017 Godot-D contributors
8 Copyright (c) 2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot.rect2;
15 
16 import godot.abi.core;
17 import godot.abi.types;
18 import godot.vector2, godot.transform2d;
19 
20 // import godot.globalenums;  // enum Side, but tbh it is bad idea to rely on this
21 
22 import std.algorithm.comparison;
23 import std.algorithm.mutation : swap;
24 
25 /**
26 Rect2 consists of a position, a size, and several utility functions. It is typically used for fast overlap tests.
27 */
28 struct Rect2 {
29 @nogc nothrow:
30 
31     Vector2 position;
32     Vector2 size;
33 
34     deprecated("pos has been renamed to position, please use position instead")
35     alias pos = position;
36 
37     alias end = getEnd;
38 
39     this(real_t p_x, real_t p_y, real_t p_width, real_t p_height) {
40         position = Vector2(p_x, p_y);
41         size = Vector2(p_width, p_height);
42     }
43 
44     this(in Rect2i b) {
45         position = b.position;
46         size = b.size;
47     }
48 
49     real_t getArea() const {
50         return size.width * size.height;
51     }
52 
53     Vector2 end() const {
54         return getEnd();
55     }
56 
57     void end(in Vector2 p_end) {
58         setEnd(p_end);
59     }
60 
61     bool intersects(in Rect2 p_rect) const {
62         if (position.x >= (p_rect.position.x + p_rect.size.width))
63             return false;
64         if ((position.x + size.width) <= p_rect.position.x)
65             return false;
66         if (position.y >= (p_rect.position.y + p_rect.size.height))
67             return false;
68         if ((position.y + size.height) <= p_rect.position.y)
69             return false;
70 
71         return true;
72     }
73 
74     bool encloses(in Rect2 p_rect) const {
75         return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
76             ((p_rect.position.x + p_rect.size.x) < (position.x + size.x)) &&
77             ((p_rect.position.y + p_rect.size.y) < (position.y + size.y));
78     }
79 
80     bool hasNoArea() const {
81         return (size.x <= 0 || size.y <= 0);
82     }
83 
84     bool hasPoint(in Vector2 p_point) const {
85         if (p_point.x < position.x)
86             return false;
87         if (p_point.y < position.y)
88             return false;
89 
90         if (p_point.x >= (position.x + size.x))
91             return false;
92         if (p_point.y >= (position.y + size.y))
93             return false;
94 
95         return true;
96     }
97 
98     Rect2 grow(real_t p_by) const {
99         Rect2 g = this;
100         g.position.x -= p_by;
101         g.position.y -= p_by;
102         g.size.width += p_by * 2;
103         g.size.height += p_by * 2;
104         return g;
105     }
106 
107     Rect2 expand(in Vector2 p_vector) const {
108         Rect2 r = this;
109         r.expandTo(p_vector);
110         return r;
111     }
112 
113     void expandTo(in Vector2 p_vector) {
114         Vector2 begin = position;
115         Vector2 end = position + size;
116 
117         if (p_vector.x < begin.x)
118             begin.x = p_vector.x;
119         if (p_vector.y < begin.y)
120             begin.y = p_vector.y;
121 
122         if (p_vector.x > end.x)
123             end.x = p_vector.x;
124         if (p_vector.y > end.y)
125             end.y = p_vector.y;
126 
127         position = begin;
128         size = end - begin;
129     }
130 
131     real_t distanceTo(in Vector2 p_point) const {
132         real_t dist = 1e20;
133 
134         if (p_point.x < position.x) {
135             dist = min(dist, position.x - p_point.x);
136         }
137         if (p_point.y < position.y) {
138             dist = min(dist, position.y - p_point.y);
139         }
140         if (p_point.x >= (position.x + size.x)) {
141             dist = min(p_point.x - (position.x + size.x), dist);
142         }
143         if (p_point.y >= (position.y + size.y)) {
144             dist = min(p_point.y - (position.y + size.y), dist);
145         }
146 
147         if (dist == 1e20)
148             return 0;
149         else
150             return dist;
151     }
152 
153     Rect2 clip(in Rect2 p_rect) const {
154 
155         Rect2 new_rect = p_rect;
156 
157         if (!intersects(new_rect))
158             return Rect2();
159 
160         new_rect.position.x = max(p_rect.position.x, position.x);
161         new_rect.position.y = max(p_rect.position.y, position.y);
162 
163         Vector2 p_rect_end = p_rect.position + p_rect.size;
164         Vector2 end = position + size;
165 
166         new_rect.size.x = min(p_rect_end.x, end.x) - new_rect.position.x;
167         new_rect.size.y = min(p_rect_end.y, end.y) - new_rect.position.y;
168 
169         return new_rect;
170     }
171 
172     Rect2 merge(in Rect2 p_rect) const {
173         Rect2 new_rect;
174 
175         new_rect.position.x = min(p_rect.position.x, position.x);
176         new_rect.position.y = min(p_rect.position.y, position.y);
177 
178         new_rect.size.x = max(p_rect.position.x + p_rect.size.x, position.x + size.x);
179         new_rect.size.y = max(p_rect.position.y + p_rect.size.y, position.y + size.y);
180 
181         new_rect.size = new_rect.size - new_rect.position; //make relative again
182 
183         return new_rect;
184     }
185 
186     bool intersectsSegment(in Vector2 p_from, in Vector2 p_to, Vector2* r_pos, Vector2* r_normal) const {
187         real_t min = 0, max = 1;
188         int axis = 0;
189         real_t sign = 0;
190 
191         for (int i = 0; i < 2; i++) {
192             real_t seg_from = p_from[i];
193             real_t seg_to = p_to[i];
194             real_t box_begin = position[i];
195             real_t box_end = box_begin + size[i];
196             real_t cmin, cmax;
197             real_t csign;
198 
199             if (seg_from < seg_to) {
200                 if (seg_from > box_end || seg_to < box_begin)
201                     return false;
202                 real_t length = seg_to - seg_from;
203                 cmin = (seg_from < box_begin) ? ((box_begin - seg_from) / length) : 0;
204                 cmax = (seg_to > box_end) ? ((box_end - seg_from) / length) : 1;
205                 csign = -1.0;
206 
207             } else {
208                 if (seg_to > box_end || seg_from < box_begin)
209                     return false;
210                 real_t length = seg_to - seg_from;
211                 cmin = (seg_from > box_end) ? (box_end - seg_from) / length : 0;
212                 cmax = (seg_to < box_begin) ? (box_begin - seg_from) / length : 1;
213                 csign = 1.0;
214             }
215 
216             if (cmin > min) {
217                 min = cmin;
218                 axis = i;
219                 sign = csign;
220             }
221             if (cmax < max)
222                 max = cmax;
223             if (max < min)
224                 return false;
225         }
226 
227         Vector2 rel = p_to - p_from;
228 
229         if (r_normal) {
230             Vector2 normal;
231             normal[axis] = sign;
232             *r_normal = normal;
233         }
234 
235         if (r_pos)
236             *r_pos = p_from + rel * min;
237 
238         return true;
239     }
240 
241     bool intersectsTransformed(in Transform2D p_xform, in Rect2 p_rect) const {
242         //SAT intersection between local and transformed rect2
243 
244         Vector2[4] xf_points = [
245             p_xform.xform(p_rect.position),
246             p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y)),
247             p_xform.xform(Vector2(p_rect.position.x, p_rect.position.y + p_rect.size.y)),
248             p_xform.xform(Vector2(p_rect.position.x + p_rect.size.x, p_rect.position.y + p_rect
249                     .size.y)),
250         ];
251 
252         real_t low_limit;
253 
254         //base rect2 first (faster)
255 
256         if (xf_points[0].y > position.y)
257             goto next1;
258         if (xf_points[1].y > position.y)
259             goto next1;
260         if (xf_points[2].y > position.y)
261             goto next1;
262         if (xf_points[3].y > position.y)
263             goto next1;
264 
265         return false;
266 
267     next1:
268 
269         low_limit = position.y + size.y;
270 
271         if (xf_points[0].y < low_limit)
272             goto next2;
273         if (xf_points[1].y < low_limit)
274             goto next2;
275         if (xf_points[2].y < low_limit)
276             goto next2;
277         if (xf_points[3].y < low_limit)
278             goto next2;
279 
280         return false;
281 
282     next2:
283 
284         if (xf_points[0].x > position.x)
285             goto next3;
286         if (xf_points[1].x > position.x)
287             goto next3;
288         if (xf_points[2].x > position.x)
289             goto next3;
290         if (xf_points[3].x > position.x)
291             goto next3;
292 
293         return false;
294 
295     next3:
296 
297         low_limit = position.x + size.x;
298 
299         if (xf_points[0].x < low_limit)
300             goto next4;
301         if (xf_points[1].x < low_limit)
302             goto next4;
303         if (xf_points[2].x < low_limit)
304             goto next4;
305         if (xf_points[3].x < low_limit)
306             goto next4;
307 
308         return false;
309 
310     next4:
311 
312         Vector2[4] xf_points2 = [
313             position,
314             Vector2(position.x + size.x, position.y),
315             Vector2(position.x, position.y + size.y),
316             Vector2(position.x + size.x, position.y + size.y),
317         ];
318 
319         real_t maxa = p_xform.columns[0].dot(xf_points2[0]);
320         real_t mina = maxa;
321 
322         real_t dp = p_xform.columns[0].dot(xf_points2[1]);
323         maxa = max(dp, maxa);
324         mina = min(dp, mina);
325 
326         dp = p_xform.columns[0].dot(xf_points2[2]);
327         maxa = max(dp, maxa);
328         mina = min(dp, mina);
329 
330         dp = p_xform.columns[0].dot(xf_points2[3]);
331         maxa = max(dp, maxa);
332         mina = min(dp, mina);
333 
334         real_t maxb = p_xform.columns[0].dot(xf_points[0]);
335         real_t minb = maxb;
336 
337         dp = p_xform.columns[0].dot(xf_points[1]);
338         maxb = max(dp, maxb);
339         minb = min(dp, minb);
340 
341         dp = p_xform.columns[0].dot(xf_points[2]);
342         maxb = max(dp, maxb);
343         minb = min(dp, minb);
344 
345         dp = p_xform.columns[0].dot(xf_points[3]);
346         maxb = max(dp, maxb);
347         minb = min(dp, minb);
348 
349         if (mina > maxb)
350             return false;
351         if (minb > maxa)
352             return false;
353 
354         maxa = p_xform.columns[1].dot(xf_points2[0]);
355         mina = maxa;
356 
357         dp = p_xform.columns[1].dot(xf_points2[1]);
358         maxa = max(dp, maxa);
359         mina = min(dp, mina);
360 
361         dp = p_xform.columns[1].dot(xf_points2[2]);
362         maxa = max(dp, maxa);
363         mina = min(dp, mina);
364 
365         dp = p_xform.columns[1].dot(xf_points2[3]);
366         maxa = max(dp, maxa);
367         mina = min(dp, mina);
368 
369         maxb = p_xform.columns[1].dot(xf_points[0]);
370         minb = maxb;
371 
372         dp = p_xform.columns[1].dot(xf_points[1]);
373         maxb = max(dp, maxb);
374         minb = min(dp, minb);
375 
376         dp = p_xform.columns[1].dot(xf_points[2]);
377         maxb = max(dp, maxb);
378         minb = min(dp, minb);
379 
380         dp = p_xform.columns[1].dot(xf_points[3]);
381         maxb = max(dp, maxb);
382         minb = min(dp, minb);
383 
384         if (mina > maxb)
385             return false;
386         if (minb > maxa)
387             return false;
388 
389         return true;
390 
391     }
392 
393     void setEnd(in Vector2 p_end) {
394         size = p_end - position;
395     }
396 
397     Vector2 getEnd() const {
398         return position + size;
399     }
400 
401 }
402 
403 struct Rect2i {
404 @nogc nothrow:
405 
406     Vector2i position;
407     Vector2i size;
408 
409     // from godot.globalenums module
410     enum {
411         /** */
412         sideLeft = 0,
413         /** */
414         sideTop = 1,
415         /** */
416         sideRight = 2,
417         /** */
418         sideBottom = 3,
419     }
420     alias Side = int;
421 
422     Vector2i end() const {
423         return getEnd();
424     }
425 
426     void end(in Vector2i p_end) {
427         setEnd(p_end);
428     }
429 
430     this(godot_int p_x, godot_int p_y, godot_int p_width, godot_int p_height) {
431         position = Vector2i(p_x, p_y);
432         size = Vector2i(p_width, p_height);
433     }
434 
435     this(in Vector2i p_pos, in Vector2i p_size) {
436         position = p_pos;
437         size = p_size;
438     }
439 
440     bool intersects(in Rect2i p_rect) const {
441         if (position.x > (p_rect.position.x + p_rect.size.width)) {
442             return false;
443         }
444         if ((position.x + size.width) < p_rect.position.x) {
445             return false;
446         }
447         if (position.y > (p_rect.position.y + p_rect.size.height)) {
448             return false;
449         }
450         if ((position.y + size.height) < p_rect.position.y) {
451             return false;
452         }
453 
454         return true;
455     }
456 
457     bool encloses(in Rect2i p_rect) const {
458         return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
459             ((p_rect.position.x + p_rect.size.x) < (position.x + size.x)) &&
460             ((p_rect.position.y + p_rect.size.y) < (position.y + size.y));
461     }
462 
463     bool hasNoArea() const {
464         return (size.x <= 0 || size.y <= 0);
465     }
466 
467     // Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
468     Rect2i intersection(in Rect2i p_rect) const {
469         Rect2i new_rect = p_rect;
470 
471         if (!intersects(new_rect)) {
472             return Rect2i();
473         }
474 
475         new_rect.position.x = max(p_rect.position.x, position.x);
476         new_rect.position.y = max(p_rect.position.y, position.y);
477 
478         Vector2i p_rect_end = p_rect.position + p_rect.size;
479         Vector2i end = position + size;
480 
481         new_rect.size.x = cast(godot_int)(min(p_rect_end.x, end.x) - new_rect.position.x);
482         new_rect.size.y = cast(godot_int)(min(p_rect_end.y, end.y) - new_rect.position.y);
483 
484         return new_rect;
485     }
486 
487     Rect2i merge(in Rect2i p_rect) const  ///< return a merged rect
488     {
489 
490         Rect2i new_rect;
491 
492         new_rect.position.x = min(p_rect.position.x, position.x);
493         new_rect.position.y = min(p_rect.position.y, position.y);
494 
495         new_rect.size.x = max(p_rect.position.x + p_rect.size.x, position.x + size.x);
496         new_rect.size.y = max(p_rect.position.y + p_rect.size.y, position.y + size.y);
497 
498         new_rect.size = new_rect.size - new_rect.position; // make relative again
499 
500         return new_rect;
501     }
502 
503     bool hasPoint(in Vector2i p_point) const {
504         if (p_point.x < position.x) {
505             return false;
506         }
507         if (p_point.y < position.y) {
508             return false;
509         }
510 
511         if (p_point.x >= (position.x + size.x)) {
512             return false;
513         }
514         if (p_point.y >= (position.y + size.y)) {
515             return false;
516         }
517 
518         return true;
519     }
520 
521     bool opEquals(in Rect2i p_rect) const {
522         return position == p_rect.position && size == p_rect.size;
523     }
524 
525     Rect2i grow(int p_amount) const {
526         Rect2i g = this;
527         g.position.x -= p_amount;
528         g.position.y -= p_amount;
529         g.size.width += p_amount * 2;
530         g.size.height += p_amount * 2;
531         return g;
532     }
533 
534     Rect2i growSide(Side p_side, int p_amount) const {
535         Rect2i g = this;
536         g = g.growIndividual((sideLeft == p_side) ? p_amount : 0,
537             (sideTop == p_side) ? p_amount : 0,
538             (sideRight == p_side) ? p_amount
539                 : 0,
540                 (sideBottom == p_side) ? p_amount : 0);
541         return g;
542     }
543 
544     Rect2i growSideBind(uint32_t p_side, int p_amount) const {
545         return growSide(Side(p_side), p_amount);
546     }
547 
548     Rect2i growIndividual(int p_left, int p_top, int p_right, int p_bottom) const {
549         Rect2i g = this;
550         g.position.x -= p_left;
551         g.position.y -= p_top;
552         g.size.width += p_left + p_right;
553         g.size.height += p_top + p_bottom;
554 
555         return g;
556     }
557 
558     Rect2i expand(in Vector2i p_vector) const {
559         Rect2i r = this;
560         r.expandTo(p_vector);
561         return r;
562     }
563 
564     void expandTo(in Vector2i p_vector) {
565         Vector2i begin = position;
566         Vector2i end = position + size;
567 
568         if (p_vector.x < begin.x) {
569             begin.x = p_vector.x;
570         }
571         if (p_vector.y < begin.y) {
572             begin.y = p_vector.y;
573         }
574 
575         if (p_vector.x > end.x) {
576             end.x = p_vector.x;
577         }
578         if (p_vector.y > end.y) {
579             end.y = p_vector.y;
580         }
581 
582         position = begin;
583         size = end - begin;
584     }
585 
586     Rect2i abs() const {
587         return Rect2i(Vector2i(position.x + min(size.x, 0), position.y + min(size.y, 0)), size.abs());
588     }
589 
590     void setEnd(in Vector2i p_end) {
591         size = p_end - position;
592     }
593 
594     Vector2i getEnd() const {
595         return position + size;
596     }
597 
598     Rect2 opCast(Rect2)() const {
599         return Rect2(cast(Vector2) position, cast(Vector2) size);
600     }
601 }