initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
/**************************************************************************/
/* test_animation.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/animation.h"
#include "tests/test_macros.h"
namespace TestAnimation {
TEST_CASE("[Animation] Empty animation getters") {
const Ref<Animation> animation = memnew(Animation);
CHECK(animation->get_length() == doctest::Approx(real_t(1.0)));
CHECK(animation->get_step() == doctest::Approx(real_t(1.0 / 30)));
}
TEST_CASE("[Animation] Create value track") {
// This creates an animation that makes the node "Enemy" move to the right by
// 100 pixels in 0.5 seconds.
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_VALUE);
CHECK(track_index == 0);
animation->track_set_path(track_index, NodePath("Enemy:position:x"));
animation->track_insert_key(track_index, 0.0, 0);
animation->track_insert_key(track_index, 0.5, 100);
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
CHECK(int(animation->track_get_key_value(0, 0)) == 0);
CHECK(int(animation->track_get_key_value(0, 1)) == 100);
CHECK(animation->value_track_interpolate(0, -0.2) == doctest::Approx(0.0));
CHECK(animation->value_track_interpolate(0, 0.0) == doctest::Approx(0.0));
CHECK(animation->value_track_interpolate(0, 0.2) == doctest::Approx(40.0));
CHECK(animation->value_track_interpolate(0, 0.4) == doctest::Approx(80.0));
CHECK(animation->value_track_interpolate(0, 0.5) == doctest::Approx(100.0));
CHECK(animation->value_track_interpolate(0, 0.6) == doctest::Approx(100.0));
CHECK(animation->track_get_key_transition(0, 0) == doctest::Approx(real_t(1.0)));
CHECK(animation->track_get_key_transition(0, 1) == doctest::Approx(real_t(1.0)));
ERR_PRINT_OFF;
// Nonexistent keys.
CHECK(animation->track_get_key_value(0, 2).is_null());
CHECK(animation->track_get_key_value(0, -1).is_null());
CHECK(animation->track_get_key_transition(0, 2) == doctest::Approx(real_t(-1.0)));
// Nonexistent track (and keys).
CHECK(animation->track_get_key_value(1, 0).is_null());
CHECK(animation->track_get_key_value(1, 1).is_null());
CHECK(animation->track_get_key_value(1, 2).is_null());
CHECK(animation->track_get_key_value(1, -1).is_null());
CHECK(animation->track_get_key_transition(1, 0) == doctest::Approx(real_t(-1.0)));
// This is a value track, so the methods below should return errors.
CHECK(animation->try_position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(0.0));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
TEST_CASE("[Animation] Create 3D position track") {
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_POSITION_3D);
animation->track_set_path(track_index, NodePath("Enemy:position"));
animation->position_track_insert_key(track_index, 0.0, Vector3(0, 1, 2));
animation->position_track_insert_key(track_index, 0.5, Vector3(3.5, 4, 5));
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
CHECK(Vector3(animation->track_get_key_value(0, 0)).is_equal_approx(Vector3(0, 1, 2)));
CHECK(Vector3(animation->track_get_key_value(0, 1)).is_equal_approx(Vector3(3.5, 4, 5)));
Vector3 r_interpolation;
CHECK(animation->try_position_track_interpolate(0, -0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2)));
CHECK(animation->try_position_track_interpolate(0, 0.0, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2)));
CHECK(animation->try_position_track_interpolate(0, 0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(1.4, 2.2, 3.2)));
CHECK(animation->try_position_track_interpolate(0, 0.4, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(2.8, 3.4, 4.4)));
CHECK(animation->try_position_track_interpolate(0, 0.5, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5)));
CHECK(animation->try_position_track_interpolate(0, 0.6, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5)));
// 3D position tracks always use linear interpolation for performance reasons.
CHECK(animation->track_get_key_transition(0, 0) == doctest::Approx(real_t(1.0)));
CHECK(animation->track_get_key_transition(0, 1) == doctest::Approx(real_t(1.0)));
// This is a 3D position track, so the methods below should return errors.
ERR_PRINT_OFF;
CHECK(animation->value_track_interpolate(0, 0.0).is_null());
CHECK(animation->try_rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(0.0));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
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::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::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;
CHECK(animation->try_rotation_track_interpolate(0, -0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.403423, 0.259035, 0.73846, 0.47416)));
CHECK(animation->try_rotation_track_interpolate(0, 0.0, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.403423, 0.259035, 0.73846, 0.47416)));
CHECK(animation->try_rotation_track_interpolate(0, 0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.336182, 0.30704, 0.751515, 0.477425)));
CHECK(animation->try_rotation_track_interpolate(0, 0.4, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.266585, 0.352893, 0.759303, 0.477344)));
CHECK(animation->try_rotation_track_interpolate(0, 0.5, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.231055, 0.374912, 0.761204, 0.476048)));
CHECK(animation->try_rotation_track_interpolate(0, 0.6, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Quaternion(0.231055, 0.374912, 0.761204, 0.476048)));
// 3D rotation tracks always use linear interpolation for performance reasons.
CHECK(animation->track_get_key_transition(0, 0) == doctest::Approx(real_t(1.0)));
CHECK(animation->track_get_key_transition(0, 1) == doctest::Approx(real_t(1.0)));
// This is a 3D rotation track, so the methods below should return errors.
ERR_PRINT_OFF;
CHECK(animation->value_track_interpolate(0, 0.0).is_null());
CHECK(animation->try_position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(real_t(0.0)));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
TEST_CASE("[Animation] Create 3D scale track") {
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_SCALE_3D);
animation->track_set_path(track_index, NodePath("Enemy:scale"));
animation->scale_track_insert_key(track_index, 0.0, Vector3(0, 1, 2));
animation->scale_track_insert_key(track_index, 0.5, Vector3(3.5, 4, 5));
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
CHECK(Vector3(animation->track_get_key_value(0, 0)).is_equal_approx(Vector3(0, 1, 2)));
CHECK(Vector3(animation->track_get_key_value(0, 1)).is_equal_approx(Vector3(3.5, 4, 5)));
Vector3 r_interpolation;
CHECK(animation->try_scale_track_interpolate(0, -0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2)));
CHECK(animation->try_scale_track_interpolate(0, 0.0, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(0, 1, 2)));
CHECK(animation->try_scale_track_interpolate(0, 0.2, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(1.4, 2.2, 3.2)));
CHECK(animation->try_scale_track_interpolate(0, 0.4, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(2.8, 3.4, 4.4)));
CHECK(animation->try_scale_track_interpolate(0, 0.5, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5)));
CHECK(animation->try_scale_track_interpolate(0, 0.6, &r_interpolation) == OK);
CHECK(r_interpolation.is_equal_approx(Vector3(3.5, 4, 5)));
// 3D scale tracks always use linear interpolation for performance reasons.
CHECK(animation->track_get_key_transition(0, 0) == doctest::Approx(1.0));
CHECK(animation->track_get_key_transition(0, 1) == doctest::Approx(1.0));
// This is a 3D scale track, so the methods below should return errors.
ERR_PRINT_OFF;
CHECK(animation->value_track_interpolate(0, 0.0).is_null());
CHECK(animation->try_position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(0.0));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
TEST_CASE("[Animation] Create blend shape track") {
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_BLEND_SHAPE);
animation->track_set_path(track_index, NodePath("Enemy:scale"));
// Negative values for blend shapes should work as expected.
animation->blend_shape_track_insert_key(track_index, 0.0, -1.0);
animation->blend_shape_track_insert_key(track_index, 0.5, 1.0);
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
float r_blend = 0.0f;
CHECK(animation->blend_shape_track_get_key(0, 0, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(-1.0f));
CHECK(animation->blend_shape_track_get_key(0, 1, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(1.0f));
CHECK(animation->try_blend_shape_track_interpolate(0, -0.2, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(-1.0f));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(-1.0f));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.2, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(-0.2f));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.4, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(0.6f));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.5, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(1.0f));
CHECK(animation->try_blend_shape_track_interpolate(0, 0.6, &r_blend) == OK);
CHECK(r_blend == doctest::Approx(1.0f));
// Blend shape tracks always use linear interpolation for performance reasons.
CHECK(animation->track_get_key_transition(0, 0) == doctest::Approx(real_t(1.0)));
CHECK(animation->track_get_key_transition(0, 1) == doctest::Approx(real_t(1.0)));
// This is a blend shape track, so the methods below should return errors.
ERR_PRINT_OFF;
CHECK(animation->value_track_interpolate(0, 0.0).is_null());
CHECK(animation->try_position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(0.0));
ERR_PRINT_ON;
}
TEST_CASE("[Animation] Create Bezier track") {
Ref<Animation> animation = memnew(Animation);
const int track_index = animation->add_track(Animation::TYPE_BEZIER);
animation->track_set_path(track_index, NodePath("Enemy:scale"));
animation->bezier_track_insert_key(track_index, 0.0, -1.0, Vector2(-1, -1), Vector2(1, 1));
animation->bezier_track_insert_key(track_index, 0.5, 1.0, Vector2(0, 1), Vector2(1, 0.5));
CHECK(animation->get_track_count() == 1);
CHECK(!animation->track_is_compressed(0));
CHECK(animation->bezier_track_get_key_value(0, 0) == doctest::Approx(real_t(-1.0)));
CHECK(animation->bezier_track_get_key_value(0, 1) == doctest::Approx(real_t(1.0)));
CHECK(animation->bezier_track_interpolate(0, -0.2) == doctest::Approx(real_t(-1.0)));
CHECK(animation->bezier_track_interpolate(0, 0.0) == doctest::Approx(real_t(-1.0)));
CHECK(animation->bezier_track_interpolate(0, 0.2) == doctest::Approx(real_t(-0.76057207584381)));
CHECK(animation->bezier_track_interpolate(0, 0.4) == doctest::Approx(real_t(-0.39975279569626)));
CHECK(animation->bezier_track_interpolate(0, 0.5) == doctest::Approx(real_t(1.0)));
CHECK(animation->bezier_track_interpolate(0, 0.6) == doctest::Approx(real_t(1.0)));
// This is a bezier track, so the methods below should return errors.
ERR_PRINT_OFF;
CHECK(animation->value_track_interpolate(0, 0.0).is_null());
CHECK(animation->try_position_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_rotation_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_scale_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
CHECK(animation->try_blend_shape_track_interpolate(0, 0.0, nullptr) == ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
} // namespace TestAnimation

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* test_animation_blend_tree.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. */
/**************************************************************************/
#pragma once
#include "scene/animation/animation_blend_tree.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestAnimationBlendTree {
TEST_CASE("[SceneTree][AnimationBlendTree] Create AnimationBlendTree and add AnimationNode") {
Ref<AnimationNodeBlendTree> blend_tree;
blend_tree.instantiate();
// Test initial state.
CHECK(blend_tree->has_node("output"));
CHECK_EQ(blend_tree->get_graph_offset(), Vector2(0, 0));
CHECK_EQ(blend_tree->get_node_list().size(), 1);
// Test adding animation node.
Ref<AnimationNodeAnimation> anim_node;
anim_node.instantiate();
anim_node->set_animation(StringName("test_animation"));
Vector2 position(100, 100);
blend_tree->add_node("test_node", anim_node, position);
// Test node existence.
CHECK(blend_tree->has_node("test_node"));
CHECK_EQ(blend_tree->get_node("test_node"), anim_node);
CHECK_EQ(blend_tree->get_node_position("test_node"), position);
// Test node connection on port 0.
CHECK_EQ(blend_tree->can_connect_node("output", 0, "test_node"), AnimationNodeBlendTree::CONNECTION_OK);
blend_tree->connect_node("output", 0, "test_node");
Vector<StringName> connections = blend_tree->get_node_connection_array("output");
CHECK_EQ(connections.size(), 1);
CHECK_EQ(connections[0], StringName("test_node"));
// Test node rename.
blend_tree->rename_node("test_node", "renamed_node");
CHECK_FALSE(blend_tree->has_node("test_node"));
CHECK(blend_tree->has_node("renamed_node"));
connections = blend_tree->get_node_connection_array("output");
CHECK_EQ(connections[0], StringName("renamed_node"));
// Test node removal.
blend_tree->remove_node("renamed_node");
CHECK_FALSE(blend_tree->has_node("renamed_node"));
connections = blend_tree->get_node_connection_array("output");
CHECK_EQ(connections[0], StringName());
}
} //namespace TestAnimationBlendTree

View File

@@ -0,0 +1,446 @@
/**************************************************************************/
/* test_arraymesh.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/mesh.h"
#include "tests/test_macros.h"
namespace TestArrayMesh {
TEST_CASE("[SceneTree][ArrayMesh] Adding and modifying blendshapes.") {
Ref<ArrayMesh> mesh;
mesh.instantiate();
StringName name_a{ "ShapeA" };
StringName name_b{ "ShapeB" };
SUBCASE("Adding a blend shape to the mesh before a surface is added.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
CHECK(mesh->get_blend_shape_name(0) == name_a);
CHECK(mesh->get_blend_shape_name(1) == name_b);
}
SUBCASE("Add same blend shape multiple times appends name with number.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_a);
CHECK(mesh->get_blend_shape_name(0) == "ShapeA");
bool all_different = (static_cast<String>(mesh->get_blend_shape_name(0)) != static_cast<String>(mesh->get_blend_shape_name(1))) &&
(static_cast<String>(mesh->get_blend_shape_name(1)) != static_cast<String>(mesh->get_blend_shape_name(2))) &&
(static_cast<String>(mesh->get_blend_shape_name(0)) != static_cast<String>(mesh->get_blend_shape_name(2)));
bool all_have_name = static_cast<String>(mesh->get_blend_shape_name(1)).contains("ShapeA") &&
static_cast<String>(mesh->get_blend_shape_name(2)).contains("ShapeA");
CHECK((all_different && all_have_name));
}
SUBCASE("ArrayMesh keeps correct count of number of blend shapes") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
mesh->add_blend_shape(name_b);
mesh->add_blend_shape(name_b);
REQUIRE(mesh->get_blend_shape_count() == 5);
}
SUBCASE("Adding blend shape after surface is added causes error") {
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
ERR_PRINT_OFF
mesh->add_blend_shape(name_a);
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Adding blend shapes once all surfaces have been removed is allowed") {
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
mesh->surface_remove(0);
ERR_PRINT_OFF
mesh->add_blend_shape(name_a);
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 0);
mesh->surface_remove(0);
mesh->add_blend_shape(name_a);
CHECK(mesh->get_blend_shape_count() == 1);
}
SUBCASE("Change blend shape name after adding.") {
mesh->add_blend_shape(name_a);
mesh->set_blend_shape_name(0, name_b);
CHECK(mesh->get_blend_shape_name(0) == name_b);
}
SUBCASE("Change blend shape name to the name of one already there, should append number to end") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
mesh->set_blend_shape_name(0, name_b);
String name_string = mesh->get_blend_shape_name(0);
CHECK(name_string.contains("ShapeB"));
CHECK(name_string.length() > static_cast<String>(name_b).size());
}
SUBCASE("Clear all blend shapes before surface has been added.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
CHECK(mesh->get_blend_shape_count() == 2);
mesh->clear_blend_shapes();
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Clearing all blend shapes once all surfaces have been removed is allowed") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
Array blend_shape;
blend_shape.resize(Mesh::ARRAY_MAX);
blend_shape[Mesh::ARRAY_VERTEX] = cylinder_array[Mesh::ARRAY_VERTEX];
blend_shape[Mesh::ARRAY_NORMAL] = cylinder_array[Mesh::ARRAY_NORMAL];
blend_shape[Mesh::ARRAY_TANGENT] = cylinder_array[Mesh::ARRAY_TANGENT];
Array blend_shapes = { blend_shape, blend_shape };
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
mesh->surface_remove(0);
ERR_PRINT_OFF
mesh->clear_blend_shapes();
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 2);
mesh->surface_remove(0);
mesh->clear_blend_shapes();
CHECK(mesh->get_blend_shape_count() == 0);
}
SUBCASE("Can't add surface with incorrect number of blend shapes.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Array cylinder_array;
ERR_PRINT_OFF
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
ERR_PRINT_ON
CHECK(mesh->get_surface_count() == 0);
}
SUBCASE("Can't clear blend shapes after surface had been added.") {
mesh->add_blend_shape(name_a);
mesh->add_blend_shape(name_b);
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
Array blend_shape;
blend_shape.resize(Mesh::ARRAY_MAX);
blend_shape[Mesh::ARRAY_VERTEX] = cylinder_array[Mesh::ARRAY_VERTEX];
blend_shape[Mesh::ARRAY_NORMAL] = cylinder_array[Mesh::ARRAY_NORMAL];
blend_shape[Mesh::ARRAY_TANGENT] = cylinder_array[Mesh::ARRAY_TANGENT];
Array blend_shapes = { blend_shape, blend_shape };
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend_shapes);
ERR_PRINT_OFF
mesh->clear_blend_shapes();
ERR_PRINT_ON
CHECK(mesh->get_blend_shape_count() == 2);
}
SUBCASE("Set the blend shape mode of ArrayMesh and underlying mesh RID.") {
mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_RELATIVE);
CHECK(mesh->get_blend_shape_mode() == Mesh::BLEND_SHAPE_MODE_RELATIVE);
}
}
TEST_CASE("[SceneTree][ArrayMesh] Surface metadata tests.") {
Ref<ArrayMesh> mesh;
mesh.instantiate();
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
cylinder->create_mesh_array(cylinder_array, 3.f, 3.f, 5.f);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
Ref<BoxMesh> box;
box.instantiate();
Array box_array;
box_array.resize(Mesh::ARRAY_MAX);
box->create_mesh_array(box_array, Vector3(2.f, 1.2f, 1.6f));
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array);
SUBCASE("Add 2 surfaces and count the number of surfaces in the mesh.") {
REQUIRE(mesh->get_surface_count() == 2);
}
SUBCASE("Get the surface array from mesh.") {
REQUIRE(mesh->surface_get_arrays(0)[0] == cylinder_array[0]);
REQUIRE(mesh->surface_get_arrays(1)[0] == box_array[0]);
}
SUBCASE("Get the array length of a particular surface.") {
CHECK(mesh->surface_get_array_len(0) == static_cast<Vector<Vector3>>(cylinder_array[RenderingServer::ARRAY_VERTEX]).size());
CHECK(mesh->surface_get_array_len(1) == static_cast<Vector<Vector3>>(box_array[RenderingServer::ARRAY_VERTEX]).size());
}
SUBCASE("Get the index array length of a particular surface.") {
CHECK(mesh->surface_get_array_index_len(0) == static_cast<Vector<Vector3>>(cylinder_array[RenderingServer::ARRAY_INDEX]).size());
CHECK(mesh->surface_get_array_index_len(1) == static_cast<Vector<Vector3>>(box_array[RenderingServer::ARRAY_INDEX]).size());
}
SUBCASE("Get correct primitive type") {
CHECK(mesh->surface_get_primitive_type(0) == Mesh::PRIMITIVE_TRIANGLES);
CHECK(mesh->surface_get_primitive_type(1) == Mesh::PRIMITIVE_TRIANGLES);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLE_STRIP, box_array);
CHECK(mesh->surface_get_primitive_type(2) == Mesh::PRIMITIVE_TRIANGLE_STRIP);
}
SUBCASE("Returns correct format for the mesh") {
int format = RS::ARRAY_FORMAT_BLEND_SHAPE_MASK | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX;
CHECK((mesh->surface_get_format(0) & format) != 0);
CHECK((mesh->surface_get_format(1) & format) != 0);
}
SUBCASE("Set a surface name and retrieve it by name.") {
mesh->surface_set_name(0, "surf1");
CHECK(mesh->surface_find_by_name("surf1") == 0);
CHECK(mesh->surface_get_name(0) == "surf1");
}
SUBCASE("Set material to two different surfaces.") {
Ref<Material> mat;
mat.instantiate();
mesh->surface_set_material(0, mat);
CHECK(mesh->surface_get_material(0) == mat);
mesh->surface_set_material(1, mat);
CHECK(mesh->surface_get_material(1) == mat);
}
SUBCASE("Set same material multiple times doesn't change material of surface.") {
Ref<Material> mat;
mat.instantiate();
mesh->surface_set_material(0, mat);
mesh->surface_set_material(0, mat);
mesh->surface_set_material(0, mat);
CHECK(mesh->surface_get_material(0) == mat);
}
SUBCASE("Set material of surface then change to different material.") {
Ref<Material> mat1;
mat1.instantiate();
Ref<Material> mat2;
mat2.instantiate();
mesh->surface_set_material(1, mat1);
CHECK(mesh->surface_get_material(1) == mat1);
mesh->surface_set_material(1, mat2);
CHECK(mesh->surface_get_material(1) == mat2);
}
SUBCASE("Get the LOD of the mesh.") {
Dictionary lod;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, TypedArray<Array>(), lod);
CHECK(mesh->surface_get_lods(2) == lod);
}
SUBCASE("Get the blend shape arrays from the mesh.") {
TypedArray<Array> blend;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array, blend);
CHECK(mesh->surface_get_blend_shape_arrays(2) == blend);
}
}
TEST_CASE("[SceneTree][ArrayMesh] Get/Set mesh metadata and actions") {
Ref<ArrayMesh> mesh;
mesh.instantiate();
Ref<CylinderMesh> cylinder;
cylinder.instantiate();
Array cylinder_array;
cylinder_array.resize(Mesh::ARRAY_MAX);
constexpr float cylinder_radius = 3.f;
constexpr float cylinder_height = 5.f;
cylinder->create_mesh_array(cylinder_array, cylinder_radius, cylinder_radius, cylinder_height);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, cylinder_array);
Ref<BoxMesh> box;
box.instantiate();
Array box_array;
box_array.resize(Mesh::ARRAY_MAX);
const Vector3 box_size = Vector3(2.f, 1.2f, 1.6f);
box->create_mesh_array(box_array, box_size);
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, box_array);
SUBCASE("Set the shadow mesh.") {
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
}
SUBCASE("Set the shadow mesh multiple times.") {
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
mesh->set_shadow_mesh(shadow);
mesh->set_shadow_mesh(shadow);
mesh->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
}
SUBCASE("Set the same shadow mesh on multiple meshes.") {
Ref<ArrayMesh> shadow;
shadow.instantiate();
Ref<ArrayMesh> mesh2;
mesh2.instantiate();
mesh->set_shadow_mesh(shadow);
mesh2->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
CHECK(mesh2->get_shadow_mesh() == shadow);
}
SUBCASE("Set the shadow mesh and then change it.") {
Ref<ArrayMesh> shadow;
shadow.instantiate();
mesh->set_shadow_mesh(shadow);
CHECK(mesh->get_shadow_mesh() == shadow);
Ref<ArrayMesh> shadow2;
shadow2.instantiate();
mesh->set_shadow_mesh(shadow2);
CHECK(mesh->get_shadow_mesh() == shadow2);
}
SUBCASE("Set custom AABB.") {
AABB bound;
mesh->set_custom_aabb(bound);
CHECK(mesh->get_custom_aabb() == bound);
}
SUBCASE("Set custom AABB multiple times.") {
AABB bound;
mesh->set_custom_aabb(bound);
mesh->set_custom_aabb(bound);
mesh->set_custom_aabb(bound);
mesh->set_custom_aabb(bound);
CHECK(mesh->get_custom_aabb() == bound);
}
SUBCASE("Set custom AABB then change to another AABB.") {
AABB bound;
AABB bound2;
mesh->set_custom_aabb(bound);
CHECK(mesh->get_custom_aabb() == bound);
mesh->set_custom_aabb(bound2);
CHECK(mesh->get_custom_aabb() == bound2);
}
SUBCASE("Clear all surfaces should leave zero count.") {
mesh->clear_surfaces();
CHECK(mesh->get_surface_count() == 0);
}
SUBCASE("Able to get correct mesh RID.") {
RID rid = mesh->get_rid();
CHECK(RS::get_singleton()->mesh_get_surface_count(rid) == 2);
}
SUBCASE("Create surface from raw SurfaceData data.") {
RID mesh_rid = mesh->get_rid();
RS::SurfaceData surface_data = RS::get_singleton()->mesh_get_surface(mesh_rid, 0);
Ref<ArrayMesh> mesh2;
mesh2.instantiate();
mesh2->add_surface(surface_data.format, Mesh::PRIMITIVE_TRIANGLES, surface_data.vertex_data, surface_data.attribute_data,
surface_data.skin_data, surface_data.vertex_count, surface_data.index_data, surface_data.index_count, surface_data.aabb);
CHECK(mesh2->get_surface_count() == 1);
CHECK(mesh2->surface_get_primitive_type(0) == Mesh::PRIMITIVE_TRIANGLES);
CHECK((mesh2->surface_get_format(0) & surface_data.format) != 0);
CHECK(mesh2->get_aabb().is_equal_approx(surface_data.aabb));
}
SUBCASE("Removing a surface decreases surface count.") {
REQUIRE(mesh->get_surface_count() == 2);
mesh->surface_remove(0);
CHECK(mesh->get_surface_count() == 1);
mesh->surface_remove(0);
CHECK(mesh->get_surface_count() == 0);
}
SUBCASE("Remove the first surface and check the mesh's AABB.") {
REQUIRE(mesh->get_surface_count() >= 1);
mesh->surface_remove(0);
const AABB box_aabb = AABB(-box_size / 2, box_size);
CHECK(mesh->get_aabb().is_equal_approx(box_aabb));
}
SUBCASE("Remove the last surface and check the mesh's AABB.") {
REQUIRE(mesh->get_surface_count() >= 1);
mesh->surface_remove(mesh->get_surface_count() - 1);
const AABB cylinder_aabb = AABB(Vector3(-cylinder_radius, -cylinder_height / 2, -cylinder_radius),
Vector3(2 * cylinder_radius, cylinder_height, 2 * cylinder_radius));
CHECK(mesh->get_aabb().is_equal_approx(cylinder_aabb));
}
SUBCASE("Remove all surfaces and check the mesh's AABB.") {
while (mesh->get_surface_count()) {
mesh->surface_remove(0);
}
CHECK(mesh->get_aabb() == AABB());
}
SUBCASE("Removing a non-existent surface causes error.") {
ERR_PRINT_OFF
mesh->surface_remove(42);
ERR_PRINT_ON
CHECK(mesh->get_surface_count() == 2);
}
}
} // namespace TestArrayMesh

View File

@@ -0,0 +1,219 @@
/**************************************************************************/
/* test_audio_stream_wav.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. */
/**************************************************************************/
#pragma once
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
#include "scene/resources/audio_stream_wav.h"
#include "tests/test_macros.h"
namespace TestAudioStreamWAV {
// Default wav rate for test cases.
constexpr float WAV_RATE = 44100;
/* Default wav count for test cases. 1 second of audio is used so that the file can be listened
to manually if needed. */
constexpr int WAV_COUNT = WAV_RATE;
float gen_wav(float frequency, float wav_rate, int wav_number) {
// formula for generating a sin wave with given frequency.
return Math::sin((Math::TAU * frequency / wav_rate) * wav_number);
}
/* Generates a 440Hz sin wave in channel 0 (mono channel or left stereo channel)
* and a 261.63Hz wave in channel 1 (right stereo channel).
* These waves correspond to the music notes A4 and C4 respectively.
*/
Vector<uint8_t> gen_pcm8_test(float wav_rate, int wav_count, bool stereo) {
Vector<uint8_t> buffer;
buffer.resize(stereo ? wav_count * 2 : wav_count);
uint8_t *write_ptr = buffer.ptrw();
for (int i = 0; i < buffer.size(); i++) {
float wav;
if (stereo) {
if (i % 2 == 0) {
wav = gen_wav(440, wav_rate, i / 2);
} else {
wav = gen_wav(261.63, wav_rate, i / 2);
}
} else {
wav = gen_wav(440, wav_rate, i);
}
// Map sin wave to full range of 8-bit values.
uint8_t wav_8bit = Math::fast_ftoi(((wav + 1) / 2) * UINT8_MAX);
// Unlike the .wav format, AudioStreamWAV expects signed 8-bit wavs.
uint8_t wav_8bit_signed = wav_8bit - (INT8_MAX + 1);
write_ptr[i] = wav_8bit_signed;
}
return buffer;
}
// Same as gen_pcm8_test but with 16-bit wavs.
Vector<uint8_t> gen_pcm16_test(float wav_rate, int wav_count, bool stereo) {
Vector<uint8_t> buffer;
buffer.resize(stereo ? wav_count * 4 : wav_count * 2);
uint8_t *write_ptr = buffer.ptrw();
for (int i = 0; i < buffer.size() / 2; i++) {
float wav;
if (stereo) {
if (i % 2 == 0) {
wav = gen_wav(440, wav_rate, i / 2);
} else {
wav = gen_wav(261.63, wav_rate, i / 2);
}
} else {
wav = gen_wav(440, wav_rate, i);
}
// Map sin wave to full range of 16-bit values.
uint16_t wav_16bit = Math::fast_ftoi(((wav + 1) / 2) * UINT16_MAX);
// The .wav format expects wavs larger than 8 bits to be signed.
uint16_t wav_16bit_signed = wav_16bit - (INT16_MAX + 1);
encode_uint16(wav_16bit_signed, write_ptr + (i * 2));
}
return buffer;
}
void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, float wav_rate, float wav_count) {
String save_path = TestUtils::get_temp_path(file_name);
Vector<uint8_t> test_data;
if (data_format == AudioStreamWAV::FORMAT_8_BITS) {
test_data = gen_pcm8_test(wav_rate, wav_count, stereo);
} else {
test_data = gen_pcm16_test(wav_rate, wav_count, stereo);
}
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
stream->set_mix_rate(wav_rate);
CHECK(stream->get_mix_rate() == wav_rate);
stream->set_format(data_format);
CHECK(stream->get_format() == data_format);
stream->set_stereo(stereo);
CHECK(stream->is_stereo() == stereo);
stream->set_data(test_data);
CHECK(stream->get_data() == test_data);
SUBCASE("Stream length is computed properly") {
CHECK(stream->get_length() == doctest::Approx(double(wav_count / wav_rate)));
}
SUBCASE("Stream can be saved as .wav") {
REQUIRE(stream->save_to_wav(save_path) == OK);
Error error;
Ref<FileAccess> wav_file = FileAccess::open(save_path, FileAccess::READ, &error);
REQUIRE(error == OK);
Dictionary options;
Ref<AudioStreamWAV> loaded_stream = AudioStreamWAV::load_from_file(save_path, options);
CHECK(loaded_stream->get_format() == stream->get_format());
CHECK(loaded_stream->get_loop_mode() == stream->get_loop_mode());
CHECK(loaded_stream->get_loop_begin() == stream->get_loop_begin());
CHECK(loaded_stream->get_loop_end() == stream->get_loop_end());
CHECK(loaded_stream->get_mix_rate() == stream->get_mix_rate());
CHECK(loaded_stream->is_stereo() == stream->is_stereo());
CHECK(loaded_stream->get_length() == stream->get_length());
CHECK(loaded_stream->is_monophonic() == stream->is_monophonic());
CHECK(loaded_stream->get_data() == stream->get_data());
}
}
TEST_CASE("[Audio][AudioStreamWAV] Mono PCM8 format") {
run_test("test_pcm8_mono.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[Audio][AudioStreamWAV] Mono PCM16 format") {
run_test("test_pcm16_mono.wav", AudioStreamWAV::FORMAT_16_BITS, false, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM8 format") {
run_test("test_pcm8_stereo.wav", AudioStreamWAV::FORMAT_8_BITS, true, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM16 format") {
run_test("test_pcm16_stereo.wav", AudioStreamWAV::FORMAT_16_BITS, true, WAV_RATE, WAV_COUNT);
}
TEST_CASE("[Audio][AudioStreamWAV] Alternate mix rate") {
run_test("test_pcm16_stereo_38000Hz.wav", AudioStreamWAV::FORMAT_16_BITS, true, 38000, 38000);
}
TEST_CASE("[Audio][AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") {
String save_path = TestUtils::get_temp_path("test_wav_extension");
Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false);
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
stream->set_data(test_data);
REQUIRE(stream->save_to_wav(save_path) == OK);
Error error;
Ref<FileAccess> wav_file = FileAccess::open(save_path + ".wav", FileAccess::READ, &error);
CHECK(error == OK);
}
TEST_CASE("[Audio][AudioStreamWAV] Default values") {
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
CHECK(stream->get_format() == AudioStreamWAV::FORMAT_8_BITS);
CHECK(stream->get_loop_mode() == AudioStreamWAV::LOOP_DISABLED);
CHECK(stream->get_loop_begin() == 0);
CHECK(stream->get_loop_end() == 0);
CHECK(stream->get_mix_rate() == 44100);
CHECK(stream->is_stereo() == false);
CHECK(stream->get_length() == 0);
CHECK(stream->is_monophonic() == false);
CHECK(stream->get_data() == Vector<uint8_t>{});
CHECK(stream->get_stream_name() == "");
}
TEST_CASE("[Audio][AudioStreamWAV] Save empty file") {
run_test("test_empty.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, 0);
}
TEST_CASE("[Audio][AudioStreamWAV] Saving IMA ADPCM is not supported") {
String save_path = TestUtils::get_temp_path("test_adpcm.wav");
Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV);
stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM);
ERR_PRINT_OFF;
CHECK(stream->save_to_wav(save_path) == ERR_UNAVAILABLE);
ERR_PRINT_ON;
}
} // namespace TestAudioStreamWAV

502
tests/scene/test_bit_map.h Normal file
View File

