summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/core/io/test_image.h23
-rw-r--r--tests/core/math/test_basis.h94
-rw-r--r--tests/core/math/test_math_funcs.h23
-rw-r--r--tests/core/math/test_vector2i.h6
-rw-r--r--tests/core/math/test_vector3i.h6
-rw-r--r--tests/core/math/test_vector4i.h6
-rw-r--r--tests/core/string/test_string.h81
-rw-r--r--tests/scene/test_camera_3d.h370
-rw-r--r--tests/scene/test_code_edit.h26
-rw-r--r--tests/scene/test_curve_2d.h36
-rw-r--r--tests/scene/test_packed_scene.h65
-rw-r--r--tests/scene/test_primitives.h2
-rw-r--r--tests/scene/test_text_edit.h56
-rw-r--r--tests/servers/test_navigation_server_3d.h99
-rw-r--r--tests/test_main.cpp1
15 files changed, 875 insertions, 19 deletions
diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h
index 07c7c04e36..945a7e1ba3 100644
--- a/tests/core/io/test_image.h
+++ b/tests/core/io/test_image.h
@@ -403,6 +403,29 @@ TEST_CASE("[Image] Custom mipmaps") {
}
}
+TEST_CASE("[Image] Convert image") {
+ for (int format = Image::FORMAT_RF; format < Image::FORMAT_RGBE9995; format++) {
+ for (int new_format = Image::FORMAT_RF; new_format < Image::FORMAT_RGBE9995; new_format++) {
+ Ref<Image> image = memnew(Image(4, 4, false, (Image::Format)format));
+ image->convert((Image::Format)new_format);
+ String format_string = Image::format_names[(Image::Format)format];
+ String new_format_string = Image::format_names[(Image::Format)new_format];
+ format_string = "Error converting from " + format_string + " to " + new_format_string + ".";
+ CHECK_MESSAGE(image->get_format() == new_format, format_string);
+ }
+ }
+
+ Ref<Image> image = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
+ PackedByteArray image_data = image->get_data();
+ image->convert((Image::Format)-1);
+ CHECK_MESSAGE(image->get_data() == image_data, "Image conversion to invalid type (-1) should not alter image.");
+
+ Ref<Image> image2 = memnew(Image(4, 4, false, Image::FORMAT_RGBA8));
+ image_data = image2->get_data();
+ image2->convert((Image::Format)(Image::FORMAT_MAX + 1));
+ CHECK_MESSAGE(image2->get_data() == image_data, "Image conversion to invalid type (Image::FORMAT_MAX + 1) should not alter image.");
+}
+
} // namespace TestImage
#endif // TEST_IMAGE_H
diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h
index fcac9a6231..a9bc2e9b99 100644
--- a/tests/core/math/test_basis.h
+++ b/tests/core/math/test_basis.h
@@ -324,6 +324,100 @@ TEST_CASE("[Basis] Is conformal checks") {
CHECK_FALSE_MESSAGE(
Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_conformal(),
"Basis with the X axis skewed 45 degrees should not be conformal.");
+
+ CHECK_MESSAGE(
+ Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_conformal(),
+ "Edge case: Basis with all zeroes should return true for is_conformal (because a 0 scale is uniform).");
+}
+
+TEST_CASE("[Basis] Is orthogonal checks") {
+ CHECK_MESSAGE(
+ Basis().is_orthogonal(),
+ "Identity Basis should be orthogonal.");
+
+ CHECK_MESSAGE(
+ Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_orthogonal(),
+ "Basis with only rotation should be orthogonal.");
+
+ CHECK_MESSAGE(
+ Basis::from_scale(Vector3(-1, -1, -1)).is_orthogonal(),
+ "Basis with only a flip should be orthogonal.");
+
+ CHECK_MESSAGE(
+ Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_orthogonal(),
+ "Basis with only scale should be orthogonal.");
+
+ CHECK_MESSAGE(
+ Basis(Vector3(3, 4, 0), Vector3(4, -3, 0), Vector3(0, 0, 5)).is_orthogonal(),
+ "Basis with a flip, rotation, and uniform scale should be orthogonal.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_orthogonal(),
+ "Basis with the X axis skewed 45 degrees should not be orthogonal.");
+
+ CHECK_MESSAGE(
+ Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_orthogonal(),
+ "Edge case: Basis with all zeroes should return true for is_orthogonal, since zero vectors are orthogonal to all vectors.");
+}
+
+TEST_CASE("[Basis] Is orthonormal checks") {
+ CHECK_MESSAGE(
+ Basis().is_orthonormal(),
+ "Identity Basis should be orthonormal.");
+
+ CHECK_MESSAGE(
+ Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_orthonormal(),
+ "Basis with only rotation should be orthonormal.");
+
+ CHECK_MESSAGE(
+ Basis::from_scale(Vector3(-1, -1, -1)).is_orthonormal(),
+ "Basis with only a flip should be orthonormal.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_orthonormal(),
+ "Basis with only scale should not be orthonormal.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(Vector3(3, 4, 0), Vector3(4, -3, 0), Vector3(0, 0, 5)).is_orthonormal(),
+ "Basis with a flip, rotation, and uniform scale should not be orthonormal.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_orthonormal(),
+ "Basis with the X axis skewed 45 degrees should not be orthonormal.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_orthonormal(),
+ "Edge case: Basis with all zeroes should return false for is_orthonormal, since the vectors do not have a length of 1.");
+}
+
+TEST_CASE("[Basis] Is rotation checks") {
+ CHECK_MESSAGE(
+ Basis().is_rotation(),
+ "Identity Basis should be a rotation (a rotation of zero).");
+
+ CHECK_MESSAGE(
+ Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_rotation(),
+ "Basis with only rotation should be a rotation.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis::from_scale(Vector3(-1, -1, -1)).is_rotation(),
+ "Basis with only a flip should not be a rotation.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_rotation(),
+ "Basis with only scale should not be a rotation.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(Vector3(2, 0, 0), Vector3(0, 0.5, 0), Vector3(0, 0, 1)).is_rotation(),
+ "Basis with a squeeze should not be a rotation.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_rotation(),
+ "Basis with the X axis skewed 45 degrees should not be a rotation.");
+
+ CHECK_FALSE_MESSAGE(
+ Basis(0, 0, 0, 0, 0, 0, 0, 0, 0).is_rotation(),
+ "Edge case: Basis with all zeroes should return false for is_rotation, because it is not just a rotation (has a scale of 0).");
}
} // namespace TestBasis
diff --git a/tests/core/math/test_math_funcs.h b/tests/core/math/test_math_funcs.h
index 5fbea4aece..0a9d9c97d9 100644
--- a/tests/core/math/test_math_funcs.h
+++ b/tests/core/math/test_math_funcs.h
@@ -110,6 +110,29 @@ TEST_CASE_TEMPLATE("[Math] round/floor/ceil", T, float, double) {
CHECK(Math::ceil((T)-1.9) == (T)-1.0);
}
+TEST_CASE_TEMPLATE("[Math] integer division round up unsigned", T, uint32_t, uint64_t) {
+ CHECK(Math::division_round_up((T)0, (T)64) == 0);
+ CHECK(Math::division_round_up((T)1, (T)64) == 1);
+ CHECK(Math::division_round_up((T)63, (T)64) == 1);
+ CHECK(Math::division_round_up((T)64, (T)64) == 1);
+ CHECK(Math::division_round_up((T)65, (T)64) == 2);
+ CHECK(Math::division_round_up((T)65, (T)1) == 65);
+}
+
+TEST_CASE_TEMPLATE("[Math] integer division round up signed", T, int32_t, int64_t) {
+ CHECK(Math::division_round_up((T)0, (T)64) == 0);
+ CHECK(Math::division_round_up((T)1, (T)64) == 1);
+ CHECK(Math::division_round_up((T)63, (T)64) == 1);
+ CHECK(Math::division_round_up((T)64, (T)64) == 1);
+ CHECK(Math::division_round_up((T)65, (T)64) == 2);
+ CHECK(Math::division_round_up((T)65, (T)1) == 65);
+ CHECK(Math::division_round_up((T)-1, (T)64) == 0);
+ CHECK(Math::division_round_up((T)-1, (T)-1) == 1);
+ CHECK(Math::division_round_up((T)-1, (T)1) == -1);
+ CHECK(Math::division_round_up((T)-1, (T)-2) == 1);
+ CHECK(Math::division_round_up((T)-4, (T)-2) == 2);
+}
+
TEST_CASE_TEMPLATE("[Math] sin/cos/tan", T, float, double) {
CHECK(Math::sin((T)-0.1) == doctest::Approx((T)-0.0998334166));
CHECK(Math::sin((T)0.1) == doctest::Approx((T)0.0998334166));
diff --git a/tests/core/math/test_vector2i.h b/tests/core/math/test_vector2i.h
index 743c87f486..0f33400f7f 100644
--- a/tests/core/math/test_vector2i.h
+++ b/tests/core/math/test_vector2i.h
@@ -87,6 +87,12 @@ TEST_CASE("[Vector2i] Length methods") {
CHECK_MESSAGE(
vector2.length() == doctest::Approx(36.05551275463989293119),
"Vector2i length should work as expected.");
+ CHECK_MESSAGE(
+ vector1.distance_squared_to(vector2) == 500,
+ "Vector2i distance_squared_to should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ vector1.distance_to(vector2) == doctest::Approx(22.36067977499789696409),
+ "Vector2i distance_to should work as expected.");
}
TEST_CASE("[Vector2i] Operators") {
diff --git a/tests/core/math/test_vector3i.h b/tests/core/math/test_vector3i.h
index 485a500715..3914b85a75 100644
--- a/tests/core/math/test_vector3i.h
+++ b/tests/core/math/test_vector3i.h
@@ -90,6 +90,12 @@ TEST_CASE("[Vector3i] Length methods") {
CHECK_MESSAGE(
vector2.length() == doctest::Approx(53.8516480713450403125),
"Vector3i length should work as expected.");
+ CHECK_MESSAGE(
+ vector1.distance_squared_to(vector2) == 1400,
+ "Vector3i distance_squared_to should work as expected and return exact result.");
+ CHECK_MESSAGE(
+ vector1.distance_to(vector2) == doctest::Approx(37.41657386773941385584),
+ "Vector3i distance_to should work as expected.");
}
TEST_CASE("[Vector3i] Operators") {
diff --git a/tests/core/math/test_vector4i.h b/tests/core/math/test_vector4i.h
index 5fda6f1778..31f68696c0 100644
--- a/tests/core/math/test_vector4i.h
+++ b/tests/core/math/test_vector4i.h
@@ -90,6 +90,12 @@ TEST_CASE("[Vector4i] Length methods") {
CHECK_MESSAGE(
vector2.length() == doctest::Approx(73.4846922835),
"Vector4i length should work as expected.");
+ CHECK_MESSAGE(
+ vector1.distance_squared_to(vector2) == 3000,
+ "Vector4i distance_squared_to should work as expected.");
+ CHECK_MESSAGE(
+ vector1.distance_to(vector2) == doctest::Approx(54.772255750517),
+ "Vector4i distance_to should work as expected.");
}
TEST_CASE("[Vector4i] Operators") {
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 02349aedc0..8a11491bb2 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -456,30 +456,93 @@ TEST_CASE("[String] Number to string") {
}
TEST_CASE("[String] String to integer") {
- static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" };
- static const int num[4] = { 1237461283, -22, 0, -1123412 };
+ static const char *nums[14] = { "1237461283", "- 22", "0", " - 1123412", "", "10_000_000", "-1_2_3_4", "10__000", " 1 2 34 ", "-0", "007", "--45", "---46", "-7-2" };
+ static const int num[14] = { 1237461283, -22, 0, -1123412, 0, 10000000, -1234, 10000, 1234, 0, 7, 45, -46, -72 };
- for (int i = 0; i < 4; i++) {
+ for (int i = 0; i < 14; i++) {
CHECK(String(nums[i]).to_int() == num[i]);
}
+ CHECK(String("0b1011").to_int() == 1011); // Looks like a binary number, but to_int() handles this as a base-10 number, "b" is just ignored.
+ CHECK(String("0x1012").to_int() == 1012); // Looks like a hexadecimal number, but to_int() handles this as a base-10 number, "x" is just ignored.
+
+ ERR_PRINT_OFF
+ CHECK(String("999999999999999999999999999999999999999999999999999999999").to_int() == INT64_MAX); // Too large, largest possible is returned.
+ CHECK(String("-999999999999999999999999999999999999999999999999999999999").to_int() == INT64_MIN); // Too small, smallest possible is returned.
+ ERR_PRINT_ON
}
TEST_CASE("[String] Hex to integer") {
- static const char *nums[4] = { "0xFFAE", "22", "0", "AADDAD" };
- static const int64_t num[4] = { 0xFFAE, 0x22, 0, 0xAADDAD };
+ static const char *nums[12] = { "0xFFAE", "22", "0", "AADDAD", "0x7FFFFFFFFFFFFFFF", "-0xf", "", "000", "000f", "0xaA", "-ff", "-" };
+ static const int64_t num[12] = { 0xFFAE, 0x22, 0, 0xAADDAD, 0x7FFFFFFFFFFFFFFF, -0xf, 0, 0, 0xf, 0xaa, -0xff, 0x0 };
- for (int i = 0; i < 4; i++) {
+ for (int i = 0; i < 12; i++) {
CHECK(String(nums[i]).hex_to_int() == num[i]);
}
+
+ // Invalid hex strings should return 0.
+ static const char *invalid_nums[15] = { "qwerty", "QWERTY", "0xqwerty", "0x00qwerty", "qwerty00", "0x", "0x__", "__", "x12", "+", " ff", "ff ", "f f", "+ff", "--0x78" };
+
+ ERR_PRINT_OFF
+ for (int i = 0; i < 15; i++) {
+ CHECK(String(invalid_nums[i]).hex_to_int() == 0);
+ }
+
+ CHECK(String("0xFFFFFFFFFFFFFFFFFFFFFFF").hex_to_int() == INT64_MAX); // Too large, largest possible is returned.
+ CHECK(String("-0xFFFFFFFFFFFFFFFFFFFFFFF").hex_to_int() == INT64_MIN); // Too small, smallest possible is returned.
+ ERR_PRINT_ON
+}
+
+TEST_CASE("[String] Bin to integer") {
+ static const char *nums[10] = { "", "0", "0b0", "0b1", "0b", "1", "0b1010", "-0b11", "-1010", "0b0111111111111111111111111111111111111111111111111111111111111111" };
+ static const int64_t num[10] = { 0, 0, 0, 1, 0, 1, 10, -3, -10, 0x7FFFFFFFFFFFFFFF };
+
+ for (int i = 0; i < 10; i++) {
+ CHECK(String(nums[i]).bin_to_int() == num[i]);
+ }
+
+ // Invalid bin strings should return 0. The long "0x11...11" is just too long for a 64 bit int.
+ static const char *invalid_nums[16] = { "qwerty", "QWERTY", "0bqwerty", "0b00qwerty", "qwerty00", "0x__", "0b__", "__", "b12", "+", "-", "0x12ab", " 11", "11 ", "1 1", "--0b11" };
+
+ for (int i = 0; i < 16; i++) {
+ CHECK(String(invalid_nums[i]).bin_to_int() == 0);
+ }
+
+ ERR_PRINT_OFF
+ CHECK(String("0b111111111111111111111111111111111111111111111111111111111111111111111111111111111").bin_to_int() == INT64_MAX); // Too large, largest possible is returned.
+ CHECK(String("-0b111111111111111111111111111111111111111111111111111111111111111111111111111111111").bin_to_int() == INT64_MIN); // Too small, smallest possible is returned.
+ ERR_PRINT_ON
}
TEST_CASE("[String] String to float") {
- static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" };
- static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 };
+ static const char *nums[12] = { "-12348298412.2", "0.05", "2.0002", " -0.0001", "0", "000", "123", "0.0", "000.000", "000.007", "234__", "3..14" };
+ static const double num[12] = { -12348298412.2, 0.05, 2.0002, -0.0001, 0.0, 0.0, 123.0, 0.0, 0.0, 0.007, 234.0, 3.0 };
- for (int i = 0; i < 4; i++) {
+ for (int i = 0; i < 12; i++) {
CHECK(!(ABS(String(nums[i]).to_float() - num[i]) > 0.00001));
}
+
+ // Invalid float strings should return 0.
+ static const char *invalid_nums[6] = { "qwerty", "qwerty123", "0xffff", "0b1010", "--3.13", "__345" };
+
+ for (int i = 0; i < 6; i++) {
+ CHECK(String(invalid_nums[i]).to_float() == 0);
+ }
+
+ // Very large exponents.
+ CHECK(String("1e308").to_float() == 1e308);
+ CHECK(String("-1e308").to_float() == -1e308);
+
+ // Exponent is so high that value is INFINITY/-INFINITY.
+ CHECK(String("1e309").to_float() == INFINITY);
+ CHECK(String("1e511").to_float() == INFINITY);
+ CHECK(String("-1e309").to_float() == -INFINITY);
+ CHECK(String("-1e511").to_float() == -INFINITY);
+
+ // Exponent is so high that a warning message is printed. Value is INFINITY/-INFINITY.
+ ERR_PRINT_OFF
+ CHECK(String("1e512").to_float() == INFINITY);
+ CHECK(String("-1e512").to_float() == -INFINITY);
+ ERR_PRINT_ON
}
TEST_CASE("[String] Slicing") {
diff --git a/tests/scene/test_camera_3d.h b/tests/scene/test_camera_3d.h
new file mode 100644
index 0000000000..169486d108
--- /dev/null
+++ b/tests/scene/test_camera_3d.h
@@ -0,0 +1,370 @@
+/**************************************************************************/
+/* test_camera_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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_CAMERA_3D_H
+#define TEST_CAMERA_3D_H
+
+#include "scene/3d/camera_3d.h"
+#include "scene/main/viewport.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+// Constants.
+#define SQRT3 (1.7320508f)
+
+TEST_CASE("[SceneTree][Camera3D] Getters and setters") {
+ Camera3D *test_camera = memnew(Camera3D);
+
+ SUBCASE("Cull mask") {
+ constexpr int cull_mask = (1 << 5) | (1 << 7) | (1 << 9);
+ constexpr int set_enable_layer = 3;
+ constexpr int set_disable_layer = 5;
+ test_camera->set_cull_mask(cull_mask);
+ CHECK(test_camera->get_cull_mask() == cull_mask);
+ test_camera->set_cull_mask_value(set_enable_layer, true);
+ CHECK(test_camera->get_cull_mask_value(set_enable_layer));
+ test_camera->set_cull_mask_value(set_disable_layer, false);
+ CHECK_FALSE(test_camera->get_cull_mask_value(set_disable_layer));
+ }
+
+ SUBCASE("Attributes") {
+ Ref<CameraAttributes> attributes = memnew(CameraAttributes);
+ test_camera->set_attributes(attributes);
+ CHECK(test_camera->get_attributes() == attributes);
+ Ref<CameraAttributesPhysical> physical_attributes = memnew(CameraAttributesPhysical);
+ test_camera->set_attributes(physical_attributes);
+ CHECK(test_camera->get_attributes() == physical_attributes);
+ }
+
+ SUBCASE("Camera frustum properties") {
+ constexpr float near = 0.2f;
+ constexpr float far = 995.0f;
+ constexpr float fov = 120.0f;
+ constexpr float size = 7.0f;
+ constexpr float h_offset = 1.1f;
+ constexpr float v_offset = -1.6f;
+ const Vector2 frustum_offset(5, 7);
+ test_camera->set_near(near);
+ CHECK(test_camera->get_near() == near);
+ test_camera->set_far(far);
+ CHECK(test_camera->get_far() == far);
+ test_camera->set_fov(fov);
+ CHECK(test_camera->get_fov() == fov);
+ test_camera->set_size(size);
+ CHECK(test_camera->get_size() == size);
+ test_camera->set_h_offset(h_offset);
+ CHECK(test_camera->get_h_offset() == h_offset);
+ test_camera->set_v_offset(v_offset);
+ CHECK(test_camera->get_v_offset() == v_offset);
+ test_camera->set_frustum_offset(frustum_offset);
+ CHECK(test_camera->get_frustum_offset() == frustum_offset);
+ test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT);
+ CHECK(test_camera->get_keep_aspect_mode() == Camera3D::KeepAspect::KEEP_HEIGHT);
+ test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_WIDTH);
+ CHECK(test_camera->get_keep_aspect_mode() == Camera3D::KeepAspect::KEEP_WIDTH);
+ }
+
+ SUBCASE("Projection mode") {
+ test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_ORTHOGONAL);
+ CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_ORTHOGONAL);
+ test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
+ CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
+ }
+
+ SUBCASE("Helper setters") {
+ constexpr float fov = 90.0f, size = 6.0f;
+ constexpr float near1 = 0.1f, near2 = 0.5f;
+ constexpr float far1 = 1001.0f, far2 = 1005.0f;
+ test_camera->set_perspective(fov, near1, far1);
+ CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
+ CHECK(test_camera->get_near() == near1);
+ CHECK(test_camera->get_far() == far1);
+ CHECK(test_camera->get_fov() == fov);
+ test_camera->set_orthogonal(size, near2, far2);
+ CHECK(test_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_ORTHOGONAL);
+ CHECK(test_camera->get_near() == near2);
+ CHECK(test_camera->get_far() == far2);
+ CHECK(test_camera->get_size() == size);
+ }
+
+ SUBCASE("Doppler tracking") {
+ test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_IDLE_STEP);
+ CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_IDLE_STEP);
+ test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_PHYSICS_STEP);
+ CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_PHYSICS_STEP);
+ test_camera->set_doppler_tracking(Camera3D::DopplerTracking::DOPPLER_TRACKING_DISABLED);
+ CHECK(test_camera->get_doppler_tracking() == Camera3D::DopplerTracking::DOPPLER_TRACKING_DISABLED);
+ }
+
+ memdelete(test_camera);
+}
+
+TEST_CASE("[SceneTree][Camera3D] Position queries") {
+ // Cameras need a viewport to know how to compute their frustums, so we make a fake one here.
+ Camera3D *test_camera = memnew(Camera3D);
+ SubViewport *mock_viewport = memnew(SubViewport);
+ // 4:2.
+ mock_viewport->set_size(Vector2(400, 200));
+ SceneTree::get_singleton()->get_root()->add_child(mock_viewport);
+ mock_viewport->add_child(test_camera);
+ test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_WIDTH);
+ REQUIRE_MESSAGE(test_camera->is_current(), "Camera3D should be made current upon entering tree.");
+
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_ORTHOGONAL);
+ // The orthogonal case is simpler, so we test a more random position + rotation combination here.
+ // For the other cases we'll use zero translation and rotation instead.
+ test_camera->set_global_position(Vector3(1, 2, 3));
+ test_camera->look_at(Vector3(-4, 5, 1));
+ // Width = 5, Aspect Ratio = 400 / 200 = 2, so Height is 2.5.
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ const Basis basis = test_camera->get_global_basis();
+ // Subtract near so offset starts from the near plane.
+ const Vector3 offset1 = basis.xform(Vector3(-1.5f, 3.5f, 0.2f - test_camera->get_near()));
+ const Vector3 offset2 = basis.xform(Vector3(2.0f, -0.5f, -0.6f - test_camera->get_near()));
+ const Vector3 offset3 = basis.xform(Vector3(-3.0f, 1.0f, -0.6f - test_camera->get_near()));
+ const Vector3 offset4 = basis.xform(Vector3(-2.0f, 1.5f, -0.6f - test_camera->get_near()));
+ const Vector3 offset5 = basis.xform(Vector3(0, 0, 10000.0f - test_camera->get_near()));
+
+ SUBCASE("is_position_behind") {
+ CHECK(test_camera->is_position_behind(test_camera->get_global_position() + offset1));
+ CHECK_FALSE(test_camera->is_position_behind(test_camera->get_global_position() + offset2));
+
+ SUBCASE("h/v offset should have no effect on the result of is_position_behind") {
+ test_camera->set_h_offset(-11.0f);
+ test_camera->set_v_offset(22.1f);
+ CHECK(test_camera->is_position_behind(test_camera->get_global_position() + offset1));
+ test_camera->set_h_offset(4.7f);
+ test_camera->set_v_offset(-3.0f);
+ CHECK_FALSE(test_camera->is_position_behind(test_camera->get_global_position() + offset2));
+ }
+ // Reset h/v offsets.
+ test_camera->set_h_offset(0);
+ test_camera->set_v_offset(0);
+ }
+
+ SUBCASE("is_position_in_frustum") {
+ // If the point is behind the near plane, it is outside the camera frustum.
+ // So offset1 is not in frustum.
+ CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset1));
+ // If |right| > 5 / 2 or |up| > 2.5 / 2, the point is outside the camera frustum.
+ // So offset2 is in frustum and offset3 and offset4 are not.
+ CHECK(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset2));
+ CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset3));
+ CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset4));
+ // offset5 is beyond the far plane, so it is not in frustum.
+ CHECK_FALSE(test_camera->is_position_in_frustum(test_camera->get_global_position() + offset5));
+ }
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_projection(Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
+ // Camera at origin, looking at +Z.
+ test_camera->set_global_position(Vector3(0, 0, 0));
+ test_camera->set_global_rotation(Vector3(0, 0, 0));
+ // Keep width, so horizontal fov = 120.
+ // Since the near plane distance is 1,
+ // with trig we know the near plane's width is 2 * sqrt(3), so its height is sqrt(3).
+ test_camera->set_perspective(120.0f, 1.0f, 1000.0f);
+
+ SUBCASE("is_position_behind") {
+ CHECK_FALSE(test_camera->is_position_behind(Vector3(0, 0, -1.5f)));
+ CHECK(test_camera->is_position_behind(Vector3(2, 0, -0.2f)));
+ }
+
+ SUBCASE("is_position_in_frustum") {
+ CHECK(test_camera->is_position_in_frustum(Vector3(-1.3f, 0, -1.1f)));
+ CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(2, 0, -1.1f)));
+ CHECK(test_camera->is_position_in_frustum(Vector3(1, 0.5f, -1.1f)));
+ CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(1, 1, -1.1f)));
+ CHECK(test_camera->is_position_in_frustum(Vector3(0, 0, -1.5f)));
+ CHECK_FALSE(test_camera->is_position_in_frustum(Vector3(0, 0, -0.5f)));
+ }
+ }
+
+ memdelete(test_camera);
+ memdelete(mock_viewport);
+}
+
+TEST_CASE("[SceneTree][Camera3D] Project/Unproject position") {
+ // Cameras need a viewport to know how to compute their frustums, so we make a fake one here.
+ Camera3D *test_camera = memnew(Camera3D);
+ SubViewport *mock_viewport = memnew(SubViewport);
+ // 4:2.
+ mock_viewport->set_size(Vector2(400, 200));
+ SceneTree::get_singleton()->get_root()->add_child(mock_viewport);
+ mock_viewport->add_child(test_camera);
+ test_camera->set_global_position(Vector3(0, 0, 0));
+ test_camera->set_global_rotation(Vector3(0, 0, 0));
+ test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT);
+
+ SUBCASE("project_position") {
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f)));
+ // Top left.
+ CHECK(test_camera->project_position(Vector2(0, 0), 1.5f).is_equal_approx(Vector3(-5.0f, 2.5f, -1.5f)));
+ // Bottom right.
+ CHECK(test_camera->project_position(Vector2(400, 200), 5.0f).is_equal_approx(Vector3(5.0f, -2.5f, -5.0f)));
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_perspective(120.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_position(Vector2(200, 100), 0.5f).is_equal_approx(Vector3(0, 0, -0.5f)));
+ CHECK(test_camera->project_position(Vector2(200, 100), 100.0f).is_equal_approx(Vector3(0, 0, -100.0f)));
+ // 3/4th way to Top left.
+ CHECK(test_camera->project_position(Vector2(100, 50), 0.5f).is_equal_approx(Vector3(-SQRT3 * 0.5f, SQRT3 * 0.25f, -0.5f)));
+ CHECK(test_camera->project_position(Vector2(100, 50), 1.0f).is_equal_approx(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f)));
+ // 3/4th way to Bottom right.
+ CHECK(test_camera->project_position(Vector2(300, 150), 0.5f).is_equal_approx(Vector3(SQRT3 * 0.5f, -SQRT3 * 0.25f, -0.5f)));
+ CHECK(test_camera->project_position(Vector2(300, 150), 1.0f).is_equal_approx(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f)));
+ }
+ }
+
+ // Uses cases that are the inverse of the above sub-case.
+ SUBCASE("unproject_position") {
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ // Center
+ CHECK(test_camera->unproject_position(Vector3(0, 0, -0.5f)).is_equal_approx(Vector2(200, 100)));
+ // Top left
+ CHECK(test_camera->unproject_position(Vector3(-5.0f, 2.5f, -1.5f)).is_equal_approx(Vector2(0, 0)));
+ // Bottom right
+ CHECK(test_camera->unproject_position(Vector3(5.0f, -2.5f, -5.0f)).is_equal_approx(Vector2(400, 200)));
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_perspective(120.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->unproject_position(Vector3(0, 0, -0.5f)).is_equal_approx(Vector2(200, 100)));
+ CHECK(test_camera->unproject_position(Vector3(0, 0, -100.0f)).is_equal_approx(Vector2(200, 100)));
+ // 3/4th way to Top left.
+ WARN(test_camera->unproject_position(Vector3(-SQRT3 * 0.5f, SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(100, 50)));
+ WARN(test_camera->unproject_position(Vector3(-SQRT3, SQRT3 * 0.5f, -1.0f)).is_equal_approx(Vector2(100, 50)));
+ // 3/4th way to Bottom right.
+ CHECK(test_camera->unproject_position(Vector3(SQRT3 * 0.5f, -SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(300, 150)));
+ CHECK(test_camera->unproject_position(Vector3(SQRT3, -SQRT3 * 0.5f, -1.0f)).is_equal_approx(Vector2(300, 150)));
+ }
+ }
+
+ memdelete(test_camera);
+ memdelete(mock_viewport);
+}
+
+TEST_CASE("[SceneTree][Camera3D] Project ray") {
+ // Cameras need a viewport to know how to compute their frustums, so we make a fake one here.
+ Camera3D *test_camera = memnew(Camera3D);
+ SubViewport *mock_viewport = memnew(SubViewport);
+ // 4:2.
+ mock_viewport->set_size(Vector2(400, 200));
+ SceneTree::get_singleton()->get_root()->add_child(mock_viewport);
+ mock_viewport->add_child(test_camera);
+ test_camera->set_global_position(Vector3(0, 0, 0));
+ test_camera->set_global_rotation(Vector3(0, 0, 0));
+ test_camera->set_keep_aspect_mode(Camera3D::KeepAspect::KEEP_HEIGHT);
+
+ SUBCASE("project_ray_origin") {
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_ray_origin(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -0.5f)));
+ // Top left.
+ CHECK(test_camera->project_ray_origin(Vector2(0, 0)).is_equal_approx(Vector3(-5.0f, 2.5f, -0.5f)));
+ // Bottom right.
+ CHECK(test_camera->project_ray_origin(Vector2(400, 200)).is_equal_approx(Vector3(5.0f, -2.5f, -0.5f)));
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_perspective(120.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_ray_origin(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, 0)));
+ // Top left.
+ CHECK(test_camera->project_ray_origin(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, 0)));
+ // Bottom right.
+ CHECK(test_camera->project_ray_origin(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, 0)));
+ }
+ }
+
+ SUBCASE("project_ray_normal") {
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1)));
+ // Top left.
+ CHECK(test_camera->project_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, -1)));
+ // Bottom right.
+ CHECK(test_camera->project_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, -1)));
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_perspective(120.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1)));
+ // Top left.
+ CHECK(test_camera->project_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(-SQRT3, SQRT3 / 2, -0.5f).normalized()));
+ // Bottom right.
+ CHECK(test_camera->project_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(SQRT3, -SQRT3 / 2, -0.5f).normalized()));
+ }
+ }
+
+ SUBCASE("project_local_ray_normal") {
+ test_camera->set_rotation_degrees(Vector3(60, 60, 60));
+
+ SUBCASE("Orthogonal projection") {
+ test_camera->set_orthogonal(5.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_local_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1)));
+ // Top left.
+ CHECK(test_camera->project_local_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(0, 0, -1)));
+ // Bottom right.
+ CHECK(test_camera->project_local_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(0, 0, -1)));
+ }
+
+ SUBCASE("Perspective projection") {
+ test_camera->set_perspective(120.0f, 0.5f, 1000.0f);
+ // Center.
+ CHECK(test_camera->project_local_ray_normal(Vector2(200, 100)).is_equal_approx(Vector3(0, 0, -1)));
+ // Top left.
+ CHECK(test_camera->project_local_ray_normal(Vector2(0, 0)).is_equal_approx(Vector3(-SQRT3, SQRT3 / 2, -0.5f).normalized()));
+ // Bottom right.
+ CHECK(test_camera->project_local_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(SQRT3, -SQRT3 / 2, -0.5f).normalized()));
+ }
+ }
+
+ memdelete(test_camera);
+ memdelete(mock_viewport);
+}
+
+#undef SQRT3
+
+#endif // TEST_CAMERA_3D_H
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index c3a374b5cd..7d98372327 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -1503,6 +1503,19 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
CHECK(code_edit->is_in_string(1) == -1);
CHECK(code_edit->is_in_string(2) != -1);
CHECK(code_edit->is_in_string(3) == -1);
+
+ /* Next check updating the delimiter cache while typing. */
+ code_edit->set_text("\n\n");
+ code_edit->set_caret_line(0);
+ code_edit->set_caret_column(0);
+ CHECK(code_edit->is_in_string(0) == -1);
+ CHECK(code_edit->is_in_string(1) == -1);
+ code_edit->insert_text_at_caret("#");
+ CHECK(code_edit->is_in_string(0) != -1);
+ CHECK(code_edit->is_in_string(1) != -1);
+ code_edit->insert_text_at_caret("#");
+ CHECK(code_edit->is_in_string(0) != -1);
+ CHECK(code_edit->is_in_string(1) == -1);
}
SUBCASE("[CodeEdit] multiline comment delimiters") {
@@ -1692,6 +1705,19 @@ TEST_CASE("[SceneTree][CodeEdit] delimiters") {
CHECK(code_edit->is_in_comment(1) == -1);
CHECK(code_edit->is_in_comment(2) != -1);
CHECK(code_edit->is_in_comment(3) == -1);
+
+ /* Next check updating the delimiter cache while typing. */
+ code_edit->set_text("\n\n");
+ code_edit->set_caret_line(0);
+ code_edit->set_caret_column(0);
+ CHECK(code_edit->is_in_comment(0) == -1);
+ CHECK(code_edit->is_in_comment(1) == -1);
+ code_edit->insert_text_at_caret("#");
+ CHECK(code_edit->is_in_comment(0) != -1);
+ CHECK(code_edit->is_in_comment(1) != -1);
+ code_edit->insert_text_at_caret("#");
+ CHECK(code_edit->is_in_comment(0) != -1);
+ CHECK(code_edit->is_in_comment(1) == -1);
}
SUBCASE("[CodeEdit] multiline mixed delimiters") {
diff --git a/tests/scene/test_curve_2d.h b/tests/scene/test_curve_2d.h
index fc141f3d09..099f6fefa9 100644
--- a/tests/scene/test_curve_2d.h
+++ b/tests/scene/test_curve_2d.h
@@ -155,17 +155,37 @@ TEST_CASE("[Curve2D] Sampling") {
SUBCASE("sample_baked_with_rotation") {
const real_t pi = 3.14159;
- Transform2D t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 0)));
- CHECK(t.get_origin() == Vector2(0, 0));
- CHECK(Math::is_equal_approx(t.get_rotation(), pi));
-
- t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 25)));
+ const real_t half_pi = pi * 0.5;
+ Ref<Curve2D> rot_curve = memnew(Curve2D);
+ Transform2D t;
+
+ rot_curve->clear_points();
+ rot_curve->add_point(Vector2());
+ rot_curve->add_point(Vector2(50, 0));
+ t = rot_curve->sample_baked_with_rotation(25);
+ CHECK(t.get_origin() == Vector2(25, 0));
+ CHECK(Math::is_equal_approx(t.get_rotation(), 0));
+
+ rot_curve->clear_points();
+ rot_curve->add_point(Vector2());
+ rot_curve->add_point(Vector2(0, 50));
+ t = rot_curve->sample_baked_with_rotation(25);
CHECK(t.get_origin() == Vector2(0, 25));
- CHECK(Math::is_equal_approx(t.get_rotation(), pi));
+ CHECK(Math::is_equal_approx(t.get_rotation(), half_pi));
- t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 50)));
- CHECK(t.get_origin() == Vector2(0, 50));
+ rot_curve->clear_points();
+ rot_curve->add_point(Vector2());
+ rot_curve->add_point(Vector2(-50, 0));
+ t = rot_curve->sample_baked_with_rotation(25);
+ CHECK(t.get_origin() == Vector2(-25, 0));
CHECK(Math::is_equal_approx(t.get_rotation(), pi));
+
+ rot_curve->clear_points();
+ rot_curve->add_point(Vector2());
+ rot_curve->add_point(Vector2(0, -50));
+ t = rot_curve->sample_baked_with_rotation(25);
+ CHECK(t.get_origin() == Vector2(0, -25));
+ CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi));
}
SUBCASE("get_closest_point") {
diff --git a/tests/scene/test_packed_scene.h b/tests/scene/test_packed_scene.h
index 3517aba31f..1e784c199d 100644
--- a/tests/scene/test_packed_scene.h
+++ b/tests/scene/test_packed_scene.h
@@ -150,6 +150,71 @@ TEST_CASE("[PackedScene] Instantiate Packed Scene With Children") {
memdelete(instance);
}
+TEST_CASE("[PackedScene] Set Path") {
+ // Create a scene to pack.
+ Node *scene = memnew(Node);
+ scene->set_name("TestScene");
+
+ // Pack the scene.
+ PackedScene packed_scene;
+ packed_scene.pack(scene);
+
+ // Set a new path for the packed scene.
+ const String new_path = "NewTestPath";
+ packed_scene.set_path(new_path);
+
+ // Check if the path has been set correctly.
+ Ref<SceneState> state = packed_scene.get_state();
+ CHECK(state.is_valid());
+ CHECK(state->get_path() == new_path);
+
+ memdelete(scene);
+}
+
+TEST_CASE("[PackedScene] Replace State") {
+ // Create a scene to pack.
+ Node *scene = memnew(Node);
+ scene->set_name("TestScene");
+
+ // Pack the scene.
+ PackedScene packed_scene;
+ packed_scene.pack(scene);
+
+ // Create another scene state to replace with.
+ Ref<SceneState> new_state = memnew(SceneState);
+ new_state->set_path("NewPath");
+
+ // Replace the state.
+ packed_scene.replace_state(new_state);
+
+ // Check if the state has been replaced.
+ Ref<SceneState> state = packed_scene.get_state();
+ CHECK(state.is_valid());
+ CHECK(state == new_state);
+
+ memdelete(scene);
+}
+
+TEST_CASE("[PackedScene] Recreate State") {
+ // Create a scene to pack.
+ Node *scene = memnew(Node);
+ scene->set_name("TestScene");
+
+ // Pack the scene.
+ PackedScene packed_scene;
+ packed_scene.pack(scene);
+
+ // Recreate the state.
+ packed_scene.recreate_state();
+
+ // Check if the state has been recreated.
+ Ref<SceneState> state = packed_scene.get_state();
+ CHECK(state.is_valid());
+ CHECK(state->get_node_count() == 0); // Since the state was recreated, it should be empty.
+
+ memdelete(scene);
+}
+
} // namespace TestPackedScene
#endif // TEST_PACKED_SCENE_H
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
index 9232a3020d..552f722d24 100644
--- a/tests/scene/test_primitives.h
+++ b/tests/scene/test_primitives.h
@@ -232,7 +232,7 @@ TEST_CASE("[SceneTree][Primitive][Cylinder] Cylinder Primitive") {
CHECK(cylinder->get_bottom_radius() > 0);
CHECK(cylinder->get_height() > 0);
CHECK(cylinder->get_radial_segments() > 0);
- CHECK(cylinder->get_rings() > 0);
+ CHECK(cylinder->get_rings() >= 0);
}
SUBCASE("[SceneTree][Primitive][Cylinder] Set properties and get them") {
diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h
index 9c9ade4445..8f603c698d 100644
--- a/tests/scene/test_text_edit.h
+++ b/tests/scene/test_text_edit.h
@@ -3186,6 +3186,60 @@ TEST_CASE("[SceneTree][TextEdit] versioning") {
CHECK(text_edit->get_version() == 3); // Should this be cleared?
CHECK(text_edit->get_saved_version() == 0);
+ SUBCASE("[TextEdit] versioning selection") {
+ text_edit->set_text("Godot Engine\nWaiting for Godot\nTest Text for multi carat\nLine 4 Text");
+ text_edit->set_multiple_carets_enabled(true);
+
+ text_edit->remove_secondary_carets();
+ text_edit->deselect();
+ text_edit->set_caret_line(0);
+ text_edit->set_caret_column(0);
+
+ CHECK(text_edit->get_caret_count() == 1);
+
+ Array caret_index;
+ caret_index.push_back(0);
+
+ for (int i = 1; i < 4; i++) {
+ caret_index.push_back(text_edit->add_caret(i, 0));
+ CHECK((int)caret_index.back() >= 0);
+ }
+
+ CHECK(text_edit->get_caret_count() == 4);
+
+ for (int i = 0; i < 4; i++) {
+ text_edit->select(i, 0, i, 5, caret_index[i]);
+ }
+
+ CHECK(text_edit->get_caret_count() == 4);
+ for (int i = 0; i < 4; i++) {
+ CHECK(text_edit->has_selection(caret_index[i]));
+ CHECK(text_edit->get_selection_from_line(caret_index[i]) == i);
+ CHECK(text_edit->get_selection_from_column(caret_index[i]) == 0);
+ CHECK(text_edit->get_selection_to_line(caret_index[i]) == i);
+ CHECK(text_edit->get_selection_to_column(caret_index[i]) == 5);
+ }
+ text_edit->begin_complex_operation();
+ text_edit->deselect();
+ text_edit->set_text("New Line Text");
+ text_edit->select(0, 0, 0, 7, 0);
+ text_edit->end_complex_operation();
+
+ CHECK(text_edit->get_caret_count() == 1);
+ CHECK(text_edit->get_selected_text(0) == "New Lin");
+
+ text_edit->undo();
+
+ CHECK(text_edit->get_caret_count() == 4);
+ for (int i = 0; i < 4; i++) {
+ CHECK(text_edit->has_selection(caret_index[i]));
+ CHECK(text_edit->get_selection_from_line(caret_index[i]) == i);
+ CHECK(text_edit->get_selection_from_column(caret_index[i]) == 0);
+ CHECK(text_edit->get_selection_to_line(caret_index[i]) == i);
+ CHECK(text_edit->get_selection_to_column(caret_index[i]) == 5);
+ }
+ }
+
memdelete(text_edit);
}
@@ -4118,7 +4172,7 @@ TEST_CASE("[SceneTree][TextEdit] setter getters") {
CHECK_FALSE(text_edit->is_drawing_spaces());
}
- SUBCASE("[TextEdit] draw minimao") {
+ SUBCASE("[TextEdit] draw minimap") {
text_edit->set_draw_minimap(true);
CHECK(text_edit->is_drawing_minimap());
text_edit->set_draw_minimap(false);
diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h
index 691536da8e..5ab2975b74 100644
--- a/tests/servers/test_navigation_server_3d.h
+++ b/tests/servers/test_navigation_server_3d.h
@@ -429,6 +429,105 @@ TEST_SUITE("[Navigation]") {
navigation_server->free(map);
}
+ TEST_CASE("[NavigationServer3D] Server should make agents avoid dynamic obstacles when avoidance enabled") {
+ NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
+
+ RID map = navigation_server->map_create();
+ RID agent_1 = navigation_server->agent_create();
+ RID obstacle_1 = navigation_server->obstacle_create();
+
+ navigation_server->map_set_active(map, true);
+
+ navigation_server->agent_set_map(agent_1, map);
+ navigation_server->agent_set_avoidance_enabled(agent_1, true);
+ navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));
+ navigation_server->agent_set_radius(agent_1, 1);
+ navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));
+ CallableMock agent_1_avoidance_callback_mock;
+ navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
+
+ navigation_server->obstacle_set_map(obstacle_1, map);
+ navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
+ navigation_server->obstacle_set_position(obstacle_1, Vector3(2.5, 0, 0.5));
+ navigation_server->obstacle_set_radius(obstacle_1, 1);
+
+ CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
+ navigation_server->process(0.0); // Give server some cycles to commit.
+ CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
+ Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
+ CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
+ CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
+
+ navigation_server->free(obstacle_1);
+ navigation_server->free(agent_1);
+ navigation_server->free(map);
+ navigation_server->process(0.0); // Give server some cycles to commit.
+ }
+
+ TEST_CASE("[NavigationServer3D] Server should make agents avoid static obstacles when avoidance enabled") {
+ NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
+
+ RID map = navigation_server->map_create();
+ RID agent_1 = navigation_server->agent_create();
+ RID agent_2 = navigation_server->agent_create();
+ RID obstacle_1 = navigation_server->obstacle_create();
+
+ navigation_server->map_set_active(map, true);
+
+ navigation_server->agent_set_map(agent_1, map);
+ navigation_server->agent_set_avoidance_enabled(agent_1, true);
+ navigation_server->agent_set_radius(agent_1, 1.6); // Have hit the obstacle already.
+ navigation_server->agent_set_velocity(agent_1, Vector3(1, 0, 0));
+ CallableMock agent_1_avoidance_callback_mock;
+ navigation_server->agent_set_avoidance_callback(agent_1, callable_mp(&agent_1_avoidance_callback_mock, &CallableMock::function1));
+
+ navigation_server->agent_set_map(agent_2, map);
+ navigation_server->agent_set_avoidance_enabled(agent_2, true);
+ navigation_server->agent_set_radius(agent_2, 1.4); // Haven't hit the obstacle yet.
+ navigation_server->agent_set_velocity(agent_2, Vector3(1, 0, 0));
+ CallableMock agent_2_avoidance_callback_mock;
+ navigation_server->agent_set_avoidance_callback(agent_2, callable_mp(&agent_2_avoidance_callback_mock, &CallableMock::function1));
+
+ navigation_server->obstacle_set_map(obstacle_1, map);
+ navigation_server->obstacle_set_avoidance_enabled(obstacle_1, true);
+ PackedVector3Array obstacle_1_vertices;
+
+ SUBCASE("Static obstacles should work on ground level") {
+ navigation_server->agent_set_position(agent_1, Vector3(0, 0, 0));
+ navigation_server->agent_set_position(agent_2, Vector3(0, 0, 5));
+ obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));
+ obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));
+ }
+
+ SUBCASE("Static obstacles should work when elevated") {
+ navigation_server->agent_set_position(agent_1, Vector3(0, 5, 0));
+ navigation_server->agent_set_position(agent_2, Vector3(0, 5, 5));
+ obstacle_1_vertices.push_back(Vector3(1.5, 0, 0.5));
+ obstacle_1_vertices.push_back(Vector3(1.5, 0, 4.5));
+ navigation_server->obstacle_set_position(obstacle_1, Vector3(0, 5, 0));
+ }
+
+ navigation_server->obstacle_set_vertices(obstacle_1, obstacle_1_vertices);
+
+ CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 0);
+ CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 0);
+ navigation_server->process(0.0); // Give server some cycles to commit.
+ CHECK_EQ(agent_1_avoidance_callback_mock.function1_calls, 1);
+ CHECK_EQ(agent_2_avoidance_callback_mock.function1_calls, 1);
+ Vector3 agent_1_safe_velocity = agent_1_avoidance_callback_mock.function1_latest_arg0;
+ Vector3 agent_2_safe_velocity = agent_2_avoidance_callback_mock.function1_latest_arg0;
+ CHECK_MESSAGE(agent_1_safe_velocity.x > 0, "Agent 1 should move a bit along desired velocity (+X).");
+ CHECK_MESSAGE(agent_1_safe_velocity.z < 0, "Agent 1 should move a bit to the side so that it avoids obstacle.");
+ CHECK_MESSAGE(agent_2_safe_velocity.x > 0, "Agent 2 should move a bit along desired velocity (+X).");
+ CHECK_MESSAGE(agent_2_safe_velocity.z == 0, "Agent 2 should not move to the side.");
+
+ navigation_server->free(obstacle_1);
+ navigation_server->free(agent_2);
+ navigation_server->free(agent_1);
+ navigation_server->free(map);
+ navigation_server->process(0.0); // Give server some cycles to commit.
+ }
+
#ifndef DISABLE_DEPRECATED
// This test case uses only public APIs on purpose - other test cases use simplified baking.
// FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed.
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 8c120f6d3a..5187ebd00f 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -94,6 +94,7 @@
#include "tests/scene/test_arraymesh.h"
#include "tests/scene/test_audio_stream_wav.h"
#include "tests/scene/test_bit_map.h"
+#include "tests/scene/test_camera_3d.h"
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_color_picker.h"
#include "tests/scene/test_control.h"