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 }