summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/core/input/test_input_event_key.h2
-rw-r--r--tests/core/io/test_image.h2
-rw-r--r--tests/core/math/test_basis.h48
-rw-r--r--tests/core/math/test_geometry_2d.h2
-rw-r--r--tests/core/math/test_math_funcs.h556
-rw-r--r--tests/core/math/test_quaternion.h57
-rw-r--r--tests/core/math/test_vector2.h6
-rw-r--r--tests/core/math/test_vector3.h6
-rw-r--r--tests/scene/test_animation.h8
-rw-r--r--tests/scene/test_bit_map.h10
-rw-r--r--tests/scene/test_path_2d.h109
-rw-r--r--tests/scene/test_primitives.h850
-rw-r--r--tests/scene/test_text_edit.h4
-rw-r--r--tests/test_main.cpp3
-rw-r--r--tests/test_validate_testing.h2
15 files changed, 1600 insertions, 65 deletions
diff --git a/tests/core/input/test_input_event_key.h b/tests/core/input/test_input_event_key.h
index ef0a656b18..b852f3ccb9 100644
--- a/tests/core/input/test_input_event_key.h
+++ b/tests/core/input/test_input_event_key.h
@@ -102,7 +102,7 @@ TEST_CASE("[InputEventKey] Key correctly converts itself to text") {
// as text. These cases are a bit weird, since None has no textual representation
// (find_keycode_name(Key::NONE) results in a nullptr). Thus, these tests look weird
// with only (Physical) or a lonely modifier with (Physical) but (as far as I
- // understand the code, that is intended behaviour.
+ // understand the code, that is intended behavior.
// Key is None without a physical key.
none_key.set_keycode(Key::NONE);
diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h
index 181d9a8a54..1559c59b5c 100644
--- a/tests/core/io/test_image.h
+++ b/tests/core/io/test_image.h
@@ -162,7 +162,7 @@ TEST_CASE("[Image] Basic getters") {
CHECK(image->get_size() == Vector2(8, 4));
CHECK(image->get_format() == Image::FORMAT_LA8);
CHECK(image->get_used_rect() == Rect2i(0, 0, 0, 0));
- Ref<Image> image_get_rect = image->get_rect(Rect2i(0, 0, 2, 1));
+ Ref<Image> image_get_rect = image->get_region(Rect2i(0, 0, 2, 1));
CHECK(image_get_rect->get_size() == Vector2(2, 1));
}
diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h
index f52b715cd7..a4099ebf7d 100644
--- a/tests/core/math/test_basis.h
+++ b/tests/core/math/test_basis.h
@@ -46,26 +46,26 @@ Vector3 rad2deg(const Vector3 &p_rotation) {
return p_rotation / Math_PI * 180.0;
}
-String get_rot_order_name(Basis::EulerOrder ro) {
+String get_rot_order_name(EulerOrder ro) {
switch (ro) {
- case Basis::EULER_ORDER_XYZ:
+ case EulerOrder::XYZ:
return "XYZ";
- case Basis::EULER_ORDER_XZY:
+ case EulerOrder::XZY:
return "XZY";
- case Basis::EULER_ORDER_YZX:
+ case EulerOrder::YZX:
return "YZX";
- case Basis::EULER_ORDER_YXZ:
+ case EulerOrder::YXZ:
return "YXZ";
- case Basis::EULER_ORDER_ZXY:
+ case EulerOrder::ZXY:
return "ZXY";
- case Basis::EULER_ORDER_ZYX:
+ case EulerOrder::ZYX:
return "ZYX";
default:
return "[Not supported]";
}
}
-void test_rotation(Vector3 deg_original_euler, Basis::EulerOrder rot_order) {
+void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
// This test:
// 1. Converts the rotation vector from deg to rad.
// 2. Converts euler to basis.
@@ -98,8 +98,8 @@ void test_rotation(Vector3 deg_original_euler, Basis::EulerOrder rot_order) {
CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))).utf8().ptr());
// Double check `to_rotation` decomposing with XYZ rotation order.
- const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(Basis::EULER_ORDER_XYZ);
- Basis rotation_from_xyz_computed_euler = Basis::from_euler(euler_xyz_from_rotation, Basis::EULER_ORDER_XYZ);
+ const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ);
+ Basis rotation_from_xyz_computed_euler = Basis::from_euler(euler_xyz_from_rotation, EulerOrder::XYZ);
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
@@ -113,13 +113,13 @@ void test_rotation(Vector3 deg_original_euler, Basis::EulerOrder rot_order) {
}
TEST_CASE("[Basis] Euler conversions") {
- Vector<Basis::EulerOrder> euler_order_to_test;
- euler_order_to_test.push_back(Basis::EULER_ORDER_XYZ);
- euler_order_to_test.push_back(Basis::EULER_ORDER_XZY);
- euler_order_to_test.push_back(Basis::EULER_ORDER_YZX);
- euler_order_to_test.push_back(Basis::EULER_ORDER_YXZ);
- euler_order_to_test.push_back(Basis::EULER_ORDER_ZXY);
- euler_order_to_test.push_back(Basis::EULER_ORDER_ZYX);
+ Vector<EulerOrder> euler_order_to_test;
+ euler_order_to_test.push_back(EulerOrder::XYZ);
+ euler_order_to_test.push_back(EulerOrder::XZY);
+ euler_order_to_test.push_back(EulerOrder::YZX);
+ euler_order_to_test.push_back(EulerOrder::YXZ);
+ euler_order_to_test.push_back(EulerOrder::ZXY);
+ euler_order_to_test.push_back(EulerOrder::ZYX);
Vector<Vector3> vectors_to_test;
@@ -185,13 +185,13 @@ TEST_CASE("[Basis] Euler conversions") {
}
TEST_CASE("[Stress][Basis] Euler conversions") {
- Vector<Basis::EulerOrder> euler_order_to_test;
- euler_order_to_test.push_back(Basis::EULER_ORDER_XYZ);
- euler_order_to_test.push_back(Basis::EULER_ORDER_XZY);
- euler_order_to_test.push_back(Basis::EULER_ORDER_YZX);
- euler_order_to_test.push_back(Basis::EULER_ORDER_YXZ);
- euler_order_to_test.push_back(Basis::EULER_ORDER_ZXY);
- euler_order_to_test.push_back(Basis::EULER_ORDER_ZYX);
+ Vector<EulerOrder> euler_order_to_test;
+ euler_order_to_test.push_back(EulerOrder::XYZ);
+ euler_order_to_test.push_back(EulerOrder::XZY);
+ euler_order_to_test.push_back(EulerOrder::YZX);
+ euler_order_to_test.push_back(EulerOrder::YXZ);
+ euler_order_to_test.push_back(EulerOrder::ZXY);
+ euler_order_to_test.push_back(EulerOrder::ZYX);
Vector<Vector3> vectors_to_test;
// Add 1000 random vectors with weirds numbers.
diff --git a/tests/core/math/test_geometry_2d.h b/tests/core/math/test_geometry_2d.h
index db4e6e2177..54893a0b87 100644
--- a/tests/core/math/test_geometry_2d.h
+++ b/tests/core/math/test_geometry_2d.h
@@ -64,7 +64,7 @@ TEST_CASE("[Geometry2D] Point in triangle") {
// This tests points on the edge of the triangle. They are treated as being outside the triangle.
// In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make
- // the behaviour consistent this may change in the future (see issue #44717 and PR #44274).
+ // the behavior consistent this may change in the future (see issue #44717 and PR #44274).
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
}
diff --git a/tests/core/math/test_math_funcs.h b/tests/core/math/test_math_funcs.h
new file mode 100644
index 0000000000..c468e73b74
--- /dev/null
+++ b/tests/core/math/test_math_funcs.h
@@ -0,0 +1,556 @@
+/*************************************************************************/
+/* test_math_funcs.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_MATH_FUNCS_H
+#define TEST_MATH_FUNCS_H
+
+#include "tests/test_macros.h"
+
+namespace TestMath {
+
+TEST_CASE("[Math] C++ macros") {
+ CHECK(MIN(-2, 2) == -2);
+ CHECK(MIN(600, 2) == 2);
+
+ CHECK(MAX(-2, 2) == 2);
+ CHECK(MAX(600, 2) == 600);
+
+ CHECK(CLAMP(600, -2, 2) == 2);
+ CHECK(CLAMP(620, 600, 650) == 620);
+ // `max` is lower than `min`.
+ CHECK(CLAMP(620, 600, 50) == 50);
+
+ CHECK(ABS(-5) == 5);
+ CHECK(ABS(0) == 0);
+ CHECK(ABS(5) == 5);
+
+ CHECK(SIGN(-5) == -1.0);
+ CHECK(SIGN(0) == 0.0);
+ CHECK(SIGN(5) == 1.0);
+}
+
+TEST_CASE("[Math] Power of two functions") {
+ CHECK(next_power_of_2(0) == 0);
+ CHECK(next_power_of_2(1) == 1);
+ CHECK(next_power_of_2(16) == 16);
+ CHECK(next_power_of_2(17) == 32);
+ CHECK(next_power_of_2(65535) == 65536);
+
+ CHECK(previous_power_of_2(0) == 0);
+ CHECK(previous_power_of_2(1) == 1);
+ CHECK(previous_power_of_2(16) == 16);
+ CHECK(previous_power_of_2(17) == 16);
+ CHECK(previous_power_of_2(65535) == 32768);
+
+ CHECK(closest_power_of_2(0) == 0);
+ CHECK(closest_power_of_2(1) == 1);
+ CHECK(closest_power_of_2(16) == 16);
+ CHECK(closest_power_of_2(17) == 16);
+ CHECK(closest_power_of_2(65535) == 65536);
+
+ CHECK(get_shift_from_power_of_2(0) == -1);
+ CHECK(get_shift_from_power_of_2(1) == 0);
+ CHECK(get_shift_from_power_of_2(16) == 4);
+ CHECK(get_shift_from_power_of_2(17) == -1);
+ CHECK(get_shift_from_power_of_2(65535) == -1);
+
+ CHECK(nearest_shift(0) == 0);
+ CHECK(nearest_shift(1) == 1);
+ CHECK(nearest_shift(16) == 5);
+ CHECK(nearest_shift(17) == 5);
+ CHECK(nearest_shift(65535) == 16);
+}
+
+TEST_CASE("[Math] abs") {
+ // int
+ CHECK(Math::abs(-1) == 1);
+ CHECK(Math::abs(0) == 0);
+ CHECK(Math::abs(1) == 1);
+
+ // double
+ CHECK(Math::abs(-0.1) == 0.1);
+ CHECK(Math::abs(0.0) == 0.0);
+ CHECK(Math::abs(0.1) == 0.1);
+
+ // float
+ CHECK(Math::abs(-0.1f) == 0.1f);
+ CHECK(Math::abs(0.0f) == 0.0f);
+ CHECK(Math::abs(0.1f) == 0.1f);
+}
+
+TEST_CASE("[Math] round/floor/ceil") {
+ CHECK(Math::round(1.5) == 2.0);
+ CHECK(Math::round(1.6) == 2.0);
+ CHECK(Math::round(-1.5) == -2.0);
+ CHECK(Math::round(-1.1) == -1.0);
+
+ CHECK(Math::floor(1.5) == 1.0);
+ CHECK(Math::floor(-1.5) == -2.0);
+
+ CHECK(Math::ceil(1.5) == 2.0);
+ CHECK(Math::ceil(-1.9) == -1.0);
+}
+
+TEST_CASE("[Math] sin/cos/tan") {
+ CHECK(Math::sin(-0.1) == doctest::Approx(-0.0998334166));
+ CHECK(Math::sin(0.1) == doctest::Approx(0.0998334166));
+ CHECK(Math::sin(0.5) == doctest::Approx(0.4794255386));
+ CHECK(Math::sin(1.0) == doctest::Approx(0.8414709848));
+ CHECK(Math::sin(1.5) == doctest::Approx(0.9974949866));
+ CHECK(Math::sin(450.0) == doctest::Approx(-0.683283725));
+
+ CHECK(Math::cos(-0.1) == doctest::Approx(0.99500416530));
+ CHECK(Math::cos(0.1) == doctest::Approx(0.9950041653));
+ CHECK(Math::cos(0.5) == doctest::Approx(0.8775825619));
+ CHECK(Math::cos(1.0) == doctest::Approx(0.5403023059));
+ CHECK(Math::cos(1.5) == doctest::Approx(0.0707372017));
+ CHECK(Math::cos(450.0) == doctest::Approx(-0.7301529642));
+
+ CHECK(Math::tan(-0.1) == doctest::Approx(-0.1003346721));
+ CHECK(Math::tan(0.1) == doctest::Approx(0.1003346721));
+ CHECK(Math::tan(0.5) == doctest::Approx(0.5463024898));
+ CHECK(Math::tan(1.0) == doctest::Approx(1.5574077247));
+ CHECK(Math::tan(1.5) == doctest::Approx(14.1014199472));
+ CHECK(Math::tan(450.0) == doctest::Approx(0.9358090134));
+}
+
+TEST_CASE("[Math] sinh/cosh/tanh") {
+ CHECK(Math::sinh(-0.1) == doctest::Approx(-0.10016675));
+ CHECK(Math::sinh(0.1) == doctest::Approx(0.10016675));
+ CHECK(Math::sinh(0.5) == doctest::Approx(0.5210953055));
+ CHECK(Math::sinh(1.0) == doctest::Approx(1.1752011936));
+ CHECK(Math::sinh(1.5) == doctest::Approx(2.1292794551));
+
+ CHECK(Math::cosh(-0.1) == doctest::Approx(1.0050041681));
+ CHECK(Math::cosh(0.1) == doctest::Approx(1.0050041681));
+ CHECK(Math::cosh(0.5) == doctest::Approx(1.1276259652));
+ CHECK(Math::cosh(1.0) == doctest::Approx(1.5430806348));
+ CHECK(Math::cosh(1.5) == doctest::Approx(2.3524096152));
+
+ CHECK(Math::tanh(-0.1) == doctest::Approx(-0.0996679946));
+ CHECK(Math::tanh(0.1) == doctest::Approx(0.0996679946));
+ CHECK(Math::tanh(0.5) == doctest::Approx(0.4621171573));
+ CHECK(Math::tanh(1.0) == doctest::Approx(0.761594156));
+ CHECK(Math::tanh(1.5) == doctest::Approx(0.9051482536));
+ CHECK(Math::tanh(450.0) == doctest::Approx(1.0));
+}
+
+TEST_CASE("[Math] asin/acos/atan") {
+ CHECK(Math::asin(-0.1) == doctest::Approx(-0.1001674212));
+ CHECK(Math::asin(0.1) == doctest::Approx(0.1001674212));
+ CHECK(Math::asin(0.5) == doctest::Approx(0.5235987756));
+ CHECK(Math::asin(1.0) == doctest::Approx(1.5707963268));
+ CHECK(Math::is_nan(Math::asin(1.5)));
+ CHECK(Math::is_nan(Math::asin(450.0)));
+
+ CHECK(Math::acos(-0.1) == doctest::Approx(1.670963748));
+ CHECK(Math::acos(0.1) == doctest::Approx(1.4706289056));
+ CHECK(Math::acos(0.5) == doctest::Approx(1.0471975512));
+ CHECK(Math::acos(1.0) == doctest::Approx(0.0));
+ CHECK(Math::is_nan(Math::acos(1.5)));
+ CHECK(Math::is_nan(Math::acos(450.0)));
+
+ CHECK(Math::atan(-0.1) == doctest::Approx(-0.0996686525));
+ CHECK(Math::atan(0.1) == doctest::Approx(0.0996686525));
+ CHECK(Math::atan(0.5) == doctest::Approx(0.463647609));
+ CHECK(Math::atan(1.0) == doctest::Approx(0.7853981634));
+ CHECK(Math::atan(1.5) == doctest::Approx(0.9827937232));
+ CHECK(Math::atan(450.0) == doctest::Approx(1.5685741082));
+}
+
+TEST_CASE("[Math] sinc/sincn/atan2") {
+ CHECK(Math::sinc(-0.1) == doctest::Approx(0.9983341665));
+ CHECK(Math::sinc(0.1) == doctest::Approx(0.9983341665));
+ CHECK(Math::sinc(0.5) == doctest::Approx(0.9588510772));
+ CHECK(Math::sinc(1.0) == doctest::Approx(0.8414709848));
+ CHECK(Math::sinc(1.5) == doctest::Approx(0.6649966577));
+ CHECK(Math::sinc(450.0) == doctest::Approx(-0.0015184083));
+
+ CHECK(Math::sincn(-0.1) == doctest::Approx(0.9836316431));
+ CHECK(Math::sincn(0.1) == doctest::Approx(0.9836316431));
+ CHECK(Math::sincn(0.5) == doctest::Approx(0.6366197724));
+ CHECK(Math::sincn(1.0) == doctest::Approx(0.0));
+ CHECK(Math::sincn(1.5) == doctest::Approx(-0.2122065908));
+ CHECK(Math::sincn(450.0) == doctest::Approx(0.0));
+
+ CHECK(Math::atan2(-0.1, 0.5) == doctest::Approx(-0.1973955598));
+ CHECK(Math::atan2(0.1, -0.5) == doctest::Approx(2.9441970937));
+ CHECK(Math::atan2(0.5, 1.5) == doctest::Approx(0.3217505544));
+ CHECK(Math::atan2(1.0, 2.5) == doctest::Approx(0.3805063771));
+ CHECK(Math::atan2(1.5, 1.0) == doctest::Approx(0.9827937232));
+ CHECK(Math::atan2(450.0, 1.0) == doctest::Approx(1.5685741082));
+}
+
+TEST_CASE("[Math] pow/log/log2/exp/sqrt") {
+ CHECK(Math::pow(-0.1, 2.0) == doctest::Approx(0.01));
+ CHECK(Math::pow(0.1, 2.5) == doctest::Approx(0.0031622777));
+ CHECK(Math::pow(0.5, 0.5) == doctest::Approx(0.7071067812));
+ CHECK(Math::pow(1.0, 1.0) == doctest::Approx(1.0));
+ CHECK(Math::pow(1.5, -1.0) == doctest::Approx(0.6666666667));
+ CHECK(Math::pow(450.0, -2.0) == doctest::Approx(0.0000049383));
+ CHECK(Math::pow(450.0, 0.0) == doctest::Approx(1.0));
+
+ CHECK(Math::is_nan(Math::log(-0.1)));
+ CHECK(Math::log(0.1) == doctest::Approx(-2.302585093));
+ CHECK(Math::log(0.5) == doctest::Approx(-0.6931471806));
+ CHECK(Math::log(1.0) == doctest::Approx(0.0));
+ CHECK(Math::log(1.5) == doctest::Approx(0.4054651081));
+ CHECK(Math::log(450.0) == doctest::Approx(6.1092475828));
+
+ CHECK(Math::is_nan(Math::log2(-0.1)));
+ CHECK(Math::log2(0.1) == doctest::Approx(-3.3219280949));
+ CHECK(Math::log2(0.5) == doctest::Approx(-1.0));
+ CHECK(Math::log2(1.0) == doctest::Approx(0.0));
+ CHECK(Math::log2(1.5) == doctest::Approx(0.5849625007));
+ CHECK(Math::log2(450.0) == doctest::Approx(8.8137811912));
+
+ CHECK(Math::exp(-0.1) == doctest::Approx(0.904837418));
+ CHECK(Math::exp(0.1) == doctest::Approx(1.1051709181));
+ CHECK(Math::exp(0.5) == doctest::Approx(1.6487212707));
+ CHECK(Math::exp(1.0) == doctest::Approx(2.7182818285));
+ CHECK(Math::exp(1.5) == doctest::Approx(4.4816890703));
+
+ CHECK(Math::is_nan(Math::sqrt(-0.1)));
+ CHECK(Math::sqrt(0.1) == doctest::Approx(0.316228));
+ CHECK(Math::sqrt(0.5) == doctest::Approx(0.707107));
+ CHECK(Math::sqrt(1.0) == doctest::Approx(1.0));
+ CHECK(Math::sqrt(1.5) == doctest::Approx(1.224745));
+}
+
+TEST_CASE("[Math] is_nan/is_inf") {
+ CHECK(!Math::is_nan(0.0));
+ CHECK(Math::is_nan(NAN));
+
+ CHECK(!Math::is_inf(0.0));
+ CHECK(Math::is_inf(INFINITY));
+}
+
+TEST_CASE("[Math] linear_to_db") {
+ CHECK(Math::linear_to_db(1.0) == doctest::Approx(0.0));
+ CHECK(Math::linear_to_db(20.0) == doctest::Approx(26.0206));
+ CHECK(Math::is_inf(Math::linear_to_db(0.0)));
+ CHECK(Math::is_nan(Math::linear_to_db(-20.0)));
+}
+
+TEST_CASE("[Math] db_to_linear") {
+ CHECK(Math::db_to_linear(0.0) == doctest::Approx(1.0));
+ CHECK(Math::db_to_linear(1.0) == doctest::Approx(1.122018));
+ CHECK(Math::db_to_linear(20.0) == doctest::Approx(10.0));
+ CHECK(Math::db_to_linear(-20.0) == doctest::Approx(0.1));
+}
+
+TEST_CASE("[Math] step_decimals") {
+ CHECK(Math::step_decimals(-0.5) == 1);
+ CHECK(Math::step_decimals(0) == 0);
+ CHECK(Math::step_decimals(1) == 0);
+ CHECK(Math::step_decimals(0.1) == 1);
+ CHECK(Math::step_decimals(0.01) == 2);
+ CHECK(Math::step_decimals(0.001) == 3);
+ CHECK(Math::step_decimals(0.0001) == 4);
+ CHECK(Math::step_decimals(0.00001) == 5);
+ CHECK(Math::step_decimals(0.000001) == 6);
+ CHECK(Math::step_decimals(0.0000001) == 7);
+ CHECK(Math::step_decimals(0.00000001) == 8);
+ CHECK(Math::step_decimals(0.000000001) == 9);
+ // Too many decimals to handle.
+ CHECK(Math::step_decimals(0.0000000001) == 0);
+}
+
+TEST_CASE("[Math] range_step_decimals") {
+ CHECK(Math::range_step_decimals(0.000000001) == 9);
+ // Too many decimals to handle.
+ CHECK(Math::range_step_decimals(0.0000000001) == 0);
+ // Should be treated as a step of 0 for use by the editor.
+ CHECK(Math::range_step_decimals(0.0) == 16);
+ CHECK(Math::range_step_decimals(-0.5) == 16);
+}
+
+TEST_CASE("[Math] lerp") {
+ CHECK(Math::lerp(2.0, 5.0, -0.1) == doctest::Approx(1.7));
+ CHECK(Math::lerp(2.0, 5.0, 0.0) == doctest::Approx(2.0));
+ CHECK(Math::lerp(2.0, 5.0, 0.1) == doctest::Approx(2.3));
+ CHECK(Math::lerp(2.0, 5.0, 1.0) == doctest::Approx(5.0));
+ CHECK(Math::lerp(2.0, 5.0, 2.0) == doctest::Approx(8.0));
+
+ CHECK(Math::lerp(-2.0, -5.0, -0.1) == doctest::Approx(-1.7));
+ CHECK(Math::lerp(-2.0, -5.0, 0.0) == doctest::Approx(-2.0));
+ CHECK(Math::lerp(-2.0, -5.0, 0.1) == doctest::Approx(-2.3));
+ CHECK(Math::lerp(-2.0, -5.0, 1.0) == doctest::Approx(-5.0));
+ CHECK(Math::lerp(-2.0, -5.0, 2.0) == doctest::Approx(-8.0));
+}
+
+TEST_CASE("[Math] inverse_lerp") {
+ CHECK(Math::inverse_lerp(2.0, 5.0, 1.7) == doctest::Approx(-0.1));
+ CHECK(Math::inverse_lerp(2.0, 5.0, 2.0) == doctest::Approx(0.0));
+ CHECK(Math::inverse_lerp(2.0, 5.0, 2.3) == doctest::Approx(0.1));
+ CHECK(Math::inverse_lerp(2.0, 5.0, 5.0) == doctest::Approx(1.0));
+ CHECK(Math::inverse_lerp(2.0, 5.0, 8.0) == doctest::Approx(2.0));
+
+ CHECK(Math::inverse_lerp(-2.0, -5.0, -1.7) == doctest::Approx(-0.1));
+ CHECK(Math::inverse_lerp(-2.0, -5.0, -2.0) == doctest::Approx(0.0));
+ CHECK(Math::inverse_lerp(-2.0, -5.0, -2.3) == doctest::Approx(0.1));
+ CHECK(Math::inverse_lerp(-2.0, -5.0, -5.0) == doctest::Approx(1.0));
+ CHECK(Math::inverse_lerp(-2.0, -5.0, -8.0) == doctest::Approx(2.0));
+}
+
+TEST_CASE("[Math] remap") {
+ CHECK(Math::remap(50.0, 100.0, 200.0, 0.0, 1000.0) == doctest::Approx(-500.0));
+ CHECK(Math::remap(100.0, 100.0, 200.0, 0.0, 1000.0) == doctest::Approx(0.0));
+ CHECK(Math::remap(200.0, 100.0, 200.0, 0.0, 1000.0) == doctest::Approx(1000.0));
+ CHECK(Math::remap(250.0, 100.0, 200.0, 0.0, 1000.0) == doctest::Approx(1500.0));
+
+ CHECK(Math::remap(-50.0, -100.0, -200.0, 0.0, 1000.0) == doctest::Approx(-500.0));
+ CHECK(Math::remap(-100.0, -100.0, -200.0, 0.0, 1000.0) == doctest::Approx(0.0));
+ CHECK(Math::remap(-200.0, -100.0, -200.0, 0.0, 1000.0) == doctest::Approx(1000.0));
+ CHECK(Math::remap(-250.0, -100.0, -200.0, 0.0, 1000.0) == doctest::Approx(1500.0));
+
+ CHECK(Math::remap(-50.0, -100.0, -200.0, 0.0, -1000.0) == doctest::Approx(500.0));
+ CHECK(Math::remap(-100.0, -100.0, -200.0, 0.0, -1000.0) == doctest::Approx(0.0));
+ CHECK(Math::remap(-200.0, -100.0, -200.0, 0.0, -1000.0) == doctest::Approx(-1000.0));
+ CHECK(Math::remap(-250.0, -100.0, -200.0, 0.0, -1000.0) == doctest::Approx(-1500.0));
+}
+
+TEST_CASE("[Math] lerp_angle") {
+ // Counter-clockwise rotation.
+ CHECK(Math::lerp_angle(0.24 * Math_TAU, 0.75 * Math_TAU, 0.5) == doctest::Approx(-0.005 * Math_TAU));
+ // Counter-clockwise rotation.
+ CHECK(Math::lerp_angle(0.25 * Math_TAU, 0.75 * Math_TAU, 0.5) == doctest::Approx(0.0));
+ // Clockwise rotation.
+ CHECK(Math::lerp_angle(0.26 * Math_TAU, 0.75 * Math_TAU, 0.5) == doctest::Approx(0.505 * Math_TAU));
+
+ CHECK(Math::lerp_angle(-0.25 * Math_TAU, 1.25 * Math_TAU, 0.5) == doctest::Approx(-0.5 * Math_TAU));
+ CHECK(Math::lerp_angle(0.72 * Math_TAU, 1.44 * Math_TAU, 0.96) == doctest::Approx(0.4512 * Math_TAU));
+ CHECK(Math::lerp_angle(0.72 * Math_TAU, 1.44 * Math_TAU, 1.04) == doctest::Approx(0.4288 * Math_TAU));
+
+ // Initial and final angles are effectively identical, so the value returned
+ // should always be the same regardless of the `weight` parameter.
+ CHECK(Math::lerp_angle(-4 * Math_TAU, 4 * Math_TAU, -1.0) == doctest::Approx(-4.0 * Math_TAU));
+ CHECK(Math::lerp_angle(-4 * Math_TAU, 4 * Math_TAU, 0.0) == doctest::Approx(-4.0 * Math_TAU));
+ CHECK(Math::lerp_angle(-4 * Math_TAU, 4 * Math_TAU, 0.5) == doctest::Approx(-4.0 * Math_TAU));
+ CHECK(Math::lerp_angle(-4 * Math_TAU, 4 * Math_TAU, 1.0) == doctest::Approx(-4.0 * Math_TAU));
+ CHECK(Math::lerp_angle(-4 * Math_TAU, 4 * Math_TAU, 500.0) == doctest::Approx(-4.0 * Math_TAU));
+}
+
+TEST_CASE("[Math] move_toward") {
+ CHECK(Math::move_toward(2.0, 5.0, -1.0) == doctest::Approx(1.0));
+ CHECK(Math::move_toward(2.0, 5.0, 2.5) == doctest::Approx(4.5));
+ CHECK(Math::move_toward(2.0, 5.0, 4.0) == doctest::Approx(5.0));
+ CHECK(Math::move_toward(-2.0, -5.0, -1.0) == doctest::Approx(-1.0));
+ CHECK(Math::move_toward(-2.0, -5.0, 2.5) == doctest::Approx(-4.5));
+ CHECK(Math::move_toward(-2.0, -5.0, 4.0) == doctest::Approx(-5.0));
+}
+
+TEST_CASE("[Math] smoothstep") {
+ CHECK(Math::smoothstep(0.0, 2.0, -5.0) == doctest::Approx(0.0));
+ CHECK(Math::smoothstep(0.0, 2.0, 0.5) == doctest::Approx(0.15625));
+ CHECK(Math::smoothstep(0.0, 2.0, 1.0) == doctest::Approx(0.5));
+ CHECK(Math::smoothstep(0.0, 2.0, 2.0) == doctest::Approx(1.0));
+}
+
+TEST_CASE("[Math] ease") {
+ CHECK(Math::ease(0.1, 1.0) == doctest::Approx(0.1));
+ CHECK(Math::ease(0.1, 2.0) == doctest::Approx(0.01));
+ CHECK(Math::ease(0.1, 0.5) == doctest::Approx(0.19));
+ CHECK(Math::ease(0.1, 0.0) == doctest::Approx(0));
+ CHECK(Math::ease(0.1, -0.5) == doctest::Approx(0.2236067977));
+ CHECK(Math::ease(0.1, -1.0) == doctest::Approx(0.1));
+ CHECK(Math::ease(0.1, -2.0) == doctest::Approx(0.02));
+
+ CHECK(Math::ease(-1.0, 1.0) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, 2.0) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, 0.5) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, 0.0) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, -0.5) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, -1.0) == doctest::Approx(0));
+ CHECK(Math::ease(-1.0, -2.0) == doctest::Approx(0));
+}
+
+TEST_CASE("[Math] snapped") {
+ CHECK(Math::snapped(0.5, 0.04) == doctest::Approx(0.52));
+ CHECK(Math::snapped(-0.5, 0.04) == doctest::Approx(-0.48));
+ CHECK(Math::snapped(0.0, 0.04) == doctest::Approx(0));
+ CHECK(Math::snapped(128'000.025, 0.04) == doctest::Approx(128'000.04));
+
+ CHECK(Math::snapped(0.5, 400) == doctest::Approx(0));
+ CHECK(Math::snapped(-0.5, 400) == doctest::Approx(0));
+ CHECK(Math::snapped(0.0, 400) == doctest::Approx(0));
+ CHECK(Math::snapped(128'000.025, 400) == doctest::Approx(128'000.0));
+
+ CHECK(Math::snapped(0.5, 0.0) == doctest::Approx(0.5));
+ CHECK(Math::snapped(-0.5, 0.0) == doctest::Approx(-0.5));
+ CHECK(Math::snapped(0.0, 0.0) == doctest::Approx(0.0));
+ CHECK(Math::snapped(128'000.025, 0.0) == doctest::Approx(128'000.0));
+
+ CHECK(Math::snapped(0.5, -1.0) == doctest::Approx(0));
+ CHECK(Math::snapped(-0.5, -1.0) == doctest::Approx(-1.0));
+ CHECK(Math::snapped(0.0, -1.0) == doctest::Approx(0));
+ CHECK(Math::snapped(128'000.025, -1.0) == doctest::Approx(128'000.0));
+}
+
+TEST_CASE("[Math] larger_prime") {
+ CHECK(Math::larger_prime(0) == 5);
+ CHECK(Math::larger_prime(1) == 5);
+ CHECK(Math::larger_prime(2) == 5);
+ CHECK(Math::larger_prime(5) == 13);
+ CHECK(Math::larger_prime(500) == 769);
+ CHECK(Math::larger_prime(1'000'000) == 1'572'869);
+ CHECK(Math::larger_prime(1'000'000'000) == 1'610'612'741);
+
+ // The next prime is larger than `INT32_MAX` and is not present in the built-in prime table.
+ ERR_PRINT_OFF;
+ CHECK(Math::larger_prime(2'000'000'000) == 0);
+ ERR_PRINT_ON;
+}
+
+TEST_CASE("[Math] fmod") {
+ CHECK(Math::fmod(-2.0, 0.3) == doctest::Approx(-0.2));
+ CHECK(Math::fmod(0.0, 0.3) == doctest::Approx(0.0));
+ CHECK(Math::fmod(2.0, 0.3) == doctest::Approx(0.2));
+
+ CHECK(Math::fmod(-2.0, -0.3) == doctest::Approx(-0.2));
+ CHECK(Math::fmod(0.0, -0.3) == doctest::Approx(0.0));
+ CHECK(Math::fmod(2.0, -0.3) == doctest::Approx(0.2));
+}
+
+TEST_CASE("[Math] fposmod") {
+ CHECK(Math::fposmod(-2.0, 0.3) == doctest::Approx(0.1));
+ CHECK(Math::fposmod(0.0, 0.3) == doctest::Approx(0.0));
+ CHECK(Math::fposmod(2.0, 0.3) == doctest::Approx(0.2));
+
+ CHECK(Math::fposmod(-2.0, -0.3) == doctest::Approx(-0.2));
+ CHECK(Math::fposmod(0.0, -0.3) == doctest::Approx(0.0));
+ CHECK(Math::fposmod(2.0, -0.3) == doctest::Approx(-0.1));
+}
+
+TEST_CASE("[Math] fposmodp") {
+ CHECK(Math::fposmodp(-2.0, 0.3) == doctest::Approx(0.1));
+ CHECK(Math::fposmodp(0.0, 0.3) == doctest::Approx(0.0));
+ CHECK(Math::fposmodp(2.0, 0.3) == doctest::Approx(0.2));
+
+ CHECK(Math::fposmodp(-2.0, -0.3) == doctest::Approx(-0.5));
+ CHECK(Math::fposmodp(0.0, -0.3) == doctest::Approx(0.0));
+ CHECK(Math::fposmodp(2.0, -0.3) == doctest::Approx(0.2));
+}
+
+TEST_CASE("[Math] posmod") {
+ CHECK(Math::posmod(-20, 3) == 1);
+ CHECK(Math::posmod(0, 3) == 0);
+ CHECK(Math::posmod(20, 3) == 2);
+ CHECK(Math::posmod(-20, -3) == -2);
+ CHECK(Math::posmod(0, -3) == 0);
+ CHECK(Math::posmod(20, -3) == -1);
+}
+
+TEST_CASE("[Math] wrapi") {
+ CHECK(Math::wrapi(-30, -20, 160) == 150);
+ CHECK(Math::wrapi(30, -20, 160) == 30);
+ CHECK(Math::wrapi(300, -20, 160) == 120);
+ CHECK(Math::wrapi(300'000'000'000, -20, 160) == 120);
+}
+
+TEST_CASE("[Math] wrapf") {
+ CHECK(Math::wrapf(-30.0, -20.0, 160.0) == doctest::Approx(150.0));
+ CHECK(Math::wrapf(30.0, -2.0, 160.0) == doctest::Approx(30.0));
+ CHECK(Math::wrapf(300.0, -20.0, 160.0) == doctest::Approx(120.0));
+ CHECK(Math::wrapf(300'000'000'000.0, -20.0, 160.0) == doctest::Approx(120.0));
+}
+
+TEST_CASE("[Math] fract") {
+ CHECK(Math::fract(1.0) == doctest::Approx(0.0));
+ CHECK(Math::fract(77.8) == doctest::Approx(0.8));
+ CHECK(Math::fract(-10.1) == doctest::Approx(0.9));
+}
+
+TEST_CASE("[Math] pingpong") {
+ CHECK(Math::pingpong(0.0, 0.0) == doctest::Approx(0.0));
+ CHECK(Math::pingpong(1.0, 1.0) == doctest::Approx(1.0));
+ CHECK(Math::pingpong(0.5, 2.0) == doctest::Approx(0.5));
+ CHECK(Math::pingpong(3.5, 2.0) == doctest::Approx(0.5));
+ CHECK(Math::pingpong(11.5, 2.0) == doctest::Approx(0.5));
+ CHECK(Math::pingpong(-2.5, 2.0) == doctest::Approx(1.5));
+}
+
+TEST_CASE("[Math] deg_to_rad/rad_to_deg") {
+ CHECK(Math::deg_to_rad(180.0) == doctest::Approx(Math_PI));
+ CHECK(Math::deg_to_rad(-27.0) == doctest::Approx(-0.471239));
+
+ CHECK(Math::rad_to_deg(Math_PI) == doctest::Approx(180.0));
+ CHECK(Math::rad_to_deg(-1.5) == doctest::Approx(-85.94366927));
+}
+
+TEST_CASE("[Math] cubic_interpolate") {
+ CHECK(Math::cubic_interpolate(0.2, 0.8, 0.0, 1.0, 0.0) == doctest::Approx(0.2));
+ CHECK(Math::cubic_interpolate(0.2, 0.8, 0.0, 1.0, 0.25) == doctest::Approx(0.33125));
+ CHECK(Math::cubic_interpolate(0.2, 0.8, 0.0, 1.0, 0.5) == doctest::Approx(0.5));
+ CHECK(Math::cubic_interpolate(0.2, 0.8, 0.0, 1.0, 0.75) == doctest::Approx(0.66875));
+ CHECK(Math::cubic_interpolate(0.2, 0.8, 0.0, 1.0, 1.0) == doctest::Approx(0.8));
+
+ CHECK(Math::cubic_interpolate(20.2, 30.1, -100.0, 32.0, -50.0) == doctest::Approx(-6662732.3));
+ CHECK(Math::cubic_interpolate(20.2, 30.1, -100.0, 32.0, -5.0) == doctest::Approx(-9356.3));
+ CHECK(Math::cubic_interpolate(20.2, 30.1, -100.0, 32.0, 0.0) == doctest::Approx(20.2));
+ CHECK(Math::cubic_interpolate(20.2, 30.1, -100.0, 32.0, 1.0) == doctest::Approx(30.1));
+ CHECK(Math::cubic_interpolate(20.2, 30.1, -100.0, 32.0, 4.0) == doctest::Approx(1853.2));
+}
+
+TEST_CASE("[Math] cubic_interpolate_angle") {
+ CHECK(Math::cubic_interpolate_angle(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.0) == doctest::Approx(Math_PI * (1.0 / 6.0)));
+ CHECK(Math::cubic_interpolate_angle(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.25) == doctest::Approx(0.973566));
+ CHECK(Math::cubic_interpolate_angle(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.5) == doctest::Approx(Math_PI / 2.0));
+ CHECK(Math::cubic_interpolate_angle(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.75) == doctest::Approx(2.16803));
+ CHECK(Math::cubic_interpolate_angle(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 1.0) == doctest::Approx(Math_PI * (5.0 / 6.0)));
+}
+
+TEST_CASE("[Math] cubic_interpolate_in_time") {
+ CHECK(Math::cubic_interpolate_in_time(0.2, 0.8, 0.0, 1.0, 0.0, 0.5, 0.0, 1.0) == doctest::Approx(0.0));
+ CHECK(Math::cubic_interpolate_in_time(0.2, 0.8, 0.0, 1.0, 0.25, 0.5, 0.0, 1.0) == doctest::Approx(0.1625));
+ CHECK(Math::cubic_interpolate_in_time(0.2, 0.8, 0.0, 1.0, 0.5, 0.5, 0.0, 1.0) == doctest::Approx(0.4));
+ CHECK(Math::cubic_interpolate_in_time(0.2, 0.8, 0.0, 1.0, 0.75, 0.5, 0.0, 1.0) == doctest::Approx(0.6375));
+ CHECK(Math::cubic_interpolate_in_time(0.2, 0.8, 0.0, 1.0, 1.0, 0.5, 0.0, 1.0) == doctest::Approx(0.8));
+}
+
+TEST_CASE("[Math] cubic_interpolate_angle_in_time") {
+ CHECK(Math::cubic_interpolate_angle_in_time(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.0, 0.5, 0.0, 1.0) == doctest::Approx(0.0));
+ CHECK(Math::cubic_interpolate_angle_in_time(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.25, 0.5, 0.0, 1.0) == doctest::Approx(0.494964));
+ CHECK(Math::cubic_interpolate_angle_in_time(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.5, 0.5, 0.0, 1.0) == doctest::Approx(1.27627));
+ CHECK(Math::cubic_interpolate_angle_in_time(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 0.75, 0.5, 0.0, 1.0) == doctest::Approx(2.07394));
+ CHECK(Math::cubic_interpolate_angle_in_time(Math_PI * (1.0 / 6.0), Math_PI * (5.0 / 6.0), 0.0, Math_PI, 1.0, 0.5, 0.0, 1.0) == doctest::Approx(Math_PI * (5.0 / 6.0)));
+}
+
+TEST_CASE("[Math] bezier_interpolate") {
+ CHECK(Math::bezier_interpolate(0.0, 0.2, 0.8, 1.0, 0.0) == doctest::Approx(0.0));
+ CHECK(Math::bezier_interpolate(0.0, 0.2, 0.8, 1.0, 0.25) == doctest::Approx(0.2125));
+ CHECK(Math::bezier_interpolate(0.0, 0.2, 0.8, 1.0, 0.5) == doctest::Approx(0.5));
+ CHECK(Math::bezier_interpolate(0.0, 0.2, 0.8, 1.0, 0.75) == doctest::Approx(0.7875));
+ CHECK(Math::bezier_interpolate(0.0, 0.2, 0.8, 1.0, 1.0) == doctest::Approx(1.0));
+}
+
+} // namespace TestMath
+
+#endif // TEST_MATH_FUNCS_H
diff --git a/tests/core/math/test_quaternion.h b/tests/core/math/test_quaternion.h
index eb5fea1d2d..c3ae322991 100644
--- a/tests/core/math/test_quaternion.h
+++ b/tests/core/math/test_quaternion.h
@@ -47,9 +47,9 @@ Quaternion quat_euler_yxz_deg(Vector3 angle) {
// Generate YXZ (Z-then-X-then-Y) Quaternion using single-axis Euler
// constructor and quaternion product, both tested separately.
- Quaternion q_y(Vector3(0.0, yaw, 0.0));
- Quaternion q_p(Vector3(pitch, 0.0, 0.0));
- Quaternion q_r(Vector3(0.0, 0.0, roll));
+ Quaternion q_y = Quaternion::from_euler(Vector3(0.0, yaw, 0.0));
+ Quaternion q_p = Quaternion::from_euler(Vector3(pitch, 0.0, 0.0));
+ Quaternion q_r = Quaternion::from_euler(Vector3(0.0, 0.0, roll));
// Roll-Z is followed by Pitch-X, then Yaw-Y.
Quaternion q_yxz = q_y * q_p * q_r;
@@ -134,21 +134,21 @@ TEST_CASE("[Quaternion] Construct Euler SingleAxis") {
double roll = Math::deg_to_rad(10.0);
Vector3 euler_y(0.0, yaw, 0.0);
- Quaternion q_y(euler_y);
+ Quaternion q_y = Quaternion::from_euler(euler_y);
CHECK(q_y[0] == doctest::Approx(0.0));
CHECK(q_y[1] == doctest::Approx(0.382684));
CHECK(q_y[2] == doctest::Approx(0.0));
CHECK(q_y[3] == doctest::Approx(0.923879));
Vector3 euler_p(pitch, 0.0, 0.0);
- Quaternion q_p(euler_p);
+ Quaternion q_p = Quaternion::from_euler(euler_p);
CHECK(q_p[0] == doctest::Approx(0.258819));
CHECK(q_p[1] == doctest::Approx(0.0));
CHECK(q_p[2] == doctest::Approx(0.0));
CHECK(q_p[3] == doctest::Approx(0.965926));
Vector3 euler_r(0.0, 0.0, roll);
- Quaternion q_r(euler_r);
+ Quaternion q_r = Quaternion::from_euler(euler_r);
CHECK(q_r[0] == doctest::Approx(0.0));
CHECK(q_r[1] == doctest::Approx(0.0));
CHECK(q_r[2] == doctest::Approx(0.0871558));
@@ -163,27 +163,27 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") {
// Generate YXZ comparison data (Z-then-X-then-Y) using single-axis Euler
// constructor and quaternion product, both tested separately.
Vector3 euler_y(0.0, yaw, 0.0);
- Quaternion q_y(euler_y);
+ Quaternion q_y = Quaternion::from_euler(euler_y);
Vector3 euler_p(pitch, 0.0, 0.0);
- Quaternion q_p(euler_p);
+ Quaternion q_p = Quaternion::from_euler(euler_p);
Vector3 euler_r(0.0, 0.0, roll);
- Quaternion q_r(euler_r);
+ Quaternion q_r = Quaternion::from_euler(euler_r);
- // Roll-Z is followed by Pitch-X.
- Quaternion check_xz = q_p * q_r;
- // Then Yaw-Y follows both.
- Quaternion check_yxz = q_y * check_xz;
+ // Instrinsically, Yaw-Y then Pitch-X then Roll-Z.
+ // Extrinsically, Roll-Z is followed by Pitch-X, then Yaw-Y.
+ Quaternion check_yxz = q_y * q_p * q_r;
// Test construction from YXZ Euler angles.
Vector3 euler_yxz(pitch, yaw, roll);
- Quaternion q(euler_yxz);
+ Quaternion q = Quaternion::from_euler(euler_yxz);
CHECK(q[0] == doctest::Approx(check_yxz[0]));
CHECK(q[1] == doctest::Approx(check_yxz[1]));
CHECK(q[2] == doctest::Approx(check_yxz[2]));
CHECK(q[3] == doctest::Approx(check_yxz[3]));
- // Sneak in a test of is_equal_approx.
CHECK(q.is_equal_approx(check_yxz));
+ CHECK(q.get_euler().is_equal_approx(euler_yxz));
+ CHECK(check_yxz.get_euler().is_equal_approx(euler_yxz));
}
TEST_CASE("[Quaternion] Construct Basis Euler") {
@@ -191,7 +191,7 @@ TEST_CASE("[Quaternion] Construct Basis Euler") {
double pitch = Math::deg_to_rad(30.0);
double roll = Math::deg_to_rad(10.0);
Vector3 euler_yxz(pitch, yaw, roll);
- Quaternion q_yxz(euler_yxz);
+ Quaternion q_yxz = Quaternion::from_euler(euler_yxz);
Basis basis_axes = Basis::from_euler(euler_yxz);
Quaternion q(basis_axes);
CHECK(q.is_equal_approx(q_yxz));
@@ -209,7 +209,7 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
// Quaternion from local calculation.
Quaternion q_local = quat_euler_yxz_deg(Vector3(31.41, -49.16, 12.34));
// Quaternion from Euler angles constructor.
- Quaternion q_euler(euler_yxz);
+ Quaternion q_euler = Quaternion::from_euler(euler_yxz);
CHECK(q_calc.is_equal_approx(q_local));
CHECK(q_local.is_equal_approx(q_euler));
@@ -235,6 +235,23 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
CHECK(q[3] == doctest::Approx(0.8582598));
}
+TEST_CASE("[Quaternion] Get Euler Orders") {
+ double x = Math::deg_to_rad(30.0);
+ double y = Math::deg_to_rad(45.0);
+ double z = Math::deg_to_rad(10.0);
+ Vector3 euler(x, y, z);
+ for (int i = 0; i < 6; i++) {
+ EulerOrder order = (EulerOrder)i;
+ Basis basis = Basis::from_euler(euler, order);
+ Quaternion q = Quaternion(basis);
+ Vector3 check = q.get_euler(order);
+ CHECK_MESSAGE(check.is_equal_approx(euler),
+ "Quaternion get_euler method should return the original angles.");
+ CHECK_MESSAGE(check.is_equal_approx(basis.get_euler(order)),
+ "Quaternion get_euler method should behave the same as Basis get_euler.");
+ }
+}
+
TEST_CASE("[Quaternion] Product (book)") {
// Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108.
Quaternion p(1.0, -2.0, 1.0, 3.0);
@@ -253,21 +270,21 @@ TEST_CASE("[Quaternion] Product") {
double roll = Math::deg_to_rad(10.0);
Vector3 euler_y(0.0, yaw, 0.0);
- Quaternion q_y(euler_y);
+ Quaternion q_y = Quaternion::from_euler(euler_y);
CHECK(q_y[0] == doctest::Approx(0.0));
CHECK(q_y[1] == doctest::Approx(0.382684));
CHECK(q_y[2] == doctest::Approx(0.0));
CHECK(q_y[3] == doctest::Approx(0.923879));
Vector3 euler_p(pitch, 0.0, 0.0);
- Quaternion q_p(euler_p);
+ Quaternion q_p = Quaternion::from_euler(euler_p);
CHECK(q_p[0] == doctest::Approx(0.258819));
CHECK(q_p[1] == doctest::Approx(0.0));
CHECK(q_p[2] == doctest::Approx(0.0));
CHECK(q_p[3] == doctest::Approx(0.965926));
Vector3 euler_r(0.0, 0.0, roll);
- Quaternion q_r(euler_r);
+ Quaternion q_r = Quaternion::from_euler(euler_r);
CHECK(q_r[0] == doctest::Approx(0.0));
CHECK(q_r[1] == doctest::Approx(0.0));
CHECK(q_r[2] == doctest::Approx(0.0871558));
diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h
index a87b9ffc02..f7e9259329 100644
--- a/tests/core/math/test_vector2.h
+++ b/tests/core/math/test_vector2.h
@@ -382,13 +382,13 @@ TEST_CASE("[Vector2] Plane methods") {
ERR_PRINT_OFF;
CHECK_MESSAGE(
vector.bounce(vector_non_normal).is_equal_approx(Vector2()),
- "Vector2 bounce should return empty Vector2 with non-normalised input.");
+ "Vector2 bounce should return empty Vector2 with non-normalized input.");
CHECK_MESSAGE(
vector.reflect(vector_non_normal).is_equal_approx(Vector2()),
- "Vector2 reflect should return empty Vector2 with non-normalised input.");
+ "Vector2 reflect should return empty Vector2 with non-normalized input.");
CHECK_MESSAGE(
vector.slide(vector_non_normal).is_equal_approx(Vector2()),
- "Vector2 slide should return empty Vector2 with non-normalised input.");
+ "Vector2 slide should return empty Vector2 with non-normalized input.");
ERR_PRINT_ON;
}
diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h
index 4932cd04db..77d3a9d93c 100644
--- a/tests/core/math/test_vector3.h
+++ b/tests/core/math/test_vector3.h
@@ -389,13 +389,13 @@ TEST_CASE("[Vector3] Plane methods") {
ERR_PRINT_OFF;
CHECK_MESSAGE(
vector.bounce(vector_non_normal).is_equal_approx(Vector3()),
- "Vector3 bounce should return empty Vector3 with non-normalised input.");
+ "Vector3 bounce should return empty Vector3 with non-normalized input.");
CHECK_MESSAGE(
vector.reflect(vector_non_normal).is_equal_approx(Vector3()),
- "Vector3 reflect should return empty Vector3 with non-normalised input.");
+ "Vector3 reflect should return empty Vector3 with non-normalized input.");
CHECK_MESSAGE(
vector.slide(vector_non_normal).is_equal_approx(Vector3()),
- "Vector3 slide should return empty Vector3 with non-normalised input.");
+ "Vector3 slide should return empty Vector3 with non-normalized input.");
ERR_PRINT_ON;
}
diff --git a/tests/scene/test_animation.h b/tests/scene/test_animation.h
index 9199713fd9..ca80f0ecab 100644
--- a/tests/scene/test_animation.h
+++ b/tests/scene/test_animation.h
@@ -140,13 +140,13 @@ TEST_CASE("[Animation] Create 3D rotation track") {
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_ROTATION_3D);
animation->track_set_path(track_index, NodePath("Enemy:rotation"));
- animation->rotation_track_insert_key(track_index, 0.0, Quaternion(Vector3(0, 1, 2)));
- animation->rotation_track_insert_key(track_index, 0.5, Quaternion(Vector3(3.5, 4, 5)));
+ animation->rotation_track_insert_key(track_index, 0.0, Quaternion::from_euler(Vector3(0, 1, 2)));
+ animation->rotation_track_insert_key(track_index, 0.5, Quaternion::from_euler(Vector3(3.5, 4, 5)));
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
- CHECK(Quaternion(animation->track_get_key_value(0, 0)).is_equal_approx(Quaternion(Vector3(0, 1, 2))));
- CHECK(Quaternion(animation->track_get_key_value(0, 1)).is_equal_approx(Quaternion(Vector3(3.5, 4, 5))));
+ CHECK(Quaternion(animation->track_get_key_value(0, 0)).is_equal_approx(Quaternion::from_euler(Vector3(0, 1, 2))));
+ CHECK(Quaternion(animation->track_get_key_value(0, 1)).is_equal_approx(Quaternion::from_euler(Vector3(3.5, 4, 5))));
Quaternion r_interpolation;
diff --git a/tests/scene/test_bit_map.h b/tests/scene/test_bit_map.h
index a102f40725..aca7e5fe22 100644
--- a/tests/scene/test_bit_map.h
+++ b/tests/scene/test_bit_map.h
@@ -234,7 +234,7 @@ TEST_CASE("[BitMap] Resize") {
TEST_CASE("[BitMap] Grow and shrink mask") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
- bit_map.grow_mask(100, Rect2i(0, 0, 128, 128)); // Check if method does not crash when working with an uninitialised bit map.
+ bit_map.grow_mask(100, Rect2i(0, 0, 128, 128)); // Check if method does not crash when working with an uninitialized bit map.
CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Size should still be equal to 0x0");
bit_map.create(dim);
@@ -335,21 +335,21 @@ TEST_CASE("[BitMap] Blit") {
blit_bit_map.instantiate();
- // Testing if uninitialised blit bit map and uninitialised bit map does not crash
+ // Testing if uninitialized blit bit map and uninitialized bit map does not crash
bit_map.blit(blit_pos, blit_bit_map);
- // Testing if uninitialised bit map does not crash
+ // Testing if uninitialized bit map does not crash
blit_bit_map->create(blit_size);
bit_map.blit(blit_pos, blit_bit_map);
- // Testing if uninitialised bit map does not crash
+ // Testing if uninitialized bit map does not crash
blit_bit_map.unref();
blit_bit_map.instantiate();
CHECK_MESSAGE(blit_bit_map->get_size() == Point2i(0, 0), "Size should be cleared by unref and instance calls.");
bit_map.create(bit_map_size);
bit_map.blit(Point2i(128, 128), blit_bit_map);
- // Testing if both initialised does not crash.
+ // Testing if both initialized does not crash.
blit_bit_map->create(blit_size);
bit_map.blit(blit_pos, blit_bit_map);
diff --git a/tests/scene/test_path_2d.h b/tests/scene/test_path_2d.h
new file mode 100644
index 0000000000..dc5eee4012
--- /dev/null
+++ b/tests/scene/test_path_2d.h
@@ -0,0 +1,109 @@
+/*************************************************************************/
+/* test_path_2d.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_PATH_2D_H
+#define TEST_PATH_2D_H
+
+#include "scene/2d/path_2d.h"
+
+#include "tests/test_macros.h"
+
+namespace TestPath2D {
+
+TEST_CASE("[SceneTree][Path2D] Initialization") {
+ SUBCASE("Path should be empty right after initialization") {
+ Path2D *test_path = memnew(Path2D);
+ CHECK(test_path->get_curve() == nullptr);
+ memdelete(test_path);
+ }
+}
+
+TEST_CASE("[SceneTree][Path2D] Curve setter and getter") {
+ Path2D *test_path = memnew(Path2D);
+ const Ref<Curve2D> &curve = memnew(Curve2D);
+
+ SUBCASE("Curve passed to the class should remain the same") {
+ test_path->set_curve(curve);
+ CHECK(test_path->get_curve() == curve);
+ }
+ SUBCASE("Curve passed many times to the class should remain the same") {
+ test_path->set_curve(curve);
+ test_path->set_curve(curve);
+ test_path->set_curve(curve);
+ CHECK(test_path->get_curve() == curve);
+ }
+ SUBCASE("Curve rewrite testing") {
+ const Ref<Curve2D> &curve1 = memnew(Curve2D);
+ const Ref<Curve2D> &curve2 = memnew(Curve2D);
+
+ test_path->set_curve(curve1);
+ test_path->set_curve(curve2);
+ CHECK_MESSAGE(test_path->get_curve() != curve1,
+ "After rewrite, second curve should be in class");
+ CHECK_MESSAGE(test_path->get_curve() == curve2,
+ "After rewrite, second curve should be in class");
+ }
+
+ SUBCASE("Assign same curve to two paths") {
+ Path2D *path2 = memnew(Path2D);
+
+ test_path->set_curve(curve);
+ path2->set_curve(curve);
+ CHECK_MESSAGE(test_path->get_curve() == path2->get_curve(),
+ "Both paths have the same curve.");
+ memdelete(path2);
+ }
+
+ SUBCASE("Swapping curves between two paths") {
+ Path2D *path2 = memnew(Path2D);
+ const Ref<Curve2D> &curve1 = memnew(Curve2D);
+ const Ref<Curve2D> &curve2 = memnew(Curve2D);
+
+ test_path->set_curve(curve1);
+ path2->set_curve(curve2);
+ CHECK(test_path->get_curve() == curve1);
+ CHECK(path2->get_curve() == curve2);
+
+ // Do the swap
+ Ref<Curve2D> temp = test_path->get_curve();
+ test_path->set_curve(path2->get_curve());
+ path2->set_curve(temp);
+
+ CHECK(test_path->get_curve() == curve2);
+ CHECK(path2->get_curve() == curve1);
+ memdelete(path2);
+ }
+
+ memdelete(test_path);
+}
+
+} // namespace TestPath2D
+
+#endif // TEST_PATH_2D_H
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
new file mode 100644
index 0000000000..ceec117700
--- /dev/null
+++ b/tests/scene/test_primitives.h
@@ -0,0 +1,850 @@
+/*************************************************************************/
+/* test_primitives.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_PRIMITIVES_H
+#define TEST_PRIMITIVES_H
+
+#include "scene/resources/primitive_meshes.h"
+
+#include "tests/test_macros.h"
+
+namespace TestPrimitives {
+
+TEST_CASE("[SceneTree][Primitive][Capsule] Capsule Primitive") {
+ Ref<CapsuleMesh> capsule = memnew(CapsuleMesh);
+
+ SUBCASE("[SceneTree][Primitive][Capsule] Default values should be valid") {
+ CHECK_MESSAGE(capsule->get_radius() > 0,
+ "Radius of default capsule positive.");
+ CHECK_MESSAGE(capsule->get_height() > 0,
+ "Height of default capsule positive.");
+ CHECK_MESSAGE(capsule->get_radial_segments() >= 0,
+ "Radius Segments of default capsule positive.");
+ CHECK_MESSAGE(capsule->get_rings() >= 0,
+ "Number of rings of default capsule positive.");
+ }
+
+ SUBCASE("[SceneTree][Primitive][Capsule] Set properties of the capsule and get them with accessor methods") {
+ capsule->set_height(7.1f);
+ capsule->set_radius(1.3f);
+ capsule->set_radial_segments(16);
+ capsule->set_rings(32);
+
+ CHECK_MESSAGE(Math::is_equal_approx(capsule->get_radius(), 1.3f),
+ "Get/Set radius work with one set.");
+ CHECK_MESSAGE(Math::is_equal_approx(capsule->get_height(), 7.1f),
+ "Get/Set radius work with one set.");
+ CHECK_MESSAGE(capsule->get_radial_segments() == 16,
+ "Get/Set radius work with one set.");
+ CHECK_MESSAGE(capsule->get_rings() == 32,
+ "Get/Set radius work with one set.");
+ }
+
+ SUBCASE("[SceneTree][Primitive][Capsule] If set segments negative, default to at least 0") {
+ capsule->set_radial_segments(-5);
+ capsule->set_rings(-17);
+
+ CHECK_MESSAGE(capsule->get_radial_segments() >= 0,
+ "Ensure number of radial segments is >= 0.");
+ CHECK_MESSAGE(capsule->get_rings() >= 0,
+ "Ensure number of rings is >= 0.");
+ }
+
+ SUBCASE("[SceneTree][Primitive][Capsule] If set height < 2*radius, adjust radius and height to radius=height*0.5") {
+ capsule->set_radius(1.f);
+ capsule->set_height(0.5f);
+
+ CHECK_MESSAGE(capsule->get_radius() >= capsule->get_height() * 0.5,
+ "Ensure radius >= height * 0.5 (needed for capsule to exist).");
+ }
+
+ SUBCASE("[Primitive][Capsule] Check mesh is correct") {
+ Array data{};
+ data.resize(RS::ARRAY_MAX);
+ float radius{ 0.5f };
+ float height{ 4.f };
+ int num_radial_segments{ 4 };
+ int num_rings{ 8 };
+ CapsuleMesh::create_mesh_array(data, radius, height, num_radial_segments, num_rings);
+ Vector<Vector3> points = data[RS::ARRAY_VERTEX];
+
+ SUBCASE("[Primitive][Capsule] Ensure all vertices positions are within bounding radius and height") {
+ // Get mesh data
+
+ // Check all points within radius of capsule
+ float dist_to_yaxis = 0.f;
+ for (Vector3 point : points) {
+ float new_dist_to_y = point.x * point.x + point.z * point.z;
+ if (new_dist_to_y > dist_to_yaxis)
+ dist_to_yaxis = new_dist_to_y;
+ }
+
+ CHECK(dist_to_yaxis <= radius * radius);
+
+ // Check highest point and lowest point are within height of each other
+ float max_y{ 0.f };
+ float min_y{ 0.f };
+ for (Vector3 point : points) {
+ if (point.y > max_y)
+ max_y = point.y;
+ if (point.y < min_y)
+ min_y = point.y;
+ }
+
+ CHECK(max_y - min_y <= height);
+ }
+
+ SUBCASE("[Primitive][Capsule] If normal.y == 0, then mesh makes a cylinder.") {
+ Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
+ for (int ii = 0; ii < points.size(); ++ii) {
+ float point_dist_from_yaxis = Math::sqrt(points[ii].x * points[ii].x + points[ii].z * points[ii].z);
+ Vector3 yaxis_to_point{ points[ii].x / point_dist_from_yaxis, 0.f, points[ii].z / point_dist_from_yaxis };
+ if (normals[ii].y == 0.f) {
+ float mag_of_normal = Math::sqrt(normals[ii].x * normals[ii].x + normals[ii].z * normals[ii].z);
+ Vector3 normalized_normal = normals[ii] / mag_of_normal;
+ CHECK_MESSAGE(Math::is_equal_approx(point_dist_from_yaxis, radius),
+ "Points on the tube of the capsule are radius away from y-axis.");
+ CHECK_MESSAGE(normalized_normal.is_equal_approx(yaxis_to_point),
+ "Normal points orthogonal from mid cylinder.");
+ }
+ }
+ }
+ }
+} // End capsule tests
+
+TEST_CASE("[SceneTree][Primitive][Box] Box Primitive") {
+ Ref<BoxMesh> box = memnew(BoxMesh);
+
+ SUBCASE("[SceneTree][Primitive][Box] Default values should be valid") {
+ CHECK(box->get_size().x > 0);
+ CHECK(box->get_size().y > 0);
+ CHECK(box->get_size().z > 0);
+ CHECK(box->get_subdivide_width() >= 0);
+ CHECK(box->get_subdivide_height() >= 0);
+ CHECK(box->get_subdivide_depth() >= 0);
+ }
+
+ SUBCASE("[SceneTree][Primitive][Box] Set properties and get them with accessor methods") {
+ Vector3 size{ 2.1, 3.3, 1.7 };
+ box->set_size(size);
+ box->set_subdivide_width(3);
+ box->set_subdivide_height(2);
+ box->set_subdivide_depth(4);
+
+ CHECK(box->get_size().is_equal_approx(size));
+ CHECK(box->get_subdivide_width() == 3);
+ CHECK(box->get_subdivide_height() == 2);
+ CHECK(box->get_subdivide_depth() == 4);
+ }
+
+ SUBCASE("[SceneTree][Primitive][Box] Set subdivides to negative and ensure they are >= 0") {
+ box->set_subdivide_width(-2);
+ box->set_subdivide_height(-2);
+ box->set_subdivide_depth(-2);
+
+ CHECK(box->get_subdivide_width() >= 0);
+ CHECK(box->get_subdivide_height() >= 0);
+ CHECK(box->get_subdivide_depth() >= 0);
+ }
+
+ SUBCASE("[Primitive][Box] Check mesh is correct.") {
+ Array data{};
+ data.resize(RS::ARRAY_MAX);
+ Vector3 size{ 0.5f, 1.2f, .9f };
+ int subdivide_width{ 3 };
+ int subdivide_height{ 2 };
+ int subdivide_depth{ 8 };
+ BoxMesh::create_mesh_array(data, size, subdivide_width, subdivide_height, subdivide_depth);
+ Vector<Vector3> points = data[RS::ARRAY_VERTEX];
+ Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
+
+ SUBCASE("Only 6 distinct normals.") {
+ Vector<Vector3> distinct_normals{};
+ distinct_normals.push_back(normals[0]);
+
+ for (const Vector3 &normal : normals) {
+ bool add_normal{ true };
+ for (const Vector3 &vec : distinct_normals) {
+ if (vec.is_equal_approx(normal))
+ add_normal = false;
+ }
+
+ if (add_normal)
+ distinct_normals.push_back(normal);
+ }
+
+ CHECK_MESSAGE(distinct_normals.size() == 6,
+ "There are exactly 6 distinct normals in the mesh data.");
+
+ // All normals are orthogonal, or pointing in same direction.
+ bool normal_correct_direction{ true };
+ for (int rowIndex = 0; rowIndex < distinct_normals.size(); ++rowIndex) {
+ for (int colIndex = rowIndex + 1; colIndex < distinct_normals.size(); ++colIndex) {
+ if (!Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), 0) &&
+ !Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), 1) &&
+ !Math::is_equal_approx(distinct_normals[rowIndex].normalized().dot(distinct_normals[colIndex].normalized()), -1)) {
+ normal_correct_direction = false;
+ break;
+ }
+ }
+ if (!normal_correct_direction)
+ break;
+ }
+
+ CHECK_MESSAGE(normal_correct_direction,
+ "All normals are either orthogonal or colinear.");
+ }
+ }
+} // End box tests
+
+TEST_CASE("[SceneTree][Primitive][Cylinder] Cylinder Primitive") {
+ Ref<CylinderMesh> cylinder = memnew(CylinderMesh);
+
+ SUBCASE("[SceneTree][Primitive][Cylinder] Default values should be valid") {
+ CHECK(cylinder->get_top_radius() > 0);
+ CHECK(cylinder->get_bottom_radius() > 0);
+ CHECK(cylinder->get_height() > 0);
+ CHECK(cylinder->get_radial_segments() > 0);
+ CHECK(cylinder->get_rings() > 0);
+ }
+
+ SUBCASE("[SceneTree][Primitive][Cylinder] Set properties and get them") {
+ cylinder->set_top_radius(4.3f);
+ cylinder->set_bottom_radius(1.2f);
+ cylinder->set_height(9.77f);
+ cylinder->set_radial_segments(12);
+ cylinder->set_rings(16);
+ cylinder->set_cap_top(false);
+ cylinder->set_cap_bottom(false);
+
+ CHECK(Math::is_equal_approx(cylinder->get_top_radius(), 4.3f));
+ CHECK(Math::is_equal_approx(cylinder->get_bottom_radius(), 1.2f));
+ CHECK(Math::is_equal_approx(cylinder->get_height(), 9.77f));
+ CHECK(cylinder->get_radial_segments() == 12);
+ CHECK(cylinder->get_rings() == 16);
+ CHECK(!cylinder->is_cap_top());
+ CHECK(!cylinder->is_cap_bottom());
+ }
+
+ SUBCASE("[SceneTree][Primitive][Cylinder] Ensure num segments is >= 0") {
+ cylinder->set_radial_segments(-12);
+ cylinder->set_rings(-16);
+
+ CHECK(cylinder->get_radial_segments() >= 0);
+ CHECK(cylinder->get_rings() >= 0);
+ }
+
+ SUBCASE("[Primitive][Cylinder] Actual cylinder mesh tests (top and bottom radius the same).") {
+ Array data{};
+ data.resize(RS::ARRAY_MAX);
+ real_t radius = .9f;
+ real_t height = 3.2f;
+ int radial_segments = 8;
+ int rings = 5;
+ bool top_cap = true;
+ bool bottom_cap = true;
+ CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, top_cap, bottom_cap);
+ Vector<Vector3> points = data[RS::ARRAY_VERTEX];
+ Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
+
+ SUBCASE("[Primitive][Cylinder] Side points are radius away from y-axis.") {
+ bool is_radius_correct{ true };
+ for (int index = 0; index < normals.size(); ++index) {
+ if (Math::is_equal_approx(normals[index].y, 0)) {
+ if (!Math::is_equal_approx((points[index] - Vector3(0, points[index].y, 0)).length_squared(), radius * radius)) {
+ is_radius_correct = false;
+ break;
+ }
+ }
+ }
+
+ CHECK(is_radius_correct);
+ }
+
+ SUBCASE("[Primitive][Cylinder] Only possible normals point in direction of point or in positive/negative y direction.") {
+ bool is_correct_normals{ true };
+ for (int index = 0; index < normals.size(); ++index) {
+ Vector3 yaxis_to_point = points[index] - Vector3(0.f, points[index].y, 0.f);
+ Vector3 point_to_normal = normals[index].normalized() - yaxis_to_point.normalized();
+ // std::cout << "<" << point_to_normal.x << ", " << point_to_normal.y << ", " << point_to_normal.z << ">\n";
+ if (!(point_to_normal.is_equal_approx(Vector3(0, 0, 0))) &&
+ (!Math::is_equal_approx(Math::abs(normals[index].normalized().y), 1))) {
+ is_correct_normals = false;
+ break;
+ }
+ }
+
+ CHECK(is_correct_normals);
+ }
+
+ SUBCASE("[Primitive][Cylinder] Points on top and bottom are height/2 away from origin.") {
+ bool is_height_correct{ true };
+ real_t half_height = 0.5 * height;
+ for (int index = 0; index < normals.size(); ++index) {
+ if (Math::is_equal_approx(normals[index].x, 0) &&
+ Math::is_equal_approx(normals[index].z, 0) &&
+ normals[index].y > 0) {
+ if (!Math::is_equal_approx(points[index].y, half_height)) {
+ is_height_correct = false;
+ break;
+ }
+ }
+ if (Math::is_equal_approx(normals[index].x, 0) &&
+ Math::is_equal_approx(normals[index].z, 0) &&
+ normals[index].y < 0) {
+ if (!Math::is_equal_approx(points[index].y, -half_height)) {
+ is_height_correct = false;
+ break;
+ }
+ }
+ }
+
+ CHECK(is_height_correct);
+ }
+
+ SUBCASE("[Primitive][Cylinder] Does mesh obey cap parameters?") {
+ CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, top_cap, false);
+ points = data[RS::ARRAY_VERTEX];
+ normals = data[RS::ARRAY_NORMAL];
+ bool no_bottom_cap{ true };
+
+ for (int index = 0; index < normals.size(); ++index) {
+ if (Math::is_equal_approx(normals[index].x, 0) &&
+ Math::is_equal_approx(normals[index].z, 0) &&
+ normals[index].y < 0) {
+ no_bottom_cap = false;
+ break;
+ }
+ }
+
+ CHECK_MESSAGE(no_bottom_cap,
+ "Check there is no bottom cap.");
+
+ CylinderMesh::create_mesh_array(data, radius, radius, height, radial_segments, rings, false, bottom_cap);
+ points = data[RS::ARRAY_VERTEX];
+ normals = data[RS::ARRAY_NORMAL];
+ bool no_top_cap{ true };
+
+ for (int index = 0; index < normals.size(); ++index) {
+ if (Math::is_equal_approx(normals[index].x, 0) &&
+ Math::is_equal_approx(normals[index].z, 0) &&
+ normals[index].y > 0) {
+ no_top_cap = false;
+ break;
+ }
+ }
+
+ CHECK_MESSAGE(no_top_cap,
+ "Check there is no top cap.");
+ }
+ }
+
+ SUBCASE("[Primitive][Cylinder] Slanted cylinder mesh (top and bottom radius different).") {
+ Array data{};
+ data.resize(RS::ARRAY_MAX);
+ real_t top_radius = 2.f;
+ real_t bottom_radius = 1.f;
+ real_t height = 1.f;
+ int radial_segments = 8;
+ int rings = 5;
+ CylinderMesh::create_mesh_array(data, top_radius, bottom_radius, height, radial_segments, rings, false, false);
+ Vector<Vector3> points = data[RS::ARRAY_VERTEX];
+ Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
+
+ SUBCASE("[Primitive][Cylinder] Side points lie correct distance from y-axis") {
+ bool is_radius_correct{ true };
+ for (int index = 0; index < points.size(); ++index) {
+ real_t radius = ((top_radius - bottom_radius) / height) * (points[index].y - 0.5 * height) + top_radius;
+ Vector3 distance_to_yaxis = points[index] - Vector3(0.f, points[index].y, 0.f);
+ if (!Math::is_equal_approx(distance_to_yaxis.length_squared(), radius * radius)) {
+ is_radius_correct = false;
+ break;
+ }
+ }
+
+ CHECK(is_radius_correct);
+ }
+
+ SUBCASE("[Primitive][Cylinder] Normal on side is orthogonal to side tangent vector") {
+ bool is_normal_correct{ true };
+ for (int index = 0; index < points.size(); ++index) {
+ Vector3 yaxis_to_point = points[index] - Vector3(0.f, points[index].y, 0.f);
+ Vector3 yaxis_to_rb = yaxis_to_point.normalized() * bottom_radius;
+ Vector3 rb_to_point = yaxis_to_point - yaxis_to_rb;
+ Vector3 y_to_bottom = -Vector3(0.f, points[index].y + 0.5 * height, 0.f);
+ Vector3 side_tangent = rb_to_point - y_to_bottom;
+
+ if (!Math::is_equal_approx(normals[index].dot(side_tangent), 0)) {
+ is_normal_correct = false;
+ break;
+ }
+ }
+
+ CHECK(is_normal_correct);
+ }
+ }
+
+} // End cylinder tests
+
+TEST_CASE("[SceneTree][Primitive][Plane] Plane Primitive") {
+ Ref<PlaneMesh> plane = memnew(PlaneMesh);
+
+ SUBCASE("[SceneTree][Primitive][Plane] Default values should be valid") {
+ CHECK(plane->get_size().x > 0);
+ CHECK(plane->get_size().y > 0);
+ CHECK(plane->get_subdivide_width() >= 0);
+ CHECK(plane->get_subdivide_depth() >= 0);
+ CHECK((plane->get_orientation() == PlaneMesh::FACE_X || plane->get_orientation() == PlaneMesh::FACE_Y || plane->get_orientation() == PlaneMesh::FACE_Z));
+ }
+
+ SUBCASE("[SceneTree][Primitive][Plane] Set properties and get them.") {
+ Size2 size{ 3.2, 1.8 };
+ Vector3 offset{ -7.3, 0.4, -1.7 };
+ plane->set_size(size);
+ plane->set_subdivide_width(15);
+ plane->set_subdivide_depth(29);
+ plane->set_center_offset(offset);
+ plane->set_orientation(PlaneMesh::FACE_X);
+
+ CHECK(plane->get_size().is_equal_approx(size));
+ CHECK(plane->get_subdivide_width() == 15);
+ CHECK(plane->get_subdivide_depth() == 29);
+ CHECK(plane->get_center_offset().is_equal_approx(offset));
+ CHECK(plane->get_orientation() == PlaneMesh::FACE_X);
+ }
+
+ SUBCASE("[SceneTree][Primitive][Plane] Ensure number of segments is >= 0.") {
+ plane->set_subdivide_width(-15);
+ plane->set_subdivide_depth(-29);
+
+ CHECK(plane->get_subdivide_width() >= 0);
+ CHECK(plane->get_subdivide_depth() >= 0);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][Quad] QuadMesh Primitive") {
+ Ref<QuadMesh> quad = memnew(QuadMesh);
+
+ SUBCASE("[Primitive][Quad] Orientation on initialization is in z direction") {
+ CHECK(quad->get_orientation() == PlaneMesh::FACE_Z);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][Prism] Prism Primitive") {
+ Ref<PrismMesh> prism = memnew(PrismMesh);
+
+ SUBCASE("[Primitive][Prism] There are valid values of properties on initialization.") {
+ CHECK(prism->get_left_to_right() >= 0);
+ CHECK(prism->get_size().x >= 0);
+ CHECK(prism->get_size().y >= 0);
+ CHECK(prism->get_size().z >= 0);
+ CHECK(prism->get_subdivide_width() >= 0);
+ CHECK(prism->get_subdivide_height() >= 0);
+ CHECK(prism->get_subdivide_depth() >= 0);
+ }
+
+ SUBCASE("[Primitive][Prism] Are able to change prism properties.") {
+ Vector3 size{ 4.3, 9.1, 0.43 };
+ prism->set_left_to_right(3.4f);
+ prism->set_size(size);
+ prism->set_subdivide_width(36);
+ prism->set_subdivide_height(5);
+ prism->set_subdivide_depth(64);
+
+ CHECK(Math::is_equal_approx(prism->get_left_to_right(), 3.4f));
+ CHECK(prism->get_size().is_equal_approx(size));
+ CHECK(prism->get_subdivide_width() == 36);
+ CHECK(prism->get_subdivide_height() == 5);
+ CHECK(prism->get_subdivide_depth() == 64);
+ }
+
+ SUBCASE("[Primitive][Prism] Ensure number of segments always >= 0") {
+ prism->set_subdivide_width(-36);
+ prism->set_subdivide_height(-5);
+ prism->set_subdivide_depth(-64);
+
+ CHECK(prism->get_subdivide_width() >= 0);
+ CHECK(prism->get_subdivide_height() >= 0);
+ CHECK(prism->get_subdivide_depth() >= 0);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][Sphere] Sphere Primitive") {
+ Ref<SphereMesh> sphere = memnew(SphereMesh);
+
+ SUBCASE("[Primitive][Sphere] There are valid values of properties on initialization.") {
+ CHECK(sphere->get_radius() >= 0);
+ CHECK(sphere->get_height() >= 0);
+ CHECK(sphere->get_radial_segments() >= 0);
+ CHECK(sphere->get_rings() >= 0);
+ }
+
+ SUBCASE("[Primitive][Sphere] Are able to change prism properties.") {
+ sphere->set_radius(3.4f);
+ sphere->set_height(2.2f);
+ sphere->set_radial_segments(36);
+ sphere->set_rings(5);
+ sphere->set_is_hemisphere(true);
+
+ CHECK(Math::is_equal_approx(sphere->get_radius(), 3.4f));
+ CHECK(Math::is_equal_approx(sphere->get_height(), 2.2f));
+ CHECK(sphere->get_radial_segments() == 36);
+ CHECK(sphere->get_rings() == 5);
+ CHECK(sphere->get_is_hemisphere());
+ }
+
+ SUBCASE("[Primitive][Sphere] Ensure number of segments always >= 0") {
+ sphere->set_radial_segments(-36);
+ sphere->set_rings(-5);
+
+ CHECK(sphere->get_radial_segments() >= 0);
+ CHECK(sphere->get_rings() >= 0);
+ }
+
+ SUBCASE("[Primitive][Sphere] Sphere mesh tests.") {
+ Array data{};
+ data.resize(RS::ARRAY_MAX);
+ real_t radius = 1.1f;
+ int radial_segments = 8;
+ int rings = 5;
+ SphereMesh::create_mesh_array(data, radius, 2 * radius, radial_segments, rings);
+ Vector<Vector3> points = data[RS::ARRAY_VERTEX];
+ Vector<Vector3> normals = data[RS::ARRAY_NORMAL];
+
+ SUBCASE("[Primitive][Sphere] All points lie radius away from origin.") {
+ bool is_radius_correct = true;
+ for (Vector3 point : points) {
+ if (!Math::is_equal_approx(point.length_squared(), radius * radius)) {
+ is_radius_correct = false;
+ break;
+ }
+ }
+
+ CHECK(is_radius_correct);
+ }
+
+ SUBCASE("[Primitive][Sphere] All normals lie in direction of corresponding point.") {
+ bool is_normals_correct = true;
+ for (int index = 0; index < points.size(); ++index) {
+ if (!Math::is_equal_approx(normals[index].normalized().dot(points[index].normalized()), 1)) {
+ is_normals_correct = false;
+ break;
+ }
+ }
+
+ CHECK(is_normals_correct);
+ }
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][Torus] Torus Primitive") {
+ Ref<TorusMesh> torus = memnew(TorusMesh);
+ Ref<PrimitiveMesh> prim = memnew(PrimitiveMesh);
+
+ SUBCASE("[Primitive][Torus] There are valid values of properties on initialization.") {
+ CHECK(torus->get_inner_radius() > 0);
+ CHECK(torus->get_outer_radius() > 0);
+ CHECK(torus->get_rings() >= 0);
+ CHECK(torus->get_ring_segments() >= 0);
+ }
+
+ SUBCASE("[Primitive][Torus] Are able to change properties.") {
+ torus->set_inner_radius(3.2f);
+ torus->set_outer_radius(9.5f);
+ torus->set_rings(19);
+ torus->set_ring_segments(43);
+
+ CHECK(Math::is_equal_approx(torus->get_inner_radius(), 3.2f));
+ CHECK(Math::is_equal_approx(torus->get_outer_radius(), 9.5f));
+ CHECK(torus->get_rings() == 19);
+ CHECK(torus->get_ring_segments() == 43);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") {
+ Ref<TubeTrailMesh> tube = memnew(TubeTrailMesh);
+
+ SUBCASE("[Primitive][TubeTrail] There are valid values of properties on initialization.") {
+ CHECK(tube->get_radius() > 0);
+ CHECK(tube->get_radial_steps() >= 0);
+ CHECK(tube->get_sections() >= 0);
+ CHECK(tube->get_section_length() > 0);
+ CHECK(tube->get_section_rings() >= 0);
+ CHECK(tube->get_curve() == nullptr);
+ CHECK(tube->get_builtin_bind_pose_count() >= 0);
+ }
+
+ SUBCASE("[Primitive][TubeTrail] Are able to change properties.") {
+ tube->set_radius(7.2f);
+ tube->set_radial_steps(9);
+ tube->set_sections(33);
+ tube->set_section_length(5.5f);
+ tube->set_section_rings(12);
+ Ref<Curve> curve = memnew(Curve);
+ tube->set_curve(curve);
+
+ CHECK(Math::is_equal_approx(tube->get_radius(), 7.2f));
+ CHECK(Math::is_equal_approx(tube->get_section_length(), 5.5f));
+ CHECK(tube->get_radial_steps() == 9);
+ CHECK(tube->get_sections() == 33);
+ CHECK(tube->get_section_rings() == 12);
+ CHECK(tube->get_curve() == curve);
+ }
+
+ SUBCASE("[Primitive][TubeTrail] Setting same curve more than once, it remains the same.") {
+ Ref<Curve> curve = memnew(Curve);
+ tube->set_curve(curve);
+ tube->set_curve(curve);
+ tube->set_curve(curve);
+
+ CHECK(tube->get_curve() == curve);
+ }
+
+ SUBCASE("[Primitive][TubeTrail] Setting curve, then changing to different curve.") {
+ Ref<Curve> curve1 = memnew(Curve);
+ Ref<Curve> curve2 = memnew(Curve);
+ tube->set_curve(curve1);
+ CHECK(tube->get_curve() == curve1);
+
+ tube->set_curve(curve2);
+ CHECK(tube->get_curve() == curve2);
+ }
+
+ SUBCASE("[Primitive][TubeTrail] Assign same curve to two different tube trails") {
+ Ref<TubeTrailMesh> tube2 = memnew(TubeTrailMesh);
+ Ref<Curve> curve = memnew(Curve);
+ tube->set_curve(curve);
+ tube2->set_curve(curve);
+
+ CHECK(tube->get_curve() == curve);
+ CHECK(tube2->get_curve() == curve);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") {
+ Ref<RibbonTrailMesh> ribbon = memnew(RibbonTrailMesh);
+
+ SUBCASE("[Primitive][RibbonTrail] There are valid values of properties on initialization.") {
+ CHECK(ribbon->get_size() > 0);
+ CHECK(ribbon->get_sections() >= 0);
+ CHECK(ribbon->get_section_length() > 0);
+ CHECK(ribbon->get_section_segments() >= 0);
+ CHECK(ribbon->get_builtin_bind_pose_count() >= 0);
+ CHECK(ribbon->get_curve() == nullptr);
+ CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS ||
+ ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT));
+ }
+
+ SUBCASE("[Primitive][RibbonTrail] Able to change properties.") {
+ Ref<Curve> curve = memnew(Curve);
+ ribbon->set_size(4.3f);
+ ribbon->set_sections(16);
+ ribbon->set_section_length(1.3f);
+ ribbon->set_section_segments(9);
+ ribbon->set_curve(curve);
+
+ CHECK(Math::is_equal_approx(ribbon->get_size(), 4.3f));
+ CHECK(Math::is_equal_approx(ribbon->get_section_length(), 1.3f));
+ CHECK(ribbon->get_sections() == 16);
+ CHECK(ribbon->get_section_segments() == 9);
+ CHECK(ribbon->get_curve() == curve);
+ }
+
+ SUBCASE("[Primitive][RibbonTrail] Setting same curve more than once, it remains the same.") {
+ Ref<Curve> curve = memnew(Curve);
+ ribbon->set_curve(curve);
+ ribbon->set_curve(curve);
+ ribbon->set_curve(curve);
+
+ CHECK(ribbon->get_curve() == curve);
+ }
+
+ SUBCASE("[Primitive][RibbonTrail] Setting curve, then changing to different curve.") {
+ Ref<Curve> curve1 = memnew(Curve);
+ Ref<Curve> curve2 = memnew(Curve);
+ ribbon->set_curve(curve1);
+ CHECK(ribbon->get_curve() == curve1);
+
+ ribbon->set_curve(curve2);
+ CHECK(ribbon->get_curve() == curve2);
+ }
+
+ SUBCASE("[Primitive][RibbonTrail] Assign same curve to two different ribbon trails") {
+ Ref<RibbonTrailMesh> ribbon2 = memnew(RibbonTrailMesh);
+ Ref<Curve> curve = memnew(Curve);
+ ribbon->set_curve(curve);
+ ribbon2->set_curve(curve);
+
+ CHECK(ribbon->get_curve() == curve);
+ CHECK(ribbon2->get_curve() == curve);
+ }
+}
+
+TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") {
+ Ref<TextMesh> text = memnew(TextMesh);
+
+ SUBCASE("[Primitive][Text] There are valid values of properties on initialization.") {
+ CHECK((text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_CENTER ||
+ text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_LEFT ||
+ text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_RIGHT ||
+ text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_FILL));
+ CHECK((text->get_vertical_alignment() == VERTICAL_ALIGNMENT_BOTTOM ||
+ text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP ||
+ text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER ||
+ text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL));
+ CHECK(text->get_font() == nullptr);
+ CHECK(text->get_font_size() > 0);
+ CHECK(text->get_line_spacing() >= 0);
+ CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF ||
+ text->get_autowrap_mode() == TextServer::AUTOWRAP_ARBITRARY ||
+ text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD ||
+ text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD_SMART));
+ CHECK((text->get_text_direction() == TextServer::DIRECTION_AUTO ||
+ text->get_text_direction() == TextServer::DIRECTION_LTR ||
+ text->get_text_direction() == TextServer::DIRECTION_RTL));
+ CHECK((text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_DEFAULT ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_URI ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_FILE ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_EMAIL ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_LIST ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_NONE ||
+ text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_CUSTOM));
+ CHECK(text->get_structured_text_bidi_override_options().size() >= 0);
+ CHECK(text->get_width() > 0);
+ CHECK(text->get_depth() > 0);
+ CHECK(text->get_curve_step() > 0);
+ CHECK(text->get_pixel_size() > 0);
+ }
+
+ SUBCASE("[Primitive][Text] Change the properties of the mesh.") {
+ Ref<Font> font = memnew(Font);
+ Array options{};
+ Point2 offset{ 30.8, 104.23 };
+ text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ text->set_vertical_alignment(VERTICAL_ALIGNMENT_BOTTOM);
+ text->set_text("Hello");
+ text->set_font(font);
+ text->set_font_size(12);
+ text->set_line_spacing(1.7f);
+ text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
+ text->set_text_direction(TextServer::DIRECTION_RTL);
+ text->set_language("French");
+ text->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_EMAIL);
+ text->set_structured_text_bidi_override_options(options);
+ text->set_uppercase(true);
+ real_t width{ 0.6 };
+ real_t depth{ 1.7 };
+ real_t pixel_size{ 2.8 };
+ real_t curve_step{ 4.8 };
+ text->set_width(width);
+ text->set_depth(depth);
+ text->set_curve_step(curve_step);
+ text->set_pixel_size(pixel_size);
+ text->set_offset(offset);
+
+ CHECK(text->get_horizontal_alignment() == HORIZONTAL_ALIGNMENT_RIGHT);
+ CHECK(text->get_vertical_alignment() == VERTICAL_ALIGNMENT_BOTTOM);
+ CHECK(text->get_text_direction() == TextServer::DIRECTION_RTL);
+ CHECK(text->get_text() == "Hello");
+ CHECK(text->get_font() == font);
+ CHECK(text->get_font_size() == 12);
+ CHECK(text->get_autowrap_mode() == TextServer::AUTOWRAP_WORD_SMART);
+ CHECK(text->get_language() == "French");
+ CHECK(text->get_structured_text_bidi_override() == TextServer::STRUCTURED_TEXT_EMAIL);
+ CHECK(text->get_structured_text_bidi_override_options() == options);
+ CHECK(text->is_uppercase() == true);
+ CHECK(text->get_offset() == offset);
+ CHECK(Math::is_equal_approx(text->get_line_spacing(), 1.7f));
+ CHECK(Math::is_equal_approx(text->get_width(), width));
+ CHECK(Math::is_equal_approx(text->get_depth(), depth));
+ CHECK(Math::is_equal_approx(text->get_curve_step(), curve_step));
+ CHECK(Math::is_equal_approx(text->get_pixel_size(), pixel_size));
+ }
+
+ SUBCASE("[Primitive][Text] Set objects multiple times.") {
+ Ref<Font> font = memnew(Font);
+ Array options{};
+ Point2 offset{ 30.8, 104.23 };
+
+ text->set_font(font);
+ text->set_font(font);
+ text->set_font(font);
+ text->set_structured_text_bidi_override_options(options);
+ text->set_structured_text_bidi_override_options(options);
+ text->set_structured_text_bidi_override_options(options);
+ text->set_offset(offset);
+ text->set_offset(offset);
+ text->set_offset(offset);
+
+ CHECK(text->get_font() == font);
+ CHECK(text->get_structured_text_bidi_override_options() == options);
+ CHECK(text->get_offset() == offset);
+ }
+
+ SUBCASE("[Primitive][Text] Set then change objects.") {
+ Ref<Font> font1 = memnew(Font);
+ Ref<Font> font2 = memnew(Font);
+ Array options1{};
+ Array options2{};
+ Point2 offset1{ 30.8, 104.23 };
+ Point2 offset2{ -30.8, -104.23 };
+
+ text->set_font(font1);
+ text->set_structured_text_bidi_override_options(options1);
+ text->set_offset(offset1);
+
+ CHECK(text->get_font() == font1);
+ CHECK(text->get_structured_text_bidi_override_options() == options1);
+ CHECK(text->get_offset() == offset1);
+
+ text->set_font(font2);
+ text->set_structured_text_bidi_override_options(options2);
+ text->set_offset(offset2);
+
+ CHECK(text->get_font() == font2);
+ CHECK(text->get_structured_text_bidi_override_options() == options2);
+ CHECK(text->get_offset() == offset2);
+ }
+
+ SUBCASE("[Primitive][Text] Assign same font to two Textmeshes.") {
+ Ref<TextMesh> text2 = memnew(TextMesh);
+ Ref<Font> font = memnew(Font);
+
+ text->set_font(font);
+ text2->set_font(font);
+
+ CHECK(text->get_font() == font);
+ CHECK(text2->get_font() == font);
+ }
+}
+
+} // namespace TestPrimitives
+
+#endif // TEST_PRIMITIVES_H
diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h
index 652cbed6e8..e34f2970d4 100644
--- a/tests/scene/test_text_edit.h
+++ b/tests/scene/test_text_edit.h
@@ -2337,7 +2337,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_DISCARD("lines_edited_from");
SIGNAL_DISCARD("caret_changed");
- // Normal left shoud deselect and place at selection start.
+ // Normal left should deselect and place at selection start.
SEND_GUI_ACTION(text_edit, "ui_text_caret_left");
CHECK(text_edit->get_viewport()->is_input_handled());
@@ -2497,7 +2497,7 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SIGNAL_DISCARD("lines_edited_from");
SIGNAL_DISCARD("caret_changed");
- // Normal right shoud deselect and place at selection start.
+ // Normal right should deselect and place at selection start.
SEND_GUI_ACTION(text_edit, "ui_text_caret_right");
CHECK(text_edit->get_viewport()->is_input_handled());
CHECK(text_edit->get_caret_line() == 0);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 80099d1dd4..cee110414e 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -47,6 +47,7 @@
#include "tests/core/math/test_expression.h"
#include "tests/core/math/test_geometry_2d.h"
#include "tests/core/math/test_geometry_3d.h"
+#include "tests/core/math/test_math_funcs.h"
#include "tests/core/math/test_plane.h"
#include "tests/core/math/test_quaternion.h"
#include "tests/core/math/test_random_number_generator.h"
@@ -90,7 +91,9 @@
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_curve.h"
#include "tests/scene/test_gradient.h"
+#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_3d.h"
+#include "tests/scene/test_primitives.h"
#include "tests/scene/test_sprite_frames.h"
#include "tests/scene/test_text_edit.h"
#include "tests/scene/test_theme.h"
diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h
index 1471a952cd..41185a7d16 100644
--- a/tests/test_validate_testing.h
+++ b/tests/test_validate_testing.h
@@ -86,7 +86,7 @@ TEST_SUITE("Validate tests") {
Plane plane(Vector3(1, 1, 1), 1.0);
INFO(plane);
- Quaternion quat(Vector3(0.5, 1.0, 2.0));
+ Quaternion quat = Quaternion::from_euler(Vector3(0.5, 1.0, 2.0));
INFO(quat);
AABB aabb(Vector3(), Vector3(100, 100, 100));