diff options
-rw-r--r-- | editor/editor_node.cpp | 3 | ||||
-rw-r--r-- | modules/enet/doc_classes/NetworkedMultiplayerENet.xml | 16 | ||||
-rw-r--r-- | modules/enet/networked_multiplayer_enet.cpp | 9 | ||||
-rw-r--r-- | modules/enet/networked_multiplayer_enet.h | 1 | ||||
-rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs | 27 | ||||
-rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj | 1 | ||||
-rw-r--r-- | platform/android/export/export.cpp | 10 | ||||
-rw-r--r-- | platform/android/export/gradle_export_util.h | 11 | ||||
-rw-r--r-- | platform/android/java/app/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | platform/android/java/app/config.gradle | 10 | ||||
-rw-r--r-- | platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java | 38 | ||||
-rw-r--r-- | tests/test_geometry_3d.h | 417 | ||||
-rw-r--r-- | tests/test_main.cpp | 1 | ||||
-rw-r--r-- | tests/test_path_follow_2d.h | 36 |
14 files changed, 506 insertions, 79 deletions
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4cf85a8caf..f0e53e7ef5 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -902,7 +902,8 @@ void EditorNode::_scan_external_changes() { // Check if any edited scene has changed. for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { - if (editor_data.get_scene_path(i) == "") { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (editor_data.get_scene_path(i) == "" || !da->file_exists(editor_data.get_scene_path(i))) { continue; } diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 6c713fa1ce..c8f32ffde6 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -124,6 +124,22 @@ Configure the [CryptoKey] to use when [member use_dtls] is [code]true[/code]. Remember to also call [method set_dtls_certificate] to setup your [X509Certificate]. </description> </method> + <method name="set_peer_timeout"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="timeout_limit" type="int"> + </argument> + <argument index="2" name="timeout_min" type="int"> + </argument> + <argument index="3" name="timeout_max" type="int"> + </argument> + <description> + Sets the timeout parameters for a peer. The timeout parameters control how and when a peer will timeout from a failure to acknowledge reliable traffic. Timeout values are expressed in milliseconds. + The [code]timeout_limit[/code] is a factor that, multiplied by a value based on the avarage round trip time, will determine the timeout limit for a reliable packet. When that limit is reached, the timeout will be doubled, and the peer will be disconnected if that limit has reached [code]timeout_min[/code]. The [code]timeout_max[/code] parameter, on the other hand, defines a fixed timeout for which any packet must be acknowledged or the peer will be dropped. + </description> + </method> </methods> <members> <member name="always_ordered" type="bool" setter="set_always_ordered" getter="is_always_ordered" default="false"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 91984b8928..276f13e553 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -784,6 +784,14 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { #endif } +void NetworkedMultiplayerENet::set_peer_timeout(int p_peer_id, int p_timeout_limit, int p_timeout_min, int p_timeout_max) { + ERR_FAIL_COND_MSG(!peer_map.has(p_peer_id), vformat("Peer ID %d not found in the list of peers.", p_peer_id)); + ERR_FAIL_COND_MSG(!is_server() && p_peer_id != 1, "Can't change the timeout of peers other then the server when acting as a client."); + ERR_FAIL_COND_MSG(peer_map[p_peer_id] == nullptr, vformat("Peer ID %d found in the list of peers, but is null.", p_peer_id)); + ERR_FAIL_COND_MSG(p_timeout_limit > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less then maximum timeout"); + enet_peer_timeout(peer_map[p_peer_id], p_timeout_limit, p_timeout_min, p_timeout_max); +} + void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { ERR_FAIL_COND_MSG(p_channel < -1 || p_channel >= channel_count, vformat("The transfer channel must be set between 0 and %d, inclusive (got %d).", channel_count - 1, p_channel)); ERR_FAIL_COND_MSG(p_channel == SYSCH_CONFIG, vformat("The channel %d is reserved.", SYSCH_CONFIG)); @@ -838,6 +846,7 @@ void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("is_dtls_verify_enabled"), &NetworkedMultiplayerENet::is_dtls_verify_enabled); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &NetworkedMultiplayerENet::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &NetworkedMultiplayerENet::get_peer_port); + ClassDB::bind_method(D_METHOD("set_peer_timeout", "id", "timeout_limit", "timeout_min", "timeout_max"), &NetworkedMultiplayerENet::set_peer_timeout); ClassDB::bind_method(D_METHOD("get_packet_channel"), &NetworkedMultiplayerENet::get_packet_channel); ClassDB::bind_method(D_METHOD("get_last_packet_channel"), &NetworkedMultiplayerENet::get_last_packet_channel); diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index eb70d71c2c..b99b14d218 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -127,6 +127,7 @@ public: virtual IP_Address get_peer_address(int p_peer_id) const; virtual int get_peer_port(int p_peer_id) const; + void set_peer_timeout(int p_peer_id, int p_timeout_limit, int p_timeout_min, int p_timeout_max); Error create_server(int p_port, int p_max_clients = 32, int p_in_bandwidth = 0, int p_out_bandwidth = 0); Error create_client(const String &p_address, int p_port, int p_in_bandwidth = 0, int p_out_bandwidth = 0, int p_client_port = 0); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs new file mode 100644 index 0000000000..763f470504 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -0,0 +1,27 @@ +namespace Godot +{ + public partial class PackedScene + { + /// <summary> + /// Instantiates the scene's node hierarchy, erroring on failure. + /// Triggers child scene instantiation(s). Triggers a + /// `Node.NotificationInstanced` notification on the root node. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + public T Instance<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + { + return (T)(object)Instance(editState); + } + + /// <summary> + /// Instantiates the scene's node hierarchy, returning null on failure. + /// Triggers child scene instantiation(s). Triggers a + /// `Node.NotificationInstanced` notification on the root node. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + public T InstanceOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + { + return Instance(editState) as T; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 86a16c17f1..7c1a23d510 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -30,6 +30,7 @@ <Compile Include="Core\DynamicObject.cs" /> <Compile Include="Core\Extensions\NodeExtensions.cs" /> <Compile Include="Core\Extensions\ObjectExtensions.cs" /> + <Compile Include="Core\Extensions\PackedSceneExtensions.cs" /> <Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" /> <Compile Include="Core\Extensions\SceneTreeExtensions.cs" /> <Compile Include="Core\GD.cs" /> diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index bca4a39bdd..326e513261 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -806,8 +806,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { manifest_text += _get_xr_features_tag(p_preset); manifest_text += _get_instrumentation_tag(p_preset); - String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); - manifest_text += _get_application_tag(p_preset, plugins_names); + manifest_text += _get_application_tag(p_preset); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -856,8 +855,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { int xr_mode_index = p_preset->get("xr_features/xr_mode"); bool focus_awareness = p_preset->get("xr_features/focus_awareness"); - String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); - Vector<String> perms; // Write permissions into the perms variable. _get_permissions(p_preset, p_give_internet, perms); @@ -995,11 +992,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } - if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.is_empty()) { - // Update the meta-data 'android:value' attribute with the list of enabled plugins. - string_table.write[attr_value] = plugins_names; - } - is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index ce6a3c96db..097a2391ee 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -269,14 +269,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_instrumentation_text; } -String _get_plugins_tag(const String &plugins_names) { - if (!plugins_names.is_empty()) { - return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names); - } else { - return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n"; - } -} - String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; String orientation = _get_android_orientation_label(_get_screen_orientation()); @@ -295,7 +287,7 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_activity_text; } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) { +String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; String manifest_application_text = " <application android:label=\"@string/godot_project_name_string\"\n" @@ -303,7 +295,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const Strin " android:icon=\"@mipmap/icon\">\n\n" " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; - manifest_application_text += _get_plugins_tag(plugins_names); if (uses_xr) { manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; } diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index cd2f1d367e..948fa8c00b 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -35,11 +35,6 @@ android:name="xr_mode_metadata_name" android:value="xr_mode_metadata_value" /> - <!-- Metadata populated at export time and used by Godot to figure out which plugins must be enabled. --> - <meta-data - android:name="plugins" - android:value="plugins_value"/> - <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 06d1f4064e..585e517631 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -99,7 +99,7 @@ ext.getGodotLibraryVersion = { -> return libraryVersion } -final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|" +final String VALUE_SEPARATOR_REGEX = "\\|" // get the list of ABIs the project should be exported to ext.getExportEnabledABIs = { -> @@ -108,7 +108,7 @@ ext.getExportEnabledABIs = { -> enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" } Set<String> exportAbiFilter = []; - for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) { if (!abi_name.trim().isEmpty()){ exportAbiFilter.add(abi_name); } @@ -143,7 +143,7 @@ ext.getGodotPluginsMavenRepos = { -> if (project.hasProperty("plugins_maven_repos")) { String mavenReposProperty = project.property("plugins_maven_repos") if (mavenReposProperty != null && !mavenReposProperty.trim().isEmpty()) { - for (String mavenRepoUrl : mavenReposProperty.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + for (String mavenRepoUrl : mavenReposProperty.split(VALUE_SEPARATOR_REGEX)) { mavenRepos += mavenRepoUrl.trim() } } @@ -163,7 +163,7 @@ ext.getGodotPluginsRemoteBinaries = { -> if (project.hasProperty("plugins_remote_binaries")) { String remoteDepsList = project.property("plugins_remote_binaries") if (remoteDepsList != null && !remoteDepsList.trim().isEmpty()) { - for (String dep: remoteDepsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + for (String dep: remoteDepsList.split(VALUE_SEPARATOR_REGEX)) { remoteDeps += dep.trim() } } @@ -182,7 +182,7 @@ ext.getGodotPluginsLocalBinaries = { -> if (project.hasProperty("plugins_local_binaries")) { String pluginsList = project.property("plugins_local_binaries") if (pluginsList != null && !pluginsList.trim().isEmpty()) { - for (String plugin : pluginsList.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { + for (String plugin : pluginsList.split(VALUE_SEPARATOR_REGEX)) { binDeps += plugin.trim() } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java index 99811f72ed..5b41205253 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -44,8 +44,6 @@ import androidx.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -56,13 +54,6 @@ public final class GodotPluginRegistry { private static final String GODOT_PLUGIN_V1_NAME_PREFIX = "org.godotengine.plugin.v1."; - /** - * Name for the metadata containing the list of Godot plugins to enable. - */ - private static final String GODOT_ENABLED_PLUGINS_LABEL = "plugins"; - - private static final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"; - private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; @@ -132,37 +123,11 @@ public final class GodotPluginRegistry { return; } - // When using the Godot editor for building and exporting the apk, this is used to check - // which plugins to enable. - // When using a custom process to generate the apk, the metadata is not needed since - // it's assumed that the developer is aware of the dependencies included in the apk. - final Set<String> enabledPluginsSet; - if (metaData.containsKey(GODOT_ENABLED_PLUGINS_LABEL)) { - String enabledPlugins = metaData.getString(GODOT_ENABLED_PLUGINS_LABEL, ""); - String[] enabledPluginsList = enabledPlugins.split(PLUGIN_VALUE_SEPARATOR_REGEX); - if (enabledPluginsList.length == 0) { - // No plugins to enable. Aborting early. - return; - } - - enabledPluginsSet = new HashSet<>(); - for (String enabledPlugin : enabledPluginsList) { - enabledPluginsSet.add(enabledPlugin.trim()); - } - } else { - enabledPluginsSet = null; - } - int godotPluginV1NamePrefixLength = GODOT_PLUGIN_V1_NAME_PREFIX.length(); for (String metaDataName : metaData.keySet()) { // Parse the meta-data looking for entry with the Godot plugin name prefix. if (metaDataName.startsWith(GODOT_PLUGIN_V1_NAME_PREFIX)) { String pluginName = metaDataName.substring(godotPluginV1NamePrefixLength).trim(); - if (enabledPluginsSet != null && !enabledPluginsSet.contains(pluginName)) { - Log.w(TAG, "Plugin " + pluginName + " is listed in the dependencies but is not enabled."); - continue; - } - Log.i(TAG, "Initializing Godot plugin " + pluginName); // Retrieve the plugin class full name. @@ -177,8 +142,7 @@ public final class GodotPluginRegistry { .getConstructor(Godot.class); GodotPlugin pluginHandle = pluginConstructor.newInstance(godot); - // Load the plugin initializer into the registry using the plugin name - // as key. + // Load the plugin initializer into the registry using the plugin name as key. if (!pluginName.equals(pluginHandle.getPluginName())) { Log.w(TAG, "Meta-data plugin name does not match the value returned by the plugin handle: " + pluginName + " =/= " + pluginHandle.getPluginName()); diff --git a/tests/test_geometry_3d.h b/tests/test_geometry_3d.h new file mode 100644 index 0000000000..2b2a424b2b --- /dev/null +++ b/tests/test_geometry_3d.h @@ -0,0 +1,417 @@ +/*************************************************************************/ +/* test_geometry_3d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_3D_GEOMETRY_H +#define TEST_3D_GEOMETRY_H + +#include "core/math/geometry_3d.h" +#include "core/math/plane.h" +#include "core/math/random_number_generator.h" +#include "core/math/vector3.h" +#include "tests/test_macros.h" +#include "vector" + +namespace Test3DGeometry { +TEST_CASE("[Geometry3D] Closest Points Between Segments") { + struct Case { + Vector3 p_1, p_2, p_3, p_4; + Vector3 got_1, got_2; + Vector3 want_1, want_2; + Case(){}; + Case(Vector3 p_p_1, Vector3 p_p_2, Vector3 p_p_3, Vector3 p_p_4, Vector3 p_want_1, Vector3 p_want_2) : + p_1(p_p_1), p_2(p_p_2), p_3(p_p_3), p_4(p_p_4), want_1(p_want_1), want_2(p_want_2){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(1, -1, 1), Vector3(1, 1, -1), Vector3(-1, -2, -1), Vector3(-1, 1, 1), Vector3(1, -0.2, 0.2), Vector3(-1, -0.2, 0.2))); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Geometry3D::get_closest_points_between_segments(current_case.p_1, current_case.p_2, current_case.p_3, current_case.p_4, current_case.got_1, current_case.got_2); + CHECK(current_case.got_1.is_equal_approx(current_case.want_1)); + CHECK(current_case.got_2.is_equal_approx(current_case.want_2)); + } +} +TEST_CASE("[Geometry3D] Closest Distance Between Segments") { + struct Case { + Vector3 p_1, p_2, p_3, p_4; + float want; + Case(){}; + Case(Vector3 p_p_1, Vector3 p_p_2, Vector3 p_p_3, Vector3 p_p_4, float p_want) : + p_1(p_p_1), p_2(p_p_2), p_3(p_p_3), p_4(p_p_4), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(1, -2, 0), Vector3(1, 2, 0), Vector3(-1, 2, 0), Vector3(-1, -2, 0), 0.0f)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + float out = Geometry3D::get_closest_distance_between_segments(current_case.p_1, current_case.p_2, current_case.p_3, current_case.p_4); + CHECK(out == current_case.want); + } +} +TEST_CASE("[Geometry3D] Build Box Planes") { + const Vector3 extents = Vector3(5, 5, 20); + Vector<Plane> box = Geometry3D::build_box_planes(extents); + CHECK(box.size() == 6); + CHECK(extents.x == box[0].d); + CHECK(box[0].normal == Vector3(1, 0, 0)); + CHECK(extents.x == box[1].d); + CHECK(box[1].normal == Vector3(-1, 0, 0)); + CHECK(extents.y == box[2].d); + CHECK(box[2].normal == Vector3(0, 1, 0)); + CHECK(extents.y == box[3].d); + CHECK(box[3].normal == Vector3(0, -1, 0)); + CHECK(extents.z == box[4].d); + CHECK(box[4].normal == Vector3(0, 0, 1)); + CHECK(extents.z == box[5].d); + CHECK(box[5].normal == Vector3(0, 0, -1)); +} +TEST_CASE("[Geometry3D] Build Capsule Planes") { + struct Case { + real_t radius, height; + int sides, lats; + Vector3::Axis axis; + int want_size; + Case(){}; + Case(real_t p_radius, real_t p_height, int p_sides, int p_lats, Vector3::Axis p_axis, int p_want) : + radius(p_radius), height(p_height), sides(p_sides), lats(p_lats), axis(p_axis), want_size(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(10, 20, 6, 10, Vector3::Axis(), 126)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector<Plane> capsule = Geometry3D::build_capsule_planes(current_case.radius, current_case.height, current_case.sides, current_case.lats, current_case.axis); + // Should equal (p_sides * p_lats) * 2 + p_sides + CHECK(capsule.size() == current_case.want_size); + } +} +TEST_CASE("[Geometry3D] Build Cylinder Planes") { + struct Case { + real_t radius, height; + int sides; + Vector3::Axis axis; + int want_size; + Case(){}; + Case(real_t p_radius, real_t p_height, int p_sides, Vector3::Axis p_axis, int p_want) : + radius(p_radius), height(p_height), sides(p_sides), axis(p_axis), want_size(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(3.0f, 10.0f, 10, Vector3::Axis(), 12)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector<Plane> planes = Geometry3D::build_cylinder_planes(current_case.radius, current_case.height, current_case.sides, current_case.axis); + CHECK(planes.size() == current_case.want_size); + } +} +TEST_CASE("[Geometry3D] Build Sphere Planes") { + struct Case { + real_t radius; + int lats, lons; + Vector3::Axis axis; + int want_size; + Case(){}; + Case(real_t p_radius, int p_lat, int p_lons, Vector3::Axis p_axis, int p_want) : + radius(p_radius), lats(p_lat), lons(p_lons), axis(p_axis), want_size(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(10.0f, 10, 3, Vector3::Axis(), 63)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector<Plane> planes = Geometry3D::build_sphere_planes(current_case.radius, current_case.lats, current_case.lons, current_case.axis); + CHECK(planes.size() == 63); + } +} +TEST_CASE("[Geometry3D] Build Convex Mesh") { + struct Case { + Vector<Plane> object; + int want_faces, want_edges, want_vertices; + Case(){}; + Case(Vector<Plane> p_object, int p_want_faces, int p_want_edges, int p_want_vertices) : + object(p_object), want_faces(p_want_faces), want_edges(p_want_edges), want_vertices(p_want_vertices){}; + }; + Vector<Case> tt; + tt.push_back(Case(Geometry3D::build_box_planes(Vector3(5, 10, 5)), 6, 12, 8)); + tt.push_back(Case(Geometry3D::build_capsule_planes(5, 5, 20, 20, Vector3::Axis()), 820, 7603, 6243)); + tt.push_back(Case(Geometry3D::build_cylinder_planes(5, 5, 20, Vector3::Axis()), 22, 100, 80)); + tt.push_back(Case(Geometry3D::build_sphere_planes(5, 5, 20), 220, 1011, 522)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Geometry3D::MeshData mesh = Geometry3D::build_convex_mesh(current_case.object); + CHECK(mesh.faces.size() == current_case.want_faces); + CHECK(mesh.edges.size() == current_case.want_edges); + CHECK(mesh.vertices.size() == current_case.want_vertices); + } +} +TEST_CASE("[Geometry3D] Clip Polygon") { + struct Case { + Plane clipping_plane; + Vector<Vector3> polygon; + bool want; + Case(){}; + Case(Plane p_clipping_plane, Vector<Vector3> p_polygon, bool p_want) : + clipping_plane(p_clipping_plane), polygon(p_polygon), want(p_want){}; + }; + Vector<Case> tt; + Vector<Plane> box_planes = Geometry3D::build_box_planes(Vector3(5, 10, 5)); + Vector<Vector3> box = Geometry3D::compute_convex_mesh_points(&box_planes[0], box_planes.size()); + tt.push_back(Case(Plane(), box, true)); + tt.push_back(Case(Plane(Vector3(0, 3, 0), Vector3(0, 1, 0)), box, false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector<Vector3> output = Geometry3D::clip_polygon(current_case.polygon, current_case.clipping_plane); + if (current_case.want) { + CHECK(output == current_case.polygon); + } else { + CHECK(output != current_case.polygon); + } + } +} +TEST_CASE("[Geometry3D] Compute Convex Mesh Points") { + struct Case { + Vector<Plane> mesh; + Vector<Vector3> want; + Case(){}; + Case(Vector<Plane> p_mesh, Vector<Vector3> p_want) : + mesh(p_mesh), want(p_want){}; + }; + Vector<Case> tt; + Vector<Vector3> cube; + cube.push_back(Vector3(-5, -5, -5)); + cube.push_back(Vector3(5, -5, -5)); + cube.push_back(Vector3(-5, 5, -5)); + cube.push_back(Vector3(5, 5, -5)); + cube.push_back(Vector3(-5, -5, 5)); + cube.push_back(Vector3(5, -5, 5)); + cube.push_back(Vector3(-5, 5, 5)); + cube.push_back(Vector3(5, 5, 5)); + tt.push_back(Case(Geometry3D::build_box_planes(Vector3(5, 5, 5)), cube)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector<Vector3> vectors = Geometry3D::compute_convex_mesh_points(¤t_case.mesh[0], current_case.mesh.size()); + CHECK(vectors == current_case.want); + } +} +TEST_CASE("[Geometry3D] Get Closest Point To Segment") { + struct Case { + Vector3 point; + Vector<Vector3> segment; + Vector3 want; + Case(){}; + Case(Vector3 p_point, Vector<Vector3> p_segment, Vector3 p_want) : + point(p_point), segment(p_segment), want(p_want){}; + }; + Vector<Case> tt; + Vector<Vector3> test_segment; + test_segment.push_back(Vector3(1, 1, 1)); + test_segment.push_back(Vector3(5, 5, 5)); + tt.push_back(Case(Vector3(2, 1, 4), test_segment, Vector3(2.33333, 2.33333, 2.33333))); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + Vector3 output = Geometry3D::get_closest_point_to_segment(current_case.point, ¤t_case.segment[0]); + CHECK(output.is_equal_approx(current_case.want)); + } +} +TEST_CASE("[Geometry3D] Plane and Box Overlap") { + struct Case { + Vector3 normal, max_box; + float d; + bool want; + Case(){}; + Case(Vector3 p_normal, float p_d, Vector3 p_max_box, bool p_want) : + normal(p_normal), max_box(p_max_box), d(p_d), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(3, 4, 2), 5, Vector3(5, 5, 5), true)); + tt.push_back(Case(Vector3(0, 1, 0), -10, Vector3(5, 5, 5), false)); + tt.push_back(Case(Vector3(1, 0, 0), -6, Vector3(5, 5, 5), false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool overlap = Geometry3D::planeBoxOverlap(current_case.normal, current_case.d, current_case.max_box); + CHECK(overlap == current_case.want); + } +} +TEST_CASE("[Geometry3D] Is Point in Projected Triangle") { + struct Case { + Vector3 point, v_1, v_2, v_3; + bool want; + Case(){}; + Case(Vector3 p_point, Vector3 p_v_1, Vector3 p_v_2, Vector3 p_v_3, bool p_want) : + point(p_point), v_1(p_v_1), v_2(p_v_2), v_3(p_v_3), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(1, 1, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0), true)); + tt.push_back(Case(Vector3(5, 1, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0), false)); + tt.push_back(Case(Vector3(3, 0, 0), Vector3(3, 0, 0), Vector3(0, 3, 0), Vector3(-3, 0, 0), true)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::point_in_projected_triangle(current_case.point, current_case.v_1, current_case.v_2, current_case.v_3); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Does Ray Intersect Triangle") { + struct Case { + Vector3 from, direction, v_1, v_2, v_3; + Vector3 *result; + bool want; + Case(){}; + Case(Vector3 p_from, Vector3 p_direction, Vector3 p_v_1, Vector3 p_v_2, Vector3 p_v_3, bool p_want) : + from(p_from), direction(p_direction), v_1(p_v_1), v_2(p_v_2), v_3(p_v_3), result(nullptr), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(0, 1, 1), Vector3(0, 0, -10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), true)); + tt.push_back(Case(Vector3(5, 10, 1), Vector3(0, 0, -10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), false)); + tt.push_back(Case(Vector3(0, 1, 1), Vector3(0, 0, 10), Vector3(0, 3, 0), Vector3(-3, 0, 0), Vector3(3, 0, 0), false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::ray_intersects_triangle(current_case.from, current_case.direction, current_case.v_1, current_case.v_2, current_case.v_3, current_case.result); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Does Segment Intersect Convex") { + struct Case { + Vector3 from, to; + Vector<Plane> planes; + Vector3 *result, *normal; + bool want; + Case(){}; + Case(Vector3 p_from, Vector3 p_to, Vector<Plane> p_planes, bool p_want) : + from(p_from), to(p_to), planes(p_planes), result(nullptr), normal(nullptr), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(10, 10, 10), Vector3(0, 0, 0), Geometry3D::build_box_planes(Vector3(5, 5, 5)), true)); + tt.push_back(Case(Vector3(10, 10, 10), Vector3(5, 5, 5), Geometry3D::build_box_planes(Vector3(5, 5, 5)), true)); + tt.push_back(Case(Vector3(10, 10, 10), Vector3(6, 5, 5), Geometry3D::build_box_planes(Vector3(5, 5, 5)), false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::segment_intersects_convex(current_case.from, current_case.to, ¤t_case.planes[0], current_case.planes.size(), current_case.result, current_case.normal); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { + struct Case { + Vector3 from, to; + real_t height, radius; + Vector3 *result, *normal; + bool want; + Case(){}; + Case(Vector3 p_from, Vector3 p_to, real_t p_height, real_t p_radius, bool p_want) : + from(p_from), to(p_to), height(p_height), radius(p_radius), result(nullptr), normal(nullptr), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(10, 10, 10), Vector3(0, 0, 0), 5, 5, true)); + tt.push_back(Case(Vector3(10, 10, 10), Vector3(6, 6, 6), 5, 5, false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::segment_intersects_cylinder(current_case.from, current_case.to, current_case.height, current_case.radius, current_case.result, current_case.normal); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Segment Intersects Cylinder") { + struct Case { + Vector3 from, to, sphere_pos; + real_t radius; + Vector3 *result, *normal; + bool want; + Case(){}; + Case(Vector3 p_from, Vector3 p_to, Vector3 p_sphere_pos, real_t p_radius, bool p_want) : + from(p_from), to(p_to), sphere_pos(p_sphere_pos), radius(p_radius), result(nullptr), normal(nullptr), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(10, 10, 10), Vector3(0, 0, 0), Vector3(0, 0, 0), 5, true)); + tt.push_back(Case(Vector3(10, 10, 10), Vector3(0, 0, 2.5), Vector3(0, 0, 0), 5, true)); + tt.push_back(Case(Vector3(10, 10, 10), Vector3(5, 5, 5), Vector3(0, 0, 0), 5, false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::segment_intersects_sphere(current_case.from, current_case.to, current_case.sphere_pos, current_case.radius, current_case.result, current_case.normal); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Segment Intersects Triangle") { + struct Case { + Vector3 from, to, v_1, v_2, v_3, *result; + bool want; + Case(){}; + Case(Vector3 p_from, Vector3 p_to, Vector3 p_v_1, Vector3 p_v_2, Vector3 p_v_3, bool p_want) : + from(p_from), to(p_to), v_1(p_v_1), v_2(p_v_2), v_3(p_v_3), result(nullptr), want(p_want){}; + }; + Vector<Case> tt; + tt.push_back(Case(Vector3(1, 1, 1), Vector3(-1, -1, -1), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), true)); + tt.push_back(Case(Vector3(1, 1, 1), Vector3(3, 0, 0), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), true)); + tt.push_back(Case(Vector3(1, 1, 1), Vector3(10, -1, -1), Vector3(-3, 0, 0), Vector3(0, 3, 0), Vector3(3, 0, 0), false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::segment_intersects_triangle(current_case.from, current_case.to, current_case.v_1, current_case.v_2, current_case.v_3, current_case.result); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Triangle and Box Overlap") { + struct Case { + Vector3 box_centre; + Vector3 box_half_size; + Vector3 *tri_verts; + bool want; + Case(){}; + Case(Vector3 p_centre, Vector3 p_half_size, Vector3 *p_verts, bool p_want) : + box_centre(p_centre), box_half_size(p_half_size), tri_verts(p_verts), want(p_want){}; + }; + Vector<Case> tt; + Vector3 GoodTriangle[3] = { Vector3(3, 2, 3), Vector3(2, 2, 1), Vector3(2, 1, 1) }; + tt.push_back(Case(Vector3(0, 0, 0), Vector3(5, 5, 5), GoodTriangle, true)); + Vector3 BadTriangle[3] = { Vector3(100, 100, 100), Vector3(-100, -100, -100), Vector3(10, 10, 10) }; + tt.push_back(Case(Vector3(1000, 1000, 1000), Vector3(1, 1, 1), BadTriangle, false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::triangle_box_overlap(current_case.box_centre, current_case.box_half_size, current_case.tri_verts); + CHECK(output == current_case.want); + } +} +TEST_CASE("[Geometry3D] Triangle and Sphere Intersect") { + struct Case { + Vector<Vector3> triangle; + Vector3 normal, sphere_pos, triangle_contact, sphere_contact; + real_t sphere_radius; + bool want; + Case(){}; + Case(Vector<Vector3> p_triangle, Vector3 p_normal, Vector3 p_sphere_pos, real_t p_sphere_radius, bool p_want) : + triangle(p_triangle), normal(p_normal), sphere_pos(p_sphere_pos), triangle_contact(Vector3()), sphere_contact(Vector3()), sphere_radius(p_sphere_radius), want(p_want){}; + }; + Vector<Case> tt; + Vector<Vector3> triangle; + triangle.push_back(Vector3(3, 0, 0)); + triangle.push_back(Vector3(-3, 0, 0)); + triangle.push_back(Vector3(0, 3, 0)); + tt.push_back(Case(triangle, Vector3(0, -1, 0), Vector3(0, 0, 0), 5, true)); + tt.push_back(Case(triangle, Vector3(0, 1, 0), Vector3(0, 0, 0), 5, true)); + tt.push_back(Case(triangle, Vector3(0, 1, 0), Vector3(20, 0, 0), 5, false)); + for (int i = 0; i < tt.size(); ++i) { + Case current_case = tt[i]; + bool output = Geometry3D::triangle_sphere_intersection_test(¤t_case.triangle[0], current_case.normal, current_case.sphere_pos, current_case.sphere_radius, current_case.triangle_contact, current_case.sphere_contact); + CHECK(output == current_case.want); + } +} +} // namespace Test3DGeometry +#endif diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 9d9d5a66db..7e9f8319a0 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -45,6 +45,7 @@ #include "test_expression.h" #include "test_file_access.h" #include "test_geometry_2d.h" +#include "test_geometry_3d.h" #include "test_gradient.h" #include "test_gui.h" #include "test_image.h" diff --git a/tests/test_path_follow_2d.h b/tests/test_path_follow_2d.h index 28b62de5bb..388b690060 100644 --- a/tests/test_path_follow_2d.h +++ b/tests/test_path_follow_2d.h @@ -45,9 +45,9 @@ TEST_CASE("[PathFollow2D] Sampling with unit offset") { curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_unit_offset(0); @@ -76,6 +76,8 @@ TEST_CASE("[PathFollow2D] Sampling with unit offset") { path_follow_2d->set_unit_offset(1); CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + + memdelete(path); } TEST_CASE("[PathFollow2D] Sampling with offset") { @@ -85,9 +87,9 @@ TEST_CASE("[PathFollow2D] Sampling with offset") { curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_offset(0); @@ -116,6 +118,8 @@ TEST_CASE("[PathFollow2D] Sampling with offset") { path_follow_2d->set_offset(400); CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + + memdelete(path); } TEST_CASE("[PathFollow2D] Removal of a point in curve") { @@ -123,9 +127,9 @@ TEST_CASE("[PathFollow2D] Removal of a point in curve") { curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_unit_offset(0.5); @@ -136,15 +140,17 @@ TEST_CASE("[PathFollow2D] Removal of a point in curve") { CHECK_MESSAGE( path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 50)), "Path follow's position should be updated after removing a point from the curve"); + + memdelete(path); } TEST_CASE("[PathFollow2D] Setting h_offset and v_offset") { const Ref<Curve2D> &curve = memnew(Curve2D()); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_unit_offset(0.5); @@ -155,15 +161,17 @@ TEST_CASE("[PathFollow2D] Setting h_offset and v_offset") { path_follow_2d->set_v_offset(25); CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(75, 25))); + + memdelete(path); } TEST_CASE("[PathFollow2D] Unit offset out of range") { const Ref<Curve2D> &curve = memnew(Curve2D()); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_loop(true); @@ -189,15 +197,17 @@ TEST_CASE("[PathFollow2D] Unit offset out of range") { CHECK_MESSAGE( path_follow_2d->get_unit_offset() == 1, "Unit Offset should be clamped at 1"); + + memdelete(path); } TEST_CASE("[PathFollow2D] Offset out of range") { const Ref<Curve2D> &curve = memnew(Curve2D()); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Ref<Path2D> &path = memnew(Path2D()); + const Path2D *path = memnew(Path2D); path->set_curve(curve); - const Ref<PathFollow2D> &path_follow_2d = memnew(PathFollow2D()); + const PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); path_follow_2d->set_loop(true); @@ -223,6 +233,8 @@ TEST_CASE("[PathFollow2D] Offset out of range") { CHECK_MESSAGE( path_follow_2d->get_offset() == 100, "Offset should be clamped at 1"); + + memdelete(path); } } // namespace TestPathFollow2D |