@@ -0,0 +1,502 @@
/**************************************************************************/
/* test_bit_map.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. */
/**************************************************************************/
#pragma once
#include "core/os/memory.h"
#include "scene/resources/bit_map.h"
#include "tests/test_macros.h"
namespace TestBitmap {
void reset_bit_map(BitMap &p_bm) {
Size2i size = p_bm.get_size();
p_bm.set_bit_rect(Rect2i(0, 0, size.width, size.height), false);
}
TEST_CASE("[BitMap] Create bit map") {
Size2i dim{ 256, 512 };
BitMap bit_map{};
bit_map.create(dim);
CHECK(bit_map.get_size() == Size2i(256, 512));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "This will go through the entire bitmask inside of bitmap, thus hopefully checking if the bitmask was correctly set up.");
ERR_PRINT_OFF
dim = Size2i(0, 256);
bit_map.create(dim);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid.");
dim = Size2i(512, 0);
bit_map.create(dim);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is invalid.");
dim = Size2i(46341, 46341);
bit_map.create(dim);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 512), "We should still have the same dimensions as before, because the new dimension is too large (46341*46341=2147488281).");
ERR_PRINT_ON
}
TEST_CASE("[BitMap] Create bit map from image alpha") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
bit_map.create(dim);
ERR_PRINT_OFF
const Ref<Image> null_img = nullptr;
bit_map.create_from_image_alpha(null_img);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from a nullptr should fail.");
Ref<Image> empty_img;
empty_img.instantiate();
bit_map.create_from_image_alpha(empty_img);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because bitmap creation from an empty image should fail.");
Ref<Image> wrong_format_img = Image::create_empty(3, 3, false, Image::Format::FORMAT_DXT1);
bit_map.create_from_image_alpha(wrong_format_img);
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Bitmap should have its old values because converting from a compressed image should fail.");
ERR_PRINT_ON
Ref<Image> img = Image::create_empty(3, 3, false, Image::Format::FORMAT_RGBA8);
img->set_pixel(0, 0, Color(0, 0, 0, 0));
img->set_pixel(0, 1, Color(0, 0, 0, 0.09f));
img->set_pixel(0, 2, Color(0, 0, 0, 0.25f));
img->set_pixel(1, 0, Color(0, 0, 0, 0.5f));
img->set_pixel(1, 1, Color(0, 0, 0, 0.75f));
img->set_pixel(1, 2, Color(0, 0, 0, 0.99f));
img->set_pixel(2, 0, Color(0, 0, 0, 1.f));
// Check different threshold values.
bit_map.create_from_image_alpha(img);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 5, "There are 5 values in the image that are smaller than the default threshold of 0.1.");
bit_map.create_from_image_alpha(img, 0.08f);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 6, "There are 6 values in the image that are smaller than the threshold of 0.08.");
bit_map.create_from_image_alpha(img, 1);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "There are no values in the image that are smaller than the threshold of 1, there is one value equal to 1, but we check for inequality only.");
}
TEST_CASE("[BitMap] Set bit") {
Size2i dim{ 256, 256 };
BitMap bit_map{};
// Setting a point before a bit map is created should not crash, because there are checks to see if we are out of bounds.
ERR_PRINT_OFF
bit_map.set_bitv(Point2i(128, 128), true);
bit_map.create(dim);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "All values should be initialized to false.");
bit_map.set_bitv(Point2i(128, 128), true);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 1, "One bit should be set to true.");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == true, "The bit at (128,128) should be set to true");
bit_map.set_bitv(Point2i(128, 128), false);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "The bit should now be set to false again");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "The bit at (128,128) should now be set to false again");
bit_map.create(dim);
bit_map.set_bitv(Point2i(512, 512), true);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Nothing should change as we were trying to edit a bit outside of the correct range.");
ERR_PRINT_ON
}
TEST_CASE("[BitMap] Get bit") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
ERR_PRINT_OFF
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 128)) == false, "Trying to access a bit outside of the BitMap's range should always return false");
bit_map.create(dim);
CHECK(bit_map.get_bitv(Point2i(128, 128)) == false);
bit_map.set_bit_rect(Rect2i(-1, -1, 257, 257), true);
// Checking that range is [0, 256).
CHECK(bit_map.get_bitv(Point2i(-1, 0)) == false);
CHECK(bit_map.get_bitv(Point2i(0, 0)) == true);
CHECK(bit_map.get_bitv(Point2i(128, 128)) == true);
CHECK(bit_map.get_bitv(Point2i(255, 255)) == true);
CHECK(bit_map.get_bitv(Point2i(256, 256)) == false);
CHECK(bit_map.get_bitv(Point2i(257, 257)) == false);
ERR_PRINT_ON
}
TEST_CASE("[BitMap] Set bit rect") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
// Although we have not setup the BitMap yet, this should not crash because we get an empty intersection inside of the method.
bit_map.set_bit_rect(Rect2i{ 0, 0, 128, 128 }, true);
bit_map.create(dim);
CHECK(bit_map.get_true_bit_count() == 0);
bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true);
CHECK(bit_map.get_true_bit_count() == 65536);
reset_bit_map(bit_map);
// Checking out of bounds handling.
ERR_PRINT_OFF
bit_map.set_bit_rect(Rect2i{ 128, 128, 256, 256 }, true);
CHECK(bit_map.get_true_bit_count() == 16384);
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i{ -128, -128, 256, 256 }, true);
CHECK(bit_map.get_true_bit_count() == 16384);
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i{ -128, -128, 512, 512 }, true);
CHECK(bit_map.get_true_bit_count() == 65536);
ERR_PRINT_ON
}
TEST_CASE("[BitMap] Get true bit count") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
CHECK(bit_map.get_true_bit_count() == 0);
bit_map.create(dim);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Uninitialized bit map should have no true bits");
bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, true);
CHECK(bit_map.get_true_bit_count() == 65536);
bit_map.set_bitv(Point2i{ 0, 0 }, false);
CHECK(bit_map.get_true_bit_count() == 65535);
bit_map.set_bit_rect(Rect2i{ 0, 0, 256, 256 }, false);
CHECK(bit_map.get_true_bit_count() == 0);
}
TEST_CASE("[BitMap] Get size") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Uninitialized bit map should have a size of 0x0");
bit_map.create(dim);
CHECK(bit_map.get_size() == Size2i(256, 256));
ERR_PRINT_OFF
bit_map.create(Size2i(-1, 0));
ERR_PRINT_ON
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "Invalid size should not be accepted by create");
bit_map.create(Size2i(256, 128));
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 128), "Bitmap should have updated size");
}
TEST_CASE("[BitMap] Resize") {
const Size2i dim{ 128, 128 };
BitMap bit_map{};
bit_map.resize(dim);
CHECK(bit_map.get_size() == dim);
bit_map.create(dim);
bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true);
bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner");
bit_map.resize(Size2i(64, 64));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 50, "There should be 25 bits in the top left corner, and 25 bits in the bottom right corner");
bit_map.create(dim);
ERR_PRINT_OFF
bit_map.resize(Size2i(-1, 128));
ERR_PRINT_ON
CHECK_MESSAGE(bit_map.get_size() == Size2i(128, 128), "When an invalid size is given the bit map will keep its size");
bit_map.create(dim);
bit_map.set_bit_rect(Rect2i(0, 0, 10, 10), true);
bit_map.set_bit_rect(Rect2i(118, 118, 10, 10), true);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 200, "There should be 100 bits in the top left corner, and 100 bits in the bottom right corner");
bit_map.resize(Size2i(256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 800, "There should still be 100 bits in the bottom right corner, and all new bits should be initialized to false");
CHECK_MESSAGE(bit_map.get_size() == Size2i(256, 256), "The bitmap should now be 256x256");
}
TEST_CASE("[BitMap] Grow and shrink mask") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
ERR_PRINT_OFF
bit_map.grow_mask(100, Rect2i(0, 0, 128, 128)); // Check if method does not crash when working with an uninitialized bit map.
ERR_PRINT_ON
CHECK_MESSAGE(bit_map.get_size() == Size2i(0, 0), "Size should still be equal to 0x0");
bit_map.create(dim);
bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Creating a square of 64x64 should be 4096 bits");
bit_map.grow_mask(0, Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 4096, "Growing with size of 0 should not change any bits");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == false, "Bits just outside of the square should not be set");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == false, "Bits just outside of the square should not be set");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == false, "Bits just outside of the square should not be set");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == false, "Bits just outside of the square should not be set");
bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 4352, "We should have 4*64 (perimeter of square) more bits set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(95, 128)) == true, "Bits that were just outside of the square should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(160, 128)) == true, "Bits that were just outside of the square should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 95)) == true, "Bits that were just outside of the square should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(128, 160)) == true, "Bits that were just outside of the square should now be set to true");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true);
CHECK(bit_map.get_true_bit_count() == 1);
bit_map.grow_mask(32, Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 3209, "Creates a circle around the initial bit with a radius of 32 bits. Any bit that has a distance within this radius will be set to true");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(127, 127, 1, 1), true);
for (int i = 0; i < 32; i++) {
bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
}
CHECK_MESSAGE(bit_map.get_true_bit_count() == 2113, "Creates a diamond around the initial bit with diagonals that are 65 bits long.");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(123, 123, 10, 10), true);
CHECK(bit_map.get_true_bit_count() == 100);
bit_map.grow_mask(-11, Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 0, "Shrinking by more than the width of the square should totally remove it.");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(96, 96, 64, 64), true);
CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == true, "Bits on the edge of the square should be true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == true, "Bits on the edge of the square should be true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == true, "Bits on the edge of the square should be true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == true, "Bits on the edge of the square should be true");
bit_map.grow_mask(-1, Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(bit_map.get_true_bit_count() == 3844, "Shrinking by 1 should set 4*63=252 bits to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(96, 129)) == false, "Bits that were on the edge of the square should now be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(159, 129)) == false, "Bits that were on the edge of the square should now be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 96)) == false, "Bits that were on the edge of the square should now be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(129, 159)) == false, "Bits that were on the edge of the square should now be set to false");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(125, 125, 1, 6), true);
bit_map.set_bit_rect(Rect2i(130, 125, 1, 6), true);
bit_map.set_bit_rect(Rect2i(125, 130, 6, 1), true);
CHECK(bit_map.get_true_bit_count() == 16);
CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == false, "Bits that are on the edge of the shape should be set to false");
bit_map.grow_mask(1, Rect2i(0, 0, 256, 256));
CHECK(bit_map.get_true_bit_count() == 48);
CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 131)) == true, "Bits that were on the edge of the shape should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 130)) == true, "Bits that were on the edge of the shape should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(125, 124)) == true, "Bits that were on the edge of the shape should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(130, 124)) == true, "Bits that were on the edge of the shape should now be set to true");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 124)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(126, 124)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(124, 131)) == false, "Bits that are on the edge of the shape should be set to false");
CHECK_MESSAGE(bit_map.get_bitv(Point2i(131, 131)) == false, "Bits that are on the edge of the shape should be set to false");
}
TEST_CASE("[BitMap] Blit") {
Point2i blit_pos{ 128, 128 };
Point2i bit_map_size{ 256, 256 };
Point2i blit_size{ 32, 32 };
BitMap bit_map{};
Ref<BitMap> blit_bit_map{};
// Testing null reference to blit bit map.
ERR_PRINT_OFF
bit_map.blit(blit_pos, blit_bit_map);
ERR_PRINT_ON
blit_bit_map.instantiate();
// Testing if uninitialized blit bit map and uninitialized bit map does not crash
bit_map.blit(blit_pos, blit_bit_map);
// Testing if uninitialized bit map does not crash
blit_bit_map->create(blit_size);
bit_map.blit(blit_pos, blit_bit_map);
// 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 initialized does not crash.
blit_bit_map->create(blit_size);
bit_map.blit(blit_pos, blit_bit_map);
bit_map.set_bit_rect(Rect2i{ 127, 127, 3, 3 }, true);
CHECK(bit_map.get_true_bit_count() == 9);
bit_map.blit(Point2i(112, 112), blit_bit_map);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "No bits should have been changed, as the blit bit map only contains falses");
bit_map.create(bit_map_size);
blit_bit_map->create(blit_size);
blit_bit_map->set_bit_rect(Rect2i(15, 15, 3, 3), true);
CHECK(blit_bit_map->get_true_bit_count() == 9);
CHECK(bit_map.get_true_bit_count() == 0);
bit_map.blit(Point2i(112, 112), blit_bit_map);
CHECK_MESSAGE(bit_map.get_true_bit_count() == 9, "All true bits should have been moved to the bit map");
for (int x = 127; x < 129; ++x) {
for (int y = 127; y < 129; ++y) {
CHECK_MESSAGE(bit_map.get_bitv(Point2i(x, y)) == true, "All true bits should have been moved to the bit map");
}
}
}
TEST_CASE("[BitMap] Convert to image") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
Ref<Image> img;
ERR_PRINT_OFF
img = bit_map.convert_to_image();
ERR_PRINT_ON
CHECK_MESSAGE(img.is_valid(), "We should receive a valid Image Object even if BitMap is not created yet");
CHECK_MESSAGE(img->get_format() == Image::FORMAT_L8, "We should receive a valid Image Object even if BitMap is not created yet");
CHECK_MESSAGE(img->get_size() == (Size2i(0, 0)), "Image should have no width or height, because BitMap has not yet been created");
bit_map.create(dim);
img = bit_map.convert_to_image();
CHECK_MESSAGE(img->get_size() == dim, "Image should have the same dimensions as the BitMap");
CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(0, 0, 0)), "BitMap is initialized to all 0's, so Image should be all black");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(0, 0, 128, 128), true);
img = bit_map.convert_to_image();
CHECK_MESSAGE(img->get_pixel(0, 0).is_equal_approx(Color(1, 1, 1)), "BitMap's top-left quadrant is all 1's, so Image should be white");
CHECK_MESSAGE(img->get_pixel(255, 255).is_equal_approx(Color(0, 0, 0)), "All other quadrants were 0's, so these should be black");
}
TEST_CASE("[BitMap] Clip to polygon") {
const Size2i dim{ 256, 256 };
BitMap bit_map{};
Vector<Vector<Vector2>> polygons;
ERR_PRINT_OFF
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
ERR_PRINT_ON
CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was not initialized");
bit_map.create(dim);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
CHECK_MESSAGE(polygons.size() == 0, "We should have no polygons, because the BitMap was all 0's");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(0, 0, 64, 64), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(0, 0, 32, 32), true);
bit_map.set_bit_rect(Rect2i(64, 64, 32, 32), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
CHECK_MESSAGE(polygons.size() == 2, "We should have exactly 2 polygons");
CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[1].size() == 4, "The polygon should have exactly 4 points");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true);
bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
CHECK_MESSAGE(polygons[0].size() == 12, "The polygon should have exactly 12 points");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(124, 112, 8, 32), true);
bit_map.set_bit_rect(Rect2i(112, 124, 32, 8), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 128, 128));
CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
CHECK_MESSAGE(polygons[0].size() == 6, "The polygon should have exactly 6 points");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(0, 0, 64, 64), true);
bit_map.set_bit_rect(Rect2i(64, 64, 64, 64), true);
bit_map.set_bit_rect(Rect2i(192, 128, 64, 64), true);
bit_map.set_bit_rect(Rect2i(128, 192, 64, 64), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 256, 256));
CHECK_MESSAGE(polygons.size() == 4, "We should have exactly 4 polygons");
CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[1].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[2].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[3].size() == 4, "The polygon should have exactly 4 points");
reset_bit_map(bit_map);
bit_map.set_bit(0, 0, true);
bit_map.set_bit(2, 0, true);
bit_map.set_bit_rect(Rect2i(1, 1, 1, 2), true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 3, 3));
CHECK_MESSAGE(polygons.size() == 3, "We should have exactly 3 polygons");
CHECK_MESSAGE(polygons[0].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[1].size() == 4, "The polygon should have exactly 4 points");
CHECK_MESSAGE(polygons[2].size() == 4, "The polygon should have exactly 4 points");
reset_bit_map(bit_map);
bit_map.set_bit_rect(Rect2i(0, 0, 2, 1), true);
bit_map.set_bit_rect(Rect2i(0, 2, 3, 1), true);
bit_map.set_bit(0, 1, true);
bit_map.set_bit(2, 1, true);
polygons = bit_map.clip_opaque_to_polygons(Rect2i(0, 0, 4, 4));
CHECK_MESSAGE(polygons.size() == 1, "We should have exactly 1 polygon");
CHECK_MESSAGE(polygons[0].size() == 6, "The polygon should have exactly 6 points");
}
} // namespace TestBitmap

64
tests/scene/test_button.h Normal file
View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* test_button.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/button.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestButton {
TEST_CASE("[SceneTree][Button] is_hovered()") {
// Create new button instance.
Button *button = memnew(Button);
CHECK(button != nullptr);
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(button);
// Set up button's size and position.
button->set_size(Size2i(50, 50));
button->set_position(Size2i(10, 10));
// Button should initially be not hovered.
CHECK(button->is_hovered() == false);
// Simulate mouse entering the button.
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(25, 25), MouseButtonMask::NONE, Key::NONE);
CHECK(button->is_hovered() == true);
// Simulate mouse exiting the button.
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(150, 150), MouseButtonMask::NONE, Key::NONE);
CHECK(button->is_hovered() == false);
memdelete(button);
}
} //namespace TestButton

View File

@@ -0,0 +1,315 @@
/**************************************************************************/
/* test_camera_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/camera_2d.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestCamera2D {
TEST_CASE("[SceneTree][Camera2D] Getters and setters") {
Camera2D *test_camera = memnew(Camera2D);
SUBCASE("AnchorMode") {
test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT);
CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_FIXED_TOP_LEFT);
test_camera->set_anchor_mode(Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER);
CHECK(test_camera->get_anchor_mode() == Camera2D::AnchorMode::ANCHOR_MODE_DRAG_CENTER);
}
SUBCASE("ProcessCallback") {
test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS);
CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_PHYSICS);
test_camera->set_process_callback(Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE);
CHECK(test_camera->get_process_callback() == Camera2D::Camera2DProcessCallback::CAMERA2D_PROCESS_IDLE);
}
SUBCASE("Drag") {
constexpr float drag_left_margin = 0.8f;
constexpr float drag_top_margin = 0.8f;
constexpr float drag_right_margin = 0.8f;
constexpr float drag_bottom_margin = 0.8f;
constexpr float drag_horizontal_offset1 = 0.5f;
constexpr float drag_horizontal_offset2 = -0.5f;
constexpr float drag_vertical_offset1 = 0.5f;
constexpr float drag_vertical_offset2 = -0.5f;
test_camera->set_drag_margin(SIDE_LEFT, drag_left_margin);
CHECK(test_camera->get_drag_margin(SIDE_LEFT) == drag_left_margin);
test_camera->set_drag_margin(SIDE_TOP, drag_top_margin);
CHECK(test_camera->get_drag_margin(SIDE_TOP) == drag_top_margin);
test_camera->set_drag_margin(SIDE_RIGHT, drag_right_margin);
CHECK(test_camera->get_drag_margin(SIDE_RIGHT) == drag_right_margin);
test_camera->set_drag_margin(SIDE_BOTTOM, drag_bottom_margin);
CHECK(test_camera->get_drag_margin(SIDE_BOTTOM) == drag_bottom_margin);
test_camera->set_drag_horizontal_enabled(true);
CHECK(test_camera->is_drag_horizontal_enabled());
test_camera->set_drag_horizontal_enabled(false);
CHECK_FALSE(test_camera->is_drag_horizontal_enabled());
test_camera->set_drag_horizontal_offset(drag_horizontal_offset1);
CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset1);
test_camera->set_drag_horizontal_offset(drag_horizontal_offset2);
CHECK(test_camera->get_drag_horizontal_offset() == drag_horizontal_offset2);
test_camera->set_drag_vertical_enabled(true);
CHECK(test_camera->is_drag_vertical_enabled());
test_camera->set_drag_vertical_enabled(false);
CHECK_FALSE(test_camera->is_drag_vertical_enabled());
test_camera->set_drag_vertical_offset(drag_vertical_offset1);
CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset1);
test_camera->set_drag_vertical_offset(drag_vertical_offset2);
CHECK(test_camera->get_drag_vertical_offset() == drag_vertical_offset2);
}
SUBCASE("Drawing") {
test_camera->set_margin_drawing_enabled(true);
CHECK(test_camera->is_margin_drawing_enabled());
test_camera->set_margin_drawing_enabled(false);
CHECK_FALSE(test_camera->is_margin_drawing_enabled());
test_camera->set_limit_drawing_enabled(true);
CHECK(test_camera->is_limit_drawing_enabled());
test_camera->set_limit_drawing_enabled(false);
CHECK_FALSE(test_camera->is_limit_drawing_enabled());
test_camera->set_screen_drawing_enabled(true);
CHECK(test_camera->is_screen_drawing_enabled());
test_camera->set_screen_drawing_enabled(false);
CHECK_FALSE(test_camera->is_screen_drawing_enabled());
}
SUBCASE("Enabled") {
test_camera->set_enabled(true);
CHECK(test_camera->is_enabled());
test_camera->set_enabled(false);
CHECK_FALSE(test_camera->is_enabled());
}
SUBCASE("Rotation") {
constexpr float rotation_smoothing_speed = 20.0f;
test_camera->set_ignore_rotation(true);
CHECK(test_camera->is_ignoring_rotation());
test_camera->set_ignore_rotation(false);
CHECK_FALSE(test_camera->is_ignoring_rotation());
test_camera->set_rotation_smoothing_enabled(true);
CHECK(test_camera->is_rotation_smoothing_enabled());
test_camera->set_rotation_smoothing_speed(rotation_smoothing_speed);
CHECK(test_camera->get_rotation_smoothing_speed() == rotation_smoothing_speed);
}
SUBCASE("Zoom") {
const Vector2 zoom = Vector2(4, 4);
test_camera->set_zoom(zoom);
CHECK(test_camera->get_zoom() == zoom);
}
SUBCASE("Offset") {
const Vector2 offset = Vector2(100, 100);
test_camera->set_offset(offset);
CHECK(test_camera->get_offset() == offset);
}
SUBCASE("Limit") {
constexpr int limit_left = 100;
constexpr int limit_top = 100;
constexpr int limit_right = 100;
constexpr int limit_bottom = 100;
test_camera->set_limit_smoothing_enabled(true);
CHECK(test_camera->is_limit_smoothing_enabled());
test_camera->set_limit_smoothing_enabled(false);
CHECK_FALSE(test_camera->is_limit_smoothing_enabled());
test_camera->set_limit(SIDE_LEFT, limit_left);
CHECK(test_camera->get_limit(SIDE_LEFT) == limit_left);
test_camera->set_limit(SIDE_TOP, limit_top);
CHECK(test_camera->get_limit(SIDE_TOP) == limit_top);
test_camera->set_limit(SIDE_RIGHT, limit_right);
CHECK(test_camera->get_limit(SIDE_RIGHT) == limit_right);
test_camera->set_limit(SIDE_BOTTOM, limit_bottom);
CHECK(test_camera->get_limit(SIDE_BOTTOM) == limit_bottom);
}
SUBCASE("Position") {
constexpr float smoothing_speed = 20.0f;
test_camera->set_position_smoothing_enabled(true);
CHECK(test_camera->is_position_smoothing_enabled());
test_camera->set_position_smoothing_speed(smoothing_speed);
CHECK(test_camera->get_position_smoothing_speed() == smoothing_speed);
}
memdelete(test_camera);
}
TEST_CASE("[SceneTree][Camera2D] Camera positioning") {
SubViewport *mock_viewport = memnew(SubViewport);
Camera2D *test_camera = memnew(Camera2D);
mock_viewport->set_size(Vector2(400, 200));
SceneTree::get_singleton()->get_root()->add_child(mock_viewport);
mock_viewport->add_child(test_camera);
SUBCASE("Anchor mode") {
test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_DRAG_CENTER);
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
test_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT);
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
}
SUBCASE("Offset") {
test_camera->set_offset(Vector2(100, 100));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(100, 100)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
test_camera->set_offset(Vector2(-100, 300));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(-100, 300)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
test_camera->set_offset(Vector2(0, 0));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
}
SUBCASE("Limits") {
test_camera->set_limit(SIDE_LEFT, 100);
test_camera->set_limit(SIDE_TOP, 50);
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(300, 150)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
test_camera->set_limit(SIDE_LEFT, 0);
test_camera->set_limit(SIDE_TOP, 0);
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 100)));
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
}
SUBCASE("Drag") {
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0)));
// horizontal
test_camera->set_drag_horizontal_enabled(true);
test_camera->set_drag_margin(SIDE_RIGHT, 0.5);
test_camera->set_position(Vector2(100, 100));
test_camera->force_update_scroll();
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 100)));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 100)));
test_camera->set_position(Vector2(101, 101));
test_camera->force_update_scroll();
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(1, 101)));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(1, 101)));
// test align
test_camera->set_position(Vector2(0, 0));
test_camera->align();
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(0, 0)));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(0, 0)));
// vertical
test_camera->set_drag_vertical_enabled(true);
test_camera->set_drag_horizontal_enabled(false);
test_camera->set_drag_margin(SIDE_TOP, 0.3);
test_camera->set_position(Vector2(200, -20));
test_camera->force_update_scroll();
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(200, 0)));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(200, 0)));
test_camera->set_position(Vector2(250, -55));
test_camera->force_update_scroll();
CHECK(test_camera->get_camera_position().is_equal_approx(Vector2(250, -25)));
CHECK(test_camera->get_camera_screen_center().is_equal_approx(Vector2(250, -25)));
}
memdelete(test_camera);
memdelete(mock_viewport);
}
TEST_CASE("[SceneTree][Camera2D] Transforms") {
SubViewport *mock_viewport = memnew(SubViewport);
Camera2D *test_camera = memnew(Camera2D);
mock_viewport->set_size(Vector2(400, 200));
SceneTree::get_singleton()->get_root()->add_child(mock_viewport);
mock_viewport->add_child(test_camera);
SUBCASE("Default camera") {
Transform2D xform = mock_viewport->get_canvas_transform();
// x,y are basis vectors, origin = screen center
Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
}
SUBCASE("Zoom") {
test_camera->set_zoom(Vector2(0.5, 2));
Transform2D xform = mock_viewport->get_canvas_transform();
Transform2D test_xform = Transform2D(Vector2(0.5, 0), Vector2(0, 2), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
test_camera->set_zoom(Vector2(10, 10));
xform = mock_viewport->get_canvas_transform();
test_xform = Transform2D(Vector2(10, 0), Vector2(0, 10), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
test_camera->set_zoom(Vector2(1, 1));
xform = mock_viewport->get_canvas_transform();
test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
}
SUBCASE("Rotation") {
test_camera->set_rotation(Math::PI / 2);
Transform2D xform = mock_viewport->get_canvas_transform();
Transform2D test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
test_camera->set_ignore_rotation(false);
xform = mock_viewport->get_canvas_transform();
test_xform = Transform2D(Vector2(0, -1), Vector2(1, 0), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
test_camera->set_rotation(-1 * Math::PI);
test_camera->force_update_scroll();
xform = mock_viewport->get_canvas_transform();
test_xform = Transform2D(Vector2(-1, 0), Vector2(0, -1), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
test_camera->set_rotation(0);
test_camera->force_update_scroll();
xform = mock_viewport->get_canvas_transform();
test_xform = Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(200, 100));
CHECK(xform.is_equal_approx(test_xform));
}
memdelete(test_camera);
memdelete(mock_viewport);
}
} // namespace TestCamera2D

View File

@@ -0,0 +1,368 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "scene/3d/camera_3d.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
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 depth_near = 0.2f;
constexpr float depth_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(depth_near);
CHECK(test_camera->get_near() == depth_near);
test_camera->set_far(depth_far);
CHECK(test_camera->get_far() == depth_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)));
CHECK(test_camera->project_position(Vector2(200, 100), test_camera->get_far()).is_equal_approx(Vector3(0, 0, -test_camera->get_far())));
// Top left.
CHECK(test_camera->project_position(Vector2(0, 0), 1.5f).is_equal_approx(Vector3(-5.0f, 2.5f, -1.5f)));
CHECK(test_camera->project_position(Vector2(0, 0), test_camera->get_near()).is_equal_approx(Vector3(-5.0f, 2.5f, -test_camera->get_near())));
// Bottom right.
CHECK(test_camera->project_position(Vector2(400, 200), 5.0f).is_equal_approx(Vector3(5.0f, -2.5f, -5.0f)));
CHECK(test_camera->project_position(Vector2(400, 200), test_camera->get_far()).is_equal_approx(Vector3(5.0f, -2.5f, -test_camera->get_far())));
}
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)));
CHECK(test_camera->project_position(Vector2(200, 100), test_camera->get_far()).is_equal_approx(Vector3(0, 0, -1.0f) * test_camera->get_far()));
// 3/4th way to Top left.
CHECK(test_camera->project_position(Vector2(100, 50), 0.5f).is_equal_approx(Vector3(-Math::SQRT3 * 0.5f, Math::SQRT3 * 0.25f, -0.5f)));
CHECK(test_camera->project_position(Vector2(100, 50), 1.0f).is_equal_approx(Vector3(-Math::SQRT3, Math::SQRT3 * 0.5f, -1.0f)));
CHECK(test_camera->project_position(Vector2(100, 50), test_camera->get_near()).is_equal_approx(Vector3(-Math::SQRT3, Math::SQRT3 * 0.5f, -1.0f) * test_camera->get_near()));
// 3/4th way to Bottom right.
CHECK(test_camera->project_position(Vector2(300, 150), 0.5f).is_equal_approx(Vector3(Math::SQRT3 * 0.5f, -Math::SQRT3 * 0.25f, -0.5f)));
CHECK(test_camera->project_position(Vector2(300, 150), 1.0f).is_equal_approx(Vector3(Math::SQRT3, -Math::SQRT3 * 0.5f, -1.0f)));
CHECK(test_camera->project_position(Vector2(300, 150), test_camera->get_far()).is_equal_approx(Vector3(Math::SQRT3, -Math::SQRT3 * 0.5f, -1.0f) * test_camera->get_far()));
}
}
// 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(-Math::SQRT3 * 0.5f, Math::SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(100, 50)));
WARN(test_camera->unproject_position(Vector3(-Math::SQRT3, Math::SQRT3 * 0.5f, -1.0f)).is_equal_approx(Vector2(100, 50)));
// 3/4th way to Bottom right.
CHECK(test_camera->unproject_position(Vector3(Math::SQRT3 * 0.5f, -Math::SQRT3 * 0.25f, -0.5f)).is_equal_approx(Vector2(300, 150)));
CHECK(test_camera->unproject_position(Vector3(Math::SQRT3, -Math::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(-Math::SQRT3, Math::SQRT3 / 2, -0.5f).normalized()));
// Bottom right.
CHECK(test_camera->project_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(Math::SQRT3, -Math::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(-Math::SQRT3, Math::SQRT3 / 2, -0.5f).normalized()));
// Bottom right.
CHECK(test_camera->project_local_ray_normal(Vector2(400, 200)).is_equal_approx(Vector3(Math::SQRT3, -Math::SQRT3 / 2, -0.5f).normalized()));
}
}
memdelete(test_camera);
memdelete(mock_viewport);
}

5707
tests/scene/test_code_edit.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* test_color_picker.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/color_picker.h"
#include "tests/test_macros.h"
namespace TestColorPicker {
TEST_CASE("[SceneTree][ColorPicker]") {
ColorPicker *cp = memnew(ColorPicker);
Window *root = SceneTree::get_singleton()->get_root();
root->add_child(cp);
SUBCASE("[COLOR_PICKER] Mouse movement after Slider release") {
Point2i pos_left = Point2i(50, 340); // On the left side of the red slider.
Point2i pos_right = Point2i(200, 340); // On the right side of the red slider.
SEND_GUI_MOUSE_MOTION_EVENT(pos_left, MouseButtonMask::NONE, Key::NONE);
SEND_GUI_MOUSE_BUTTON_EVENT(pos_left, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(cp->get_pick_color().r < 0.5);
SEND_GUI_MOUSE_MOTION_EVENT(pos_right, MouseButtonMask::LEFT, Key::NONE);
CHECK(cp->get_pick_color().r > 0.5);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(pos_right, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(pos_left, MouseButtonMask::NONE, Key::NONE);
CHECK(cp->get_pick_color().r > 0.5); // Issue GH-77773.
}
}
} // namespace TestColorPicker

1215
tests/scene/test_control.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
/**************************************************************************/
/* test_convert_transform_modifier_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. */
/**************************************************************************/
#pragma once
#include "tests/test_macros.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/convert_transform_modifier_3d.h"
namespace TestConvertTransformModifier3D {
Transform3D make_random_transform_3d(int p_seed) {
RandomNumberGenerator rng;
rng.set_seed(p_seed);
Vector3 pos;
pos.x = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
pos.y = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
pos.z = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
Quaternion rot;
rot.x = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.y = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.z = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.w = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot = rot.normalized();
Vector3 scl;
scl.x = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
scl.y = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
scl.z = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
return Transform3D(Basis(rot).scaled(scl), pos);
}
TEST_CASE("[SceneTree][ConvertTransformModifier3D]") {
SceneTree *tree = SceneTree::get_singleton();
int seed = 12345;
Skeleton3D *skeleton = memnew(Skeleton3D);
ConvertTransformModifier3D *mod = memnew(ConvertTransformModifier3D);
// Instead of awaiting the process to wait to finish deferred process and watch "skeleton_updated" signal,
// force notify NOTIFICATION_UPDATE_SKELETON and get the modified pose from the BoneAttachment's transform.
BoneAttachment3D *modified = memnew(BoneAttachment3D);
tree->get_root()->add_child(skeleton);
int root = skeleton->add_bone("root");
skeleton->set_bone_rest(root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(root, make_random_transform_3d(++seed));
int apl_root = skeleton->add_bone("apl_root");
skeleton->set_bone_parent(apl_root, root);
skeleton->set_bone_rest(apl_root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(apl_root, make_random_transform_3d(++seed));
int apl_bone = skeleton->add_bone("apl_bone");
skeleton->set_bone_parent(apl_bone, apl_root);
skeleton->set_bone_rest(apl_bone, make_random_transform_3d(++seed));
skeleton->set_bone_pose(apl_bone, make_random_transform_3d(++seed));
int tgt_root = skeleton->add_bone("tgt_root");
skeleton->set_bone_parent(tgt_root, root);
skeleton->set_bone_rest(tgt_root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(tgt_root, make_random_transform_3d(++seed));
int tgt_bone = skeleton->add_bone("tgt_bone");
skeleton->set_bone_parent(tgt_bone, tgt_root);
skeleton->set_bone_rest(tgt_bone, make_random_transform_3d(++seed));
skeleton->set_bone_pose(tgt_bone, make_random_transform_3d(++seed));
skeleton->add_child(mod);
skeleton->add_child(modified);
modified->set_rotation_edit_mode(Node3D::ROTATION_EDIT_MODE_QUATERNION);
modified->set_bone_idx(apl_bone);
mod->set_setting_count(1);
mod->set_reference_bone(0, tgt_bone);
mod->set_apply_bone(0, apl_bone);
mod->set_reference_axis(0, Vector3::AXIS_X);
mod->set_apply_axis(0, Vector3::AXIS_Y);
// ===== [ConvertTransformModifier3D] Position x to y =====
mod->set_reference_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_POSITION);
mod->set_reference_range_min(0, -100.0);
mod->set_reference_range_max(0, 100.0);
mod->set_apply_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_POSITION);
mod->set_apply_range_min(0, -100.0);
mod->set_apply_range_max(0, 100.0);
SUBCASE("[ConvertTransformModifier3D] Position x to y, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin.y));
}
SUBCASE("[ConvertTransformModifier3D] Position x to y, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).y));
}
SUBCASE("[ConvertTransformModifier3D] Position x to y, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin).y));
}
SUBCASE("[ConvertTransformModifier3D] Position x to y, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).y));
}
// ===== [ConvertTransformModifier3D] Rotation (roll) x to y =====
mod->set_reference_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_ROTATION);
mod->set_reference_range_min(0, -180.0);
mod->set_reference_range_max(0, 180.0);
mod->set_apply_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_ROTATION);
mod->set_apply_range_min(0, -180.0);
mod->set_apply_range_max(0, 180.0);
SUBCASE("[ConvertTransformModifier3D] Rotation (roll) x to y, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Y))));
}
SUBCASE("[ConvertTransformModifier3D] Rotation (roll) x to y, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Y))));
}
SUBCASE("[ConvertTransformModifier3D] Rotation (roll) x to y, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Y))));
}
SUBCASE("[ConvertTransformModifier3D] Rotation (roll) x to y, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Y))));
}
// ===== [ConvertTransformModifier3D] Scale x to y =====
mod->set_reference_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_SCALE);
mod->set_reference_range_min(0, 0);
mod->set_reference_range_max(0, 10.0);
mod->set_apply_transform_mode(0, ConvertTransformModifier3D::TRANSFORM_MODE_SCALE);
mod->set_apply_range_min(0, 0);
mod->set_apply_range_max(0, 10.0);
SUBCASE("[ConvertTransformModifier3D] Scale x to y, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale().y));
}
SUBCASE("[ConvertTransformModifier3D] Scale x to y, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).y));
}
SUBCASE("[ConvertTransformModifier3D] Scale x to y, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()).y));
}
SUBCASE("[ConvertTransformModifier3D] Scale x to y, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).y));
}
memdelete(modified);
memdelete(mod);
memdelete(skeleton);
}
} // namespace TestConvertTransformModifier3D

