Просмотр исходного кода

Core: Move Rect2i to its own `rect2i.h` header

And take the opportunity to improve interdependencies a bit with forward
declares where possible.
Rémi Verschelde 3 лет назад
Родитель
Сommit
5f56d385b0

+ 4 - 0
core/math/camera_matrix.cpp

@@ -30,7 +30,11 @@
 
 #include "camera_matrix.h"
 
+#include "core/math/aabb.h"
 #include "core/math/math_funcs.h"
+#include "core/math/plane.h"
+#include "core/math/rect2.h"
+#include "core/math/transform_3d.h"
 #include "core/string/print_string.h"
 
 float CameraMatrix::determinant() const {

+ 8 - 2
core/math/camera_matrix.h

@@ -31,8 +31,14 @@
 #ifndef CAMERA_MATRIX_H
 #define CAMERA_MATRIX_H
 
-#include "core/math/rect2.h"
-#include "core/math/transform_3d.h"
+#include "core/math/math_defs.h"
+#include "core/math/vector3.h"
+
+class AABB;
+class Plane;
+class Transform3D;
+struct Rect2;
+struct Vector2;
 
 struct CameraMatrix {
 	enum Planes {

+ 7 - 3
core/math/rect2.cpp

@@ -28,7 +28,11 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 
-#include "core/math/transform_2d.h" // Includes rect2.h but Rect2 needs Transform2D
+#include "rect2.h"
+
+#include "core/math/rect2i.h"
+#include "core/math/transform_2d.h"
+#include "core/string/ustring.h"
 
 bool Rect2::is_equal_approx(const Rect2 &p_rect) const {
 	return position.is_equal_approx(p_rect.position) && size.is_equal_approx(p_rect.size);
@@ -278,6 +282,6 @@ Rect2::operator String() const {
 	return "[P: " + position.operator String() + ", S: " + size + "]";
 }
 
-Rect2i::operator String() const {
-	return "[P: " + position.operator String() + ", S: " + size + "]";
+Rect2::operator Rect2i() const {
+	return Rect2i(position, size);
 }

+ 6 - 211
core/math/rect2.h

@@ -31,8 +31,10 @@
 #ifndef RECT2_H
 #define RECT2_H
 
-#include "core/math/vector2.h" // also includes math_funcs and ustring
+#include "core/math/vector2.h"
 
+class String;
+struct Rect2i;
 struct Transform2D;
 
 struct _NO_DISCARD_ Rect2 {
@@ -179,6 +181,7 @@ struct _NO_DISCARD_ Rect2 {
 
 		return new_rect;
 	}
+
 	inline bool has_point(const Point2 &p_point) const {
 #ifdef MATH_CHECKS
 		if (unlikely(size.x < 0 || size.y < 0)) {
@@ -201,6 +204,7 @@ struct _NO_DISCARD_ Rect2 {
 
 		return true;
 	}
+
 	bool is_equal_approx(const Rect2 &p_rect) const;
 
 	bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
@@ -351,6 +355,7 @@ struct _NO_DISCARD_ Rect2 {
 	}
 
 	operator String() const;
+	operator Rect2i() const;
 
 	Rect2() {}
 	Rect2(real_t p_x, real_t p_y, real_t p_width, real_t p_height) :
@@ -363,214 +368,4 @@ struct _NO_DISCARD_ Rect2 {
 	}
 };
 
-struct _NO_DISCARD_ Rect2i {
-	Point2i position;
-	Size2i size;
-
-	const Point2i &get_position() const { return position; }
-	void set_position(const Point2i &p_position) { position = p_position; }
-	const Size2i &get_size() const { return size; }
-	void set_size(const Size2i &p_size) { size = p_size; }
-
-	int get_area() const { return size.width * size.height; }
-
-	_FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); }
-
-	inline bool intersects(const Rect2i &p_rect) const {
-#ifdef MATH_CHECKS
-		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
-			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
-		}
-#endif
-		if (position.x >= (p_rect.position.x + p_rect.size.width)) {
-			return false;
-		}
-		if ((position.x + size.width) <= p_rect.position.x) {
-			return false;
-		}
-		if (position.y >= (p_rect.position.y + p_rect.size.height)) {
-			return false;
-		}
-		if ((position.y + size.height) <= p_rect.position.y) {
-			return false;
-		}
-
-		return true;
-	}
-
-	inline bool encloses(const Rect2i &p_rect) const {
-#ifdef MATH_CHECKS
-		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
-			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
-		}
-#endif
-		return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
-				((p_rect.position.x + p_rect.size.x) <= (position.x + size.x)) &&
-				((p_rect.position.y + p_rect.size.y) <= (position.y + size.y));
-	}
-
-	_FORCE_INLINE_ bool has_no_area() const {
-		return (size.x <= 0 || size.y <= 0);
-	}
-
-	// Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
-	inline Rect2i intersection(const Rect2i &p_rect) const {
-		Rect2i new_rect = p_rect;
-
-		if (!intersects(new_rect)) {
-			return Rect2i();
-		}
-
-		new_rect.position.x = MAX(p_rect.position.x, position.x);
-		new_rect.position.y = MAX(p_rect.position.y, position.y);
-
-		Point2i p_rect_end = p_rect.position + p_rect.size;
-		Point2i end = position + size;
-
-		new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
-		new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
-
-		return new_rect;
-	}
-
-	inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect
-#ifdef MATH_CHECKS
-		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
-			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
-		}
-#endif
-		Rect2i new_rect;
-
-		new_rect.position.x = MIN(p_rect.position.x, position.x);
-		new_rect.position.y = MIN(p_rect.position.y, position.y);
-
-		new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
-		new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
-
-		new_rect.size = new_rect.size - new_rect.position; //make relative again
-
-		return new_rect;
-	}
-	bool has_point(const Point2i &p_point) const {
-#ifdef MATH_CHECKS
-		if (unlikely(size.x < 0 || size.y < 0)) {
-			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
-		}
-#endif
-		if (p_point.x < position.x) {
-			return false;
-		}
-		if (p_point.y < position.y) {
-			return false;
-		}
-
-		if (p_point.x >= (position.x + size.x)) {
-			return false;
-		}
-		if (p_point.y >= (position.y + size.y)) {
-			return false;
-		}
-
-		return true;
-	}
-
-	bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; }
-	bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; }
-
-	Rect2i grow(int p_amount) const {
-		Rect2i g = *this;
-		g.position.x -= p_amount;
-		g.position.y -= p_amount;
-		g.size.width += p_amount * 2;
-		g.size.height += p_amount * 2;
-		return g;
-	}
-
-	inline Rect2i grow_side(Side p_side, int p_amount) const {
-		Rect2i g = *this;
-		g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0,
-				(SIDE_TOP == p_side) ? p_amount : 0,
-				(SIDE_RIGHT == p_side) ? p_amount : 0,
-				(SIDE_BOTTOM == p_side) ? p_amount : 0);
-		return g;
-	}
-
-	inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const {
-		return grow_side(Side(p_side), p_amount);
-	}
-
-	inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const {
-		Rect2i g = *this;
-		g.position.x -= p_left;
-		g.position.y -= p_top;
-		g.size.width += p_left + p_right;
-		g.size.height += p_top + p_bottom;
-
-		return g;
-	}
-
-	_FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const {
-		Rect2i r = *this;
-		r.expand_to(p_vector);
-		return r;
-	}
-
-	inline void expand_to(const Point2i &p_vector) {
-#ifdef MATH_CHECKS
-		if (unlikely(size.x < 0 || size.y < 0)) {
-			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
-		}
-#endif
-		Point2i begin = position;
-		Point2i end = position + size;
-
-		if (p_vector.x < begin.x) {
-			begin.x = p_vector.x;
-		}
-		if (p_vector.y < begin.y) {
-			begin.y = p_vector.y;
-		}
-
-		if (p_vector.x > end.x) {
-			end.x = p_vector.x;
-		}
-		if (p_vector.y > end.y) {
-			end.y = p_vector.y;
-		}
-
-		position = begin;
-		size = end - begin;
-	}
-
-	_FORCE_INLINE_ Rect2i abs() const {
-		return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
-	}
-
-	_FORCE_INLINE_ void set_end(const Vector2i &p_end) {
-		size = p_end - position;
-	}
-
-	_FORCE_INLINE_ Vector2i get_end() const {
-		return position + size;
-	}
-
-	operator String() const;
-
-	operator Rect2() const { return Rect2(position, size); }
-
-	Rect2i() {}
-	Rect2i(const Rect2 &p_r2) :
-			position(p_r2.position),
-			size(p_r2.size) {
-	}
-	Rect2i(int p_x, int p_y, int p_width, int p_height) :
-			position(Point2i(p_x, p_y)),
-			size(Size2i(p_width, p_height)) {
-	}
-	Rect2i(const Point2i &p_pos, const Size2i &p_size) :
-			position(p_pos),
-			size(p_size) {
-	}
-};
-
 #endif // RECT2_H

