Przeglądaj źródła

Add `is_finite` method for checking built-in types

Haoyu Qiu 3 lat temu
rodzic
commit
5da515773d
46 zmienionych plików z 504 dodań i 12 usunięć
  1. 4 0
      core/math/aabb.cpp
  2. 1 0
      core/math/aabb.h
  3. 4 0
      core/math/basis.cpp
  4. 1 0
      core/math/basis.h
  5. 3 0
      core/math/math_funcs.h
  6. 4 0
      core/math/plane.cpp
  7. 1 0
      core/math/plane.h
  8. 4 0
      core/math/quaternion.cpp
  9. 1 0
      core/math/quaternion.h
  10. 4 0
      core/math/rect2.cpp
  11. 1 0
      core/math/rect2.h
  12. 4 0
      core/math/transform_2d.cpp
  13. 1 0
      core/math/transform_2d.h
  14. 4 0
      core/math/transform_3d.cpp
  15. 1 0
      core/math/transform_3d.h
  16. 4 0
      core/math/vector2.cpp
  17. 1 0
      core/math/vector2.h
  18. 4 0
      core/math/vector3.cpp
  19. 1 0
      core/math/vector3.h
  20. 4 0
      core/math/vector4.cpp
  21. 1 0
      core/math/vector4.h
  22. 6 6
      core/string/ustring.cpp
  23. 10 0
      core/variant/variant_call.cpp
  24. 5 0
      core/variant/variant_utility.cpp
  25. 7 0
      doc/classes/@GlobalScope.xml
  26. 6 0
      doc/classes/AABB.xml
  27. 6 0
      doc/classes/Basis.xml
  28. 6 0
      doc/classes/Plane.xml
  29. 6 0
      doc/classes/Quaternion.xml
  30. 6 0
      doc/classes/Rect2.xml
  31. 6 0
      doc/classes/Transform2D.xml
  32. 6 0
      doc/classes/Transform3D.xml
  33. 6 0
      doc/classes/Vector2.xml
  34. 6 0
      doc/classes/Vector3.xml
  35. 6 0
      doc/classes/Vector4.xml
  36. 1 6
      servers/rendering/renderer_scene_cull.cpp
  37. 21 0
      tests/core/math/test_aabb.h
  38. 34 0
      tests/core/math/test_basis.h
  39. 23 0
      tests/core/math/test_plane.h
  40. 57 0
      tests/core/math/test_quaternion.h
  41. 21 0
      tests/core/math/test_rect2.h
  42. 34 0
      tests/core/math/test_transform_2d.h
  43. 23 0
      tests/core/math/test_transform_3d.h
  44. 26 0
      tests/core/math/test_vector2.h
  45. 45 0
      tests/core/math/test_vector3.h
  46. 78 0
      tests/core/math/test_vector4.h

+ 4 - 0
core/math/aabb.cpp

@@ -76,6 +76,10 @@ bool AABB::is_equal_approx(const AABB &p_aabb) const {
 	return position.is_equal_approx(p_aabb.position) && size.is_equal_approx(p_aabb.size);
 }
 