View File

@@ -0,0 +1,529 @@
/**************************************************************************/
/* test_copy_transform_modifier_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. */
/**************************************************************************/
#pragma once
#include "tests/test_macros.h"
#include "scene/3d/bone_attachment_3d.h"
#include "scene/3d/copy_transform_modifier_3d.h"
namespace TestCopyTransformModifier3D {
Transform3D make_random_transform_3d(int p_seed) {
RandomNumberGenerator rng;
rng.set_seed(p_seed);
Vector3 pos;
pos.x = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
pos.y = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
pos.z = rng.randf_range(-10.0, 10.0);
rng.set_seed(++p_seed);
Quaternion rot;
rot.x = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.y = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.z = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot.w = rng.randf_range(-1.0, 1.0);
rng.set_seed(++p_seed);
rot = rot.normalized();
Vector3 scl;
scl.x = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
scl.y = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
scl.z = rng.randf_range(0.5, 2.0);
rng.set_seed(++p_seed);
return Transform3D(Basis(rot).scaled(scl), pos);
}
Vector3 flip_x(Vector3 p_pos) {
return Vector3(-p_pos.x, p_pos.y, p_pos.z);
}
Vector3 flip_xy(Vector3 p_pos) {
return Vector3(-p_pos.x, -p_pos.y, p_pos.z);
}
Vector3 flip_all(Vector3 p_pos) {
return -p_pos;
}
// Quaternion's phase can be confused by inversion, it is aligned via casting to Basis.
Quaternion flip_x(Quaternion p_rot) {
return Basis(Quaternion(-p_rot.x, p_rot.y, p_rot.z, p_rot.w).normalized()).get_rotation_quaternion();
}
Quaternion flip_xy(Quaternion p_rot) {
return Basis(Quaternion(-p_rot.x, -p_rot.y, p_rot.z, p_rot.w).normalized()).get_rotation_quaternion();
}
Quaternion flip_all(Quaternion p_rot) {
return Basis(p_rot.inverse()).get_rotation_quaternion();
}
Vector3 inv_x(Vector3 p_scl) {
return Vector3(1.0 / p_scl.x, p_scl.y, p_scl.z);
}
Vector3 inv_xy(Vector3 p_scl) {
return Vector3(1.0 / p_scl.x, 1.0 / p_scl.y, p_scl.z);
}
Vector3 inv_all(Vector3 p_scl) {
return Vector3(1.0, 1.0, 1.0) / p_scl;
}
TEST_CASE("[SceneTree][CopyTransformModifier3D]") {
SceneTree *tree = SceneTree::get_singleton();
int seed = 12345;
Skeleton3D *skeleton = memnew(Skeleton3D);
CopyTransformModifier3D *mod = memnew(CopyTransformModifier3D);
// Instead of awaiting the process to wait to finish deferred process and watch "skeleton_updated" signal,
// force notify NOTIFICATION_UPDATE_SKELETON and get the modified pose from the BoneAttachment's transform.
BoneAttachment3D *modified = memnew(BoneAttachment3D);
tree->get_root()->add_child(skeleton);
int root = skeleton->add_bone("root");
skeleton->set_bone_rest(root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(root, make_random_transform_3d(++seed));
int apl_root = skeleton->add_bone("apl_root");
skeleton->set_bone_parent(apl_root, root);
skeleton->set_bone_rest(apl_root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(apl_root, make_random_transform_3d(++seed));
int apl_bone = skeleton->add_bone("apl_bone");
skeleton->set_bone_parent(apl_bone, apl_root);
skeleton->set_bone_rest(apl_bone, make_random_transform_3d(++seed));
skeleton->set_bone_pose(apl_bone, make_random_transform_3d(++seed));
int tgt_root = skeleton->add_bone("tgt_root");
skeleton->set_bone_parent(tgt_root, root);
skeleton->set_bone_rest(tgt_root, make_random_transform_3d(++seed));
skeleton->set_bone_pose(tgt_root, make_random_transform_3d(++seed));
int tgt_bone = skeleton->add_bone("tgt_bone");
skeleton->set_bone_parent(tgt_bone, tgt_root);
skeleton->set_bone_rest(tgt_bone, make_random_transform_3d(++seed));
skeleton->set_bone_pose(tgt_bone, make_random_transform_3d(++seed));
skeleton->add_child(mod);
skeleton->add_child(modified);
modified->set_rotation_edit_mode(Node3D::ROTATION_EDIT_MODE_QUATERNION);
modified->set_bone_idx(apl_bone);
mod->set_setting_count(1);
mod->set_reference_bone(0, tgt_bone);
mod->set_apply_bone(0, apl_bone);
mod->set_copy_position(0, true);
mod->set_copy_rotation(0, true);
mod->set_copy_scale(0, true);
// ===== [CopyTransformModifier3D] Enable 1 axis =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, false);
mod->set_axis_z_enabled(0, false);
mod->set_axis_x_inverted(0, false);
mod->set_axis_y_inverted(0, false);
mod->set_axis_z_inverted(0, false);
SUBCASE("[CopyTransformModifier3D] Enable 1 axis, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin.x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X))),
"Rotation x (roll x) is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale().x),
"Scale x is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 1 axis, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X))),
"Rotation x (roll x) is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).x),
"Scale x is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 1 axis, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X))),
"Rotation x (roll x) is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()).x),
"Scale x is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 1 axis, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X)),
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_X))),
"Rotation x (roll x) is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).x),
"Scale x is copied correctly.");
}
// ===== [CopyTransformModifier3D] Enable 2 axes =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, true);
mod->set_axis_z_enabled(0, false);
mod->set_axis_x_inverted(0, false);
mod->set_axis_y_inverted(0, false);
mod->set_axis_z_inverted(0, false);
SUBCASE("[CopyTransformModifier3D] Enable 2 axes, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin.x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).y,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin.y),
"Position y is copied correctly.");
CHECK_MESSAGE(Math::is_zero_approx(
BoneConstraint3D::get_roll_angle((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Z))),
"Rotation z (roll z) is zero correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale().x),
"Scale x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).y,
(skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale().y),
"Scale y is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 2 axes, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_position(tgt_bone).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).y),
"Position y is copied correctly.");
CHECK_MESSAGE(Math::is_zero_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Z))),
"Rotation z (roll z) is zero correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).x),
"Scale x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
skeleton->get_bone_pose_scale(tgt_bone).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).y),
"Scale y is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 2 axes, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin).y),
"Position y is copied correctly.");
CHECK_MESSAGE(Math::is_zero_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Z))),
"Rotation z (roll z) is zero correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()).x),
"Scale x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()).y),
"Scale y is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable 2 axes, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).x),
"Position x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)).y),
"Position y is copied correctly.");
CHECK_MESSAGE(Math::is_zero_approx(
BoneConstraint3D::get_roll_angle(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion(), BoneConstraint3D::get_vector_from_axis(Vector3::AXIS_Z))),
"Rotation z (roll z) is zero correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).x,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).x),
"Scale x is copied correctly.");
CHECK_MESSAGE(Math::is_equal_approx(
(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).y,
((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)).y),
"Scale y is copied correctly.");
}
// ===== [CopyTransformModifier3D] Enable all axes =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, true);
mod->set_axis_z_enabled(0, true);
mod->set_axis_x_inverted(0, false);
mod->set_axis_y_inverted(0, false);
mod->set_axis_z_inverted(0, false);
SUBCASE("[CopyTransformModifier3D] Enable all axes, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(skeleton->get_bone_pose_position(tgt_bone).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin), "Position is copied correctly.");
CHECK_MESSAGE(Basis(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion())), "Rotation is copied correctly.");
CHECK_MESSAGE(skeleton->get_bone_pose_scale(tgt_bone).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale()), "Scale is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(skeleton->get_bone_pose_position(tgt_bone).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied correctly.");
CHECK_MESSAGE(Basis(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()), "Rotation is copied correctly.");
CHECK_MESSAGE(skeleton->get_bone_pose_scale(tgt_bone).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE((skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin), "Position is copied correctly.");
CHECK_MESSAGE(Basis(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion())), "Rotation is copied correctly.");
CHECK_MESSAGE((skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()), "Scale is copied correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE((skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied correctly.");
CHECK_MESSAGE(Basis(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()), "Rotation is copied correctly.");
CHECK_MESSAGE((skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied correctly.");
}
// ===== [CopyTransformModifier3D] Enable all axes, invert 1 axis =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, true);
mod->set_axis_z_enabled(0, true);
mod->set_axis_x_inverted(0, true);
mod->set_axis_y_inverted(0, false);
mod->set_axis_z_inverted(0, false);
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 1 axis, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_x(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 1 axis, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_x(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 1 axis, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_x(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_x(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 1 axis, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_x(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_x(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_x(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
// ===== [CopyTransformModifier3D] Enable all axes, invert 2 axes =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, true);
mod->set_axis_z_enabled(0, true);
mod->set_axis_x_inverted(0, true);
mod->set_axis_y_inverted(0, true);
mod->set_axis_z_inverted(0, false);
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 2 axes, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_xy(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 2 axes, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_xy(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 2 axes, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_xy(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_xy(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert 2 axes, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_xy(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_xy(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_xy(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
// ===== [CopyTransformModifier3D] Enable all axes, invert all axes =====
mod->set_axis_x_enabled(0, true);
mod->set_axis_y_enabled(0, true);
mod->set_axis_z_enabled(0, true);
mod->set_axis_x_inverted(0, true);
mod->set_axis_y_inverted(0, true);
mod->set_axis_z_inverted(0, true);
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert all axes, additive=false, relative=false") {
mod->set_additive(0, false);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_all(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert all axes, additive=true, relative=false") {
mod->set_additive(0, true);
mod->set_relative(0, false);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_position(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_all(skeleton->get_bone_pose_scale(tgt_bone)).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert all axes, additive=false, relative=true") {
mod->set_additive(0, false);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_rest(apl_bone).origin), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_all(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_rest(apl_bone).basis.get_rotation_quaternion().inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_all(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_rest(apl_bone).basis.get_scale()), "Scale is copied/inverted correctly.");
}
SUBCASE("[CopyTransformModifier3D] Enable all axes, invert all axes, additive=true, relative=true") {
mod->set_additive(0, true);
mod->set_relative(0, true);
skeleton->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON);
CHECK_MESSAGE(flip_all(skeleton->get_bone_pose_position(tgt_bone) - skeleton->get_bone_rest(tgt_bone).origin).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).origin - skeleton->get_bone_pose_position(apl_bone)), "Position is copied/inverted correctly.");
CHECK_MESSAGE(flip_all(skeleton->get_bone_rest(tgt_bone).basis.get_rotation_quaternion().inverse() * skeleton->get_bone_pose_rotation(tgt_bone)).is_equal_approx(Basis(skeleton->get_bone_pose_rotation(apl_bone).inverse() * (skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_rotation_quaternion()).get_rotation_quaternion()), "Rotation is copied/inverted correctly.");
CHECK_MESSAGE(inv_all(skeleton->get_bone_pose_scale(tgt_bone) / skeleton->get_bone_rest(tgt_bone).basis.get_scale()).is_equal_approx((skeleton->get_bone_global_pose(apl_root).affine_inverse() * modified->get_transform()).basis.get_scale() / skeleton->get_bone_pose_scale(apl_bone)), "Scale is copied/inverted correctly.");
}
memdelete(modified);
memdelete(mod);
memdelete(skeleton);
}
} // namespace TestCopyTransformModifier3D

432
tests/scene/test_curve.h Normal file
View File

@@ -0,0 +1,432 @@
/**************************************************************************/
/* test_curve.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. */
/**************************************************************************/
#pragma once
#include "core/math/math_funcs.h"
#include "scene/resources/curve.h"
#include "tests/test_macros.h"
namespace TestCurve {
TEST_CASE("[Curve] Default curve") {
const Ref<Curve> curve = memnew(Curve);
CHECK_MESSAGE(
curve->get_point_count() == 0,
"Default curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(0)),
"Default curve should return the expected value at offset 0.0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(0.5)),
"Default curve should return the expected value at offset 0.5.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(1)),
"Default curve should return the expected value at offset 1.0.");
}
TEST_CASE("[Curve] Custom unit curve with free tangents") {
Ref<Curve> curve = memnew(Curve);
// "Sawtooth" curve with an open ending towards the 1.0 offset.
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(0.25, 1));
curve->add_point(Vector2(0.5, 0));
curve->add_point(Vector2(0.75, 1));
curve->set_bake_resolution(11);
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_left_tangent(0)),
"get_point_left_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(0)),
"get_point_right_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_left_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_right_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_right_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom free curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-0.1)),
"Custom free curve should return the expected value at offset -0.1.");
CHECK_MESSAGE(
curve->sample(0.1) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset 0.1.");
CHECK_MESSAGE(
curve->sample(0.4) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset 0.4.");
CHECK_MESSAGE(
curve->sample(0.7) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected value at offset 0.7.");
CHECK_MESSAGE(
curve->sample(1) == doctest::Approx(1),
"Custom free curve should return the expected value at offset 1.");
CHECK_MESSAGE(
curve->sample(2) == doctest::Approx(1),
"Custom free curve should return the expected value at offset 2.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-0.1)),
"Custom free curve should return the expected baked value at offset -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected baked value at offset 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset 1.");
CHECK_MESSAGE(
curve->sample_baked(2) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset 2.");
curve->remove_point(1);
CHECK_MESSAGE(
curve->sample(0.1) == doctest::Approx(0),
"Custom free curve should return the expected value at offset 0.1 after removing point at index 1.");
CHECK_MESSAGE(
curve->sample_baked(0.1) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset 0.1 after removing point at index 1.");
curve->clear_points();
CHECK_MESSAGE(
curve->sample(0.6) == doctest::Approx(0),
"Custom free curve should return the expected value at offset 0.6 after clearing all points.");
CHECK_MESSAGE(
curve->sample_baked(0.6) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset 0.6 after clearing all points.");
}
TEST_CASE("[Curve] Custom non-unit curve with free tangents") {
Ref<Curve> curve = memnew(Curve);
curve->set_min_domain(-100.0);
curve->set_max_domain(100.0);
// "Sawtooth" curve with an open ending towards the 100 offset.
curve->add_point(Vector2(-100, 0));
curve->add_point(Vector2(-50, 1));
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(50, 1));
curve->set_bake_resolution(11);
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_left_tangent(0)),
"get_point_left_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(0)),
"get_point_right_tangent() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_left_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_right_mode(0) == Curve::TangentMode::TANGENT_FREE,
"get_point_right_mode() should return the expected value for point index 0.");
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom free curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-200)),
"Custom free curve should return the expected value at offset -200.");
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 1.");
CHECK_MESSAGE(
curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 2.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-200)),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 1.");
CHECK_MESSAGE(
curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 2.");
curve->remove_point(1);
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1 after removing point at index 1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1 after removing point at index 1.");
curve->clear_points();
CHECK_MESSAGE(
curve->sample(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected value at offset 0.6 after clearing all points.");
CHECK_MESSAGE(
curve->sample_baked(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0),
"Custom free curve should return the expected baked value at offset 0.6 after clearing all points.");
}
TEST_CASE("[Curve] Custom unit curve with linear tangents") {
Ref<Curve> curve = memnew(Curve);
// "Sawtooth" curve with an open ending towards the 1.0 offset.
curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(0.25, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(0.5, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(0.75, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
CHECK_MESSAGE(
curve->get_point_left_tangent(3) == doctest::Approx(4),
"get_point_left_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(3)),
"get_point_right_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_left_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_left_mode() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_right_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_right_mode() should return the expected value for point index 3.");
ERR_PRINT_OFF;
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(300)),
"get_point_right_tangent() should return the expected value for invalid point index 300.");
CHECK_MESSAGE(
curve->get_point_left_mode(-12345) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for invalid point index -12345.");
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom linear curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-0.1)),
"Custom linear curve should return the expected value at offset -0.1.");
CHECK_MESSAGE(
curve->sample(0.1) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset 0.1.");
CHECK_MESSAGE(
curve->sample(0.4) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset 0.4.");
CHECK_MESSAGE(
curve->sample(0.7) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected value at offset 0.7.");
CHECK_MESSAGE(
curve->sample(1) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset 1.0.");
CHECK_MESSAGE(
curve->sample(2) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset 2.0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-0.1)),
"Custom linear curve should return the expected baked value at offset -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected baked value at offset 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset 1.0.");
CHECK_MESSAGE(
curve->sample_baked(2) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset 2.0.");
ERR_PRINT_OFF;
curve->remove_point(10);
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->sample(0.7) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected value at offset 0.7 after removing point at invalid index 10.");
CHECK_MESSAGE(
curve->sample_baked(0.7) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10.");
}
TEST_CASE("[Curve] Custom non-unit curve with linear tangents") {
Ref<Curve> curve = memnew(Curve);
curve->set_min_domain(-100.0);
curve->set_max_domain(100.0);
// "Sawtooth" curve with an open ending towards the 100 offset.
curve->add_point(Vector2(-100, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(-50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
curve->add_point(Vector2(50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
CHECK_MESSAGE(
curve->get_point_left_tangent(3) == doctest::Approx(1.f / 50),
"get_point_left_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(3)),
"get_point_right_tangent() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_left_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_left_mode() should return the expected value for point index 3.");
CHECK_MESSAGE(
curve->get_point_right_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
"get_point_right_mode() should return the expected value for point index 3.");
ERR_PRINT_OFF;
CHECK_MESSAGE(
Math::is_zero_approx(curve->get_point_right_tangent(300)),
"get_point_right_tangent() should return the expected value for invalid point index 300.");
CHECK_MESSAGE(
curve->get_point_left_mode(-12345) == Curve::TangentMode::TANGENT_FREE,
"get_point_left_mode() should return the expected value for invalid point index -12345.");
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->get_point_count() == 4,
"Custom linear unit curve should contain the expected number of points.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample(-0.1 * curve->get_domain_range() + curve->get_min_domain())),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 1.0.");
CHECK_MESSAGE(
curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected value at offset equivalent to a unit curve's 2.0.");
CHECK_MESSAGE(
Math::is_zero_approx(curve->sample_baked(-0.1 * curve->get_domain_range() + curve->get_min_domain())),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's -0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.1.");
CHECK_MESSAGE(
curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.4.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.7.");
CHECK_MESSAGE(
curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 1.0.");
CHECK_MESSAGE(
curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1),
"Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 2.0.");
ERR_PRINT_OFF;
curve->remove_point(10);
ERR_PRINT_ON;
CHECK_MESSAGE(
curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10.");
CHECK_MESSAGE(
curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8),
"Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10.");
}
TEST_CASE("[Curve] Straight line offset test") {
Ref<Curve> curve = memnew(Curve);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(1, 1));
CHECK_MESSAGE(
curve->sample_baked(1.0 - (0.5 / curve->get_bake_resolution())) != curve->sample_baked(1),
"Straight line curve should return different baked values at offset 1 vs offset (1 - 0.5 / bake resolution) .");
}
TEST_CASE("[Curve2D] Linear sampling should return exact value") {
Ref<Curve2D> curve = memnew(Curve2D);
real_t len = 2048.0;
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(len, 0));
real_t baked_length = curve->get_baked_length();
CHECK(len == baked_length);
for (int i = 0; i < len; i++) {
Vector2 pos = curve->sample_baked(i);
CHECK_MESSAGE(Math::is_equal_approx(pos.x, i), "sample_baked should return exact value");
}
}
TEST_CASE("[Curve3D] Linear sampling should return exact value") {
Ref<Curve3D> curve = memnew(Curve3D);
real_t len = 2048.0;
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(len, 0, 0));
ERR_PRINT_OFF
real_t baked_length = curve->get_baked_length();
ERR_PRINT_ON
CHECK(len == baked_length);
for (int i = 0; i < len; i++) {
Vector3 pos = curve->sample_baked(i);
CHECK_MESSAGE(Math::is_equal_approx(pos.x, i), "sample_baked should return exact value");
}
}
} // namespace TestCurve

299
tests/scene/test_curve_2d.h Normal file
View File

@@ -0,0 +1,299 @@
/**************************************************************************/
/* test_curve_2d.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. */
/**************************************************************************/
#pragma once
#include "core/math/math_funcs.h"
#include "scene/resources/curve.h"
#include "tests/test_macros.h"
namespace TestCurve2D {
void add_sample_curve_points(Ref<Curve2D> &curve) {
Vector2 p0 = Vector2(0, 0);
Vector2 p1 = Vector2(50, 0);
Vector2 p2 = Vector2(50, 50);
Vector2 p3 = Vector2(0, 50);
Vector2 control0 = p1 - p0;
Vector2 control1 = p3 - p2;
curve->add_point(p0, Vector2(), control0);
curve->add_point(p3, control1, Vector2());
}
TEST_CASE("[Curve2D] Default curve is empty") {
const Ref<Curve2D> curve = memnew(Curve2D);
CHECK(curve->get_point_count() == 0);
}
TEST_CASE("[Curve2D] Point management") {
Ref<Curve2D> curve = memnew(Curve2D);
SUBCASE("Functions for adding/removing points should behave as expected") {
curve->set_point_count(2);
CHECK(curve->get_point_count() == 2);
curve->remove_point(0);
CHECK(curve->get_point_count() == 1);
curve->add_point(Vector2());
CHECK(curve->get_point_count() == 2);
curve->clear_points();
CHECK(curve->get_point_count() == 0);
}
SUBCASE("Functions for changing single point properties should behave as expected") {
Vector2 new_in = Vector2(1, 1);
Vector2 new_out = Vector2(1, 1);
Vector2 new_pos = Vector2(1, 1);
curve->add_point(Vector2());
CHECK(curve->get_point_in(0) != new_in);
curve->set_point_in(0, new_in);
CHECK(curve->get_point_in(0) == new_in);
CHECK(curve->get_point_out(0) != new_out);
curve->set_point_out(0, new_out);
CHECK(curve->get_point_out(0) == new_out);
CHECK(curve->get_point_position(0) != new_pos);
curve->set_point_position(0, new_pos);
CHECK(curve->get_point_position(0) == new_pos);
}
}
TEST_CASE("[Curve2D] Baked") {
Ref<Curve2D> curve = memnew(Curve2D);
SUBCASE("Single Point") {
curve->add_point(Vector2());
CHECK(curve->get_baked_length() == 0);
CHECK(curve->get_baked_points().size() == 1);
}
SUBCASE("Straight line") {
curve->add_point(Vector2());
curve->add_point(Vector2(0, 50));
CHECK(Math::is_equal_approx(curve->get_baked_length(), 50));
CHECK(curve->get_baked_points().size() == 15);
}
SUBCASE("Beziér Curve") {
add_sample_curve_points(curve);
real_t len = curve->get_baked_length();
real_t n_points = curve->get_baked_points().size();
// Curve length should be bigger than a straight between points
CHECK(len > 50);
SUBCASE("Increase bake interval") {
curve->set_bake_interval(10.0);
// Lower resolution should imply less points and smaller length
CHECK(curve->get_baked_length() < len);
CHECK(curve->get_baked_points().size() < n_points);
}
}
}
TEST_CASE("[Curve2D] Sampling") {
// Sampling over a simple straight line to make assertions simpler
Ref<Curve2D> curve = memnew(Curve2D);
curve->add_point(Vector2());
curve->add_point(Vector2(0, 50));
SUBCASE("sample") {
CHECK(curve->sample(0, 0) == Vector2(0, 0));
CHECK(curve->sample(0, 0.5) == Vector2(0, 25));
CHECK(curve->sample(0, 1) == Vector2(0, 50));
}
SUBCASE("samplef") {
CHECK(curve->samplef(0) == Vector2(0, 0));
CHECK(curve->samplef(0.5) == Vector2(0, 25));
CHECK(curve->samplef(1) == Vector2(0, 50));
}
SUBCASE("sample_baked, cubic = false") {
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0))) == Vector2(0, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25))) == Vector2(0, 25));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50))) == Vector2(0, 50));
}
SUBCASE("sample_baked, cubic = true") {
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 0)), true) == Vector2(0, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 25)), true) == Vector2(0, 25));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector2(0, 50)), true) == Vector2(0, 50));
}
SUBCASE("sample_baked_with_rotation, cubic = false") {
const real_t pi = 3.14159;
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(), half_pi));
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("sample_baked_with_rotation, cubic = true") {
const real_t pi = 3.14159;
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, true);
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, true);
CHECK(t.get_origin() == Vector2(0, 25));
CHECK(Math::is_equal_approx(t.get_rotation(), half_pi));
rot_curve->clear_points();
rot_curve->add_point(Vector2());
rot_curve->add_point(Vector2(-50, 0));
t = rot_curve->sample_baked_with_rotation(25, true);
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, true);
CHECK(t.get_origin() == Vector2(0, -25));
CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi));
}
SUBCASE("get_closest_point") {
CHECK(curve->get_closest_point(Vector2(0, 0)) == Vector2(0, 0));
CHECK(curve->get_closest_point(Vector2(0, 25)) == Vector2(0, 25));
CHECK(curve->get_closest_point(Vector2(50, 25)) == Vector2(0, 25));
CHECK(curve->get_closest_point(Vector2(0, 50)) == Vector2(0, 50));
CHECK(curve->get_closest_point(Vector2(50, 50)) == Vector2(0, 50));
CHECK(curve->get_closest_point(Vector2(0, 100)) == Vector2(0, 50));
}
SUBCASE("sample_baked_with_rotation, linear curve with control1 = end and control2 = begin") {
// Regression test for issue #88923
// The Vector2s that aren't relevant to the issue have x = 2 or x = -2.
// They're just set to make collisions with corner cases less likely
// that involve zero-vector control points.
Ref<Curve2D> cross_linear_curve = memnew(Curve2D);
cross_linear_curve->set_bake_interval(0.5);
cross_linear_curve->add_point(Vector2(), Vector2(-2, 0), Vector2(1, 0));
cross_linear_curve->add_point(Vector2(1, 0), Vector2(-1, 0), Vector2(2, 0));
CHECK(cross_linear_curve->get_baked_points().size() >= 3);
CHECK(cross_linear_curve->sample_baked_with_rotation(cross_linear_curve->get_closest_offset(Vector2(0.5, 0))).is_equal_approx(Transform2D(Vector2(1, 0), Vector2(0, 1), Vector2(0.5, 0))));
}
}
TEST_CASE("[Curve2D] Tessellation") {
Ref<Curve2D> curve = memnew(Curve2D);
add_sample_curve_points(curve);
const int default_size = curve->tessellate().size();
SUBCASE("Increase to max stages should increase num of points") {
CHECK(curve->tessellate(6).size() > default_size);
}
SUBCASE("Decrease to max stages should decrease num of points") {
CHECK(curve->tessellate(4).size() < default_size);
}
SUBCASE("Increase to tolerance should decrease num of points") {
CHECK(curve->tessellate(5, 5).size() < default_size);
}
SUBCASE("Decrease to tolerance should increase num of points") {
CHECK(curve->tessellate(5, 3).size() > default_size);
}
SUBCASE("Adding a straight segment should only add the last point to tessellate return array") {
curve->add_point(Vector2(0, 100));
PackedVector2Array tes = curve->tessellate();
CHECK(tes.size() == default_size + 1);
CHECK(tes[tes.size() - 1] == Vector2(0, 100));
CHECK(tes[tes.size() - 2] == Vector2(0, 50));
}
}
TEST_CASE("[Curve2D] Even length tessellation") {
Ref<Curve2D> curve = memnew(Curve2D);
add_sample_curve_points(curve);
const int default_size = curve->tessellate_even_length().size();
// Default tessellate_even_length tolerance_length is 20.0, by adding a 100 units
// straight, we expect the total size to be increased by more than 5,
// that is, the algo will pick a length < 20.0 and will divide the straight as
// well as the curve as opposed to tessellate() which only adds the final point
curve->add_point(Vector2(0, 150));
CHECK(curve->tessellate_even_length().size() > default_size + 5);
}
} // namespace TestCurve2D

296
tests/scene/test_curve_3d.h Normal file
View File

@@ -0,0 +1,296 @@
/**************************************************************************/
/* test_curve_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. */
/**************************************************************************/
#pragma once
#include "core/math/math_funcs.h"
#include "scene/resources/curve.h"
#include "tests/test_macros.h"
namespace TestCurve3D {
void add_sample_curve_points(Ref<Curve3D> &curve) {
Vector3 p0 = Vector3(0, 0, 0);
Vector3 p1 = Vector3(50, 0, 0);
Vector3 p2 = Vector3(50, 50, 50);
Vector3 p3 = Vector3(0, 50, 0);
Vector3 control0 = p1 - p0;
Vector3 control1 = p3 - p2;
curve->add_point(p0, Vector3(), control0);
curve->add_point(p3, control1, Vector3());
}
TEST_CASE("[Curve3D] Default curve is empty") {
const Ref<Curve3D> curve = memnew(Curve3D);
CHECK(curve->get_point_count() == 0);
}
TEST_CASE("[Curve3D] Point management") {
Ref<Curve3D> curve = memnew(Curve3D);
SUBCASE("Functions for adding/removing points should behave as expected") {
curve->set_point_count(2);
CHECK(curve->get_point_count() == 2);
curve->remove_point(0);
CHECK(curve->get_point_count() == 1);
curve->add_point(Vector3());
CHECK(curve->get_point_count() == 2);
curve->clear_points();
CHECK(curve->get_point_count() == 0);
}
SUBCASE("Functions for changing single point properties should behave as expected") {
Vector3 new_in = Vector3(1, 1, 1);
Vector3 new_out = Vector3(1, 1, 1);
Vector3 new_pos = Vector3(1, 1, 1);
real_t new_tilt = 1;
curve->add_point(Vector3());
CHECK(curve->get_point_in(0) != new_in);
curve->set_point_in(0, new_in);
CHECK(curve->get_point_in(0) == new_in);
CHECK(curve->get_point_out(0) != new_out);
curve->set_point_out(0, new_out);
CHECK(curve->get_point_out(0) == new_out);
CHECK(curve->get_point_position(0) != new_pos);
curve->set_point_position(0, new_pos);
CHECK(curve->get_point_position(0) == new_pos);
CHECK(curve->get_point_tilt(0) != new_tilt);
curve->set_point_tilt(0, new_tilt);
CHECK(curve->get_point_tilt(0) == new_tilt);
}
}
TEST_CASE("[Curve3D] Baked") {
Ref<Curve3D> curve = memnew(Curve3D);
SUBCASE("Single Point") {
curve->add_point(Vector3());
CHECK(curve->get_baked_length() == 0);
CHECK(curve->get_baked_points().size() == 1);
CHECK(curve->get_baked_tilts().size() == 1);
CHECK(curve->get_baked_up_vectors().size() == 1);
}
SUBCASE("Straight line") {
curve->add_point(Vector3());
curve->add_point(Vector3(0, 50, 0));
CHECK(Math::is_equal_approx(curve->get_baked_length(), 50));
CHECK(curve->get_baked_points().size() == 369);
CHECK(curve->get_baked_tilts().size() == 369);
CHECK(curve->get_baked_up_vectors().size() == 369);
}
SUBCASE("Beziér Curve") {
add_sample_curve_points(curve);
real_t len = curve->get_baked_length();
real_t n_points = curve->get_baked_points().size();
// Curve length should be bigger than a straight line between points
CHECK(len > 50);
SUBCASE("Increase bake interval") {
curve->set_bake_interval(10.0);
CHECK(curve->get_bake_interval() == 10.0);
// Lower resolution should imply less points and smaller length
CHECK(curve->get_baked_length() < len);
CHECK(curve->get_baked_points().size() < n_points);
CHECK(curve->get_baked_tilts().size() < n_points);
CHECK(curve->get_baked_up_vectors().size() < n_points);
}
SUBCASE("Disable up vectors") {
curve->set_up_vector_enabled(false);
CHECK(curve->is_up_vector_enabled() == false);
CHECK(curve->get_baked_up_vectors().size() == 0);
}
}
}
TEST_CASE("[Curve3D] Sampling") {
// Sampling over a simple straight line to make assertions simpler
Ref<Curve3D> curve = memnew(Curve3D);
curve->add_point(Vector3());
curve->add_point(Vector3(0, 50, 0));
SUBCASE("sample") {
CHECK(curve->sample(0, 0) == Vector3(0, 0, 0));
CHECK(curve->sample(0, 0.5) == Vector3(0, 25, 0));
CHECK(curve->sample(0, 1) == Vector3(0, 50, 0));
}
SUBCASE("samplef") {
CHECK(curve->samplef(0) == Vector3(0, 0, 0));
CHECK(curve->samplef(0.5) == Vector3(0, 25, 0));
CHECK(curve->samplef(1) == Vector3(0, 50, 0));
}
SUBCASE("sample_baked, cubic = false") {
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 0, 0))) == Vector3(0, 0, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 25, 0))) == Vector3(0, 25, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0))) == Vector3(0, 50, 0));
}
SUBCASE("sample_baked, cubic = true") {
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Vector3(0, 0, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Vector3(0, 25, 0));
CHECK(curve->sample_baked(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(0, 50, 0));
}
SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = false") {
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0))) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0)));
}
SUBCASE("sample_baked_with_rotation, cubic = false, p_apply_tilt = true") {
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), false, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0)));
}
SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = false") {
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0)));
}
SUBCASE("sample_baked_with_rotation, cubic = true, p_apply_tilt = true") {
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 0, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 0, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 25, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 25, 0)));
CHECK(curve->sample_baked_with_rotation(curve->get_closest_offset(Vector3(0, 50, 0)), true, true) == Transform3D(Basis(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0)), Vector3(0, 50, 0)));
}
SUBCASE("sample_baked_tilt") {
CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 0, 0))) == 0);
CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 25, 0))) == 0);
CHECK(curve->sample_baked_tilt(curve->get_closest_offset(Vector3(0, 50, 0))) == 0);
}
SUBCASE("sample_baked_up_vector, p_apply_tilt = false") {
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 0, 0))) == Vector3(1, 0, 0));
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 25, 0))) == Vector3(1, 0, 0));
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 50, 0))) == Vector3(1, 0, 0));
}
SUBCASE("sample_baked_up_vector, p_apply_tilt = true") {
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 0, 0)), true) == Vector3(1, 0, 0));
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 25, 0)), true) == Vector3(1, 0, 0));
CHECK(curve->sample_baked_up_vector(curve->get_closest_offset(Vector3(0, 50, 0)), true) == Vector3(1, 0, 0));
}
SUBCASE("get_closest_point") {
CHECK(curve->get_closest_point(Vector3(0, 0, 0)) == Vector3(0, 0, 0));
CHECK(curve->get_closest_point(Vector3(0, 25, 0)) == Vector3(0, 25, 0));
CHECK(curve->get_closest_point(Vector3(50, 25, 0)) == Vector3(0, 25, 0));
CHECK(curve->get_closest_point(Vector3(0, 50, 0)) == Vector3(0, 50, 0));
CHECK(curve->get_closest_point(Vector3(50, 50, 0)) == Vector3(0, 50, 0));
CHECK(curve->get_closest_point(Vector3(0, 100, 0)) == Vector3(0, 50, 0));
}
SUBCASE("sample_baked_up_vector, off-axis") {
// Regression test for issue #81879
Ref<Curve3D> c = memnew(Curve3D);
c->add_point(Vector3());
c->add_point(Vector3(0, .1, 1));
CHECK_LT((c->sample_baked_up_vector(c->get_closest_offset(Vector3(0, 0, .9))) - Vector3(0, 0.995037, -0.099504)).length(), 0.01);
}
SUBCASE("sample_baked_with_rotation, linear curve with control1 = end and control2 = begin") {
// Regression test for issue #88923
// The Vector3s that aren't relevant to the issue have z = 2.
// They're just set to make collisions with corner cases less likely
// that involve zero-vector control points.
Ref<Curve3D> cross_linear_curve = memnew(Curve3D);
cross_linear_curve->add_point(Vector3(), Vector3(-1, 0, 2), Vector3(1, 0, 0));
cross_linear_curve->add_point(Vector3(1, 0, 0), Vector3(-1, 0, 0), Vector3(1, 0, 2));
CHECK(cross_linear_curve->get_baked_points().size() >= 3);
CHECK(cross_linear_curve->sample_baked_with_rotation(cross_linear_curve->get_closest_offset(Vector3(0.5, 0, 0))).is_equal_approx(Transform3D(Basis(Vector3(0, 0, 1), Vector3(0, 1, 0), Vector3(-1, 0, 0)), Vector3(0.5, 0, 0))));
}
}
TEST_CASE("[Curve3D] Tessellation") {
Ref<Curve3D> curve = memnew(Curve3D);
add_sample_curve_points(curve);
const int default_size = curve->tessellate().size();
SUBCASE("Increase to max stages should increase num of points") {
CHECK(curve->tessellate(6).size() > default_size);
}
SUBCASE("Decrease to max stages should decrease num of points") {
CHECK(curve->tessellate(4).size() < default_size);
}
SUBCASE("Increase to tolerance should decrease num of points") {
CHECK(curve->tessellate(5, 5).size() < default_size);
}
SUBCASE("Decrease to tolerance should increase num of points") {
CHECK(curve->tessellate(5, 3).size() > default_size);
}
SUBCASE("Adding a straight segment should only add the last point to tessellate return array") {
curve->add_point(Vector3(0, 100, 0));
PackedVector3Array tes = curve->tessellate();
CHECK(tes.size() == default_size + 1);
CHECK(tes[tes.size() - 1] == Vector3(0, 100, 0));
CHECK(tes[tes.size() - 2] == Vector3(0, 50, 0));
}
}
TEST_CASE("[Curve3D] Even length tessellation") {
Ref<Curve3D> curve = memnew(Curve3D);
add_sample_curve_points(curve);
const int default_size = curve->tessellate_even_length().size();
// Default tessellate_even_length tolerance_length is 20.0, by adding a 100 units
// straight, we expect the total size to be increased by more than 5,
// that is, the algo will pick a length < 20.0 and will divide the straight as
// well as the curve as opposed to tessellate() which only adds the final point.
curve->add_point(Vector3(0, 150, 0));
CHECK(curve->tessellate_even_length().size() > default_size + 5);
}
} // namespace TestCurve3D