+ 42 - 0
core/math/rect2i.cpp

@@ -0,0 +1,42 @@
+/*************************************************************************/
+/*  rect2i.cpp                                                           */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "rect2i.h"
+
+#include "core/math/rect2.h"
+#include "core/string/ustring.h"
+
+Rect2i::operator String() const {
+	return "[P: " + position.operator String() + ", S: " + size + "]";
+}
+
+Rect2i::operator Rect2() const {
+	return Rect2(position, size);
+}

+ 244 - 0
core/math/rect2i.h

@@ -0,0 +1,244 @@
+/*************************************************************************/
+/*  rect2i.h                                                             */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef RECT2I_H
+#define RECT2I_H
+
+#include "core/math/vector2.h"
+
+class String;
+struct Rect2;
+
+struct _NO_DISCARD_ Rect2i {
+	Point2i position;
+	Size2i size;
+
+	const Point2i &get_position() const { return position; }
+	void set_position(const Point2i &p_position) { position = p_position; }
+	const Size2i &get_size() const { return size; }
+	void set_size(const Size2i &p_size) { size = p_size; }
+
+	int get_area() const { return size.width * size.height; }
+
+	_FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); }
+
+	inline bool intersects(const Rect2i &p_rect) const {
+#ifdef MATH_CHECKS
+		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+		}
+#endif
+		if (position.x >= (p_rect.position.x + p_rect.size.width)) {
+			return false;
+		}
+		if ((position.x + size.width) <= p_rect.position.x) {
+			return false;
+		}
+		if (position.y >= (p_rect.position.y + p_rect.size.height)) {
+			return false;
+		}
+		if ((position.y + size.height) <= p_rect.position.y) {
+			return false;
+		}
+
+		return true;
+	}
+
+	inline bool encloses(const Rect2i &p_rect) const {
+#ifdef MATH_CHECKS
+		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+		}
+#endif
+		return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
+				((p_rect.position.x + p_rect.size.x) <= (position.x + size.x)) &&
+				((p_rect.position.y + p_rect.size.y) <= (position.y + size.y));
+	}
+
+	_FORCE_INLINE_ bool has_no_area() const {
+		return (size.x <= 0 || size.y <= 0);
+	}
+
+	// Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
+	inline Rect2i intersection(const Rect2i &p_rect) const {
+		Rect2i new_rect = p_rect;
+
+		if (!intersects(new_rect)) {
+			return Rect2i();
+		}
+
+		new_rect.position.x = MAX(p_rect.position.x, position.x);
+		new_rect.position.y = MAX(p_rect.position.y, position.y);
+
+		Point2i p_rect_end = p_rect.position + p_rect.size;
+		Point2i end = position + size;
+
+		new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
+		new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
+
+		return new_rect;
+	}
+
+	inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect
+#ifdef MATH_CHECKS
+		if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+		}
+#endif
+		Rect2i new_rect;
+
+		new_rect.position.x = MIN(p_rect.position.x, position.x);
+		new_rect.position.y = MIN(p_rect.position.y, position.y);
+
+		new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
+		new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
+
+		new_rect.size = new_rect.size - new_rect.position; //make relative again
+
+		return new_rect;
+	}
+	bool has_point(const Point2i &p_point) const {
+#ifdef MATH_CHECKS
+		if (unlikely(size.x < 0 || size.y < 0)) {
+			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+		}
+#endif
+		if (p_point.x < position.x) {
+			return false;
+		}
+		if (p_point.y < position.y) {
+			return false;
+		}
+
+		if (p_point.x >= (position.x + size.x)) {
+			return false;
+		}
+		if (p_point.y >= (position.y + size.y)) {
+			return false;
+		}
+
+		return true;
+	}
+
+	bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; }
+	bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; }
+
+	Rect2i grow(int p_amount) const {
+		Rect2i g = *this;
+		g.position.x -= p_amount;
+		g.position.y -= p_amount;
+		g.size.width += p_amount * 2;
+		g.size.height += p_amount * 2;
+		return g;
+	}
+
+	inline Rect2i grow_side(Side p_side, int p_amount) const {
+		Rect2i g = *this;
+		g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0,
+				(SIDE_TOP == p_side) ? p_amount : 0,
+				(SIDE_RIGHT == p_side) ? p_amount : 0,
+				(SIDE_BOTTOM == p_side) ? p_amount : 0);
+		return g;
+	}
+
+	inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const {
+		return grow_side(Side(p_side), p_amount);
+	}
+
+	inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const {
+		Rect2i g = *this;
+		g.position.x -= p_left;
+		g.position.y -= p_top;
+		g.size.width += p_left + p_right;
+		g.size.height += p_top + p_bottom;
+
+		return g;
+	}
+
+	_FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const {
+		Rect2i r = *this;
+		r.expand_to(p_vector);
+		return r;
+	}
+
+	inline void expand_to(const Point2i &p_vector) {
+#ifdef MATH_CHECKS
+		if (unlikely(size.x < 0 || size.y < 0)) {
+			ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+		}
+#endif
+		Point2i begin = position;
+		Point2i end = position + size;
+
+		if (p_vector.x < begin.x) {
+			begin.x = p_vector.x;
+		}
+		if (p_vector.y < begin.y) {
+			begin.y = p_vector.y;
+		}
+
+		if (p_vector.x > end.x) {
+			end.x = p_vector.x;
+		}
+		if (p_vector.y > end.y) {
+			end.y = p_vector.y;
+		}
+
+		position = begin;
+		size = end - begin;
+	}
+
+	_FORCE_INLINE_ Rect2i abs() const {
+		return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
+	}
+
+	_FORCE_INLINE_ void set_end(const Vector2i &p_end) {
+		size = p_end - position;
+	}
+
+	_FORCE_INLINE_ Vector2i get_end() const {
+		return position + size;
+	}
+
+	operator String() const;
+	operator Rect2() const;
+
+	Rect2i() {}
+	Rect2i(int p_x, int p_y, int p_width, int p_height) :
+			position(Point2i(p_x, p_y)),
+			size(Size2i(p_width, p_height)) {
+	}
+	Rect2i(const Point2i &p_pos, const Size2i &p_size) :
+			position(p_pos),
+			size(p_size) {
+	}
+};
+
+#endif // RECT2I_H