+bool AABB::is_finite() const {
+	return position.is_finite() && size.is_finite();
+}
+
 AABB AABB::intersection(const AABB &p_aabb) const {
 #ifdef MATH_CHECKS
 	if (unlikely(size.x < 0 || size.y < 0 || size.z < 0 || p_aabb.size.x < 0 || p_aabb.size.y < 0 || p_aabb.size.z < 0)) {

+ 1 - 0
core/math/aabb.h

@@ -63,6 +63,7 @@ struct _NO_DISCARD_ AABB {
 	bool operator!=(const AABB &p_rval) const;
 
 	bool is_equal_approx(const AABB &p_aabb) const;
+	bool is_finite() const;
 	_FORCE_INLINE_ bool intersects(const AABB &p_aabb) const; /// Both AABBs overlap
 	_FORCE_INLINE_ bool intersects_inclusive(const AABB &p_aabb) const; /// Both AABBs (or their faces) overlap
 	_FORCE_INLINE_ bool encloses(const AABB &p_aabb) const; /// p_aabb is completely inside this

+ 4 - 0
core/math/basis.cpp

@@ -691,6 +691,10 @@ bool Basis::is_equal_approx(const Basis &p_basis) const {
 	return rows[0].is_equal_approx(p_basis.rows[0]) && rows[1].is_equal_approx(p_basis.rows[1]) && rows[2].is_equal_approx(p_basis.rows[2]);
 }
 
+bool Basis::is_finite() const {
+	return rows[0].is_finite() && rows[1].is_finite() && rows[2].is_finite();
+}
+
 bool Basis::operator==(const Basis &p_matrix) const {
 	for (int i = 0; i < 3; i++) {
 		for (int j = 0; j < 3; j++) {

+ 1 - 0
core/math/basis.h

@@ -134,6 +134,7 @@ struct _NO_DISCARD_ Basis {
 	}
 
 	bool is_equal_approx(const Basis &p_basis) const;
+	bool is_finite() const;
 
 	bool operator==(const Basis &p_matrix) const;
 	bool operator!=(const Basis &p_matrix) const;

+ 3 - 0
core/math/math_funcs.h

@@ -184,6 +184,9 @@ public:
 #endif
 	}
 
+	static _ALWAYS_INLINE_ bool is_finite(double p_val) { return isfinite(p_val); }
+	static _ALWAYS_INLINE_ bool is_finite(float p_val) { return isfinite(p_val); }
+
 	static _ALWAYS_INLINE_ double abs(double g) { return absd(g); }
 	static _ALWAYS_INLINE_ float abs(float g) { return absf(g); }
 	static _ALWAYS_INLINE_ int abs(int g) { return g > 0 ? g : -g; }

+ 4 - 0
core/math/plane.cpp

@@ -176,6 +176,10 @@ bool Plane::is_equal_approx(const Plane &p_plane) const {
 	return normal.is_equal_approx(p_plane.normal) && Math::is_equal_approx(d, p_plane.d);
 }
 
+bool Plane::is_finite() const {
+	return normal.is_finite() && Math::is_finite(d);
+}
+
 Plane::operator String() const {
 	return "[N: " + normal.operator String() + ", D: " + String::num_real(d, false) + "]";
 }

+ 1 - 0
core/math/plane.h

@@ -74,6 +74,7 @@ struct _NO_DISCARD_ Plane {
 	Plane operator-() const { return Plane(-normal, -d); }
 	bool is_equal_approx(const Plane &p_plane) const;
 	bool is_equal_approx_any_side(const Plane &p_plane) const;
+	bool is_finite() const;
 
 	_FORCE_INLINE_ bool operator==(const Plane &p_plane) const;
 	_FORCE_INLINE_ bool operator!=(const Plane &p_plane) const;

+ 4 - 0
core/math/quaternion.cpp

@@ -79,6 +79,10 @@ bool Quaternion::is_equal_approx(const Quaternion &p_quaternion) const {
 	return Math::is_equal_approx(x, p_quaternion.x) && Math::is_equal_approx(y, p_quaternion.y) && Math::is_equal_approx(z, p_quaternion.z) && Math::is_equal_approx(w, p_quaternion.w);
 }
 
+bool Quaternion::is_finite() const {
+	return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w);
+}
+
 real_t Quaternion::length() const {
 	return Math::sqrt(length_squared());
 }

+ 1 - 0
core/math/quaternion.h

@@ -55,6 +55,7 @@ struct _NO_DISCARD_ Quaternion {
 	}
 	_FORCE_INLINE_ real_t length_squared() const;
 	bool is_equal_approx(const Quaternion &p_quaternion) const;
+	bool is_finite() const;
 	real_t length() const;
 	void normalize();
 	Quaternion normalized() const;

+ 4 - 0
core/math/rect2.cpp

@@ -38,6 +38,10 @@ 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);
 }
 
+bool Rect2::is_finite() const {
+	return position.is_finite() && size.is_finite();
+}
+
 bool Rect2::intersects_segment(const Point2 &p_from, const Point2 &p_to, Point2 *r_pos, Point2 *r_normal) const {
 #ifdef MATH_CHECKS
 	if (unlikely(size.x < 0 || size.y < 0)) {

+ 1 - 0
core/math/rect2.h

@@ -207,6 +207,7 @@ struct _NO_DISCARD_ Rect2 {
 	}
 
 	bool is_equal_approx(const Rect2 &p_rect) const;
+	bool is_finite() const;
 
 	bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
 	bool operator!=(const Rect2 &p_rect) const { return position != p_rect.position || size != p_rect.size; }

+ 4 - 0
core/math/transform_2d.cpp

@@ -168,6 +168,10 @@ bool Transform2D::is_equal_approx(const Transform2D &p_transform) const {
 	return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]);
 }
 
+bool Transform2D::is_finite() const {
+	return columns[0].is_finite() && columns[1].is_finite() && columns[2].is_finite();
+}
+
 Transform2D Transform2D::looking_at(const Vector2 &p_target) const {
 	Transform2D return_trans = Transform2D(get_rotation(), get_origin());
 	Vector2 target_position = affine_inverse().xform(p_target);

+ 1 - 0
core/math/transform_2d.h

@@ -98,6 +98,7 @@ struct _NO_DISCARD_ Transform2D {
 	void orthonormalize();
 	Transform2D orthonormalized() const;
 	bool is_equal_approx(const Transform2D &p_transform) const;
+	bool is_finite() const;
 
 	Transform2D looking_at(const Vector2 &p_target) const;
 

+ 4 - 0
core/math/transform_3d.cpp

@@ -174,6 +174,10 @@ bool Transform3D::is_equal_approx(const Transform3D &p_transform) const {
 	return basis.is_equal_approx(p_transform.basis) && origin.is_equal_approx(p_transform.origin);
 }
 
+bool Transform3D::is_finite() const {
+	return basis.is_finite() && origin.is_finite();
+}
+
 bool Transform3D::operator==(const Transform3D &p_transform) const {
 	return (basis == p_transform.basis && origin == p_transform.origin);
 }

+ 1 - 0
core/math/transform_3d.h

@@ -75,6 +75,7 @@ struct _NO_DISCARD_ Transform3D {
 	void orthogonalize();
 	Transform3D orthogonalized() const;
 	bool is_equal_approx(const Transform3D &p_transform) const;
+	bool is_finite() const;
 
 	bool operator==(const Transform3D &p_transform) const;
 	bool operator!=(const Transform3D &p_transform) const;

+ 4 - 0
core/math/vector2.cpp

@@ -186,6 +186,10 @@ bool Vector2::is_zero_approx() const {
 	return Math::is_zero_approx(x) && Math::is_zero_approx(y);
 }
 
+bool Vector2::is_finite() const {
+	return Math::is_finite(x) && Math::is_finite(y);
+}
+
 Vector2::operator String() const {
 	return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
 }

+ 1 - 0
core/math/vector2.h

@@ -121,6 +121,7 @@ struct _NO_DISCARD_ Vector2 {
 
 	bool is_equal_approx(const Vector2 &p_v) const;
 	bool is_zero_approx() const;
+	bool is_finite() const;
 
 	Vector2 operator+(const Vector2 &p_v) const;
 	void operator+=(const Vector2 &p_v);

+ 4 - 0
core/math/vector3.cpp

@@ -139,6 +139,10 @@ bool Vector3::is_zero_approx() const {
 	return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z);
 }
 
+bool Vector3::is_finite() const {
+	return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z);
+}
+
 Vector3::operator String() const {
 	return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")";
 }

+ 1 - 0
core/math/vector3.h

@@ -136,6 +136,7 @@ struct _NO_DISCARD_ Vector3 {
 
 	bool is_equal_approx(const Vector3 &p_v) const;
 	bool is_zero_approx() const;
+	bool is_finite() const;
 
 	/* Operators */
 

+ 4 - 0
core/math/vector4.cpp

@@ -64,6 +64,10 @@ bool Vector4::is_zero_approx() const {
 	return Math::is_zero_approx(x) && Math::is_zero_approx(y) && Math::is_zero_approx(z) && Math::is_zero_approx(w);
 }
 
+bool Vector4::is_finite() const {
+	return Math::is_finite(x) && Math::is_finite(y) && Math::is_finite(z) && Math::is_finite(w);
+}
+
 real_t Vector4::length() const {
 	return Math::sqrt(length_squared());
 }

+ 1 - 0
core/math/vector4.h

@@ -71,6 +71,7 @@ struct _NO_DISCARD_ Vector4 {
 	_FORCE_INLINE_ real_t length_squared() const;
 	bool is_equal_approx(const Vector4 &p_vec4) const;
 	bool is_zero_approx() const;
+	bool is_finite() const;
 	real_t length() const;
 	void normalize();
 	Vector4 normalized() const;

+ 6 - 6
core/string/ustring.cpp

@@ -4651,10 +4651,10 @@ String String::sprintf(const Array &values, bool *error) const {
 					double value = values[value_index];
 					bool is_negative = (value < 0);
 					String str = String::num(ABS(value), min_decimals);
-					bool not_numeric = isinf(value) || isnan(value);
+					const bool is_finite = Math::is_finite(value);
 
 					// Pad decimals out.
-					if (!not_numeric) {
+					if (is_finite) {
 						str = str.pad_decimals(min_decimals);
 					}
 
@@ -4662,7 +4662,7 @@ String String::sprintf(const Array &values, bool *error) const {
 
 					// Padding. Leave room for sign later if required.
 					int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
-					String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+					String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
 					if (left_justified) {
 						str = str.rpad(pad_chars_count, pad_char);
 					} else {
@@ -4713,10 +4713,10 @@ String String::sprintf(const Array &values, bool *error) const {
 					for (int i = 0; i < count; i++) {
 						double val = vec[i];
 						String number_str = String::num(ABS(val), min_decimals);
-						bool not_numeric = isinf(val) || isnan(val);
+						const bool is_finite = Math::is_finite(val);
 
 						// Pad decimals out.
-						if (!not_numeric) {
+						if (is_finite) {
 							number_str = number_str.pad_decimals(min_decimals);
 						}
 
@@ -4724,7 +4724,7 @@ String String::sprintf(const Array &values, bool *error) const {
 
 						// Padding. Leave room for sign later if required.
 						int pad_chars_count = val < 0 ? min_chars - 1 : min_chars;
-						String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+						String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
 						if (left_justified) {
 							number_str = number_str.rpad(pad_chars_count, pad_char);
 						} else {

+ 10 - 0
core/variant/variant_call.cpp

@@ -1606,6 +1606,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Vector2, is_normalized, sarray(), varray());
 	bind_method(Vector2, is_equal_approx, sarray("to"), varray());
 	bind_method(Vector2, is_zero_approx, sarray(), varray());
+	bind_method(Vector2, is_finite, sarray(), varray());
 	bind_method(Vector2, posmod, sarray("mod"), varray());
 	bind_method(Vector2, posmodv, sarray("modv"), varray());
 	bind_method(Vector2, project, sarray("b"), varray());
@@ -1653,6 +1654,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Rect2, has_area, sarray(), varray());
 	bind_method(Rect2, has_point, sarray("point"), varray());
 	bind_method(Rect2, is_equal_approx, sarray("rect"), varray());
+	bind_method(Rect2, is_finite, sarray(), varray());
 	bind_method(Rect2, intersects, sarray("b", "include_borders"), varray(false));
 	bind_method(Rect2, encloses, sarray("b"), varray());
 	bind_method(Rect2, intersection, sarray("b"), varray());
@@ -1695,6 +1697,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Vector3, is_normalized, sarray(), varray());
 	bind_method(Vector3, is_equal_approx, sarray("to"), varray());
 	bind_method(Vector3, is_zero_approx, sarray(), varray());
+	bind_method(Vector3, is_finite, sarray(), varray());
 	bind_method(Vector3, inverse, sarray(), varray());
 	bind_method(Vector3, clamp, sarray("min", "max"), varray());
 	bind_method(Vector3, snapped, sarray("step"), varray());
@@ -1759,6 +1762,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Vector4, inverse, sarray(), varray());
 	bind_method(Vector4, is_equal_approx, sarray("with"), varray());
 	bind_method(Vector4, is_zero_approx, sarray(), varray());
+	bind_method(Vector4, is_finite, sarray(), varray());
 
 	/* Vector4i */
 
@@ -1775,6 +1779,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Plane, normalized, sarray(), varray());
 	bind_method(Plane, center, sarray(), varray());
 	bind_method(Plane, is_equal_approx, sarray("to_plane"), varray());
+	bind_method(Plane, is_finite, sarray(), varray());
 	bind_method(Plane, is_point_over, sarray("point"), varray());
 	bind_method(Plane, distance_to, sarray("point"), varray());
 	bind_method(Plane, has_point, sarray("point", "tolerance"), varray(CMP_EPSILON));
@@ -1790,6 +1795,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Quaternion, normalized, sarray(), varray());
 	bind_method(Quaternion, is_normalized, sarray(), varray());
 	bind_method(Quaternion, is_equal_approx, sarray("to"), varray());
+	bind_method(Quaternion, is_finite, sarray(), varray());
 	bind_method(Quaternion, inverse, sarray(), varray());
 	bind_method(Quaternion, log, sarray(), varray());
 	bind_method(Quaternion, exp, sarray(), varray());
@@ -1909,6 +1915,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Transform2D, basis_xform_inv, sarray("v"), varray());
 	bind_method(Transform2D, interpolate_with, sarray("xform", "weight"), varray());
 	bind_method(Transform2D, is_equal_approx, sarray("xform"), varray());
+	bind_method(Transform2D, is_finite, sarray(), varray());
 	bind_method(Transform2D, set_rotation, sarray("rotation"), varray());
 	bind_method(Transform2D, set_scale, sarray("scale"), varray());
 	bind_method(Transform2D, set_skew, sarray("skew"), varray());
@@ -1929,6 +1936,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Basis, tdotz, sarray("with"), varray());
 	bind_method(Basis, slerp, sarray("to", "weight"), varray());
 	bind_method(Basis, is_equal_approx, sarray("b"), varray());
+	bind_method(Basis, is_finite, sarray(), varray());
 	bind_method(Basis, get_rotation_quaternion, sarray(), varray());
 	bind_static_method(Basis, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
 	bind_static_method(Basis, from_scale, sarray("scale"), varray());
@@ -1943,6 +1951,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(AABB, has_surface, sarray(), varray());
 	bind_method(AABB, has_point, sarray("point"), varray());
 	bind_method(AABB, is_equal_approx, sarray("aabb"), varray());
+	bind_method(AABB, is_finite, sarray(), varray());
 	bind_method(AABB, intersects, sarray("with"), varray());
 	bind_method(AABB, encloses, sarray("with"), varray());
 	bind_method(AABB, intersects_plane, sarray("plane"), varray());
@@ -1975,6 +1984,7 @@ static void _register_variant_builtin_methods() {
 	bind_method(Transform3D, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
 	bind_method(Transform3D, interpolate_with, sarray("xform", "weight"), varray());
 	bind_method(Transform3D, is_equal_approx, sarray("xform"), varray());
+	bind_method(Transform3D, is_finite, sarray(), varray());
 
 	/* Projection */
 

+ 5 - 0
core/variant/variant_utility.cpp

@@ -310,6 +310,10 @@ struct VariantUtilityFunctions {
 		return Math::is_zero_approx(x);
 	}
 
+	static inline bool is_finite(double x) {
+		return Math::is_finite(x);
+	}
+
 	static inline double ease(float x, float curve) {
 		return Math::ease(x, curve);
 	}
@@ -1420,6 +1424,7 @@ void Variant::_register_variant_utility_functions() {
 
 	FUNCBINDR(is_equal_approx, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
 	FUNCBINDR(is_zero_approx, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+	FUNCBINDR(is_finite, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
 
 	FUNCBINDR(ease, sarray("x", "curve"), Variant::UTILITY_FUNC_TYPE_MATH);
 	FUNCBINDR(step_decimals, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);

+ 7 - 0
doc/classes/@GlobalScope.xml

@@ -476,6 +476,13 @@
 				Infinity values of the same sign are considered equal.
 			</description>
 		</method>
+		<method name="is_finite">
+			<return type="bool" />
+			<param index="0" name="x" type="float" />
+			<description>
+				Returns whether [code]x[/code] is a finite value, i.e. it is not [constant @GDScript.NAN], positive infinity, or negative infinity.
+			</description>
+		</method>
 		<method name="is_inf">
 			<return type="bool" />
 			<param index="0" name="x" type="float" />

+ 6 - 0
doc/classes/AABB.xml

@@ -205,6 +205,12 @@
 				Returns [code]true[/code] if this [AABB] and [param aabb] are approximately equal, by calling [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this [AABB] is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="merge" qualifiers="const">
 			<return type="AABB" />
 			<param index="0" name="with" type="AABB" />

+ 6 - 0
doc/classes/Basis.xml

@@ -112,6 +112,12 @@
 				Returns [code]true[/code] if this basis and [param b] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this basis is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="looking_at" qualifiers="static">
 			<return type="Basis" />
 			<param index="0" name="target" type="Vector3" />

+ 6 - 0
doc/classes/Plane.xml

@@ -119,6 +119,12 @@
 				Returns [code]true[/code] if this plane and [param to_plane] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this plane is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="is_point_over" qualifiers="const">
 			<return type="bool" />
 			<param index="0" name="point" type="Vector3" />

+ 6 - 0
doc/classes/Quaternion.xml

@@ -115,6 +115,12 @@
 				Returns [code]true[/code] if this quaternion and [param to] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this quaternion is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="is_normalized" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/Rect2.xml

@@ -165,6 +165,12 @@
 				Returns [code]true[/code] if this [Rect2] and [param rect] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this [Rect2] is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="merge" qualifiers="const">
 			<return type="Rect2" />
 			<param index="0" name="b" type="Rect2" />

+ 6 - 0
doc/classes/Transform2D.xml

@@ -123,6 +123,12 @@
 				Returns [code]true[/code] if this transform and [code]transform[/code] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this transform is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="looking_at" qualifiers="const">
 			<return type="Transform2D" />
 			<param index="0" name="target" type="Vector2" default="Vector2(0, 0)" />

+ 6 - 0
doc/classes/Transform3D.xml

@@ -82,6 +82,12 @@
 				Returns [code]true[/code] if this transform and [code]transform[/code] are approximately equal, by calling [code]is_equal_approx[/code] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this transform is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="looking_at" qualifiers="const">
 			<return type="Transform3D" />
 			<param index="0" name="target" type="Vector3" />

+ 6 - 0
doc/classes/Vector2.xml

@@ -206,6 +206,12 @@
 				Returns [code]true[/code] if this vector and [code]v[/code] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this vector is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="is_normalized" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/Vector3.xml

@@ -174,6 +174,12 @@
 				Returns [code]true[/code] if this vector and [param to] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this vector is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="is_normalized" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 6 - 0
doc/classes/Vector4.xml

@@ -135,6 +135,12 @@
 				Returns [code]true[/code] if this vector and [param with] are approximately equal, by running [method @GlobalScope.is_equal_approx] on each component.
 			</description>
 		</method>
+		<method name="is_finite" qualifiers="const">
+			<return type="bool" />
+			<description>
+				Returns [code]true[/code] if this vector is finite, by calling [method @GlobalScope.is_finite] on each component.
+			</description>
+		</method>
 		<method name="is_normalized" qualifiers="const">
 			<return type="bool" />
 			<description>

+ 1 - 6
servers/rendering/renderer_scene_cull.cpp

@@ -869,12 +869,7 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
 
 	for (int i = 0; i < 4; i++) {
 		const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin;
-		ERR_FAIL_COND(Math::is_inf(v.x));
-		ERR_FAIL_COND(Math::is_nan(v.x));
-		ERR_FAIL_COND(Math::is_inf(v.y));
-		ERR_FAIL_COND(Math::is_nan(v.y));
-		ERR_FAIL_COND(Math::is_inf(v.z));
-		ERR_FAIL_COND(Math::is_nan(v.z));
+		ERR_FAIL_COND(!v.is_finite());
 	}
 
 #endif

+ 21 - 0
tests/core/math/test_aabb.h

@@ -389,6 +389,27 @@ TEST_CASE("[AABB] Expanding") {
 			aabb.expand(Vector3(-20, 0, 0)).is_equal_approx(AABB(Vector3(-20, 0, -2.5), Vector3(22.5, 7, 6))),
 			"expand() with non-contained point should return the expected AABB.");
 }
+
+TEST_CASE("[AABB] Finite number checks") {
+	const Vector3 x(0, 1, 2);
+	const Vector3 infinite(NAN, NAN, NAN);
+
+	CHECK_MESSAGE(
+			AABB(x, x).is_finite(),
+			"AABB with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			AABB(infinite, x).is_finite(),
+			"AABB with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			AABB(x, infinite).is_finite(),
+			"AABB with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			AABB(infinite, infinite).is_finite(),
+			"AABB with two components infinite should not be finite.");
+}
+
 } // namespace TestAABB
 
 #endif // TEST_AABB_H

+ 34 - 0
tests/core/math/test_basis.h

@@ -334,6 +334,40 @@ TEST_CASE("[Basis] Set axis angle") {
 	bugNan.get_axis_angle(axis, angle);
 	CHECK(!Math::is_nan(angle));
 }
+
+TEST_CASE("[Basis] Finite number checks") {
+	const Vector3 x(0, 1, 2);
+	const Vector3 infinite(NAN, NAN, NAN);
+
+	CHECK_MESSAGE(
+			Basis(x, x, x).is_finite(),
+			"Basis with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Basis(infinite, x, x).is_finite(),
+			"Basis with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Basis(x, infinite, x).is_finite(),
+			"Basis with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Basis(x, x, infinite).is_finite(),
+			"Basis with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Basis(infinite, infinite, x).is_finite(),
+			"Basis with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Basis(infinite, x, infinite).is_finite(),
+			"Basis with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Basis(x, infinite, infinite).is_finite(),
+			"Basis with two components infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Basis(infinite, infinite, infinite).is_finite(),
+			"Basis with three components infinite should not be finite.");
+}
+
 } // namespace TestBasis
 
 #endif // TEST_BASIS_H

+ 23 - 0
tests/core/math/test_plane.h

@@ -167,6 +167,29 @@ TEST_CASE("[Plane] Intersection") {
 			vec_out.is_equal_approx(Vector3(1, 1, 1)),
 			"intersects_segment() should modify vec_out to the expected result.");
 }
+
+TEST_CASE("[Plane] Finite number checks") {
+	const Vector3 x(0, 1, 2);
+	const Vector3 infinite_vec(NAN, NAN, NAN);
+	const real_t y = 0;
+	const real_t infinite_y = NAN;
+
+	CHECK_MESSAGE(
+			Plane(x, y).is_finite(),
+			"Plane with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Plane(x, infinite_y).is_finite(),
+			"Plane with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Plane(infinite_vec, y).is_finite(),
+			"Plane with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Plane(infinite_vec, infinite_y).is_finite(),
+			"Plane with two components infinite should not be finite.");
+}
+
 } // namespace TestPlane
 
 #endif // TEST_PLANE_H

+ 57 - 0
tests/core/math/test_quaternion.h

@@ -384,6 +384,63 @@ TEST_CASE("[Stress][Quaternion] Many vector xforms") {
 	}
 }
 
+TEST_CASE("[Quaternion] Finite number checks") {
+	const real_t x = NAN;
+
+	CHECK_MESSAGE(
+			Quaternion(0, 1, 2, 3).is_finite(),
+			"Quaternion with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, 1, 2, 3).is_finite(),
+			"Quaternion with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, x, 2, 3).is_finite(),
+			"Quaternion with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, 1, x, 3).is_finite(),
+			"Quaternion with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, 1, 2, x).is_finite(),
+			"Quaternion with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, x, 2, 3).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, 1, x, 3).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, 1, 2, x).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, x, x, 3).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, x, 2, x).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, 1, x, x).is_finite(),
+			"Quaternion with two components infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Quaternion(0, x, x, x).is_finite(),
+			"Quaternion with three components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, 1, x, x).is_finite(),
+			"Quaternion with three components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, x, 2, x).is_finite(),
+			"Quaternion with three components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, x, x, 3).is_finite(),
+			"Quaternion with three components infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Quaternion(x, x, x, x).is_finite(),
+			"Quaternion with four components infinite should not be finite.");
+}
+
 } // namespace TestQuaternion
 
 #endif // TEST_QUATERNION_H

+ 21 - 0
tests/core/math/test_rect2.h

@@ -300,6 +300,27 @@ 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.");
 }
+
+TEST_CASE("[Rect2] Finite number checks") {
+	const Vector2 x(0, 1);
+	const Vector2 infinite(NAN, NAN);
+
+	CHECK_MESSAGE(
+			Rect2(x, x).is_finite(),
+			"Rect2 with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Rect2(infinite, x).is_finite(),
+			"Rect2 with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Rect2(x, infinite).is_finite(),
+			"Rect2 with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Rect2(infinite, infinite).is_finite(),
+			"Rect2 with two components infinite should not be finite.");
+}
+
 } // namespace TestRect2
 
 #endif // TEST_RECT2_H

+ 34 - 0
tests/core/math/test_transform_2d.h

@@ -83,6 +83,40 @@ TEST_CASE("[Transform2D] rotation") {
 	CHECK(orig.rotated(phi) == R * orig);
 	CHECK(orig.rotated_local(phi) == orig * R);
 }
+
+TEST_CASE("[Transform2D] Finite number checks") {
+	const Vector2 x(0, 1);
+	const Vector2 infinite(NAN, NAN);
+
+	CHECK_MESSAGE(
+			Transform2D(x, x, x).is_finite(),
+			"Transform2D with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Transform2D(infinite, x, x).is_finite(),
+			"Transform2D with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Transform2D(x, infinite, x).is_finite(),
+			"Transform2D with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Transform2D(x, x, infinite).is_finite(),
+			"Transform2D with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Transform2D(infinite, infinite, x).is_finite(),
+			"Transform2D with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Transform2D(infinite, x, infinite).is_finite(),
+			"Transform2D with two components infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Transform2D(x, infinite, infinite).is_finite(),
+			"Transform2D with two components infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Transform2D(infinite, infinite, infinite).is_finite(),
+			"Transform2D with three components infinite should not be finite.");
+}
+
 } // namespace TestTransform2D
 
 #endif // TEST_TRANSFORM_2D_H

+ 23 - 0
tests/core/math/test_transform_3d.h

@@ -84,6 +84,29 @@ TEST_CASE("[Transform3D] rotation") {
 	CHECK(orig.rotated(axis, phi) == R * orig);
 	CHECK(orig.rotated_local(axis, phi) == orig * R);
 }
+
+TEST_CASE("[Transform3D] Finite number checks") {
+	const Vector3 y(0, 1, 2);
+	const Vector3 infinite_vec(NAN, NAN, NAN);
+	const Basis x(y, y, y);
+	const Basis infinite_basis(infinite_vec, infinite_vec, infinite_vec);
+
+	CHECK_MESSAGE(
+			Transform3D(x, y).is_finite(),
+			"Transform3D with all components finite should be finite");
+
+	CHECK_FALSE_MESSAGE(
+			Transform3D(x, infinite_vec).is_finite(),
+			"Transform3D with one component infinite should not be finite.");
+	CHECK_FALSE_MESSAGE(
+			Transform3D(infinite_basis, y).is_finite(),
+			"Transform3D with one component infinite should not be finite.");
+
+	CHECK_FALSE_MESSAGE(
+			Transform3D(infinite_basis, infinite_vec).is_finite(),
+			"Transform3D with two components infinite should not be finite.");
+}
+
 } // namespace TestTransform3D
 
 #endif // TEST_TRANSFORM_3D_H

+ 26 - 0
tests/core/math/test_vector2.h

@@ -465,6 +465,32 @@ TEST_CASE("[Vector2] Linear algebra methods") {
 			Math::is_equal_approx(Vector2(-a.x, a.y).dot(Vector2(b.x, -b.y)), (real_t)-57.3),
 			"Vector2 dot should return expected value.");
 }
+
+TEST_CASE("[Vector2] Finite number checks") {
+	const double infinite[] = { NAN, INFINITY, -INFINITY };
+
+	CHECK_MESSAGE(
+			Vector2(0, 1).is_finite(),
+			"Vector2(0, 1) should be finite");
+
+	for (double x : infinite) {
+		CHECK_FALSE_MESSAGE(
+				Vector2(x, 1).is_finite(),
+				"Vector2 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector2(0, x).is_finite(),
+				"Vector2 with one component infinite should not be finite.");
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			CHECK_FALSE_MESSAGE(
+					Vector2(x, y).is_finite(),
+					"Vector2 with two components infinite should not be finite.");
+		}
+	}
+}
+
 } // namespace TestVector2
 
 #endif // TEST_VECTOR2_H

+ 45 - 0
tests/core/math/test_vector3.h

@@ -479,6 +479,51 @@ TEST_CASE("[Vector3] Linear algebra methods") {
 			Math::is_equal_approx(Vector3(-a.x, a.y, -a.z).dot(Vector3(b.x, -b.y, b.z)), (real_t)-75.24),
 			"Vector3 dot should return expected value.");
 }
+
+TEST_CASE("[Vector3] Finite number checks") {
+	const double infinite[] = { NAN, INFINITY, -INFINITY };
+
+	CHECK_MESSAGE(
+			Vector3(0, 1, 2).is_finite(),
+			"Vector3(0, 1, 2) should be finite");
+
+	for (double x : infinite) {
+		CHECK_FALSE_MESSAGE(
+				Vector3(x, 1, 2).is_finite(),
+				"Vector3 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector3(0, x, 2).is_finite(),
+				"Vector3 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector3(0, 1, x).is_finite(),
+				"Vector3 with one component infinite should not be finite.");
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			CHECK_FALSE_MESSAGE(
+					Vector3(x, y, 2).is_finite(),
+					"Vector3 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector3(x, 1, y).is_finite(),
+					"Vector3 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector3(0, x, y).is_finite(),
+					"Vector3 with two components infinite should not be finite.");
+		}
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			for (double z : infinite) {
+				CHECK_FALSE_MESSAGE(
+						Vector3(x, y, z).is_finite(),
+						"Vector3 with three components infinite should not be finite.");
+			}
+		}
+	}
+}
+
 } // namespace TestVector3
 
 #endif // TEST_VECTOR3_H