127
tests/scene/test_fontfile.h Normal file
View File

@@ -0,0 +1,127 @@
/**************************************************************************/
/* test_fontfile.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. */
/**************************************************************************/
#pragma once
#include "modules/modules_enabled.gen.h"
#include "scene/resources/font.h"
#include "tests/test_macros.h"
namespace TestFontfile {
TEST_CASE("[FontFile] Load Dynamic Font - getters") {
#ifdef MODULE_FREETYPE_ENABLED
String test_dynamic_font = "thirdparty/fonts/NotoSansHebrew_Regular.woff2";
Ref<FontFile> ff;
ff.instantiate();
CHECK(ff->load_dynamic_font(test_dynamic_font) == OK);
// These properties come from the font file itself.
CHECK(ff->get_font_name() == "Noto Sans Hebrew");
CHECK(ff->get_font_style_name() == "Regular");
CHECK(ff->get_font_weight() == 400);
CHECK(ff->get_font_stretch() == 100);
CHECK(ff->get_opentype_features() == Dictionary());
Dictionary expected_ot_name_strings;
Dictionary en_dict;
en_dict["copyright"] = "Copyright 2022 The Noto Project Authors (https://github.com/notofonts/hebrew)";
en_dict["family_name"] = "Noto Sans Hebrew";
en_dict["subfamily_name"] = "Regular";
en_dict["full_name"] = "Noto Sans Hebrew Regular";
en_dict["unique_identifier"] = "2.003;GOOG;NotoSansHebrew-Regular";
en_dict["version"] = "Version 2.003";
en_dict["postscript_name"] = "NotoSansHebrew-Regular";
en_dict["trademark"] = "Noto is a trademark of Google Inc.";
en_dict["license"] = "This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: https://scripts.sil.org/OFL";
en_dict["license_url"] = "https://scripts.sil.org/OFL";
en_dict["designer"] = "Monotype Design Team";
en_dict["designer_url"] = "http://www.monotype.com/studio";
en_dict["description"] = "Designed by Monotype design team.";
en_dict["manufacturer"] = "Monotype Imaging Inc.";
en_dict["vendor_url"] = "http://www.google.com/get/noto/";
expected_ot_name_strings["en"] = en_dict;
CHECK(ff->get_ot_name_strings() == expected_ot_name_strings);
// These are dependent on size and potentially other state. Act as regression tests based of arbitrary small size 10 and large size 100.
CHECK(ff->get_height(10) == doctest::Approx((real_t)14));
CHECK(ff->get_ascent(10) == doctest::Approx((real_t)11));
CHECK(ff->get_descent(10) == doctest::Approx((real_t)3));
CHECK(ff->get_underline_position(10) == doctest::Approx((real_t)1.25));
CHECK(ff->get_underline_thickness(10) == doctest::Approx((real_t)0.5));
CHECK(ff->get_height(100) == doctest::Approx((real_t)137));
CHECK(ff->get_ascent(100) == doctest::Approx((real_t)107));
CHECK(ff->get_descent(100) == doctest::Approx((real_t)30));
CHECK(ff->get_underline_position(100) == doctest::Approx((real_t)12.5));
CHECK(ff->get_underline_thickness(100) == doctest::Approx((real_t)5));
#endif
}
TEST_CASE("[FontFile] Create font file and check data") {
// Create test instance.
Ref<FontFile> font_file;
font_file.instantiate();
#ifdef MODULE_FREETYPE_ENABLED
// Try to load non-existent files.
ERR_PRINT_OFF
CHECK(font_file->load_dynamic_font("") == OK);
CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded.");
CHECK(font_file->load_dynamic_font("thirdparty/fonts/nofonthasthisname.woff2") == OK);
CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded.");
ERR_PRINT_ON
// Load a valid file.
CHECK(font_file->load_dynamic_font("thirdparty/fonts/NotoSans_Regular.woff2") == OK);
// Check fontfile data.
CHECK_MESSAGE(font_file->get_data().is_empty() == false, "Fontfile should have been loaded.");
CHECK_MESSAGE(font_file->get_font_name() == "Noto Sans", "Loaded correct font name.");
CHECK_MESSAGE(font_file->get_font_style_name() == "Regular", "Loaded correct font style.");
CHECK_MESSAGE(font_file->get_data().size() == 148480llu, "Whole fontfile was loaded.");
// Valid glyphs.
CHECK_MESSAGE(font_file->get_glyph_index(2, 'a', 0) != 0, "Glyph index for 'a' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 'b', 0) != 0, "Glyph index for 'b' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0103, 0) != 0, "Glyph index for 'latin small letter a with breve' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x03a8, 0) != 0, "Glyph index for 'Greek psi' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0416, 0) != 0, "Glyph index for 'Cyrillic zhe' is valid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, '&', 0) != 0, "Glyph index for '&' is valid.");
// Invalid glyphs.
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x4416, 0) == 0, "Glyph index is invalid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x5555, 0) == 0, "Glyph index is invalid.");
CHECK_MESSAGE(font_file->get_glyph_index(2, 0x2901, 0) == 0, "Glyph index is invalid.");
#endif
}
} // namespace TestFontfile

View File

@@ -0,0 +1,247 @@
/**************************************************************************/
/* test_gltf_document.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. */
/**************************************************************************/
#pragma once
#include "modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h"
#include "modules/gltf/gltf_document.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestGLTFDocument {
struct GLTFArraySize {
String key;
int val;
};
struct GLTFKeyValue {
String key;
Variant val;
};
struct GLTFTestCase {
String filename;
String copyright;
String generator;
String version;
Vector<GLTFArraySize> array_sizes;
Vector<GLTFArraySize> json_array_sizes;
Vector<GLTFKeyValue> keyvalues;
};
const GLTFTestCase glTF_test_cases[] = {
{ "models/cube.gltf",
"",
"Khronos glTF Blender I/O v4.3.47",
"2.0",
// Here are the array sizes.
{
{ "nodes", 1 },
{ "buffers", 1 },
{ "buffer_views", 13 },
{ "accessors", 13 },
{ "meshes", 1 },
{ "materials", 2 },
{ "root_nodes", 1 },
{ "textures", 0 },
{ "texture_samplers", 0 },
{ "images", 0 },
{ "skins", 0 },
{ "cameras", 0 },
{ "lights", 0 },
{ "skeletons", 0 },
{ "animations", 1 },
},
// Here are the json array sizes.
{
{ "scenes", 1 },
{ "nodes", 1 },
{ "animations", 1 },
{ "meshes", 1 },
{ "accessors", 13 },
{ "bufferViews", 13 },
{ "buffers", 1 },
},
// Here are the key-value pairs.
{
{ "major_version", 2 },
{ "minor_version", 0 },
{ "scene_name", "cube" },
{ "filename", "cube" } } },
{ "models/suzanne.glb",
"this is example text",
"Khronos glTF Blender I/O v4.3.47",
"2.0",
// Here are the array sizes.
{
{ "glb_data", 68908 },
{ "nodes", 2 },
{ "buffers", 1 },
{ "buffer_views", 5 },
{ "accessors", 4 },
{ "meshes", 1 },
{ "materials", 1 },
{ "root_nodes", 2 },
{ "textures", 1 },
{ "texture_samplers", 1 },
{ "images", 1 },
{ "skins", 0 },
{ "cameras", 1 },
{ "lights", 0 },
{ "unique_names", 4 },
{ "skeletons", 0 },
{ "animations", 0 },
},
// Here are the json array sizes.
{
{ "scenes", 1 },
{ "nodes", 2 },
{ "cameras", 1 },
{ "materials", 1 },
{ "meshes", 1 },
{ "textures", 1 },
{ "images", 1 },
{ "accessors", 4 },
{ "bufferViews", 5 },
{ "buffers", 1 },
},
// Here are the key-value pairs.
{
{ "major_version", 2 },
{ "minor_version", 0 },
{ "scene_name", "suzanne" },
{ "filename", "suzanne" } } },
};
void register_gltf_extension() {
GLTFDocument::unregister_all_gltf_document_extensions();
// Ensures meshes become a MeshInstance3D and not an ImporterMeshInstance3D.
Ref<GLTFDocumentExtensionConvertImporterMesh> extension_GLTFDocumentExtensionConvertImporterMesh;
extension_GLTFDocumentExtensionConvertImporterMesh.instantiate();
GLTFDocument::register_gltf_document_extension(extension_GLTFDocumentExtensionConvertImporterMesh);
}
void test_gltf_document_values(Ref<GLTFDocument> &p_gltf_document, Ref<GLTFState> &p_gltf_state, const GLTFTestCase &p_test_case) {
const Error err = p_gltf_document->append_from_file(TestUtils::get_data_path(p_test_case.filename), p_gltf_state);
REQUIRE(err == OK);
for (GLTFArraySize array_size : p_test_case.array_sizes) {
CHECK_MESSAGE(((Array)(p_gltf_state->getvar(array_size.key))).size() == array_size.val, "Expected \"", array_size.key, "\" to have ", array_size.val, " elements.");
}
for (GLTFArraySize array_size : p_test_case.json_array_sizes) {
CHECK(p_gltf_state->get_json().has(array_size.key));
CHECK_MESSAGE(((Array)(p_gltf_state->get_json()[array_size.key])).size() == array_size.val, "Expected \"", array_size.key, "\" to have ", array_size.val, " elements.");
}
for (GLTFKeyValue key_value : p_test_case.keyvalues) {
CHECK_MESSAGE(p_gltf_state->getvar(key_value.key) == key_value.val, "Expected \"", key_value.key, "\" to be \"", key_value.val, "\".");
}
CHECK(p_gltf_state->get_copyright() == p_test_case.copyright);
CHECK(((Dictionary)p_gltf_state->get_json()["asset"])["generator"] == p_test_case.generator);
CHECK(((Dictionary)p_gltf_state->get_json()["asset"])["version"] == p_test_case.version);
}
void test_gltf_save(Node *p_node) {
Ref<GLTFDocument> gltf_document_save;
gltf_document_save.instantiate();
Ref<GLTFState> gltf_state_save;
gltf_state_save.instantiate();
gltf_document_save->append_from_scene(p_node, gltf_state_save);
// Check saving the scene to gltf and glb.
const Error err_save_gltf = gltf_document_save->write_to_filesystem(gltf_state_save, TestUtils::get_temp_path("cube.gltf"));
const Error err_save_glb = gltf_document_save->write_to_filesystem(gltf_state_save, TestUtils::get_temp_path("cube.glb"));
CHECK(err_save_gltf == OK);
CHECK(err_save_glb == OK);
}
TEST_CASE("[SceneTree][GLTFDocument] Load cube.gltf") {
register_gltf_extension();
Ref<GLTFDocument> gltf_document;
gltf_document.instantiate();
Ref<GLTFState> gltf_state;
gltf_state.instantiate();
test_gltf_document_values(gltf_document, gltf_state, glTF_test_cases[0]);
Node *node = gltf_document->generate_scene(gltf_state);
// Check the loaded scene.
CHECK(node->is_class("Node3D"));
CHECK(node->get_name() == "cube");
CHECK(node->get_child(0)->is_class("MeshInstance3D"));
CHECK(node->get_child(0)->get_name() == "Cube");
CHECK(node->get_child(1)->is_class("AnimationPlayer"));
CHECK(node->get_child(1)->get_name() == "AnimationPlayer");
test_gltf_save(node);
// Clean up the node.
memdelete(node);
}
TEST_CASE("[SceneTree][GLTFDocument] Load suzanne.glb") {
register_gltf_extension();
Ref<GLTFDocument> gltf_document;
gltf_document.instantiate();
Ref<GLTFState> gltf_state;
gltf_state.instantiate();
test_gltf_document_values(gltf_document, gltf_state, glTF_test_cases[1]);
Node *node = gltf_document->generate_scene(gltf_state);
// Check the loaded scene.
CHECK(node->is_class("Node3D"));
CHECK(node->get_name() == "suzanne");
CHECK(node->get_child(0)->is_class("MeshInstance3D"));
CHECK(node->get_child(0)->get_name() == "Suzanne");
CHECK(node->get_child(1)->is_class("Camera3D"));
CHECK(node->get_child(1)->get_name() == "Camera");
test_gltf_save(node);
// Clean up the node.
memdelete(node);
}
} // namespace TestGLTFDocument

144
tests/scene/test_gradient.h Normal file
View File

@@ -0,0 +1,144 @@
/**************************************************************************/
/* test_gradient.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/gradient.h"
#include "thirdparty/doctest/doctest.h"
namespace TestGradient {
TEST_CASE("[Gradient] Default gradient") {
// Black-white gradient.
Ref<Gradient> gradient = memnew(Gradient);
CHECK_MESSAGE(
gradient->get_point_count() == 2,
"Default gradient should contain the expected number of points.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.0).is_equal_approx(Color(0, 0, 0)),
"Default gradient should return the expected interpolated value at offset 0.0.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.4).is_equal_approx(Color(0.4, 0.4, 0.4)),
"Default gradient should return the expected interpolated value at offset 0.4.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.8).is_equal_approx(Color(0.8, 0.8, 0.8)),
"Default gradient should return the expected interpolated value at offset 0.8.");
CHECK_MESSAGE(
gradient->get_color_at_offset(1.0).is_equal_approx(Color(1, 1, 1)),
"Default gradient should return the expected interpolated value at offset 1.0.");
// Out of bounds checks.
CHECK_MESSAGE(
gradient->get_color_at_offset(-1.0).is_equal_approx(Color(0, 0, 0)),
"Default gradient should return the expected interpolated value at offset -1.0.");
CHECK_MESSAGE(
gradient->get_color_at_offset(1234.0).is_equal_approx(Color(1, 1, 1)),
"Default gradient should return the expected interpolated value at offset 1234.0.");
}
TEST_CASE("[Gradient] Custom gradient (points specified in order)") {
// Red-yellow-green gradient (with overbright green).
Ref<Gradient> gradient = memnew(Gradient);
Vector<float> offsets = { 0.0, 0.5, 1.0 };
Vector<Color> colors = { Color(1, 0, 0), Color(1, 1, 0), Color(0, 2, 0) };
gradient->set_offsets(offsets);
gradient->set_colors(colors);
CHECK_MESSAGE(
gradient->get_point_count() == 3,
"Custom gradient should contain the expected number of points.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.0).is_equal_approx(Color(1, 0, 0)),
"Custom gradient should return the expected interpolated value at offset 0.0.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.25).is_equal_approx(Color(1, 0.5, 0)),
"Custom gradient should return the expected interpolated value at offset 0.25.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.5).is_equal_approx(Color(1, 1, 0)),
"Custom gradient should return the expected interpolated value at offset 0.5.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.75).is_equal_approx(Color(0.5, 1.5, 0)),
"Custom gradient should return the expected interpolated value at offset 0.75.");
CHECK_MESSAGE(
gradient->get_color_at_offset(1.0).is_equal_approx(Color(0, 2, 0)),
"Custom gradient should return the expected interpolated value at offset 1.0.");
gradient->remove_point(1);
CHECK_MESSAGE(
gradient->get_point_count() == 2,
"Custom gradient should contain the expected number of points after removing one point.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.5).is_equal_approx(Color(0.5, 1, 0)),
"Custom gradient should return the expected interpolated value at offset 0.5 after removing point at index 1.");
}
TEST_CASE("[Gradient] Custom gradient (points specified out-of-order)") {
// HSL rainbow with points specified out of order.
// These should be sorted automatically when adding points.
Ref<Gradient> gradient = memnew(Gradient);
LocalVector<Gradient::Point> points;
Vector<float> offsets = { 0.2, 0.0, 0.8, 0.4, 1.0, 0.6 };
Vector<Color> colors = { Color(1, 0, 0), Color(1, 1, 0), Color(0, 1, 0), Color(0, 1, 1), Color(0, 0, 1), Color(1, 0, 1) };
gradient->set_offsets(offsets);
gradient->set_colors(colors);
CHECK_MESSAGE(
gradient->get_point_count() == 6,
"Custom out-of-order gradient should contain the expected number of points.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.0).is_equal_approx(Color(1, 1, 0)),
"Custom out-of-order gradient should return the expected interpolated value at offset 0.0.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.3).is_equal_approx(Color(0.5, 0.5, 0.5)),
"Custom out-of-order gradient should return the expected interpolated value at offset 0.3.");
CHECK_MESSAGE(
gradient->get_color_at_offset(0.6).is_equal_approx(Color(1, 0, 1)),
"Custom out-of-order gradient should return the expected interpolated value at offset 0.6.");
CHECK_MESSAGE(
gradient->get_color_at_offset(1.0).is_equal_approx(Color(0, 0, 1)),
"Custom out-of-order gradient should return the expected interpolated value at offset 1.0.");
gradient->remove_point(0);
CHECK_MESSAGE(
gradient->get_point_count() == 5,
"Custom out-of-order gradient should contain the expected number of points after removing one point.");
// The color will be clamped to the nearest point (which is at offset 0.2).
CHECK_MESSAGE(
gradient->get_color_at_offset(0.1).is_equal_approx(Color(1, 0, 0)),
"Custom out-of-order gradient should return the expected interpolated value at offset 0.1 after removing point at index 0.");
}
} // namespace TestGradient

View File

@@ -0,0 +1,84 @@
/**************************************************************************/
/* test_gradient_texture.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/gradient_texture.h"
#include "tests/test_macros.h"
namespace TestGradientTexture {
// [SceneTree] in a test case name enables initializing a mock render server,
// which ImageTexture is dependent on.
TEST_CASE("[SceneTree][GradientTexture1D] Create GradientTexture1D") {
Ref<GradientTexture1D> gradient_texture = memnew(GradientTexture1D);
Ref<Gradient> test_gradient = memnew(Gradient);
gradient_texture->set_gradient(test_gradient);
CHECK(gradient_texture->get_gradient() == test_gradient);
gradient_texture->set_width(83);
CHECK(gradient_texture->get_width() == 83);
gradient_texture->set_use_hdr(true);
CHECK(gradient_texture->is_using_hdr());
}
TEST_CASE("[SceneTree][GradientTexture2D] Create GradientTexture2D") {
Ref<GradientTexture2D> gradient_texture = memnew(GradientTexture2D);
Ref<Gradient> test_gradient = memnew(Gradient);
gradient_texture->set_gradient(test_gradient);
CHECK(gradient_texture->get_gradient() == test_gradient);
gradient_texture->set_width(82);
CHECK(gradient_texture->get_width() == 82);
gradient_texture->set_height(81);
CHECK(gradient_texture->get_height() == 81);
gradient_texture->set_use_hdr(true);
CHECK(gradient_texture->is_using_hdr());
gradient_texture->set_fill(GradientTexture2D::Fill::FILL_SQUARE);
CHECK(gradient_texture->get_fill() == GradientTexture2D::Fill::FILL_SQUARE);
gradient_texture->set_fill_from(Vector2(0.2, 0.25));
CHECK(gradient_texture->get_fill_from() == Vector2(0.2, 0.25));
gradient_texture->set_fill_to(Vector2(0.35, 0.5));
CHECK(gradient_texture->get_fill_to() == Vector2(0.35, 0.5));
gradient_texture->set_repeat(GradientTexture2D::Repeat::REPEAT);
CHECK(gradient_texture->get_repeat() == GradientTexture2D::Repeat::REPEAT);
}
} //namespace TestGradientTexture

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* test_graph_node.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/graph_node.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestGraphNode {
TEST_CASE("[GraphNode][SceneTree]") {
SUBCASE("[GraphNode] Graph Node only child on delete should not cause error.") {
// Setup.
GraphNode *test_node = memnew(GraphNode);
test_node->set_name("Graph Node");
Control *test_child = memnew(Control);
test_child->set_name("child");
test_node->add_child(test_child);
// Test.
test_node->remove_child(test_child);
CHECK(test_node->get_child_count(false) == 0);
memdelete(test_child);
memdelete(test_node);
}
}
} // namespace TestGraphNode

View File

@@ -0,0 +1,119 @@
/**************************************************************************/
/* test_height_map_shape_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. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/image_texture.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestHeightMapShape3D {
TEST_CASE("[SceneTree][HeightMapShape3D] Constructor") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
CHECK(height_map_shape->get_map_width() == 2);
CHECK(height_map_shape->get_map_depth() == 2);
CHECK(height_map_shape->get_map_data().size() == 4);
CHECK(height_map_shape->get_min_height() == 0.0);
CHECK(height_map_shape->get_max_height() == 0.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_width and get_map_width") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(10);
CHECK(height_map_shape->get_map_width() == 10);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_depth and get_map_depth") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_depth(15);
CHECK(height_map_shape->get_map_depth() == 15);
}
TEST_CASE("[SceneTree][HeightMapShape3D] set_map_data and get_map_data") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
Vector<real_t> map_data;
map_data.push_back(1.0);
map_data.push_back(2.0);
height_map_shape->set_map_data(map_data);
CHECK(height_map_shape->get_map_data().size() == 4.0);
CHECK(height_map_shape->get_map_data()[0] == 0.0);
CHECK(height_map_shape->get_map_data()[1] == 0.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] get_min_height") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(3);
height_map_shape->set_map_depth(1);
height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
CHECK(height_map_shape->get_min_height() == 0.5);
}
TEST_CASE("[SceneTree][HeightMapShape3D] get_max_height") {
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
height_map_shape->set_map_width(3);
height_map_shape->set_map_depth(1);
height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 });
CHECK(height_map_shape->get_max_height() == 2.0);
}
TEST_CASE("[SceneTree][HeightMapShape3D] update_map_data_from_image") {
// Create a HeightMapShape3D instance.
Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D);
// Create a mock image with FORMAT_R8 and set its data.
Vector<uint8_t> image_data;
image_data.push_back(0);
image_data.push_back(128);
image_data.push_back(255);
image_data.push_back(64);
Ref<Image> image = memnew(Image);
image->set_data(2, 2, false, Image::FORMAT_R8, image_data);
height_map_shape->update_map_data_from_image(image, 0.0, 10.0);
// Check the map data.
Vector<real_t> expected_map_data = { 0.0, 5.0, 10.0, 2.5 };
Vector<real_t> actual_map_data = height_map_shape->get_map_data();
real_t tolerance = 0.1;
for (int i = 0; i < expected_map_data.size(); ++i) {
CHECK(Math::abs(actual_map_data[i] - expected_map_data[i]) < tolerance);
}
// Check the min and max heights.
CHECK(height_map_shape->get_min_height() == 0.0);
CHECK(height_map_shape->get_max_height() == 10.0);
}
} // namespace TestHeightMapShape3D

View File

@@ -0,0 +1,108 @@
/**************************************************************************/
/* test_image_texture.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. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "scene/resources/image_texture.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestImageTexture {
// [SceneTree] in a test case name enables initializing a mock render server,
// which ImageTexture is dependent on.
TEST_CASE("[SceneTree][ImageTexture] constructor") {
Ref<ImageTexture> image_texture = memnew(ImageTexture);
CHECK(image_texture->get_width() == 0);
CHECK(image_texture->get_height() == 0);
CHECK(image_texture->get_format() == 0);
CHECK(image_texture->has_alpha() == false);
CHECK(image_texture->get_image() == Ref<Image>());
}
TEST_CASE("[SceneTree][ImageTexture] create_from_image") {
Ref<Image> image = memnew(Image(16, 8, true, Image::FORMAT_RGBA8));
Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image);
CHECK(image_texture->get_width() == 16);
CHECK(image_texture->get_height() == 8);
CHECK(image_texture->get_format() == Image::FORMAT_RGBA8);
CHECK(image_texture->has_alpha() == true);
CHECK(image_texture->get_rid().is_valid() == true);
}
TEST_CASE("[SceneTree][ImageTexture] set_image") {
Ref<ImageTexture> image_texture = memnew(ImageTexture);
Ref<Image> image = memnew(Image(8, 4, false, Image::FORMAT_RGB8));
image_texture->set_image(image);
CHECK(image_texture->get_width() == 8);
CHECK(image_texture->get_height() == 4);
CHECK(image_texture->get_format() == Image::FORMAT_RGB8);
CHECK(image_texture->has_alpha() == false);
CHECK(image_texture->get_width() == image_texture->get_image()->get_width());
CHECK(image_texture->get_height() == image_texture->get_image()->get_height());
CHECK(image_texture->get_format() == image_texture->get_image()->get_format());
}
TEST_CASE("[SceneTree][ImageTexture] set_size_override") {
Ref<Image> image = memnew(Image(16, 8, false, Image::FORMAT_RGB8));
Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image);
CHECK(image_texture->get_width() == 16);
CHECK(image_texture->get_height() == 8);
image_texture->set_size_override(Size2i(32, 16));
CHECK(image_texture->get_width() == 32);
CHECK(image_texture->get_height() == 16);
}
TEST_CASE("[SceneTree][ImageTexture] is_pixel_opaque") {
Ref<Image> image = memnew(Image(8, 8, false, Image::FORMAT_RGBA8));
image->set_pixel(0, 0, Color(0.0, 0.0, 0.0, 0.0)); // not opaque
image->set_pixel(0, 1, Color(0.0, 0.0, 0.0, 0.1)); // not opaque
image->set_pixel(0, 2, Color(0.0, 0.0, 0.0, 0.5)); // opaque
image->set_pixel(0, 3, Color(0.0, 0.0, 0.0, 0.9)); // opaque
image->set_pixel(0, 4, Color(0.0, 0.0, 0.0, 1.0)); // opaque
Ref<ImageTexture> image_texture = ImageTexture::create_from_image(image);
CHECK(image_texture->is_pixel_opaque(0, 0) == false);
CHECK(image_texture->is_pixel_opaque(0, 1) == false);
CHECK(image_texture->is_pixel_opaque(0, 2) == true);
CHECK(image_texture->is_pixel_opaque(0, 3) == true);
CHECK(image_texture->is_pixel_opaque(0, 4) == true);
}
TEST_CASE("[SceneTree][ImageTexture] set_path") {
Ref<ImageTexture> image_texture = memnew(ImageTexture);
String path = TestUtils::get_data_path("images/icon.png");
image_texture->set_path(path, true);
CHECK(image_texture->get_path() == path);
}
} //namespace TestImageTexture

View File

@@ -0,0 +1,98 @@
/**************************************************************************/
/* test_image_texture_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. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "scene/resources/image_texture.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestImageTexture3D {
// [SceneTree] in a test case name enables initializing a mock render server,
// which ImageTexture3D is dependent on.
TEST_CASE("[SceneTree][ImageTexture3D] Constructor") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->get_format() == Image::FORMAT_L8);
CHECK(image_texture_3d->get_width() == 1);
CHECK(image_texture_3d->get_height() == 1);
CHECK(image_texture_3d->get_depth() == 1);
CHECK(image_texture_3d->has_mipmaps() == false);
}
TEST_CASE("[SceneTree][ImageTexture3D] get_format") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->get_format() == Image::FORMAT_L8);
}
TEST_CASE("[SceneTree][ImageTexture3D] get_width") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->get_width() == 1);
}
TEST_CASE("[SceneTree][ImageTexture3D] get_height") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->get_height() == 1);
}
TEST_CASE("[SceneTree][ImageTexture3D] get_depth") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->get_depth() == 1);
}
TEST_CASE("[SceneTree][ImageTexture3D] has_mipmaps") {
const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) };
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->has_mipmaps() == false); // No mipmaps.
image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images);
CHECK(image_texture_3d->has_mipmaps() == true); // Mipmaps.
}
TEST_CASE("[SceneTree][ImageTexture3D] create") {
const Vector<Ref<Image>> images = { memnew(Image(8, 8, false, Image::FORMAT_RGBA8)), memnew(Image(8, 8, false, Image::FORMAT_RGBA8)) };
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
CHECK(image_texture_3d->create(Image::FORMAT_RGBA8, 2, 2, 2, true, images) == OK); // Run create and check return value simultaneously.
CHECK(image_texture_3d->get_format() == Image::FORMAT_RGBA8);
CHECK(image_texture_3d->get_width() == 2);
CHECK(image_texture_3d->get_height() == 2);
CHECK(image_texture_3d->get_depth() == 2);
CHECK(image_texture_3d->has_mipmaps() == true);
}
TEST_CASE("[SceneTree][ImageTexture3D] set_path") {
Ref<ImageTexture3D> image_texture_3d = memnew(ImageTexture3D);
String path = TestUtils::get_data_path("images/icon.png");
image_texture_3d->set_path(path, true);
CHECK(image_texture_3d->get_path() == path);
}
} //namespace TestImageTexture3D

View File

@@ -0,0 +1,532 @@
/**************************************************************************/
/* test_instance_placeholder.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. */
/**************************************************************************/
#pragma once
#include "scene/main/instance_placeholder.h"
#include "scene/resources/packed_scene.h"
#include "tests/test_macros.h"
class _TestInstancePlaceholderNode : public Node {
GDCLASS(_TestInstancePlaceholderNode, Node);
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("set_int_property", "int_property"), &_TestInstancePlaceholderNode::set_int_property);
ClassDB::bind_method(D_METHOD("get_int_property"), &_TestInstancePlaceholderNode::get_int_property);
ADD_PROPERTY(PropertyInfo(Variant::INT, "int_property"), "set_int_property", "get_int_property");
ClassDB::bind_method(D_METHOD("set_reference_property", "reference_property"), &_TestInstancePlaceholderNode::set_reference_property);
ClassDB::bind_method(D_METHOD("get_reference_property"), &_TestInstancePlaceholderNode::get_reference_property);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "reference_property", PROPERTY_HINT_NODE_TYPE), "set_reference_property", "get_reference_property");
ClassDB::bind_method(D_METHOD("set_reference_array_property", "reference_array_property"), &_TestInstancePlaceholderNode::set_reference_array_property);
ClassDB::bind_method(D_METHOD("get_reference_array_property"), &_TestInstancePlaceholderNode::get_reference_array_property);
// The hint string value "24/34:Node" is determined from existing PackedScenes with typed Array properties.
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "reference_array_property", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_reference_array_property", "get_reference_array_property");
}
public:
int int_property = 0;
void set_int_property(int p_int) {
int_property = p_int;
}
int get_int_property() const {
return int_property;
}
Variant reference_property;
void set_reference_property(const Variant &p_node) {
reference_property = p_node;
}
Variant get_reference_property() const {
return reference_property;
}
Array reference_array_property;
void set_reference_array_property(const Array &p_array) {
reference_array_property = p_array;
}
Array get_reference_array_property() const {
return reference_array_property;
}
_TestInstancePlaceholderNode() {
reference_array_property.set_typed(Variant::OBJECT, "Node", Variant());
}
};
namespace TestInstancePlaceholder {
TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with no overrides") {
GDREGISTER_CLASS(_TestInstancePlaceholderNode);
SUBCASE("with non-node values") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
ip->set_name("TestScene");
Node *root = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
root->add_child(ip);
// Create a scene to instance.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
scene->set_int_property(12);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
const Error err = packed_scene->pack(scene);
REQUIRE(err == OK);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_name() == "TestScene");
CHECK(created->get_int_property() == 12);
root->queue_free();
memdelete(scene);
}
SUBCASE("with node value") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
ip->set_name("TestScene");
Node *root = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
root->add_child(ip);
// Create a scene to instance.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
Node *referenced = memnew(Node);
scene->add_child(referenced);
referenced->set_owner(scene);
scene->set_reference_property(referenced);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
const Error err = packed_scene->pack(scene);
REQUIRE(err == OK);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_name() == "TestScene");
CHECK(created->get_child_count() == 1);
CHECK(created->get_reference_property().identity_compare(created->get_child(0, false)));
CHECK_FALSE(created->get_reference_property().identity_compare(referenced));
root->queue_free();
memdelete(scene);
}
SUBCASE("with node-array value") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
ip->set_name("TestScene");
Node *root = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
root->add_child(ip);
// Create a scene to instance.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
Node *referenced1 = memnew(Node);
Node *referenced2 = memnew(Node);
scene->add_child(referenced1);
scene->add_child(referenced2);
referenced1->set_owner(scene);
referenced2->set_owner(scene);
Array node_array;
node_array.set_typed(Variant::OBJECT, "Node", Variant());
node_array.push_back(referenced1);
node_array.push_back(referenced2);
scene->set_reference_array_property(node_array);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
const Error err = packed_scene->pack(scene);
REQUIRE(err == OK);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_name() == "TestScene");
CHECK(created->get_child_count() == 2);
Array created_array = created->get_reference_array_property();
REQUIRE(created_array.size() == node_array.size());
REQUIRE(created_array.size() == created->get_child_count());
// Iterate over all nodes, since the ordering is not guaranteed.
for (int i = 0; i < node_array.size(); i++) {
bool node_found = false;
for (int j = 0; j < created->get_child_count(); j++) {
if (created_array[i].identity_compare(created->get_child(j, true))) {
node_found = true;
}
}
CHECK(node_found);
}
root->queue_free();
memdelete(scene);
}
}
TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with overrides") {
GDREGISTER_CLASS(_TestInstancePlaceholderNode);
SUBCASE("with non-node values") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
Node *root = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
root->add_child(ip);
ip->set_name("TestScene");
ip->set("int_property", 45);
// Create a scene to pack.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
scene->set_int_property(12);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
packed_scene->pack(scene);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_int_property() == 45);
root->queue_free();
memdelete(scene);
}
SUBCASE("with node values") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
ip->set_name("TestScene");
Node *root = memnew(Node);
Node *overriding = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
root->add_child(ip);
root->add_child(overriding);
ip->set("reference_property", overriding);
// Create a scene to instance.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
Node *referenced = memnew(Node);
scene->add_child(referenced);
referenced->set_owner(scene);
scene->set_reference_property(referenced);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
const Error err = packed_scene->pack(scene);
REQUIRE(err == OK);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_name() == "TestScene");
CHECK(created->get_child_count() == 1);
CHECK(created->get_reference_property().identity_compare(overriding));
CHECK_FALSE(created->get_reference_property().identity_compare(referenced));
root->queue_free();
memdelete(scene);
}
SUBCASE("with node-array value") {
InstancePlaceholder *ip = memnew(InstancePlaceholder);
ip->set_name("TestScene");
Node *root = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(root);
Node *override1 = memnew(Node);
Node *override2 = memnew(Node);
Node *override3 = memnew(Node);
root->add_child(ip);
root->add_child(override1);
root->add_child(override2);
root->add_child(override3);
Array override_node_array;
override_node_array.set_typed(Variant::OBJECT, "Node", Variant());
override_node_array.push_back(override1);
override_node_array.push_back(override2);
override_node_array.push_back(override3);
ip->set("reference_array_property", override_node_array);
// Create a scene to instance.
_TestInstancePlaceholderNode *scene = memnew(_TestInstancePlaceholderNode);
Node *referenced1 = memnew(Node);
Node *referenced2 = memnew(Node);
scene->add_child(referenced1);
scene->add_child(referenced2);
referenced1->set_owner(scene);
referenced2->set_owner(scene);
Array referenced_array;
referenced_array.set_typed(Variant::OBJECT, "Node", Variant());
referenced_array.push_back(referenced1);
referenced_array.push_back(referenced2);
scene->set_reference_array_property(referenced_array);
// Pack the scene.
PackedScene *packed_scene = memnew(PackedScene);
const Error err = packed_scene->pack(scene);
REQUIRE(err == OK);
// Instantiate the scene.
_TestInstancePlaceholderNode *created = Object::cast_to<_TestInstancePlaceholderNode>(ip->create_instance(true, packed_scene));
REQUIRE(created != nullptr);
CHECK(created->get_name() == "TestScene");
CHECK(created->get_child_count() == 2);
Array created_array = created->get_reference_array_property();
REQUIRE_FALSE(created_array.size() == referenced_array.size());
REQUIRE(created_array.size() == override_node_array.size());
REQUIRE_FALSE(created_array.size() == created->get_child_count());
// Iterate over all nodes, since the ordering is not guaranteed.
for (int i = 0; i < override_node_array.size(); i++) {
bool node_found = false;
for (int j = 0; j < created_array.size(); j++) {
if (override_node_array[i].identity_compare(created_array[j])) {
node_found = true;
}
}
CHECK(node_found);
}
root->queue_free();
memdelete(scene);
}
}
#ifdef TOOLS_ENABLED
TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with no overrides") {
GDREGISTER_CLASS(_TestInstancePlaceholderNode);
// Create the internal scene.
_TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode);
internal->set_name("InternalNode");
Node *referenced = memnew(Node);
referenced->set_name("OriginalReference");
internal->add_child(referenced);
referenced->set_owner(internal);
internal->set_reference_property(referenced);
// Pack the internal scene.
PackedScene *internal_scene = memnew(PackedScene);
Error err = internal_scene->pack(internal);
REQUIRE(err == OK);
const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal.tscn");
err = ResourceSaver::save(internal_scene, internal_path);
REQUIRE(err == OK);
Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err);
REQUIRE(err == OK);
// Create the main scene.
Node *root = memnew(Node);
root->set_name("MainNode");
Node *overriding = memnew(Node);
overriding->set_name("OverridingReference");
_TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED));
internal_created->set_scene_instance_load_placeholder(true);
root->add_child(internal_created);
internal_created->set_owner(root);
root->add_child(overriding);
overriding->set_owner(root);
// Here we introduce an error, we override the property with an internal node to the instance placeholder.
// The InstancePlaceholder is now forced to properly resolve the Node.
internal_created->set("reference_property", NodePath("OriginalReference"));
// Pack the main scene.
PackedScene *main_scene = memnew(PackedScene);
err = main_scene->pack(root);
REQUIRE(err == OK);
const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main.tscn");
err = ResourceSaver::save(main_scene, main_path);
REQUIRE(err == OK);
// // Instantiate the scene.
Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err);
REQUIRE(err == OK);
Node *instanced_main_node = main_scene_loaded->instantiate();
REQUIRE(instanced_main_node != nullptr);
SceneTree::get_singleton()->get_root()->add_child(instanced_main_node);
CHECK(instanced_main_node->get_name() == "MainNode");
REQUIRE(instanced_main_node->get_child_count() == 2);
InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true));
REQUIRE(instanced_placeholder != nullptr);
_TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true));
REQUIRE(final_node != nullptr);
REQUIRE(final_node->get_child_count() == 1);
REQUIRE(final_node->get_reference_property().identity_compare(final_node->get_child(0, true)));
instanced_main_node->queue_free();
memdelete(overriding);
memdelete(root);
memdelete(internal);
DirAccess::remove_file_or_error(internal_path);
DirAccess::remove_file_or_error(main_path);
}
TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with overrides") {
GDREGISTER_CLASS(_TestInstancePlaceholderNode);
// Create the internal scene.
_TestInstancePlaceholderNode *internal = memnew(_TestInstancePlaceholderNode);
internal->set_name("InternalNode");
Node *referenced = memnew(Node);
referenced->set_name("OriginalReference");
internal->add_child(referenced);
referenced->set_owner(internal);
internal->set_reference_property(referenced);
Node *array_ref1 = memnew(Node);
array_ref1->set_name("ArrayRef1");
internal->add_child(array_ref1);
array_ref1->set_owner(internal);
Node *array_ref2 = memnew(Node);
array_ref2->set_name("ArrayRef2");
internal->add_child(array_ref2);
array_ref2->set_owner(internal);
Array referenced_array;
referenced_array.set_typed(Variant::OBJECT, "Node", Variant());
referenced_array.push_back(array_ref1);
referenced_array.push_back(array_ref2);
internal->set_reference_array_property(referenced_array);
// Pack the internal scene.
PackedScene *internal_scene = memnew(PackedScene);
Error err = internal_scene->pack(internal);
REQUIRE(err == OK);
const String internal_path = TestUtils::get_temp_path("instance_placeholder_test_internal_override.tscn");
err = ResourceSaver::save(internal_scene, internal_path);
REQUIRE(err == OK);
Ref<PackedScene> internal_scene_loaded = ResourceLoader::load(internal_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err);
REQUIRE(err == OK);
// Create the main scene.
Node *root = memnew(Node);
root->set_name("MainNode");
Node *overriding = memnew(Node);
overriding->set_name("OverridingReference");
Node *array_ext = memnew(Node);
array_ext->set_name("ExternalArrayMember");
_TestInstancePlaceholderNode *internal_created = Object::cast_to<_TestInstancePlaceholderNode>(internal_scene_loaded->instantiate(PackedScene::GEN_EDIT_STATE_MAIN_INHERITED));
internal_created->set_scene_instance_load_placeholder(true);
root->add_child(internal_created);
internal_created->set_owner(root);
root->add_child(overriding);
overriding->set_owner(root);
root->add_child(array_ext);
array_ext->set_owner(root);
// Here we introduce an error, we override the property with an internal node to the instance placeholder.
// The InstancePlaceholder is now forced to properly resolve the Node.
internal_created->set_reference_property(overriding);
Array internal_array = internal_created->get_reference_array_property();
Array override_array;
override_array.set_typed(Variant::OBJECT, "Node", Variant());
for (int i = 0; i < internal_array.size(); i++) {
override_array.push_back(internal_array[i]);
}
override_array.push_back(array_ext);
internal_created->set_reference_array_property(override_array);
// Pack the main scene.
PackedScene *main_scene = memnew(PackedScene);
err = main_scene->pack(root);
REQUIRE(err == OK);
const String main_path = TestUtils::get_temp_path("instance_placeholder_test_main_override.tscn");
err = ResourceSaver::save(main_scene, main_path);
REQUIRE(err == OK);
// // Instantiate the scene.
Ref<PackedScene> main_scene_loaded = ResourceLoader::load(main_path, "PackedScene", ResourceFormatLoader::CacheMode::CACHE_MODE_IGNORE, &err);
REQUIRE(err == OK);
Node *instanced_main_node = main_scene_loaded->instantiate();
REQUIRE(instanced_main_node != nullptr);
SceneTree::get_singleton()->get_root()->add_child(instanced_main_node);
CHECK(instanced_main_node->get_name() == "MainNode");
REQUIRE(instanced_main_node->get_child_count() == 3);
InstancePlaceholder *instanced_placeholder = Object::cast_to<InstancePlaceholder>(instanced_main_node->get_child(0, true));
REQUIRE(instanced_placeholder != nullptr);
_TestInstancePlaceholderNode *final_node = Object::cast_to<_TestInstancePlaceholderNode>(instanced_placeholder->create_instance(true));
REQUIRE(final_node != nullptr);
REQUIRE(final_node->get_child_count() == 3);
REQUIRE(final_node->get_reference_property().identity_compare(instanced_main_node->get_child(1, true)));
Array final_array = final_node->get_reference_array_property();
REQUIRE(final_array.size() == 3);
Array wanted_node_array = {
instanced_main_node->get_child(2, true), // ExternalArrayMember
final_node->get_child(1, true), // ArrayRef1
final_node->get_child(2, true) // ArrayRef2
};
// Iterate over all nodes, since the ordering is not guaranteed.
for (int i = 0; i < wanted_node_array.size(); i++) {
bool node_found = false;
for (int j = 0; j < final_array.size(); j++) {
if (wanted_node_array[i].identity_compare(final_array[j])) {
node_found = true;
}
}
CHECK(node_found);
}
instanced_main_node->queue_free();
memdelete(array_ext);
memdelete(overriding);
memdelete(root);
memdelete(internal);
DirAccess::remove_file_or_error(internal_path);
DirAccess::remove_file_or_error(main_path);
}
#endif // TOOLS_ENABLED
} //namespace TestInstancePlaceholder

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* test_navigation_agent_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/navigation/navigation_agent_2d.h"
#include "scene/2d/node_2d.h"
#include "scene/main/window.h"
#include "scene/resources/world_2d.h"
#include "tests/test_macros.h"
namespace TestNavigationAgent2D {
TEST_SUITE("[Navigation2D]") {
TEST_CASE("[SceneTree][NavigationAgent2D] New agent should have valid RID") {
NavigationAgent2D *agent_node = memnew(NavigationAgent2D);
CHECK(agent_node->get_rid().is_valid());
memdelete(agent_node);
}
TEST_CASE("[SceneTree][NavigationAgent2D] New agent should attach to default map") {
Node2D *node_2d = memnew(Node2D);
SceneTree::get_singleton()->get_root()->add_child(node_2d);
NavigationAgent2D *agent_node = memnew(NavigationAgent2D);
// agent should not be attached to any map when outside of tree
CHECK_FALSE(agent_node->get_navigation_map().is_valid());
SUBCASE("Agent should attach to default map when it enters the tree") {
node_2d->add_child(agent_node);
CHECK(agent_node->get_navigation_map().is_valid());
CHECK(agent_node->get_navigation_map() == node_2d->get_world_2d()->get_navigation_map());
}
memdelete(agent_node);
memdelete(node_2d);
}
}
} //namespace TestNavigationAgent2D