+ 2 - 0
core/math/transform_2d.cpp

@@ -30,6 +30,8 @@
 
 #include "transform_2d.h"
 
+#include "core/string/ustring.h"
+
 void Transform2D::invert() {
 	// FIXME: this function assumes the basis is a rotation matrix, with no scaling.
 	// Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.

+ 5 - 1
core/math/transform_2d.h

@@ -31,7 +31,11 @@
 #ifndef TRANSFORM_2D_H
 #define TRANSFORM_2D_H
 
-#include "core/math/rect2.h" // also includes vector2, math_funcs, and ustring
+#include "core/math/math_funcs.h"
+#include "core/math/rect2.h"
+#include "core/math/vector2.h"
+
+class String;
 
 struct _NO_DISCARD_ Transform2D {
 	// Warning #1: basis of Transform2D is stored differently from Basis. In terms of elements array, the basis matrix looks like "on paper":

+ 0 - 1
core/variant/method_ptrcall.h

@@ -31,7 +31,6 @@
 #ifndef METHOD_PTRCALL_H
 #define METHOD_PTRCALL_H
 
-#include "core/math/transform_2d.h"
 #include "core/object/object_id.h"
 #include "core/typedefs.h"
 #include "core/variant/variant.h"

+ 3 - 0
core/variant/variant.h

@@ -39,8 +39,11 @@
 #include "core/math/face3.h"
 #include "core/math/plane.h"
 #include "core/math/quaternion.h"
+#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
 #include "core/math/transform_2d.h"
 #include "core/math/transform_3d.h"
+#include "core/math/vector2.h"
 #include "core/math/vector3.h"
 #include "core/math/vector3i.h"
 #include "core/object/object_id.h"

+ 1 - 0
modules/gdnative/gdnative/rect2.cpp

@@ -31,6 +31,7 @@
 #include "gdnative/rect2.h"
 
 #include "core/math/rect2.h"
+#include "core/math/rect2i.h"
 
 static_assert(sizeof(godot_rect2) == sizeof(Rect2), "Rect2 size mismatch");
 static_assert(sizeof(godot_rect2i) == sizeof(Rect2i), "Rect2i size mismatch");

+ 1 - 277
tests/core/math/test_rect2.h

@@ -32,15 +32,11 @@
 #define TEST_RECT2_H
 
 #include "core/math/rect2.h"
+#include "core/math/rect2i.h"
 
 #include "thirdparty/doctest/doctest.h"
 
 namespace TestRect2 {
-// We also test Rect2i here, for consistency with the source code where Rect2
-// and Rect2i are defined in the same file.
-
-// Rect2
-
 TEST_CASE("[Rect2] Constructor methods") {
 	const Rect2 rect = Rect2(0, 100, 1280, 720);
 	const Rect2 rect_vector = Rect2(Vector2(0, 100), Vector2(1280, 720));
@@ -304,278 +300,6 @@ TEST_CASE("[Rect2] Merging") {
 			Rect2(0, 100, 1280, 720).merge(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2(-4000, -4000, 5280, 4820)),
 			"merge() with non-enclosed Rect2 should return the expected result.");
 }
-
-// Rect2i
-
-TEST_CASE("[Rect2i] Constructor methods") {
-	Rect2i recti = Rect2i(0, 100, 1280, 720);
-	Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720));
-	Rect2i recti_copy_recti = Rect2i(recti);
-	Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720));
-
-	CHECK_MESSAGE(
-			recti == recti_vector,
-			"Rect2is created with the same dimensions but by different methods should be equal.");
-	CHECK_MESSAGE(
-			recti == recti_copy_recti,
-			"Rect2is created with the same dimensions but by different methods should be equal.");
-	CHECK_MESSAGE(
-			recti == recti_copy_rect,
-			"Rect2is created with the same dimensions but by different methods should be equal.");
-}
-
-TEST_CASE("[Rect2i] String conversion") {
-	// Note: This also depends on the Vector2 string representation.
-	CHECK_MESSAGE(
-			String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
-			"The string representation should match the expected value.");
-}
-
-TEST_CASE("[Rect2i] Basic getters") {
-	const Rect2i rect = Rect2i(0, 100, 1280, 720);
-	CHECK_MESSAGE(
-			rect.get_position() == Vector2i(0, 100),
-			"get_position() should return the expected value.");
-	CHECK_MESSAGE(
-			rect.get_size() == Vector2i(1280, 720),
-			"get_size() should return the expected value.");
-	CHECK_MESSAGE(
-			rect.get_end() == Vector2i(1280, 820),
-			"get_end() should return the expected value.");
-	CHECK_MESSAGE(
-			rect.get_center() == Vector2i(640, 460),
-			"get_center() should return the expected value.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
-			"get_center() should return the expected value.");
-}
-
-TEST_CASE("[Rect2i] Basic setters") {
-	Rect2i rect = Rect2i(0, 100, 1280, 720);
-	rect.set_end(Vector2i(4000, 4000));
-	CHECK_MESSAGE(
-			rect == Rect2i(0, 100, 4000, 3900),
-			"set_end() should result in the expected Rect2i.");
-
-	rect = Rect2i(0, 100, 1280, 720);
-	rect.set_position(Vector2i(4000, 4000));
-	CHECK_MESSAGE(
-			rect == Rect2i(4000, 4000, 1280, 720),
-			"set_position() should result in the expected Rect2i.");
-
-	rect = Rect2i(0, 100, 1280, 720);
-	rect.set_size(Vector2i(4000, 4000));
-	CHECK_MESSAGE(
-			rect == Rect2i(0, 100, 4000, 4000),
-			"set_size() should result in the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Area getters") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).get_area() == 921'600,
-			"get_area() should return the expected value.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, -1280, -720).get_area() == 921'600,
-			"get_area() should return the expected value.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, -720).get_area() == -921'600,
-			"get_area() should return the expected value.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, -1280, 720).get_area() == -921'600,
-			"get_area() should return the expected value.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 0, 720).get_area() == 0,
-			"get_area() should return the expected value.");
-
-	CHECK_MESSAGE(
-			!Rect2i(0, 100, 1280, 720).has_no_area(),
-			"has_no_area() should return the expected value on Rect2i with an area.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 0, 500).has_no_area(),
-			"has_no_area() should return the expected value on Rect2i with no area.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 500, 0).has_no_area(),
-			"has_no_area() should return the expected value on Rect2i with no area.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 0, 0).has_no_area(),
-			"has_no_area() should return the expected value on Rect2i with no area.");
-}
-
-TEST_CASE("[Rect2i] Absolute coordinates") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720),
-			"abs() should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720),
-			"abs() should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720),
-			"abs() should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720),
-			"abs() should return the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Intersection") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100),
-			"intersection() with fully enclosed Rect2i should return the expected result.");
-	// The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels.
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100),
-			"intersection() with partially enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(),
-			"intersection() with non-enclosed Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Enclosing") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)),
-			"encloses() with fully contained Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			!Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)),
-			"encloses() with partially contained Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			!Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)),
-			"encloses() with non-contained Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)),
-			"encloses() with identical Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Expanding") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720),
-			"expand() with contained Vector2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820),
-			"expand() with non-contained Vector2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Growing") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920),
-			"grow() with positive value should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520),
-			"grow() with negative value should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280),
-			"grow() with large negative value should return the expected Rect2i.");
-
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320),
-			"grow_individual() with positive values should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520),
-			"grow_individual() with positive and negative values should return the expected Rect2i.");
-
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220),
-			"grow_side() with positive value should return the expected Rect2i.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220),
-			"grow_side() with negative value should return the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Has point") {
-	Rect2i rect = Rect2i(0, 100, 1280, 720);
-	CHECK_MESSAGE(
-			rect.has_point(Vector2i(500, 600)),
-			"has_point() with contained Vector2i should return the expected result.");
-	CHECK_MESSAGE(
-			!rect.has_point(Vector2i(0, 0)),
-			"has_point() with non-contained Vector2i should return the expected result.");
-
-	CHECK_MESSAGE(
-			rect.has_point(rect.position),
-			"has_point() with positive size should include `position`.");
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + Vector2i(1, 1)),
-			"has_point() with positive size should include `position + (1, 1)`.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + Vector2i(1, -1)),
-			"has_point() with positive size should not include `position + (1, -1)`.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + rect.size),
-			"has_point() with positive size should not include `position + size`.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + rect.size + Vector2i(1, 1)),
-			"has_point() with positive size should not include `position + size + (1, 1)`.");
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + rect.size + Vector2i(-1, -1)),
-			"has_point() with positive size should include `position + size + (-1, -1)`.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + rect.size + Vector2i(-1, 1)),
-			"has_point() with positive size should not include `position + size + (-1, 1)`.");
-
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + Vector2i(0, 10)),
-			"has_point() with point located on left edge should return true.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
-			"has_point() with point located on right edge should return false.");
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + Vector2i(10, 0)),
-			"has_point() with point located on top edge should return true.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
-			"has_point() with point located on bottom edge should return false.");
-
-	/*
-	// FIXME: Disabled for now until GH-37617 is fixed one way or another.
-	// More tests should then be written like for the positive size case.
-	rect = Rect2i(0, 100, -1280, -720);
-	CHECK_MESSAGE(
-			rect.has_point(rect.position),
-			"has_point() with negative size should include `position`.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + rect.size),
-			"has_point() with negative size should not include `position + size`.");
-	*/
-
-	rect = Rect2i(-4000, -200, 1280, 720);
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + Vector2i(0, 10)),
-			"has_point() with negative position and point located on left edge should return true.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
-			"has_point() with negative position and point located on right edge should return false.");
-	CHECK_MESSAGE(
-			rect.has_point(rect.position + Vector2i(10, 0)),
-			"has_point() with negative position and point located on top edge should return true.");
-	CHECK_MESSAGE(
-			!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
-			"has_point() with negative position and point located on bottom edge should return false.");
-}
-
-TEST_CASE("[Rect2i] Intersection") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)),
-			"intersects() with fully enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)),
-			"intersects() with partially enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			!Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)),
-			"intersects() with non-enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			!Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)),
-			"intersects() with adjacent Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Merging") {
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720),
-			"merge() with fully enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720),
-			"merge() with partially enclosed Rect2i should return the expected result.");
-	CHECK_MESSAGE(
-			Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820),
-			"merge() with non-enclosed Rect2i should return the expected result.");
-}
 } // namespace TestRect2
 
 #endif // TEST_RECT2_H