+ 78 - 0
tests/core/math/test_vector4.h

@@ -314,6 +314,84 @@ TEST_CASE("[Vector4] Linear algebra methods") {
 			Math::is_equal_approx((vector1 * 2).dot(vector2 * 4), (real_t)-25.9 * 8),
 			"Vector4 dot product should work as expected.");
 }
+
+TEST_CASE("[Vector4] Finite number checks") {
+	const double infinite[] = { NAN, INFINITY, -INFINITY };
+
+	CHECK_MESSAGE(
+			Vector4(0, 1, 2, 3).is_finite(),
+			"Vector4(0, 1, 2, 3) should be finite");
+
+	for (double x : infinite) {
+		CHECK_FALSE_MESSAGE(
+				Vector4(x, 1, 2, 3).is_finite(),
+				"Vector4 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector4(0, x, 2, 3).is_finite(),
+				"Vector4 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector4(0, 1, x, 3).is_finite(),
+				"Vector4 with one component infinite should not be finite.");
+		CHECK_FALSE_MESSAGE(
+				Vector4(0, 1, 2, x).is_finite(),
+				"Vector4 with one component infinite should not be finite.");
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			CHECK_FALSE_MESSAGE(
+					Vector4(x, y, 2, 3).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector4(x, 1, y, 3).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector4(x, 1, 2, y).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector4(0, x, y, 3).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector4(0, x, 2, y).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+			CHECK_FALSE_MESSAGE(
+					Vector4(0, 1, x, y).is_finite(),
+					"Vector4 with two components infinite should not be finite.");
+		}
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			for (double z : infinite) {
+				CHECK_FALSE_MESSAGE(
+						Vector4(0, x, y, z).is_finite(),
+						"Vector4 with three components infinite should not be finite.");
+				CHECK_FALSE_MESSAGE(
+						Vector4(x, 1, y, z).is_finite(),
+						"Vector4 with three components infinite should not be finite.");
+				CHECK_FALSE_MESSAGE(
+						Vector4(x, y, 2, z).is_finite(),
+						"Vector4 with three components infinite should not be finite.");
+				CHECK_FALSE_MESSAGE(
+						Vector4(x, y, z, 3).is_finite(),
+						"Vector4 with three components infinite should not be finite.");
+			}
+		}
+	}
+
+	for (double x : infinite) {
+		for (double y : infinite) {
+			for (double z : infinite) {
+				for (double w : infinite) {
+					CHECK_FALSE_MESSAGE(
+							Vector4(x, y, z, w).is_finite(),
+							"Vector4 with four components infinite should not be finite.");
+				}
+			}
+		}
+	}
+}
+
 } // namespace TestVector4
 
 #endif // TEST_VECTOR4_H