View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* test_navigation_agent_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/navigation/navigation_agent_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestNavigationAgent3D {
TEST_SUITE("[Navigation3D]") {
TEST_CASE("[SceneTree][NavigationAgent3D] New agent should have valid RID") {
NavigationAgent3D *agent_node = memnew(NavigationAgent3D);
CHECK(agent_node->get_rid().is_valid());
memdelete(agent_node);
}
TEST_CASE("[SceneTree][NavigationAgent3D] New agent should attach to default map") {
Node3D *node_3d = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(node_3d);
NavigationAgent3D *agent_node = memnew(NavigationAgent3D);
// agent should not be attached to any map when outside of tree
CHECK_FALSE(agent_node->get_navigation_map().is_valid());
SUBCASE("Agent should attach to default map when it enters the tree") {
node_3d->add_child(agent_node);
CHECK(agent_node->get_navigation_map().is_valid());
CHECK(agent_node->get_navigation_map() == node_3d->get_world_3d()->get_navigation_map());
}
memdelete(agent_node);
memdelete(node_3d);
}
}
} //namespace TestNavigationAgent3D

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* test_navigation_obstacle_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/navigation/navigation_obstacle_2d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestNavigationObstacle2D {
TEST_SUITE("[Navigation2D]") {
TEST_CASE("[SceneTree][NavigationObstacle2D] New obstacle should have valid RID") {
NavigationObstacle2D *obstacle_node = memnew(NavigationObstacle2D);
CHECK(obstacle_node->get_rid().is_valid());
memdelete(obstacle_node);
}
TEST_CASE("[SceneTree][NavigationObstacle2D] New obstacle should attach to default map") {
Node2D *node_2d = memnew(Node2D);
SceneTree::get_singleton()->get_root()->add_child(node_2d);
NavigationObstacle2D *obstacle_node = memnew(NavigationObstacle2D);
// obstacle should not be attached to any map when outside of tree
CHECK_FALSE(obstacle_node->get_navigation_map().is_valid());
SUBCASE("Obstacle should attach to default map when it enters the tree") {
node_2d->add_child(obstacle_node);
CHECK(obstacle_node->get_navigation_map().is_valid());
CHECK(obstacle_node->get_navigation_map() == node_2d->get_world_2d()->get_navigation_map());
}
memdelete(obstacle_node);
memdelete(node_2d);
}
}
} //namespace TestNavigationObstacle2D

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* test_navigation_obstacle_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/navigation/navigation_obstacle_3d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestNavigationObstacle3D {
TEST_SUITE("[Navigation3D]") {
TEST_CASE("[SceneTree][NavigationObstacle3D] New obstacle should have valid RID") {
NavigationObstacle3D *obstacle_node = memnew(NavigationObstacle3D);
CHECK(obstacle_node->get_rid().is_valid());
memdelete(obstacle_node);
}
TEST_CASE("[SceneTree][NavigationObstacle3D] New obstacle should attach to default map") {
Node3D *node_3d = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(node_3d);
NavigationObstacle3D *obstacle_node = memnew(NavigationObstacle3D);
// obstacle should not be attached to any map when outside of tree
CHECK_FALSE(obstacle_node->get_navigation_map().is_valid());
SUBCASE("Obstacle should attach to default map when it enters the tree") {
node_3d->add_child(obstacle_node);
CHECK(obstacle_node->get_navigation_map().is_valid());
CHECK(obstacle_node->get_navigation_map() == node_3d->get_world_3d()->get_navigation_map());
}
memdelete(obstacle_node);
memdelete(node_3d);
}
}
} //namespace TestNavigationObstacle3D

View File

@@ -0,0 +1,48 @@
/**************************************************************************/
/* test_navigation_region_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/navigation/navigation_region_2d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestNavigationRegion2D {
TEST_SUITE("[Navigation2D]") {
TEST_CASE("[SceneTree][NavigationRegion2D] New region should have valid RID") {
NavigationRegion2D *region_node = memnew(NavigationRegion2D);
CHECK(region_node->get_rid().is_valid());
memdelete(region_node);
}
}
} //namespace TestNavigationRegion2D

View File

@@ -0,0 +1,81 @@
/**************************************************************************/
/* test_navigation_region_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/navigation/navigation_region_3d.h"
#include "scene/main/window.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "tests/test_macros.h"
namespace TestNavigationRegion3D {
TEST_SUITE("[Navigation3D]") {
TEST_CASE("[SceneTree][NavigationRegion3D] New region should have valid RID") {
NavigationRegion3D *region_node = memnew(NavigationRegion3D);
CHECK(region_node->get_rid().is_valid());
memdelete(region_node);
}
TEST_CASE("[SceneTree][NavigationRegion3D] Region should bake successfully from valid geometry") {
Node3D *node_3d = memnew(Node3D);
SceneTree::get_singleton()->get_root()->add_child(node_3d);
Ref<NavigationMesh> navigation_mesh = memnew(NavigationMesh);
NavigationRegion3D *navigation_region = memnew(NavigationRegion3D);
navigation_region->set_navigation_mesh(navigation_mesh);
node_3d->add_child(navigation_region);
Ref<PlaneMesh> plane_mesh = memnew(PlaneMesh);
plane_mesh->set_size(Size2(10.0, 10.0));
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
mesh_instance->set_mesh(plane_mesh);
navigation_region->add_child(mesh_instance);
CHECK_FALSE(navigation_region->is_baking());
CHECK_EQ(navigation_mesh->get_polygon_count(), 0);
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
SUBCASE("Synchronous bake should have immediate effects") {
ERR_PRINT_OFF; // Suppress warning about baking from visual meshes as source geometry.
navigation_region->bake_navigation_mesh(false);
ERR_PRINT_ON;
CHECK_FALSE(navigation_region->is_baking());
CHECK_NE(navigation_mesh->get_polygon_count(), 0);
CHECK_NE(navigation_mesh->get_vertices().size(), 0);
}
memdelete(mesh_instance);
memdelete(navigation_region);
memdelete(node_3d);
}
}
} //namespace TestNavigationRegion3D

919
tests/scene/test_node.h Normal file
View File

@@ -0,0 +1,919 @@
/**************************************************************************/
/* test_node.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. */
/**************************************************************************/
#pragma once
#include "core/object/class_db.h"
#include "scene/main/node.h"
#include "scene/resources/packed_scene.h"
#include "tests/test_macros.h"
namespace TestNode {
class TestNode : public Node {
GDCLASS(TestNode, Node);
protected:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
internal_process_counter++;
push_self();
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
internal_physics_process_counter++;
push_self();
} break;
case NOTIFICATION_PROCESS: {
process_counter++;
push_self();
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
physics_process_counter++;
push_self();
} break;
}
}
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("set_exported_node", "node"), &TestNode::set_exported_node);
ClassDB::bind_method(D_METHOD("get_exported_node"), &TestNode::get_exported_node);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "exported_node", PROPERTY_HINT_NODE_TYPE, "Node"), "set_exported_node", "get_exported_node");
ClassDB::bind_method(D_METHOD("set_exported_nodes", "node"), &TestNode::set_exported_nodes);
ClassDB::bind_method(D_METHOD("get_exported_nodes"), &TestNode::get_exported_nodes);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "exported_nodes", PROPERTY_HINT_TYPE_STRING, "24/34:Node"), "set_exported_nodes", "get_exported_nodes");
}
private:
void push_self() {
if (callback_list) {
callback_list->push_back(this);
}
}
public:
int internal_process_counter = 0;
int internal_physics_process_counter = 0;
int process_counter = 0;
int physics_process_counter = 0;
Node *exported_node = nullptr;
Array exported_nodes;
List<Node *> *callback_list = nullptr;
void set_exported_node(Node *p_node) { exported_node = p_node; }
Node *get_exported_node() const { return exported_node; }
void set_exported_nodes(const Array &p_nodes) { exported_nodes = p_nodes; }
Array get_exported_nodes() const { return exported_nodes; }
TestNode() {
Node *internal = memnew(Node);
add_child(internal, false, INTERNAL_MODE_FRONT);
internal = memnew(Node);
add_child(internal, false, INTERNAL_MODE_BACK);
}
};
TEST_CASE("[SceneTree][Node] Testing node operations with a very simple scene tree") {
Node *node = memnew(Node);
// Check initial scene tree setup.
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 0);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 1);
// Check initial node setup.
CHECK(node->get_name() == StringName());
CHECK_FALSE(node->is_inside_tree());
CHECK_EQ(node->get_parent(), nullptr);
ERR_PRINT_OFF;
CHECK(node->get_path().is_empty());
ERR_PRINT_ON;
CHECK_EQ(node->get_child_count(), 0);
SceneTree::get_singleton()->get_root()->add_child(node);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 1);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 2);
CHECK(node->get_name() != StringName());
CHECK(node->is_inside_tree());
CHECK_EQ(SceneTree::get_singleton()->get_root(), node->get_parent());
CHECK_FALSE(node->get_path().is_empty());
CHECK_EQ(node->get_child_count(), 0);
SUBCASE("Node should be accessible as first child") {
Node *child = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child, node);
}
SUBCASE("Node should be accessible via the node path") {
Node *child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(node->get_path());
CHECK_EQ(child_by_path, node);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("Node"));
CHECK_EQ(child_by_path, nullptr);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("/root/Node"));
CHECK_EQ(child_by_path, nullptr);
node->set_name("Node");
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(node->get_path());
CHECK_EQ(child_by_path, node);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("Node"));
CHECK_EQ(child_by_path, node);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("/root/Node"));
CHECK_EQ(child_by_path, node);
}
SUBCASE("Node should be accessible via group") {
List<Node *> nodes;
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK(nodes.is_empty());
node->add_to_group("nodes");
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK_EQ(nodes.size(), 1);
List<Node *>::Element *E = nodes.front();
CHECK_EQ(E->get(), node);
}
SUBCASE("Node should be possible to find") {
Node *child = SceneTree::get_singleton()->get_root()->find_child("Node", true, false);
CHECK_EQ(child, nullptr);
node->set_name("Node");
child = SceneTree::get_singleton()->get_root()->find_child("Node", true, false);
CHECK_EQ(child, node);
}
SUBCASE("Node should be possible to remove") {
SceneTree::get_singleton()->get_root()->remove_child(node);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 0);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 1);
CHECK_FALSE(node->is_inside_tree());
CHECK_EQ(node->get_parent(), nullptr);
ERR_PRINT_OFF;
CHECK(node->get_path().is_empty());
ERR_PRINT_ON;
}
SUBCASE("Node should be possible to move") {
SceneTree::get_singleton()->get_root()->move_child(node, 0);
Node *child = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child, node);
CHECK(node->is_inside_tree());
}
SUBCASE("Node should be possible to reparent") {
node->reparent(SceneTree::get_singleton()->get_root());
Node *child = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child, node);
CHECK(node->is_inside_tree());
}
SUBCASE("Node should be possible to duplicate") {
node->set_name("MyName");
Node *duplicate = node->duplicate();
CHECK_FALSE(node == duplicate);
CHECK_FALSE(duplicate->is_inside_tree());
CHECK_EQ(duplicate->get_name(), node->get_name());
memdelete(duplicate);
}
memdelete(node);
}
TEST_CASE("[SceneTree][Node] Testing node operations with a more complex simple scene tree") {
Node *node1 = memnew(Node);
Node *node2 = memnew(Node);
Node *node1_1 = memnew(Node);
SceneTree::get_singleton()->get_root()->add_child(node1);
SceneTree::get_singleton()->get_root()->add_child(node2);
node1->add_child(node1_1);
CHECK(node1_1->is_inside_tree());
CHECK_EQ(node1_1->get_parent(), node1);
CHECK_EQ(node1->get_child_count(), 1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 2);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 4);
SUBCASE("Nodes should be accessible via get_child(..)") {
Node *child1 = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child1, node1);
Node *child2 = SceneTree::get_singleton()->get_root()->get_child(1);
CHECK_EQ(child2, node2);
Node *child1_1 = node1->get_child(0);
CHECK_EQ(child1_1, node1_1);
}
SUBCASE("Removed nodes should also remove their children from the scene tree") {
// Should also remove node1_1 from the scene tree.
SceneTree::get_singleton()->get_root()->remove_child(node1);
CHECK_EQ(node1->get_child_count(), 1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 1);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 2);
// First child should now be the second node.
Node *child1 = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child1, node2);
}
SUBCASE("Removed children nodes should not affect their parent in the scene tree") {
node1->remove_child(node1_1);
CHECK_EQ(node1_1->get_parent(), nullptr);
CHECK_EQ(node1->get_child_count(), 0);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 3);
}
SUBCASE("Nodes should be in the expected order when a node is moved to the back") {
SceneTree::get_singleton()->get_root()->move_child(node1, 1);
Node *child1 = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child1, node2);
Node *child2 = SceneTree::get_singleton()->get_root()->get_child(1);
CHECK_EQ(child2, node1);
}
SUBCASE("Nodes should be in the expected order when a node is moved to the front") {
SceneTree::get_singleton()->get_root()->move_child(node2, 0);
Node *child1 = SceneTree::get_singleton()->get_root()->get_child(0);
CHECK_EQ(child1, node2);
Node *child2 = SceneTree::get_singleton()->get_root()->get_child(1);
CHECK_EQ(child2, node1);
}
SUBCASE("Nodes should be in the expected order when reparented (remove/add)") {
CHECK_EQ(node2->get_child_count(), 0);
node1->remove_child(node1_1);
CHECK_EQ(node1->get_child_count(), 0);
CHECK_EQ(node1_1->get_parent(), nullptr);
node2->add_child(node1_1);
CHECK_EQ(node2->get_child_count(), 1);
CHECK_EQ(node1_1->get_parent(), node2);
Node *child = node2->get_child(0);
CHECK_EQ(child, node1_1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 2);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 4);
}
SUBCASE("Nodes should be in the expected order when reparented") {
CHECK_EQ(node2->get_child_count(), 0);
node1_1->reparent(node2);
CHECK_EQ(node1->get_child_count(), 0);
CHECK_EQ(node2->get_child_count(), 1);
CHECK_EQ(node1_1->get_parent(), node2);
Node *child = node2->get_child(0);
CHECK_EQ(child, node1_1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 2);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 4);
}
SUBCASE("Nodes should be possible to find") {
Node *child = SceneTree::get_singleton()->get_root()->find_child("NestedNode", true, false);
CHECK_EQ(child, nullptr);
TypedArray<Node> children = SceneTree::get_singleton()->get_root()->find_children("NestedNode", "", true, false);
CHECK_EQ(children.size(), 0);
node1->set_name("Node1");
node2->set_name("Node2");
node1_1->set_name("NestedNode");
child = SceneTree::get_singleton()->get_root()->find_child("NestedNode", true, false);
CHECK_EQ(child, node1_1);
children = SceneTree::get_singleton()->get_root()->find_children("NestedNode", "", true, false);
CHECK_EQ(children.size(), 1);
CHECK_EQ(Object::cast_to<Node>(children[0]), node1_1);
// First node that matches with the name is node1.
child = SceneTree::get_singleton()->get_root()->find_child("Node?", true, false);
CHECK_EQ(child, node1);
children = SceneTree::get_singleton()->get_root()->find_children("Node?", "", true, false);
CHECK_EQ(children.size(), 2);
CHECK_EQ(Object::cast_to<Node>(children[0]), node1);
CHECK_EQ(Object::cast_to<Node>(children[1]), node2);
SceneTree::get_singleton()->get_root()->move_child(node2, 0);
// It should be node2, as it is now the first one in the tree.
child = SceneTree::get_singleton()->get_root()->find_child("Node?", true, false);
CHECK_EQ(child, node2);
children = SceneTree::get_singleton()->get_root()->find_children("Node?", "", true, false);
CHECK_EQ(children.size(), 2);
CHECK_EQ(Object::cast_to<Node>(children[0]), node2);
CHECK_EQ(Object::cast_to<Node>(children[1]), node1);
}
SUBCASE("Nodes should be accessible via their node path") {
Node *child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(node1->get_path());
CHECK_EQ(child_by_path, node1);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(node2->get_path());
CHECK_EQ(child_by_path, node2);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(node1_1->get_path());
CHECK_EQ(child_by_path, node1_1);
node1->set_name("Node1");
node1_1->set_name("NestedNode");
child_by_path = node1->get_node_or_null(NodePath("NestedNode"));
CHECK_EQ(child_by_path, node1_1);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("/root/Node1/NestedNode"));
CHECK_EQ(child_by_path, node1_1);
child_by_path = SceneTree::get_singleton()->get_root()->get_node_or_null(NodePath("Node1/NestedNode"));
CHECK_EQ(child_by_path, node1_1);
}
SUBCASE("Nodes should be accessible via their groups") {
List<Node *> nodes;
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK(nodes.is_empty());
SceneTree::get_singleton()->get_nodes_in_group("other_nodes", &nodes);
CHECK(nodes.is_empty());
node1->add_to_group("nodes");
node2->add_to_group("other_nodes");
node1_1->add_to_group("nodes");
node1_1->add_to_group("other_nodes");
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK_EQ(nodes.size(), 2);
List<Node *>::Element *E = nodes.front();
CHECK_EQ(E->get(), node1);
E = E->next();
CHECK_EQ(E->get(), node1_1);
// Clear and try again with the other group.
nodes.clear();
SceneTree::get_singleton()->get_nodes_in_group("other_nodes", &nodes);
CHECK_EQ(nodes.size(), 2);
E = nodes.front();
CHECK_EQ(E->get(), node1_1);
E = E->next();
CHECK_EQ(E->get(), node2);
// Clear and try again with the other group and one node removed.
nodes.clear();
node1->remove_from_group("nodes");
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK_EQ(nodes.size(), 1);
E = nodes.front();
CHECK_EQ(E->get(), node1_1);
}
SUBCASE("Nodes added as siblings of another node should be right next to it") {
node1->remove_child(node1_1);
node1->add_sibling(node1_1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 3);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 4);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child(0), node1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child(1), node1_1);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child(2), node2);
}
SUBCASE("Replaced nodes should be be removed and the replacing node added") {
SceneTree::get_singleton()->get_root()->remove_child(node2);
node1->replace_by(node2);
CHECK_EQ(SceneTree::get_singleton()->get_root()->get_child_count(), 1);
CHECK_EQ(SceneTree::get_singleton()->get_node_count(), 3);
CHECK_FALSE(node1->is_inside_tree());
CHECK(node2->is_inside_tree());
CHECK_EQ(node1->get_parent(), nullptr);
CHECK_EQ(node2->get_parent(), SceneTree::get_singleton()->get_root());
CHECK_EQ(node2->get_child_count(), 1);
CHECK_EQ(node2->get_child(0), node1_1);
}
SUBCASE("Replacing nodes should keep the groups of the replaced nodes") {
SceneTree::get_singleton()->get_root()->remove_child(node2);
node1->add_to_group("nodes");
node1->replace_by(node2, true);
List<Node *> nodes;
SceneTree::get_singleton()->get_nodes_in_group("nodes", &nodes);
CHECK_EQ(nodes.size(), 1);
List<Node *>::Element *E = nodes.front();
CHECK_EQ(E->get(), node2);
}
SUBCASE("Duplicating a node should also duplicate the children") {
node1->set_name("MyName1");
node1_1->set_name("MyName1_1");
Node *duplicate1 = node1->duplicate();
CHECK_EQ(duplicate1->get_child_count(), node1->get_child_count());
Node *duplicate1_1 = duplicate1->get_child(0);
CHECK_EQ(duplicate1_1->get_child_count(), node1_1->get_child_count());
CHECK_EQ(duplicate1->get_name(), node1->get_name());
CHECK_EQ(duplicate1_1->get_name(), node1_1->get_name());
CHECK_FALSE(duplicate1->is_inside_tree());
CHECK_FALSE(duplicate1_1->is_inside_tree());
memdelete(duplicate1_1);
memdelete(duplicate1);
}
memdelete(node1_1);
memdelete(node1);
memdelete(node2);
}
TEST_CASE("[SceneTree][Node] Duplicating node with internal children") {
GDREGISTER_CLASS(TestNode);
TestNode *node = memnew(TestNode);
Node *child = memnew(Node);
child->set_name("Child");
node->add_child(child);
int child_count = node->get_child_count();
Node *dup = node->duplicate();
CHECK(dup->get_child_count() == child_count);
CHECK(dup->has_node(String("Child")));
memdelete(node);
memdelete(dup);
}
TEST_CASE("[SceneTree][Node]Exported node checks") {
TestNode *node = memnew(TestNode);
SceneTree::get_singleton()->get_root()->add_child(node);
Node *child = memnew(Node);
child->set_name("Child");
node->add_child(child);
child->set_owner(node);
Node *child2 = memnew(Node);
child2->set_name("Child2");
node->add_child(child2);
child2->set_owner(node);
Array children;
children.append(child);
node->set("exported_node", child);
node->set("exported_nodes", children);
SUBCASE("Property of duplicated node should point to duplicated child") {
GDREGISTER_CLASS(TestNode);
TestNode *dup = Object::cast_to<TestNode>(node->duplicate());
Node *new_exported = Object::cast_to<Node>(dup->get("exported_node"));
CHECK(new_exported == dup->get_child(0, false));
memdelete(dup);
}
#ifdef TOOLS_ENABLED
SUBCASE("Saving instance with exported nodes should not store the unchanged property") {
Ref<PackedScene> ps;
ps.instantiate();
ps->pack(node);
String scene_path = TestUtils::get_temp_path("test_scene.tscn");
ps->set_path(scene_path);
Node *root = memnew(Node);
Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN);
root->add_child(sub_child);
sub_child->set_owner(root);
Ref<PackedScene> ps2;
ps2.instantiate();
ps2->pack(root);
scene_path = TestUtils::get_temp_path("new_test_scene.tscn");
ResourceSaver::save(ps2, scene_path);
memdelete(root);
bool is_wrong = false;
Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ);
while (!fa->eof_reached()) {
const String line = fa->get_line();
if (line.begins_with("exported_node")) {
// The property was saved, while it shouldn't.
is_wrong = true;
break;
}
}
CHECK_FALSE(is_wrong);
}
SUBCASE("Saving instance with exported nodes should store property if changed") {
Ref<PackedScene> ps;
ps.instantiate();
ps->pack(node);
String scene_path = TestUtils::get_temp_path("test_scene.tscn");
ps->set_path(scene_path);
Node *root = memnew(Node);
Node *sub_child = ps->instantiate(PackedScene::GEN_EDIT_STATE_MAIN);
root->add_child(sub_child);
sub_child->set_owner(root);
sub_child->set("exported_node", sub_child->get_child(1, false));
children = Array();
children.append(sub_child->get_child(1, false));
sub_child->set("exported_nodes", children);
Ref<PackedScene> ps2;
ps2.instantiate();
ps2->pack(root);
scene_path = TestUtils::get_temp_path("new_test_scene2.tscn");
ResourceSaver::save(ps2, scene_path);
memdelete(root);
int stored_properties = 0;
Ref<FileAccess> fa = FileAccess::open(scene_path, FileAccess::READ);
while (!fa->eof_reached()) {
const String line = fa->get_line();
if (line.begins_with("exported_node")) {
stored_properties++;
}
}
CHECK_EQ(stored_properties, 2);
}
#endif // TOOLS_ENABLED
memdelete(node);
}
TEST_CASE("[Node] Processing checks") {
Node *node = memnew(Node);
SUBCASE("Processing") {
CHECK_FALSE(node->is_processing());
node->set_process(true);
CHECK(node->is_processing());
node->set_process(false);
CHECK_FALSE(node->is_processing());
}
SUBCASE("Physics processing") {
CHECK_FALSE(node->is_physics_processing());
node->set_physics_process(true);
CHECK(node->is_physics_processing());
node->set_physics_process(false);
CHECK_FALSE(node->is_physics_processing());
}
SUBCASE("Unhandled input processing") {
CHECK_FALSE(node->is_processing_unhandled_input());
node->set_process_unhandled_input(true);
CHECK(node->is_processing_unhandled_input());
node->set_process_unhandled_input(false);
CHECK_FALSE(node->is_processing_unhandled_input());
}
SUBCASE("Input processing") {
CHECK_FALSE(node->is_processing_input());
node->set_process_input(true);
CHECK(node->is_processing_input());
node->set_process_input(false);
CHECK_FALSE(node->is_processing_input());
}
SUBCASE("Unhandled key input processing") {
CHECK_FALSE(node->is_processing_unhandled_key_input());
node->set_process_unhandled_key_input(true);
CHECK(node->is_processing_unhandled_key_input());
node->set_process_unhandled_key_input(false);
CHECK_FALSE(node->is_processing_unhandled_key_input());
}
SUBCASE("Shortcut input processing") {
CHECK_FALSE(node->is_processing_shortcut_input());
node->set_process_shortcut_input(true);
CHECK(node->is_processing_shortcut_input());
node->set_process_shortcut_input(false);
CHECK_FALSE(node->is_processing_shortcut_input());
}
SUBCASE("Internal processing") {
CHECK_FALSE(node->is_processing_internal());
node->set_process_internal(true);
CHECK(node->is_processing_internal());
node->set_process_internal(false);
CHECK_FALSE(node->is_processing_internal());
}
SUBCASE("Process priority") {
CHECK_EQ(0, node->get_process_priority());
node->set_process_priority(1);
CHECK_EQ(1, node->get_process_priority());
}
SUBCASE("Physics process priority") {
CHECK_EQ(0, node->get_physics_process_priority());
node->set_physics_process_priority(1);
CHECK_EQ(1, node->get_physics_process_priority());
}
memdelete(node);
}
TEST_CASE("[SceneTree][Node] Test the processing") {
TestNode *node = memnew(TestNode);
SceneTree::get_singleton()->get_root()->add_child(node);
SUBCASE("No process") {
CHECK_EQ(0, node->process_counter);
CHECK_EQ(0, node->physics_process_counter);
}
SUBCASE("Process") {
node->set_process(true);
SceneTree::get_singleton()->process(0);
CHECK_EQ(1, node->process_counter);
CHECK_EQ(0, node->physics_process_counter);
CHECK_EQ(0, node->internal_process_counter);
CHECK_EQ(0, node->internal_physics_process_counter);
}
SUBCASE("Physics process") {
node->set_physics_process(true);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(0, node->process_counter);
CHECK_EQ(1, node->physics_process_counter);
CHECK_EQ(0, node->internal_process_counter);
CHECK_EQ(0, node->internal_physics_process_counter);
}
SUBCASE("Normal and physics process") {
node->set_process(true);
node->set_physics_process(true);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(1, node->process_counter);
CHECK_EQ(1, node->physics_process_counter);
CHECK_EQ(0, node->internal_process_counter);
CHECK_EQ(0, node->internal_physics_process_counter);
}
SUBCASE("Internal, normal and physics process") {
node->set_process_internal(true);
node->set_physics_process_internal(true);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(0, node->process_counter);
CHECK_EQ(0, node->physics_process_counter);
CHECK_EQ(1, node->internal_process_counter);
CHECK_EQ(1, node->internal_physics_process_counter);
}
SUBCASE("All processing") {
node->set_process(true);
node->set_physics_process(true);
node->set_process_internal(true);
node->set_physics_process_internal(true);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(1, node->process_counter);
CHECK_EQ(1, node->physics_process_counter);
CHECK_EQ(1, node->internal_process_counter);
CHECK_EQ(1, node->internal_physics_process_counter);
}
SUBCASE("All processing twice") {
node->set_process(true);
node->set_physics_process(true);
node->set_process_internal(true);
node->set_physics_process_internal(true);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(2, node->process_counter);
CHECK_EQ(2, node->physics_process_counter);
CHECK_EQ(2, node->internal_process_counter);
CHECK_EQ(2, node->internal_physics_process_counter);
}
SUBCASE("Enable and disable processing") {
node->set_process(true);
node->set_physics_process(true);
node->set_process_internal(true);
node->set_physics_process_internal(true);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
node->set_process(false);
node->set_physics_process(false);
node->set_process_internal(false);
node->set_physics_process_internal(false);
SceneTree::get_singleton()->process(0);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(1, node->process_counter);
CHECK_EQ(1, node->physics_process_counter);
CHECK_EQ(1, node->internal_process_counter);
CHECK_EQ(1, node->internal_physics_process_counter);
}
memdelete(node);
}
TEST_CASE("[SceneTree][Node] Test the process priority") {
List<Node *> process_order;
TestNode *node = memnew(TestNode);
node->callback_list = &process_order;
SceneTree::get_singleton()->get_root()->add_child(node);
TestNode *node2 = memnew(TestNode);
node2->callback_list = &process_order;
SceneTree::get_singleton()->get_root()->add_child(node2);
TestNode *node3 = memnew(TestNode);
node3->callback_list = &process_order;
SceneTree::get_singleton()->get_root()->add_child(node3);
TestNode *node4 = memnew(TestNode);
node4->callback_list = &process_order;
SceneTree::get_singleton()->get_root()->add_child(node4);
SUBCASE("Process priority") {
node->set_process(true);
node->set_process_priority(20);
node2->set_process(true);
node2->set_process_priority(10);
node3->set_process(true);
node3->set_process_priority(40);
node4->set_process(true);
node4->set_process_priority(30);
SceneTree::get_singleton()->process(0);
CHECK_EQ(4, process_order.size());
List<Node *>::Element *E = process_order.front();
CHECK_EQ(E->get(), node2);
E = E->next();
CHECK_EQ(E->get(), node);
E = E->next();
CHECK_EQ(E->get(), node4);
E = E->next();
CHECK_EQ(E->get(), node3);
}
SUBCASE("Physics process priority") {
node->set_physics_process(true);
node->set_physics_process_priority(20);
node2->set_physics_process(true);
node2->set_physics_process_priority(10);
node3->set_physics_process(true);
node3->set_physics_process_priority(40);
node4->set_physics_process(true);
node4->set_physics_process_priority(30);
SceneTree::get_singleton()->physics_process(0);
CHECK_EQ(4, process_order.size());
List<Node *>::Element *E = process_order.front();
CHECK_EQ(E->get(), node2);
E = E->next();
CHECK_EQ(E->get(), node);
E = E->next();
CHECK_EQ(E->get(), node4);
E = E->next();
CHECK_EQ(E->get(), node3);
}
memdelete(node);
memdelete(node2);
memdelete(node3);
memdelete(node4);
}
} // namespace TestNode