+ 311 - 0
tests/core/math/test_rect2i.h

@@ -0,0 +1,311 @@
+/*************************************************************************/
+/*  test_rect2i.h                                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef TEST_RECT2I_H
+#define TEST_RECT2I_H
+
+#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestRect2i {
+TEST_CASE("[Rect2i] Constructor methods") {
+	Rect2i recti = Rect2i(0, 100, 1280, 720);
+	Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720));
+	Rect2i recti_copy_recti = Rect2i(recti);
+	Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720));
+
+	CHECK_MESSAGE(
+			recti == recti_vector,
+			"Rect2is created with the same dimensions but by different methods should be equal.");
+	CHECK_MESSAGE(
+			recti == recti_copy_recti,
+			"Rect2is created with the same dimensions but by different methods should be equal.");
+	CHECK_MESSAGE(
+			recti == recti_copy_rect,
+			"Rect2is created with the same dimensions but by different methods should be equal.");
+}
+
+TEST_CASE("[Rect2i] String conversion") {
+	// Note: This also depends on the Vector2 string representation.
+	CHECK_MESSAGE(
+			String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
+			"The string representation should match the expected value.");
+}
+
+TEST_CASE("[Rect2i] Basic getters") {
+	const Rect2i rect = Rect2i(0, 100, 1280, 720);
+	CHECK_MESSAGE(
+			rect.get_position() == Vector2i(0, 100),
+			"get_position() should return the expected value.");
+	CHECK_MESSAGE(
+			rect.get_size() == Vector2i(1280, 720),
+			"get_size() should return the expected value.");
+	CHECK_MESSAGE(
+			rect.get_end() == Vector2i(1280, 820),
+			"get_end() should return the expected value.");
+	CHECK_MESSAGE(
+			rect.get_center() == Vector2i(640, 460),
+			"get_center() should return the expected value.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
+			"get_center() should return the expected value.");
+}
+
+TEST_CASE("[Rect2i] Basic setters") {
+	Rect2i rect = Rect2i(0, 100, 1280, 720);
+	rect.set_end(Vector2i(4000, 4000));
+	CHECK_MESSAGE(
+			rect == Rect2i(0, 100, 4000, 3900),
+			"set_end() should result in the expected Rect2i.");
+
+	rect = Rect2i(0, 100, 1280, 720);
+	rect.set_position(Vector2i(4000, 4000));
+	CHECK_MESSAGE(
+			rect == Rect2i(4000, 4000, 1280, 720),
+			"set_position() should result in the expected Rect2i.");
+
+	rect = Rect2i(0, 100, 1280, 720);
+	rect.set_size(Vector2i(4000, 4000));
+	CHECK_MESSAGE(
+			rect == Rect2i(0, 100, 4000, 4000),
+			"set_size() should result in the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Area getters") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).get_area() == 921'600,
+			"get_area() should return the expected value.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, -1280, -720).get_area() == 921'600,
+			"get_area() should return the expected value.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, -720).get_area() == -921'600,
+			"get_area() should return the expected value.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, -1280, 720).get_area() == -921'600,
+			"get_area() should return the expected value.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 0, 720).get_area() == 0,
+			"get_area() should return the expected value.");
+
+	CHECK_MESSAGE(
+			!Rect2i(0, 100, 1280, 720).has_no_area(),
+			"has_no_area() should return the expected value on Rect2i with an area.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 0, 500).has_no_area(),
+			"has_no_area() should return the expected value on Rect2i with no area.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 500, 0).has_no_area(),
+			"has_no_area() should return the expected value on Rect2i with no area.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 0, 0).has_no_area(),
+			"has_no_area() should return the expected value on Rect2i with no area.");
+}
+
+TEST_CASE("[Rect2i] Absolute coordinates") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720),
+			"abs() should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720),
+			"abs() should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720),
+			"abs() should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720),
+			"abs() should return the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Intersection") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100),
+			"intersection() with fully enclosed Rect2i should return the expected result.");
+	// The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels.
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100),
+			"intersection() with partially enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(),
+			"intersection() with non-enclosed Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Enclosing") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)),
+			"encloses() with fully contained Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			!Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)),
+			"encloses() with partially contained Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			!Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)),
+			"encloses() with non-contained Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)),
+			"encloses() with identical Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Expanding") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720),
+			"expand() with contained Vector2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820),
+			"expand() with non-contained Vector2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Growing") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920),
+			"grow() with positive value should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520),
+			"grow() with negative value should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280),
+			"grow() with large negative value should return the expected Rect2i.");
+
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320),
+			"grow_individual() with positive values should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520),
+			"grow_individual() with positive and negative values should return the expected Rect2i.");
+
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220),
+			"grow_side() with positive value should return the expected Rect2i.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220),
+			"grow_side() with negative value should return the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Has point") {
+	Rect2i rect = Rect2i(0, 100, 1280, 720);
+	CHECK_MESSAGE(
+			rect.has_point(Vector2i(500, 600)),
+			"has_point() with contained Vector2i should return the expected result.");
+	CHECK_MESSAGE(
+			!rect.has_point(Vector2i(0, 0)),
+			"has_point() with non-contained Vector2i should return the expected result.");
+
+	CHECK_MESSAGE(
+			rect.has_point(rect.position),
+			"has_point() with positive size should include `position`.");
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + Vector2i(1, 1)),
+			"has_point() with positive size should include `position + (1, 1)`.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + Vector2i(1, -1)),
+			"has_point() with positive size should not include `position + (1, -1)`.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + rect.size),
+			"has_point() with positive size should not include `position + size`.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + rect.size + Vector2i(1, 1)),
+			"has_point() with positive size should not include `position + size + (1, 1)`.");
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + rect.size + Vector2i(-1, -1)),
+			"has_point() with positive size should include `position + size + (-1, -1)`.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + rect.size + Vector2i(-1, 1)),
+			"has_point() with positive size should not include `position + size + (-1, 1)`.");
+
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + Vector2i(0, 10)),
+			"has_point() with point located on left edge should return true.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
+			"has_point() with point located on right edge should return false.");
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + Vector2i(10, 0)),
+			"has_point() with point located on top edge should return true.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
+			"has_point() with point located on bottom edge should return false.");
+
+	/*
+	// FIXME: Disabled for now until GH-37617 is fixed one way or another.
+	// More tests should then be written like for the positive size case.
+	rect = Rect2i(0, 100, -1280, -720);
+	CHECK_MESSAGE(
+			rect.has_point(rect.position),
+			"has_point() with negative size should include `position`.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + rect.size),
+			"has_point() with negative size should not include `position + size`.");
+	*/
+
+	rect = Rect2i(-4000, -200, 1280, 720);
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + Vector2i(0, 10)),
+			"has_point() with negative position and point located on left edge should return true.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
+			"has_point() with negative position and point located on right edge should return false.");
+	CHECK_MESSAGE(
+			rect.has_point(rect.position + Vector2i(10, 0)),
+			"has_point() with negative position and point located on top edge should return true.");
+	CHECK_MESSAGE(
+			!rect.has_point(rect.position + Vector2i(10, rect.size.y)),
+			"has_point() with negative position and point located on bottom edge should return false.");
+}
+
+TEST_CASE("[Rect2i] Intersection") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)),
+			"intersects() with fully enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)),
+			"intersects() with partially enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			!Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)),
+			"intersects() with non-enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			!Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)),
+			"intersects() with adjacent Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Merging") {
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720),
+			"merge() with fully enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720),
+			"merge() with partially enclosed Rect2i should return the expected result.");
+	CHECK_MESSAGE(
+			Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820),
+			"merge() with non-enclosed Rect2i should return the expected result.");
+}
+} // namespace TestRect2i
+
+#endif // TEST_RECT2I_H

+ 1 - 0
tests/test_main.cpp

@@ -48,6 +48,7 @@
 #include "tests/core/math/test_math.h"
 #include "tests/core/math/test_random_number_generator.h"
 #include "tests/core/math/test_rect2.h"
+#include "tests/core/math/test_rect2i.h"
 #include "tests/core/math/test_vector2.h"
 #include "tests/core/math/test_vector2i.h"
 #include "tests/core/math/test_vector3.h"