213
tests/scene/test_node_2d.h Normal file
View File

@@ -0,0 +1,213 @@
/**************************************************************************/
/* test_node_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/node_2d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestNode2D {
TEST_CASE("[SceneTree][Node2D]") {
SUBCASE("[Node2D][Global Transform] Global Transform should be accessible while not in SceneTree.") { // GH-79453
Node2D *test_node = memnew(Node2D);
test_node->set_name("node");
Node2D *test_child = memnew(Node2D);
test_child->set_name("child");
test_node->add_child(test_child);
test_node->set_global_position(Point2(1, 1));
CHECK_EQ(test_node->get_global_position(), Point2(1, 1));
CHECK_EQ(test_child->get_global_position(), Point2(1, 1));
test_node->set_global_position(Point2(2, 2));
CHECK_EQ(test_node->get_global_position(), Point2(2, 2));
test_node->set_global_transform(Transform2D(0, Point2(3, 3)));
CHECK_EQ(test_node->get_global_position(), Point2(3, 3));
memdelete(test_child);
memdelete(test_node);
}
SUBCASE("[Node2D][Global Transform] Global Transform should be correct after inserting node from detached tree into SceneTree.") { // GH-86841
Node2D *main = memnew(Node2D);
Node2D *outer = memnew(Node2D);
Node2D *inner = memnew(Node2D);
SceneTree::get_singleton()->get_root()->add_child(main);
main->set_position(Point2(100, 100));
outer->set_position(Point2(10, 0));
inner->set_position(Point2(0, 10));
outer->add_child(inner);
// `inner` is still detached.
CHECK_EQ(inner->get_global_position(), Point2(10, 10));
main->add_child(outer);
// `inner` is in scene tree.
CHECK_EQ(inner->get_global_position(), Point2(110, 110));
main->remove_child(outer);
// `inner` is detached again.
CHECK_EQ(inner->get_global_position(), Point2(10, 10));
memdelete(inner);
memdelete(outer);
memdelete(main);
}
}
TEST_CASE("[SceneTree][Node2D] Utility methods") {
Node2D *test_node1 = memnew(Node2D);
Node2D *test_node2 = memnew(Node2D);
Node2D *test_node3 = memnew(Node2D);
Node2D *test_sibling = memnew(Node2D);
SceneTree::get_singleton()->get_root()->add_child(test_node1);
test_node1->set_position(Point2(100, 100));
test_node1->set_rotation(Math::deg_to_rad(30.0));
test_node1->set_scale(Size2(1, 1));
test_node1->add_child(test_node2);
test_node2->set_position(Point2(10, 0));
test_node2->set_rotation(Math::deg_to_rad(60.0));
test_node2->set_scale(Size2(1, 1));
test_node2->add_child(test_node3);
test_node2->add_child(test_sibling);
test_node3->set_position(Point2(0, 10));
test_node3->set_rotation(Math::deg_to_rad(0.0));
test_node3->set_scale(Size2(2, 2));
test_sibling->set_position(Point2(5, 10));
test_sibling->set_rotation(Math::deg_to_rad(90.0));
test_sibling->set_scale(Size2(2, 1));
SUBCASE("[Node2D] look_at") {
test_node3->look_at(Vector2(1, 1));
CHECK(test_node3->get_global_position().is_equal_approx(Point2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.32477)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.38762)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
test_node3->look_at(Vector2(0, 10));
CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
// Don't do anything if look_at own position.
test_node3->look_at(test_node3->get_global_position());
CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
// Revert any rotation caused by look_at, must run after look_at tests
test_node3->set_rotation(Math::deg_to_rad(0.0));
}
SUBCASE("[Node2D] get_angle_to") {
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(1, 1)), real_t(2.38762)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(0, 10)), real_t(2.3373)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(2, -5)), real_t(2.42065)));
CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(-2, 5)), real_t(2.3529)));
// Return 0 when get_angle_to own position.
CHECK(Math::is_equal_approx(test_node3->get_angle_to(test_node3->get_global_position()), real_t(0)));
}
SUBCASE("[Node2D] to_local") {
Point2 node3_local = test_node3->to_local(Point2(1, 2));
CHECK(node3_local.is_equal_approx(Point2(-51.5, 48.83013)));
node3_local = test_node3->to_local(Point2(-2, 1));
CHECK(node3_local.is_equal_approx(Point2(-52, 50.33013)));
node3_local = test_node3->to_local(Point2(0, 0));
CHECK(node3_local.is_equal_approx(Point2(-52.5, 49.33013)));
node3_local = test_node3->to_local(test_node3->get_global_position());
CHECK(node3_local.is_equal_approx(Point2(0, 0)));
}
SUBCASE("[Node2D] to_global") {
Point2 node3_global = test_node3->to_global(Point2(1, 2));
CHECK(node3_global.is_equal_approx(Point2(94.66026, 107)));
node3_global = test_node3->to_global(Point2(-2, 1));
CHECK(node3_global.is_equal_approx(Point2(96.66026, 101)));
node3_global = test_node3->to_global(Point2(0, 0));
CHECK(node3_global.is_equal_approx(test_node3->get_global_position()));
}
SUBCASE("[Node2D] get_relative_transform_to_parent") {
Transform2D relative_xform = test_node3->get_relative_transform_to_parent(test_node3);
CHECK(relative_xform.is_equal_approx(Transform2D()));
relative_xform = test_node3->get_relative_transform_to_parent(test_node2);
CHECK(relative_xform.get_origin().is_equal_approx(Vector2(0, 10)));
CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(0)));
CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
relative_xform = test_node3->get_relative_transform_to_parent(test_node1);
CHECK(relative_xform.get_origin().is_equal_approx(Vector2(1.339746, 5)));
CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(1.0472)));
CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
ERR_PRINT_OFF;
// In case of a sibling all transforms until the root are accumulated.
Transform2D xform = test_node3->get_relative_transform_to_parent(test_sibling);
Transform2D return_xform = test_node1->get_global_transform().inverse() * test_node3->get_global_transform();
CHECK(xform.is_equal_approx(return_xform));
ERR_PRINT_ON;
}
memdelete(test_sibling);
memdelete(test_node3);
memdelete(test_node2);
memdelete(test_node1);
}
} // namespace TestNode2D

View File

@@ -0,0 +1,158 @@
/**************************************************************************/
/* test_option_button.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/option_button.h"
#include "tests/test_macros.h"
namespace TestOptionButton {
TEST_CASE("[SceneTree][OptionButton] Initialization") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("There should be no options right after initialization") {
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
memdelete(test_opt);
}
TEST_CASE("[SceneTree][OptionButton] Single item") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("There should a single item after after adding one") {
test_opt->add_item("single", 1013);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
CHECK(test_opt->get_item_index(1013) == 0);
CHECK(test_opt->get_item_id(0) == 1013);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
SUBCASE("There should a single item after after adding an icon") {
Ref<Texture2D> test_icon = memnew(Texture2D);
test_opt->add_icon_item(test_icon, "icon", 345);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
CHECK(test_opt->get_item_index(345) == 0);
CHECK(test_opt->get_item_id(0) == 345);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 0);
}
memdelete(test_opt);
}
TEST_CASE("[SceneTree][OptionButton] Many items") {
OptionButton *test_opt = memnew(OptionButton);
SUBCASE("Creating a complex structure and checking getters") {
// Regular item at index 0.
Ref<Texture2D> test_icon1 = memnew(Texture2D);
Ref<Texture2D> test_icon2 = memnew(Texture2D);
// Regular item at index 3.
Ref<Texture2D> test_icon4 = memnew(Texture2D);
test_opt->add_item("first", 100);
test_opt->add_icon_item(test_icon1, "second_icon", 101);
test_opt->add_icon_item(test_icon2, "third_icon", 102);
test_opt->add_item("fourth", 104);
test_opt->add_icon_item(test_icon4, "fifth_icon", 104);
// Disable test_icon4.
test_opt->set_item_disabled(4, true);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 5);
// Check for test_icon2.
CHECK(test_opt->get_item_index(102) == 2);
CHECK(test_opt->get_item_id(2) == 102);
// Remove the two regular items.
test_opt->remove_item(3);
test_opt->remove_item(0);
CHECK(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 3);
// Check test_icon4.
CHECK(test_opt->get_item_index(104) == 2);
CHECK(test_opt->get_item_id(2) == 104);
// Remove the two non-disabled icon items.
test_opt->remove_item(1);
test_opt->remove_item(0);
CHECK_FALSE(test_opt->has_selectable_items());
CHECK(test_opt->get_item_count() == 1);
}
SUBCASE("Getters and setters not related to structure") {
test_opt->add_item("regular", 2019);
Ref<Texture2D> test_icon = memnew(Texture2D);
test_opt->add_icon_item(test_icon, "icon", 3092);
// item_text.
test_opt->set_item_text(0, "example text");
CHECK(test_opt->get_item_text(0) == "example text");
// item_metadata.
Dictionary m;
m["bool"] = true;
m["String"] = "yes";
test_opt->set_item_metadata(1, m);
CHECK(test_opt->get_item_metadata(1) == m);
// item_tooltip.
test_opt->set_item_tooltip(0, "tooltip guide");
CHECK(test_opt->get_item_tooltip(0) == "tooltip guide");
test_opt->remove_item(1);
test_opt->remove_item(0);
}
memdelete(test_opt);
}
} // namespace TestOptionButton

View File

@@ -0,0 +1,283 @@
/**************************************************************************/
/* test_packed_scene.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/packed_scene.h"
#include "tests/test_macros.h"
namespace TestPackedScene {
TEST_CASE("[PackedScene] Pack Scene and Retrieve State") {
// Create a scene to pack.
Node *scene = memnew(Node);
scene->set_name("TestScene");
// Pack the scene.
PackedScene packed_scene;
const Error err = packed_scene.pack(scene);
CHECK(err == OK);
// Retrieve the packed state.
Ref<SceneState> state = packed_scene.get_state();
CHECK(state.is_valid());
CHECK(state->get_node_count() == 1);
CHECK(state->get_node_name(0) == "TestScene");
memdelete(scene);
}
TEST_CASE("[PackedScene] Signals Preserved when Packing Scene") {
// Create main scene
// root
// `- sub_node (local)
// `- sub_scene (instance of another scene)
// `- sub_scene_node (owned by sub_scene)
Node *main_scene_root = memnew(Node);
Node *sub_node = memnew(Node);
Node *sub_scene_root = memnew(Node);
Node *sub_scene_node = memnew(Node);
main_scene_root->add_child(sub_node);
sub_node->set_owner(main_scene_root);
sub_scene_root->add_child(sub_scene_node);
sub_scene_node->set_owner(sub_scene_root);
main_scene_root->add_child(sub_scene_root);
sub_scene_root->set_owner(main_scene_root);
SUBCASE("Signals that should be saved") {
int main_flags = Object::CONNECT_PERSIST;
// sub node to a node in main scene
sub_node->connect("ready", callable_mp(main_scene_root, &Node::is_ready), main_flags);
// subscene root to a node in main scene
sub_scene_root->connect("ready", callable_mp(main_scene_root, &Node::is_ready), main_flags);
//subscene root to subscene root (connected within main scene)
sub_scene_root->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), main_flags);
// Pack the scene.
Ref<PackedScene> packed_scene;
packed_scene.instantiate();
const Error err = packed_scene->pack(main_scene_root);
CHECK(err == OK);
// Make sure the right connections are in packed scene.
Ref<SceneState> state = packed_scene->get_state();
CHECK_EQ(state->get_connection_count(), 3);
}
/*
// FIXME: This subcase requires GH-48064 to be fixed.
SUBCASE("Signals that should not be saved") {
int subscene_flags = Object::CONNECT_PERSIST | Object::CONNECT_INHERITED;
// subscene node to itself
sub_scene_node->connect("ready", callable_mp(sub_scene_node, &Node::is_ready), subscene_flags);
// subscene node to subscene root
sub_scene_node->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), subscene_flags);
//subscene root to subscene root (connected within sub scene)
sub_scene_root->connect("ready", callable_mp(sub_scene_root, &Node::is_ready), subscene_flags);
// Pack the scene.
Ref<PackedScene> packed_scene;
packed_scene.instantiate();
const Error err = packed_scene->pack(main_scene_root);
CHECK(err == OK);
// Make sure the right connections are in packed scene.
Ref<SceneState> state = packed_scene->get_state();
CHECK_EQ(state->get_connection_count(), 0);
}
*/
memdelete(main_scene_root);
}
TEST_CASE("[PackedScene] Clear Packed Scene") {
// Create a scene to pack.
Node *scene = memnew(Node);
scene->set_name("TestScene");
// Pack the scene.
PackedScene packed_scene;
packed_scene.pack(scene);
// Clear the packed scene.
packed_scene.clear();
// Check if it has been cleared.
Ref<SceneState> state = packed_scene.get_state();
CHECK_FALSE(state->get_node_count() == 1);
memdelete(scene);
}
TEST_CASE("[PackedScene] Can Instantiate Packed Scene") {
// Create a scene to pack.
Node *scene = memnew(Node);
scene->set_name("TestScene");
// Pack the scene.
PackedScene packed_scene;
packed_scene.pack(scene);
// Check if the packed scene can be instantiated.
const bool can_instantiate = packed_scene.can_instantiate();
CHECK(can_instantiate == true);
memdelete(scene);
}
TEST_CASE("[PackedScene] Instantiate Packed Scene") {
// Create a scene to pack.
Node *scene = memnew(Node);
scene->set_name("TestScene");
// Pack the scene.
PackedScene packed_scene;
packed_scene.pack(scene);
// Instantiate the packed scene.
Node *instance = packed_scene.instantiate();
CHECK(instance != nullptr);
CHECK(instance->get_name() == "TestScene");
memdelete(scene);
memdelete(instance);
}
TEST_CASE("[PackedScene] Instantiate Packed Scene With Children") {
// Create a scene to pack.
Node *scene = memnew(Node);
scene->set_name("TestScene");
// Add persisting child nodes to the scene.
Node *child1 = memnew(Node);
child1->set_name("Child1");
scene->add_child(child1);
child1->set_owner(scene);
Node *child2 = memnew(Node);
child2->set_name("Child2");
scene->add_child(child2);
child2->set_owner(scene);
// Add non persisting child node to the scene.
Node *child3 = memnew(Node);
child3->set_name("Child3");
scene->add_child(child3);
// Pack the scene.
PackedScene packed_scene;
packed_scene.pack(scene);
// Instantiate the packed scene.
Node *instance = packed_scene.instantiate();
CHECK(instance != nullptr);
CHECK(instance->get_name() == "TestScene");
// Validate the child nodes of the instantiated scene.
CHECK(instance->get_child_count() == 2);
CHECK(instance->get_child(0)->get_name() == "Child1");
CHECK(instance->get_child(1)->get_name() == "Child2");
CHECK(instance->get_child(0)->get_owner() == instance);
CHECK(instance->get_child(1)->get_owner() == instance);
memdelete(scene);
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

View File

@@ -0,0 +1,128 @@
/**************************************************************************/
/* test_parallax_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/parallax_2d.h"
#include "tests/test_macros.h"
namespace TestParallax2D {
// Test cases for the Parallax2D class to ensure its properties are set and retrieved correctly.
TEST_CASE("[SceneTree][Parallax2D] Scroll Scale") {
// Test setting and getting the scroll scale.
Parallax2D *parallax = memnew(Parallax2D);
Size2 scale(2, 2);
parallax->set_scroll_scale(scale);
CHECK(parallax->get_scroll_scale() == scale);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Repeat Size") {
// Test setting and getting the repeat size.
Parallax2D *parallax = memnew(Parallax2D);
Size2 size(100, 100);
parallax->set_repeat_size(size);
CHECK(parallax->get_repeat_size() == size);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Repeat Times") {
// Test setting and getting the repeat times.
Parallax2D *parallax = memnew(Parallax2D);
int times = 5;
parallax->set_repeat_times(times);
CHECK(parallax->get_repeat_times() == times);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Autoscroll") {
// Test setting and getting the autoscroll values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 autoscroll(1, 1);
parallax->set_autoscroll(autoscroll);
CHECK(parallax->get_autoscroll() == autoscroll);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Scroll Offset") {
// Test setting and getting the scroll offset.
Parallax2D *parallax = memnew(Parallax2D);
Point2 offset(10, 10);
parallax->set_scroll_offset(offset);
CHECK(parallax->get_scroll_offset() == offset);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Screen Offset") {
// Test setting and getting the screen offset.
Parallax2D *parallax = memnew(Parallax2D);
Point2 offset(20, 20);
parallax->set_screen_offset(offset);
CHECK(parallax->get_screen_offset() == offset);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Limit Begin") {
// Test setting and getting the limit begin values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 limit_begin(-100, -100);
parallax->set_limit_begin(limit_begin);
CHECK(parallax->get_limit_begin() == limit_begin);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Limit End") {
// Test setting and getting the limit end values.
Parallax2D *parallax = memnew(Parallax2D);
Point2 limit_end(100, 100);
parallax->set_limit_end(limit_end);
CHECK(parallax->get_limit_end() == limit_end);
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Follow Viewport") {
// Test setting and getting the follow viewport flag.
Parallax2D *parallax = memnew(Parallax2D);
parallax->set_follow_viewport(false);
CHECK_FALSE(parallax->get_follow_viewport());
memdelete(parallax);
}
TEST_CASE("[SceneTree][Parallax2D] Ignore Camera Scroll") {
// Test setting and getting the ignore camera scroll flag.
Parallax2D *parallax = memnew(Parallax2D);
parallax->set_ignore_camera_scroll(true);
CHECK(parallax->is_ignore_camera_scroll());
memdelete(parallax);
}
} // namespace TestParallax2D

106
tests/scene/test_path_2d.h Normal file
View File

@@ -0,0 +1,106 @@
/**************************************************************************/
/* test_path_2d.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. */
/**************************************************************************/
#pragma once
#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().is_null());
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

View File

@@ -0,0 +1,81 @@
/**************************************************************************/
/* test_path_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/path_3d.h"
#include "tests/test_macros.h"
namespace TestPath3D {
TEST_CASE("[Path3D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path3D *test_path = memnew(Path3D);
CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}
TEST_CASE("[Path3D] Curve setter and getter") {
SUBCASE("Curve passed to the class should remain the same") {
Path3D *test_path = memnew(Path3D);
const Ref<Curve3D> &curve = memnew(Curve3D);
test_path->set_curve(curve);
CHECK(test_path->get_curve() == curve);
memdelete(test_path);
}
SUBCASE("Curve passed many times to the class should remain the same") {
Path3D *test_path = memnew(Path3D);
const Ref<Curve3D> &curve = memnew(Curve3D);
test_path->set_curve(curve);
test_path->set_curve(curve);
test_path->set_curve(curve);
CHECK(test_path->get_curve() == curve);
memdelete(test_path);
}
SUBCASE("Curve rewrite testing") {
Path3D *test_path = memnew(Path3D);
const Ref<Curve3D> &curve1 = memnew(Curve3D);
const Ref<Curve3D> &curve2 = memnew(Curve3D);
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");
memdelete(test_path);
}
}
} // namespace TestPath3D

View File

@@ -0,0 +1,255 @@
/**************************************************************************/
/* test_path_follow_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/path_2d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestPathFollow2D {
bool is_equal_approx(const Vector2 &p_a, const Vector2 &p_b) {
const real_t tolerance = 0.001;
return Math::is_equal_approx(p_a.x, p_b.x, tolerance) &&
Math::is_equal_approx(p_a.y, p_b.y, tolerance);
}
TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress ratio") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->set_bake_interval(1);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
curve->add_point(Vector2(100, 100));
curve->add_point(Vector2(0, 100));
curve->add_point(Vector2(0, 0));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path_follow_2d->set_loop(false);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_progress_ratio(0);
CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.125);
CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.25);
CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.375);
CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.5);
CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.625);
CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.75);
CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(0.875);
CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress_ratio(1);
CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin()));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->set_bake_interval(1);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
curve->add_point(Vector2(100, 100));
curve->add_point(Vector2(0, 100));
curve->add_point(Vector2(0, 0));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path_follow_2d->set_loop(false);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_progress(0);
CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(50);
CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(100);
CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(150);
CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(200);
CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(250);
CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(300);
CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(350);
CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_progress(400);
CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin()));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow2D] Removal of a point in curve") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
curve->add_point(Vector2(100, 100));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_progress_ratio(0.5);
CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin()));
curve->remove_point(1);
path_follow_2d->set_progress_ratio(0.5);
CHECK_MESSAGE(
is_equal_approx(Vector2(50, 50), path_follow_2d->get_transform().get_origin()),
"Path follow's position should be updated after removing a point from the curve");
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow2D] Setting h_offset and v_offset") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_progress_ratio(0.5);
CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_h_offset(25);
CHECK(is_equal_approx(Vector2(75, 0), path_follow_2d->get_transform().get_origin()));
path_follow_2d->set_v_offset(25);
CHECK(is_equal_approx(Vector2(75, 25), path_follow_2d->get_transform().get_origin()));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow2D] Progress ratio out of range") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_loop(true);
path_follow_2d->set_progress_ratio(-0.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.7),
"Progress Ratio should loop back from the end in the opposite direction");
path_follow_2d->set_progress_ratio(1.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.3),
"Progress Ratio should loop back from the end in the opposite direction");
path_follow_2d->set_loop(false);
path_follow_2d->set_progress_ratio(-0.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 0),
"Progress Ratio should be clamped at 0");
path_follow_2d->set_progress_ratio(1.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 1),
"Progress Ratio should be clamped at 1");
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow2D] Progress out of range") {
Ref<Curve2D> curve = memnew(Curve2D);
curve->add_point(Vector2(0, 0));
curve->add_point(Vector2(100, 0));
Path2D *path = memnew(Path2D);
path->set_curve(curve);
PathFollow2D *path_follow_2d = memnew(PathFollow2D);
path->add_child(path_follow_2d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_2d->set_loop(true);
path_follow_2d->set_progress(-50);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress(), 50),
"Progress should loop back from the end in the opposite direction");
path_follow_2d->set_progress(150);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress(), 50),
"Progress should loop back from the end in the opposite direction");
path_follow_2d->set_loop(false);
path_follow_2d->set_progress(-50);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress(), 0),
"Progress should be clamped at 0");
path_follow_2d->set_progress(150);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_2d->get_progress(), 100),
"Progress should be clamped at 1");
memdelete(path);
}
} // namespace TestPathFollow2D

View File

@@ -0,0 +1,340 @@
/**************************************************************************/
/* test_path_follow_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. */
/**************************************************************************/
#pragma once
#include "scene/3d/path_3d.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestPathFollow3D {
bool is_equal_approx(const Vector3 &p_a, const Vector3 &p_b) {
const real_t tolerance = 0.001;
return Math::is_equal_approx(p_a.x, p_b.x, tolerance) &&
Math::is_equal_approx(p_a.y, p_b.y, tolerance) &&
Math::is_equal_approx(p_a.z, p_b.z, tolerance);
}
TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
curve->add_point(Vector3(100, 100, 0));
curve->add_point(Vector3(100, 100, 100));
curve->add_point(Vector3(100, 0, 100));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path_follow_3d->set_loop(false);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.125);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.25);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.375);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.5);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.625);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.75);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(0.875);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress_ratio(1);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
curve->add_point(Vector3(100, 100, 0));
curve->add_point(Vector3(100, 100, 100));
curve->add_point(Vector3(100, 0, 100));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path_follow_3d->set_loop(false);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress(0);
CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(50);
CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(100);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(150);
CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(200);
CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(250);
CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(300);
CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(350);
CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin()));
path_follow_3d->set_progress(400);
CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin()));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
curve->add_point(Vector3(100, 100, 0));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_progress_ratio(0.5);
CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin()));
curve->remove_point(1);
path_follow_3d->set_progress_ratio(0.5);
CHECK_MESSAGE(
is_equal_approx(Vector3(50, 50, 0), path_follow_3d->get_transform().get_origin()),
"Path follow's position should be updated after removing a point from the curve");
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_loop(true);
path_follow_3d->set_progress_ratio(-0.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.7),
"Progress Ratio should loop back from the end in the opposite direction");
path_follow_3d->set_progress_ratio(1.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.3),
"Progress Ratio should loop back from the end in the opposite direction");
path_follow_3d->set_loop(false);
path_follow_3d->set_progress_ratio(-0.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 0),
"Progress Ratio should be clamped at 0");
path_follow_3d->set_progress_ratio(1.3);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 1),
"Progress Ratio should be clamped at 1");
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_loop(true);
path_follow_3d->set_progress(-50);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress(), 50),
"Progress should loop back from the end in the opposite direction");
path_follow_3d->set_progress(150);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress(), 50),
"Progress should loop back from the end in the opposite direction");
path_follow_3d->set_loop(false);
path_follow_3d->set_progress(-50);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress(), 0),
"Progress should be clamped at 0");
path_follow_3d->set_progress(150);
CHECK_MESSAGE(
Math::is_equal_approx(path_follow_3d->get_progress(), 100),
"Progress should be clamped at max value of curve");
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") {
const real_t dist_cube_100 = 100 * Math::sqrt(3.0);
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 0));
curve->add_point(Vector3(100, 0, 0));
curve->add_point(Vector3(200, 100, -100));
curve->add_point(Vector3(200, 100, 200));
curve->add_point(Vector3(100, 0, 100));
curve->add_point(Vector3(0, 0, 100));
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_loop(false);
path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED);
path_follow_3d->set_progress(-50);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(0);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(50);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 / 2);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(100 + dist_cube_100 - 0.01);
CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(250 + dist_cube_100);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + dist_cube_100 - 0.01);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 1.5 * dist_cube_100);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(400 + 2 * dist_cube_100 - 0.01);
CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress(500 + 2 * dist_cube_100);
CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
memdelete(path);
}
TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector with degenerate curves") {
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(Vector3(0, 0, 1), Vector3(), Vector3(1, 0, 0));
curve->add_point(Vector3(1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0));
curve->add_point(Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(-1, 0, 0));
curve->add_point(Vector3(-1, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0));
curve->add_point(Vector3(0, 0, 1), Vector3(-1, 0, 0), Vector3());
Path3D *path = memnew(Path3D);
path->set_curve(curve);
PathFollow3D *path_follow_3d = memnew(PathFollow3D);
path->add_child(path_follow_3d);
SceneTree::get_singleton()->get_root()->add_child(path);
path_follow_3d->set_loop(false);
path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED);
path_follow_3d->set_progress_ratio(0.00);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.25);
CHECK(is_equal_approx(Vector3(0, 0, 1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.50);
CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.75);
CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(1.00);
CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.125);
CHECK(is_equal_approx(Vector3(-0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.375);
CHECK(is_equal_approx(Vector3(0.688375, 0, 0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.625);
CHECK(is_equal_approx(Vector3(0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
path_follow_3d->set_progress_ratio(0.875);
CHECK(is_equal_approx(Vector3(-0.688375, 0, -0.725355), path_follow_3d->get_transform().get_basis().get_column(2)));
memdelete(path);
}
} // namespace TestPathFollow3D

View File

@@ -0,0 +1,104 @@
/**************************************************************************/
/* test_physics_material.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/physics_material.h"
#include "tests/test_macros.h"
namespace TestPhysics_material {
TEST_CASE("[Physics_material] Defaults") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
CHECK(physics_material->get_friction() == 1.);
CHECK(physics_material->is_rough() == false);
CHECK(physics_material->get_bounce() == 0.);
CHECK(physics_material->is_absorbent() == false);
}
TEST_CASE("[Physics_material] Friction") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
real_t friction = 0.314;
physics_material->set_friction(friction);
CHECK(physics_material->get_friction() == friction);
}
TEST_CASE("[Physics_material] Rough") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
bool rough = true;
physics_material->set_rough(rough);
CHECK(physics_material->is_rough() == rough);
real_t friction = 0.314;
physics_material->set_friction(friction);
CHECK(physics_material->computed_friction() == -friction);
rough = false;
physics_material->set_rough(rough);
CHECK(physics_material->is_rough() == rough);
CHECK(physics_material->computed_friction() == friction);
}
TEST_CASE("[Physics_material] Bounce") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
real_t bounce = 0.271;
physics_material->set_bounce(bounce);
CHECK(physics_material->get_bounce() == bounce);
}
TEST_CASE("[Physics_material] Absorbent") {
Ref<PhysicsMaterial> physics_material;
physics_material.instantiate();
bool absorbent = true;
physics_material->set_absorbent(absorbent);
CHECK(physics_material->is_absorbent() == absorbent);
real_t bounce = 0.271;
physics_material->set_bounce(bounce);
CHECK(physics_material->computed_bounce() == -bounce);
absorbent = false;
physics_material->set_absorbent(absorbent);
CHECK(physics_material->is_absorbent() == absorbent);
CHECK(physics_material->computed_bounce() == bounce);
}
} // namespace TestPhysics_material

View File

@@ -0,0 +1,865 @@
/**************************************************************************/
/* test_primitives.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/3d/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(capsule->get_radius() == doctest::Approx(1.3f),
"Get/Set radius work with one set.");
CHECK_MESSAGE(capsule->get_height() == doctest::Approx(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") {
ERR_PRINT_OFF;
capsule->set_radial_segments(-5);
capsule->set_rings(-17);
ERR_PRINT_ON;
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(point_dist_from_yaxis == doctest::Approx(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") {
ERR_PRINT_OFF;
box->set_subdivide_width(-2);
box->set_subdivide_height(-2);
box->set_subdivide_depth(-2);
ERR_PRINT_ON;
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(cylinder->get_top_radius() == doctest::Approx(4.3f));
CHECK(cylinder->get_bottom_radius() == doctest::Approx(1.2f));
CHECK(cylinder->get_height() == doctest::Approx(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") {
ERR_PRINT_OFF;
cylinder->set_radial_segments(-12);
cylinder->set_rings(-16);
ERR_PRINT_ON;
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.") {
ERR_PRINT_OFF;
plane->set_subdivide_width(-15);
plane->set_subdivide_depth(-29);
ERR_PRINT_ON;
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(prism->get_left_to_right() == doctest::Approx(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") {
ERR_PRINT_OFF;
prism->set_subdivide_width(-36);
prism->set_subdivide_height(-5);
prism->set_subdivide_depth(-64);
ERR_PRINT_ON;
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(sphere->get_radius() == doctest::Approx(3.4f));
CHECK(sphere->get_height() == doctest::Approx(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") {
ERR_PRINT_OFF;
sphere->set_radial_segments(-36);
sphere->set_rings(-5);
ERR_PRINT_ON;
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(torus->get_inner_radius() == doctest::Approx(3.2f));
CHECK(torus->get_outer_radius() == doctest::Approx(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().is_null());
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(tube->get_radius() == doctest::Approx(7.2f));
CHECK(tube->get_section_length() == doctest::Approx(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().is_null());
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(ribbon->get_size() == doctest::Approx(4.3f));
CHECK(ribbon->get_section_length() == doctest::Approx(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().is_null());
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_GDSCRIPT ||
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(text->get_line_spacing() == doctest::Approx(1.7f));
CHECK(text->get_width() == doctest::Approx(width));
CHECK(text->get_depth() == doctest::Approx(depth));
CHECK(text->get_curve_step() == doctest::Approx(curve_step));
CHECK(text->get_pixel_size() == doctest::Approx(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

View File

@@ -0,0 +1,75 @@
/**************************************************************************/
/* test_skeleton_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. */
/**************************************************************************/
#pragma once
#include "tests/test_macros.h"
#include "scene/3d/skeleton_3d.h"
namespace TestSkeleton3D {
TEST_CASE("[Skeleton3D] Test per-bone meta") {
Skeleton3D *skeleton = memnew(Skeleton3D);
skeleton->add_bone("root");
skeleton->set_bone_rest(0, Transform3D());
// Adding meta to bone.
skeleton->set_bone_meta(0, "key1", "value1");
skeleton->set_bone_meta(0, "key2", 12345);
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
// Rename bone and check if meta persists.
skeleton->set_bone_name(0, "renamed_root");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
// Retrieve list of keys.
List<StringName> keys;
skeleton->get_bone_meta_list(0, &keys);
CHECK_MESSAGE(keys.size() == 2, "Wrong number of bone meta keys.");
CHECK_MESSAGE(keys.find("key1"), "key1 not found in bone meta list");
CHECK_MESSAGE(keys.find("key2"), "key2 not found in bone meta list");
// Removing meta.
skeleton->set_bone_meta(0, "key1", Variant());
skeleton->set_bone_meta(0, "key2", Variant());
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key1"), "Bone meta key1 should be deleted.");
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key2"), "Bone meta key2 should be deleted.");
List<StringName> should_be_empty_keys;
skeleton->get_bone_meta_list(0, &should_be_empty_keys);
CHECK_MESSAGE(should_be_empty_keys.size() == 0, "Wrong number of bone meta keys.");
// Deleting non-existing key should succeed.
skeleton->set_bone_meta(0, "non-existing-key", Variant());
memdelete(skeleton);
}
} // namespace TestSkeleton3D

219
tests/scene/test_sky.h Normal file
View File

@@ -0,0 +1,219 @@
/**************************************************************************/
/* test_sky.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/sky.h"
#include "tests/test_macros.h"
namespace TestSky {
TEST_CASE("[SceneTree][Sky] Constructor") {
Ref<Sky> test_sky;
test_sky.instantiate();
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
CHECK(test_sky->get_material().is_null());
}
TEST_CASE("[SceneTree][Sky] Radiance size setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check default.
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_1024);
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
ERR_PRINT_OFF;
// Check setting invalid radiance size.
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX);
ERR_PRINT_ON;
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
}
TEST_CASE("[SceneTree][Sky] Process mode setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check default.
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
test_sky->set_process_mode(Sky::PROCESS_MODE_INCREMENTAL);
CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_INCREMENTAL);
}
TEST_CASE("[SceneTree][Sky] Material setter and getter") {
Ref<Sky> test_sky;
test_sky.instantiate();
Ref<Material> material;
material.instantiate();
SUBCASE("Material passed to the class should remain the same") {
test_sky->set_material(material);
CHECK(test_sky->get_material() == material);
}
SUBCASE("Material passed many times to the class should remain the same") {
test_sky->set_material(material);
test_sky->set_material(material);
test_sky->set_material(material);
CHECK(test_sky->get_material() == material);
}
SUBCASE("Material rewrite testing") {
Ref<Material> material1;
Ref<Material> material2;
material1.instantiate();
material2.instantiate();
test_sky->set_material(material1);
test_sky->set_material(material2);
CHECK_MESSAGE(test_sky->get_material() != material1,
"After rewrite, second material should be in class.");
CHECK_MESSAGE(test_sky->get_material() == material2,
"After rewrite, second material should be in class.");
}
SUBCASE("Assign same material to two skys") {
Ref<Sky> sky2;
sky2.instantiate();
test_sky->set_material(material);
sky2->set_material(material);
CHECK_MESSAGE(test_sky->get_material() == sky2->get_material(),
"Both skys should have the same material.");
}
SUBCASE("Swapping materials between two skys") {
Ref<Sky> sky2;
sky2.instantiate();
Ref<Material> material1;
Ref<Material> material2;
material1.instantiate();
material2.instantiate();
test_sky->set_material(material1);
sky2->set_material(material2);
CHECK(test_sky->get_material() == material1);
CHECK(sky2->get_material() == material2);
// Do the swap.
Ref<Material> temp = test_sky->get_material();
test_sky->set_material(sky2->get_material());
sky2->set_material(temp);
CHECK(test_sky->get_material() == material2);
CHECK(sky2->get_material() == material1);
}
}
TEST_CASE("[SceneTree][Sky] Invalid radiance size handling") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Attempt to set an invalid radiance size.
ERR_PRINT_OFF;
test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX);
ERR_PRINT_ON;
// Verify that the radiance size remains unchanged.
CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
}
TEST_CASE("[SceneTree][Sky] Process mode variations") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Test all process modes.
const Sky::ProcessMode process_modes[] = {
Sky::PROCESS_MODE_AUTOMATIC,
Sky::PROCESS_MODE_QUALITY,
Sky::PROCESS_MODE_INCREMENTAL,
Sky::PROCESS_MODE_REALTIME
};
for (Sky::ProcessMode mode : process_modes) {
test_sky->set_process_mode(mode);
CHECK(test_sky->get_process_mode() == mode);
}
}
TEST_CASE("[SceneTree][Sky] Radiance size variations") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Test all radiance sizes except MAX.
const Sky::RadianceSize radiance_sizes[] = {
Sky::RADIANCE_SIZE_32,
Sky::RADIANCE_SIZE_64,
Sky::RADIANCE_SIZE_128,
Sky::RADIANCE_SIZE_256,
Sky::RADIANCE_SIZE_512,
Sky::RADIANCE_SIZE_1024,
Sky::RADIANCE_SIZE_2048
};
for (Sky::RadianceSize size : radiance_sizes) {
test_sky->set_radiance_size(size);
CHECK(test_sky->get_radiance_size() == size);
}
}
TEST_CASE("[SceneTree][Sky] Null material handling") {
Ref<Sky> test_sky;
test_sky.instantiate();
SUBCASE("Setting null material") {
test_sky->set_material(Ref<Material>());
CHECK(test_sky->get_material().is_null());
}
SUBCASE("Overwriting existing material with null") {
Ref<Material> material;
material.instantiate();
test_sky->set_material(material);
test_sky->set_material(Ref<Material>());
CHECK(test_sky->get_material().is_null());
}
}
TEST_CASE("[SceneTree][Sky] RID generation") {
Ref<Sky> test_sky;
test_sky.instantiate();
// Check validity.
CHECK(!test_sky->get_rid().is_valid());
}
} // namespace TestSky

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
/**************************************************************************/
/* test_sprite_2d.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. */
/**************************************************************************/
#pragma once
#include "scene/2d/sprite_2d.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestSprite2D {
TEST_CASE("[SceneTree][Sprite2D] Constructor") {
Sprite2D *sprite_2d = memnew(Sprite2D);
CHECK(sprite_2d->get_texture().is_null());
CHECK_EQ(sprite_2d->get_offset(), Point2(0, 0));
CHECK(sprite_2d->is_centered());
CHECK_FALSE(sprite_2d->is_flipped_h());
CHECK_FALSE(sprite_2d->is_flipped_v());
CHECK_EQ(sprite_2d->get_hframes(), 1);
CHECK_EQ(sprite_2d->get_vframes(), 1);
CHECK_EQ(sprite_2d->get_frame(), 0);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 0));
CHECK_FALSE(sprite_2d->is_region_enabled());
memdelete(sprite_2d);
}
TEST_CASE("[SceneTree][Sprite2D] Frames") {
Sprite2D *sprite_2d = memnew(Sprite2D);
SUBCASE("Invalid range") {
ERR_PRINT_OFF;
sprite_2d->set_frame(30);
sprite_2d->set_frame(-1);
ERR_PRINT_ON;
CHECK(sprite_2d->get_frame() == 0);
}
SUBCASE("Base value") {
sprite_2d->set_hframes(1);
sprite_2d->set_vframes(1);
CHECK(sprite_2d->get_frame() == 0);
}
SUBCASE("2x2 frames") {
sprite_2d->set_hframes(2);
sprite_2d->set_vframes(2);
CHECK(sprite_2d->get_hframes() == 2);
CHECK(sprite_2d->get_vframes() == 2);
sprite_2d->set_frame(0);
CHECK(sprite_2d->get_frame() == 0);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 0));
sprite_2d->set_frame(1);
CHECK(sprite_2d->get_frame() == 1);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(1, 0));
sprite_2d->set_frame(2);
CHECK(sprite_2d->get_frame() == 2);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 1));
sprite_2d->set_frame(3);
CHECK(sprite_2d->get_frame() == 3);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(1, 1));
}
SUBCASE("4x4 frames") {
sprite_2d->set_hframes(4);
sprite_2d->set_vframes(4);
CHECK(sprite_2d->get_hframes() == 4);
CHECK(sprite_2d->get_vframes() == 4);
sprite_2d->set_frame(0);
CHECK(sprite_2d->get_frame() == 0);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 0));
sprite_2d->set_frame(2);
CHECK(sprite_2d->get_frame() == 2);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(2, 0));
sprite_2d->set_frame(4);
CHECK(sprite_2d->get_frame() == 4);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 1));
sprite_2d->set_frame(6);
CHECK(sprite_2d->get_frame() == 6);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(2, 1));
sprite_2d->set_frame(8);
CHECK(sprite_2d->get_frame() == 8);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 2));
sprite_2d->set_frame(10);
CHECK(sprite_2d->get_frame() == 10);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(2, 2));
sprite_2d->set_frame(12);
CHECK(sprite_2d->get_frame() == 12);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 3));
sprite_2d->set_frame(14);
CHECK(sprite_2d->get_frame() == 14);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(2, 3));
}
SUBCASE("8x4 frames") {
sprite_2d->set_hframes(8);
sprite_2d->set_vframes(4);
CHECK(sprite_2d->get_hframes() == 8);
CHECK(sprite_2d->get_vframes() == 4);
sprite_2d->set_frame(0);
CHECK(sprite_2d->get_frame() == 0);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 0));
sprite_2d->set_frame(4);
CHECK(sprite_2d->get_frame() == 4);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(4, 0));
sprite_2d->set_frame(8);
CHECK(sprite_2d->get_frame() == 8);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 1));
sprite_2d->set_frame(16);
CHECK(sprite_2d->get_frame() == 16);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 2));
sprite_2d->set_frame(31);
CHECK(sprite_2d->get_frame() == 31);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(7, 3));
}
SUBCASE("100x100 frames") {
sprite_2d->set_hframes(100);
sprite_2d->set_vframes(100);
CHECK(sprite_2d->get_hframes() == 100);
CHECK(sprite_2d->get_vframes() == 100);
sprite_2d->set_frame(0);
CHECK(sprite_2d->get_frame() == 0);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(0, 0));
sprite_2d->set_frame(60);
CHECK(sprite_2d->get_frame() == 60);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(60, 0));
sprite_2d->set_frame(120);
CHECK(sprite_2d->get_frame() == 120);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(20, 1));
sprite_2d->set_frame(240);
CHECK(sprite_2d->get_frame() == 240);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(40, 2));
sprite_2d->set_frame(360);
CHECK(sprite_2d->get_frame() == 360);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(60, 3));
sprite_2d->set_frame(2048);
CHECK(sprite_2d->get_frame() == 2048);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(48, 20));
sprite_2d->set_frame(8192);
CHECK(sprite_2d->get_frame() == 8192);
CHECK_EQ(sprite_2d->get_frame_coords(), Vector2i(92, 81));
}
memdelete(sprite_2d);
}
TEST_CASE("[SceneTree][Sprite2D] Flipping") {
Sprite2D *sprite_2d = memnew(Sprite2D);
SUBCASE("Both False") {
CHECK_FALSE(sprite_2d->is_flipped_h());
CHECK_FALSE(sprite_2d->is_flipped_v());
}
SUBCASE("Both True") {
sprite_2d->set_flip_h(true);
sprite_2d->set_flip_v(true);
CHECK(sprite_2d->is_flipped_h());
CHECK(sprite_2d->is_flipped_v());
}
SUBCASE("True False") {
sprite_2d->set_flip_h(true);
sprite_2d->set_flip_v(false);
CHECK(sprite_2d->is_flipped_h());
CHECK_FALSE(sprite_2d->is_flipped_v());
}
SUBCASE("False True") {
sprite_2d->set_flip_h(false);
sprite_2d->set_flip_v(true);
CHECK_FALSE(sprite_2d->is_flipped_h());
CHECK(sprite_2d->is_flipped_v());
}
memdelete(sprite_2d);
}
TEST_CASE("[SceneTree][Sprite2D] Offset") {
Sprite2D *sprite_2d = memnew(Sprite2D);
sprite_2d->set_offset(Point2(0.0, 0.0));
CHECK(sprite_2d->get_offset() == Point2(0.0, 0.0));
sprite_2d->set_offset(Point2(8.0, 8.0));
CHECK(sprite_2d->get_offset() == Point2(8.0, 8.0));
sprite_2d->set_offset(Point2(25.0, 50.0));
CHECK(sprite_2d->get_offset() == Point2(25.0, 50.0));
sprite_2d->set_offset(Point2(500.0, 250.0));
CHECK(sprite_2d->get_offset() == Point2(500.0, 250.0));
sprite_2d->set_offset(Point2(-8.0, -8.0));
CHECK(sprite_2d->get_offset() == Point2(-8.0, -8.0));
sprite_2d->set_offset(Point2(-25.0, -50.0));
CHECK(sprite_2d->get_offset() == Point2(-25.0, -50.0));
sprite_2d->set_offset(Point2(-500.0, -250.0));
CHECK(sprite_2d->get_offset() == Point2(-500.0, -250.0));
memdelete(sprite_2d);
}
} // namespace TestSprite2D

View File

@@ -0,0 +1,245 @@
/**************************************************************************/
/* test_sprite_frames.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/sprite_frames.h"
#include "tests/test_macros.h"
namespace TestSpriteFrames {
const String test_animation_name = "GodotTest";
TEST_CASE("[SpriteFrames] Constructor methods") {
const SpriteFrames frames;
CHECK_MESSAGE(
frames.get_animation_names().size() == 1,
"Should be initialized with 1 entry.");
CHECK_MESSAGE(
frames.get_animation_names().get(0) == "default",
"Should be initialized with default entry.");
}
TEST_CASE("[SpriteFrames] Animation addition, list getter, renaming, removal, and retrieval") {
SpriteFrames frames;
Vector<String> test_names = { "default", "2", "1", "3" };
// "default" is there already
frames.add_animation("2");
frames.add_animation("1");
frames.add_animation("3");
for (int i = 0; i < test_names.size(); i++) {
CHECK_MESSAGE(
frames.has_animation(test_names[i]),
"Add animation properly worked for each value");
}
CHECK_MESSAGE(
!frames.has_animation("999"),
"Return false when checking for animation that does not exist");
List<StringName> sname_list;
frames.get_animation_list(&sname_list);
CHECK_MESSAGE(
sname_list.size() == test_names.size(),
"StringName List getter returned list of expected size");
int idx = 0;
for (List<StringName>::ConstIterator itr = sname_list.begin(); itr != sname_list.end(); ++itr, ++idx) {
CHECK_MESSAGE(
*itr == StringName(test_names[idx]),
"StringName List getter returned expected values");
}
// get_animation_names() sorts the results.
Vector<String> string_vector = frames.get_animation_names();
test_names.sort();
for (int i = 0; i < test_names.size(); i++) {
CHECK_MESSAGE(
string_vector[i] == test_names[i],
"String Vector getter returned expected values");
}
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.rename_animation("This does not exist", "0");
ERR_PRINT_ON;
CHECK_MESSAGE(
!frames.has_animation("0"),
"Correctly handles rename error when entry does not exist");
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.rename_animation("3", "1");
ERR_PRINT_ON;
CHECK_MESSAGE(
frames.has_animation("3"),
"Correctly handles rename error when entry exists, but new name already exists");
ERR_PRINT_OFF;
frames.add_animation("1");
ERR_PRINT_ON;
CHECK_MESSAGE(
frames.get_animation_names().size() == 4,
"Correctly does not add when entry already exists");
frames.rename_animation("3", "9");
CHECK_MESSAGE(
frames.has_animation("9"),
"Animation renamed correctly");
frames.remove_animation("9");
CHECK_MESSAGE(
!frames.has_animation("9"),
"Animation removed correctly");
frames.clear_all();
CHECK_MESSAGE(
frames.get_animation_names().size() == 1,
"Clear all removed all animations and re-added the default animation entry");
}
TEST_CASE("[SpriteFrames] Animation Speed getter and setter") {
SpriteFrames frames;
frames.add_animation(test_animation_name);
CHECK_MESSAGE(
frames.get_animation_speed(test_animation_name) == 5.0,
"Sets new animation to default speed");
frames.set_animation_speed(test_animation_name, 123.0004);
CHECK_MESSAGE(
frames.get_animation_speed(test_animation_name) == 123.0004,
"Sets animation to positive double");
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.get_animation_speed("This does not exist");
frames.set_animation_speed("This does not exist", 100);
frames.set_animation_speed(test_animation_name, -999.999);
ERR_PRINT_ON;
CHECK_MESSAGE(
frames.get_animation_speed(test_animation_name) == 123.0004,
"Prevents speed of animation being set to a negative value");
frames.set_animation_speed(test_animation_name, 0.0);
CHECK_MESSAGE(
frames.get_animation_speed(test_animation_name) == 0.0,
"Sets animation to zero");
}
TEST_CASE("[SpriteFrames] Animation Loop getter and setter") {
SpriteFrames frames;
frames.add_animation(test_animation_name);
CHECK_MESSAGE(
frames.get_animation_loop(test_animation_name),
"Sets new animation to default loop value.");
frames.set_animation_loop(test_animation_name, true);
CHECK_MESSAGE(
frames.get_animation_loop(test_animation_name),
"Sets animation loop to true");
frames.set_animation_loop(test_animation_name, false);
CHECK_MESSAGE(
!frames.get_animation_loop(test_animation_name),
"Sets animation loop to false");
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.get_animation_loop("This does not exist");
frames.set_animation_loop("This does not exist", false);
ERR_PRINT_ON;
}
// TODO
TEST_CASE("[SpriteFrames] Frame addition, removal, and retrieval") {
Ref<Texture2D> dummy_frame1;
dummy_frame1.instantiate();
SpriteFrames frames;
frames.add_animation(test_animation_name);
frames.add_animation("1");
frames.add_animation("2");
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 0,
"Animation has a default frame count of 0");
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 0);
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 1);
frames.add_frame(test_animation_name, dummy_frame1, 1.0, 2);
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 3,
"Adds multiple frames");
frames.remove_frame(test_animation_name, 1);
frames.remove_frame(test_animation_name, 0);
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 1,
"Removes multiple frames");
// These error handling cases should not crash.
ERR_PRINT_OFF;
frames.add_frame("does not exist", dummy_frame1, 1.0, 0);
frames.remove_frame(test_animation_name, -99);
frames.remove_frame("does not exist", 0);
ERR_PRINT_ON;
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 1,
"Handles bad values when adding or removing frames.");
frames.clear(test_animation_name);
CHECK_MESSAGE(
frames.get_frame_count(test_animation_name) == 0,
"Clears frames.");
}
} // namespace TestSpriteFrames

View File

@@ -0,0 +1,191 @@
/**************************************************************************/
/* test_style_box_texture.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/style_box_texture.h"
#include "tests/test_macros.h"
namespace TestStyleBoxTexture {
TEST_CASE("[StyleBoxTexture] Constructor") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->is_draw_center_enabled() == true);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 0);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 0);
CHECK(style_box_texture->get_modulate() == Color(1, 1, 1, 1));
CHECK(style_box_texture->get_region_rect() == Rect2(0, 0, 0, 0));
CHECK(style_box_texture->get_texture() == Ref<Texture2D>());
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 0);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 0);
}
TEST_CASE("[StyleBoxTexture] set_texture, get_texture") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
Ref<Texture2D> texture = memnew(Texture2D);
style_box_texture->set_texture(texture);
CHECK(style_box_texture->get_texture() == texture);
}
TEST_CASE("[StyleBoxTexture] set_texture_margin, set_texture_margin_all, set_texture_margin_individual, get_texture_margin") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_texture_margin, get_texture_margin") {
style_box_texture->set_texture_margin(SIDE_LEFT, 1);
style_box_texture->set_texture_margin(SIDE_TOP, 1);
style_box_texture->set_texture_margin(SIDE_RIGHT, 1);
style_box_texture->set_texture_margin(SIDE_BOTTOM, 1);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 1);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 1);
}
SUBCASE("set_texture_margin_all") {
style_box_texture->set_texture_margin_all(2);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 2);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 2);
}
SUBCASE("set_texture_margin_individual") {
style_box_texture->set_texture_margin_individual(3, 4, 5, 6);
CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 3);
CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 4);
CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 5);
CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 6);
}
}
TEST_CASE("[StyleBoxTexture] set_expand_margin, set_expand_margin_all, set_expand_margin_individual") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_expand_margin, get_expand_margin") {
style_box_texture->set_expand_margin(SIDE_LEFT, 1);
style_box_texture->set_expand_margin(SIDE_TOP, 1);
style_box_texture->set_expand_margin(SIDE_RIGHT, 1);
style_box_texture->set_expand_margin(SIDE_BOTTOM, 1);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 1);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 1);
}
SUBCASE("set_expand_margin_all") {
style_box_texture->set_expand_margin_all(2);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 2);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 2);
}
SUBCASE("set_expand_margin_individual") {
style_box_texture->set_expand_margin_individual(3, 4, 5, 6);
CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 3);
CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 4);
CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 5);
CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 6);
}
}
TEST_CASE("[StyleBoxTexture] set_region_rect, get_region_rect") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_region_rect(Rect2(1, 1, 1, 1));
CHECK(style_box_texture->get_region_rect() == Rect2(1, 1, 1, 1));
}
TEST_CASE("[StyleBoxTexture] set_draw_center, get_draw_center") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_draw_center(false);
CHECK(style_box_texture->is_draw_center_enabled() == false);
}
TEST_CASE("[StyleBoxTexture] set_h_axis_stretch_mode, set_v_axis_stretch_mode, get_h_axis_stretch_mode, get_v_axis_stretch_mode") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
SUBCASE("set_h_axis_stretch_mode, get_h_axis_stretch_mode") {
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
}
SUBCASE("set_v_axis_stretch_mode, get_v_axis_stretch_mode") {
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
}
}
TEST_CASE("[StyleBoxTexture] set_modulate, get_modulate") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_modulate(Color(0, 0, 0, 0));
CHECK(style_box_texture->get_modulate() == Color(0, 0, 0, 0));
}
TEST_CASE("[StyleBoxTexture] get_draw_rect") {
Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
style_box_texture->set_expand_margin_all(5);
CHECK(style_box_texture->get_draw_rect(Rect2(0, 0, 1, 1)) == Rect2(-5, -5, 11, 11));
}
} // namespace TestStyleBoxTexture

1161
tests/scene/test_tab_bar.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,919 @@
/**************************************************************************/
/* test_tab_container.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/tab_container.h"
#include "tests/test_macros.h"
namespace TestTabContainer {
TEST_CASE("[SceneTree][TabContainer] tab operations") {
TabContainer *tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
SIGNAL_WATCH(tab_container, "tab_selected");
SIGNAL_WATCH(tab_container, "tab_changed");
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1");
Control *tab2 = memnew(Control);
tab2->set_name("tab2");
SUBCASE("[TabContainer] add tabs by adding children") {
CHECK(tab_container->get_tab_count() == 0);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
// Add first tab child.
tab_container->add_child(tab0);
// MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK("tab_changed", { { 0 } });
// Add second tab child.
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Check default values, the title is the name of the child.
CHECK(tab_container->get_tab_control(0) == tab0);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 0);
CHECK(tab_container->get_tab_title(0) == "tab0");
CHECK(tab_container->get_tab_tooltip(0) == "");
CHECK_FALSE(tab_container->is_tab_disabled(0));
CHECK_FALSE(tab_container->is_tab_hidden(0));
CHECK(tab_container->get_tab_control(1) == tab1);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_title(1) == "tab1");
CHECK(tab_container->get_tab_tooltip(1) == "");
CHECK_FALSE(tab_container->is_tab_disabled(1));
CHECK_FALSE(tab_container->is_tab_hidden(1));
}
SUBCASE("[TabContainer] remove tabs by removing children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Remove first tab.
tab_container->remove_child(tab0);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_tab_title(0) == "tab1");
CHECK(tab_container->get_tab_title(1) == "tab2");
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove last tab.
tab_container->remove_child(tab2);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_tab_title(0) == "tab1");
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Remove only tab.
tab_container->remove_child(tab1);
CHECK(tab_container->get_tab_count() == 0);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", { { -1 } });
// Remove current tab when there are other tabs.
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
tab_container->set_current_tab(2);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 2);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
tab_container->remove_child(tab2);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK("tab_changed", { { 1 } });
}
SUBCASE("[TabContainer] move tabs by moving children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Move the first tab to the end.
tab_container->move_child(tab0, 2);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Move the second tab to the front.
tab_container->move_child(tab2, 0);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 0);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabContainer] set current tab") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK("tab_changed", { { 0 } });
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Set the current tab.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Set to same tab.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Out of bounds.
ERR_PRINT_OFF;
tab_container->set_current_tab(-5);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
tab_container->set_current_tab(5);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
ERR_PRINT_ON;
}
SUBCASE("[TabContainer] change current tab by changing visibility of children") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Show a child to make it the current tab.
tab1->show();
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Hide the visible child to select the next tab.
tab1->hide();
CHECK(tab_container->get_current_tab() == 2);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", { { 2 } });
SIGNAL_CHECK("tab_changed", { { 2 } });
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK(tab2->is_visible());
// Hide the visible child to select the previous tab if there is no next.
tab2->hide();
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 2);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
// Cannot hide if there is only one valid child since deselection is not enabled.
tab_container->remove_child(tab1);
tab_container->remove_child(tab2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
tab0->hide();
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
// Can hide the last tab if deselection is enabled.
tab_container->set_deselect_enabled(true);
tab0->hide();
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { -1 } });
SIGNAL_CHECK("tab_changed", { { -1 } });
MessageQueue::get_singleton()->flush();
CHECK_FALSE(tab0->is_visible());
}
SIGNAL_UNWATCH(tab_container, "tab_selected");
SIGNAL_UNWATCH(tab_container, "tab_changed");
memdelete(tab2);
memdelete(tab1);
memdelete(tab0);
memdelete(tab_container);
}
TEST_CASE("[SceneTree][TabContainer] initialization") {
TabContainer *tab_container = memnew(TabContainer);
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1 ");
Control *tab2 = memnew(Control);
tab2->set_name("tab2 ");
SIGNAL_WATCH(tab_container, "tab_selected");
SIGNAL_WATCH(tab_container, "tab_changed");
SUBCASE("[TabContainer] add children before entering tree") {
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
}
SUBCASE("[TabContainer] current tab can be set before children are added") {
// Set the current tab before there are any tabs.
// This queues the current tab to update on entering the tree.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab_container->add_child(tab2);
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Current tab is set when entering the tree.
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
}
SUBCASE("[TabContainer] cannot set current tab to an invalid value before tabs are set") {
tab_container->set_current_tab(100);
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// This will print an error message as if `set_current_tab` was called after.
ERR_PRINT_OFF;
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
ERR_PRINT_ON;
}
SUBCASE("[TabContainer] children visibility before entering tree") {
CHECK(tab_container->get_current_tab() == -1);
CHECK(tab_container->get_previous_tab() == -1);
// Adding a hidden child first will change visibility because it is the current tab.
tab0->hide();
tab_container->add_child(tab0);
CHECK(tab_container->get_tab_count() == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
// Adding a visible child after will hide it because it is not the current tab.
tab_container->add_child(tab1);
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
MessageQueue::get_singleton()->flush();
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
// Can change current by showing child now after children have been added.
// This queues the current tab to update on entering the tree.
tab1->show();
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK(tab1->is_visible());
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 2);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
}
SUBCASE("[TabContainer] setting current tab and changing child visibility after adding") {
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
tab2->show();
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab0->is_visible());
CHECK_FALSE(tab1->is_visible());
CHECK(tab2->is_visible());
// Whichever happens last will have priority.
tab_container->set_current_tab(1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
// Current tab is set when entering the tree.
SceneTree::get_singleton()->get_root()->add_child(tab_container);
MessageQueue::get_singleton()->flush();
CHECK(tab_container->get_tab_count() == 3);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
CHECK_FALSE(tab0->is_visible());
CHECK(tab1->is_visible());
CHECK_FALSE(tab2->is_visible());
}
SIGNAL_UNWATCH(tab_container, "tab_selected");
SIGNAL_UNWATCH(tab_container, "tab_changed");
memdelete(tab2);
memdelete(tab1);
memdelete(tab0);
memdelete(tab_container);
}
TEST_CASE("[SceneTree][TabContainer] layout and offset") {
TabContainer *tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
tab_container->set_clip_tabs(false);
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1 ");
Control *tab2 = memnew(Control);
tab2->set_name("tab2 ");
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
MessageQueue::get_singleton()->flush();
Size2 all_tabs_size = tab_container->get_size();
const float side_margin = tab_container->get_theme_constant("side_margin");
TabBar *tab_bar = tab_container->get_tab_bar();
Vector<Rect2> tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
SUBCASE("[TabContainer] tabs are arranged next to each other") {
// Horizontal positions are next to each other.
CHECK(tab_rects[0].position.x == 0);
CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
// Fills the entire width.
CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x - side_margin);
// Horizontal sizes are positive.
CHECK(tab_rects[0].size.x > 0);
CHECK(tab_rects[1].size.x > 0);
CHECK(tab_rects[2].size.x > 0);
// Vertical positions are at 0.
CHECK(tab_rects[0].position.y == 0);
CHECK(tab_rects[1].position.y == 0);
CHECK(tab_rects[2].position.y == 0);
// Vertical sizes are the same.
CHECK(tab_rects[0].size.y == tab_rects[1].size.y);
CHECK(tab_rects[1].size.y == tab_rects[2].size.y);
}
SUBCASE("[TabContainer] tab position") {
float tab_height = tab_rects[0].size.y;
Ref<StyleBox> panel_style = tab_container->get_theme_stylebox("panel_style");
// Initial position, same as top position.
// Tab bar is at the top.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and below the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
// Bottom position.
tab_container->set_tabs_position(TabContainer::POSITION_BOTTOM);
CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_BOTTOM);
MessageQueue::get_singleton()->flush();
// Tab bar is at the bottom.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 1);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == -tab_height);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and above the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == 0);
CHECK(tab0->get_offset(SIDE_BOTTOM) == -tab_height);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
// Top position.
tab_container->set_tabs_position(TabContainer::POSITION_TOP);
CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_TOP);
MessageQueue::get_singleton()->flush();
// Tab bar is at the top.
CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
// Child is expanded and below the tab bar.
CHECK(tab0->get_anchor(SIDE_TOP) == 0);
CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
CHECK(tab0->get_offset(SIDE_LEFT) == 0);
CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
}
memdelete(tab_container);
}
TEST_CASE("[SceneTree][TabContainer] Mouse interaction") {
TabContainer *tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(tab_container);
tab_container->set_clip_tabs(false);
Control *tab0 = memnew(Control);
tab0->set_name("tab0");
Control *tab1 = memnew(Control);
tab1->set_name("tab1 ");
Control *tab2 = memnew(Control);
tab2->set_name("tab2 ");
tab_container->add_child(tab0);
tab_container->add_child(tab1);
tab_container->add_child(tab2);
MessageQueue::get_singleton()->flush();
const float side_margin = tab_container->get_theme_constant("side_margin");
TabBar *tab_bar = tab_container->get_tab_bar();
Vector<Rect2> tab_rects = {
tab_bar->get_tab_rect(0),
tab_bar->get_tab_rect(1),
tab_bar->get_tab_rect(2)
};
SIGNAL_WATCH(tab_container, "active_tab_rearranged");
SIGNAL_WATCH(tab_container, "tab_changed");
SIGNAL_WATCH(tab_container, "tab_clicked");
SIGNAL_WATCH(tab_container, "tab_selected");
SUBCASE("[TabContainer] Click to change current") {
CHECK(tab_container->get_current_tab() == 0);
CHECK(tab_container->get_previous_tab() == -1);
SIGNAL_DISCARD("tab_selected");
SIGNAL_DISCARD("tab_changed");
// Click to set the current tab.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 0);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK("tab_changed", { { 1 } });
SIGNAL_CHECK("tab_clicked", { { 1 } });
// Click on the same tab.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK_FALSE("tab_changed");
SIGNAL_CHECK("tab_clicked", { { 1 } });
// Click outside of tabs.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(0, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
CHECK(tab_container->get_current_tab() == 1);
CHECK(tab_container->get_previous_tab() == 1);
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
SIGNAL_CHECK_FALSE("tab_clicked");
}
SUBCASE("[TabContainer] Drag and drop internally") {
// Cannot drag if not enabled.
CHECK_FALSE(tab_container->get_drag_to_rearrange_enabled());
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_idx_from_control(tab0) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 2);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
tab_container->set_drag_to_rearrange_enabled(true);
CHECK(tab_container->get_drag_to_rearrange_enabled());
// Release over the same tab to not move.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_idx_from_control(tab0) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 2);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Move the first tab after the second.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[1].position, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2(side_margin, 0) + tab_rects[1].position + Point2(tab_rects[1].size.x / 2 + 1, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 2);
SIGNAL_CHECK("active_tab_rearranged", { { 1 } });
SIGNAL_CHECK("tab_selected", { { 1 } });
SIGNAL_CHECK_FALSE("tab_changed");
tab_rects = { tab_bar->get_tab_rect(0), tab_bar->get_tab_rect(1), tab_bar->get_tab_rect(2) };
// Move the last tab to be the first.
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[2].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 2 } });
SIGNAL_CHECK("tab_changed", { { 2 } });
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_idx_from_control(tab2) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
SIGNAL_CHECK("active_tab_rearranged", { { 0 } });
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
}
SUBCASE("[TabContainer] Drag and drop to different TabContainer") {
TabContainer *target_tab_container = memnew(TabContainer);
SceneTree::get_singleton()->get_root()->add_child(target_tab_container);
target_tab_container->set_clip_tabs(false);
Control *other_tab0 = memnew(Control);
other_tab0->set_name("other_tab0");
target_tab_container->add_child(other_tab0);
target_tab_container->set_position(tab_container->get_size());
MessageQueue::get_singleton()->flush();
Vector<Rect2> target_tab_rects = {
target_tab_container->get_tab_bar()->get_tab_rect(0)
};
tab_container->set_drag_to_rearrange_enabled(true);
tab_container->set_tabs_rearrange_group(1);
Point2 target_tab_after_first = Point2(side_margin, 0) + target_tab_container->get_position() + target_tab_rects[0].position + Point2(target_tab_rects[0].size.x / 2 + 1, 0);
// Cannot drag to another TabContainer that does not have drag to rearrange enabled.
CHECK_FALSE(target_tab_container->get_drag_to_rearrange_enabled());
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position + Point2(20, 0), MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_count() == 3);
CHECK(target_tab_container->get_tab_count() == 1);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Cannot drag to another TabContainer that has a tabs rearrange group of -1.
target_tab_container->set_drag_to_rearrange_enabled(true);
tab_container->set_tabs_rearrange_group(-1);
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position + Point2(20, 0), MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_count() == 3);
CHECK(target_tab_container->get_tab_count() == 1);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Cannot drag to another TabContainer that has a different tabs rearrange group.
tab_container->set_tabs_rearrange_group(1);
target_tab_container->set_tabs_rearrange_group(2);
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position + Point2(20, 0), MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_count() == 3);
CHECK(target_tab_container->get_tab_count() == 1);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected");
SIGNAL_CHECK_FALSE("tab_changed");
// Drag to target container.
target_tab_container->set_tabs_rearrange_group(1);
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position + Point2(20, 0), MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(target_tab_after_first, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_tab_after_first, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_count() == 2);
CHECK(target_tab_container->get_tab_count() == 2);
CHECK(tab_container->get_child_count(false) == 2);
CHECK(target_tab_container->get_child_count(false) == 2);
CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
CHECK(target_tab_container->get_tab_idx_from_control(other_tab0) == 0);
CHECK(target_tab_container->get_tab_idx_from_control(tab0) == 1);
CHECK(tab_container->get_current_tab() == 0);
CHECK(target_tab_container->get_current_tab() == 1);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected"); // Does not send since tab was removed.
SIGNAL_CHECK("tab_changed", { { 0 } });
Point2 target_tab = Point2(side_margin, 0) + target_tab_container->get_position();
// Drag to target container at first index.
target_tab_container->set_tabs_rearrange_group(1);
SEND_GUI_MOUSE_BUTTON_EVENT(Point2(side_margin, 0) + tab_rects[0].position, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(Point2(side_margin, 0) + tab_rects[0].position + Point2(20, 0), MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_MOTION_EVENT(target_tab, MouseButtonMask::LEFT, Key::NONE);
SIGNAL_CHECK("tab_selected", { { 0 } });
SIGNAL_CHECK_FALSE("tab_changed");
CHECK(tab_container->get_viewport()->gui_is_dragging());
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(target_tab, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(tab_container->get_viewport()->gui_is_dragging());
CHECK(tab_container->get_tab_count() == 1);
CHECK(target_tab_container->get_tab_count() == 3);
CHECK(tab_container->get_tab_idx_from_control(tab2) == 0);
CHECK(target_tab_container->get_tab_idx_from_control(tab1) == 0);
CHECK(target_tab_container->get_tab_idx_from_control(other_tab0) == 1);
CHECK(target_tab_container->get_tab_idx_from_control(tab0) == 2);
CHECK(tab_container->get_current_tab() == 0);
CHECK(target_tab_container->get_current_tab() == 0);
SIGNAL_CHECK_FALSE("active_tab_rearranged");
SIGNAL_CHECK_FALSE("tab_selected"); // Does not send since tab was removed.
SIGNAL_CHECK("tab_changed", { { 0 } });
memdelete(target_tab_container);
}
SIGNAL_UNWATCH(tab_container, "active_tab_rearranged");
SIGNAL_UNWATCH(tab_container, "tab_changed");
SIGNAL_UNWATCH(tab_container, "tab_clicked");
SIGNAL_UNWATCH(tab_container, "tab_selected");
memdelete(tab_container);
}
// FIXME: Add tests for keyboard navigation and other methods.
} // namespace TestTabContainer

8299
tests/scene/test_text_edit.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
/**************************************************************************/
/* test_texture_progress_bar.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/texture_progress_bar.h"
#include "tests/test_macros.h"
namespace TestTextureProgressBar {
TEST_CASE("[SceneTree][TextureProgressBar]") {
TextureProgressBar *texture_progress_bar = memnew(TextureProgressBar);
SUBCASE("[TextureProgressBar] set_radial_initial_angle() should wrap angle between 0 and 360 degrees (inclusive).") {
texture_progress_bar->set_radial_initial_angle(0.0);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)0.0));
texture_progress_bar->set_radial_initial_angle(360.0);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)360.0));
texture_progress_bar->set_radial_initial_angle(30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
texture_progress_bar->set_radial_initial_angle(-30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)(360 - 30.5)));
texture_progress_bar->set_radial_initial_angle(36000 + 30.5);
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
texture_progress_bar->set_radial_initial_angle(-(36000 + 30.5));
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)(360 - 30.5)));
}
SUBCASE("[TextureProgressBar] set_radial_initial_angle() should not set non-finite values.") {
texture_progress_bar->set_radial_initial_angle(30.5);
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(Math::INF);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(-Math::INF);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(Math::NaN);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
ERR_PRINT_OFF;
texture_progress_bar->set_radial_initial_angle(-Math::NaN);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(texture_progress_bar->get_radial_initial_angle(), (float)30.5));
}
memdelete(texture_progress_bar);
}
} // namespace TestTextureProgressBar

270
tests/scene/test_theme.h Normal file
View File

@@ -0,0 +1,270 @@
/**************************************************************************/
/* test_theme.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/image_texture.h"
#include "scene/resources/style_box_flat.h"
#include "scene/resources/theme.h"
#include "tests/test_tools.h"
#include "thirdparty/doctest/doctest.h"
namespace TestTheme {
class Fixture {
public:
struct DataEntry {
Theme::DataType type;
Variant value;
} const valid_data[Theme::DATA_TYPE_MAX] = {
{ Theme::DATA_TYPE_COLOR, Color() },
{ Theme::DATA_TYPE_CONSTANT, 42 },
{ Theme::DATA_TYPE_FONT, Ref<FontFile>(memnew(FontFile)) },
{ Theme::DATA_TYPE_FONT_SIZE, 42 },
{ Theme::DATA_TYPE_ICON, Ref<Texture>(memnew(ImageTexture)) },
{ Theme::DATA_TYPE_STYLEBOX, Ref<StyleBox>(memnew(StyleBoxFlat)) },
};
const StringName valid_item_name = "valid_item_name";
const StringName valid_type_name = "ValidTypeName";
};
TEST_CASE_FIXTURE(Fixture, "[Theme] Good theme type names") {
StringName names[] = {
"", // Empty name.
"CapitalizedName",
"snake_cased_name",
"42",
"_Underscore_",
};
SUBCASE("add_type") {
for (const StringName &name : names) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->add_type(name);
CHECK_FALSE(ed.has_error);
}
}
SUBCASE("set_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_theme_item(entry.type, valid_item_name, name, entry.value);
CHECK_FALSE(ed.has_error);
}
}
}
SUBCASE("add_theme_item_type") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->add_theme_item_type(entry.type, name);
CHECK_FALSE(ed.has_error);
}
}
}
SUBCASE("set_type_variation") {
for (const StringName &name : names) {
if (name == StringName()) { // Skip empty here, not allowed.
continue;
}
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_type_variation(valid_type_name, name);
CHECK_FALSE(ed.has_error);
}
for (const StringName &name : names) {
if (name == StringName()) { // Skip empty here, not allowed.
continue;
}
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_type_variation(name, valid_type_name);
CHECK_FALSE(ed.has_error);
}
}
}
TEST_CASE_FIXTURE(Fixture, "[Theme] Bad theme type names") {
StringName names[] = {
"With/Slash",
"With Space",
"With@various$symbols!",
String::utf8("contains_汉字"),
};
ERR_PRINT_OFF; // All these rightfully print errors.
SUBCASE("add_type") {
for (const StringName &name : names) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->add_type(name);
CHECK(ed.has_error);
}
}
SUBCASE("set_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_theme_item(entry.type, valid_item_name, name, entry.value);
CHECK(ed.has_error);
}
}
}
SUBCASE("add_theme_item_type") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->add_theme_item_type(entry.type, name);
CHECK(ed.has_error);
}
}
}
SUBCASE("set_type_variation") {
for (const StringName &name : names) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_type_variation(valid_type_name, name);
CHECK(ed.has_error);
}
for (const StringName &name : names) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_type_variation(name, valid_type_name);
CHECK(ed.has_error);
}
}
ERR_PRINT_ON;
}
TEST_CASE_FIXTURE(Fixture, "[Theme] Good theme item names") {
StringName names[] = {
"CapitalizedName",
"snake_cased_name",
"42",
"_Underscore_",
};
SUBCASE("set_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_theme_item(entry.type, name, valid_type_name, entry.value);
CHECK_FALSE(ed.has_error);
CHECK(theme->has_theme_item(entry.type, name, valid_type_name));
}
}
}
SUBCASE("rename_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
theme->set_theme_item(entry.type, valid_item_name, valid_type_name, entry.value);
ErrorDetector ed;
theme->rename_theme_item(entry.type, valid_item_name, name, valid_type_name);
CHECK_FALSE(ed.has_error);
CHECK_FALSE(theme->has_theme_item(entry.type, valid_item_name, valid_type_name));
CHECK(theme->has_theme_item(entry.type, name, valid_type_name));
}
}
}
}
TEST_CASE_FIXTURE(Fixture, "[Theme] Bad theme item names") {
StringName names[] = {
"", // Empty name.
"With/Slash",
"With Space",
"With@various$symbols!",
String::utf8("contains_汉字"),
};
ERR_PRINT_OFF; // All these rightfully print errors.
SUBCASE("set_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
ErrorDetector ed;
theme->set_theme_item(entry.type, name, valid_type_name, entry.value);
CHECK(ed.has_error);
CHECK_FALSE(theme->has_theme_item(entry.type, name, valid_type_name));
}
}
}
SUBCASE("rename_theme_item") {
for (const StringName &name : names) {
for (const DataEntry &entry : valid_data) {
Ref<Theme> theme = memnew(Theme);
theme->set_theme_item(entry.type, valid_item_name, valid_type_name, entry.value);
ErrorDetector ed;
theme->rename_theme_item(entry.type, valid_item_name, name, valid_type_name);
CHECK(ed.has_error);
CHECK(theme->has_theme_item(entry.type, valid_item_name, valid_type_name));
CHECK_FALSE(theme->has_theme_item(entry.type, name, valid_type_name));
}
}
}
ERR_PRINT_ON;
}
} // namespace TestTheme

206
tests/scene/test_timer.h Normal file
View File

@@ -0,0 +1,206 @@
/**************************************************************************/
/* test_timer.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. */
/**************************************************************************/
#pragma once
#include "scene/main/timer.h"
#include "tests/test_macros.h"
namespace TestTimer {
TEST_CASE("[SceneTree][Timer] Check Timer Setters and Getters") {
Timer *test_timer = memnew(Timer);
SUBCASE("[Timer] Timer set and get wait time") {
// check default
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 1.0));
test_timer->set_wait_time(50.0);
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 50.0));
test_timer->set_wait_time(42.0);
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0));
// wait time remains unchanged if we attempt to set it negative or zero
ERR_PRINT_OFF;
test_timer->set_wait_time(-22.0);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0));
ERR_PRINT_OFF;
test_timer->set_wait_time(0.0);
ERR_PRINT_ON;
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0));
}
SUBCASE("[Timer] Timer set and get one shot") {
// check default
CHECK(test_timer->is_one_shot() == false);
test_timer->set_one_shot(true);
CHECK(test_timer->is_one_shot() == true);
test_timer->set_one_shot(false);
CHECK(test_timer->is_one_shot() == false);
}
SUBCASE("[Timer] Timer set and get autostart") {
// check default
CHECK(test_timer->has_autostart() == false);
test_timer->set_autostart(true);
CHECK(test_timer->has_autostart() == true);
test_timer->set_autostart(false);
CHECK(test_timer->has_autostart() == false);
}
SUBCASE("[Timer] Timer start and stop") {
test_timer->set_autostart(false);
}
SUBCASE("[Timer] Timer set and get paused") {
// check default
CHECK(test_timer->is_paused() == false);
test_timer->set_paused(true);
CHECK(test_timer->is_paused() == true);
test_timer->set_paused(false);
CHECK(test_timer->is_paused() == false);
}
memdelete(test_timer);
}
TEST_CASE("[SceneTree][Timer] Check Timer Start and Stop") {
Timer *test_timer = memnew(Timer);
SUBCASE("[Timer] Timer start and stop") {
SceneTree::get_singleton()->get_root()->add_child(test_timer);
test_timer->start(5.0);
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0));
CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0));
test_timer->start(-2.0);
// the wait time and time left remains unchanged when started with a negative start time
CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0));
CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0));
test_timer->stop();
CHECK(test_timer->is_processing() == false);
CHECK(test_timer->has_autostart() == false);
}
memdelete(test_timer);
}
TEST_CASE("[SceneTree][Timer] Check Timer process callback") {
Timer *test_timer = memnew(Timer);
SUBCASE("[Timer] Timer process callback") {
// check default
CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE);
test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS);
CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS);
test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_IDLE);
CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE);
}
memdelete(test_timer);
}
TEST_CASE("[SceneTree][Timer] Check Timer timeout signal") {
Timer *test_timer = memnew(Timer);
SceneTree::get_singleton()->get_root()->add_child(test_timer);
test_timer->set_process(true);
test_timer->set_physics_process(true);
SUBCASE("[Timer] Timer process timeout signal must be emitted") {
SIGNAL_WATCH(test_timer, SNAME("timeout"));
test_timer->start(0.1);
SceneTree::get_singleton()->process(0.2);
Array signal_args = { {} };
SIGNAL_CHECK(SNAME("timeout"), signal_args);
SIGNAL_UNWATCH(test_timer, SNAME("timeout"));
}
SUBCASE("[Timer] Timer process timeout signal must not be emitted") {
SIGNAL_WATCH(test_timer, SNAME("timeout"));
test_timer->start(0.1);
SceneTree::get_singleton()->process(0.05);
Array signal_args = { {} };
SIGNAL_CHECK_FALSE(SNAME("timeout"));
SIGNAL_UNWATCH(test_timer, SNAME("timeout"));
}
test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS);
SUBCASE("[Timer] Timer physics process timeout signal must be emitted") {
SIGNAL_WATCH(test_timer, SNAME("timeout"));
test_timer->start(0.1);
SceneTree::get_singleton()->physics_process(0.2);
Array signal_args = { {} };
SIGNAL_CHECK(SNAME("timeout"), signal_args);
SIGNAL_UNWATCH(test_timer, SNAME("timeout"));
}
SUBCASE("[Timer] Timer physics process timeout signal must not be emitted") {
SIGNAL_WATCH(test_timer, SNAME("timeout"));
test_timer->start(0.1);
SceneTree::get_singleton()->physics_process(0.05);
Array signal_args = { {} };
SIGNAL_CHECK_FALSE(SNAME("timeout"));
SIGNAL_UNWATCH(test_timer, SNAME("timeout"));
}
memdelete(test_timer);
}
} // namespace TestTimer

298
tests/scene/test_tree.h Normal file
View File

@@ -0,0 +1,298 @@
/**************************************************************************/
/* test_tree.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/tree.h"
#include "tests/test_macros.h"
namespace TestTree {
TEST_CASE("[SceneTree][Tree]") {
SUBCASE("[Tree] Create and remove items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
CHECK_EQ(root->get_child_count(), 1);
TreeItem *child2 = tree->create_item(root);
CHECK_EQ(root->get_child_count(), 2);
TreeItem *child3 = tree->create_item(root, 0);
CHECK_EQ(root->get_child_count(), 3);
CHECK_EQ(root->get_child(0), child3);
CHECK_EQ(root->get_child(1), child1);
CHECK_EQ(root->get_child(2), child2);
root->remove_child(child3);
CHECK_EQ(root->get_child_count(), 2);
root->add_child(child3);
CHECK_EQ(root->get_child_count(), 3);
TreeItem *child4 = root->create_child();
CHECK_EQ(root->get_child_count(), 4);
CHECK_EQ(root->get_child(0), child1);
CHECK_EQ(root->get_child(1), child2);
CHECK_EQ(root->get_child(2), child3);
CHECK_EQ(root->get_child(3), child4);
memdelete(tree);
}
SUBCASE("[Tree] Clear items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
for (int i = 0; i < 10; i++) {
tree->create_item();
}
CHECK_EQ(root->get_child_count(), 10);
root->clear_children();
CHECK_EQ(root->get_child_count(), 0);
memdelete(tree);
}
SUBCASE("[Tree] Get last item.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *last;
for (int i = 0; i < 10; i++) {
last = tree->create_item();
}
CHECK_EQ(root->get_child_count(), 10);
CHECK_EQ(tree->get_last_item(), last);
// Check nested.
TreeItem *old_last = last;
for (int i = 0; i < 10; i++) {
last = tree->create_item(old_last);
}
CHECK_EQ(tree->get_last_item(), last);
memdelete(tree);
}
// https://github.com/godotengine/godot/issues/96205
SUBCASE("[Tree] Get last item after removal.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item(root);
TreeItem *child2 = tree->create_item(root);
CHECK_EQ(root->get_child_count(), 2);
CHECK_EQ(tree->get_last_item(), child2);
root->remove_child(child2);
CHECK_EQ(root->get_child_count(), 1);
CHECK_EQ(tree->get_last_item(), child1);
root->add_child(child2);
CHECK_EQ(root->get_child_count(), 2);
CHECK_EQ(tree->get_last_item(), child2);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next(), nullptr);
CHECK_EQ(root->get_next_visible(), child1);
CHECK_EQ(root->get_next_in_tree(), child1);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), child3);
CHECK_EQ(child2->get_next_in_tree(), child3);
CHECK_EQ(child3->get_next(), nullptr);
CHECK_EQ(child3->get_next_visible(), nullptr);
CHECK_EQ(child3->get_next_in_tree(), nullptr);
CHECK_EQ(root->get_prev(), nullptr);
CHECK_EQ(root->get_prev_visible(), nullptr);
CHECK_EQ(root->get_prev_in_tree(), nullptr);
CHECK_EQ(child1->get_prev(), nullptr);
CHECK_EQ(child1->get_prev_visible(), root);
CHECK_EQ(child1->get_prev_in_tree(), root);
CHECK_EQ(child2->get_prev(), child1);
CHECK_EQ(child2->get_prev_visible(), child1);
CHECK_EQ(child2->get_prev_in_tree(), child1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), child2);
CHECK_EQ(child3->get_prev_in_tree(), child2);
TreeItem *nested1 = tree->create_item(child2);
TreeItem *nested2 = tree->create_item(child2);
TreeItem *nested3 = tree->create_item(child2);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), nested1);
CHECK_EQ(child2->get_next_in_tree(), nested1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), nested3);
CHECK_EQ(child3->get_prev_in_tree(), nested3);
CHECK_EQ(nested1->get_prev_in_tree(), child2);
CHECK_EQ(nested1->get_next_in_tree(), nested2);
CHECK_EQ(nested3->get_next_in_tree(), child3);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items with hide root.") {
Tree *tree = memnew(Tree);
tree->set_hide_root(true);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next(), nullptr);
CHECK_EQ(root->get_next_visible(), child1);
CHECK_EQ(root->get_next_in_tree(), child1);
CHECK_EQ(child1->get_next(), child2);
CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
CHECK_EQ(child2->get_next_visible(), child3);
CHECK_EQ(child2->get_next_in_tree(), child3);
CHECK_EQ(child3->get_next(), nullptr);
CHECK_EQ(child3->get_next_visible(), nullptr);
CHECK_EQ(child3->get_next_in_tree(), nullptr);
CHECK_EQ(root->get_prev(), nullptr);
CHECK_EQ(root->get_prev_visible(), nullptr);
CHECK_EQ(root->get_prev_in_tree(), nullptr);
CHECK_EQ(child1->get_prev(), nullptr);
CHECK_EQ(child1->get_prev_visible(), nullptr);
CHECK_EQ(child1->get_prev_in_tree(), nullptr);
CHECK_EQ(child2->get_prev(), child1);
CHECK_EQ(child2->get_prev_visible(), child1);
CHECK_EQ(child2->get_prev_in_tree(), child1);
CHECK_EQ(child3->get_prev(), child2);
CHECK_EQ(child3->get_prev_visible(), child2);
CHECK_EQ(child3->get_prev_in_tree(), child2);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items wrapping.") {
Tree *tree = memnew(Tree);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next_visible(true), child1);
CHECK_EQ(root->get_next_in_tree(true), child1);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), child3);
CHECK_EQ(child2->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_next_visible(true), root);
CHECK_EQ(child3->get_next_in_tree(true), root);
CHECK_EQ(root->get_prev_visible(true), child3);
CHECK_EQ(root->get_prev_in_tree(true), child3);
CHECK_EQ(child1->get_prev_visible(true), root);
CHECK_EQ(child1->get_prev_in_tree(true), root);
CHECK_EQ(child2->get_prev_visible(true), child1);
CHECK_EQ(child2->get_prev_in_tree(true), child1);
CHECK_EQ(child3->get_prev_visible(true), child2);
CHECK_EQ(child3->get_prev_in_tree(true), child2);
TreeItem *nested1 = tree->create_item(child2);
TreeItem *nested2 = tree->create_item(child2);
TreeItem *nested3 = tree->create_item(child2);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), nested1);
CHECK_EQ(child2->get_next_in_tree(true), nested1);
CHECK_EQ(nested3->get_next_visible(true), child3);
CHECK_EQ(nested3->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_prev_visible(true), nested3);
CHECK_EQ(child3->get_prev_in_tree(true), nested3);
CHECK_EQ(nested1->get_prev_in_tree(true), child2);
CHECK_EQ(nested1->get_next_in_tree(true), nested2);
CHECK_EQ(nested3->get_next_in_tree(true), child3);
memdelete(tree);
}
SUBCASE("[Tree] Previous and Next items wrapping with hide root.") {
Tree *tree = memnew(Tree);
tree->set_hide_root(true);
TreeItem *root = tree->create_item();
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
CHECK_EQ(root->get_next_visible(true), child1);
CHECK_EQ(root->get_next_in_tree(true), child1);
CHECK_EQ(child1->get_next_visible(true), child2);
CHECK_EQ(child1->get_next_in_tree(true), child2);
CHECK_EQ(child2->get_next_visible(true), child3);
CHECK_EQ(child2->get_next_in_tree(true), child3);
CHECK_EQ(child3->get_next_visible(true), root);
CHECK_EQ(child3->get_next_in_tree(true), root);
CHECK_EQ(root->get_prev_visible(true), child3);
CHECK_EQ(root->get_prev_in_tree(true), child3);
CHECK_EQ(child1->get_prev_visible(true), child3);
CHECK_EQ(child1->get_prev_in_tree(true), child3);
CHECK_EQ(child2->get_prev_visible(true), child1);
CHECK_EQ(child2->get_prev_in_tree(true), child1);
CHECK_EQ(child3->get_prev_visible(true), child2);
CHECK_EQ(child3->get_prev_in_tree(true), child2);
memdelete(tree);
}
}
} // namespace TestTree

1974
tests/scene/test_viewport.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
/**************************************************************************/
/* test_visual_shader.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. */
/**************************************************************************/
#pragma once
#include "scene/resources/visual_shader.h"
#include "tests/test_macros.h"
namespace TestVisualArray {
TEST_CASE("[SceneTree][VisualShader] Object creation and parameter") {
Ref<VisualShader> vs = memnew(VisualShader);
CHECK(vs.is_valid());
CHECK(vs->get_mode() == Shader::MODE_SPATIAL);
for (int i = 1; i < Shader::MODE_MAX; i++) {
vs->set_mode((Shader::Mode)i);
CHECK(vs->get_mode() == i);
}
}
TEST_CASE("[SceneTree][VisualShader] Testing VisualShaderNodes") {
SUBCASE("Testing Node Creation") {
Ref<VisualShader> vs = memnew(VisualShader);
CHECK(vs.is_valid());
for (int i = 0; i < VisualShader::TYPE_MAX; i++) {
Ref<VisualShaderNode> vsn = memnew(VisualShaderNodeInput);
CHECK(vsn.is_valid());
vs->add_node(VisualShader::Type(i), vsn, Vector2(1, 10), i + 2);
CHECK(vs->get_node(VisualShader::Type(i), i + 2) == vsn);
}
ERR_PRINT_OFF;
// Testing for Invalid entries.
Ref<VisualShaderNode> vsn5 = memnew(VisualShaderNodeInput);
Ref<VisualShaderNode> vsn6 = memnew(VisualShaderNodeInput);
CHECK(vsn6.is_valid());
CHECK(vsn5.is_valid());
vs->add_node(VisualShader::TYPE_SKY, vsn5, Vector2(1, 10), 0);
CHECK_FALSE(vs->get_node(VisualShader::TYPE_SKY, 0) == vsn6);
vs->add_node(VisualShader::TYPE_MAX, vsn6, Vector2(1, 10), 7);
CHECK_FALSE(vs->get_node(VisualShader::TYPE_SKY, 7) == vsn6);
ERR_PRINT_ON;
}
SUBCASE("Testing VisualShaderNode position getter and setter") {
Ref<VisualShader> vs = memnew(VisualShader);
CHECK(vs.is_valid());
Ref<VisualShaderNode> vsn1 = memnew(VisualShaderNodeInput);
CHECK(vsn1.is_valid());
vs->add_node(VisualShader::TYPE_COLLIDE, vsn1, Vector2(0, 0), 3);
CHECK(vs->get_node_position(VisualShader::TYPE_COLLIDE, 3) == Vector2(0, 0));
vs->set_node_position(VisualShader::TYPE_COLLIDE, 3, Vector2(1, 1));
CHECK(vs->get_node_position(VisualShader::TYPE_COLLIDE, 3) == Vector2(1, 1));
Ref<VisualShaderNode> vsn2 = memnew(VisualShaderNodeInput);
CHECK(vsn2.is_valid());
vs->add_node(VisualShader::TYPE_FOG, vsn2, Vector2(1, 2), 4);
CHECK(vs->get_node_position(VisualShader::TYPE_FOG, 4) == Vector2(1, 2));
vs->set_node_position(VisualShader::TYPE_FOG, 4, Vector2(2, 2));
CHECK(vs->get_node_position(VisualShader::TYPE_FOG, 4) == Vector2(2, 2));
}
SUBCASE("Testing VisualShaderNode ID") {
Ref<VisualShader> vs = memnew(VisualShader);
CHECK(vs.is_valid());
for (int i = 0; i < VisualShader::TYPE_MAX; i++) {
Ref<VisualShaderNode> vsn = memnew(VisualShaderNodeInput);
CHECK(vsn.is_valid());
vs->add_node(VisualShader::Type(i), vsn, Vector2(1, 10), i + 2);
CHECK(vs->get_valid_node_id(VisualShader::Type(i)) - 1 == i + 2);
}
}
SUBCASE("Testing remove and replace VisualShaderNode") {
Ref<VisualShader> vs = memnew(VisualShader);
CHECK(vs.is_valid());
ERR_PRINT_OFF;
for (int i = 0; i < VisualShader::TYPE_MAX; i++) {
Ref<VisualShaderNode> vsn = memnew(VisualShaderNodeInput);
CHECK(vsn.is_valid());
vs->add_node(VisualShader::Type(i), vsn, Vector2(1, 10), i + 2);
CHECK(vs->get_node(VisualShader::Type(i), i + 2) == vsn);
vs->remove_node(VisualShader::Type(i), i + 2);
CHECK_FALSE(vs->get_node(VisualShader::Type(i), i + 2) == vsn);
}
ERR_PRINT_ON;
}
}
TEST_CASE("[SceneTree][VisualShader] Testing Varyings") {
Ref<VisualShader> vs = memnew(VisualShader);
vs->add_varying("Test1", VisualShader::VARYING_MODE_FRAG_TO_LIGHT, VisualShader::VARYING_TYPE_TRANSFORM);
CHECK(vs->has_varying("Test1") == true);
vs->add_varying("Test2", VisualShader::VARYING_MODE_VERTEX_TO_FRAG_LIGHT, VisualShader::VARYING_TYPE_VECTOR_2D);
CHECK(vs->has_varying("Test2"));
CHECK_FALSE(vs->has_varying("Does_not_exits"));
ERR_PRINT_OFF;
vs->add_varying("Test3", VisualShader::VARYING_MODE_MAX, VisualShader::VARYING_TYPE_INT);
CHECK_FALSE(vs->has_varying("Test3"));
vs->add_varying("Test4", VisualShader::VARYING_MODE_FRAG_TO_LIGHT, VisualShader::VARYING_TYPE_MAX);
CHECK_FALSE(vs->has_varying("Test4"));
ERR_PRINT_ON;
}
} //namespace TestVisualArray

93
tests/scene/test_window.h Normal file
View File

@@ -0,0 +1,93 @@
/**************************************************************************/
/* test_window.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. */
/**************************************************************************/
#pragma once
#include "scene/gui/control.h"
#include "scene/main/window.h"
#include "tests/test_macros.h"
namespace TestWindow {
class NotificationControlWindow : public Control {
GDCLASS(NotificationControlWindow, Control);
protected:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
mouse_over = true;
} break;
case NOTIFICATION_MOUSE_EXIT: {
mouse_over = false;
} break;
}
}
public:
bool mouse_over = false;
};
TEST_CASE("[SceneTree][Window]") {
Window *root = SceneTree::get_singleton()->get_root();
SUBCASE("Control-mouse-over within Window-black bars should not happen") {
Window *w = memnew(Window);
root->add_child(w);
w->set_size(Size2i(400, 200));
w->set_position(Size2i(0, 0));
w->set_content_scale_size(Size2i(200, 200));
w->set_content_scale_mode(Window::CONTENT_SCALE_MODE_CANVAS_ITEMS);
w->set_content_scale_aspect(Window::CONTENT_SCALE_ASPECT_KEEP);
NotificationControlWindow *c = memnew(NotificationControlWindow);
w->add_child(c);
c->set_size(Size2i(100, 100));
c->set_position(Size2i(-50, -50));
CHECK_FALSE(c->mouse_over);
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(110, 10), MouseButtonMask::NONE, Key::NONE);
CHECK(c->mouse_over);
SEND_GUI_MOUSE_MOTION_EVENT(Point2i(90, 10), MouseButtonMask::NONE, Key::NONE);
CHECK_FALSE(c->mouse_over); // GH-80011
/* TODO:
SEND_GUI_MOUSE_BUTTON_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
CHECK(Control was not pressed);
*/
memdelete(c);
memdelete(w);
}
}
} // namespace TestWindow