summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/basis_universal/SCsub4
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp221
-rw-r--r--modules/basis_universal/image_compress_basisu.h9
-rw-r--r--modules/basis_universal/patches/external-jpgd.patch13
-rw-r--r--modules/bcdec/image_decompress_bcdec.cpp148
-rw-r--r--modules/betsy/SCsub1
-rw-r--r--modules/betsy/bc4.glsl151
-rw-r--r--modules/betsy/image_compress_betsy.cpp90
-rw-r--r--modules/betsy/image_compress_betsy.h11
-rw-r--r--modules/camera/camera_feed_linux.cpp2
-rw-r--r--modules/camera/camera_linux.cpp2
-rw-r--r--modules/camera/camera_macos.mm14
-rw-r--r--modules/camera/camera_win.cpp6
-rw-r--r--modules/csg/editor/csg_gizmos.cpp56
-rw-r--r--modules/dds/texture_loader_dds.cpp522
-rw-r--r--modules/enet/enet_connection.cpp2
-rw-r--r--modules/fbx/fbx_document.cpp18
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml20
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml3
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp4
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp8
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.cpp94
-rw-r--r--modules/gdscript/editor/gdscript_translation_parser_plugin.h15
-rw-r--r--modules/gdscript/gdscript.cpp53
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp77
-rw-r--r--modules/gdscript/gdscript_cache.cpp3
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp6
-rw-r--r--modules/gdscript/gdscript_editor.cpp52
-rw-r--r--modules/gdscript/gdscript_parser.cpp22
-rw-r--r--modules/gdscript/gdscript_parser.h6
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.h2
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp492
-rw-r--r--modules/gdscript/gdscript_vm.cpp22
-rw-r--r--modules/gdscript/gdscript_warning.cpp7
-rw-r--r--modules/gdscript/gdscript_warning.h4
-rw-r--r--modules/gdscript/tests/scripts/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out32
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn13
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg6
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.out26
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.out22
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd1
-rw-r--r--modules/gltf/config.py1
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml38
-rw-r--r--modules/gltf/doc_classes/GLTFAnimation.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFBufferView.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFCamera.xml6
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml20
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml29
-rw-r--r--modules/gltf/doc_classes/GLTFMesh.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml11
-rw-r--r--modules/gltf/doc_classes/GLTFObjectModelProperty.xml114
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml10
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_settings.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp8
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp18
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h4
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp1
-rw-r--r--modules/gltf/extensions/gltf_light.cpp16
-rw-r--r--modules/gltf/extensions/gltf_light.h3
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp278
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.h3
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp3
-rw-r--r--modules/gltf/gltf_defines.h1
-rw-r--r--modules/gltf/gltf_document.cpp2324
-rw-r--r--modules/gltf/gltf_document.h53
-rw-r--r--modules/gltf/gltf_state.cpp24
-rw-r--r--modules/gltf/gltf_state.h13
-rw-r--r--modules/gltf/register_types.cpp2
-rw-r--r--modules/gltf/structures/gltf_accessor.cpp17
-rw-r--r--modules/gltf/structures/gltf_accessor.h20
-rw-r--r--modules/gltf/structures/gltf_animation.cpp40
-rw-r--r--modules/gltf/structures/gltf_animation.h26
-rw-r--r--modules/gltf/structures/gltf_camera.cpp16
-rw-r--r--modules/gltf/structures/gltf_camera.h3
-rw-r--r--modules/gltf/structures/gltf_node.cpp45
-rw-r--r--modules/gltf/structures/gltf_node.h3
-rw-r--r--modules/gltf/structures/gltf_object_model_property.cpp173
-rw-r--r--modules/gltf/structures/gltf_object_model_property.h104
-rw-r--r--modules/gltf/tests/test_gltf_extras.h2
-rw-r--r--modules/godot_physics_2d/godot_joints_2d.cpp2
-rw-r--r--modules/godot_physics_2d/godot_joints_2d.h2
-rw-r--r--modules/godot_physics_2d/godot_physics_server_2d.cpp44
-rw-r--r--modules/godot_physics_2d/godot_space_2d.cpp4
-rw-r--r--modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp11
-rw-r--r--modules/godot_physics_3d/godot_physics_server_3d.cpp44
-rw-r--r--modules/godot_physics_3d/godot_shape_3d.cpp9
-rw-r--r--modules/godot_physics_3d/godot_soft_body_3d.cpp4
-rw-r--r--modules/godot_physics_3d/godot_space_3d.cpp4
-rw-r--r--modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp2
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp597
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h38
-rw-r--r--modules/gridmap/grid_map.cpp8
-rw-r--r--modules/interactive_music/doc_classes/AudioStreamInteractive.xml2
-rw-r--r--modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp2
-rw-r--r--modules/mbedtls/crypto_mbedtls.h2
-rw-r--r--modules/mbedtls/stream_peer_mbedtls.cpp62
-rw-r--r--modules/meshoptimizer/register_types.cpp4
-rw-r--r--modules/minimp3/doc_classes/ResourceImporterMP3.xml6
-rw-r--r--modules/minimp3/resource_importer_mp3.cpp2
-rw-r--r--modules/minimp3/resource_importer_mp3.h4
-rw-r--r--modules/mobile_vr/mobile_vr_interface.cpp54
-rw-r--r--modules/mobile_vr/mobile_vr_interface.h8
-rw-r--r--modules/mono/csharp_script.cpp8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs13
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs23
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs38
-rw-r--r--modules/mono/editor/bindings_generator.cpp207
-rw-r--r--modules/mono/editor/bindings_generator.h3
-rw-r--r--modules/mono/editor/code_completion.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs68
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs10
-rw-r--r--modules/mono/utils/path_utils.cpp2
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml4
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp10
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp8
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp10
-rw-r--r--modules/multiplayer/multiplayer_spawner.h1
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp6
-rw-r--r--modules/multiplayer/scene_multiplayer.h4
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp12
-rw-r--r--modules/multiplayer/scene_rpc_interface.h2
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp184
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.cpp21
-rw-r--r--modules/navigation/3d/nav_mesh_queries_3d.cpp2
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp4
-rw-r--r--modules/navigation/nav_agent.cpp2
-rw-r--r--modules/navigation/nav_agent.h8
-rw-r--r--modules/navigation/nav_link.cpp2
-rw-r--r--modules/navigation/nav_map.cpp72
-rw-r--r--modules/navigation/nav_map.h8
-rw-r--r--modules/navigation/nav_obstacle.h2
-rw-r--r--modules/navigation/nav_region.cpp2
-rw-r--r--modules/navigation/nav_region.h2
-rw-r--r--modules/navigation/nav_utils.h2
-rw-r--r--modules/noise/doc_classes/FastNoiseLite.xml6
-rw-r--r--modules/noise/tests/test_noise_texture_2d.h2
-rw-r--r--modules/noise/tests/test_noise_texture_3d.h2
-rw-r--r--modules/openxr/SCsub2
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp23
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.cpp123
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.h32
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayer.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml17
-rw-r--r--modules/openxr/doc_classes/OpenXRIPBinding.xml18
-rw-r--r--modules/openxr/editor/openxr_action_editor.cpp2
-rw-r--r--modules/openxr/editor/openxr_action_editor.h2
-rw-r--r--modules/openxr/editor/openxr_action_set_editor.cpp8
-rw-r--r--modules/openxr/editor/openxr_action_set_editor.h2
-rw-r--r--modules/openxr/editor/openxr_interaction_profile_editor.cpp45
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_debug_utils_extension.cpp4
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.cpp13
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper_extension.h4
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.cpp39
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.h11
-rw-r--r--modules/openxr/openxr_api.cpp12
-rw-r--r--modules/openxr/openxr_api.h12
-rw-r--r--modules/openxr/openxr_interface.cpp21
-rw-r--r--modules/openxr/openxr_interface.h3
-rw-r--r--modules/openxr/openxr_platform_inc.h7
-rw-r--r--modules/openxr/scene/openxr_composition_layer.cpp4
-rw-r--r--modules/raycast/raycast_occlusion_cull.cpp31
-rw-r--r--modules/regex/doc_classes/RegEx.xml4
-rw-r--r--modules/svg/image_loader_svg.cpp28
-rw-r--r--modules/text_server_adv/SCsub2
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp62
-rw-r--r--modules/text_server_adv/text_server_adv.h9
-rw-r--r--modules/text_server_fb/text_server_fb.cpp70
-rw-r--r--modules/tga/image_loader_tga.cpp1
-rw-r--r--modules/theora/video_stream_theora.cpp7
-rw-r--r--modules/tinyexr/image_saver_tinyexr.cpp1
-rw-r--r--modules/tinyexr/image_saver_tinyexr.h2
-rw-r--r--modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml6
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp2
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.h4
-rw-r--r--modules/webp/webp_common.cpp2
-rw-r--r--modules/webrtc/webrtc_peer_connection_js.cpp2
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml2
-rw-r--r--modules/websocket/doc_classes/WebSocketPeer.xml6
-rw-r--r--modules/websocket/emws_peer.h2
-rw-r--r--modules/websocket/packet_buffer.h8
-rw-r--r--modules/websocket/websocket_peer.cpp14
-rw-r--r--modules/websocket/websocket_peer.h6
-rw-r--r--modules/websocket/wsl_peer.cpp123
-rw-r--r--modules/websocket/wsl_peer.h19
-rw-r--r--modules/webxr/webxr_interface_js.cpp26
233 files changed, 5910 insertions, 2575 deletions
diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub
index 9bea0a0ca9..e201452385 100644
--- a/modules/basis_universal/SCsub
+++ b/modules/basis_universal/SCsub
@@ -14,6 +14,8 @@ thirdparty_obj = []
thirdparty_dir = "#thirdparty/basis_universal/"
# Sync list with upstream CMakeLists.txt
encoder_sources = [
+ "3rdparty/android_astc_decomp.cpp",
+ "basisu_astc_hdr_enc.cpp",
"basisu_backend.cpp",
"basisu_basis_file.cpp",
"basisu_bc7enc.cpp",
@@ -45,6 +47,8 @@ else:
if env["builtin_zstd"]:
env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"])
+env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"])
+
if env.dev_build:
env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)])
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index ab20d00b5b..be28d89508 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -30,25 +30,73 @@
#include "image_compress_basisu.h"
+#include "core/io/image.h"
+#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "servers/rendering_server.h"
#include <transcoder/basisu_transcoder.h>
#ifdef TOOLS_ENABLED
#include <encoder/basisu_comp.h>
-#endif
-void basis_universal_init() {
-#ifdef TOOLS_ENABLED
- basisu::basisu_encoder_init();
+static Mutex init_mutex;
+static bool initialized = false;
#endif
+void basis_universal_init() {
basist::basisu_transcoder_init();
}
#ifdef TOOLS_ENABLED
+template <typename T>
+inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t> &r_mip_data_padded, int p_next_width, int p_next_height, int p_width, int p_height, int64_t p_size) {
+ // Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
+ const T *mip_src_data = reinterpret_cast<const T *>(p_image_mip_data);
+
+ // Reserve space in the padded buffer.
+ r_mip_data_padded.resize(p_next_width * p_next_height * sizeof(T));
+ T *data_padded_ptr = reinterpret_cast<T *>(r_mip_data_padded.ptrw());
+
+ // Pad mipmap to the nearest block by smearing.
+ int x = 0, y = 0;
+ for (y = 0; y < p_height; y++) {
+ for (x = 0; x < p_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = mip_src_data[p_width * y + x];
+ }
+
+ // First, smear in x.
+ for (; x < p_next_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - 1];
+ }
+ }
+
+ // Then, smear in y.
+ for (; y < p_next_height; y++) {
+ for (x = 0; x < p_next_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - p_next_width];
+ }
+ }
+}
+
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
+ init_mutex.lock();
+ if (!initialized) {
+ basisu::basisu_encoder_init();
+ initialized = true;
+ }
+ init_mutex.unlock();
+
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
Ref<Image> image = p_image->duplicate();
- image->convert(Image::FORMAT_RGBA8);
+ bool is_hdr = false;
+
+ if (image->get_format() <= Image::FORMAT_RGB565) {
+ image->convert(Image::FORMAT_RGBA8);
+ } else if (image->get_format() <= Image::FORMAT_RGBE9995) {
+ image->convert(Image::FORMAT_RGBAF);
+ is_hdr = true;
+ }
basisu::basis_compressor_params params;
@@ -74,32 +122,42 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
params.m_pJob_pool = &job_pool;
- BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG;
- switch (p_channels) {
- case Image::USED_CHANNELS_L: {
- decompress_format = BASIS_DECOMPRESS_RGB;
- } break;
- case Image::USED_CHANNELS_LA: {
- params.m_force_alpha = true;
- decompress_format = BASIS_DECOMPRESS_RGBA;
- } break;
- case Image::USED_CHANNELS_R: {
- decompress_format = BASIS_DECOMPRESS_R;
- } break;
- case Image::USED_CHANNELS_RG: {
- params.m_force_alpha = true;
- image->convert_rg_to_ra_rgba8();
- decompress_format = BASIS_DECOMPRESS_RG;
- } break;
- case Image::USED_CHANNELS_RGB: {
- decompress_format = BASIS_DECOMPRESS_RGB;
- } break;
- case Image::USED_CHANNELS_RGBA: {
- params.m_force_alpha = true;
- decompress_format = BASIS_DECOMPRESS_RGBA;
- } break;
+ BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;
+
+ if (is_hdr) {
+ decompress_format = BASIS_DECOMPRESS_HDR_RGB;
+ params.m_hdr = true;
+ params.m_uastc_hdr_options.set_quality_level(0);
+
+ } else {
+ switch (p_channels) {
+ case Image::USED_CHANNELS_L: {
+ decompress_format = BASIS_DECOMPRESS_RGB;
+ } break;
+ case Image::USED_CHANNELS_LA: {
+ params.m_force_alpha = true;
+ decompress_format = BASIS_DECOMPRESS_RGBA;
+ } break;
+ case Image::USED_CHANNELS_R: {
+ decompress_format = BASIS_DECOMPRESS_R;
+ } break;
+ case Image::USED_CHANNELS_RG: {
+ params.m_force_alpha = true;
+ image->convert_rg_to_ra_rgba8();
+ decompress_format = BASIS_DECOMPRESS_RG;
+ } break;
+ case Image::USED_CHANNELS_RGB: {
+ decompress_format = BASIS_DECOMPRESS_RGB;
+ } break;
+ case Image::USED_CHANNELS_RGBA: {
+ params.m_force_alpha = true;
+ decompress_format = BASIS_DECOMPRESS_RGBA;
+ } break;
+ }
}
+ ERR_FAIL_COND_V(decompress_format == BASIS_DECOMPRESS_MAX, Vector<uint8_t>());
+
// Copy the source image data with mipmaps into BasisU.
{
const int orig_width = image->get_width();
@@ -113,9 +171,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
Vector<uint8_t> image_data = image->get_data();
basisu::vector<basisu::image> basisu_mipmaps;
+ basisu::vector<basisu::imagef> basisu_mipmaps_hdr;
// Buffer for storing padded mipmap data.
- Vector<uint32_t> mip_data_padded;
+ Vector<uint8_t> mip_data_padded;
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int64_t ofs, size;
@@ -126,31 +185,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
// Pad the mipmap's data if its resolution isn't divisible by 4.
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
- // Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
- const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);
-
- // Reserve space in the padded buffer.
- mip_data_padded.resize(next_width * next_height);
- uint32_t *data_padded_ptr = mip_data_padded.ptrw();
-
- // Pad mipmap to the nearest block by smearing.
- int x = 0, y = 0;
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x++) {
- data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
- }
-
- // First, smear in x.
- for (; x < next_width; x++) {
- data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
- }
- }
-
- // Then, smear in y.
- for (; y < next_height; y++) {
- for (x = 0; x < next_width; x++) {
- data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
- }
+ if (is_hdr) {
+ _basisu_pad_mipmap<BasisRGBAF>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
+ } else {
+ _basisu_pad_mipmap<uint32_t>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
}
// Override the image_mip_data pointer with our temporary Vector.
@@ -159,7 +197,7 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
// Override the mipmap's properties.
width = next_width;
height = next_height;
- size = mip_data_padded.size() * 4;
+ size = mip_data_padded.size();
}
// Get the next mipmap's resolution.
@@ -167,44 +205,61 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
next_height /= 2;
// Copy the source mipmap's data to a BasisU image.
- basisu::image basisu_image(width, height);
- memcpy(basisu_image.get_ptr(), image_mip_data, size);
+ if (is_hdr) {
+ basisu::imagef basisu_image(width, height);
+ memcpy(reinterpret_cast<uint8_t *>(basisu_image.get_ptr()), image_mip_data, size);
+
+ if (i == 0) {
+ params.m_source_images_hdr.push_back(basisu_image);
+ } else {
+ basisu_mipmaps_hdr.push_back(basisu_image);
+ }
- if (i == 0) {
- params.m_source_images.push_back(basisu_image);
} else {
- basisu_mipmaps.push_back(basisu_image);
+ basisu::image basisu_image(width, height);
+ memcpy(basisu_image.get_ptr(), image_mip_data, size);
+
+ if (i == 0) {
+ params.m_source_images.push_back(basisu_image);
+ } else {
+ basisu_mipmaps.push_back(basisu_image);
+ }
}
}
- params.m_source_mipmap_images.push_back(basisu_mipmaps);
+ if (is_hdr) {
+ params.m_source_mipmap_images_hdr.push_back(basisu_mipmaps_hdr);
+ } else {
+ params.m_source_mipmap_images.push_back(basisu_mipmaps);
+ }
}
// Encode the image data.
- Vector<uint8_t> basisu_data;
-
basisu::basis_compressor compressor;
compressor.init(params);
int basisu_err = compressor.process();
- ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data);
+ ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
- const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file();
- basisu_data.resize(basisu_out.size() + 4);
+ const basisu::uint8_vec &basisu_encoded = compressor.get_output_basis_file();
- // Copy the encoded data to the buffer.
- {
- uint8_t *wb = basisu_data.ptrw();
- *(uint32_t *)wb = decompress_format;
+ Vector<uint8_t> basisu_data;
+ basisu_data.resize(basisu_encoded.size() + 4);
+ uint8_t *basisu_data_ptr = basisu_data.ptrw();
- memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
- }
+ // Copy the encoded BasisU data into the output buffer.
+ *(uint32_t *)basisu_data_ptr = decompress_format;
+ memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());
+
+ print_verbose(vformat("BasisU: Encoding a %dx%d image with %d mipmaps took %d ms.", p_image->get_width(), p_image->get_height(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
return basisu_data;
}
#endif // TOOLS_ENABLED
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
Ref<Image> image;
ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");
@@ -320,6 +375,23 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}
} break;
+ case BASIS_DECOMPRESS_HDR_RGB: {
+ if (bptc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC6H;
+ image_format = Image::FORMAT_BPTC_RGBFU;
+ } else if (astc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA;
+ image_format = Image::FORMAT_ASTC_4x4_HDR;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;
+ image_format = Image::FORMAT_RGBE9995;
+ }
+
+ } break;
+ default: {
+ ERR_FAIL_V(image);
+ } break;
}
src_ptr += 4;
@@ -371,6 +443,9 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}
}
+ print_verbose(vformat("BasisU: Transcoding a %dx%d image with %d mipmaps into %s took %d ms.",
+ image->get_width(), image->get_height(), image->get_mipmap_count(), Image::get_format_name(image_format), OS::get_singleton()->get_ticks_msec() - start_time));
+
return image;
}
diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h
index 5e36d448f6..81c8511f60 100644
--- a/modules/basis_universal/image_compress_basisu.h
+++ b/modules/basis_universal/image_compress_basisu.h
@@ -39,11 +39,20 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
BASIS_DECOMPRESS_R,
+ BASIS_DECOMPRESS_HDR_RGB,
+ BASIS_DECOMPRESS_MAX
};
void basis_universal_init();
#ifdef TOOLS_ENABLED
+struct BasisRGBAF {
+ uint32_t r;
+ uint32_t g;
+ uint32_t b;
+ uint32_t a;
+};
+
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels);
#endif
diff --git a/modules/basis_universal/patches/external-jpgd.patch b/modules/basis_universal/patches/external-jpgd.patch
deleted file mode 100644
index 7a805d00cb..0000000000
--- a/modules/basis_universal/patches/external-jpgd.patch
+++ /dev/null
@@ -1,13 +0,0 @@
-diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
-index c431ceaf12..e87dd636a2 100644
---- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
-+++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
-@@ -409,7 +409,7 @@ namespace basisu
- bool load_jpg(const char *pFilename, image& img)
- {
- int width = 0, height = 0, actual_comps = 0;
-- uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagLinearChromaFiltering);
-+ uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagBoxChromaFiltering);
- if (!pImage_data)
- return false;
-
diff --git a/modules/bcdec/image_decompress_bcdec.cpp b/modules/bcdec/image_decompress_bcdec.cpp
index 30ca1fccb3..8a62cd2a4e 100644
--- a/modules/bcdec/image_decompress_bcdec.cpp
+++ b/modules/bcdec/image_decompress_bcdec.cpp
@@ -47,7 +47,6 @@ inline void bcdec_bc6h_half_u(const void *compressedBlock, void *decompressedBlo
static void decompress_image(BCdecFormat format, const void *src, void *dst, const uint64_t width, const uint64_t height) {
const uint8_t *src_blocks = reinterpret_cast<const uint8_t *>(src);
uint8_t *dec_blocks = reinterpret_cast<uint8_t *>(dst);
- uint64_t src_pos = 0, dst_pos = 0;
#define DECOMPRESS_LOOP(func, block_size, color_bytesize, color_components) \
for (uint64_t y = 0; y < height; y += 4) { \
@@ -59,41 +58,115 @@ static void decompress_image(BCdecFormat format, const void *src, void *dst, con
dst_pos += 3 * width * color_bytesize; \
}
- switch (format) {
- case BCdec_BC1: {
- DECOMPRESS_LOOP(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4)
- } break;
- case BCdec_BC2: {
- DECOMPRESS_LOOP(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4)
- } break;
- case BCdec_BC3: {
- DECOMPRESS_LOOP(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4)
- } break;
- case BCdec_BC4: {
- DECOMPRESS_LOOP(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1)
- } break;
- case BCdec_BC5: {
- DECOMPRESS_LOOP(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2)
- } break;
- case BCdec_BC6U: {
- DECOMPRESS_LOOP(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
- } break;
- case BCdec_BC6S: {
- DECOMPRESS_LOOP(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
- } break;
- case BCdec_BC7: {
- DECOMPRESS_LOOP(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4)
- } break;
+#define DECOMPRESS_LOOP_SAFE(func, block_size, color_bytesize, color_components, output) \
+ for (uint64_t y = 0; y < height; y += 4) { \
+ for (uint64_t x = 0; x < width; x += 4) { \
+ const uint32_t yblock = MIN(height - y, 4ul); \
+ const uint32_t xblock = MIN(width - x, 4ul); \
+ \
+ const bool incomplete = yblock < 4 && xblock < 4; \
+ uint8_t *dec_out = incomplete ? output : &dec_blocks[y * 4 * width + x * color_bytesize]; \
+ \
+ func(&src_blocks[src_pos], dec_out, 4 * color_components); \
+ src_pos += block_size; \
+ \
+ if (incomplete) { \
+ for (uint32_t cy = 0; cy < yblock; cy++) { \
+ for (uint32_t cx = 0; cx < xblock; cx++) { \
+ memcpy(&dec_blocks[(y + cy) * 4 * width + (x + cx) * color_bytesize], &output[cy * 4 + cx * color_bytesize], color_bytesize); \
+ } \
+ } \
+ } \
+ } \
+ }
+
+ if (width % 4 != 0 || height % 4 != 0) {
+ uint64_t src_pos = 0;
+
+ uint8_t r8_output[4 * 4];
+ uint8_t rg8_output[4 * 4 * 2];
+ uint8_t rgba8_output[4 * 4 * 4];
+ uint8_t rgbh_output[4 * 4 * 6];
+
+ switch (format) {
+ case BCdec_BC1: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4, rgba8_output)
+ } break;
+ case BCdec_BC2: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4, rgba8_output)
+ } break;
+ case BCdec_BC3: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4, rgba8_output)
+ } break;
+ case BCdec_BC4: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1, r8_output)
+ } break;
+ case BCdec_BC5: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2, rg8_output)
+ } break;
+ case BCdec_BC6U: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3, rgbh_output)
+ } break;
+ case BCdec_BC6S: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3, rgbh_output)
+ } break;
+ case BCdec_BC7: {
+ DECOMPRESS_LOOP_SAFE(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4, rgba8_output)
+ } break;
+ }
+
+ } else {
+ uint64_t src_pos = 0, dst_pos = 0;
+
+ switch (format) {
+ case BCdec_BC1: {
+ DECOMPRESS_LOOP(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC2: {
+ DECOMPRESS_LOOP(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC3: {
+ DECOMPRESS_LOOP(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC4: {
+ DECOMPRESS_LOOP(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1)
+ } break;
+ case BCdec_BC5: {
+ DECOMPRESS_LOOP(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2)
+ } break;
+ case BCdec_BC6U: {
+ DECOMPRESS_LOOP(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
+ } break;
+ case BCdec_BC6S: {
+ DECOMPRESS_LOOP(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
+ } break;
+ case BCdec_BC7: {
+ DECOMPRESS_LOOP(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4)
+ } break;
+ }
}
#undef DECOMPRESS_LOOP
+#undef DECOMPRESS_LOOP_SAFE
}
void image_decompress_bcdec(Image *p_image) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
- int w = p_image->get_width();
- int h = p_image->get_height();
+ int width = p_image->get_width();
+ int height = p_image->get_height();
+
+ // Compressed images' dimensions should be padded to the upper multiple of 4.
+ // If they aren't, they need to be realigned (the actual data is correctly padded though).
+ if (width % 4 != 0 || height % 4 != 0) {
+ int new_width = width + (4 - (width % 4));
+ int new_height = height + (4 - (height % 4));
+
+ print_verbose(vformat("Compressed image's dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", width, height, new_width, new_height));
+
+ width = new_width;
+ height = new_height;
+ }
Image::Format source_format = p_image->get_format();
Image::Format target_format = Image::FORMAT_MAX;
@@ -148,30 +221,27 @@ void image_decompress_bcdec(Image *p_image) {
}
int mm_count = p_image->get_mipmap_count();
- int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
+ int64_t target_size = Image::get_image_data_size(width, height, target_format, p_image->has_mipmaps());
+ // Decompressed data.
Vector<uint8_t> data;
data.resize(target_size);
+ uint8_t *wb = data.ptrw();
+ // Source data.
const uint8_t *rb = p_image->get_data().ptr();
- uint8_t *wb = data.ptrw();
// Decompress mipmaps.
for (int i = 0; i <= mm_count; i++) {
- int64_t src_ofs = 0, mipmap_size = 0;
int mipmap_w = 0, mipmap_h = 0;
- p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
-
- int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
+ int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h);
+ int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
decompress_image(bcdec_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h);
-
- w >>= 1;
- h >>= 1;
}
- p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
+ p_image->set_data(width, height, p_image->has_mipmaps(), target_format, data);
- // Swap channels if necessary.
+ // Swap channels if the format is using a channel swizzle.
if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
p_image->convert_ra_rgba8_to_rg();
}
diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub
index 2735116cc3..621ba58ae3 100644
--- a/modules/betsy/SCsub
+++ b/modules/betsy/SCsub
@@ -7,6 +7,7 @@ Import("env_modules")
env_betsy = env_modules.Clone()
env_betsy.GLSL_HEADER("bc6h.glsl")
env_betsy.GLSL_HEADER("bc1.glsl")
+env_betsy.GLSL_HEADER("bc4.glsl")
env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"])
# Thirdparty source files
diff --git a/modules/betsy/bc4.glsl b/modules/betsy/bc4.glsl
new file mode 100644
index 0000000000..b7a5f6a686
--- /dev/null
+++ b/modules/betsy/bc4.glsl
@@ -0,0 +1,151 @@
+#[versions]
+
+unsigned = "";
+signed = "#define SNORM";
+
+#[compute]
+#version 450
+
+#include "CrossPlatformSettings_piece_all.glsl"
+#include "UavCrossPlatform_piece_all.glsl"
+
+#VERSION_DEFINES
+
+shared float2 g_minMaxValues[4u * 4u * 4u];
+shared uint2 g_mask[4u * 4u];
+
+layout(binding = 0) uniform sampler2D srcTex;
+layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture;
+
+layout(push_constant, std430) uniform Params {
+ uint p_channelIdx;
+ uint p_padding[3];
+}
+params;
+
+layout(local_size_x = 4, //
+ local_size_y = 4, //
+ local_size_z = 4) in;
+
+/// Each block is 16 pixels
+/// Each thread works on 4 pixels
+/// Therefore each block needs 4 threads, generating 8 masks
+/// At the end these 8 masks get merged into 2 and results written to output
+///
+/// **Q: Why 4 pixels per thread? Why not 1 pixel per thread? Why not 2? Why not 16?**
+///
+/// A: It's a sweetspot.
+/// - Very short threads cannot fill expensive GPUs with enough work (dispatch bound)
+/// - Lots of threads means lots of synchronization (e.g. evaluating min/max, merging masks)
+/// overhead, and also more LDS usage which reduces occupancy.
+/// - Long threads (e.g. 1 thread per block) misses parallelism opportunities
+void main() {
+ float minVal, maxVal;
+ float4 srcPixel;
+
+ const uint blockThreadId = gl_LocalInvocationID.x;
+
+ const uint2 pixelsToLoadBase = gl_GlobalInvocationID.yz << 2u;
+
+ for (uint i = 0u; i < 4u; ++i) {
+ const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i, blockThreadId);
+
+ const float4 value = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyzw;
+ srcPixel[i] = params.p_channelIdx == 0 ? value.x : (params.p_channelIdx == 1 ? value.y : value.w);
+ srcPixel[i] *= 255.0f;
+ }
+
+ minVal = min3(srcPixel.x, srcPixel.y, srcPixel.z);
+ maxVal = max3(srcPixel.x, srcPixel.y, srcPixel.z);
+ minVal = min(minVal, srcPixel.w);
+ maxVal = max(maxVal, srcPixel.w);
+
+ const uint minMaxIdxBase = (gl_LocalInvocationID.z << 4u) + (gl_LocalInvocationID.y << 2u);
+ const uint maskIdxBase = (gl_LocalInvocationID.z << 2u) + gl_LocalInvocationID.y;
+
+ g_minMaxValues[minMaxIdxBase + blockThreadId] = float2(minVal, maxVal);
+ g_mask[maskIdxBase] = uint2(0u, 0u);
+
+ memoryBarrierShared();
+ barrier();
+
+ // Have all 4 threads in the block grab the min/max value by comparing what all 4 threads uploaded
+ for (uint i = 0u; i < 4u; ++i) {
+ minVal = min(g_minMaxValues[minMaxIdxBase + i].x, minVal);
+ maxVal = max(g_minMaxValues[minMaxIdxBase + i].y, maxVal);
+ }
+
+ // determine bias and emit color indices
+ // given the choice of maxVal/minVal, these indices are optimal:
+ // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/
+ float dist = maxVal - minVal;
+ float dist4 = dist * 4.0f;
+ float dist2 = dist * 2.0f;
+ float bias = (dist < 8) ? (dist - 1) : (trunc(dist * 0.5f) + 2);
+ bias -= minVal * 7;
+
+ uint mask0 = 0u, mask1 = 0u;
+
+ for (uint i = 0u; i < 4u; ++i) {
+ float a = srcPixel[i] * 7.0f + bias;
+
+ int ind = 0;
+
+ // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max).
+ if (a >= dist4) {
+ ind = 4;
+ a -= dist4;
+ }
+
+ if (a >= dist2) {
+ ind += 2;
+ a -= dist2;
+ }
+
+ if (a >= dist)
+ ind += 1;
+
+ // turn linear scale into DXT index (0/1 are extremal pts)
+ ind = -ind & 7;
+ ind ^= (2 > ind) ? 1 : 0;
+
+ // write index
+ const uint bits = 16u + ((blockThreadId << 2u) + i) * 3u;
+ if (bits < 32u) {
+ mask0 |= uint(ind) << bits;
+ if (bits + 3u > 32u) {
+ mask1 |= uint(ind) >> (32u - bits);
+ }
+ } else {
+ mask1 |= uint(ind) << (bits - 32u);
+ }
+ }
+
+ if (mask0 != 0u)
+ atomicOr(g_mask[maskIdxBase].x, mask0);
+ if (mask1 != 0u)
+ atomicOr(g_mask[maskIdxBase].y, mask1);
+
+ memoryBarrierShared();
+ barrier();
+
+ if (blockThreadId == 0u) {
+ // Save data
+ uint2 outputBytes;
+
+#ifdef SNORM
+ outputBytes.x =
+ packSnorm4x8(float4(maxVal * (1.0f / 255.0f) * 2.0f - 1.0f,
+ minVal * (1.0f / 255.0f) * 2.0f - 1.0f, 0.0f, 0.0f));
+#else
+ outputBytes.x = packUnorm4x8(
+ float4(maxVal * (1.0f / 255.0f), minVal * (1.0f / 255.0f), 0.0f, 0.0f));
+#endif
+
+ outputBytes.x |= g_mask[maskIdxBase].x;
+ outputBytes.y = g_mask[maskIdxBase].y;
+
+ uint2 dstUV = gl_GlobalInvocationID.yz;
+ imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u));
+ }
+}
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 7b4d8b3dfb..1ad9bed721 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -35,12 +35,18 @@
#include "betsy_bc1.h"
#include "bc1.glsl.gen.h"
+#include "bc4.glsl.gen.h"
#include "bc6h.glsl.gen.h"
+#include "servers/display_server.h"
static Mutex betsy_mutex;
static BetsyCompressor *betsy = nullptr;
void BetsyCompressor::_init() {
+ if (!DisplayServer::can_create_rendering_device()) {
+ return;
+ }
+
// Create local RD.
RenderingContextDriver *rcd = nullptr;
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
@@ -165,6 +171,10 @@ static String get_shader_name(BetsyFormat p_format) {
case BETSY_FORMAT_BC3:
return "BC3";
+ case BETSY_FORMAT_BC4_SIGNED:
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ return "BC4";
+
case BETSY_FORMAT_BC6_SIGNED:
case BETSY_FORMAT_BC6_UNSIGNED:
return "BC6";
@@ -177,6 +187,11 @@ static String get_shader_name(BetsyFormat p_format) {
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+ // Return an error so that the compression can fall back to cpu compression
+ if (compress_rd == nullptr) {
+ return ERR_CANT_CREATE;
+ }
+
if (r_img->is_compressed()) {
return ERR_INVALID_DATA;
}
@@ -202,6 +217,12 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
dest_format = Image::FORMAT_DXT1;
break;
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ version = "unsigned";
+ dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
+ dest_format = Image::FORMAT_RGTC_R;
+ break;
+
case BETSY_FORMAT_BC6_SIGNED:
version = "signed";
dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
@@ -235,8 +256,13 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
err = source->parse_versions_from_text(bc1_shader_glsl);
break;
- case BETSY_FORMAT_BC6_UNSIGNED:
+ case BETSY_FORMAT_BC4_SIGNED:
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ err = source->parse_versions_from_text(bc4_shader_glsl);
+ break;
+
case BETSY_FORMAT_BC6_SIGNED:
+ case BETSY_FORMAT_BC6_UNSIGNED:
err = source->parse_versions_from_text(bc6h_shader_glsl);
break;
@@ -430,26 +456,45 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
- if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) {
- BC6PushConstant push_constant;
- push_constant.sizeX = 1.0f / width;
- push_constant.sizeY = 1.0f / height;
- push_constant.padding[0] = 0;
- push_constant.padding[1] = 0;
-
- compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
-
- } else {
- BC1PushConstant push_constant;
- push_constant.num_refines = 2;
- push_constant.padding[0] = 0;
- push_constant.padding[1] = 0;
- push_constant.padding[2] = 0;
-
- compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
+ switch (dest_format) {
+ case Image::FORMAT_BPTC_RGBFU:
+ case Image::FORMAT_BPTC_RGBF: {
+ BC6PushConstant push_constant;
+ push_constant.sizeX = 1.0f / width;
+ push_constant.sizeY = 1.0f / height;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+ } break;
+
+ case Image::FORMAT_DXT1: {
+ BC1PushConstant push_constant;
+ push_constant.num_refines = 2;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+ push_constant.padding[2] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+ } break;
+
+ case Image::FORMAT_RGTC_R: {
+ BC4PushConstant push_constant;
+ push_constant.channel_idx = 0;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+ push_constant.padding[2] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
+ } break;
+
+ default: {
+ } break;
}
- compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
compress_rd->compute_list_end();
compress_rd->submit();
@@ -511,13 +556,14 @@ Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
switch (p_channels) {
case Image::USED_CHANNELS_RGB:
- result = betsy->compress(BETSY_FORMAT_BC1_DITHER, r_img);
- break;
-
case Image::USED_CHANNELS_L:
result = betsy->compress(BETSY_FORMAT_BC1, r_img);
break;
+ case Image::USED_CHANNELS_R:
+ result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);
+ break;
+
default:
break;
}
diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h
index 70e4ae85ed..ab7b785803 100644
--- a/modules/betsy/image_compress_betsy.h
+++ b/modules/betsy/image_compress_betsy.h
@@ -50,6 +50,8 @@ enum BetsyFormat {
BETSY_FORMAT_BC1,
BETSY_FORMAT_BC1_DITHER,
BETSY_FORMAT_BC3,
+ BETSY_FORMAT_BC4_SIGNED,
+ BETSY_FORMAT_BC4_UNSIGNED,
BETSY_FORMAT_BC6_SIGNED,
BETSY_FORMAT_BC6_UNSIGNED,
};
@@ -65,6 +67,11 @@ struct BC1PushConstant {
uint32_t padding[3];
};
+struct BC4PushConstant {
+ uint32_t channel_idx;
+ uint32_t padding[3];
+};
+
void free_device();
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels);
@@ -84,10 +91,10 @@ class BetsyCompressor : public Object {
RenderingDevice *compress_rd = nullptr;
RenderingContextDriver *compress_rcd = nullptr;
HashMap<String, BetsyShader> cached_shaders;
- RID src_sampler = RID();
+ RID src_sampler;
// Format-specific resources.
- RID dxt1_encoding_table_buffer = RID();
+ RID dxt1_encoding_table_buffer;
void _init();
void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id);
diff --git a/modules/camera/camera_feed_linux.cpp b/modules/camera/camera_feed_linux.cpp
index 94bb2b6ad3..3ae1b70ac9 100644
--- a/modules/camera/camera_feed_linux.cpp
+++ b/modules/camera/camera_feed_linux.cpp
@@ -145,7 +145,7 @@ bool CameraFeedLinux::_request_buffers() {
}
buffers[i].length = buffer.length;
- buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
+ buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
if (buffers[i].start == MAP_FAILED) {
for (unsigned int b = 0; b < i; b++) {
diff --git a/modules/camera/camera_linux.cpp b/modules/camera/camera_linux.cpp
index 0cfb6b7b9e..e5558bf96c 100644
--- a/modules/camera/camera_linux.cpp
+++ b/modules/camera/camera_linux.cpp
@@ -161,7 +161,7 @@ bool CameraLinux::_can_query_format(int p_file_descriptor, int p_type) {
CameraLinux::CameraLinux() {
camera_thread.start(CameraLinux::camera_thread_func, this);
-};
+}
CameraLinux::~CameraLinux() {
exit_flag.set();
diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm
index de4f814846..bd718a0cb6 100644
--- a/modules/camera/camera_macos.mm
+++ b/modules/camera/camera_macos.mm
@@ -212,12 +212,12 @@ public:
AVCaptureDevice *CameraFeedMacOS::get_device() const {
return device;
-};
+}
CameraFeedMacOS::CameraFeedMacOS() {
device = nullptr;
capture_session = nullptr;
-};
+}
void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
device = p_device;
@@ -231,7 +231,7 @@ void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
} else if ([p_device position] == AVCaptureDevicePositionFront) {
position = CameraFeed::FEED_FRONT;
};
-};
+}
bool CameraFeedMacOS::activate_feed() {
if (capture_session) {
@@ -257,7 +257,7 @@ bool CameraFeedMacOS::activate_feed() {
};
return true;
-};
+}
void CameraFeedMacOS::deactivate_feed() {
// end camera capture if we have one
@@ -265,7 +265,7 @@ void CameraFeedMacOS::deactivate_feed() {
[capture_session cleanup];
capture_session = nullptr;
};
-};
+}
//////////////////////////////////////////////////////////////////////////
// MyDeviceNotifications - This is a little helper class gets notifications
@@ -351,7 +351,7 @@ void CameraMacOS::update_feeds() {
add_feed(newfeed);
};
};
-};
+}
CameraMacOS::CameraMacOS() {
// Find available cameras we have at this time
@@ -359,4 +359,4 @@ CameraMacOS::CameraMacOS() {
// should only have one of these....
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
-};
+}
diff --git a/modules/camera/camera_win.cpp b/modules/camera/camera_win.cpp
index 755642270e..7871595d91 100644
--- a/modules/camera/camera_win.cpp
+++ b/modules/camera/camera_win.cpp
@@ -64,13 +64,13 @@ CameraFeedWindows::~CameraFeedWindows() {
};
///@TODO free up anything used by this
-};
+}
bool CameraFeedWindows::activate_feed() {
///@TODO this should activate our camera and start the process of capturing frames
return true;
-};
+}
///@TODO we should probably have a callback method here that is being called by the
// camera API which provides frames and call back into the CameraServer to update our texture
@@ -91,4 +91,4 @@ CameraWindows::CameraWindows() {
add_active_cameras();
// need to add something that will react to devices being connected/removed...
-};
+}
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 95ffeed6c3..7e1c2e39b3 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -63,7 +63,7 @@ void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
void CSGShapeEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
- options->set_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
+ options->set_button_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
} break;
}
}
@@ -278,24 +278,13 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- Vector3 axis;
- axis[p_id == 0 ? 0 : 1] = 1.0;
- Vector3 ra, rb;
- Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
- float d = axis.dot(ra);
- if (Node3DEditor::get_singleton()->is_snap_enabled()) {
- d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
- }
-
- if (d < 0.001) {
- d = 0.001;
- }
-
- if (p_id == 0) {
- s->set_radius(d);
- } else if (p_id == 1) {
- s->set_height(d * 2.0);
- }
+ real_t height = s->get_height();
+ real_t radius = s->get_radius();
+ Vector3 position;
+ helper->cylinder_set_handle(sg, p_id, height, radius, position);
+ s->set_height(height);
+ s->set_radius(radius);
+ s->set_global_position(position);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
@@ -340,32 +329,11 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
}
if (Object::cast_to<CSGBox3D>(cs)) {
- helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
+ helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
- CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- if (p_cancel) {
- if (p_id == 0) {
- s->set_radius(p_restore);
- } else {
- s->set_height(p_restore);
- }
- return;
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- if (p_id == 0) {
- ur->create_action(TTR("Change Cylinder Radius"));
- ur->add_do_method(s, "set_radius", s->get_radius());
- ur->add_undo_method(s, "set_radius", p_restore);
- } else {
- ur->create_action(TTR("Change Cylinder Height"));
- ur->add_do_method(s, "set_height", s->get_height());
- ur->add_undo_method(s, "set_height", p_restore);
- }
-
- ur->commit_action();
+ helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
@@ -506,9 +474,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- Vector<Vector3> handles;
- handles.push_back(Vector3(s->get_radius(), 0, 0));
- handles.push_back(Vector3(0, s->get_height() * 0.5, 0));
+ Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index 6ea44c5fc3..601d0e0c24 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -46,7 +46,12 @@ enum {
DDPF_ALPHAONLY = 0x00000002,
DDPF_FOURCC = 0x00000004,
DDPF_RGB = 0x00000040,
- DDPF_RG_SNORM = 0x00080000
+ DDPF_RG_SNORM = 0x00080000,
+ DDSC2_CUBEMAP = 0x200,
+ DDSC2_VOLUME = 0x200000,
+ DX10D_1D = 2,
+ DX10D_2D = 3,
+ DX10D_3D = 4,
};
enum DDSFourCC {
@@ -139,6 +144,15 @@ enum DDSFormat {
DDS_MAX
};
+enum DDSType {
+ DDST_2D = 1,
+ DDST_CUBEMAP,
+ DDST_3D,
+
+ DDST_TYPE_MASK = 0x7F,
+ DDST_ARRAY = 0x80,
+};
+
struct DDSFormatInfo {
const char *name = nullptr;
bool compressed = false;
@@ -180,7 +194,7 @@ static const DDSFormatInfo dds_format_info[DDS_MAX] = {
{ "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 }
};
-static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
+inline DDSFormat _dxgi_to_dds_format(uint32_t p_dxgi_format) {
switch (p_dxgi_format) {
case DXGI_R32G32B32A32_FLOAT: {
return DDS_RGBA32F;
@@ -267,210 +281,23 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
}
}
-Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
- if (r_error) {
- *r_error = ERR_CANT_OPEN;
- }
-
- Error err;
- Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
- if (f.is_null()) {
- return Ref<Resource>();
- }
-
- Ref<FileAccess> fref(f);
- if (r_error) {
- *r_error = ERR_FILE_CORRUPT;
- }
-
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open DDS texture file '" + p_path + "'.");
-
- uint32_t magic = f->get_32();
- uint32_t hsize = f->get_32();
- uint32_t flags = f->get_32();
- uint32_t height = f->get_32();
- uint32_t width = f->get_32();
- uint32_t pitch = f->get_32();
- /* uint32_t depth = */ f->get_32();
- uint32_t mipmaps = f->get_32();
-
- // Skip reserved.
- for (int i = 0; i < 11; i++) {
- f->get_32();
- }
-
- // Validate.
- // We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
- // but non-mandatory when reading (as some writers don't set them).
- if (magic != DDS_MAGIC || hsize != 124) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
- }
-
- /* uint32_t format_size = */ f->get_32();
- uint32_t format_flags = f->get_32();
- uint32_t format_fourcc = f->get_32();
- uint32_t format_rgb_bits = f->get_32();
- uint32_t format_red_mask = f->get_32();
- uint32_t format_green_mask = f->get_32();
- uint32_t format_blue_mask = f->get_32();
- uint32_t format_alpha_mask = f->get_32();
-
- /* uint32_t caps_1 = */ f->get_32();
- /* uint32_t caps_2 = */ f->get_32();
- /* uint32_t caps_3 = */ f->get_32();
- /* uint32_t caps_4 = */ f->get_32();
-
- // Skip reserved.
- f->get_32();
-
- if (f->get_position() < 128) {
- f->seek(128);
- }
-
- DDSFormat dds_format = DDS_MAX;
-
- if (format_flags & DDPF_FOURCC) {
- // FourCC formats.
- switch (format_fourcc) {
- case DDFCC_DXT1: {
- dds_format = DDS_DXT1;
- } break;
- case DDFCC_DXT2:
- case DDFCC_DXT3: {
- dds_format = DDS_DXT3;
- } break;
- case DDFCC_DXT4:
- case DDFCC_DXT5: {
- dds_format = DDS_DXT5;
- } break;
- case DDFCC_ATI1:
- case DDFCC_BC4U: {
- dds_format = DDS_ATI1;
- } break;
- case DDFCC_ATI2:
- case DDFCC_BC5U:
- case DDFCC_A2XY: {
- dds_format = DDS_ATI2;
- } break;
- case DDFCC_R16F: {
- dds_format = DDS_R16F;
- } break;
- case DDFCC_RG16F: {
- dds_format = DDS_RG16F;
- } break;
- case DDFCC_RGBA16F: {
- dds_format = DDS_RGBA16F;
- } break;
- case DDFCC_R32F: {
- dds_format = DDS_R32F;
- } break;
- case DDFCC_RG32F: {
- dds_format = DDS_RG32F;
- } break;
- case DDFCC_RGBA32F: {
- dds_format = DDS_RGBA32F;
- } break;
- case DDFCC_DX10: {
- uint32_t dxgi_format = f->get_32();
- /* uint32_t dimension = */ f->get_32();
- /* uint32_t misc_flags_1 = */ f->get_32();
- /* uint32_t array_size = */ f->get_32();
- /* uint32_t misc_flags_2 = */ f->get_32();
-
- dds_format = dxgi_to_dds_format(dxgi_format);
- } break;
-
- default: {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported FourCC in DDS '" + p_path + "'.");
- }
- }
-
- } else if (format_flags & DDPF_RGB) {
- // Channel-bitmasked formats.
- if (format_flags & DDPF_ALPHAPIXELS) {
- // With alpha.
- if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
- dds_format = DDS_BGRA8;
- } else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
- dds_format = DDS_RGBA8;
- } else if (format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
- dds_format = DDS_BGR5A1;
- } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
- dds_format = DDS_BGR10A2;
- } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000) {
- dds_format = DDS_RGB10A2;
- } else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) {
- dds_format = DDS_BGRA4;
- } else if (format_rgb_bits == 16 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00) {
- dds_format = DDS_B2GR3A8;
- }
+static Ref<Image> _dds_load_layer(Ref<FileAccess> p_file, DDSFormat p_dds_format, uint32_t p_width, uint32_t p_height, uint32_t p_mipmaps, uint32_t p_pitch, uint32_t p_flags, Vector<uint8_t> &r_src_data) {
+ const DDSFormatInfo &info = dds_format_info[p_dds_format];
- } else {
- // Without alpha.
- if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
- dds_format = DDS_BGR8;
- } else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
- dds_format = DDS_RGB8;
- } else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
- dds_format = DDS_BGR565;
- } else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) {
- dds_format = DDS_B2GR3;
- }
- }
-
- } else {
- // Other formats.
- if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) {
- // Alpha only.
- dds_format = DDS_LUMINANCE;
- }
- }
-
- // Depending on the writer, luminance formats may or may not have the DDPF_RGB or DDPF_LUMINANCE flags defined,
- // so we check for these formats after everything else failed.
- if (dds_format == DDS_MAX) {
- if (format_flags & DDPF_ALPHAPIXELS) {
- // With alpha.
- if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
- dds_format = DDS_LUMINANCE_ALPHA;
- } else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) {
- dds_format = DDS_LUMINANCE_ALPHA_4;
- }
-
- } else {
- // Without alpha.
- if (format_rgb_bits == 8 && format_red_mask == 0xff) {
- dds_format = DDS_LUMINANCE;
- }
- }
- }
-
- // No format detected, error.
- if (dds_format == DDS_MAX) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
- }
-
- if (!(flags & DDSD_MIPMAPCOUNT)) {
- mipmaps = 1;
- }
-
- Vector<uint8_t> src_data;
-
- const DDSFormatInfo &info = dds_format_info[dds_format];
- uint32_t w = width;
- uint32_t h = height;
+ uint32_t w = p_width;
+ uint32_t h = p_height;
if (info.compressed) {
// BC compressed.
uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
- if (flags & DDSD_LINEARSIZE) {
- ERR_FAIL_COND_V_MSG(size != pitch, Ref<Resource>(), "DDS header flags specify that a linear size of the top-level image is present, but the specified size does not match the expected value.");
+ if (p_flags & DDSD_LINEARSIZE) {
+ ERR_FAIL_COND_V_MSG(size != p_pitch, Ref<Resource>(), "DDS header flags specify that a linear size of the top-level image is present, but the specified size does not match the expected value.");
} else {
- ERR_FAIL_COND_V_MSG(pitch != 0, Ref<Resource>(), "DDS header flags specify that no linear size will given for the top-level image, but a non-zero linear size value is present in the header.");
+ ERR_FAIL_COND_V_MSG(p_pitch != 0, Ref<Resource>(), "DDS header flags specify that no linear size will given for the top-level image, but a non-zero linear size value is present in the header.");
}
- for (uint32_t i = 1; i < mipmaps; i++) {
+ for (uint32_t i = 1; i < p_mipmaps; i++) {
w = MAX(1u, w >> 1);
h = MAX(1u, h >> 1);
@@ -478,22 +305,22 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
size += bsize;
}
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
+ r_src_data.resize(size);
+ uint8_t *wb = r_src_data.ptrw();
+ p_file->get_buffer(wb, size);
} else {
// Generic uncompressed.
- uint32_t size = width * height * info.block_size;
+ uint32_t size = p_width * p_height * info.block_size;
- for (uint32_t i = 1; i < mipmaps; i++) {
+ for (uint32_t i = 1; i < p_mipmaps; i++) {
w = (w + 1) >> 1;
h = (h + 1) >> 1;
size += w * h * info.block_size;
}
// Calculate the space these formats will take up after decoding.
- switch (dds_format) {
+ switch (p_dds_format) {
case DDS_BGR565:
size = size * 3 / 2;
break;
@@ -513,12 +340,11 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
break;
}
- src_data.resize(size);
- uint8_t *wb = src_data.ptrw();
- f->get_buffer(wb, size);
+ r_src_data.resize(size);
+ uint8_t *wb = r_src_data.ptrw();
+ p_file->get_buffer(wb, size);
- // Decode nonstandard formats.
- switch (dds_format) {
+ switch (p_dds_format) {
case DDS_BGR5A1: {
// To RGBA8.
int colcount = size / 4;
@@ -705,14 +531,282 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
}
}
- Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data));
- Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
+ return memnew(Image(p_width, p_height, p_mipmaps > 1, info.format, r_src_data));
+}
+
+Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
+ if (r_error) {
+ *r_error = ERR_CANT_OPEN;
+ }
+
+ Error err;
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
+ if (f.is_null()) {
+ return Ref<Resource>();
+ }
+ Ref<FileAccess> fref(f);
if (r_error) {
- *r_error = OK;
+ *r_error = ERR_FILE_CORRUPT;
+ }
+
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), vformat("Unable to open DDS texture file '%s'.", p_path));
+
+ uint32_t magic = f->get_32();
+ uint32_t hsize = f->get_32();
+ uint32_t flags = f->get_32();
+ uint32_t height = f->get_32();
+ uint32_t width = f->get_32();
+ uint32_t pitch = f->get_32();
+ uint32_t depth = f->get_32();
+ uint32_t mipmaps = f->get_32();
+
+ // Skip reserved.
+ for (int i = 0; i < 11; i++) {
+ f->get_32();
+ }
+
+ // Validate.
+ // We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
+ // but non-mandatory when reading (as some writers don't set them).
+ if (magic != DDS_MAGIC || hsize != 124) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Invalid or unsupported DDS texture file '%s'.", p_path));
+ }
+
+ /* uint32_t format_size = */ f->get_32();
+ uint32_t format_flags = f->get_32();
+ uint32_t format_fourcc = f->get_32();
+ uint32_t format_rgb_bits = f->get_32();
+ uint32_t format_red_mask = f->get_32();
+ uint32_t format_green_mask = f->get_32();
+ uint32_t format_blue_mask = f->get_32();
+ uint32_t format_alpha_mask = f->get_32();
+
+ /* uint32_t caps_1 = */ f->get_32();
+ uint32_t caps_2 = f->get_32();
+ /* uint32_t caps_3 = */ f->get_32();
+ /* uint32_t caps_4 = */ f->get_32();
+
+ // Skip reserved.
+ f->get_32();
+
+ if (f->get_position() < 128) {
+ f->seek(128);
+ }
+
+ uint32_t layer_count = 1;
+ uint32_t dds_type = DDST_2D;
+
+ if (caps_2 & DDSC2_CUBEMAP) {
+ dds_type = DDST_CUBEMAP;
+ layer_count *= 6;
+
+ } else if (caps_2 & DDSC2_VOLUME) {
+ dds_type = DDST_3D;
+ layer_count = depth;
+ }
+
+ DDSFormat dds_format = DDS_MAX;
+
+ if (format_flags & DDPF_FOURCC) {
+ // FourCC formats.
+ switch (format_fourcc) {
+ case DDFCC_DXT1: {
+ dds_format = DDS_DXT1;
+ } break;
+ case DDFCC_DXT2:
+ case DDFCC_DXT3: {
+ dds_format = DDS_DXT3;
+ } break;
+ case DDFCC_DXT4:
+ case DDFCC_DXT5: {
+ dds_format = DDS_DXT5;
+ } break;
+ case DDFCC_ATI1:
+ case DDFCC_BC4U: {
+ dds_format = DDS_ATI1;
+ } break;
+ case DDFCC_ATI2:
+ case DDFCC_BC5U:
+ case DDFCC_A2XY: {
+ dds_format = DDS_ATI2;
+ } break;
+ case DDFCC_R16F: {
+ dds_format = DDS_R16F;
+ } break;
+ case DDFCC_RG16F: {
+ dds_format = DDS_RG16F;
+ } break;
+ case DDFCC_RGBA16F: {
+ dds_format = DDS_RGBA16F;
+ } break;
+ case DDFCC_R32F: {
+ dds_format = DDS_R32F;
+ } break;
+ case DDFCC_RG32F: {
+ dds_format = DDS_RG32F;
+ } break;
+ case DDFCC_RGBA32F: {
+ dds_format = DDS_RGBA32F;
+ } break;
+ case DDFCC_DX10: {
+ uint32_t dxgi_format = f->get_32();
+ uint32_t dimension = f->get_32();
+ /* uint32_t misc_flags_1 = */ f->get_32();
+ uint32_t array_size = f->get_32();
+ /* uint32_t misc_flags_2 = */ f->get_32();
+
+ if (dimension == DX10D_3D) {
+ dds_type = DDST_3D;
+ layer_count = depth;
+ }
+
+ if (array_size > 1) {
+ layer_count *= array_size;
+ dds_type |= DDST_ARRAY;
+ }
+
+ dds_format = _dxgi_to_dds_format(dxgi_format);
+ } break;
+
+ default: {
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unrecognized or unsupported FourCC in DDS '%s'.", p_path));
+ }
+ }
+
+ } else if (format_flags & DDPF_RGB) {
+ // Channel-bitmasked formats.
+ if (format_flags & DDPF_ALPHAPIXELS) {
+ // With alpha.
+ if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_BGRA8;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
+ dds_format = DDS_RGBA8;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
+ dds_format = DDS_BGR5A1;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
+ dds_format = DDS_BGR10A2;
+ } else if (format_rgb_bits == 32 && format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000) {
+ dds_format = DDS_RGB10A2;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) {
+ dds_format = DDS_BGRA4;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00) {
+ dds_format = DDS_B2GR3A8;
+ }
+
+ } else {
+ // Without alpha.
+ if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
+ dds_format = DDS_BGR8;
+ } else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
+ dds_format = DDS_RGB8;
+ } else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
+ dds_format = DDS_BGR565;
+ } else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) {
+ dds_format = DDS_B2GR3;
+ }
+ }
+
+ } else {
+ // Other formats.
+ if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) {
+ // Alpha only.
+ dds_format = DDS_LUMINANCE;
+ }
+ }
+
+ // Depending on the writer, luminance formats may or may not have the DDPF_RGB or DDPF_LUMINANCE flags defined,
+ // so we check for these formats after everything else failed.
+ if (dds_format == DDS_MAX) {
+ if (format_flags & DDPF_ALPHAPIXELS) {
+ // With alpha.
+ if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
+ dds_format = DDS_LUMINANCE_ALPHA;
+ } else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) {
+ dds_format = DDS_LUMINANCE_ALPHA_4;
+ }
+
+ } else {
+ // Without alpha.
+ if (format_rgb_bits == 8 && format_red_mask == 0xff) {
+ dds_format = DDS_LUMINANCE;
+ }
+ }
+ }
+
+ // No format detected, error.
+ if (dds_format == DDS_MAX) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unrecognized or unsupported color layout in DDS '%s'.", p_path));
+ }
+
+ if (!(flags & DDSD_MIPMAPCOUNT)) {
+ mipmaps = 1;
+ }
+
+ Vector<uint8_t> src_data;
+
+ Vector<Ref<Image>> images;
+ images.resize(layer_count);
+
+ for (uint32_t i = 0; i < layer_count; i++) {
+ images.write[i] = _dds_load_layer(f, dds_format, width, height, mipmaps, pitch, flags, src_data);
+ }
+
+ if ((dds_type & DDST_TYPE_MASK) == DDST_2D) {
+ if (dds_type & DDST_ARRAY) {
+ Ref<Texture2DArray> texture = memnew(Texture2DArray());
+ texture->create_from_images(images);
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ return texture;
+
+ } else {
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ return ImageTexture::create_from_image(images[0]);
+ }
+
+ } else if ((dds_type & DDST_TYPE_MASK) == DDST_CUBEMAP) {
+ ERR_FAIL_COND_V(layer_count % 6 != 0, Ref<Resource>());
+
+ if (dds_type & DDST_ARRAY) {
+ Ref<CubemapArray> texture = memnew(CubemapArray());
+ texture->create_from_images(images);
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ return texture;
+
+ } else {
+ Ref<Cubemap> texture = memnew(Cubemap());
+ texture->create_from_images(images);
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ return texture;
+ }
+
+ } else if ((dds_type & DDST_TYPE_MASK) == DDST_3D) {
+ Ref<ImageTexture3D> texture = memnew(ImageTexture3D());
+ texture->create(images[0]->get_format(), width, height, layer_count, mipmaps > 1, images);
+
+ if (r_error) {
+ *r_error = OK;
+ }
+
+ return texture;
}
- return texture;
+ return Ref<Resource>();
}
void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) const {
@@ -720,12 +814,12 @@ void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) co
}
bool ResourceFormatDDS::handles_type(const String &p_type) const {
- return ClassDB::is_parent_class(p_type, "Texture2D");
+ return ClassDB::is_parent_class(p_type, "Texture");
}
String ResourceFormatDDS::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "dds") {
- return "ImageTexture";
+ return "Texture";
}
return "";
}
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp
index 2ccfd5d326..9c9302a51c 100644
--- a/modules/enet/enet_connection.cpp
+++ b/modules/enet/enet_connection.cpp
@@ -113,7 +113,7 @@ Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int
if (peer == nullptr) {
return nullptr;
}
- out = Ref<ENetPacketPeer>(memnew(ENetPacketPeer(peer)));
+ out.instantiate(peer);
peers.push_back(out);
return out;
}
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index ce097092fb..d6c304d056 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -369,21 +369,25 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
// all skin clusters connected to the bone.
for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
- if (!child_cluster)
+ if (!child_cluster) {
continue;
+ }
ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
- if (!child_deformer)
+ if (!child_deformer) {
continue;
+ }
// Found a skin cluster: Now iterate through all the skin clusters of the parent and
// try to find one that used by the same deformer.
for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) {
ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst);
- if (!parent_cluster)
+ if (!parent_cluster) {
continue;
+ }
ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster);
- if (parent_deformer != child_deformer)
+ if (parent_deformer != child_deformer) {
continue;
+ }
// Success: Found two skin clusters from the same deformer, now we can resolve the
// local bind pose from the difference between the two world-space bind poses.
@@ -1389,7 +1393,7 @@ Error FBXDocument::_parse_animations(Ref<FBXState> p_state) {
for (const ufbx_baked_node &fbx_baked_node : fbx_baked_anim->nodes) {
const GLTFNodeIndex node = fbx_baked_node.typed_id;
- GLTFAnimation::Track &track = animation->get_tracks()[node];
+ GLTFAnimation::NodeTrack &track = animation->get_node_tracks()[node];
for (const ufbx_baked_vec3 &key : fbx_baked_node.translation_keys) {
track.position_track.times.push_back(float(key.time));
@@ -1779,8 +1783,8 @@ void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_an
double anim_start_offset = p_trimming ? double(additional_animation_data["time_begin"]) : 0.0;
- for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
- const GLTFAnimation::Track &track = track_i.value;
+ for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
+ const GLTFAnimation::NodeTrack &track = track_i.value;
//need to find the path: for skeletons, weight tracks will affect the mesh
NodePath node_path;
//for skeletons, transform tracks always affect bones
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 5fe47d69df..0355119442 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -4,8 +4,8 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
- A list of GDScript-specific utility functions and annotations accessible from any script.
- For the list of the global functions and constants see [@GlobalScope].
+ A list of utility functions and annotations accessible from any script written in GDScript.
+ For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
@@ -61,7 +61,7 @@
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
- <param index="1" name="type" type="int" />
+ <param index="1" name="type" type="int" enum="Variant.Type" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
@@ -666,7 +666,19 @@
@export var car_label = "Speedy"
@export var car_number = 3
[/codeblock]
- [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
+ [b]Note:[/b] Subgroups cannot be nested, but you can use the slash separator ([code]/[/code]) to achieve the desired effect:
+ [codeblock]
+ @export_group("Car Properties")
+ @export_subgroup("Wheels", "wheel_")
+ @export_subgroup("Wheels/Front", "front_wheel_")
+ @export var front_wheel_strength = 10
+ @export var front_wheel_mobility = 5
+ @export_subgroup("Wheels/Rear", "rear_wheel_")
+ @export var rear_wheel_strength = 8
+ @export var rear_wheel_mobility = 3
+ @export_subgroup("Wheels", "wheel_")
+ @export var wheel_material: PhysicsMaterial
+ [/codeblock]
</description>
</annotation>
<annotation name="@export_tool_button">
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 5f7a7e2915..c3fa59dc23 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -16,11 +16,10 @@
<return type="Variant" />
<description>
Returns a new instance of the script.
- For example:
[codeblock]
var MyClass = load("myclass.gd")
var instance = MyClass.new()
- assert(instance.get_script() == MyClass)
+ print(instance.get_script() == MyClass) # Prints true
[/codeblock]
</description>
</method>
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 32ef429b0d..3a5a88d356 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -140,7 +140,7 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
r_enum = r_enum.trim_prefix("res://");
- int dot_pos = r_enum.rfind(".");
+ int dot_pos = r_enum.rfind_char('.');
if (dot_pos >= 0) {
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
}
@@ -217,7 +217,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index d765cfa1ea..629581bd6c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -163,7 +163,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
- if (str.find("\\", from) >= 0) {
+ if (str.find_char('\\', from) >= 0) {
break;
}
}
@@ -236,7 +236,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
// Don't break if '\' to highlight esc chars.
- if (str.find("\\", from) < 0) {
+ if (str.find_char('\\', from) < 0) {
break;
}
}
@@ -701,7 +701,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- class_names[E] = types_color;
+ if (ClassDB::is_class_exposed(E)) {
+ class_names[E] = types_color;
+ }
}
/* User types. */
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
index b31ae878ce..172ad6be9f 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
@@ -51,6 +51,10 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;
+
+ ids_comment.clear();
+ ids_ctx_plural_comment.clear();
+
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
@@ -62,18 +66,90 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
err = analyzer.analyze();
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
+ comment_data = &parser.comment_data;
+
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
_traverse_class(c);
+ comment_data = nullptr;
+
return OK;
}
+void GDScriptEditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) {
+ r_ids_comment->append_array(ids_comment);
+ r_ids_ctx_plural_comment->append_array(ids_ctx_plural_comment);
+}
+
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
return p_expression->is_constant && p_expression->reduced_value.is_string();
}
+String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const {
+ // Parse inline comment.
+ if (comment_data->has(p_line)) {
+ const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges();
+
+ if (stripped_comment.begins_with("TRANSLATORS:")) {
+ return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
+ }
+ if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
+ r_skip = true;
+ return String();
+ }
+ }
+
+ // Parse multiline comment.
+ String multiline_comment;
+ for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) {
+ const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges();
+
+ if (stripped_comment.is_empty()) {
+ continue;
+ }
+
+ if (multiline_comment.is_empty()) {
+ multiline_comment = stripped_comment;
+ } else {
+ multiline_comment = stripped_comment + "\n" + multiline_comment;
+ }
+
+ if (stripped_comment.begins_with("TRANSLATORS:")) {
+ return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
+ }
+ if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
+ r_skip = true;
+ return String();
+ }
+ }
+
+ return String();
+}
+
+void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) {
+ bool skip = false;
+ const String comment = _parse_comment(p_line, skip);
+ if (skip) {
+ return;
+ }
+
+ ids->push_back(p_id);
+ ids_comment.push_back(comment);
+}
+
+void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) {
+ bool skip = false;
+ const String comment = _parse_comment(p_line, skip);
+ if (skip) {
+ return;
+ }
+
+ ids_ctx_plural->push_back(p_id_ctx_plural);
+ ids_ctx_plural_comment.push_back(comment);
+}
+
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
@@ -253,7 +329,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
- ids->push_back(p_assignment->assigned_value->reduced_value);
+ _add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line);
} else if (assignee_name == fd_filters) {
// Extract from `get_node("FileDialog").filters = <filter array>`.
_extract_fd_filter_array(p_assignment->assigned_value);
@@ -287,7 +363,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
- ids_ctx_plural->push_back(id_ctx_plural);
+ _add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (function_name == trn_func || function_name == atrn_func) {
// Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`.
@@ -307,20 +383,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
- ids_ctx_plural->push_back(id_ctx_plural);
+ _add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (first_arg_patterns.has(function_name)) {
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
- ids->push_back(p_call->arguments[0]->reduced_value);
+ _add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line);
}
} else if (second_arg_patterns.has(function_name)) {
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
- ids->push_back(p_call->arguments[1]->reduced_value);
+ _add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
if (!p_call->arguments.is_empty()) {
- _extract_fd_filter_string(p_call->arguments[0]);
+ _extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line);
}
} else if (function_name == fd_set_filter) {
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
@@ -330,12 +406,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
-void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
+void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) {
// Extract the name in "extension ; name".
if (_is_constant_string(p_expression)) {
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
- ids->push_back(arr[1].strip_edges());
+ _add_id(arr[1].strip_edges(), p_line);
}
}
@@ -355,7 +431,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr
if (array_node) {
for (int i = 0; i < array_node->elements.size(); i++) {
- _extract_fd_filter_string(array_node->elements[i]);
+ _extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line);
}
}
}
diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
index 61ff81ed66..73e8f53110 100644
--- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h
+++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h
@@ -32,16 +32,23 @@
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#include "../gdscript_parser.h"
+#include "../gdscript_tokenizer.h"
+#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "editor/editor_translation_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
+ const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr;
+
Vector<String> *ids = nullptr;
Vector<Vector<String>> *ids_ctx_plural = nullptr;
+ Vector<String> ids_comment;
+ Vector<String> ids_ctx_plural_comment;
+
// List of patterns used for extracting translation strings.
StringName tr_func = "tr";
StringName trn_func = "tr_n";
@@ -57,6 +64,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
+ String _parse_comment(int p_line, bool &r_skip) const;
+
+ void _add_id(const String &p_id, int p_line);
+ void _add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line);
+
void _traverse_class(const GDScriptParser::ClassNode *p_class);
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
@@ -65,11 +77,12 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
void _assess_call(const GDScriptParser::CallNode *p_call);
- void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
+ void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line);
void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
+ virtual void get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7b9aa70686..8c094c0ab0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -76,9 +76,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
- } else {
- return false;
}
+
+ MethodBind *method = ClassDB::get_method(name, p_name);
+ if (method && method->is_static()) {
+ // Native static method.
+ r_ret = Callable(this, p_name);
+ return true;
+ }
+
+ return false;
}
void GDScriptNativeClass::_bind_methods() {
@@ -693,10 +700,16 @@ void GDScript::_static_default_init() {
continue;
}
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
+ const GDScriptDataType element_type = type.get_container_element_type(0);
Array default_value;
- const GDScriptDataType &element_type = type.get_container_element_type(0);
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
static_variables.write[E.value.index] = default_value;
+ } else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
+ const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
+ const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
+ Dictionary default_value;
+ default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ static_variables.write[E.value.index] = default_value;
} else {
Variant default_value;
Callable::CallError err;
@@ -1062,6 +1075,26 @@ void GDScript::_bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
}
+void GDScript::set_path_cache(const String &p_path) {
+ if (ResourceCache::has(p_path)) {
+ set_path(p_path, true);
+ return;
+ }
+
+ if (is_root_script()) {
+ Script::set_path_cache(p_path);
+ }
+
+ String old_path = path;
+ path = p_path;
+ path_valid = true;
+ GDScriptCache::move_script(old_path, p_path);
+
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path_cache(p_path);
+ }
+}
+
void GDScript::set_path(const String &p_path, bool p_take_over) {
if (is_root_script()) {
Script::set_path(p_path, p_take_over);
@@ -1829,14 +1862,14 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
}
void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
- Variant property = (Dictionary)p_property;
- const Variant *args[1] = { &property };
-
const GDScript *sptr = script.ptr();
while (sptr) {
if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
if (E) {
+ Variant property = (Dictionary)p_property;
+ const Variant *args[1] = { &property };
+
Callable::CallError err;
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
if (err.error == Callable::CallError::CALL_OK) {
@@ -2540,11 +2573,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
-#endif
+#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@@ -2614,7 +2647,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
-#endif
+#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@@ -2682,7 +2715,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9bb39aac0f..006a09debb 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -300,6 +300,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
+ virtual void set_path_cache(const String &p_path) override;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
String get_script_path() const;
Error load_source_code(const String &p_path);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7f0d5005cb..6241ada06a 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
+static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
+
+ type.class_type = p_class;
+ type.script_path = p_script_path;
+
+ return type;
+}
+
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@@ -931,8 +940,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
- if (member_type.has_container_element_type(0)) {
- ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
+ for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
+ ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@@ -1101,7 +1110,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
member.m_enum->set_datatype(resolving_datatype);
- GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
+ GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -1194,7 +1203,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
- member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
+ member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -3807,6 +3816,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
+ // Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
+ // Search dependencies for the parser that owns `p_class` and make a cache entry for it.
+ // Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
+ // Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
+ // Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
+
if (p_class == nullptr) {
return nullptr;
}
@@ -3823,8 +3838,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
- String script_path = p_class->get_datatype().script_path;
-
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@@ -4249,7 +4262,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
- GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
+ GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
@@ -5796,8 +5809,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
#ifdef DEBUG_ENABLED
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
const StringName &name = p_identifier->name;
- GDScriptParser::DataType base = parser->current_class->get_datatype();
- GDScriptParser::ClassNode *base_class = base.class_type;
{
List<MethodInfo> gdscript_funcs;
@@ -5825,40 +5836,56 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
}
}
+ const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
if (p_in_local_scope) {
- while (base_class != nullptr) {
+ GDScriptParser::ClassNode *base_class = current_class_type.class_type;
+
+ if (base_class != nullptr) {
if (base_class->has_member(name)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
return;
}
base_class = base_class->base_type.class_type;
}
+
+ while (base_class != nullptr) {
+ if (base_class->has_member(name)) {
+ String base_class_name = base_class->get_global_name();
+ if (base_class_name.is_empty()) {
+ base_class_name = base_class->fqcn;
+ }
+
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
+ return;
+ }
+ base_class = base_class->base_type.class_type;
+ }
}
- StringName parent = base.native_type;
- while (parent != StringName()) {
- ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class.");
+ StringName native_base_class = current_class_type.native_type;
+ while (native_base_class != StringName()) {
+ ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
- if (ClassDB::has_method(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent);
+ if (ClassDB::has_method(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
return;
- } else if (ClassDB::has_signal(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent);
+ } else if (ClassDB::has_signal(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
return;
- } else if (ClassDB::has_property(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent);
+ } else if (ClassDB::has_property(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
return;
- } else if (ClassDB::has_integer_constant(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent);
+ } else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
return;
- } else if (ClassDB::has_enum(parent, name, true)) {
- parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent);
+ } else if (ClassDB::has_enum(native_base_class, name, true)) {
+ parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
return;
}
- parent = ClassDB::get_parent_class(parent);
+ native_base_class = ClassDB::get_parent_class(native_base_class);
}
}
-#endif
+#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 3c022412bd..fa22798edf 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -312,7 +312,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
Ref<GDScript> script;
script.instantiate();
- script->set_path(p_path, true);
+ script->set_path_cache(p_path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
@@ -360,6 +360,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
}
+ script->set_path(p_path, true);
const String remapped_path = ResourceLoader::path_remap(p_path);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index bc063693a3..d94a6dfda2 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -790,8 +790,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
@@ -833,8 +834,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += method->get_name();
text += "(";
for (int i = 0; i < argc; i++) {
- if (i > 0)
+ if (i > 0) {
text += ", ";
+ }
text += DADDR(1 + i);
}
text += ")";
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 0fd891aa80..d58cd2c3f7 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -43,6 +43,7 @@
#include "core/config/engine.h"
#include "core/core_constants.h"
#include "core/io/file_access.h"
+#include "core/math/expression.h"
#ifdef TOOLS_ENABLED
#include "core/config/project_settings.h"
@@ -427,7 +428,30 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
}
String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
- return "";
+ List<String> names;
+ List<Variant> values;
+ debug_get_stack_level_locals(p_level, &names, &values, p_max_subitems, p_max_depth);
+
+ Vector<String> name_vector;
+ for (const String &name : names) {
+ name_vector.push_back(name);
+ }
+
+ Array value_array;
+ for (const Variant &value : values) {
+ value_array.push_back(value);
+ }
+
+ Expression expression;
+ if (expression.parse(p_expression, name_vector) == OK) {
+ ScriptInstance *instance = debug_get_stack_level_instance(p_level);
+ if (instance) {
+ Variant return_val = expression.execute(value_array, instance->get_owner());
+ return return_val.get_construct_string();
+ }
+ }
+
+ return String();
}
void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const {
@@ -3164,7 +3188,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
HashMap<String, ScriptLanguage::CodeCompletionOption> options;
GDScriptParser::CompletionContext completion_context = parser.get_completion_context();
- completion_context.base = p_owner;
+ if (completion_context.current_class != nullptr && completion_context.current_class->outer == nullptr) {
+ completion_context.base = p_owner;
+ }
bool is_function = false;
switch (completion_context.type) {
@@ -3446,7 +3472,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
String arg = arg_itr->name;
if (arg.contains(":")) {
- arg = arg.substr(0, arg.find(":"));
+ arg = arg.substr(0, arg.find_char(':'));
}
method_hint += arg;
if (use_type_hint) {
@@ -3534,13 +3560,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return OK;
}
-#else
+#else // !TOOLS_ENABLED
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
-#endif
+#endif // TOOLS_ENABLED
//////// END COMPLETION //////////
@@ -3782,7 +3808,19 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
case GDScriptParser::DataType::ENUM: {
- if (base_type.enum_values.has(p_symbol)) {
+ if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) {
+ GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
+ for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
+ if (value.identifier && value.identifier->name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
+ r_result.class_path = base_type.script_path;
+ r_result.location = value.line;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
+ }
+ }
+ } else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
@@ -4113,4 +4151,4 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-#endif
+#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 111a39d730..12e71004db 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -244,7 +244,7 @@ void GDScriptParser::apply_pending_warnings() {
pending_warnings.clear();
}
-#endif
+#endif // DEBUG_ENABLED
void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
if (!for_completion) {
@@ -409,6 +409,10 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
parse_program();
pop_multiline();
+#ifdef TOOLS_ENABLED
+ comment_data = tokenizer->get_comments();
+#endif
+
memdelete(text_tokenizer);
tokenizer = nullptr;
@@ -1624,15 +1628,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- annotation->info = &valid_annotations[annotation->name];
+ if (valid) {
+ annotation->info = &valid_annotations[annotation->name];
- if (!annotation->applies_to(p_valid_targets)) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
- } else {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (!annotation->applies_to(p_valid_targets)) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
+ valid = false;
}
- valid = false;
}
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7f64ae902b..d40ba217c1 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -165,6 +165,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
+ _FORCE_INLINE_ int get_container_element_type_count() const {
+ return container_element_types.size();
+ }
+
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
@@ -1597,6 +1601,8 @@ public:
#ifdef TOOLS_ENABLED
static HashMap<String, String> theme_color_names;
+
+ HashMap<int, GDScriptTokenizer::CommentData> comment_data;
#endif // TOOLS_ENABLED
GDScriptParser();
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.h b/modules/gdscript/gdscript_tokenizer_buffer.h
index 55df66e50f..d5d2a4d096 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.h
+++ b/modules/gdscript/gdscript_tokenizer_buffer.h
@@ -79,7 +79,7 @@ public:
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
- virtual bool is_text() override { return false; };
+ virtual bool is_text() override { return false; }
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 59dd983ed2..8246069696 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -34,7 +34,6 @@
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
-#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
@@ -42,101 +41,105 @@
#ifdef DEBUG_ENABLED
-#define VALIDATE_ARG_COUNT(m_count) \
- if (p_arg_count < m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
- r_error.expected = m_count; \
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
+ if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
+ r_error.expected = m_min_count; \
return; \
} \
- if (p_arg_count > m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
- r_error.expected = m_count; \
+ if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
+ r_error.expected = m_max_count; \
return; \
}
-#define VALIDATE_ARG_INT(m_arg) \
- if (p_args[m_arg]->get_type() != Variant::INT) { \
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
+ if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
+ *r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
+ r_error.argument = m_arg; \
+ r_error.expected = m_type; \
+ return; \
+ }
+
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::INT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#define VALIDATE_ARG_NUM(m_arg) \
- if (!p_args[m_arg]->is_num()) { \
+#else // !DEBUG_ENABLED
+
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count)
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type)
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg)
+
+#endif // DEBUG_ENABLED
+
+#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::FLOAT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#else
-
-#define VALIDATE_ARG_COUNT(m_count)
-#define VALIDATE_ARG_INT(m_arg)
-#define VALIDATE_ARG_NUM(m_arg)
-
-#endif
+#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
+ return; \
+ }
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
- VALIDATE_ARG_INT(1);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+
int type = *p_args[1];
- if (type < 0 || type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::INT;
- return;
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::INT, type < 0 || type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument to convert(), use TYPE_* constants."));
- } else {
- Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
- if (r_error.error != Callable::CallError::CALL_OK) {
- *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
- }
- }
+ Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- VALIDATE_ARG_INT(0);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ DEBUG_VALIDATE_ARG_COUNT(1, 3);
switch (p_arg_count) {
- case 0: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 1;
- *r_ret = Variant();
- } break;
case 1: {
- VALIDATE_ARG_NUM(0);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+
int count = *p_args[0];
+
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
+
Error err = arr.resize(count);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = 0; i < count; i++) {
arr[i] = i;
@@ -145,8 +148,8 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
case 2: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
@@ -156,30 +159,26 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
return;
}
+
Error err = arr.resize(to - from);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
+
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
+
*r_ret = arr;
} break;
case 3: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- VALIDATE_ARG_NUM(2);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
- if (incr == 0) {
- *r_ret = RTR("Step argument is zero!");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+
+ VALIDATE_ARG_CUSTOM(2, Variant::INT, incr == 0, RTR("Step argument is zero!"));
Array arr;
if (from >= to && incr > 0) {
@@ -200,12 +199,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
Error err = arr.resize(count);
-
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
if (incr > 0) {
int idx = 0;
@@ -221,138 +215,79 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
- default: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
-
- } break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- if (!p_args[0]->is_string()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::STRING;
- *r_ret = Variant();
- } else {
- *r_ret = ResourceLoader::load(*p_args[0]);
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING);
+ *r_ret = ResourceLoader::load(*p_args[0]);
}
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
- } else if (p_args[0]->get_type() != Variant::OBJECT) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
+ return;
+ }
+
+ Object *obj = *p_args[0];
+ if (!obj) {
*r_ret = Variant();
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- *r_ret = Variant();
+ return;
+ }
- } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not a script with an instance");
- return;
- } else {
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
- Ref<GDScript> base = ins->get_script();
- if (base.is_null()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not based on a script");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT,
+ !obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton(),
+ RTR("Not a script with an instance."));
- GDScript *p = base.ptr();
- String path = p->get_script_path();
- Vector<StringName> sname;
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(obj->get_script_instance());
- while (p->_owner) {
- sname.push_back(p->local_name);
- p = p->_owner;
- }
- sname.reverse();
+ Ref<GDScript> base = inst->get_script();
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, base.is_null(), RTR("Not based on a script."));
- if (!path.is_resource_file()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
+ GDScript *p = base.ptr();
+ String path = p->get_script_path();
+ Vector<StringName> sname;
- *r_ret = RTR("Not based on a resource file");
+ while (p->_owner) {
+ sname.push_back(p->local_name);
+ p = p->_owner;
+ }
+ sname.reverse();
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, !path.is_resource_file(), RTR("Not based on a resource file."));
- NodePath cp(sname, Vector<StringName>(), false);
+ NodePath cp(sname, Vector<StringName>(), false);
- Dictionary d;
- d["@subpath"] = cp;
- d["@path"] = path;
+ Dictionary d;
+ d["@subpath"] = cp;
+ d["@path"] = path;
- for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
- if (!d.has(E.key)) {
- d[E.key] = ins->members[E.value.index];
- }
- }
- *r_ret = d;
+ for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
+ if (!d.has(E.key)) {
+ d[E.key] = inst->members[E.value.index];
}
}
+
+ *r_ret = d;
}
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
-
- if (p_args[0]->get_type() != Variant::DICTIONARY) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
-
- return;
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY);
Dictionary d = *p_args[0];
- if (!d.has("@path")) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (missing @path)");
-
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path)."));
Ref<Script> scr = ResourceLoader::load(d["@path"]);
- if (!scr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !scr.is_valid(), RTR("Invalid instance dictionary format (can't load script at @path)."));
Ref<GDScript> gdscr = scr;
-
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary format (invalid script at @path)."));
NodePath sub;
if (d.has("@subpath")) {
@@ -361,54 +296,35 @@ struct GDScriptUtilityFunctionsDefinitions {
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary (invalid subclasses)."));
}
- *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
+ *r_ret = gdscr->_new(nullptr, -1 /* skip initializer */, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = RTR("Cannot instantiate GDScript class.");
return;
}
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
- Ref<GDScript> gd_ref = ins->get_script();
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
+ Ref<GDScript> gd_ref = inst->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
- ins->members.write[E.value.index] = d[E.key];
+ inst->members.write[E.value.index] = d[E.key];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- if (p_arg_count < 3) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
- return;
- }
- if (p_arg_count > 4) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 4;
- *r_ret = Variant();
- return;
- }
-
- VALIDATE_ARG_INT(0);
- VALIDATE_ARG_INT(1);
- VALIDATE_ARG_INT(2);
+ DEBUG_VALIDATE_ARG_COUNT(3, 4);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
if (p_arg_count == 4) {
- VALIDATE_ARG_INT(3);
+ DEBUG_VALIDATE_ARG_TYPE(3, Variant::INT);
color.a = (int64_t)*p_args[3] / 255.0f;
}
@@ -435,7 +351,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
@@ -449,7 +366,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
@@ -468,7 +386,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
switch (p_args[0]->get_type()) {
case Variant::STRING:
case Variant::STRING_NAME: {
@@ -524,56 +442,34 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = d.size();
} break;
default: {
+ *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
- *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
- }
+ } break;
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
- if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::NIL, builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."));
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
- if (was_type_freed) {
- *r_ret = RTR("Type argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
- if (!type_object) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, was_type_freed, RTR("Type argument is a previously freed instance."));
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, !type_object,
+ RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."));
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
- if (was_value_freed) {
- *r_ret = RTR("Value argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::NIL, was_value_freed, RTR("Value argument is a previously freed instance."));
if (!value_object) {
*r_ret = false;
return;
@@ -618,113 +514,77 @@ struct GDScriptUtilityFunctionInfo {
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
-static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
- StringName sname(p_name);
-
- ERR_FAIL_COND(utility_function_table.has(sname));
+static void _register_function(const StringName &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
+ ERR_FAIL_COND(utility_function_table.has(p_name));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
- utility_function_table.insert(sname, function);
- utility_function_name_table.push_back(sname);
+ utility_function_table.insert(p_name, function);
+ utility_function_name_table.push_back(p_name);
}
-#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
+#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ name = name.substr(1); \
} \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
-
-#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ MethodInfo info = m_args; \
+ info.name = name; \
+ info.return_val = m_return; \
+ info.default_arguments = m_default_args; \
+ if (m_is_vararg) { \
+ info.flags |= METHOD_FLAG_VARARG; \
} \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
-#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
- info.flags |= METHOD_FLAG_VARARG; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RET(m_type) \
+ PropertyInfo(Variant::m_type, "")
-#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::NIL; \
- info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETVAR \
+ PropertyInfo(Variant::NIL, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::OBJECT; \
- info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
- info.return_val.class_name = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETCLS(m_class) \
+ PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, m_class)
-#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- info.default_arguments.push_back(m_default); \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define NOARGS \
+ MethodInfo()
+
+#define ARGS(...) \
+ MethodInfo("", __VA_ARGS__)
#define ARG(m_name, m_type) \
- PropertyInfo(m_type, m_name)
+ PropertyInfo(Variant::m_type, m_name)
+
+#define ARGVAR(m_name) \
+ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define VARARG(m_name) \
- PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
+#define ARGTYPE(m_name) \
+ PropertyInfo(Variant::INT, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "Variant.Type")
void GDScriptUtilityFunctions::register_functions() {
+ /* clang-format off */
#ifndef DISABLE_DEPRECATED
- REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
+ REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
- REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
- REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
- REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
- REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
- REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
- REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
- REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
- REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
- REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
- REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
+ REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
+ REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("char", INT) ), false, varray( ));
+ REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));
+ REGISTER_FUNC( load, false, RETCLS("Resource"), ARGS( ARG("path", STRING) ), false, varray( ));
+ REGISTER_FUNC( inst_to_dict, false, RET(DICTIONARY), ARGS( ARG("instance", OBJECT) ), false, varray( ));
+ REGISTER_FUNC( dict_to_inst, false, RET(OBJECT), ARGS( ARG("dictionary", DICTIONARY) ), false, varray( ));
+ REGISTER_FUNC( Color8, true, RET(COLOR), ARGS( ARG("r8", INT), ARG("g8", INT),
+ ARG("b8", INT), ARG("a8", INT) ), false, varray( 255 ));
+ REGISTER_FUNC( print_debug, false, RET(NIL), NOARGS, true, varray( ));
+ REGISTER_FUNC( print_stack, false, RET(NIL), NOARGS, false, varray( ));
+ REGISTER_FUNC( get_stack, false, RET(ARRAY), NOARGS, false, varray( ));
+ REGISTER_FUNC( len, true, RET(INT), ARGS( ARGVAR("var") ), false, varray( ));
+ REGISTER_FUNC( is_instance_of, true, RET(BOOL), ARGS( ARGVAR("value"), ARGVAR("type") ), false, varray( ));
+ /* clang-format on */
}
void GDScriptUtilityFunctions::unregister_functions() {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index d8139d913a..26c5cfe23c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -397,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
-#else
+#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#endif
+#endif // DEBUG_ENABLED
+
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
-#else
+#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
-#else
+#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
-#endif
+#endif // _MSC_VER
+
#define OPCODE_BREAK break
#define OPCODE_OUT break
-#endif
+#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@@ -663,7 +667,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#else
+#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@@ -676,7 +680,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#endif
+#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@@ -1965,7 +1969,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
-#endif
+#endif // DEBUG_ENABLED
ip += 3;
}
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 4ffb4bd9d1..a601cc4993 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4);
- return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
+ if (symbols.size() > 4) {
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
+ }
+ return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index ffcf00a830..99e9b30af5 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -53,8 +53,8 @@ public:
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
- SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
- SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
+ SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
+ SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
diff --git a/modules/gdscript/tests/scripts/.editorconfig b/modules/gdscript/tests/scripts/.editorconfig
index da1efefe3c..34fff8d1de 100644
--- a/modules/gdscript/tests/scripts/.editorconfig
+++ b/modules/gdscript/tests/scripts/.editorconfig
@@ -1,5 +1,3 @@
-# This file is required to workaround `.editorconfig` autogeneration (see #96845).
-
# Some tests handle invalid syntax deliberately; exclude relevant attributes.
[parser/features/mixed_indentation_on_blank_lines.gd]
diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
index 94e2ec2af8..fb616f1e94 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
@@ -1,2 +1,2 @@
GDTEST_OK
-0
+0.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index 5318d11f33..e91c7386fe 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -7,17 +7,17 @@ const const_packed_ints: PackedFloat64Array = [52]
func test():
Utils.check(typeof(const_float_int) == TYPE_FLOAT)
- Utils.check(str(const_float_int) == '19')
+ Utils.check(str(const_float_int) == '19.0')
Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
- Utils.check(str(const_float_plus) == '34')
+ Utils.check(str(const_float_plus) == '34.0')
Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
- Utils.check(str(const_float_cast) == '76')
+ Utils.check(str(const_float_cast) == '76.0')
Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
Utils.check(str(const_packed_empty) == '[]')
Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(str(const_packed_ints) == '[52.0]')
Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- Utils.check(str(const_packed_ints[0]) == '52')
+ Utils.check(str(const_packed_ints[0]) == '52.0')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
index 15666c46ad..abf11548cb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
@@ -1,2 +1,2 @@
GDTEST_OK
-4
+4.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index fe0274c27b..eb53d0a700 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -54,39 +54,39 @@ func test():
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
- Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
- Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
- Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(untyped_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(inferred_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(typed_basic) == '[705.0, 430.0, 263.0, 518.0]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(str(filled_floats) == '[950.0, 170.0, 954.0, 693.0]')
Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- Utils.check(str(casted_floats) == '[526]')
+ Utils.check(str(casted_floats) == '[526.0]')
Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- Utils.check(str(returned_floats) == '[554]')
+ Utils.check(str(returned_floats) == '[554.0]')
Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- Utils.check(str(passed_floats) == '[663]')
+ Utils.check(str(passed_floats) == '[663.0]')
Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- Utils.check(str(default_floats) == '[364]')
+ Utils.check(str(default_floats) == '[364.0]')
Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(str(converted_floats) == '[556.0, 498.0]')
Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -95,7 +95,7 @@ func test():
Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- Utils.check(str(constant_floats) == '[552]')
+ Utils.check(str(constant_floats) == '[552.0]')
Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -103,15 +103,15 @@ func test():
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- Utils.check(str(source_floats) == '[999]')
- Utils.check(str(untyped_basic) == '[999]')
- Utils.check(str(destination_floats) == '[999]')
+ Utils.check(str(source_floats) == '[999.0]')
+ Utils.check(str(untyped_basic) == '[999.0]')
+ Utils.check(str(destination_floats) == '[999.0]')
Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(str(duplicated_floats) == '[789.0]')
Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
index 9d3fffd1de..c9ab368f45 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -62,44 +62,44 @@ func test():
untyped_basic[430.0] = 34.0
inferred_basic[263.0] = 362.0
typed_basic[518.0] = 815.0
- Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(empty_floats) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(untyped_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(inferred_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(typed_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
- Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
+ Utils.check(str(filled_floats) == '{ 950.0: 170.0, 954.0: 396.0 }')
Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
- Utils.check(str(casted_floats) == '{ 724: 181 }')
+ Utils.check(str(casted_floats) == '{ 724.0: 181.0 }')
Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
- Utils.check(str(returned_floats) == '{ 554: 455 }')
+ Utils.check(str(returned_floats) == '{ 554.0: 455.0 }')
Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
- Utils.check(str(passed_floats) == '{ 663: 366 }')
+ Utils.check(str(passed_floats) == '{ 663.0: 366.0 }')
Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
- Utils.check(str(default_floats) == '{ 364: 463 }')
+ Utils.check(str(default_floats) == '{ 364.0: 463.0 }')
Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
converted_floats[498.0] = 894
- Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
+ Utils.check(str(converted_floats) == '{ 556.0: 556.0, 498.0: 894.0 }')
Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -110,7 +110,7 @@ func test():
Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
- Utils.check(str(constant_floats) == '{ -42: 1942 }')
+ Utils.check(str(constant_floats) == '{ -42.0: 1942.0 }')
Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -119,9 +119,9 @@ func test():
untyped_basic = source_floats
var destination_floats: Dictionary[float, float] = untyped_basic
destination_floats[999.74] -= 0.999
- Utils.check(str(source_floats) == '{ 999.74: 47 }')
- Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
- Utils.check(str(destination_floats) == '{ 999.74: 47 }')
+ Utils.check(str(source_floats) == '{ 999.74: 47.0 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47.0 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47.0 }')
Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -131,7 +131,7 @@ func test():
duplicated_floats.erase(430.0)
duplicated_floats.erase(518.0)
duplicated_floats[263.0] *= 3
- Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
+ Utils.check(str(duplicated_floats) == '{ 263.0: 1086.0 }')
Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
index 0e0d607831..cfe91e00bd 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage.out
@@ -6,6 +6,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
index 228a510490..ae0f2d8b8b 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_initializer.out
@@ -10,6 +10,6 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
index 0d20e9f7a0..101d27df9d 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_local_usage_loop.out
@@ -6,7 +6,7 @@ GDTEST_OK
>> WARNING
>> Line: 6
>> SHADOWED_VARIABLE
->> The local variable "a" is shadowing an already-declared variable at line 1.
+>> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1
2
1
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
index a98d80514c..5d059b9193 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 4
>> SHADOWED_VARIABLE
->> The local function parameter "shadow" is shadowing an already-declared variable at line 1.
+>> The local function parameter "shadow" is shadowing an already-declared variable at line 1 in the current class.
shadow
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
new file mode 100644
index 0000000000..5819246ded
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowing_base.notest.gd
@@ -0,0 +1,7 @@
+class_name ShadowingBase
+
+const base_const_member = 1
+var base_variable_member
+
+func base_function_member():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
index 939e787ea5..6a16ae6bcc 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
@@ -1,4 +1,5 @@
class_name ShadowedClass
+extends ShadowingBase
var member: int = 0
@@ -7,6 +8,7 @@ var print_debug := 'print_debug'
var print := 'print'
@warning_ignore("unused_variable")
+@warning_ignore("unused_local_constant")
func test():
var Array := 'Array'
var Node := 'Node'
@@ -15,5 +17,8 @@ func test():
var member := 'member'
var reference := 'reference'
var ShadowedClass := 'ShadowedClass'
+ var base_variable_member
+ const base_function_member = 1
+ var base_const_member
print('warn')
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
index 8297eed4b8..075f5d3225 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
@@ -1,34 +1,46 @@
GDTEST_OK
>> WARNING
->> Line: 5
+>> Line: 6
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "print_debug" has the same name as a built-in function.
>> WARNING
->> Line: 11
+>> Line: 13
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Array" has the same name as a built-in type.
>> WARNING
->> Line: 12
+>> Line: 14
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Node" has the same name as a native class.
>> WARNING
->> Line: 13
+>> Line: 15
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "is_same" has the same name as a built-in function.
>> WARNING
->> Line: 14
+>> Line: 16
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "sqrt" has the same name as a built-in function.
>> WARNING
->> Line: 15
+>> Line: 17
>> SHADOWED_VARIABLE
->> The local variable "member" is shadowing an already-declared variable at line 3.
+>> The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
>> WARNING
->> Line: 16
+>> Line: 18
>> SHADOWED_VARIABLE_BASE_CLASS
->> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted".
+>> The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
>> WARNING
->> Line: 17
+>> Line: 19
>> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
+>> WARNING
+>> Line: 20
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 21
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase".
+>> WARNING
+>> Line: 22
+>> SHADOWED_VARIABLE_BASE_CLASS
+>> The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase".
warn
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
index d3dea6b12b..082c87e708 100644
--- a/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
+++ b/modules/gdscript/tests/scripts/completion/argument_options/argument_options.tscn
@@ -1,3 +1,16 @@
[gd_scene load_steps=1 format=3 uid="uid://dl28pdkxcjvym"]
+[sub_resource type="Animation" id="Animation_d1pub"]
+resource_name = "bounce"
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_gs7mj"]
+_data = {
+"bounce": SubResource("Animation_d1pub")
+}
+
[node name="GetNode" type="Node"]
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = {
+"": SubResource("AnimationLibrary_gs7mj")
+}
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd
new file mode 100644
index 0000000000..abeadbe5ee
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_inferred.gd
@@ -0,0 +1,5 @@
+@onready var anim := $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd
new file mode 100644
index 0000000000..d11f81e985
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_typed.gd
@@ -0,0 +1,5 @@
+@onready var anim: AnimationPlayer = $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg
new file mode 100644
index 0000000000..ca108a18c2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.cfg
@@ -0,0 +1,6 @@
+[input]
+scene="res://completion/argument_options/argument_options.tscn"
+[output]
+include=[
+ {"display": "\"bounce\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd
new file mode 100644
index 0000000000..4ddfd21ac6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/play_untyped.gd
@@ -0,0 +1,5 @@
+@onready var anim = $AnimationPlayer
+
+func test():
+ anim.play(➡)
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
new file mode 100644
index 0000000000..cfacb6a010
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
@@ -0,0 +1,3 @@
+@export
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
new file mode 100644
index 0000000000..ed677cd39a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Annotation "@export" cannot be applied to a function.
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
new file mode 100644
index 0000000000..a6b171a428
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
@@ -0,0 +1,3 @@
+@hello_world
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
new file mode 100644
index 0000000000..540e66f15b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unrecognized annotation: "@hello_world".
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
index f1522d096f..7201d8082d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
@@ -80,21 +80,21 @@ var test_placeholder: Array
var test_placeholder_packed: PackedStringArray
hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed_byte: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed32: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed64: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_float_step: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float: Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed32: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed64: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing: Array
hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing_packed32: PackedFloat32Array
@@ -126,14 +126,14 @@ var test_weak_packed_vector3_array: PackedVector3Array
var test_weak_packed_vector4_array: PackedVector4Array
hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_byte_array: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int32_array: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int64_array: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float32_array: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float64_array: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_noalpha_weak_packed_color_array: PackedColorArray
hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index 0d915e00e6..c0bf4d6e06 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -4,11 +4,11 @@ var test_weak_int: int = 1
var test_hard_int: int = 2
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range: int = 100
- hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step: int = 101
- hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step_or_greater: int = 102
- hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color: Color = Color(0, 0, 0, 1)
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color_no_alpha: Color = Color(0, 0, 0, 1)
diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
index c5958365ec..d94cbe5556 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
@@ -13,4 +13,4 @@ true
0
-255
256
-2
+2.0
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
index b0d2fd94fe..9407af9cd8 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -13,12 +13,12 @@ GDTEST_OK
---
-1234.4567
-1234.4567
--1234
--1234
+-1234.0
+-1234.0
0.4567
0.4567
---
--1234500
--1234500
--1234500
--1234500
+-1234500.0
+-1234500.0
+-1234500.0
+-1234500.0
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
index b0cb63ef59..29910adf38 100644
--- a/modules/gdscript/tests/scripts/parser/features/operator_assign.out
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
@@ -1,2 +1,2 @@
GDTEST_OK
-8
+8.0
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
index 75fa01f928..04df229f66 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local constant "TEST" is shadowing an already-declared constant at line 2.
+>> The local constant "TEST" is shadowing an already-declared constant at line 2 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
index aab27e78e2..4a6964f503 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 8
>> SHADOWED_VARIABLE
->> The local variable "foo" is shadowing an already-declared variable at line 1.
+>> The local variable "foo" is shadowing an already-declared variable at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
index e3cd358126..45fb771829 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out
@@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING
>> Line: 2
>> SHADOWED_VARIABLE
->> The local variable "test" is shadowing an already-declared function at line 1.
+>> The local variable "test" is shadowing an already-declared function at line 1 in the current class.
diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
index 22929bf636..04b0773991 100644
--- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
+++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
@@ -1,7 +1,7 @@
GDTEST_OK
-{ 1: (2, 0) }
-{ 3: (4, 0) }
-[[(5, 0)]]
-[[(6, 0)]]
-[[(7, 0)]]
-[X: (8, 9, 7), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+{ 1: (2.0, 0.0) }
+{ 3: (4.0, 0.0) }
+[[(5.0, 0.0)]]
+[[(6.0, 0.0)]]
+[[(7.0, 0.0)]]
+[X: (8.0, 9.0, 7.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
index a9ef4919cf..78ea2a2d80 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
@@ -1,8 +1,8 @@
GDTEST_OK
-x is 1
+x is 1.0
typeof x is 3
-x is 2
+x is 2.0
typeof x is 3
-x is 3
+x is 3.0
typeof x is 3
ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
new file mode 100644
index 0000000000..59bdb6eceb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
@@ -0,0 +1,12 @@
+func test():
+ const COLOR = Color8(255, 0.0, false)
+ var false_value := false
+ @warning_ignore("narrowing_conversion")
+ var color = Color8(255, 0.0, false_value)
+ print(var_to_str(COLOR))
+ print(var_to_str(color))
+
+ var string := "Node"
+ var string_name := &"Node"
+ print(type_exists(string))
+ print(type_exists(string_name))
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
new file mode 100644
index 0000000000..00913faa49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Color(1, 0, 0, 1)
+Color(1, 0, 0, 1)
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
new file mode 100644
index 0000000000..63d5935d1e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
@@ -0,0 +1,8 @@
+func get_parse_string(t: Variant):
+ return t.parse_string
+
+func test():
+ var a: Callable = JSON.parse_string
+ var b: Callable = get_parse_string(JSON)
+ prints(a.call("{\"test\": \"a\"}"), a.is_valid())
+ prints(b.call("{\"test\": \"b\"}"), b.is_valid())
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
new file mode 100644
index 0000000000..a2cb4b9a07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "test": "a" } false
+{ "test": "b" } false
diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
index 5b981bc8bb..1650acadb5 100644
--- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
+++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
@@ -2,16 +2,16 @@ GDTEST_OK
>> WARNING
>> Line: 5
>> SHADOWED_VARIABLE
->> The local function parameter "a" is shadowing an already-declared variable at line 3.
+>> The local function parameter "a" is shadowing an already-declared variable at line 3 in the current class.
>> WARNING
>> Line: 15
>> SHADOWED_VARIABLE
->> The local function parameter "v" is shadowing an already-declared variable at line 13.
+>> The local function parameter "v" is shadowing an already-declared variable at line 13 in the current class.
a
1
b
1
-(1, 1)
-(0, 0)
-(6, 1)
-(0, 0)
+(1.0, 1.0)
+(0.0, 0.0)
+(6.0, 1.0)
+(0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
index c51759f481..e82e31bbed 100644
--- a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
@@ -1,26 +1,26 @@
GDTEST_OK
===
-prop1 setter (0, 0)
-prop1 setter (1, 0)
+prop1 setter (0.0, 0.0)
+prop1 setter (1.0, 0.0)
---
prop1 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop2 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
-prop3 setter (0, 0)
+prop3 setter (0.0, 0.0)
prop3 getter
-prop3 setter (1, 0)
+prop3 setter (1.0, 0.0)
---
prop3 setter <Inner>
prop3 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop4 setter <Inner>
prop4 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
index 31b3b3a3a8..8617a65c33 100644
--- a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
@@ -1,4 +1,4 @@
GDTEST_OK
-setting vec from (0, 0) to (2, 0)
-setting vec from (0, 0) to (0, 2)
-vec is (0, 0)
+setting vec from (0.0, 0.0) to (2.0, 0.0)
+setting vec from (0.0, 0.0) to (0.0, 2.0)
+vec is (0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out
index 7833b6e213..2463d70ef4 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.out
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out
@@ -9,13 +9,13 @@ hello world
[P: (0, 0), S: (0, 0)]
(0.25, 0.25, 0.25)
(0, 0, 0)
-[X: (1, 0), Y: (0, 1), O: (0, 0)]
-[N: (1, 2, 3), D: 4]
-(1, 2, 3, 4)
-[P: (0, 0, 0), S: (1, 1, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+[X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)]
+[N: (1.0, 2.0, 3.0), D: 4]
(1, 2, 3, 4)
+[P: (0.0, 0.0, 0.0), S: (1.0, 1.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
+(1.0, 2.0, 3.0, 4.0)
hello
hello/world
RID(0)
@@ -26,10 +26,10 @@ Node::[signal]property_list_changed
[255, 0, 1]
[-1, 0, 1]
[-1, 0, 1]
-[-1, 0, 1]
-[-1, 0, 1]
+[-1.0, 0.0, 1.0]
+[-1.0, 0.0, 1.0]
["hello", "world"]
-[(1, 1), (0, 0)]
-[(1, 1, 1), (0, 0, 0)]
-[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)]
+[(1.0, 1.0), (0.0, 0.0)]
+[(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)]
+[(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)]
[(1, 1, 1, 1), (0, 0, 0, 0)]
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index fa289e442f..225bcb3008 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,6 +1,5 @@
class_name Utils
-
# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
# for anything other than testing the `assert()` itself.
static func check(condition: Variant) -> void:
diff --git a/modules/gltf/config.py b/modules/gltf/config.py
index 67233db579..823b8dbec2 100644
--- a/modules/gltf/config.py
+++ b/modules/gltf/config.py
@@ -20,6 +20,7 @@ def get_doc_classes():
"GLTFLight",
"GLTFMesh",
"GLTFNode",
+ "GLTFObjectModelProperty",
"GLTFPhysicsBody",
"GLTFPhysicsShape",
"GLTFSkeleton",
diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml
index bc142797a3..271f098803 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -22,7 +22,7 @@
The offset relative to the start of the buffer view in bytes.
</member>
<member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0">
- The glTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices.
+ The glTF component type as an enum. See [enum GLTFComponentType] for possible values. Within the core glTF specification, a value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices.
</member>
<member name="count" type="int" setter="set_count" getter="get_count" default="0">
The number of elements referenced by this accessor.
@@ -80,5 +80,41 @@
<constant name="TYPE_MAT4" value="6" enum="GLTFAccessorType">
Accessor type "MAT4". For the glTF object model, this maps to "float4x4", represented in the glTF JSON as an array of sixteen floats.
</constant>
+ <constant name="COMPONENT_TYPE_NONE" value="0" enum="GLTFComponentType">
+ Component type "NONE". This is not a valid component type, and is used to indicate that the component type is not set.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_BYTE" value="5120" enum="GLTFComponentType">
+ Component type "BYTE". The value is [code]0x1400[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit signed integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_BYTE" value="5121" enum="GLTFComponentType">
+ Component type "UNSIGNED_BYTE". The value is [code]0x1401[/code] which comes from OpenGL. This indicates data is stored in 1-byte or 8-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_SHORT" value="5122" enum="GLTFComponentType">
+ Component type "SHORT". The value is [code]0x1402[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit signed integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_SHORT" value="5123" enum="GLTFComponentType">
+ Component type "UNSIGNED_SHORT". The value is [code]0x1403[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_INT" value="5124" enum="GLTFComponentType">
+ Component type "INT". The value is [code]0x1404[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_INT" value="5125" enum="GLTFComponentType">
+ Component type "UNSIGNED_INT". The value is [code]0x1405[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit unsigned integers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_SINGLE_FLOAT" value="5126" enum="GLTFComponentType">
+ Component type "FLOAT". The value is [code]0x1406[/code] which comes from OpenGL. This indicates data is stored in 4-byte or 32-bit floating-point numbers. This is a core part of the glTF specification.
+ </constant>
+ <constant name="COMPONENT_TYPE_DOUBLE_FLOAT" value="5130" enum="GLTFComponentType">
+ Component type "DOUBLE". The value is [code]0x140A[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit floating-point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_HALF_FLOAT" value="5131" enum="GLTFComponentType">
+ Component type "HALF_FLOAT". The value is [code]0x140B[/code] which comes from OpenGL. This indicates data is stored in 2-byte or 16-bit floating-point numbers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_SIGNED_LONG" value="5134" enum="GLTFComponentType">
+ Component type "LONG". The value is [code]0x140E[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit signed integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
+ <constant name="COMPONENT_TYPE_UNSIGNED_LONG" value="5135" enum="GLTFComponentType">
+ Component type "UNSIGNED_LONG". The value is [code]0x140F[/code] which comes from OpenGL. This indicates data is stored in 8-byte or 64-bit unsigned integers. This is NOT a core part of the glTF specification, and may not be supported by all glTF importers. May be used by some extensions including [code]KHR_interactivity[/code].
+ </constant>
</constants>
</class>
diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml
index d269145bbd..f11dea8ee8 100644
--- a/modules/gltf/doc_classes/GLTFAnimation.xml
+++ b/modules/gltf/doc_classes/GLTFAnimation.xml
@@ -13,7 +13,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml
index b7f499ad72..9c2ee3197f 100644
--- a/modules/gltf/doc_classes/GLTFBufferView.xml
+++ b/modules/gltf/doc_classes/GLTFBufferView.xml
@@ -34,10 +34,10 @@
The stride, in bytes, between interleaved data. If [code]-1[/code], this buffer view is not interleaved.
</member>
<member name="indices" type="bool" setter="set_indices" getter="get_indices" default="false">
- True if the GLTFBufferView's OpenGL GPU buffer type is an [code]ELEMENT_ARRAY_BUFFER[/code] used for vertex indices (integer constant [code]34963[/code]). False if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
+ [code]true[/code] if the GLTFBufferView's OpenGL GPU buffer type is an [code]ELEMENT_ARRAY_BUFFER[/code] used for vertex indices (integer constant [code]34963[/code]). [code]false[/code] if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
</member>
<member name="vertex_attributes" type="bool" setter="set_vertex_attributes" getter="get_vertex_attributes" default="false">
- True if the GLTFBufferView's OpenGL GPU buffer type is an [code]ARRAY_BUFFER[/code] used for vertex attributes (integer constant [code]34962[/code]). False if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
+ [code]true[/code] if the GLTFBufferView's OpenGL GPU buffer type is an [code]ARRAY_BUFFER[/code] used for vertex attributes (integer constant [code]34962[/code]). [code]false[/code] if the buffer type is any other value. See [url=https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md]Buffers, BufferViews, and Accessors[/url] for possible values. This property is set on import and used on export.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml
index 9fce21659c..12334683ba 100644
--- a/modules/gltf/doc_classes/GLTFCamera.xml
+++ b/modules/gltf/doc_classes/GLTFCamera.xml
@@ -47,13 +47,13 @@
The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]znear[/code] property.
</member>
<member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.309">
- The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true.
+ The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is [code]true[/code].
</member>
<member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">
- Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information.
+ If [code]true[/code], the camera is in perspective mode. Otherwise, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information.
</member>
<member name="size_mag" type="float" setter="set_size_mag" getter="get_size_mag" default="0.5">
- The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false.
+ The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is [code]false[/code].
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 10534594d3..47ffc624ba 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -45,6 +45,16 @@
Takes a Godot Engine scene node and exports it and its descendants to the given [GLTFState] object through the [param state] parameter.
</description>
</method>
+ <method name="export_object_model_property" qualifiers="static">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="node_path" type="NodePath" />
+ <param index="2" name="godot_node" type="Node" />
+ <param index="3" name="gltf_node_index" type="int" />
+ <description>
+ Determines a mapping between the given Godot [param node_path] and the corresponding glTF Object Model JSON pointer(s) in the generated glTF file. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._import_object_model_property] callback method.
+ </description>
+ </method>
<method name="generate_buffer">
<return type="PackedByteArray" />
<param index="0" name="state" type="GLTFState" />
@@ -70,12 +80,20 @@
[b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered.
</description>
</method>
+ <method name="import_object_model_property" qualifiers="static">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="json_pointer" type="String" />
+ <description>
+ Determines a mapping between the given glTF Object Model [param json_pointer] and the corresponding Godot node path(s) in the generated Godot scene. The details of this mapping are returned in a [GLTFObjectModelProperty] object. Additional mappings can be supplied via the [method GLTFDocumentExtension._export_object_model_property] callback method.
+ </description>
+ </method>
<method name="register_gltf_document_extension" qualifiers="static">
<return type="void" />
<param index="0" name="extension" type="GLTFDocumentExtension" />
<param index="1" name="first_priority" type="bool" default="false" />
<description>
- Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is true, this extension will be run first. Otherwise, it will be run last.
+ Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is [code]true[/code], this extension will be run first. Otherwise, it will be run last.
[b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode].
</description>
</method>
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index b33e296e1c..abdbce7eeb 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -30,7 +30,21 @@
<param index="3" name="node" type="Node" />
<description>
Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json].
- This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting glTF data not generated from a Godot scene.
+ This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [Node] is also provided if available. [param node] may be [code]null[/code] if not available, such as when exporting glTF data not generated from a Godot scene.
+ </description>
+ </method>
+ <method name="_export_object_model_property" qualifiers="virtual">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="node_path" type="NodePath" />
+ <param index="2" name="godot_node" type="Node" />
+ <param index="3" name="gltf_node_index" type="int" />
+ <param index="4" name="target_object" type="Object" />
+ <param index="5" name="target_depth" type="int" />
+ <description>
+ Part of the export process. Allows GLTFDocumentExtension classes to provide mappings for properties of nodes in the Godot scene tree, to JSON pointers to glTF properties, as defined by the glTF object model.
+ Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return [code]null[/code] or an instance without any JSON pointers (see [method GLTFObjectModelProperty.has_json_pointers]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and set the JSON pointer(s) using the [member GLTFObjectModelProperty.json_pointers] property.
+ The parameters provide context for the property, including the NodePath, the Godot node, the GLTF node index, and the target object. The [param target_object] will be equal to [param godot_node] if no sub-object can be found, otherwise it will point to a sub-object. For example, if the path is [code]^"A/B/C/MeshInstance3D:mesh:surface_0/material:emission_intensity"[/code], it will get the node, then the mesh, and then the material, so [param target_object] will be the [Material] resource, and [param target_depth] will be 2 because 2 levels were traversed to get to the target.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
@@ -75,7 +89,7 @@
<description>
Part of the import process. This method is run after [method _import_pre_generate] and before [method _import_node].
Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
- [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node.
+ [b]Note:[/b] The [param scene_parent] parameter may be [code]null[/code] if this is the single root node.
</description>
</method>
<method name="_get_image_file_extension" qualifiers="virtual">
@@ -109,6 +123,17 @@
This method can be used to make modifications to each of the generated Godot scene nodes.
</description>
</method>
+ <method name="_import_object_model_property" qualifiers="virtual">
+ <return type="GLTFObjectModelProperty" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="split_json_pointer" type="PackedStringArray" />
+ <param index="2" name="partial_paths" type="NodePath[]" />
+ <description>
+ Part of the import process. Allows GLTFDocumentExtension classes to provide mappings for JSON pointers to glTF properties, as defined by the glTF object model, to properties of nodes in the Godot scene tree.
+ Returns a [GLTFObjectModelProperty] instance that defines how the property should be mapped. If your extension can't handle the property, return [code]null[/code] or an instance without any NodePaths (see [method GLTFObjectModelProperty.has_node_paths]). You should use [method GLTFObjectModelProperty.set_types] to set the types, and [method GLTFObjectModelProperty.append_path_to_property] function is useful for most simple cases.
+ In many cases, [param partial_paths] will contain the start of a path, allowing the extension to complete the path. For example, for [code]/nodes/3/extensions/MY_ext/prop[/code], Godot will pass you a NodePath that leads to node 3, so the GLTFDocumentExtension class only needs to resolve the last [code]MY_ext/prop[/code] part of the path. In this example, the extension should check [code]split.size() &gt; 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"[/code] at the start of the function to check if this JSON pointer applies to it, then it can use [param partial_paths] and handle [code]split[4][/code].
+ </description>
+ </method>
<method name="_import_post" qualifiers="virtual">
<return type="int" enum="Error" />
<param index="0" name="state" type="GLTFState" />
diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml
index da73c20c1d..439078ffcc 100644
--- a/modules/gltf/doc_classes/GLTFMesh.xml
+++ b/modules/gltf/doc_classes/GLTFMesh.xml
@@ -15,7 +15,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index a242a0d1d8..0fb85e4029 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -24,7 +24,16 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
+ </description>
+ </method>
+ <method name="get_scene_node_path">
+ <return type="NodePath" />
+ <param index="0" name="gltf_state" type="GLTFState" />
+ <param index="1" name="handle_skeletons" type="bool" default="true" />
+ <description>
+ Returns the [NodePath] that this GLTF node will have in the Godot scene tree after being imported. This is useful when importing glTF object model pointers with [GLTFObjectModelProperty], for handling extensions such as [code]KHR_animation_pointer[/code] or [code]KHR_interactivity[/code].
+ If [param handle_skeletons] is [code]true[/code], paths to skeleton bone glTF nodes will be resolved properly. For example, a path that would be [code]^"A/B/C/Bone1/Bone2/Bone3"[/code] if [code]false[/code] will become [code]^"A/B/C/Skeleton3D:Bone3"[/code].
</description>
</method>
<method name="set_additional_data">
diff --git a/modules/gltf/doc_classes/GLTFObjectModelProperty.xml b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml
new file mode 100644
index 0000000000..a457d94c41
--- /dev/null
+++ b/modules/gltf/doc_classes/GLTFObjectModelProperty.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GLTFObjectModelProperty" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Describes how to access a property as defined in the glTF object model.
+ </brief_description>
+ <description>
+ GLTFObjectModelProperty defines a mapping between a property in the glTF object model and a NodePath in the Godot scene tree. This can be used to animate properties in a glTF file using the [code]KHR_animation_pointer[/code] extension, or to access them through an engine-agnostic script such as a behavior graph as defined by the [code]KHR_interactivity[/code] extension.
+ The glTF property is identified by JSON pointer(s) stored in [member json_pointers], while the Godot property it maps to is defined by [member node_paths]. In most cases [member json_pointers] and [member node_paths] will each only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties, or a single Godot property will be mapped to multiple glTF JSON pointers, or it might be a many-to-many relationship.
+ [Expression] objects can be used to define conversions between the data, such as when glTF defines an angle in radians and Godot uses degrees. The [member object_model_type] property defines the type of data stored in the glTF file as defined by the object model, see [enum GLTFObjectModelType] for possible values.
+ </description>
+ <tutorials>
+ <link title="GLTF Object Model">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc</link>
+ <link title="KHR_animation_pointer GLTF extension">https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer</link>
+ </tutorials>
+ <methods>
+ <method name="append_node_path">
+ <return type="void" />
+ <param index="0" name="node_path" type="NodePath" />
+ <description>
+ Appends a [NodePath] to [member node_paths]. This can be used by [GLTFDocumentExtension] classes to define how a glTF object model property maps to a Godot property, or multiple Godot properties. Prefer using [method append_path_to_property] for simple cases. Be sure to also call [method set_types] once (the order does not matter).
+ </description>
+ </method>
+ <method name="append_path_to_property">
+ <return type="void" />
+ <param index="0" name="node_path" type="NodePath" />
+ <param index="1" name="prop_name" type="StringName" />
+ <description>
+ High-level wrapper over [method append_node_path] that handles the most common cases. It constructs a new [NodePath] using [param node_path] as a base and appends [param prop_name] to the subpath. Be sure to also call [method set_types] once (the order does not matter).
+ </description>
+ </method>
+ <method name="get_accessor_type" qualifiers="const">
+ <return type="int" enum="GLTFAccessor.GLTFAccessorType" />
+ <description>
+ The GLTF accessor type associated with this property's [member object_model_type]. See [member GLTFAccessor.accessor_type] for possible values, and see [enum GLTFObjectModelType] for how the object model type maps to accessor types.
+ </description>
+ </method>
+ <method name="has_json_pointers" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if [member json_pointers] is not empty. This is used during export to determine if a [GLTFObjectModelProperty] can handle converting a Godot property to a glTF object model property.
+ </description>
+ </method>
+ <method name="has_node_paths" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if [member node_paths] is not empty. This is used during import to determine if a [GLTFObjectModelProperty] can handle converting a glTF object model property to a Godot property.
+ </description>
+ </method>
+ <method name="set_types">
+ <return type="void" />
+ <param index="0" name="variant_type" type="int" enum="Variant.Type" />
+ <param index="1" name="obj_model_type" type="int" enum="GLTFObjectModelProperty.GLTFObjectModelType" />
+ <description>
+ Sets the [member variant_type] and [member object_model_type] properties. This is a convenience method to set both properties at once, since they are almost always known at the same time. This method should be called once. Calling it again with the same values will have no effect.
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="gltf_to_godot_expression" type="Expression" setter="set_gltf_to_godot_expression" getter="get_gltf_to_godot_expression">
+ If set, this [Expression] will be used to convert the property value from the glTF object model to the value expected by the Godot property. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is.
+ </member>
+ <member name="godot_to_gltf_expression" type="Expression" setter="set_godot_to_gltf_expression" getter="get_godot_to_gltf_expression">
+ If set, this [Expression] will be used to convert the property value from the Godot property to the value expected by the glTF object model. This is useful when the glTF object model uses a different unit system, or when the data needs to be transformed in some way. If [code]null[/code], the value will be copied as-is.
+ </member>
+ <member name="json_pointers" type="PackedStringArray[]" setter="set_json_pointers" getter="get_json_pointers" default="[]">
+ The glTF object model JSON pointers used to identify the property in the glTF object model. In most cases, there will be only one item in this array, but niche cases may require multiple pointers. The items are themselves arrays which represent the JSON pointer split into its components.
+ </member>
+ <member name="node_paths" type="NodePath[]" setter="set_node_paths" getter="get_node_paths" default="[]">
+ An array of [NodePath]s that point to a property, or multiple properties, in the Godot scene tree. On import, this will either be set by [GLTFDocument], or by a [GLTFDocumentExtension] class. For simple cases, use [method append_path_to_property] to add properties to this array.
+ In most cases [member node_paths] will only have one item, but in some cases a single glTF JSON pointer will map to multiple Godot properties. For example, a [GLTFCamera] or [GLTFLight] used on multiple glTF nodes will be represented by multiple Godot nodes.
+ </member>
+ <member name="object_model_type" type="int" setter="set_object_model_type" getter="get_object_model_type" enum="GLTFObjectModelProperty.GLTFObjectModelType" default="0">
+ The type of data stored in the glTF file as defined by the object model. This is a superset of the available accessor types, and determines the accessor type. See [enum GLTFObjectModelType] for possible values.
+ </member>
+ <member name="variant_type" type="int" setter="set_variant_type" getter="get_variant_type" enum="Variant.Type" default="0">
+ The type of data stored in the Godot property. This is the type of the property that the [member node_paths] point to.
+ </member>
+ </members>
+ <constants>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_UNKNOWN" value="0" enum="GLTFObjectModelType">
+ Unknown or not set object model type. If the object model type is set to this value, the real type still needs to be determined.
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_BOOL" value="1" enum="GLTFObjectModelType">
+ Object model type "bool". Represented in the glTF JSON as a boolean, and encoded in a [GLTFAccessor] as "SCALAR". When encoded in an accessor, a value of [code]0[/code] is [code]false[/code], and any other value is [code]true[/code].
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT" value="2" enum="GLTFObjectModelType">
+ Object model type "float". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY" value="3" enum="GLTFObjectModelType">
+ Object model type "float[lb][rb]". Represented in the glTF JSON as an array of numbers, and encoded in a [GLTFAccessor] as "SCALAR".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2" value="4" enum="GLTFObjectModelType">
+ Object model type "float2". Represented in the glTF JSON as an array of two numbers, and encoded in a [GLTFAccessor] as "VEC2".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3" value="5" enum="GLTFObjectModelType">
+ Object model type "float3". Represented in the glTF JSON as an array of three numbers, and encoded in a [GLTFAccessor] as "VEC3".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4" value="6" enum="GLTFObjectModelType">
+ Object model type "float4". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "VEC4".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT2X2" value="7" enum="GLTFObjectModelType">
+ Object model type "float2x2". Represented in the glTF JSON as an array of four numbers, and encoded in a [GLTFAccessor] as "MAT2".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT3X3" value="8" enum="GLTFObjectModelType">
+ Object model type "float3x3". Represented in the glTF JSON as an array of nine numbers, and encoded in a [GLTFAccessor] as "MAT3".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_FLOAT4X4" value="9" enum="GLTFObjectModelType">
+ Object model type "float4x4". Represented in the glTF JSON as an array of sixteen numbers, and encoded in a [GLTFAccessor] as "MAT4".
+ </constant>
+ <constant name="GLTF_OBJECT_MODEL_TYPE_INT" value="10" enum="GLTFObjectModelType">
+ Object model type "int". Represented in the glTF JSON as a number, and encoded in a [GLTFAccessor] as "SCALAR". The range of values is limited to signed integers. For [code]KHR_interactivity[/code], only 32-bit integers are supported.
+ </constant>
+ </constants>
+</class>
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index de7ec2a4ca..c81c2f09f0 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -17,7 +17,7 @@
<param index="0" name="extension_name" type="String" />
<param index="1" name="required" type="bool" />
<description>
- Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically.
+ Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is [code]true[/code], the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically.
</description>
</method>
<method name="append_data_to_buffers">
@@ -25,7 +25,7 @@
<param index="0" name="data" type="PackedByteArray" />
<param index="1" name="deduplication" type="bool" />
<description>
- Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is true, the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
+ Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is [code]true[/code], the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
</description>
</method>
<method name="append_gltf_node">
@@ -35,7 +35,7 @@
<param index="2" name="parent_node_index" type="int" />
<description>
Append the given [GLTFNode] to the state, and return its new index. This can be used to export one Godot node as multiple glTF nodes, or inject new glTF nodes at import time. On import, this must be called before [method GLTFDocumentExtension._generate_scene_node] finishes for the parent node. On export, this must be called before [method GLTFDocumentExtension._export_node] runs for the parent node.
- The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be null if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each.
+ The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be [code]null[/code] if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each.
The [param parent_node_index] parameter is the index of the parent [GLTFNode] in the state. If [code]-1[/code], the node will be a root node, otherwise the new node will be added to the parent's list of children. The index will also be written to the [member GLTFNode.parent] property of the new node.
</description>
</method>
@@ -49,7 +49,7 @@
<param index="0" name="extension_name" type="StringName" />
<description>
Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless.
- The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null.
+ The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is [code]null[/code].
</description>
</method>
<method name="get_animation_player">
@@ -306,7 +306,7 @@
The binary buffer attached to a .glb file.
</member>
<member name="import_as_skeleton_bones" type="bool" setter="set_import_as_skeleton_bones" getter="get_import_as_skeleton_bones" default="false">
- True to force all GLTFNodes in the document to be bones of a single Skeleton3D godot node.
+ If [code]true[/code], forces all GLTFNodes in the document to be bones of a single [Skeleton3D] Godot node.
</member>
<member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}">
The original raw JSON document corresponding to this GLTFState.
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 022d2e4477..872054ec2e 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -88,7 +88,7 @@ void SceneExporterGLTFPlugin::_popup_gltf_export_dialog() {
}
_file_dialog->set_current_file(filename + String(".gltf"));
// Generate and refresh the export settings.
- _export_settings->generate_property_list(_gltf_document);
+ _export_settings->generate_property_list(_gltf_document, root);
_settings_inspector->edit(nullptr);
_settings_inspector->edit(_export_settings.ptr());
// Show the file dialog.
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
index 511da078d8..c14e92c3a0 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.cpp
@@ -129,7 +129,7 @@ String get_friendly_config_prefix(Ref<GLTFDocumentExtension> p_extension) {
}
// Run this before popping up the export settings, because the extensions may have changed.
-void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document) {
+void EditorSceneExporterGLTFSettings::generate_property_list(Ref<GLTFDocument> p_document, Node *p_root) {
_property_list.clear();
_document = p_document;
String image_format_hint_string = "None,PNG,JPEG";
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
index 898cddfd68..aa0e54078d 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_settings.h
@@ -55,7 +55,7 @@ protected:
bool _get_extension_setting(const String &p_name_str, Variant &r_ret) const;
public:
- void generate_property_list(Ref<GLTFDocument> p_document);
+ void generate_property_list(Ref<GLTFDocument> p_document, Node *p_root = nullptr);
String get_copyright() const;
void set_copyright(const String &p_copyright);
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 8e5a992bd4..5ba9ee3fa6 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -80,7 +80,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
}
pipe = pipe.substr(bl);
pipe = pipe.replace_first("Blender ", "");
- int pp = pipe.find(".");
+ int pp = pipe.find_char('.');
if (pp == -1) {
if (r_err) {
*r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path);
@@ -96,7 +96,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
return false;
}
- int pp2 = pipe.find(".", pp + 1);
+ int pp2 = pipe.find_char('.', pp + 1);
r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0;
return true;
@@ -319,6 +319,8 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
state->set_import_as_skeleton_bones(true);
}
state->set_scene_name(blend_basename);
+ state->set_extract_path(p_path.get_base_dir());
+ state->set_extract_prefix(blend_basename);
err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir);
if (err != OK) {
if (r_err) {
@@ -503,7 +505,7 @@ void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() {
}
void EditorFileSystemImportFormatSupportQueryBlend::_update_icons() {
- blender_path_browse->set_icon(blender_path_browse->get_editor_theme_icon(SNAME("FolderBrowse")));
+ blender_path_browse->set_button_icon(blender_path_browse->get_editor_theme_icon(SNAME("FolderBrowse")));
}
bool EditorFileSystemImportFormatSupportQueryBlend::query() {
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 6e611762b6..0806eee6bf 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -38,6 +38,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image");
GDVIRTUAL_BIND(_get_image_file_extension);
GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture");
+ GDVIRTUAL_BIND(_import_object_model_property, "state", "split_json_pointer", "partial_paths");
GDVIRTUAL_BIND(_import_post_parse, "state");
GDVIRTUAL_BIND(_import_pre_generate, "state");
GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent");
@@ -48,6 +49,7 @@ void GLTFDocumentExtension::_bind_methods() {
GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node");
GDVIRTUAL_BIND(_export_post_convert, "state", "root");
GDVIRTUAL_BIND(_export_preserialize, "state");
+ GDVIRTUAL_BIND(_export_object_model_property, "state", "node_path", "godot_node", "gltf_node_index", "target_object", "target_depth");
GDVIRTUAL_BIND(_get_saveable_image_formats);
GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality");
GDVIRTUAL_BIND(_save_image_at_path, "state", "image", "file_path", "image_format", "lossy_quality");
@@ -100,6 +102,13 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di
return err;
}
+Ref<GLTFObjectModelProperty> GLTFDocumentExtension::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) {
+ Ref<GLTFObjectModelProperty> ret;
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ GDVIRTUAL_CALL(_import_object_model_property, p_state, p_split_json_pointer, p_partial_paths, ret);
+ return ret;
+}
+
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
@@ -169,6 +178,15 @@ Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) {
return err;
}
+Ref<GLTFObjectModelProperty> GLTFDocumentExtension::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) {
+ Ref<GLTFObjectModelProperty> ret;
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ ERR_FAIL_NULL_V(p_godot_node, ret);
+ ERR_FAIL_NULL_V(p_target_object, ret);
+ GDVIRTUAL_CALL(_export_object_model_property, p_state, p_node_path, p_godot_node, p_gltf_node_index, p_target_object, p_target_depth, ret);
+ return ret;
+}
+
Vector<String> GLTFDocumentExtension::get_saveable_image_formats() {
Vector<String> ret;
GDVIRTUAL_CALL(_get_saveable_image_formats, ret);
diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index b70710e015..a6368ea780 100644
--- a/modules/gltf/extensions/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -49,6 +49,7 @@ public:
virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image);
virtual String get_image_file_extension();
virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture);
+ virtual Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths);
virtual Error import_post_parse(Ref<GLTFState> p_state);
virtual Error import_pre_generate(Ref<GLTFState> p_state);
virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
@@ -59,6 +60,7 @@ public:
virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
virtual Error export_post_convert(Ref<GLTFState> p_state, Node *p_root);
virtual Error export_preserialize(Ref<GLTFState> p_state);
+ virtual Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth);
virtual Vector<String> get_saveable_image_formats();
virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality);
virtual Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality);
@@ -73,6 +75,7 @@ public:
GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>);
GDVIRTUAL0R(String, _get_image_file_extension);
GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>);
+ GDVIRTUAL3R(Ref<GLTFObjectModelProperty>, _import_object_model_property, Ref<GLTFState>, PackedStringArray, TypedArray<NodePath>);
GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>);
GDVIRTUAL1R(Error, _import_pre_generate, Ref<GLTFState>);
GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
@@ -83,6 +86,7 @@ public:
GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL2R(Error, _export_post_convert, Ref<GLTFState>, Node *);
GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>);
+ GDVIRTUAL6R(Ref<GLTFObjectModelProperty>, _export_object_model_property, Ref<GLTFState>, NodePath, const Node *, GLTFNodeIndex, const Object *, int);
GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats);
GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float);
GDVIRTUAL5R(Error, _save_image_at_path, Ref<GLTFState>, Ref<Image>, String, String, float);
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index cde30bce18..b5edd35ad5 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -63,6 +63,7 @@ Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_sta
mesh_instance_node_3d->set_mesh(array_mesh);
mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin());
mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path());
+ mesh_instance_node_3d->set_visible(importer_mesh_3d->is_visible());
node->replace_by(mesh_instance_node_3d);
_copy_meta(importer_mesh_3d, mesh_instance_node_3d);
_copy_meta(mesh.ptr(), array_mesh.ptr());
diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp
index f6e91c1635..2bdcab2f0c 100644
--- a/modules/gltf/extensions/gltf_light.cpp
+++ b/modules/gltf/extensions/gltf_light.cpp
@@ -30,6 +30,7 @@
#include "gltf_light.h"
+#include "../structures/gltf_object_model_property.h"
#include "scene/3d/light_3d.h"
void GLTFLight::_bind_methods() {
@@ -62,6 +63,21 @@ void GLTFLight::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_cone_angle"), "set_outer_cone_angle", "get_outer_cone_angle"); // float
}
+void GLTFLight::set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) {
+ // Expression to convert glTF innerConeAngle to Godot spot_angle_attenuation.
+ Ref<Expression> gltf_to_godot_expr;
+ gltf_to_godot_expr.instantiate();
+ PackedStringArray gltf_to_godot_args = { "inner_cone_angle" };
+ gltf_to_godot_expr->parse("0.2 / (1.0 - inner_cone_angle / spot_angle) - 0.1", gltf_to_godot_args);
+ r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr);
+ // Expression to convert Godot spot_angle_attenuation to glTF innerConeAngle.
+ Ref<Expression> godot_to_gltf_expr;
+ godot_to_gltf_expr.instantiate();
+ PackedStringArray godot_to_gltf_args = { "godot_spot_angle_att" };
+ godot_to_gltf_expr->parse("spot_angle * maxf(0.0, 1.0 - (0.2 / (0.1 + godot_spot_angle_att)))", godot_to_gltf_args);
+ r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr);
+}
+
Color GLTFLight::get_color() {
return color;
}
diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h
index e0894fc8c6..3d522bd174 100644
--- a/modules/gltf/extensions/gltf_light.h
+++ b/modules/gltf/extensions/gltf_light.h
@@ -33,6 +33,7 @@
#include "core/io/resource.h"
+class GLTFObjectModelProperty;
class Light3D;
// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual
@@ -54,6 +55,8 @@ private:
Dictionary additional_data;
public:
+ static void set_cone_inner_attenuation_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop);
+
Color get_color();
void set_color(Color p_color);
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 5c26a1686b..512f25a216 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -31,8 +31,11 @@
#include "gltf_document_extension_physics.h"
#include "scene/3d/physics/area_3d.h"
+#include "scene/3d/physics/rigid_body_3d.h"
#include "scene/3d/physics/static_body_3d.h"
+using GLTFShapeIndex = int64_t;
+
// Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) {
@@ -105,6 +108,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), node_shape_index);
} else {
// If this node is a collider but does not have a collider
// shape, then it only serves to combine together shapes.
@@ -119,6 +123,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]);
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), node_shape_index);
} else {
// If this node is a trigger but does not have a trigger shape,
// then it's a trigger body, what Godot calls an Area3D node.
@@ -129,8 +134,8 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
}
// If this node defines explicit member shape nodes, save this information.
if (node_trigger.has("nodes")) {
- Array node_trigger_nodes = node_trigger["nodes"];
- p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), node_trigger_nodes);
+ Array compound_trigger_nodes = node_trigger["nodes"];
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), compound_trigger_nodes);
}
}
if (physics_body_ext.has("motion") || physics_body_ext.has("type")) {
@@ -140,6 +145,144 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
return OK;
}
+bool _will_gltf_shape_become_subnode(Ref<GLTFState> p_state, const Ref<GLTFNode> p_gltf_node, GLTFNodeIndex p_gltf_node_index) {
+ if (p_gltf_node->has_additional_data(StringName("GLTFPhysicsBody"))) {
+ return true;
+ }
+ const TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ const GLTFNodeIndex parent_index = p_gltf_node->get_parent();
+ if (parent_index == -1 || parent_index >= state_gltf_nodes.size()) {
+ return true;
+ }
+ const Ref<GLTFNode> parent_gltf_node = state_gltf_nodes[parent_index];
+ const Variant parent_body_maybe = parent_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
+ if (parent_body_maybe.get_type() != Variant::NIL) {
+ Ref<GLTFPhysicsBody> parent_body = parent_body_maybe;
+ // If the parent matches the triggerness, then this node will be generated as a shape (CollisionShape3D).
+ // Otherwise, if there is a mismatch, a body will be generated for this node, and a subnode will also be generated for the shape.
+ if (parent_body->get_body_type() == "trigger") {
+ return p_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape"));
+ } else {
+ return p_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ }
+ }
+ if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsColliderShape"))) {
+ return false;
+ }
+ if (parent_gltf_node->has_additional_data(StringName("GLTFPhysicsTriggerShape"))) {
+ return false;
+ }
+ Variant compound_trigger_maybe = parent_gltf_node->has_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"));
+ if (compound_trigger_maybe.get_type() != Variant::NIL) {
+ Array compound_trigger_nodes = compound_trigger_maybe;
+ // Remember, JSON only has numbers, not integers, so must cast to double.
+ return !compound_trigger_nodes.has((double)p_gltf_node_index);
+ }
+ return true;
+}
+
+NodePath _get_scene_node_path_for_shape_index(Ref<GLTFState> p_state, const GLTFNodeIndex p_shape_index) {
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ for (GLTFNodeIndex node_index = 0; node_index < state_gltf_nodes.size(); node_index++) {
+ const Ref<GLTFNode> gltf_node = state_gltf_nodes[node_index];
+ ERR_CONTINUE(gltf_node.is_null());
+ // Check if this node has a shape index and if it matches the one we are looking for.
+ Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ if (shape_index_maybe.get_type() != Variant::INT) {
+ shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (shape_index_maybe.get_type() != Variant::INT) {
+ continue;
+ }
+ }
+ const GLTFShapeIndex shape_index = shape_index_maybe;
+ if (shape_index != p_shape_index) {
+ continue;
+ }
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ // At this point, we have found a node with the shape index we were looking for.
+ if (_will_gltf_shape_become_subnode(p_state, gltf_node, node_index)) {
+ Vector<StringName> sname_path = node_path.get_names();
+ sname_path.append(gltf_node->get_name() + "Shape");
+ node_path = NodePath(sname_path, false);
+ }
+ return node_path;
+ }
+ return NodePath();
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) {
+ Ref<GLTFObjectModelProperty> ret;
+ if (p_split_json_pointer.size() != 6) {
+ // The only properties this class cares about are exactly 6 levels deep.
+ return ret;
+ }
+ ret.instantiate();
+ const String &prop_name = p_split_json_pointer[5];
+ if (p_split_json_pointer[0] == "extensions" && p_split_json_pointer[2] == "shapes") {
+ if (p_split_json_pointer[1] == "OMI_physics_shape" || p_split_json_pointer[1] == "KHR_collision_shapes") {
+ const GLTFNodeIndex shape_index = p_split_json_pointer[3].to_int();
+ NodePath node_path = _get_scene_node_path_for_shape_index(p_state, shape_index);
+ if (node_path.is_empty()) {
+ return ret;
+ }
+ String godot_prop_name = prop_name;
+ if (prop_name == "size") {
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "height" || prop_name == "radius") {
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (prop_name == "radiusBottom" || prop_name == "radiusTop") {
+ godot_prop_name = "radius";
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else {
+ // Not something we handle, return without appending a NodePath.
+ return ret;
+ }
+ // Example: `A/B/C/CollisionShape3D:shape:radius`.
+ Vector<StringName> subnames;
+ subnames.append("shape");
+ subnames.append(godot_prop_name);
+ node_path = NodePath(node_path.get_names(), subnames, false);
+ ret->append_node_path(node_path);
+ }
+ } else if (p_split_json_pointer[0] == "nodes" && p_split_json_pointer[2] == "extensions" && p_split_json_pointer[4] == "motion") {
+ if (p_split_json_pointer[3] == "OMI_physics_body" || p_split_json_pointer[3] == "KHR_physics_rigid_bodies") {
+ const GLTFNodeIndex node_index = p_split_json_pointer[1].to_int();
+ const TypedArray<GLTFNode> all_gltf_nodes = p_state->get_nodes();
+ ERR_FAIL_INDEX_V_MSG(node_index, all_gltf_nodes.size(), ret, "GLTF Physics: The node index " + itos(node_index) + " is not in the state nodes (size: " + itos(all_gltf_nodes.size()) + ").");
+ const Ref<GLTFNode> gltf_node = all_gltf_nodes[node_index];
+ NodePath node_path;
+ if (p_partial_paths.is_empty()) {
+ node_path = gltf_node->get_scene_node_path(p_state);
+ } else {
+ // The path is already computed for us, just grab it.
+ node_path = p_partial_paths[0];
+ }
+ if (prop_name == "mass") {
+ ret->append_path_to_property(node_path, "mass");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (prop_name == "linearVelocity") {
+ ret->append_path_to_property(node_path, "linear_velocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "angularVelocity") {
+ ret->append_path_to_property(node_path, "angular_velocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "centerOfMass") {
+ ret->append_path_to_property(node_path, "center_of_mass");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "inertiaDiagonal") {
+ ret->append_path_to_property(node_path, "inertia");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (prop_name == "inertiaOrientation") {
+ WARN_PRINT("GLTF Physics: The 'inertiaOrientation' property is not supported by Godot.");
+ } else {
+ // Not something we handle, return without appending a NodePath.
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) {
GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index();
if (shape_mesh_index == -1) {
@@ -434,24 +577,126 @@ Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) {
return state_shapes;
}
-Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
+GLTFShapeIndex _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) {
Array state_shapes = _get_or_create_state_shapes_in_state(p_state);
- int size = state_shapes.size();
+ GLTFShapeIndex size = state_shapes.size();
Dictionary shape_property;
Dictionary shape_dict = p_physics_shape->to_dictionary();
- for (int i = 0; i < size; i++) {
+ for (GLTFShapeIndex i = 0; i < size; i++) {
Dictionary other = state_shapes[i];
if (other == shape_dict) {
// De-duplication: If we already have an identical shape,
// set the shape index to the existing one and return.
- shape_property["shape"] = i;
- return shape_property;
+ return i;
}
}
// If we don't have an identical shape, add it to the array.
state_shapes.push_back(shape_dict);
- shape_property["shape"] = size;
- return shape_property;
+ return size;
+}
+
+Error GLTFDocumentExtensionPhysics::export_preserialize(Ref<GLTFState> p_state) {
+ // Note: Need to do _export_node_shape before exporting animations, so export_node is too late.
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ for (Ref<GLTFNode> gltf_node : state_gltf_nodes) {
+ Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ if (collider_shape.is_valid()) {
+ GLTFShapeIndex collider_shape_index = _export_node_shape(p_state, collider_shape);
+ gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShapeIndex"), collider_shape_index);
+ }
+ Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ if (trigger_shape.is_valid()) {
+ GLTFShapeIndex trigger_shape_index = _export_node_shape(p_state, trigger_shape);
+ gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"), trigger_shape_index);
+ }
+ }
+ return OK;
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocumentExtensionPhysics::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) {
+ Ref<GLTFObjectModelProperty> ret;
+ const Vector<StringName> &path_subnames = p_node_path.get_subnames();
+ if (path_subnames.is_empty()) {
+ return ret;
+ }
+ ret.instantiate();
+ const StringName &node_prop = path_subnames[0];
+ if (Object::cast_to<RigidBody3D>(p_target_object)) {
+ if (path_subnames.size() != 1) {
+ return ret;
+ }
+ // Example: `/nodes/0/extensions/OMI_physics_body/motion/mass`
+ PackedStringArray split_json_pointer;
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("OMI_physics_body");
+ split_json_pointer.append("motion");
+ if (node_prop == StringName("mass")) {
+ split_json_pointer.append("mass");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (node_prop == StringName("linear_velocity")) {
+ split_json_pointer.append("linearVelocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("angular_velocity")) {
+ split_json_pointer.append("angularVelocity");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("center_of_mass")) {
+ split_json_pointer.append("centerOfMass");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == StringName("inertia")) {
+ split_json_pointer.append("inertiaDiagonal");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else {
+ // Not something we handle, return without setting the JSON pointer.
+ return ret;
+ }
+ ret->set_json_pointers({ split_json_pointer });
+ } else if (Object::cast_to<CollisionShape3D>(p_godot_node)) {
+ if (path_subnames.size() != 2) {
+ return ret;
+ }
+ // Example: `/extensions/OMI_physics_shape/shapes/0/box/size`
+ PackedStringArray split_json_pointer;
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("OMI_physics_shape");
+ split_json_pointer.append("shapes");
+ TypedArray<GLTFNode> state_gltf_nodes = p_state->get_nodes();
+ ERR_FAIL_INDEX_V(p_gltf_node_index, state_gltf_nodes.size(), ret);
+ Ref<GLTFNode> gltf_node = state_gltf_nodes[p_gltf_node_index];
+ Variant shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ String shape_type;
+ if (shape_index_maybe.get_type() == Variant::INT) {
+ Ref<GLTFPhysicsShape> collider_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ shape_type = collider_shape->get_shape_type();
+ } else {
+ shape_index_maybe = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (shape_index_maybe.get_type() == Variant::INT) {
+ Ref<GLTFPhysicsShape> trigger_shape = gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
+ shape_type = trigger_shape->get_shape_type();
+ }
+ }
+ ERR_FAIL_COND_V(shape_index_maybe.get_type() != Variant::INT, ret);
+ GLTFShapeIndex shape_index = shape_index_maybe;
+ split_json_pointer.append(itos(shape_index));
+ split_json_pointer.append(shape_type);
+ const StringName &shape_prop = path_subnames[1];
+ if (shape_prop == StringName("size")) {
+ split_json_pointer.append("size");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (shape_prop == StringName("radius")) {
+ split_json_pointer.append("radius");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (shape_prop == StringName("height")) {
+ split_json_pointer.append("height");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else {
+ // Not something we handle, return without setting the JSON pointer.
+ return ret;
+ }
+ ret->set_json_pointers({ split_json_pointer });
+ }
+ return ret;
}
Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) {
@@ -465,13 +710,16 @@ Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTF
trigger_property["nodes"] = compound_trigger_nodes;
}
}
- Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
- if (collider_shape.is_valid()) {
- physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape);
+ Variant collider_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShapeIndex"));
+ if (collider_shape_index.get_type() == Variant::INT) {
+ Dictionary collider_dict;
+ collider_dict["shape"] = collider_shape_index;
+ physics_body_ext["collider"] = collider_dict;
}
- Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
- if (trigger_shape.is_valid()) {
- physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape);
+ Variant trigger_shape_index = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShapeIndex"));
+ if (trigger_shape_index.get_type() == Variant::INT) {
+ Dictionary trigger_dict = physics_body_ext.get_or_add("trigger", {});
+ trigger_dict["shape"] = trigger_shape_index;
}
if (!physics_body_ext.is_empty()) {
Dictionary node_extensions = r_node_json["extensions"];
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.h b/modules/gltf/extensions/physics/gltf_document_extension_physics.h
index 3d5027c0df..76a60a6375 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.h
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.h
@@ -43,9 +43,12 @@ public:
Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) override;
Vector<String> get_supported_extensions() override;
Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) override;
+ Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const PackedStringArray &p_split_json_pointer, const TypedArray<NodePath> &p_partial_paths) override;
Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) override;
// Export process.
void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) override;
+ Error export_preserialize(Ref<GLTFState> p_state) override;
+ Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index, const Object *p_target_object, int p_target_depth) override;
Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_scene_node) override;
};
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index c11aa5d2ff..7c40f96e0a 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -193,9 +193,6 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
physics_body->angular_velocity = body->get_angular_velocity();
physics_body->center_of_mass = body->get_center_of_mass();
physics_body->inertia_diagonal = body->get_inertia();
- if (body->get_center_of_mass() != Vector3()) {
- WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to glTF.");
- }
if (cast_to<VehicleBody3D>(p_body_node)) {
physics_body->body_type = PhysicsBodyType::VEHICLE;
} else {
diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h
index c1918e5908..4d88f7c342 100644
--- a/modules/gltf/gltf_defines.h
+++ b/modules/gltf/gltf_defines.h
@@ -43,6 +43,7 @@ class GLTFDocumentExtension;
class GLTFLight;
class GLTFMesh;
class GLTFNode;
+class GLTFObjectModelProperty;
class GLTFSkeleton;
class GLTFSkin;
class GLTFSpecGloss;
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 992075e980..2f36c29ec4 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -69,6 +69,10 @@
#include <stdlib.h>
#include <cstdint>
+constexpr int COMPONENT_COUNT_FOR_ACCESSOR_TYPE[7] = {
+ 1, 2, 3, 4, 4, 9, 16
+};
+
static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) {
if (!p_extras.is_empty()) {
p_node->set_meta("extras", p_extras);
@@ -113,7 +117,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
mat_name = mat->get_name();
} else {
// Assign default material when no material is assigned.
- mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+ mat.instantiate();
}
importer_mesh->add_surface(p_mesh->surface_get_primitive_type(surface_i),
array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat,
@@ -613,12 +617,8 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
if (n.has("scale")) {
node->set_scale(_arr_to_vec3(n["scale"]));
}
-
- Transform3D godot_rest_transform;
- godot_rest_transform.basis.set_quaternion_scale(node->transform.basis.get_rotation_quaternion(), node->transform.basis.get_scale());
- godot_rest_transform.origin = node->transform.origin;
- node->set_additional_data("GODOT_rest_transform", godot_rest_transform);
}
+ node->set_additional_data("GODOT_rest_transform", node->transform);
if (n.has("extensions")) {
Dictionary extensions = n["extensions"];
@@ -689,7 +689,7 @@ void GLTFDocument::_compute_node_heights(Ref<GLTFState> p_state) {
}
static Vector<uint8_t> _parse_base64_uri(const String &p_uri) {
- int start = p_uri.find(",");
+ int start = p_uri.find_char(',');
ERR_FAIL_COND_V(start == -1, Vector<uint8_t>());
CharString substr = p_uri.substr(start + 1).ascii();
@@ -1017,7 +1017,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
accessor.instantiate();
ERR_FAIL_COND_V(!d.has("componentType"), ERR_PARSE_ERROR);
- accessor->component_type = d["componentType"];
+ accessor->component_type = (GLTFAccessor::GLTFComponentType)(int32_t)d["componentType"];
ERR_FAIL_COND_V(!d.has("count"), ERR_PARSE_ERROR);
accessor->count = d["count"];
ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR);
@@ -1054,7 +1054,7 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(!si.has("bufferView"), ERR_PARSE_ERROR);
accessor->sparse_indices_buffer_view = si["bufferView"];
ERR_FAIL_COND_V(!si.has("componentType"), ERR_PARSE_ERROR);
- accessor->sparse_indices_component_type = si["componentType"];
+ accessor->sparse_indices_component_type = (GLTFAccessor::GLTFComponentType)(int32_t)si["componentType"];
if (si.has("byteOffset")) {
accessor->sparse_indices_byte_offset = si["byteOffset"];
@@ -1086,31 +1086,39 @@ double GLTFDocument::_filter_number(double p_float) {
return (double)(float)p_float;
}
-String GLTFDocument::_get_component_type_name(const uint32_t p_component) {
+String GLTFDocument::_get_component_type_name(const GLTFAccessor::GLTFComponentType p_component) {
switch (p_component) {
- case GLTFDocument::COMPONENT_TYPE_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_NONE:
+ return "None";
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
return "Byte";
- case GLTFDocument::COMPONENT_TYPE_UNSIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE:
return "UByte";
- case GLTFDocument::COMPONENT_TYPE_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
return "Short";
- case GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT:
return "UShort";
- case GLTFDocument::COMPONENT_TYPE_INT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT:
return "Int";
- case GLTFDocument::COMPONENT_TYPE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT:
+ return "UInt";
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT:
return "Float";
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT:
+ return "Double";
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT:
+ return "Half";
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG:
+ return "Long";
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG:
+ return "ULong";
}
return "<Error>";
}
-Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
- const int component_count_for_type[7] = {
- 1, 2, 3, 4, 4, 9, 16
- };
-
- const int component_count = component_count_for_type[p_accessor_type];
+Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
const int component_size = _get_component_type_size(p_component_type);
ERR_FAIL_COND_V(component_size == 0, FAILED);
@@ -1118,8 +1126,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
int skip_bytes = 0;
//special case of alignments, as described in spec
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
if (p_accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
@@ -1129,8 +1137,8 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
skip_bytes = 1;
}
} break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
if (p_accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
@@ -1165,7 +1173,10 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
}
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_NONE: {
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to encode buffer view, component type not set.");
+ }
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: {
Vector<int8_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1189,7 +1200,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int8_t));
bv->byte_length = buffer.size() * sizeof(int8_t);
} break;
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
Vector<uint8_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1211,7 +1222,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
gltf_buffer.append_array(buffer);
bv->byte_length = buffer.size() * sizeof(uint8_t);
} break;
- case COMPONENT_TYPE_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: {
Vector<int16_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1235,7 +1246,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int16_t));
bv->byte_length = buffer.size() * sizeof(int16_t);
} break;
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
Vector<uint16_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1259,8 +1270,28 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint16_t));
bv->byte_length = buffer.size() * sizeof(uint16_t);
} break;
- case COMPONENT_TYPE_INT: {
- Vector<int> buffer;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: {
+ Vector<int32_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t));
+ bv->byte_length = buffer.size() * sizeof(uint32_t);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: {
+ Vector<uint32_t> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
for (int i = 0; i < p_count; i++) {
@@ -1275,11 +1306,11 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
}
}
int64_t old_size = gltf_buffer.size();
- gltf_buffer.resize(old_size + (buffer.size() * sizeof(int32_t)));
- memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int32_t));
- bv->byte_length = buffer.size() * sizeof(int32_t);
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint32_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint32_t));
+ bv->byte_length = buffer.size() * sizeof(uint32_t);
} break;
- case COMPONENT_TYPE_FLOAT: {
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: {
Vector<float> buffer;
buffer.resize(p_count * component_count);
int32_t dst_i = 0;
@@ -1299,6 +1330,71 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(float));
bv->byte_length = buffer.size() * sizeof(float);
} break;
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: {
+ Vector<double> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(double)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(double));
+ bv->byte_length = buffer.size() * sizeof(double);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: {
+ Vector<int64_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ // FIXME: This can result in precision loss because int64_t can store some values that double can't.
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(int64_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(int64_t));
+ bv->byte_length = buffer.size() * sizeof(int64_t);
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: {
+ Vector<uint64_t> buffer;
+ buffer.resize(p_count * component_count);
+ int32_t dst_i = 0;
+ for (int i = 0; i < p_count; i++) {
+ for (int j = 0; j < component_count; j++) {
+ if (skip_every && j > 0 && (j % skip_every) == 0) {
+ dst_i += skip_bytes;
+ }
+ // FIXME: This can result in precision loss because int64_t can store some values that double can't.
+ double d = *p_src;
+ buffer.write[dst_i] = d;
+ p_src++;
+ dst_i++;
+ }
+ }
+ int64_t old_size = gltf_buffer.size();
+ gltf_buffer.resize(old_size + (buffer.size() * sizeof(uint64_t)));
+ memcpy(gltf_buffer.ptrw() + old_size, buffer.ptrw(), buffer.size() * sizeof(uint64_t));
+ bv->byte_length = buffer.size() * sizeof(uint64_t);
+ } break;
}
ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA);
@@ -1313,7 +1409,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
return OK;
}
-Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
+Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view];
int stride = p_element_size;
@@ -1352,7 +1448,10 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
double d = 0;
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_NONE: {
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "glTF: Failed to decode buffer view, component type not set.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE: {
int8_t b = int8_t(*src);
if (p_normalized) {
d = (double(b) / 128.0);
@@ -1360,7 +1459,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(b);
}
} break;
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
uint8_t b = *src;
if (p_normalized) {
d = (double(b) / 255.0);
@@ -1368,7 +1467,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(b);
}
} break;
- case COMPONENT_TYPE_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT: {
int16_t s = *(int16_t *)src;
if (p_normalized) {
d = (double(s) / 32768.0);
@@ -1376,7 +1475,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(s);
}
} break;
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
uint16_t s = *(uint16_t *)src;
if (p_normalized) {
d = (double(s) / 65535.0);
@@ -1384,12 +1483,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
d = double(s);
}
} break;
- case COMPONENT_TYPE_INT: {
- d = *(int *)src;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT: {
+ d = *(int32_t *)src;
} break;
- case COMPONENT_TYPE_FLOAT: {
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT: {
+ d = *(uint32_t *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT: {
d = *(float *)src;
} break;
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT: {
+ d = *(double *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT: {
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Half float not supported yet.");
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG: {
+ d = *(int64_t *)src;
+ } break;
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG: {
+ d = *(uint64_t *)src;
+ } break;
}
*p_dst++ = d;
@@ -1400,25 +1514,27 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, c
return OK;
}
-int GLTFDocument::_get_component_type_size(const int p_component_type) {
+int GLTFDocument::_get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type) {
switch (p_component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_NONE:
+ ERR_FAIL_V(0);
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE:
return 1;
- break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_HALF_FLOAT:
return 2;
- break;
- case COMPONENT_TYPE_INT:
- case COMPONENT_TYPE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_INT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT:
+ case GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT:
return 4;
- break;
- default: {
- ERR_FAIL_V(0);
- }
+ case GLTFAccessor::COMPONENT_TYPE_DOUBLE_FLOAT:
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_LONG:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_LONG:
+ return 8;
}
- return 0;
+ ERR_FAIL_V(0);
}
Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) {
@@ -1429,11 +1545,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
const Ref<GLTFAccessor> a = p_state->accessors[p_accessor];
- const int component_count_for_type[7] = {
- 1, 2, 3, 4, 4, 9, 16
- };
-
- const int component_count = component_count_for_type[a->accessor_type];
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[a->accessor_type];
const int component_size = _get_component_type_size(a->component_type);
ERR_FAIL_COND_V(component_size == 0, Vector<double>());
int element_size = component_count * component_size;
@@ -1442,8 +1554,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
int skip_bytes = 0;
//special case of alignments, as described in spec
switch (a->component_type) {
- case COMPONENT_TYPE_BYTE:
- case COMPONENT_TYPE_UNSIGNED_BYTE: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_BYTE:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_BYTE: {
if (a->accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
@@ -1455,8 +1567,8 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
element_size = 12; //override for this case
}
} break;
- case COMPONENT_TYPE_SHORT:
- case COMPONENT_TYPE_UNSIGNED_SHORT: {
+ case GLTFAccessor::COMPONENT_TYPE_SIGNED_SHORT:
+ case GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT: {
if (a->accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
@@ -1554,11 +1666,11 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
- int component_type;
+ GLTFAccessor::GLTFComponentType component_type;
if (max_index > 65535 || p_for_vertex) {
- component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT;
} else {
- component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
}
accessor->max = type_max;
@@ -1668,7 +1780,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC2;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1721,7 +1833,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1788,7 +1900,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1839,7 +1951,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1892,7 +2004,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -1936,7 +2048,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, c
return ret;
}
-GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<real_t> p_attribs, const bool p_for_vertex) {
+GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<double> p_attribs, const bool p_for_vertex) {
if (p_attribs.size() == 0) {
return -1;
}
@@ -1967,7 +2079,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2017,7 +2129,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state,
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2093,7 +2205,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
sparse_accessor->normalized = false;
sparse_accessor->count = p_attribs.size();
@@ -2116,9 +2228,9 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
GLTFBufferIndex buffer_view_i_indices = -1;
GLTFBufferIndex buffer_view_i_values = -1;
if (sparse_accessor_index_stride == 4) {
- sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_INT;
+ sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_INT;
} else {
- sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
+ sparse_accessor->sparse_indices_component_type = GLTFAccessor::COMPONENT_TYPE_UNSIGNED_SHORT;
}
if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessor::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) {
return -1;
@@ -2198,7 +2310,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state
}
int64_t size = p_state->buffers[0].size();
const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_MAT4;
- const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
+ const GLTFAccessor::GLTFComponentType component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT;
accessor->max = type_max;
accessor->min = type_min;
@@ -2347,6 +2459,325 @@ Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> p_sta
return ret;
}
+Vector<Variant> GLTFDocument::_decode_accessor_as_variant(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type) {
+ const Vector<double> attribs = _decode_accessor(p_state, p_accessor, false);
+ Vector<Variant> ret;
+ ERR_FAIL_COND_V_MSG(attribs.is_empty(), ret, "glTF: The accessor was empty.");
+ const int component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
+ ERR_FAIL_COND_V_MSG(attribs.size() % component_count != 0, ret, "glTF: The accessor size was not a multiple of the component count.");
+ const int ret_size = attribs.size() / component_count;
+ ret.resize(ret_size);
+ for (int i = 0; i < ret_size; i++) {
+ switch (p_variant_type) {
+ case Variant::BOOL: {
+ ret.write[i] = attribs[i * component_count] != 0.0;
+ } break;
+ case Variant::INT: {
+ ret.write[i] = (int64_t)attribs[i * component_count];
+ } break;
+ case Variant::FLOAT: {
+ ret.write[i] = attribs[i * component_count];
+ } break;
+ case Variant::VECTOR2:
+ case Variant::RECT2:
+ case Variant::VECTOR3:
+ case Variant::VECTOR4:
+ case Variant::PLANE:
+ case Variant::QUATERNION: {
+ // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `real_t`s in size.
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Vector4(attribs[i * component_count], 0.0f, 0.0f, 0.0f);
+ } break;
+ case 2: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 0.0f);
+ } break;
+ case 3: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f);
+ } break;
+ default: {
+ v = Vector4(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ }
+ // Evil hack that relies on the structure of Variant, but it's the
+ // only way to accomplish this without a ton of code duplication.
+ *(Variant::Type *)&v = p_variant_type;
+ ret.write[i] = v;
+ } break;
+ case Variant::VECTOR2I:
+ case Variant::RECT2I:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4I: {
+ // General-purpose code for importing glTF accessor data with any component count into structs up to 4 `int32_t`s in size.
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Vector4i((int32_t)attribs[i * component_count], 0, 0, 0);
+ } break;
+ case 2: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], 0, 0);
+ } break;
+ case 3: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], 0);
+ } break;
+ default: {
+ v = Vector4i((int32_t)attribs[i * component_count], (int32_t)attribs[i * component_count + 1], (int32_t)attribs[i * component_count + 2], (int32_t)attribs[i * component_count + 3]);
+ } break;
+ }
+ // Evil hack that relies on the structure of Variant, but it's the
+ // only way to accomplish this without a ton of code duplication.
+ *(Variant::Type *)&v = p_variant_type;
+ ret.write[i] = v;
+ } break;
+ // No more generalized hacks, each of the below types needs a lot of repetitive code.
+ case Variant::COLOR: {
+ Variant v;
+ switch (component_count) {
+ case 1: {
+ v = Color(attribs[i * component_count], 0.0f, 0.0f, 1.0f);
+ } break;
+ case 2: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], 0.0f, 1.0f);
+ } break;
+ case 3: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], 1.0f);
+ } break;
+ default: {
+ v = Color(attribs[i * component_count], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ }
+ ret.write[i] = v;
+ } break;
+ case Variant::TRANSFORM2D: {
+ Transform2D t;
+ switch (component_count) {
+ case 4: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ } break;
+ case 9: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 3], attribs[i * component_count + 4]);
+ t.columns[2] = Vector2(attribs[i * component_count + 6], attribs[i * component_count + 7]);
+ } break;
+ case 16: {
+ t.columns[0] = Vector2(attribs[i * component_count + 0], attribs[i * component_count + 1]);
+ t.columns[1] = Vector2(attribs[i * component_count + 4], attribs[i * component_count + 5]);
+ t.columns[2] = Vector2(attribs[i * component_count + 12], attribs[i * component_count + 13]);
+ } break;
+ }
+ ret.write[i] = t;
+ } break;
+ case Variant::BASIS: {
+ Basis b;
+ switch (component_count) {
+ case 4: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f);
+ } break;
+ case 9: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]);
+ b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]);
+ } break;
+ case 16: {
+ b.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]);
+ b.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]);
+ b.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]);
+ } break;
+ }
+ ret.write[i] = b;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Transform3D t;
+ switch (component_count) {
+ case 4: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 2], 0.0f);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 3], 0.0f);
+ } break;
+ case 9: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 3], attribs[i * component_count + 6]);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 4], attribs[i * component_count + 7]);
+ t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 5], attribs[i * component_count + 8]);
+ } break;
+ case 16: {
+ t.basis.rows[0] = Vector3(attribs[i * component_count + 0], attribs[i * component_count + 4], attribs[i * component_count + 8]);
+ t.basis.rows[1] = Vector3(attribs[i * component_count + 1], attribs[i * component_count + 5], attribs[i * component_count + 9]);
+ t.basis.rows[2] = Vector3(attribs[i * component_count + 2], attribs[i * component_count + 6], attribs[i * component_count + 10]);
+ t.origin = Vector3(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14]);
+ } break;
+ }
+ ret.write[i] = t;
+ } break;
+ case Variant::PROJECTION: {
+ Projection p;
+ switch (component_count) {
+ case 4: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], 0.0f, 0.0f);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], 0.0f, 0.0f);
+ } break;
+ case 9: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], 0.0f);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], 0.0f);
+ p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], 0.0f);
+ } break;
+ case 16: {
+ p.columns[0] = Vector4(attribs[i * component_count + 0], attribs[i * component_count + 1], attribs[i * component_count + 2], attribs[i * component_count + 3]);
+ p.columns[1] = Vector4(attribs[i * component_count + 4], attribs[i * component_count + 5], attribs[i * component_count + 6], attribs[i * component_count + 7]);
+ p.columns[2] = Vector4(attribs[i * component_count + 8], attribs[i * component_count + 9], attribs[i * component_count + 10], attribs[i * component_count + 11]);
+ p.columns[3] = Vector4(attribs[i * component_count + 12], attribs[i * component_count + 13], attribs[i * component_count + 14], attribs[i * component_count + 15]);
+ } break;
+ }
+ ret.write[i] = p;
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(ret, "glTF: Cannot decode accessor as Variant of type " + Variant::get_type_name(p_variant_type) + ".");
+ }
+ }
+ }
+ return ret;
+}
+
+GLTFAccessorIndex GLTFDocument::_encode_accessor_as_variant(Ref<GLTFState> p_state, Vector<Variant> p_attribs, Variant::Type p_variant_type, GLTFAccessor::GLTFAccessorType p_accessor_type, GLTFAccessor::GLTFComponentType p_component_type) {
+ const int accessor_component_count = COMPONENT_COUNT_FOR_ACCESSOR_TYPE[p_accessor_type];
+ Vector<double> encoded_attribs;
+ for (const Variant &v : p_attribs) {
+ switch (p_variant_type) {
+ case Variant::NIL:
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::FLOAT: {
+ // For scalar values, just append them. Variant can convert all of these to double. Some padding may also be needed.
+ encoded_attribs.append(v);
+ if (unlikely(accessor_component_count > 1)) {
+ for (int i = 1; i < accessor_component_count; i++) {
+ encoded_attribs.append(0.0);
+ }
+ }
+ } break;
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I:
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I:
+ case Variant::VECTOR4:
+ case Variant::VECTOR4I: {
+ // Variant can handle converting Vector2/2i/3/3i/4/4i to Vector4 for us.
+ Vector4 vec = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(vec[i]);
+ }
+ }
+ } break;
+ case Variant::PLANE: {
+ Plane p = v;
+ if (likely(accessor_component_count == 4)) {
+ encoded_attribs.append(p.normal.x);
+ encoded_attribs.append(p.normal.y);
+ encoded_attribs.append(p.normal.z);
+ encoded_attribs.append(p.d);
+ }
+ } break;
+ case Variant::QUATERNION: {
+ Quaternion q = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(q[i]);
+ }
+ }
+ } break;
+ case Variant::COLOR: {
+ Color c = v;
+ if (likely(accessor_component_count < 5)) {
+ for (int i = 0; i < accessor_component_count; i++) {
+ encoded_attribs.append(c[i]);
+ }
+ }
+ } break;
+ case Variant::RECT2:
+ case Variant::RECT2I: {
+ // Variant can handle converting Rect2i to Rect2 for us.
+ Rect2 r = v;
+ if (likely(accessor_component_count == 4)) {
+ encoded_attribs.append(r.position.x);
+ encoded_attribs.append(r.position.y);
+ encoded_attribs.append(r.size.x);
+ encoded_attribs.append(r.size.y);
+ }
+ } break;
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::TRANSFORM3D:
+ case Variant::PROJECTION: {
+ // Variant can handle converting Transform2D/Transform3D/Basis to Projection for us.
+ Projection p = v;
+ if (accessor_component_count == 16) {
+ for (int i = 0; i < 4; i++) {
+ encoded_attribs.append(p.columns[i][0]);
+ encoded_attribs.append(p.columns[i][1]);
+ encoded_attribs.append(p.columns[i][2]);
+ encoded_attribs.append(p.columns[i][3]);
+ }
+ } else if (accessor_component_count == 9) {
+ for (int i = 0; i < 3; i++) {
+ encoded_attribs.append(p.columns[i][0]);
+ encoded_attribs.append(p.columns[i][1]);
+ encoded_attribs.append(p.columns[i][2]);
+ }
+ } else if (accessor_component_count == 4) {
+ encoded_attribs.append(p.columns[0][0]);
+ encoded_attribs.append(p.columns[0][1]);
+ encoded_attribs.append(p.columns[1][0]);
+ encoded_attribs.append(p.columns[1][1]);
+ }
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(-1, "glTF: Cannot encode accessor from Variant of type " + Variant::get_type_name(p_variant_type) + ".");
+ }
+ }
+ }
+ // Determine the min and max values for the accessor.
+ Vector<double> type_max;
+ type_max.resize(accessor_component_count);
+ Vector<double> type_min;
+ type_min.resize(accessor_component_count);
+ for (int i = 0; i < encoded_attribs.size(); i++) {
+ if (Math::is_zero_approx(encoded_attribs[i])) {
+ encoded_attribs.write[i] = 0.0;
+ } else {
+ encoded_attribs.write[i] = _filter_number(encoded_attribs[i]);
+ }
+ }
+ for (int i = 0; i < p_attribs.size(); i++) {
+ _calc_accessor_min_max(i, accessor_component_count, type_max, encoded_attribs, type_min);
+ }
+ _round_min_max_components(type_min, type_max);
+ // Encode the data in a buffer view.
+ GLTFBufferIndex buffer_view_index = 0;
+ if (p_state->buffers.is_empty()) {
+ p_state->buffers.push_back(Vector<uint8_t>());
+ }
+ const int64_t buffer_size = p_state->buffers[buffer_view_index].size();
+ Error err = _encode_buffer_view(p_state, encoded_attribs.ptr(), p_attribs.size(), p_accessor_type, p_component_type, false, buffer_size, false, buffer_view_index);
+ if (err != OK) {
+ return -1;
+ }
+ // Create the accessor and fill it with the data.
+ Ref<GLTFAccessor> accessor;
+ accessor.instantiate();
+ accessor->max = type_max;
+ accessor->min = type_min;
+ accessor->count = p_attribs.size();
+ accessor->accessor_type = p_accessor_type;
+ accessor->component_type = p_component_type;
+ accessor->byte_offset = 0;
+ accessor->buffer_view = buffer_view_index;
+ const GLTFAccessorIndex new_accessor_index = p_state->accessors.size();
+ p_state->accessors.push_back(accessor);
+ return new_accessor_index;
+}
+
Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Array meshes;
for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < p_state->meshes.size(); gltf_mesh_i++) {
@@ -2782,41 +3213,42 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array meshes = p_state->json["meshes"];
for (GLTFMeshIndex i = 0; i < meshes.size(); i++) {
print_verbose("glTF: Parsing mesh: " + itos(i));
- Dictionary d = meshes[i];
+ Dictionary mesh_dict = meshes[i];
Ref<GLTFMesh> mesh;
mesh.instantiate();
bool has_vertex_color = false;
- ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!mesh_dict.has("primitives"), ERR_PARSE_ERROR);
- Array primitives = d["primitives"];
- const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+ Array primitives = mesh_dict["primitives"];
+ const Dictionary &extras = mesh_dict.has("extras") ? (Dictionary)mesh_dict["extras"] : Dictionary();
_attach_extras_to_meta(extras, mesh);
Ref<ImporterMesh> import_mesh;
import_mesh.instantiate();
String mesh_name = "mesh";
- if (d.has("name") && !String(d["name"]).is_empty()) {
- mesh_name = d["name"];
+ if (mesh_dict.has("name") && !String(mesh_dict["name"]).is_empty()) {
+ mesh_name = mesh_dict["name"];
mesh->set_original_name(mesh_name);
}
import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name)));
mesh->set_name(import_mesh->get_name());
+ TypedArray<Material> instance_materials;
for (int j = 0; j < primitives.size(); j++) {
uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
- Dictionary p = primitives[j];
+ Dictionary mesh_prim = primitives[j];
Array array;
array.resize(Mesh::ARRAY_MAX);
- ERR_FAIL_COND_V(!p.has("attributes"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!mesh_prim.has("attributes"), ERR_PARSE_ERROR);
- Dictionary a = p["attributes"];
+ Dictionary a = mesh_prim["attributes"];
Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
- if (p.has("mode")) {
- const int mode = p["mode"];
+ if (mesh_prim.has("mode")) {
+ const int mode = mesh_prim["mode"];
ERR_FAIL_INDEX_V(mode, 7, ERR_FILE_CORRUPT);
// Convert mesh.primitive.mode to Godot Mesh enum. See:
// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_mesh_primitive_mode
@@ -2847,8 +3279,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Vector<int> indices_mapping;
Vector<int> indices_rev_mapping;
Vector<int> indices_vec4_mapping;
- if (p.has("indices")) {
- indices = _decode_accessor_as_ints(p_state, p["indices"], false);
+ if (mesh_prim.has("indices")) {
+ indices = _decode_accessor_as_ints(p_state, mesh_prim["indices"], false);
const int is = indices.size();
if (primitive == Mesh::PRIMITIVE_TRIANGLES) {
@@ -3050,6 +3482,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
array[Mesh::ARRAY_WEIGHTS] = weights;
+ flags |= Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS;
}
if (!indices.is_empty()) {
@@ -3106,7 +3539,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
- if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
+ if (p_state->force_disable_compression || is_mesh_2d || !a.has("POSITION") || !a.has("NORMAL") || mesh_prim.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) {
flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES;
}
@@ -3138,9 +3571,9 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array morphs;
// Blend shapes
- if (p.has("targets")) {
+ if (mesh_prim.has("targets")) {
print_verbose("glTF: Mesh has targets");
- const Array &targets = p["targets"];
+ const Array &targets = mesh_prim["targets"];
import_mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
@@ -3271,8 +3704,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Ref<Material> mat;
String mat_name;
if (!p_state->discard_meshes_and_materials) {
- if (p.has("material")) {
- const int material = p["material"];
+ if (mesh_prim.has("material")) {
+ const int material = mesh_prim["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
@@ -3292,6 +3725,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
mat = mat3d;
}
ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
+ instance_materials.append(mat);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -3304,8 +3738,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
blend_weights.write[weight_i] = 0.0f;
}
- if (d.has("weights")) {
- const Array &weights = d["weights"];
+ if (mesh_dict.has("weights")) {
+ const Array &weights = mesh_dict["weights"];
for (int j = 0; j < weights.size(); j++) {
if (j >= blend_weights.size()) {
break;
@@ -3314,6 +3748,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
}
mesh->set_blend_weights(blend_weights);
+ mesh->set_instance_materials(instance_materials);
mesh->set_mesh(import_mesh);
p_state->meshes.push_back(mesh);
@@ -3505,18 +3940,19 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) {
- if (p_state->base_path.is_empty()) {
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
- } else if (p_image->get_name().is_empty()) {
- WARN_PRINT(vformat("glTF: Image index '%d' couldn't be named. Skipping it.", p_index));
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
+ if (p_state->extract_path.is_empty()) {
+ WARN_PRINT("glTF: Couldn't extract image because the base and extract paths are empty. It will be loaded directly instead, uncompressed.");
+ } else if (p_state->extract_path.begins_with("res://.godot/imported")) {
+ WARN_PRINT(vformat("glTF: Extract path is in the imported directory. Image index '%d' will be loaded directly, uncompressed.", p_index));
} else {
+ if (p_image->get_name().is_empty()) {
+ WARN_PRINT(vformat("glTF: Image index '%d' did not have a name. It will be automatically given a name based on its index.", p_index));
+ p_image->set_name(itos(p_index));
+ }
bool must_import = true;
Vector<uint8_t> img_data = p_image->get_data();
Dictionary generator_parameters;
- String file_path = p_state->get_base_path().path_join(p_state->filename.get_basename() + "_" + p_image->get_name());
+ String file_path = p_state->get_extract_path().path_join(p_state->get_extract_prefix() + "_" + p_image->get_name());
file_path += p_file_extension.is_empty() ? ".png" : p_file_extension;
if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
@@ -3563,14 +3999,11 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
if (saved_image.is_valid()) {
p_state->images.push_back(saved_image);
p_state->source_images.push_back(saved_image->get_image());
+ return;
} else {
- WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded with the name: %s. Skipping it.", p_index, p_image->get_name()));
- // Placeholder to keep count.
- p_state->images.push_back(Ref<Texture2D>());
- p_state->source_images.push_back(Ref<Image>());
+ WARN_PRINT(vformat("glTF: Image index '%d' with the name '%s' couldn't be imported. It will be loaded directly instead, uncompressed.", p_index, p_image->get_name()));
}
}
- return;
}
#endif // TOOLS_ENABLED
if (handling == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) {
@@ -3653,16 +4086,19 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER);
uri = uri.uri_decode();
uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows.
- // ResourceLoader will rely on the file extension to use the relevant loader.
- // The spec says that if mimeType is defined, it should take precedence (e.g.
- // there could be a `.png` image which is actually JPEG), but there's no easy
- // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in
- // the material), so we only do that only as fallback.
- Ref<Texture2D> texture = ResourceLoader::load(uri);
- if (texture.is_valid()) {
- p_state->images.push_back(texture);
- p_state->source_images.push_back(texture->get_image());
- continue;
+ // If the image is in the .godot/imported directory, we can't use ResourceLoader.
+ if (!p_base_path.begins_with("res://.godot/imported")) {
+ // ResourceLoader will rely on the file extension to use the relevant loader.
+ // The spec says that if mimeType is defined, it should take precedence (e.g.
+ // there could be a `.png` image which is actually JPEG), but there's no easy
+ // API for that in Godot, so we'd have to load as a buffer (i.e. embedded in
+ // the material), so we only do that only as fallback.
+ Ref<Texture2D> texture = ResourceLoader::load(uri, "Texture2D");
+ if (texture.is_valid()) {
+ p_state->images.push_back(texture);
+ p_state->source_images.push_back(texture->get_image());
+ continue;
+ }
}
// mimeType is optional, but if we have it in the file extension, let's use it.
// If the mimeType does not match with the file extension, either it should be
@@ -4785,7 +5221,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
for (GLTFAnimationIndex animation_i = 0; animation_i < p_state->animations.size(); animation_i++) {
Dictionary d;
Ref<GLTFAnimation> gltf_animation = p_state->animations[animation_i];
- if (!gltf_animation->get_tracks().size()) {
+ if (gltf_animation->is_empty_of_tracks()) {
continue;
}
@@ -4794,18 +5230,18 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
}
Array channels;
Array samplers;
-
- for (KeyValue<int, GLTFAnimation::Track> &track_i : gltf_animation->get_tracks()) {
- GLTFAnimation::Track track = track_i.value;
+ // Serialize glTF node tracks with the vanilla glTF animation system.
+ for (KeyValue<int, GLTFAnimation::NodeTrack> &track_i : gltf_animation->get_node_tracks()) {
+ GLTFAnimation::NodeTrack track = track_i.value;
if (track.position_track.times.size()) {
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
s["interpolation"] = interpolation_to_string(track.position_track.interpolation);
- Vector<real_t> times = Variant(track.position_track.times);
+ Vector<double> times = track.position_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
- Vector<Vector3> values = Variant(track.position_track.values);
+ Vector<Vector3> values = track.position_track.values;
s["output"] = _encode_accessor_as_vec3(p_state, values, false);
samplers.push_back(s);
@@ -4823,7 +5259,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary s;
s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation);
- Vector<real_t> times = Variant(track.rotation_track.times);
+ Vector<double> times = track.rotation_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
Vector<Quaternion> values = track.rotation_track.values;
s["output"] = _encode_accessor_as_quaternions(p_state, values, false);
@@ -4843,9 +5279,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary s;
s["interpolation"] = interpolation_to_string(track.scale_track.interpolation);
- Vector<real_t> times = Variant(track.scale_track.times);
+ Vector<double> times = track.scale_track.times;
s["input"] = _encode_accessor_as_floats(p_state, times, false);
- Vector<Vector3> values = Variant(track.scale_track.values);
+ Vector<Vector3> values = track.scale_track.values;
s["output"] = _encode_accessor_as_vec3(p_state, values, false);
samplers.push_back(s);
@@ -4868,7 +5304,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
Dictionary t;
t["sampler"] = samplers.size();
Dictionary s;
- Vector<real_t> times;
+ Vector<double> times;
const double increment = 1.0 / p_state->get_bake_fps();
{
double time = 0.0;
@@ -4909,8 +5345,8 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
track.weight_tracks.write[track_idx].values = weight_track;
}
- Vector<real_t> all_track_times = times;
- Vector<real_t> all_track_values;
+ Vector<double> all_track_times = times;
+ Vector<double> all_track_values;
int32_t values_size = track.weight_tracks[0].values.size();
int32_t weight_tracks_size = track.weight_tracks.size();
all_track_values.resize(weight_tracks_size * values_size);
@@ -4937,6 +5373,33 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
channels.push_back(t);
}
}
+ if (!gltf_animation->get_pointer_tracks().is_empty()) {
+ // Serialize glTF pointer tracks with the KHR_animation_pointer extension.
+ if (!p_state->extensions_used.has("KHR_animation_pointer")) {
+ p_state->extensions_used.push_back("KHR_animation_pointer");
+ }
+ for (KeyValue<String, GLTFAnimation::Channel<Variant>> &pointer_track_iter : gltf_animation->get_pointer_tracks()) {
+ const String &json_pointer = pointer_track_iter.key;
+ const GLTFAnimation::Channel<Variant> &pointer_track = pointer_track_iter.value;
+ const Ref<GLTFObjectModelProperty> &obj_model_prop = p_state->object_model_properties[json_pointer];
+ Dictionary channel;
+ channel["sampler"] = samplers.size();
+ Dictionary channel_target;
+ channel_target["path"] = "pointer";
+ Dictionary channel_target_ext;
+ Dictionary channel_target_ext_khr_anim_ptr;
+ channel_target_ext_khr_anim_ptr["pointer"] = json_pointer;
+ channel_target_ext["KHR_animation_pointer"] = channel_target_ext_khr_anim_ptr;
+ channel_target["extensions"] = channel_target_ext;
+ channel["target"] = channel_target;
+ channels.push_back(channel);
+ Dictionary sampler;
+ sampler["input"] = _encode_accessor_as_floats(p_state, pointer_track.times, false);
+ sampler["interpolation"] = interpolation_to_string(pointer_track.interpolation);
+ sampler["output"] = _encode_accessor_as_variant(p_state, pointer_track.values, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type());
+ samplers.push_back(sampler);
+ }
+ }
if (channels.size() && samplers.size()) {
d["channels"] = channels;
d["samplers"] = samplers;
@@ -4961,21 +5424,21 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
const Array &animations = p_state->json["animations"];
- for (GLTFAnimationIndex i = 0; i < animations.size(); i++) {
- const Dictionary &d = animations[i];
+ for (GLTFAnimationIndex anim_index = 0; anim_index < animations.size(); anim_index++) {
+ const Dictionary &anim_dict = animations[anim_index];
Ref<GLTFAnimation> animation;
animation.instantiate();
- if (!d.has("channels") || !d.has("samplers")) {
+ if (!anim_dict.has("channels") || !anim_dict.has("samplers")) {
continue;
}
- Array channels = d["channels"];
- Array samplers = d["samplers"];
+ Array channels = anim_dict["channels"];
+ Array samplers = anim_dict["samplers"];
- if (d.has("name")) {
- const String anim_name = d["name"];
+ if (anim_dict.has("name")) {
+ const String anim_name = anim_dict["name"];
const String anim_name_lower = anim_name.to_lower();
if (anim_name_lower.begins_with("loop") || anim_name_lower.ends_with("loop") || anim_name_lower.begins_with("cycle") || anim_name_lower.ends_with("cycle")) {
animation->set_loop(true);
@@ -4984,46 +5447,22 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
animation->set_name(_gen_unique_animation_name(p_state, anim_name));
}
- for (int j = 0; j < channels.size(); j++) {
- const Dictionary &c = channels[j];
- if (!c.has("target")) {
- continue;
- }
-
- const Dictionary &t = c["target"];
- if (!t.has("node") || !t.has("path")) {
- continue;
- }
-
- ERR_FAIL_COND_V(!c.has("sampler"), ERR_PARSE_ERROR);
- const int sampler = c["sampler"];
- ERR_FAIL_INDEX_V(sampler, samplers.size(), ERR_PARSE_ERROR);
-
- GLTFNodeIndex node = t["node"];
- String path = t["path"];
-
- ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR);
-
- GLTFAnimation::Track *track = nullptr;
-
- if (!animation->get_tracks().has(node)) {
- animation->get_tracks()[node] = GLTFAnimation::Track();
- }
-
- track = &animation->get_tracks()[node];
-
- const Dictionary &s = samplers[sampler];
-
- ERR_FAIL_COND_V(!s.has("input"), ERR_PARSE_ERROR);
- ERR_FAIL_COND_V(!s.has("output"), ERR_PARSE_ERROR);
-
- const int input = s["input"];
- const int output = s["output"];
-
+ for (int channel_index = 0; channel_index < channels.size(); channel_index++) {
+ const Dictionary &anim_channel = channels[channel_index];
+ ERR_FAIL_COND_V_MSG(!anim_channel.has("sampler"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'sampler' property.");
+ ERR_FAIL_COND_V_MSG(!anim_channel.has("target"), ERR_PARSE_ERROR, "glTF: Animation channel missing required 'target' property.");
+ // Parse sampler.
+ const int sampler_index = anim_channel["sampler"];
+ ERR_FAIL_INDEX_V(sampler_index, samplers.size(), ERR_PARSE_ERROR);
+ const Dictionary &sampler_dict = samplers[sampler_index];
+ ERR_FAIL_COND_V(!sampler_dict.has("input"), ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(!sampler_dict.has("output"), ERR_PARSE_ERROR);
+ const int input_time_accessor_index = sampler_dict["input"];
+ const int output_value_accessor_index = sampler_dict["output"];
GLTFAnimation::Interpolation interp = GLTFAnimation::INTERP_LINEAR;
int output_count = 1;
- if (s.has("interpolation")) {
- const String &in = s["interpolation"];
+ if (sampler_dict.has("interpolation")) {
+ const String &in = sampler_dict["interpolation"];
if (in == "STEP") {
interp = GLTFAnimation::INTERP_STEP;
} else if (in == "LINEAR") {
@@ -5036,52 +5475,83 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
output_count = 3;
}
}
+ const Vector<double> times = _decode_accessor(p_state, input_time_accessor_index, false);
+ // Parse target.
+ const Dictionary &anim_target = anim_channel["target"];
+ ERR_FAIL_COND_V_MSG(!anim_target.has("path"), ERR_PARSE_ERROR, "glTF: Animation channel target missing required 'path' property.");
+ String path = anim_target["path"];
+ if (path == "pointer") {
+ ERR_FAIL_COND_V(!anim_target.has("extensions"), ERR_PARSE_ERROR);
+ Dictionary target_extensions = anim_target["extensions"];
+ ERR_FAIL_COND_V(!target_extensions.has("KHR_animation_pointer"), ERR_PARSE_ERROR);
+ Dictionary khr_anim_ptr = target_extensions["KHR_animation_pointer"];
+ ERR_FAIL_COND_V(!khr_anim_ptr.has("pointer"), ERR_PARSE_ERROR);
+ String anim_json_ptr = khr_anim_ptr["pointer"];
+ _parse_animation_pointer(p_state, anim_json_ptr, animation, interp, times, output_value_accessor_index);
+ } else {
+ // If it's not a pointer, it's a regular animation channel from vanilla glTF (pos/rot/scale/weights).
+ if (!anim_target.has("node")) {
+ WARN_PRINT("glTF: Animation channel target missing 'node' property. Ignoring this channel.");
+ continue;
+ }
- const Vector<float> times = _decode_accessor_as_floats(p_state, input, false);
- if (path == "translation") {
- const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output, false);
- track->position_track.interpolation = interp;
- track->position_track.times = Variant(times); //convert via variant
- track->position_track.values = Variant(positions); //convert via variant
- } else if (path == "rotation") {
- const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output, false);
- track->rotation_track.interpolation = interp;
- track->rotation_track.times = Variant(times); //convert via variant
- track->rotation_track.values = rotations;
- } else if (path == "scale") {
- const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output, false);
- track->scale_track.interpolation = interp;
- track->scale_track.times = Variant(times); //convert via variant
- track->scale_track.values = Variant(scales); //convert via variant
- } else if (path == "weights") {
- const Vector<float> weights = _decode_accessor_as_floats(p_state, output, false);
-
- ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR);
- Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh];
- ERR_CONTINUE(!mesh->get_blend_weights().size());
- const int wc = mesh->get_blend_weights().size();
-
- track->weight_tracks.resize(wc);
-
- const int expected_value_count = times.size() * output_count * wc;
- ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
-
- const int wlen = weights.size() / wc;
- for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
- GLTFAnimation::Channel<real_t> cf;
- cf.interpolation = interp;
- cf.times = Variant(times);
- Vector<real_t> wdata;
- wdata.resize(wlen);
- for (int l = 0; l < wlen; l++) {
- wdata.write[l] = weights[l * wc + k];
- }
+ GLTFNodeIndex node = anim_target["node"];
+
+ ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR);
- cf.values = wdata;
- track->weight_tracks.write[k] = cf;
+ GLTFAnimation::NodeTrack *track = nullptr;
+
+ if (!animation->get_node_tracks().has(node)) {
+ animation->get_node_tracks()[node] = GLTFAnimation::NodeTrack();
+ }
+
+ track = &animation->get_node_tracks()[node];
+
+ if (path == "translation") {
+ const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false);
+ track->position_track.interpolation = interp;
+ track->position_track.times = times;
+ track->position_track.values = positions;
+ } else if (path == "rotation") {
+ const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output_value_accessor_index, false);
+ track->rotation_track.interpolation = interp;
+ track->rotation_track.times = times;
+ track->rotation_track.values = rotations;
+ } else if (path == "scale") {
+ const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output_value_accessor_index, false);
+ track->scale_track.interpolation = interp;
+ track->scale_track.times = times;
+ track->scale_track.values = scales;
+ } else if (path == "weights") {
+ const Vector<float> weights = _decode_accessor_as_floats(p_state, output_value_accessor_index, false);
+
+ ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR);
+ Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh];
+ const int wc = mesh->get_blend_weights().size();
+ ERR_CONTINUE_MSG(wc == 0, "glTF: Animation tried to animate weights, but mesh has no weights.");
+
+ track->weight_tracks.resize(wc);
+
+ const int expected_value_count = times.size() * output_count * wc;
+ ERR_CONTINUE_MSG(weights.size() != expected_value_count, "Invalid weight data, expected " + itos(expected_value_count) + " weight values, got " + itos(weights.size()) + " instead.");
+
+ const int wlen = weights.size() / wc;
+ for (int k = 0; k < wc; k++) { //separate tracks, having them together is not such a good idea
+ GLTFAnimation::Channel<real_t> cf;
+ cf.interpolation = interp;
+ cf.times = Variant(times);
+ Vector<real_t> wdata;
+ wdata.resize(wlen);
+ for (int l = 0; l < wlen; l++) {
+ wdata.write[l] = weights[l * wc + k];
+ }
+
+ cf.values = wdata;
+ track->weight_tracks.write[k] = cf;
+ }
+ } else {
+ WARN_PRINT("Invalid path '" + path + "'.");
}
- } else {
- WARN_PRINT("Invalid path '" + path + "'.");
}
}
@@ -5093,6 +5563,96 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) {
return OK;
}
+void GLTFDocument::_parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index) {
+ // Special case: Convert TRS animation pointers to node track pos/rot/scale.
+ // This is required to handle skeleton bones, and improves performance for regular nodes.
+ // Mark this as unlikely because TRS animation pointers are not recommended,
+ // since vanilla glTF animations can already animate TRS properties directly.
+ // But having this code exist is required to be spec-compliant and handle all test files.
+ // Note that TRS still needs to be handled in the general case as well, for KHR_interactivity.
+ const PackedStringArray split = p_animation_json_pointer.split("/", false, 3);
+ if (unlikely(split.size() == 3 && split[0] == "nodes" && (split[2] == "translation" || split[2] == "rotation" || split[2] == "scale" || split[2] == "matrix" || split[2] == "weights"))) {
+ const GLTFNodeIndex node_index = split[1].to_int();
+ HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = p_gltf_animation->get_node_tracks();
+ if (!node_tracks.has(node_index)) {
+ node_tracks[node_index] = GLTFAnimation::NodeTrack();
+ }
+ GLTFAnimation::NodeTrack *track = &node_tracks[node_index];
+ if (split[2] == "translation") {
+ const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false);
+ track->position_track.interpolation = p_interp;
+ track->position_track.times = p_times;
+ track->position_track.values = positions;
+ } else if (split[2] == "rotation") {
+ const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, p_output_value_accessor_index, false);
+ track->rotation_track.interpolation = p_interp;
+ track->rotation_track.times = p_times;
+ track->rotation_track.values = rotations;
+ } else if (split[2] == "scale") {
+ const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, p_output_value_accessor_index, false);
+ track->scale_track.interpolation = p_interp;
+ track->scale_track.times = p_times;
+ track->scale_track.values = scales;
+ } else if (split[2] == "matrix") {
+ const Vector<Transform3D> transforms = _decode_accessor_as_xform(p_state, p_output_value_accessor_index, false);
+ track->position_track.interpolation = p_interp;
+ track->position_track.times = p_times;
+ track->position_track.values.resize(transforms.size());
+ track->rotation_track.interpolation = p_interp;
+ track->rotation_track.times = p_times;
+ track->rotation_track.values.resize(transforms.size());
+ track->scale_track.interpolation = p_interp;
+ track->scale_track.times = p_times;
+ track->scale_track.values.resize(transforms.size());
+ for (int i = 0; i < transforms.size(); i++) {
+ track->position_track.values.write[i] = transforms[i].get_origin();
+ track->rotation_track.values.write[i] = transforms[i].basis.get_rotation_quaternion();
+ track->scale_track.values.write[i] = transforms[i].basis.get_scale();
+ }
+ } else { // if (split[2] == "weights")
+ const Vector<float> accessor_weights = _decode_accessor_as_floats(p_state, p_output_value_accessor_index, false);
+ const GLTFMeshIndex mesh_index = p_state->nodes[node_index]->mesh;
+ ERR_FAIL_INDEX(mesh_index, p_state->meshes.size());
+ const Ref<GLTFMesh> gltf_mesh = p_state->meshes[mesh_index];
+ const Vector<float> &blend_weights = gltf_mesh->get_blend_weights();
+ const int blend_weight_count = gltf_mesh->get_blend_weights().size();
+ const int anim_weights_size = accessor_weights.size();
+ // For example, if a mesh has 2 blend weights, and the accessor provides 10 values, then there are 5 frames of animation, each with 2 blend weights.
+ ERR_FAIL_COND_MSG(blend_weight_count == 0 || ((anim_weights_size % blend_weight_count) != 0), "glTF: Cannot apply " + itos(accessor_weights.size()) + " weights to a mesh with " + itos(blend_weights.size()) + " blend weights.");
+ const int frame_count = anim_weights_size / blend_weight_count;
+ track->weight_tracks.resize(blend_weight_count);
+ for (int blend_weight_index = 0; blend_weight_index < blend_weight_count; blend_weight_index++) {
+ GLTFAnimation::Channel<real_t> weight_track;
+ weight_track.interpolation = p_interp;
+ weight_track.times = p_times;
+ weight_track.values.resize(frame_count);
+ for (int frame_index = 0; frame_index < frame_count; frame_index++) {
+ // For example, if a mesh has 2 blend weights, and the accessor provides 10 values,
+ // then the first frame has indices [0, 1], the second frame has [2, 3], and so on.
+ // Here we process all frames of one blend weight, so we want [0, 2, 4, 6, 8] or [1, 3, 5, 7, 9].
+ // For the fist one we calculate 0 * 2 + 0, 1 * 2 + 0, 2 * 2 + 0, etc, then for the second 0 * 2 + 1, 1 * 2 + 1, 2 * 2 + 1, etc.
+ weight_track.values.write[frame_index] = accessor_weights[frame_index * blend_weight_count + blend_weight_index];
+ }
+ track->weight_tracks.write[blend_weight_index] = weight_track;
+ }
+ }
+ // The special case was handled, return to skip the general case.
+ return;
+ }
+ // General case: Convert animation pointers to Variant value pointer tracks.
+ Ref<GLTFObjectModelProperty> obj_model_prop = import_object_model_property(p_state, p_animation_json_pointer);
+ if (obj_model_prop.is_null() || !obj_model_prop->has_node_paths()) {
+ // Exit quietly, `import_object_model_property` already prints a warning if the property is not found.
+ return;
+ }
+ HashMap<String, GLTFAnimation::Channel<Variant>> &anim_ptr_map = p_gltf_animation->get_pointer_tracks();
+ GLTFAnimation::Channel<Variant> channel;
+ channel.interpolation = p_interp;
+ channel.times = p_times;
+ channel.values = _decode_accessor_as_variant(p_state, p_output_value_accessor_index, obj_model_prop->get_variant_type(), obj_model_prop->get_accessor_type());
+ anim_ptr_map[p_animation_json_pointer] = channel;
+}
+
void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) {
for (int i = 0; i < p_state->nodes.size(); i++) {
Ref<GLTFNode> gltf_node = p_state->nodes[i];
@@ -5258,46 +5818,46 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_node->set_original_name(p_current->get_name());
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
gltf_node->merge_meta_from(p_current);
- if (cast_to<Node3D>(p_current)) {
- Node3D *spatial = cast_to<Node3D>(p_current);
+ if (Object::cast_to<Node3D>(p_current)) {
+ Node3D *spatial = Object::cast_to<Node3D>(p_current);
_convert_spatial(p_state, spatial, gltf_node);
}
- if (cast_to<MeshInstance3D>(p_current)) {
- MeshInstance3D *mi = cast_to<MeshInstance3D>(p_current);
+ if (Object::cast_to<MeshInstance3D>(p_current)) {
+ MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_current);
_convert_mesh_instance_to_gltf(mi, p_state, gltf_node);
- } else if (cast_to<BoneAttachment3D>(p_current)) {
- BoneAttachment3D *bone = cast_to<BoneAttachment3D>(p_current);
+ } else if (Object::cast_to<BoneAttachment3D>(p_current)) {
+ BoneAttachment3D *bone = Object::cast_to<BoneAttachment3D>(p_current);
_convert_bone_attachment_to_gltf(bone, p_state, p_gltf_parent, p_gltf_root, gltf_node);
return;
- } else if (cast_to<Skeleton3D>(p_current)) {
- Skeleton3D *skel = cast_to<Skeleton3D>(p_current);
+ } else if (Object::cast_to<Skeleton3D>(p_current)) {
+ Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_current);
_convert_skeleton_to_gltf(skel, p_state, p_gltf_parent, p_gltf_root, gltf_node);
// We ignore the Godot Engine node that is the skeleton.
return;
- } else if (cast_to<MultiMeshInstance3D>(p_current)) {
- MultiMeshInstance3D *multi = cast_to<MultiMeshInstance3D>(p_current);
+ } else if (Object::cast_to<MultiMeshInstance3D>(p_current)) {
+ MultiMeshInstance3D *multi = Object::cast_to<MultiMeshInstance3D>(p_current);
_convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, p_state);
#ifdef MODULE_CSG_ENABLED
- } else if (cast_to<CSGShape3D>(p_current)) {
- CSGShape3D *shape = cast_to<CSGShape3D>(p_current);
+ } else if (Object::cast_to<CSGShape3D>(p_current)) {
+ CSGShape3D *shape = Object::cast_to<CSGShape3D>(p_current);
if (shape->get_parent() && shape->is_root_shape()) {
_convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state);
}
#endif // MODULE_CSG_ENABLED
#ifdef MODULE_GRIDMAP_ENABLED
- } else if (cast_to<GridMap>(p_current)) {
+ } else if (Object::cast_to<GridMap>(p_current)) {
GridMap *gridmap = Object::cast_to<GridMap>(p_current);
_convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state);
#endif // MODULE_GRIDMAP_ENABLED
- } else if (cast_to<Camera3D>(p_current)) {
+ } else if (Object::cast_to<Camera3D>(p_current)) {
Camera3D *camera = Object::cast_to<Camera3D>(p_current);
_convert_camera_to_gltf(camera, p_state, gltf_node);
- } else if (cast_to<Light3D>(p_current)) {
+ } else if (Object::cast_to<Light3D>(p_current)) {
Light3D *light = Object::cast_to<Light3D>(p_current);
_convert_light_to_gltf(light, p_state, gltf_node);
- } else if (cast_to<AnimationPlayer>(p_current)) {
+ } else if (Object::cast_to<AnimationPlayer>(p_current)) {
AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
- _convert_animation_player_to_gltf(animation_player, p_state, p_gltf_parent, p_gltf_root, gltf_node, p_current);
+ p_state->animation_players.push_back(animation_player);
}
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
@@ -5353,7 +5913,7 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
mat_name = mat->get_name();
} else {
// Assign default material when no material is assigned.
- mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+ mat.instantiate();
}
mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i),
@@ -5375,12 +5935,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
}
#endif // MODULE_CSG_ENABLED
-void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- ERR_FAIL_NULL(p_animation_player);
- p_state->animation_players.push_back(p_animation_player);
- print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name());
-}
-
void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) {
r_retflag = true;
Node3D *spatial = Object::cast_to<Node3D>(p_node);
@@ -5573,9 +6127,9 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att
Skeleton3D *skeleton;
// Note that relative transforms to external skeletons and pose overrides are not supported.
if (p_bone_attachment->get_use_external_skeleton()) {
- skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
+ skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_node_or_null(p_bone_attachment->get_external_skeleton()));
} else {
- skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent());
+ skeleton = Object::cast_to<Skeleton3D>(p_bone_attachment->get_parent());
}
GLTFSkeletonIndex skel_gltf_i = -1;
if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) {
@@ -5858,7 +6412,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
};
template <typename T>
-T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
+T GLTFDocument::_interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp) {
ERR_FAIL_COND_V(p_values.is_empty(), T());
if (p_times.size() != (p_values.size() / (p_interp == GLTFAnimation::INTERP_CUBIC_SPLINE ? 3 : 1))) {
ERR_PRINT_ONCE("The interpolated values are not corresponding to its times.");
@@ -5929,8 +6483,433 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
ERR_FAIL_V(p_values[0]);
}
+NodePath GLTFDocument::_find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material) {
+ int mesh_index = 0;
+ for (Ref<GLTFMesh> gltf_mesh : p_state->meshes) {
+ TypedArray<Material> materials = gltf_mesh->get_instance_materials();
+ for (int mat_index = 0; mat_index < materials.size(); mat_index++) {
+ if (materials[mat_index] == p_material) {
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->mesh == mesh_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ // Example: MyNode:mesh:surface_0/material:albedo_color, so we want the mesh:surface_0/material part.
+ Vector<StringName> subpath;
+ subpath.append("mesh");
+ subpath.append("surface_" + itos(mat_index) + "/material");
+ return NodePath(node_path.get_names(), subpath, false);
+ }
+ }
+ }
+ }
+ mesh_index++;
+ }
+ return NodePath();
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocument::import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer) {
+ if (p_state->object_model_properties.has(p_json_pointer)) {
+ return p_state->object_model_properties[p_json_pointer];
+ }
+ Ref<GLTFObjectModelProperty> ret;
+ // Split the JSON pointer into its components.
+ const PackedStringArray split = p_json_pointer.split("/", false);
+ ERR_FAIL_COND_V_MSG(split.size() < 3, ret, "glTF: Cannot use JSON pointer '" + p_json_pointer + "' because it does not contain enough elements. The only animatable properties are at least 3 levels deep (ex: '/nodes/0/translation' or '/materials/0/emissiveFactor').");
+ ret.instantiate();
+ ret->set_json_pointers({ split });
+ // Partial paths are passed to GLTFDocumentExtension classes if GLTFDocument cannot handle a given JSON pointer.
+ TypedArray<NodePath> partial_paths;
+ // Note: This might not be an integer, but in that case, we don't use this value anyway.
+ const int top_level_index = split[1].to_int();
+ // For JSON pointers present in the core glTF Object Model, hard-code them in GLTFDocument.
+ // https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc
+ if (split[0] == "nodes") {
+ ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->nodes.size(), ret, vformat("glTF: Unable to find node %d for JSON pointer '%s'.", top_level_index, p_json_pointer));
+ Ref<GLTFNode> pointed_gltf_node = p_state->nodes[top_level_index];
+ NodePath node_path = pointed_gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ // Check if it's something we should be able to handle.
+ const String &node_prop = split[2];
+ if (node_prop == "translation") {
+ ret->append_path_to_property(node_path, "position");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == "rotation") {
+ ret->append_path_to_property(node_path, "quaternion");
+ ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (node_prop == "scale") {
+ ret->append_path_to_property(node_path, "scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (node_prop == "matrix") {
+ ret->append_path_to_property(node_path, "transform");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (node_prop == "globalMatrix") {
+ ret->append_path_to_property(node_path, "global_transform");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (node_prop == "weights") {
+ if (split.size() > 3) {
+ const String &weight_index_string = split[3];
+ ret->append_path_to_property(node_path, "blend_shapes/morph_" + weight_index_string);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ // Else, Godot's MeshInstance3D does not expose the blend shape weights as one property.
+ // But that's fine, we handle this case in _parse_animation_pointer instead.
+ }
+ } else if (split[0] == "cameras") {
+ const String &camera_prop = split[2];
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->camera == top_level_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ // Check if it's something we should be able to handle.
+ if (camera_prop == "orthographic" || camera_prop == "perspective") {
+ ERR_FAIL_COND_V(split.size() < 4, ret);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ const String &sub_prop = split[3];
+ if (sub_prop == "xmag" || sub_prop == "ymag") {
+ ret->append_path_to_property(node_path, "size");
+ } else if (sub_prop == "yfov") {
+ ret->append_path_to_property(node_path, "fov");
+ GLTFCamera::set_fov_conversion_expressions(ret);
+ } else if (sub_prop == "zfar") {
+ ret->append_path_to_property(node_path, "far");
+ } else if (sub_prop == "znear") {
+ ret->append_path_to_property(node_path, "near");
+ }
+ }
+ }
+ }
+ } else if (split[0] == "materials") {
+ ERR_FAIL_INDEX_V_MSG(top_level_index, p_state->materials.size(), ret, vformat("glTF: Unable to find material %d for JSON pointer '%s'.", top_level_index, p_json_pointer));
+ Ref<Material> pointed_material = p_state->materials[top_level_index];
+ NodePath mat_path = _find_material_node_path(p_state, pointed_material);
+ if (mat_path.is_empty()) {
+ WARN_PRINT(vformat("glTF: Unable to find a path to the material %d for JSON pointer '%s'. This is likely bad data but it's also possible this is intentional. Continuing anyway.", top_level_index, p_json_pointer));
+ } else {
+ partial_paths.append(mat_path);
+ const String &mat_prop = split[2];
+ if (mat_prop == "alphaCutoff") {
+ ret->append_path_to_property(mat_path, "alpha_scissor_threshold");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (mat_prop == "emissiveFactor") {
+ ret->append_path_to_property(mat_path, "emission");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (mat_prop == "extensions") {
+ ERR_FAIL_COND_V(split.size() < 5, ret);
+ const String &ext_name = split[3];
+ const String &ext_prop = split[4];
+ if (ext_name == "KHR_materials_emissive_strength" && ext_prop == "emissiveStrength") {
+ ret->append_path_to_property(mat_path, "emission_energy_multiplier");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else {
+ ERR_FAIL_COND_V(split.size() < 4, ret);
+ const String &sub_prop = split[3];
+ if (mat_prop == "normalTexture") {
+ if (sub_prop == "scale") {
+ ret->append_path_to_property(mat_path, "normal_scale");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else if (mat_prop == "occlusionTexture") {
+ if (sub_prop == "strength") {
+ // This is the closest thing Godot has to an occlusion strength property.
+ ret->append_path_to_property(mat_path, "ao_light_affect");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ }
+ } else if (mat_prop == "pbrMetallicRoughness") {
+ if (sub_prop == "baseColorFactor") {
+ ret->append_path_to_property(mat_path, "albedo_color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (sub_prop == "metallicFactor") {
+ ret->append_path_to_property(mat_path, "metallic");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (sub_prop == "roughnessFactor") {
+ ret->append_path_to_property(mat_path, "roughness");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (sub_prop == "baseColorTexture") {
+ ERR_FAIL_COND_V(split.size() < 6, ret);
+ const String &tex_ext_dict = split[4];
+ const String &tex_ext_name = split[5];
+ const String &tex_ext_prop = split[6];
+ if (tex_ext_dict == "extensions" && tex_ext_name == "KHR_texture_transform") {
+ // Godot only supports UVs for the whole material, not per texture.
+ // We treat the albedo texture as the main texture, and import as UV1.
+ // Godot does not support texture rotation, only offset and scale.
+ if (tex_ext_prop == "offset") {
+ ret->append_path_to_property(mat_path, "uv1_offset");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ } else if (tex_ext_prop == "scale") {
+ ret->append_path_to_property(mat_path, "uv1_scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (split[0] == "meshes") {
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->mesh == top_level_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ Vector<StringName> subpath;
+ subpath.append("mesh");
+ partial_paths.append(NodePath(node_path.get_names(), subpath, false));
+ break;
+ }
+ }
+ } else if (split[0] == "extensions") {
+ if (split[1] == "KHR_lights_punctual" && split[2] == "lights" && split.size() > 4) {
+ const int light_index = split[3].to_int();
+ ERR_FAIL_INDEX_V_MSG(light_index, p_state->lights.size(), ret, vformat("glTF: Unable to find light %d for JSON pointer '%s'.", light_index, p_json_pointer));
+ const String &light_prop = split[4];
+ const Ref<GLTFLight> pointed_light = p_state->lights[light_index];
+ for (Ref<GLTFNode> gltf_node : p_state->nodes) {
+ if (gltf_node->light == light_index) {
+ NodePath node_path = gltf_node->get_scene_node_path(p_state);
+ partial_paths.append(node_path);
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ // Check if it's something we should be able to handle.
+ if (light_prop == "color") {
+ ret->append_path_to_property(node_path, "light_color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (light_prop == "intensity") {
+ ret->append_path_to_property(node_path, "light_energy");
+ } else if (light_prop == "range") {
+ const String &light_type = p_state->lights[light_index]->light_type;
+ if (light_type == "spot") {
+ ret->append_path_to_property(node_path, "spot_range");
+ } else {
+ ret->append_path_to_property(node_path, "omni_range");
+ }
+ } else if (light_prop == "spot") {
+ ERR_FAIL_COND_V(split.size() < 6, ret);
+ const String &sub_prop = split[5];
+ if (sub_prop == "innerConeAngle") {
+ ret->append_path_to_property(node_path, "spot_angle_attenuation");
+ GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret);
+ } else if (sub_prop == "outerConeAngle") {
+ ret->append_path_to_property(node_path, "spot_angle");
+ }
+ }
+ }
+ }
+ }
+ }
+ // Additional JSON pointers can be added by GLTFDocumentExtension classes.
+ // We only need this if no mapping has been found yet from GLTFDocument's internal code.
+ // When available, we pass the partial paths to the extension to help it generate the full path.
+ // For example, for `/nodes/3/extensions/MY_ext/prop`, we pass a NodePath that leads to node 3,
+ // so the GLTFDocumentExtension class only needs to resolve the last `MY_ext/prop` part of the path.
+ // It should check `split.size() > 4 and split[0] == "nodes" and split[2] == "extensions" and split[3] == "MY_ext"`
+ // at the start of the function to check if this JSON pointer applies to it, then it can handle `split[4]`.
+ if (!ret->has_node_paths()) {
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ret = ext->import_object_model_property(p_state, split, partial_paths);
+ if (ret.is_valid() && ret->has_node_paths()) {
+ if (!ret->has_json_pointers()) {
+ ret->set_json_pointers({ split });
+ }
+ break;
+ }
+ }
+ if (ret.is_null() || !ret->has_node_paths()) {
+ if (split.has("KHR_texture_transform")) {
+ WARN_VERBOSE(vformat("glTF: Texture transforms are only supported per material in Godot. All KHR_texture_transform properties will be ignored except for the albedo texture. Ignoring JSON pointer '%s'.", p_json_pointer));
+ } else {
+ WARN_PRINT(vformat("glTF: Animation contained JSON pointer '%s' which could not be resolved. This property will not be animated.", p_json_pointer));
+ }
+ }
+ }
+ p_state->object_model_properties[p_json_pointer] = ret;
+ return ret;
+}
+
+Ref<GLTFObjectModelProperty> GLTFDocument::export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index) {
+ Ref<GLTFObjectModelProperty> ret;
+ const Object *target_object = p_godot_node;
+ const Vector<StringName> subpath = p_node_path.get_subnames();
+ ERR_FAIL_COND_V_MSG(subpath.is_empty(), ret, "glTF: Cannot export empty property. No property was specified in the NodePath: " + p_node_path);
+ int target_prop_depth = 0;
+ for (StringName subname : subpath) {
+ Variant target_property = target_object->get(subname);
+ if (target_property.get_type() == Variant::OBJECT) {
+ target_object = target_property;
+ if (target_object) {
+ target_prop_depth++;
+ continue;
+ }
+ }
+ break;
+ }
+ const String &target_prop = subpath[target_prop_depth];
+ ret.instantiate();
+ ret->set_node_paths({ p_node_path });
+ Vector<PackedStringArray> split_json_pointers;
+ PackedStringArray split_json_pointer;
+ if (Object::cast_to<BaseMaterial3D>(target_object)) {
+ for (int i = 0; i < p_state->materials.size(); i++) {
+ if (p_state->materials[i].ptr() == target_object) {
+ split_json_pointer.append("materials");
+ split_json_pointer.append(itos(i));
+ if (target_prop == "alpha_scissor_threshold") {
+ split_json_pointer.append("alphaCutoff");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "emission") {
+ split_json_pointer.append("emissiveFactor");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "emission_energy_multiplier") {
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_materials_emissive_strength");
+ split_json_pointer.append("emissiveStrength");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "normal_scale") {
+ split_json_pointer.append("normalTexture");
+ split_json_pointer.append("scale");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "ao_light_affect") {
+ split_json_pointer.append("occlusionTexture");
+ split_json_pointer.append("strength");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "albedo_color") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("baseColorFactor");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (target_prop == "metallic") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("metallicFactor");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "roughness") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("roughnessFactor");
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ } else if (target_prop == "uv1_offset" || target_prop == "uv1_scale") {
+ split_json_pointer.append("pbrMetallicRoughness");
+ split_json_pointer.append("baseColorTexture");
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_texture_transform");
+ if (target_prop == "uv1_offset") {
+ split_json_pointer.append("offset");
+ } else {
+ split_json_pointer.append("scale");
+ }
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ } else {
+ split_json_pointer.clear();
+ }
+ break;
+ }
+ }
+ } else {
+ // Properties directly on Godot nodes.
+ Ref<GLTFNode> gltf_node = p_state->nodes[p_gltf_node_index];
+ if (Object::cast_to<Camera3D>(target_object) && gltf_node->camera >= 0) {
+ split_json_pointer.append("cameras");
+ split_json_pointer.append(itos(gltf_node->camera));
+ const Camera3D *camera_node = Object::cast_to<Camera3D>(target_object);
+ const Camera3D::ProjectionType projection_type = camera_node->get_projection();
+ if (projection_type == Camera3D::PROJECTION_PERSPECTIVE) {
+ split_json_pointer.append("perspective");
+ } else {
+ split_json_pointer.append("orthographic");
+ }
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ if (target_prop == "size") {
+ PackedStringArray xmag = split_json_pointer.duplicate();
+ xmag.append("xmag");
+ split_json_pointers.append(xmag);
+ split_json_pointer.append("ymag");
+ } else if (target_prop == "fov") {
+ split_json_pointer.append("yfov");
+ GLTFCamera::set_fov_conversion_expressions(ret);
+ } else if (target_prop == "far") {
+ split_json_pointer.append("zfar");
+ } else if (target_prop == "near") {
+ split_json_pointer.append("znear");
+ } else {
+ split_json_pointer.clear();
+ }
+ } else if (Object::cast_to<Light3D>(target_object) && gltf_node->light >= 0) {
+ split_json_pointer.append("extensions");
+ split_json_pointer.append("KHR_lights_punctual");
+ split_json_pointer.append("lights");
+ split_json_pointer.append(itos(gltf_node->light));
+ ret->set_types(Variant::FLOAT, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ if (target_prop == "light_color") {
+ split_json_pointer.append("color");
+ ret->set_types(Variant::COLOR, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "light_energy") {
+ split_json_pointer.append("intensity");
+ } else if (target_prop == "spot_range") {
+ split_json_pointer.append("range");
+ } else if (target_prop == "omni_range") {
+ split_json_pointer.append("range");
+ } else if (target_prop == "spot_angle") {
+ split_json_pointer.append("spot");
+ split_json_pointer.append("outerConeAngle");
+ } else if (target_prop == "spot_angle_attenuation") {
+ split_json_pointer.append("spot");
+ split_json_pointer.append("innerConeAngle");
+ GLTFLight::set_cone_inner_attenuation_conversion_expressions(ret);
+ } else {
+ split_json_pointer.clear();
+ }
+ } else if (Object::cast_to<MeshInstance3D>(target_object) && target_prop.begins_with("blend_shapes/morph_")) {
+ const String &weight_index_string = target_prop.trim_prefix("blend_shapes/morph_");
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ split_json_pointer.append("weights");
+ split_json_pointer.append(weight_index_string);
+ }
+ // Transform properties. Check for all 3D nodes if we haven't resolved the JSON pointer yet.
+ // Note: Do not put this in an `else`, because otherwise this will not be reached.
+ if (split_json_pointer.is_empty() && Object::cast_to<Node3D>(target_object)) {
+ split_json_pointer.append("nodes");
+ split_json_pointer.append(itos(p_gltf_node_index));
+ if (target_prop == "position") {
+ split_json_pointer.append("translation");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "quaternion") {
+ // Note: Only Quaternion rotation can be converted from Godot in this mapping.
+ // Struct methods like from_euler are not accessible from the Expression class. :(
+ split_json_pointer.append("rotation");
+ ret->set_types(Variant::QUATERNION, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ } else if (target_prop == "scale") {
+ split_json_pointer.append("scale");
+ ret->set_types(Variant::VECTOR3, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ } else if (target_prop == "transform") {
+ split_json_pointer.append("matrix");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else if (target_prop == "global_transform") {
+ split_json_pointer.append("globalMatrix");
+ ret->set_types(Variant::TRANSFORM3D, GLTFObjectModelProperty::GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ } else {
+ split_json_pointer.clear();
+ }
+ }
+ }
+ // Additional JSON pointers can be added by GLTFDocumentExtension classes.
+ // We only need this if no mapping has been found yet from GLTFDocument's internal code.
+ // We pass as many pieces of information as we can to the extension to give it lots of context.
+ if (split_json_pointer.is_empty()) {
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ret = ext->export_object_model_property(p_state, p_node_path, p_godot_node, p_gltf_node_index, target_object, target_prop_depth);
+ if (ret.is_valid() && ret->has_json_pointers()) {
+ if (!ret->has_node_paths()) {
+ ret->set_node_paths({ p_node_path });
+ }
+ break;
+ }
+ }
+ } else {
+ // GLTFDocument's internal code found a mapping, so set it and return it.
+ split_json_pointers.append(split_json_pointer);
+ ret->set_json_pointers(split_json_pointers);
+ }
+ return ret;
+}
+
void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks) {
ERR_FAIL_COND(p_state.is_null());
+ Node *scene_root = p_animation_player->get_parent();
+ ERR_FAIL_NULL(scene_root);
Ref<GLTFAnimation> anim = p_state->animations[p_index];
String anim_name = anim->get_name();
@@ -5951,8 +6930,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
double anim_start = p_trimming ? INFINITY : 0.0;
double anim_end = 0.0;
- for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
- const GLTFAnimation::Track &track = track_i.value;
+ for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
+ const GLTFAnimation::NodeTrack &track = track_i.value;
//need to find the path: for skeletons, weight tracks will affect the mesh
NodePath node_path;
//for skeletons, transform tracks always affect bones
@@ -5964,14 +6943,12 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key];
- Node *root = p_animation_player->get_parent();
- ERR_FAIL_NULL(root);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
- node_path = root->get_path_to(node_element->value);
+ node_path = scene_root->get_path_to(node_element->value);
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *>::Iterator mesh_instance_element = p_state->scene_mesh_instances.find(node_index);
if (mesh_instance_element) {
- mesh_instance_node_path = root->get_path_to(mesh_instance_element->value);
+ mesh_instance_node_path = scene_root->get_path_to(mesh_instance_element->value);
} else {
mesh_instance_node_path = node_path;
}
@@ -6205,6 +7182,56 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
}
}
+ for (const KeyValue<String, GLTFAnimation::Channel<Variant>> &track_iter : anim->get_pointer_tracks()) {
+ // Determine the property to animate.
+ const String json_pointer = track_iter.key;
+ const Ref<GLTFObjectModelProperty> prop = import_object_model_property(p_state, json_pointer);
+ ERR_FAIL_COND(prop.is_null());
+ // Adjust the animation duration to encompass all keyframes.
+ const GLTFAnimation::Channel<Variant> &channel = track_iter.value;
+ ERR_CONTINUE_MSG(channel.times.size() != channel.values.size(), vformat("glTF: Animation pointer '%s' has mismatched keyframe times and values.", json_pointer));
+ if (p_trimming) {
+ for (int i = 0; i < channel.times.size(); i++) {
+ anim_start = MIN(anim_start, channel.times[i]);
+ anim_end = MAX(anim_end, channel.times[i]);
+ }
+ } else {
+ for (int i = 0; i < channel.times.size(); i++) {
+ anim_end = MAX(anim_end, channel.times[i]);
+ }
+ }
+ // Begin converting the glTF animation to a Godot animation.
+ const Ref<Expression> gltf_to_godot_expr = prop->get_gltf_to_godot_expression();
+ const bool is_gltf_to_godot_expr_valid = gltf_to_godot_expr.is_valid();
+ for (const NodePath node_path : prop->get_node_paths()) {
+ // If using an expression, determine the base instance to pass to the expression.
+ Object *base_instance = nullptr;
+ if (is_gltf_to_godot_expr_valid) {
+ Ref<Resource> resource;
+ Vector<StringName> leftover_subpath;
+ base_instance = scene_root->get_node_and_resource(node_path, resource, leftover_subpath);
+ if (resource.is_valid()) {
+ base_instance = resource.ptr();
+ }
+ }
+ // Add a track and insert all keys and values.
+ const int track_index = animation->get_track_count();
+ animation->add_track(Animation::TYPE_VALUE);
+ animation->track_set_interpolation_type(track_index, GLTFAnimation::gltf_to_godot_interpolation(channel.interpolation));
+ animation->track_set_path(track_index, node_path);
+ for (int i = 0; i < channel.times.size(); i++) {
+ const double time = channel.times[i];
+ Variant value = channel.values[i];
+ if (is_gltf_to_godot_expr_valid) {
+ Array inputs;
+ inputs.append(value);
+ value = gltf_to_godot_expr->execute(inputs, base_instance);
+ }
+ animation->track_insert_key(track_index, time, value);
+ }
+ }
+ }
+
animation->set_length(anim_end - anim_start);
Ref<AnimationLibrary> library;
@@ -6375,41 +7402,56 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene
}
}
-GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i) {
- Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i);
-
- GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
- gltf_interpolation = GLTFAnimation::INTERP_STEP;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
- gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
+GLTFNodeIndex GLTFDocument::_node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node) {
+ const Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_godot_node);
+ if (skeleton && p_node_subpath.size() == 1) {
+ // Special case: Handle skeleton bone TRS tracks. They use the format `A/B/C/Skeleton3D:bone_name`.
+ // We have a Skeleton3D, check if it has a bone with the same name as this subpath.
+ const String &bone_name = p_node_subpath[0];
+ const int32_t bone_index = skeleton->find_bone(bone_name);
+ if (bone_index != -1) {
+ // A bone was found! But we still need to figure out which glTF node it corresponds to.
+ for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
+ const Ref<GLTFSkeleton> &skeleton_gltf = p_state->skeletons[skeleton_i];
+ if (skeleton == skeleton_gltf->godot_skeleton) {
+ GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone_index];
+ return node_i;
+ }
+ }
+ ERR_FAIL_V_MSG(-1, vformat("glTF: Found a bone %s in a Skeleton3D that wasn't in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting.", bone_name));
+ }
}
- Animation::TrackType track_type = p_animation->track_get_type(p_track_i);
- int32_t key_count = p_animation->track_get_key_count(p_track_i);
- Vector<real_t> times;
- times.resize(key_count);
- String path = p_animation->track_get_path(p_track_i);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i);
+ // General case: Not a skeleton bone, usually this means a normal node, or it could be the Skeleton3D itself.
+ for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
+ if (scene_node_i.value == p_godot_node) {
+ return scene_node_i.key;
+ }
}
- double anim_end = p_animation->get_length();
+ ERR_FAIL_V_MSG(-1, vformat("glTF: A node was animated, but it wasn't found in the GLTFState. Ensure that all nodes referenced by the AnimationPlayer are in the scene you are exporting."));
+}
+
+bool GLTFDocument::_convert_animation_node_track(Ref<GLTFState> p_state, GLTFAnimation::NodeTrack &p_gltf_node_track, const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index, Vector<double> &p_times) {
+ GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(p_godot_animation, p_godot_anim_track_index);
+ const Animation::TrackType track_type = p_godot_animation->track_get_type(p_godot_anim_track_index);
+ const int32_t key_count = p_godot_animation->track_get_key_count(p_godot_anim_track_index);
+ const NodePath node_path = p_godot_animation->track_get_path(p_godot_anim_track_index);
+ const Vector<StringName> subpath = node_path.get_subnames();
+ double anim_end = p_godot_animation->get_length();
if (track_type == Animation::TYPE_SCALE_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.scale_track.times.clear();
- p_track.scale_track.values.clear();
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Vector3 scale;
- Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale);
+ Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
ERR_CONTINUE(err != OK);
- p_track.scale_track.values.push_back(scale);
- p_track.scale_track.times.push_back(time);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
if (last) {
break;
}
@@ -6420,31 +7462,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
- p_track.scale_track.values.resize(key_count);
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.values.resize(key_count);
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Vector3 scale;
- Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
+ Error err = p_godot_animation->scale_track_get_key(p_godot_anim_track_index, key_i, &scale);
ERR_CONTINUE(err != OK);
- p_track.scale_track.values.write[key_i] = scale;
+ p_gltf_node_track.scale_track.values.write[key_i] = scale;
}
}
} else if (track_type == Animation::TYPE_POSITION_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.position_track.times.clear();
- p_track.position_track.values.clear();
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Vector3 scale;
- Error err = p_animation->try_position_track_interpolate(p_track_i, time, &scale);
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &scale);
ERR_CONTINUE(err != OK);
- p_track.position_track.values.push_back(scale);
- p_track.position_track.times.push_back(time);
+ p_gltf_node_track.position_track.values.push_back(scale);
+ p_gltf_node_track.position_track.times.push_back(time);
if (last) {
break;
}
@@ -6455,31 +7497,31 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Vector3 position;
- Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
+ Error err = p_godot_animation->position_track_get_key(p_godot_anim_track_index, key_i, &position);
ERR_CONTINUE(err != OK);
- p_track.position_track.values.write[key_i] = position;
+ p_gltf_node_track.position_track.values.write[key_i] = position;
}
}
} else if (track_type == Animation::TYPE_ROTATION_3D) {
if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.rotation_track.times.clear();
- p_track.rotation_track.values.clear();
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
// CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
const double increment = 1.0 / p_state->get_bake_fps();
double time = 0.0;
bool last = false;
while (true) {
Quaternion rotation;
- Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation);
+ Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.push_back(rotation);
- p_track.rotation_track.times.push_back(time);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
if (last) {
break;
}
@@ -6490,306 +7532,326 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
}
}
} else {
- p_track.rotation_track.times = times;
- p_track.rotation_track.values.resize(key_count);
- p_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Quaternion rotation;
- Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
+ Error err = p_godot_animation->rotation_track_get_key(p_godot_anim_track_index, key_i, &rotation);
ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.write[key_i] = rotation;
- }
- }
- } else if (track_type == Animation::TYPE_VALUE) {
- if (path.contains(":position")) {
- p_track.position_track.interpolation = gltf_interpolation;
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
-
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.position_track.times.clear();
- p_track.position_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Vector3 position;
- Error err = p_animation->try_position_track_interpolate(p_track_i, time, &position);
- ERR_CONTINUE(err != OK);
- p_track.position_track.values.push_back(position);
- p_track.position_track.times.push_back(time);
- if (last) {
- break;
+ p_gltf_node_track.rotation_track.values.write[key_i] = rotation;
+ }
+ }
+ } else if (subpath.size() > 0) {
+ const StringName &node_prop = subpath[0];
+ if (track_type == Animation::TYPE_VALUE) {
+ if (node_prop == "position") {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.position_track.values.push_back(position);
+ p_gltf_node_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.position_track.values.write[key_i] = position;
}
}
- } else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.position_track.values.write[key_i] = position;
- }
- }
- } else if (path.contains(":rotation")) {
- p_track.rotation_track.interpolation = gltf_interpolation;
- p_track.rotation_track.times = times;
- p_track.rotation_track.values.resize(key_count);
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.rotation_track.times.clear();
- p_track.rotation_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Quaternion rotation;
- Error err = p_animation->try_rotation_track_interpolate(p_track_i, time, &rotation);
- ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.push_back(rotation);
- p_track.rotation_track.times.push_back(time);
- if (last) {
- break;
+ } else if (node_prop == "rotation" || node_prop == "rotation_degrees" || node_prop == "quaternion") {
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Quaternion rotation_quaternion;
+ if (node_prop == "quaternion") {
+ rotation_quaternion = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ } else {
+ Vector3 rotation_euler = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ if (node_prop == "rotation_degrees") {
+ rotation_euler *= Math_TAU / 360.0;
+ }
+ rotation_quaternion = Quaternion::from_euler(rotation_euler);
+ }
+ p_gltf_node_track.rotation_track.values.write[key_i] = rotation_quaternion;
}
}
- } else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ } else if (node_prop == "scale") {
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.values.resize(key_count);
+
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale_track = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.scale_track.values.write[key_i] = scale_track;
+ }
}
- }
- } else if (path.contains(":scale")) {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
-
- p_track.scale_track.values.resize(key_count);
- p_track.scale_track.interpolation = gltf_interpolation;
-
- if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- p_track.scale_track.times.clear();
- p_track.scale_track.values.clear();
- // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
- const double increment = 1.0 / p_state->get_bake_fps();
- double time = 0.0;
- bool last = false;
- while (true) {
- Vector3 scale;
- Error err = p_animation->try_scale_track_interpolate(p_track_i, time, &scale);
- ERR_CONTINUE(err != OK);
- p_track.scale_track.values.push_back(scale);
- p_track.scale_track.times.push_back(time);
- if (last) {
- break;
+ } else if (node_prop == "transform") {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.position_track.times = p_times;
+ p_gltf_node_track.position_track.values.resize(key_count);
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.rotation_track.times = p_times;
+ p_gltf_node_track.rotation_track.values.resize(key_count);
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ p_gltf_node_track.scale_track.times = p_times;
+ p_gltf_node_track.scale_track.values.resize(key_count);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_gltf_node_track.position_track.times.clear();
+ p_gltf_node_track.position_track.values.clear();
+ p_gltf_node_track.rotation_track.times.clear();
+ p_gltf_node_track.rotation_track.values.clear();
+ p_gltf_node_track.scale_track.times.clear();
+ p_gltf_node_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / p_state->get_bake_fps();
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Quaternion rotation;
+ Vector3 scale;
+ Error err = p_godot_animation->try_position_track_interpolate(p_godot_anim_track_index, time, &position);
+ ERR_CONTINUE(err != OK);
+ err = p_godot_animation->try_rotation_track_interpolate(p_godot_anim_track_index, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ err = p_godot_animation->try_scale_track_interpolate(p_godot_anim_track_index, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_gltf_node_track.position_track.values.push_back(position);
+ p_gltf_node_track.position_track.times.push_back(time);
+ p_gltf_node_track.rotation_track.values.push_back(rotation);
+ p_gltf_node_track.rotation_track.times.push_back(time);
+ p_gltf_node_track.scale_track.values.push_back(scale);
+ p_gltf_node_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
}
- time += increment;
- if (time >= anim_end) {
- last = true;
- time = anim_end;
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Transform3D transform = p_godot_animation->track_get_key_value(p_godot_anim_track_index, key_i);
+ p_gltf_node_track.position_track.values.write[key_i] = transform.get_origin();
+ p_gltf_node_track.rotation_track.values.write[key_i] = transform.basis.get_rotation_quaternion();
+ p_gltf_node_track.scale_track.values.write[key_i] = transform.basis.get_scale();
}
}
} else {
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.scale_track.values.write[key_i] = scale_track;
- }
- }
- }
- } else if (track_type == Animation::TYPE_BEZIER) {
- const int32_t keys = anim_end * p_state->get_bake_fps();
- if (path.contains(":scale")) {
- if (!p_track.scale_track.times.size()) {
- p_track.scale_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
- }
- p_track.scale_track.times = new_times;
+ // This is a Value track animating a property, but not a TRS property, so it can't be converted into a node track.
+ return false;
+ }
+ } else if (track_type == Animation::TYPE_BEZIER) {
+ const int32_t keys = anim_end * p_state->get_bake_fps();
+ if (node_prop == "scale") {
+ if (p_gltf_node_track.scale_track.times.is_empty()) {
+ p_gltf_node_track.scale_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.scale_track.times = new_times;
- p_track.scale_track.values.resize(keys);
+ p_gltf_node_track.scale_track.values.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
- }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ p_gltf_node_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
+ }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.scale_track.values[key_i];
- if (path.contains(":scale:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":scale:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":scale:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_gltf_node_track.scale_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.scale_track.values.write[key_i] = bezier_track;
}
- p_track.scale_track.values.write[key_i] = bezier_track;
}
- }
- } else if (path.contains(":position")) {
- if (!p_track.position_track.times.size()) {
- p_track.position_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ } else if (node_prop == "position") {
+ if (p_gltf_node_track.position_track.times.is_empty()) {
+ p_gltf_node_track.position_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.position_track.times = new_times;
+
+ p_gltf_node_track.position_track.values.resize(keys);
}
- p_track.position_track.times = new_times;
- p_track.position_track.values.resize(keys);
- }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_gltf_node_track.position_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.position_track.values.write[key_i] = bezier_track;
+ }
+ } else if (node_prop == "quaternion") {
+ if (p_gltf_node_track.rotation_track.times.is_empty()) {
+ p_gltf_node_track.rotation_track.interpolation = gltf_interpolation;
+ Vector<double> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / p_state->get_bake_fps();
+ }
+ p_gltf_node_track.rotation_track.times = new_times;
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.position_track.values[key_i];
- if (path.contains(":position:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":position:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":position:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ p_gltf_node_track.rotation_track.values.resize(keys);
}
- p_track.position_track.values.write[key_i] = bezier_track;
- }
- } else if (path.contains(":rotation")) {
- if (!p_track.rotation_track.times.size()) {
- p_track.rotation_track.interpolation = gltf_interpolation;
- Vector<real_t> new_times;
- new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
- new_times.write[key_i] = key_i / p_state->get_bake_fps();
- }
- p_track.rotation_track.times = new_times;
-
- p_track.rotation_track.values.resize(keys);
- }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Quaternion bezier_track = p_track.rotation_track.values[key_i];
- if (path.contains(":rotation:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
- } else if (path.contains(":rotation:w")) {
- bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / p_state->get_bake_fps());
+ Quaternion bezier_track = p_gltf_node_track.rotation_track.values[key_i];
+ if (subpath.size() == 2) {
+ if (subpath[1] == StringName("x")) {
+ bezier_track.x = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("y")) {
+ bezier_track.y = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("z")) {
+ bezier_track.z = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ } else if (subpath[1] == StringName("w")) {
+ bezier_track.w = p_godot_animation->bezier_track_interpolate(p_godot_anim_track_index, key_i / p_state->get_bake_fps());
+ }
+ }
+ p_gltf_node_track.rotation_track.values.write[key_i] = bezier_track;
}
- p_track.rotation_track.values.write[key_i] = bezier_track;
+ } else {
+ // This is a Bezier track animating a property, but not a TRS property, so it can't be converted into a node track.
+ return false;
}
+ } else {
+ // This property track isn't a Value track or Bezier track, so it can't be converted into a node track.
+ return false;
}
+ } else {
+ // This isn't a TRS track or a property track, so it can't be converted into a node track.
+ return false;
}
- return p_track;
+ // If we reached this point, the track was some kind of TRS track and was successfully converted.
+ // All failure paths should return false before this point to indicate this
+ // isn't a node track so it can be handled by KHR_animation_pointer instead.
+ return true;
}
-void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name) {
+void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name) {
Ref<Animation> animation = p_animation_player->get_animation(p_animation_track_name);
Ref<GLTFAnimation> gltf_animation;
gltf_animation.instantiate();
gltf_animation->set_original_name(p_animation_track_name);
gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name));
- for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) {
- if (!animation->track_is_enabled(track_i)) {
+ HashMap<int, GLTFAnimation::NodeTrack> &node_tracks = gltf_animation->get_node_tracks();
+ for (int32_t track_index = 0; track_index < animation->get_track_count(); track_index++) {
+ if (!animation->track_is_enabled(track_index)) {
continue;
}
- String final_track_path = animation->track_get_path(track_i);
- Node *animation_base_node = p_animation_player->get_parent();
- ERR_CONTINUE_MSG(!animation_base_node, "Cannot get the parent of the animation player.");
- if (String(final_track_path).contains(":position")) {
- const Vector<String> node_suffix = String(final_track_path).split(":position");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a position path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) {
- if (position_scene_node_i.value == node) {
- GLTFNodeIndex node_index = position_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator position_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (position_track_i) {
- track = position_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":rotation_degrees")) {
- const Vector<String> node_suffix = String(final_track_path).split(":rotation_degrees");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a rotation degrees path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) {
- if (rotation_degree_scene_node_i.value == node) {
- GLTFNodeIndex node_index = rotation_degree_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator rotation_degree_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (rotation_degree_track_i) {
- track = rotation_degree_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":scale")) {
- const Vector<String> node_suffix = String(final_track_path).split(":scale");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a scale path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) {
- if (scale_scene_node_i.value == node) {
- GLTFNodeIndex node_index = scale_scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator scale_track_i = gltf_animation->get_tracks().find(node_index);
- GLTFAnimation::Track track;
- if (scale_track_i) {
- track = scale_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_index);
- gltf_animation->get_tracks().insert(node_index, track);
- }
- }
- } else if (String(final_track_path).contains(":transform")) {
- const Vector<String> node_suffix = String(final_track_path).split(":transform");
- const NodePath path = node_suffix[0];
- const Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a transform path.");
- for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) {
- if (transform_track_i.value == node) {
- GLTFAnimation::Track track;
- track = _convert_animation_track(p_state, track, animation, track_i, transform_track_i.key);
- gltf_animation->get_tracks().insert(transform_track_i.key, track);
- }
- }
- } else if (String(final_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
- const Vector<String> node_suffix = String(final_track_path).split(":");
- const NodePath path = node_suffix[0];
- const String suffix = node_suffix[1];
- Node *node = animation_base_node->get_node_or_null(path);
- ERR_CONTINUE_MSG(!node, "Cannot get the node from a blend shape path.");
- MeshInstance3D *mi = cast_to<MeshInstance3D>(node);
- if (!mi) {
- continue;
- }
- Ref<Mesh> mesh = mi->get_mesh();
+ // Get the Godot node and the glTF node index for the animation track.
+ const NodePath track_path = animation->track_get_path(track_index);
+ const Node *anim_player_parent = p_animation_player->get_parent();
+ const Node *animated_node = anim_player_parent->get_node_or_null(track_path);
+ ERR_CONTINUE_MSG(!animated_node, "glTF: Cannot get node for animated track using path: " + String(track_path));
+ const GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::godot_to_gltf_interpolation(animation, track_index);
+ // First, check if it's a Blend Shape track.
+ if (animation->track_get_type(track_index) == Animation::TYPE_BLEND_SHAPE) {
+ const MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(animated_node);
+ ERR_CONTINUE_MSG(!mesh_instance, "glTF: Animation had a Blend Shape track, but the node wasn't a MeshInstance3D. Ignoring this track.");
+ Ref<Mesh> mesh = mesh_instance->get_mesh();
ERR_CONTINUE(mesh.is_null());
int32_t mesh_index = -1;
for (const KeyValue<GLTFNodeIndex, Node *> &mesh_track_i : p_state->scene_nodes) {
- if (mesh_track_i.value == node) {
+ if (mesh_track_i.value == animated_node) {
mesh_index = mesh_track_i.key;
}
}
ERR_CONTINUE(mesh_index == -1);
- HashMap<int, GLTFAnimation::Track> &tracks = gltf_animation->get_tracks();
- GLTFAnimation::Track track = gltf_animation->get_tracks().has(mesh_index) ? gltf_animation->get_tracks()[mesh_index] : GLTFAnimation::Track();
- if (!tracks.has(mesh_index)) {
+ GLTFAnimation::NodeTrack track = node_tracks.has(mesh_index) ? node_tracks[mesh_index] : GLTFAnimation::NodeTrack();
+ if (!node_tracks.has(mesh_index)) {
for (int32_t shape_i = 0; shape_i < mesh->get_blend_shape_count(); shape_i++) {
String shape_name = mesh->get_blend_shape_name(shape_i);
- NodePath shape_path = String(path) + ":" + shape_name;
+ NodePath shape_path = NodePath(track_path.get_names(), { shape_name }, false);
int32_t shape_track_i = animation->find_track(shape_path, Animation::TYPE_BLEND_SHAPE);
if (shape_track_i == -1) {
GLTFAnimation::Channel<real_t> weight;
@@ -6801,15 +7863,6 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
track.weight_tracks.push_back(weight);
continue;
}
- Animation::InterpolationType interpolation = animation->track_get_interpolation_type(track_i);
- GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- if (interpolation == Animation::InterpolationType::INTERPOLATION_LINEAR) {
- gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_NEAREST) {
- gltf_interpolation = GLTFAnimation::INTERP_STEP;
- } else if (interpolation == Animation::InterpolationType::INTERPOLATION_CUBIC) {
- gltf_interpolation = GLTFAnimation::INTERP_CUBIC_SPLINE;
- }
int32_t key_count = animation->track_get_key_count(shape_track_i);
GLTFAnimation::Channel<real_t> weight;
weight.interpolation = gltf_interpolation;
@@ -6823,64 +7876,74 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
}
track.weight_tracks.push_back(weight);
}
- tracks[mesh_index] = track;
- }
- } else if (String(final_track_path).contains(":")) {
- //Process skeleton
- const Vector<String> node_suffix = String(final_track_path).split(":");
- const String &node = node_suffix[0];
- const NodePath node_path = node;
- const String &suffix = node_suffix[1];
- Node *godot_node = animation_base_node->get_node_or_null(node_path);
- if (!godot_node) {
- continue;
- }
- Skeleton3D *skeleton = cast_to<Skeleton3D>(animation_base_node->get_node_or_null(node));
- if (!skeleton) {
- continue;
- }
- GLTFSkeletonIndex skeleton_gltf_i = -1;
- for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
- if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) {
- skeleton = p_state->skeletons[skeleton_i]->godot_skeleton;
- skeleton_gltf_i = skeleton_i;
- ERR_CONTINUE(!skeleton);
- Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i];
- int32_t bone = skeleton->find_bone(suffix);
- ERR_CONTINUE_MSG(bone == -1, vformat("Cannot find the bone %s.", suffix));
- if (!skeleton_gltf->godot_bone_node.has(bone)) {
- continue;
- }
- GLTFNodeIndex node_i = skeleton_gltf->godot_bone_node[bone];
- HashMap<int, GLTFAnimation::Track>::Iterator property_track_i = gltf_animation->get_tracks().find(node_i);
- GLTFAnimation::Track track;
- if (property_track_i) {
- track = property_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_i);
- gltf_animation->get_tracks()[node_i] = track;
- }
- }
- } else if (!String(final_track_path).contains(":")) {
- ERR_CONTINUE(!animation_base_node);
- Node *godot_node = animation_base_node->get_node_or_null(final_track_path);
- ERR_CONTINUE_MSG(!godot_node, vformat("Cannot get the node from a skeleton path %s.", final_track_path));
- for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
- if (scene_node_i.value == godot_node) {
- GLTFNodeIndex node_i = scene_node_i.key;
- HashMap<int, GLTFAnimation::Track>::Iterator node_track_i = gltf_animation->get_tracks().find(node_i);
- GLTFAnimation::Track track;
- if (node_track_i) {
- track = node_track_i->value;
- }
- track = _convert_animation_track(p_state, track, animation, track_i, node_i);
- gltf_animation->get_tracks()[node_i] = track;
- break;
- }
+ node_tracks[mesh_index] = track;
}
+ continue;
}
- }
- if (gltf_animation->get_tracks().size()) {
+ // If it's not a Blend Shape track, it must either be a TRS track, a property Value track, or something we can't handle.
+ // For the cases we can handle, we will need to know the glTF node index, glTF interpolation, and the times of the track.
+ const Vector<StringName> subnames = track_path.get_subnames();
+ const GLTFNodeIndex node_i = _node_and_or_bone_to_gltf_node_index(p_state, subnames, animated_node);
+ ERR_CONTINUE_MSG(node_i == -1, "glTF: Cannot get glTF node index for animated track using path: " + String(track_path));
+ const int anim_key_count = animation->track_get_key_count(track_index);
+ Vector<double> times;
+ times.resize(anim_key_count);
+ for (int32_t key_i = 0; key_i < anim_key_count; key_i++) {
+ times.write[key_i] = animation->track_get_key_time(track_index, key_i);
+ }
+ // Try converting the track to a TRS glTF node track. This will only succeed if the Godot animation is a TRS track.
+ const HashMap<int, GLTFAnimation::NodeTrack>::Iterator node_track_iter = node_tracks.find(node_i);
+ GLTFAnimation::NodeTrack track;
+ if (node_track_iter) {
+ track = node_track_iter->value;
+ }
+ if (_convert_animation_node_track(p_state, track, animation, track_index, times)) {
+ // If the track was successfully converted, save it and continue to the next track.
+ node_tracks[node_i] = track;
+ continue;
+ }
+ // If the track wasn't a TRS track or Blend Shape track, it might be a Value track animating a property.
+ // Then this is something that we need to handle with KHR_animation_pointer.
+ Ref<GLTFObjectModelProperty> obj_model_prop = export_object_model_property(p_state, track_path, animated_node, node_i);
+ if (obj_model_prop.is_valid() && obj_model_prop->has_json_pointers()) {
+ // Insert the property track into the KHR_animation_pointer pointer tracks.
+ GLTFAnimation::Channel<Variant> channel;
+ channel.interpolation = gltf_interpolation;
+ channel.times = times;
+ channel.values.resize(anim_key_count);
+ // If using an expression, determine the base instance to pass to the expression.
+ const Ref<Expression> godot_to_gltf_expr = obj_model_prop->get_godot_to_gltf_expression();
+ const bool is_godot_to_gltf_expr_valid = godot_to_gltf_expr.is_valid();
+ Object *base_instance = nullptr;
+ if (is_godot_to_gltf_expr_valid) {
+ Ref<Resource> resource;
+ Vector<StringName> leftover_subpath;
+ base_instance = anim_player_parent->get_node_and_resource(track_path, resource, leftover_subpath);
+ if (resource.is_valid()) {
+ base_instance = resource.ptr();
+ }
+ }
+ // Convert the Godot animation values into glTF animation values (still Variant).
+ for (int32_t key_i = 0; key_i < anim_key_count; key_i++) {
+ Variant value = animation->track_get_key_value(track_index, key_i);
+ if (is_godot_to_gltf_expr_valid) {
+ Array inputs;
+ inputs.append(value);
+ value = godot_to_gltf_expr->execute(inputs, base_instance);
+ }
+ channel.values.write[key_i] = value;
+ }
+ // Use the JSON pointer to insert the property track into the pointer tracks. There will usually be just one JSON pointer.
+ HashMap<String, GLTFAnimation::Channel<Variant>> &pointer_tracks = gltf_animation->get_pointer_tracks();
+ Vector<PackedStringArray> split_json_pointers = obj_model_prop->get_json_pointers();
+ for (const PackedStringArray &split_json_pointer : split_json_pointers) {
+ String json_pointer_str = "/" + String("/").join(split_json_pointer);
+ p_state->object_model_properties[json_pointer_str] = obj_model_prop;
+ pointer_tracks[json_pointer_str] = channel;
+ }
+ }
+ }
+ if (!gltf_animation->is_empty_of_tracks()) {
p_state->animations.push_back(gltf_animation);
}
}
@@ -7079,6 +8142,9 @@ void GLTFDocument::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality");
ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode");
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("import_object_model_property", "state", "json_pointer"), &GLTFDocument::import_object_model_property);
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("export_object_model_property", "state", "node_path", "godot_node", "gltf_node_index"), &GLTFDocument::export_object_model_property);
+
ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
@@ -7140,6 +8206,7 @@ HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
// If the extension is supported directly in GLTFDocument, list it here.
// Other built-in extensions are supported by GLTFDocumentExtension classes.
supported_extensions.insert("GODOT_single_root");
+ supported_extensions.insert("KHR_animation_pointer");
supported_extensions.insert("KHR_lights_punctual");
supported_extensions.insert("KHR_materials_emissive_strength");
supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
@@ -7317,6 +8384,10 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector<GLTFNodeIndex>());
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
+ /* ASSIGN SCENE NODE NAMES */
+ // This must be run AFTER determining skeletons, and BEFORE parsing animations.
+ _assign_node_names(p_state);
+
/* PARSE MESHES (we have enough info now) */
err = _parse_meshes(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
@@ -7333,9 +8404,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
err = _parse_animations(p_state);
ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
- /* ASSIGN SCENE NAMES */
- _assign_node_names(p_state);
-
return OK;
}
@@ -7354,7 +8422,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
Ref<GLTFState> state = p_state;
ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER);
- state->base_path = p_path.get_base_dir();
+ state->set_base_path(p_path.get_base_dir());
state->filename = p_path.get_file();
Error err = _serialize(state);
if (err != OK) {
@@ -7467,7 +8535,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
Ref<FileAccessMemory> file_access;
file_access.instantiate();
file_access->open_custom(p_bytes.ptr(), p_bytes.size());
- state->base_path = p_base_path.get_base_dir();
+ state->set_base_path(p_base_path.get_base_dir());
err = _parse(p_state, state->base_path, file_access);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
@@ -7484,7 +8552,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
if (state == Ref<GLTFState>()) {
state.instantiate();
}
- state->filename = p_path.get_file().get_basename();
+ state->set_filename(p_path.get_file().get_basename());
state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS;
@@ -7492,13 +8560,13 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat(R"(Can't open file at path "%s")", p_path));
ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
String base_path = p_base_path;
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
}
- state->base_path = base_path;
+ state->set_base_path(base_path);
err = _parse(p_state, base_path, file);
ERR_FAIL_COND_V(err != OK, err);
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index d347d49102..a6d6caa3f0 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -56,13 +56,6 @@ public:
enum {
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963,
-
- COMPONENT_TYPE_BYTE = 5120,
- COMPONENT_TYPE_UNSIGNED_BYTE = 5121,
- COMPONENT_TYPE_SHORT = 5122,
- COMPONENT_TYPE_UNSIGNED_SHORT = 5123,
- COMPONENT_TYPE_INT = 5125,
- COMPONENT_TYPE_FLOAT = 5126,
};
enum {
TEXTURE_TYPE_GENERIC = 0,
@@ -95,6 +88,10 @@ public:
static Vector<String> get_supported_gltf_extensions();
static HashSet<String> get_supported_gltf_extensions_hashset();
+ static NodePath _find_material_node_path(Ref<GLTFState> p_state, Ref<Material> p_material);
+ static Ref<GLTFObjectModelProperty> import_object_model_property(Ref<GLTFState> p_state, const String &p_json_pointer);
+ static Ref<GLTFObjectModelProperty> export_object_model_property(Ref<GLTFState> p_state, const NodePath &p_node_path, const Node *p_godot_node, GLTFNodeIndex p_gltf_node_index);
+
void set_naming_version(int p_version);
int get_naming_version() const;
void set_image_format(const String &p_image_format);
@@ -109,8 +106,8 @@ private:
void _build_parent_hierachy(Ref<GLTFState> p_state);
double _filter_number(double p_float);
void _round_min_max_components(Vector<double> &r_type_min, Vector<double> &r_type_max);
- String _get_component_type_name(const uint32_t p_component);
- int _get_component_type_size(const int p_component_type);
+ String _get_component_type_name(const GLTFAccessor::GLTFComponentType p_component_type);
+ int _get_component_type_size(const GLTFAccessor::GLTFComponentType p_component_type);
Error _parse_scenes(Ref<GLTFState> p_state);
Error _parse_nodes(Ref<GLTFState> p_state);
String _get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type);
@@ -140,7 +137,7 @@ private:
const int p_skip_every, const int p_skip_bytes,
const int p_element_size, const int p_count,
const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count,
- const int p_component_type, const int p_component_size,
+ const GLTFAccessor::GLTFComponentType p_component_type, const int p_component_size,
const bool p_normalized, const int p_byte_offset,
const bool p_for_vertex);
Vector<double> _decode_accessor(Ref<GLTFState> p_state,
@@ -178,6 +175,15 @@ private:
Vector<Transform3D> _decode_accessor_as_xform(Ref<GLTFState> p_state,
const GLTFAccessorIndex p_accessor,
const bool p_for_vertex);
+ Vector<Variant> _decode_accessor_as_variant(Ref<GLTFState> p_state,
+ const GLTFAccessorIndex p_accessor,
+ Variant::Type p_variant_type,
+ GLTFAccessor::GLTFAccessorType p_accessor_type);
+ GLTFAccessorIndex _encode_accessor_as_variant(Ref<GLTFState> p_state,
+ Vector<Variant> p_attribs,
+ Variant::Type p_variant_type,
+ GLTFAccessor::GLTFAccessorType p_accessor_type,
+ GLTFAccessor::GLTFComponentType p_component_type = GLTFAccessor::COMPONENT_TYPE_SINGLE_FLOAT);
Error _parse_meshes(Ref<GLTFState> p_state);
Error _serialize_textures(Ref<GLTFState> p_state);
Error _serialize_texture_samplers(Ref<GLTFState> p_state);
@@ -205,6 +211,7 @@ private:
Error _parse_cameras(Ref<GLTFState> p_state);
Error _parse_lights(Ref<GLTFState> p_state);
Error _parse_animations(Ref<GLTFState> p_state);
+ void _parse_animation_pointer(Ref<GLTFState> p_state, const String &p_animation_json_pointer, const Ref<GLTFAnimation> p_gltf_animation, const GLTFAnimation::Interpolation p_interp, const Vector<double> &p_times, const int p_output_value_accessor_index);
Error _serialize_animations(Ref<GLTFState> p_state);
BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> p_state,
Skeleton3D *p_skeleton,
@@ -216,7 +223,7 @@ private:
Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index);
void _assign_node_names(Ref<GLTFState> p_state);
template <typename T>
- T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values,
+ T _interpolate_track(const Vector<double> &p_times, const Vector<T> &p_values,
const float p_time,
const GLTFAnimation::Interpolation p_interp);
GLTFAccessorIndex _encode_accessor_as_quaternions(Ref<GLTFState> p_state,
@@ -229,7 +236,7 @@ private:
const Vector<Color> p_attribs,
const bool p_for_vertex);
GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> p_state,
- const Vector<real_t> p_attribs,
+ const Vector<double> p_attribs,
const bool p_for_vertex);
GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> p_state,
const Vector<Vector2> p_attribs,
@@ -269,7 +276,7 @@ private:
const bool p_for_vertex);
Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src,
const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type,
- const int p_component_type, const bool p_normalized,
+ const GLTFAccessor::GLTFComponentType p_component_type, const bool p_normalized,
const int p_byte_offset, const bool p_for_vertex,
GLTFBufferViewIndex &r_accessor, const bool p_for_indices = false);
@@ -280,11 +287,6 @@ private:
Error _serialize_nodes(Ref<GLTFState> p_state);
Error _serialize_scenes(Ref<GLTFState> p_state);
String interpolation_to_string(const GLTFAnimation::Interpolation p_interp);
- GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> p_state,
- GLTFAnimation::Track p_track,
- Ref<Animation> p_animation,
- int32_t p_track_i,
- GLTFNodeIndex p_node_i);
Error _encode_buffer_bins(Ref<GLTFState> p_state, const String &p_path);
Error _encode_buffer_glb(Ref<GLTFState> p_state, const String &p_path);
PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err);
@@ -342,11 +344,6 @@ public:
void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state);
#endif // MODULE_CSG_ENABLED
- void _convert_animation_player_to_gltf(
- AnimationPlayer *p_animation_player, Ref<GLTFState> p_state,
- GLTFNodeIndex p_gltf_current,
- GLTFNodeIndex p_gltf_root_index,
- Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
void _check_visibility(Node *p_node, bool &r_retflag);
void _convert_camera_to_gltf(Camera3D *p_camera, Ref<GLTFState> p_state,
Ref<GLTFNode> p_gltf_node);
@@ -377,7 +374,15 @@ public:
Ref<GLTFNode> p_gltf_node);
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
- void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
+
+ GLTFNodeIndex _node_and_or_bone_to_gltf_node_index(Ref<GLTFState> p_state, const Vector<StringName> &p_node_subpath, const Node *p_godot_node);
+ bool _convert_animation_node_track(Ref<GLTFState> p_state,
+ GLTFAnimation::NodeTrack &p_gltf_node_track,
+ const Ref<Animation> &p_godot_animation,
+ int32_t p_godot_anim_track_index,
+ Vector<double> &p_times);
+ void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const String &p_animation_track_name);
+
Error _serialize(Ref<GLTFState> p_state);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 7763874d02..2488e73d08 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -397,8 +397,27 @@ String GLTFState::get_base_path() {
return base_path;
}
-void GLTFState::set_base_path(String p_base_path) {
+void GLTFState::set_base_path(const String &p_base_path) {
base_path = p_base_path;
+ if (extract_path.is_empty()) {
+ extract_path = p_base_path;
+ }
+}
+
+String GLTFState::get_extract_path() {
+ return extract_path;
+}
+
+void GLTFState::set_extract_path(const String &p_extract_path) {
+ extract_path = p_extract_path;
+}
+
+String GLTFState::get_extract_prefix() {
+ return extract_prefix;
+}
+
+void GLTFState::set_extract_prefix(const String &p_extract_prefix) {
+ extract_prefix = p_extract_prefix;
}
String GLTFState::get_filename() const {
@@ -407,6 +426,9 @@ String GLTFState::get_filename() const {
void GLTFState::set_filename(const String &p_filename) {
filename = p_filename;
+ if (extract_prefix.is_empty()) {
+ extract_prefix = p_filename.get_basename();
+ }
}
Variant GLTFState::get_additional_data(const StringName &p_extension_name) {
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 7954049192..d667cf8858 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -38,6 +38,7 @@
#include "structures/gltf_camera.h"
#include "structures/gltf_mesh.h"
#include "structures/gltf_node.h"
+#include "structures/gltf_object_model_property.h"
#include "structures/gltf_skeleton.h"
#include "structures/gltf_skin.h"
#include "structures/gltf_texture.h"
@@ -48,9 +49,12 @@
class GLTFState : public Resource {
GDCLASS(GLTFState, Resource);
friend class GLTFDocument;
+ friend class GLTFNode;
protected:
String base_path;
+ String extract_path;
+ String extract_prefix;
String filename;
Dictionary json;
int major_version = 0;
@@ -100,6 +104,7 @@ protected:
Vector<Ref<GLTFAnimation>> animations;
HashMap<GLTFNodeIndex, Node *> scene_nodes;
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *> scene_mesh_instances;
+ HashMap<String, Ref<GLTFObjectModelProperty>> object_model_properties;
HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_gltf_skeleton;
HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_gltf_skin;
@@ -186,7 +191,13 @@ public:
void set_scene_name(String p_scene_name);
String get_base_path();
- void set_base_path(String p_base_path);
+ void set_base_path(const String &p_base_path);
+
+ String get_extract_path();
+ void set_extract_path(const String &p_extract_path);
+
+ String get_extract_prefix();
+ void set_extract_prefix(const String &p_extract_prefix);
String get_filename() const;
void set_filename(const String &p_filename);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 53e9f2b84c..fbc3ae611c 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -37,6 +37,7 @@
#include "extensions/physics/gltf_document_extension_physics.h"
#include "gltf_document.h"
#include "gltf_state.h"
+#include "structures/gltf_object_model_property.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_import_blend_runner.h"
@@ -112,6 +113,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFLight);
GDREGISTER_CLASS(GLTFMesh);
GDREGISTER_CLASS(GLTFNode);
+ GDREGISTER_CLASS(GLTFObjectModelProperty);
GDREGISTER_CLASS(GLTFPhysicsBody);
GDREGISTER_CLASS(GLTFPhysicsShape);
GDREGISTER_CLASS(GLTFSkeleton);
diff --git a/modules/gltf/structures/gltf_accessor.cpp b/modules/gltf/structures/gltf_accessor.cpp
index 1ebc00a514..300fce09ff 100644
--- a/modules/gltf/structures/gltf_accessor.cpp
+++ b/modules/gltf/structures/gltf_accessor.cpp
@@ -39,6 +39,19 @@ void GLTFAccessor::_bind_methods() {
BIND_ENUM_CONSTANT(TYPE_MAT3);
BIND_ENUM_CONSTANT(TYPE_MAT4);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_NONE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_BYTE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_BYTE);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_SHORT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_SHORT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_INT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_INT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SINGLE_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_DOUBLE_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_HALF_FLOAT);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_SIGNED_LONG);
+ BIND_ENUM_CONSTANT(COMPONENT_TYPE_UNSIGNED_LONG);
+
ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view);
ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view);
ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset);
@@ -108,7 +121,7 @@ int GLTFAccessor::get_component_type() {
}
void GLTFAccessor::set_component_type(int p_component_type) {
- component_type = p_component_type;
+ component_type = (GLTFComponentType)p_component_type;
}
bool GLTFAccessor::get_normalized() {
@@ -188,7 +201,7 @@ int GLTFAccessor::get_sparse_indices_component_type() {
}
void GLTFAccessor::set_sparse_indices_component_type(int p_sparse_indices_component_type) {
- sparse_indices_component_type = p_sparse_indices_component_type;
+ sparse_indices_component_type = (GLTFComponentType)p_sparse_indices_component_type;
}
int GLTFAccessor::get_sparse_values_buffer_view() {
diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h
index 1a3a2cb494..b00e6a0f92 100644
--- a/modules/gltf/structures/gltf_accessor.h
+++ b/modules/gltf/structures/gltf_accessor.h
@@ -50,10 +50,25 @@ public:
TYPE_MAT4,
};
+ enum GLTFComponentType {
+ COMPONENT_TYPE_NONE = 0,
+ COMPONENT_TYPE_SIGNED_BYTE = 5120,
+ COMPONENT_TYPE_UNSIGNED_BYTE = 5121,
+ COMPONENT_TYPE_SIGNED_SHORT = 5122,
+ COMPONENT_TYPE_UNSIGNED_SHORT = 5123,
+ COMPONENT_TYPE_SIGNED_INT = 5124,
+ COMPONENT_TYPE_UNSIGNED_INT = 5125,
+ COMPONENT_TYPE_SINGLE_FLOAT = 5126,
+ COMPONENT_TYPE_DOUBLE_FLOAT = 5130,
+ COMPONENT_TYPE_HALF_FLOAT = 5131,
+ COMPONENT_TYPE_SIGNED_LONG = 5134,
+ COMPONENT_TYPE_UNSIGNED_LONG = 5135,
+ };
+
private:
GLTFBufferViewIndex buffer_view = -1;
int byte_offset = 0;
- int component_type = 0;
+ GLTFComponentType component_type = COMPONENT_TYPE_NONE;
bool normalized = false;
int count = 0;
GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR;
@@ -62,7 +77,7 @@ private:
int sparse_count = 0;
int sparse_indices_buffer_view = 0;
int sparse_indices_byte_offset = 0;
- int sparse_indices_component_type = 0;
+ GLTFComponentType sparse_indices_component_type = COMPONENT_TYPE_NONE;
int sparse_values_buffer_view = 0;
int sparse_values_byte_offset = 0;
@@ -117,5 +132,6 @@ public:
};
VARIANT_ENUM_CAST(GLTFAccessor::GLTFAccessorType);
+VARIANT_ENUM_CAST(GLTFAccessor::GLTFComponentType);
#endif // GLTF_ACCESSOR_H
diff --git a/modules/gltf/structures/gltf_animation.cpp b/modules/gltf/structures/gltf_animation.cpp
index 94fda8e2f5..adc0354c4b 100644
--- a/modules/gltf/structures/gltf_animation.cpp
+++ b/modules/gltf/structures/gltf_animation.cpp
@@ -42,6 +42,34 @@ void GLTFAnimation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop"); // bool
}
+GLTFAnimation::Interpolation GLTFAnimation::godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index) {
+ Animation::InterpolationType interpolation = p_godot_animation->track_get_interpolation_type(p_godot_anim_track_index);
+ switch (interpolation) {
+ case Animation::INTERPOLATION_LINEAR:
+ case Animation::INTERPOLATION_LINEAR_ANGLE:
+ return INTERP_LINEAR;
+ case Animation::INTERPOLATION_NEAREST:
+ return INTERP_STEP;
+ case Animation::INTERPOLATION_CUBIC:
+ case Animation::INTERPOLATION_CUBIC_ANGLE:
+ return INTERP_CUBIC_SPLINE;
+ }
+ return INTERP_LINEAR;
+}
+
+Animation::InterpolationType GLTFAnimation::gltf_to_godot_interpolation(Interpolation p_gltf_interpolation) {
+ switch (p_gltf_interpolation) {
+ case INTERP_LINEAR:
+ return Animation::INTERPOLATION_LINEAR;
+ case INTERP_STEP:
+ return Animation::INTERPOLATION_NEAREST;
+ case INTERP_CATMULLROMSPLINE:
+ case INTERP_CUBIC_SPLINE:
+ return Animation::INTERPOLATION_CUBIC;
+ }
+ return Animation::INTERPOLATION_LINEAR;
+}
+
String GLTFAnimation::get_original_name() {
return original_name;
}
@@ -58,8 +86,16 @@ void GLTFAnimation::set_loop(bool p_val) {
loop = p_val;
}
-HashMap<int, GLTFAnimation::Track> &GLTFAnimation::get_tracks() {
- return tracks;
+HashMap<int, GLTFAnimation::NodeTrack> &GLTFAnimation::get_node_tracks() {
+ return node_tracks;
+}
+
+HashMap<String, GLTFAnimation::Channel<Variant>> &GLTFAnimation::get_pointer_tracks() {
+ return pointer_tracks;
+}
+
+bool GLTFAnimation::is_empty_of_tracks() const {
+ return node_tracks.is_empty() && pointer_tracks.is_empty();
}
GLTFAnimation::GLTFAnimation() {
diff --git a/modules/gltf/structures/gltf_animation.h b/modules/gltf/structures/gltf_animation.h
index afc9784895..6b692d06e6 100644
--- a/modules/gltf/structures/gltf_animation.h
+++ b/modules/gltf/structures/gltf_animation.h
@@ -50,33 +50,41 @@ public:
template <typename T>
struct Channel {
Interpolation interpolation = INTERP_LINEAR;
- Vector<real_t> times;
+ Vector<double> times;
Vector<T> values;
};
- struct Track {
+ struct NodeTrack {
Channel<Vector3> position_track;
Channel<Quaternion> rotation_track;
Channel<Vector3> scale_track;
Vector<Channel<real_t>> weight_tracks;
};
+ String original_name;
+ bool loop = false;
+ HashMap<int, NodeTrack> node_tracks;
+ HashMap<String, Channel<Variant>> pointer_tracks;
+ Dictionary additional_data;
+
public:
+ static Interpolation godot_to_gltf_interpolation(const Ref<Animation> &p_godot_animation, int32_t p_godot_anim_track_index);
+ static Animation::InterpolationType gltf_to_godot_interpolation(Interpolation p_gltf_interpolation);
+
String get_original_name();
void set_original_name(String p_name);
bool get_loop() const;
void set_loop(bool p_val);
- HashMap<int, GLTFAnimation::Track> &get_tracks();
+
+ HashMap<int, GLTFAnimation::NodeTrack> &get_node_tracks();
+ HashMap<String, GLTFAnimation::Channel<Variant>> &get_pointer_tracks();
+ bool is_empty_of_tracks() const;
+
Variant get_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
- GLTFAnimation();
-private:
- String original_name;
- bool loop = false;
- HashMap<int, Track> tracks;
- Dictionary additional_data;
+ GLTFAnimation();
};
#endif // GLTF_ANIMATION_H
diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp
index 863e1df967..2960ec351d 100644
--- a/modules/gltf/structures/gltf_camera.cpp
+++ b/modules/gltf/structures/gltf_camera.cpp
@@ -30,6 +30,7 @@
#include "gltf_camera.h"
+#include "gltf_object_model_property.h"
#include "scene/3d/camera_3d.h"
void GLTFCamera::_bind_methods() {
@@ -57,6 +58,21 @@ void GLTFCamera::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near");
}
+void GLTFCamera::set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop) {
+ // Expression to convert glTF yfov in radians to Godot fov in degrees.
+ Ref<Expression> gltf_to_godot_expr;
+ gltf_to_godot_expr.instantiate();
+ PackedStringArray gltf_to_godot_args = { "yfov_rad" };
+ gltf_to_godot_expr->parse("rad_to_deg(yfov_rad)", gltf_to_godot_args);
+ r_obj_model_prop->set_gltf_to_godot_expression(gltf_to_godot_expr);
+ // Expression to convert Godot fov in degrees to glTF yfov in radians.
+ Ref<Expression> godot_to_gltf_expr;
+ godot_to_gltf_expr.instantiate();
+ PackedStringArray godot_to_gltf_args = { "fov_deg" };
+ godot_to_gltf_expr->parse("deg_to_rad(fov_deg)", godot_to_gltf_args);
+ r_obj_model_prop->set_godot_to_gltf_expression(godot_to_gltf_expr);
+}
+
Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) {
Ref<GLTFCamera> c;
c.instantiate();
diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h
index 1a583c82cc..497b6cd4f1 100644
--- a/modules/gltf/structures/gltf_camera.h
+++ b/modules/gltf/structures/gltf_camera.h
@@ -34,6 +34,7 @@
#include "core/io/resource.h"
class Camera3D;
+class GLTFObjectModelProperty;
// Reference and test file:
// https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md
@@ -54,6 +55,8 @@ protected:
static void _bind_methods();
public:
+ static void set_fov_conversion_expressions(Ref<GLTFObjectModelProperty> &r_obj_model_prop);
+
bool get_perspective() const { return perspective; }
void set_perspective(bool p_val) { perspective = p_val; }
real_t get_fov() const { return fov; }
diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp
index ccee5e8ca4..1626313551 100644
--- a/modules/gltf/structures/gltf_node.cpp
+++ b/modules/gltf/structures/gltf_node.cpp
@@ -30,6 +30,8 @@
#include "gltf_node.h"
+#include "../gltf_state.h"
+
void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFNode::get_original_name);
ClassDB::bind_method(D_METHOD("set_original_name", "original_name"), &GLTFNode::set_original_name);
@@ -60,6 +62,7 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
ClassDB::bind_method(D_METHOD("set_additional_data", "extension_name", "additional_data"), &GLTFNode::set_additional_data);
+ ClassDB::bind_method(D_METHOD("get_scene_node_path", "gltf_state", "handle_skeletons"), &GLTFNode::get_scene_node_path, DEFVAL(true));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_name"), "set_original_name", "get_original_name"); // String
ADD_PROPERTY(PropertyInfo(Variant::INT, "parent"), "set_parent", "get_parent"); // GLTFNodeIndex
@@ -187,6 +190,48 @@ Variant GLTFNode::get_additional_data(const StringName &p_extension_name) {
return additional_data[p_extension_name];
}
+bool GLTFNode::has_additional_data(const StringName &p_extension_name) {
+ return additional_data.has(p_extension_name);
+}
+
void GLTFNode::set_additional_data(const StringName &p_extension_name, Variant p_additional_data) {
additional_data[p_extension_name] = p_additional_data;
}
+
+NodePath GLTFNode::get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons) {
+ Vector<StringName> path;
+ Vector<StringName> subpath;
+ Ref<GLTFNode> current_gltf_node = this;
+ const int gltf_node_count = p_state->nodes.size();
+ if (p_handle_skeletons && skeleton != -1) {
+ // Special case for skeleton nodes, skip all bones so that the path is to the Skeleton3D node.
+ // A path that would otherwise be `A/B/C/Bone1/Bone2/Bone3` becomes `A/B/C/Skeleton3D:Bone3`.
+ subpath.append(get_name());
+ // The generated Skeleton3D node will be named Skeleton3D, so add it to the path.
+ path.append("Skeleton3D");
+ do {
+ const int parent_index = current_gltf_node->get_parent();
+ ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+ current_gltf_node = p_state->nodes[parent_index];
+ } while (current_gltf_node->skeleton != -1);
+ }
+ const bool is_godot_single_root = p_state->extensions_used.has("GODOT_single_root");
+ while (true) {
+ const int parent_index = current_gltf_node->get_parent();
+ if (is_godot_single_root && parent_index == -1) {
+ // For GODOT_single_root scenes, the root glTF node becomes the Godot scene root, so it
+ // should not be included in the path. Ex: A/B/C, A is single root, we want B/C only.
+ break;
+ }
+ path.insert(0, current_gltf_node->get_name());
+ if (!is_godot_single_root && parent_index == -1) {
+ break;
+ }
+ ERR_FAIL_INDEX_V(parent_index, gltf_node_count, NodePath());
+ current_gltf_node = p_state->nodes[parent_index];
+ }
+ if (unlikely(path.is_empty())) {
+ path.append(".");
+ }
+ return NodePath(path, subpath, false);
+}
diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h
index f3f6bfa2f1..f72b65a003 100644
--- a/modules/gltf/structures/gltf_node.h
+++ b/modules/gltf/structures/gltf_node.h
@@ -103,7 +103,10 @@ public:
void set_light(GLTFLightIndex p_light);
Variant get_additional_data(const StringName &p_extension_name);
+ bool has_additional_data(const StringName &p_extension_name);
void set_additional_data(const StringName &p_extension_name, Variant p_additional_data);
+
+ NodePath get_scene_node_path(Ref<GLTFState> p_state, bool p_handle_skeletons = true);
};
#endif // GLTF_NODE_H
diff --git a/modules/gltf/structures/gltf_object_model_property.cpp b/modules/gltf/structures/gltf_object_model_property.cpp
new file mode 100644
index 0000000000..d405c362db
--- /dev/null
+++ b/modules/gltf/structures/gltf_object_model_property.cpp
@@ -0,0 +1,173 @@
+/**************************************************************************/
+/* gltf_object_model_property.cpp */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#include "gltf_object_model_property.h"
+
+#include "../gltf_template_convert.h"
+
+void GLTFObjectModelProperty::_bind_methods() {
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_UNKNOWN);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_BOOL);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT2X2);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT3X3);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_FLOAT4X4);
+ BIND_ENUM_CONSTANT(GLTF_OBJECT_MODEL_TYPE_INT);
+
+ ClassDB::bind_method(D_METHOD("append_node_path", "node_path"), &GLTFObjectModelProperty::append_node_path);
+ ClassDB::bind_method(D_METHOD("append_path_to_property", "node_path", "prop_name"), &GLTFObjectModelProperty::append_path_to_property);
+
+ ClassDB::bind_method(D_METHOD("get_accessor_type"), &GLTFObjectModelProperty::get_accessor_type);
+ ClassDB::bind_method(D_METHOD("get_gltf_to_godot_expression"), &GLTFObjectModelProperty::get_gltf_to_godot_expression);
+ ClassDB::bind_method(D_METHOD("set_gltf_to_godot_expression", "gltf_to_godot_expr"), &GLTFObjectModelProperty::set_gltf_to_godot_expression);
+ ClassDB::bind_method(D_METHOD("get_godot_to_gltf_expression"), &GLTFObjectModelProperty::get_godot_to_gltf_expression);
+ ClassDB::bind_method(D_METHOD("set_godot_to_gltf_expression", "godot_to_gltf_expr"), &GLTFObjectModelProperty::set_godot_to_gltf_expression);
+ ClassDB::bind_method(D_METHOD("get_node_paths"), &GLTFObjectModelProperty::get_node_paths);
+ ClassDB::bind_method(D_METHOD("has_node_paths"), &GLTFObjectModelProperty::has_node_paths);
+ ClassDB::bind_method(D_METHOD("set_node_paths", "node_paths"), &GLTFObjectModelProperty::set_node_paths);
+ ClassDB::bind_method(D_METHOD("get_object_model_type"), &GLTFObjectModelProperty::get_object_model_type);
+ ClassDB::bind_method(D_METHOD("set_object_model_type", "type"), &GLTFObjectModelProperty::set_object_model_type);
+ ClassDB::bind_method(D_METHOD("get_json_pointers"), &GLTFObjectModelProperty::get_json_pointers_bind);
+ ClassDB::bind_method(D_METHOD("has_json_pointers"), &GLTFObjectModelProperty::has_json_pointers);
+ ClassDB::bind_method(D_METHOD("set_json_pointers", "json_pointers"), &GLTFObjectModelProperty::set_json_pointers_bind);
+ ClassDB::bind_method(D_METHOD("get_variant_type"), &GLTFObjectModelProperty::get_variant_type);
+ ClassDB::bind_method(D_METHOD("set_variant_type", "variant_type"), &GLTFObjectModelProperty::set_variant_type);
+ ClassDB::bind_method(D_METHOD("set_types", "variant_type", "obj_model_type"), &GLTFObjectModelProperty::set_types);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gltf_to_godot_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_gltf_to_godot_expression", "get_gltf_to_godot_expression"); // Ref<Expression>
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "godot_to_gltf_expression", PROPERTY_HINT_RESOURCE_TYPE, "Expression"), "set_godot_to_gltf_expression", "get_godot_to_gltf_expression"); // Ref<Expression>
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "node_paths", PROPERTY_HINT_TYPE_STRING, "NodePath"), "set_node_paths", "get_node_paths"); // TypedArray<NodePath>
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "object_model_type"), "set_object_model_type", "get_object_model_type"); // GLTFObjectModelType
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "json_pointers"), "set_json_pointers", "get_json_pointers"); // TypedArray<PackedStringArray>
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "variant_type"), "set_variant_type", "get_variant_type"); // Variant::Type
+}
+
+void GLTFObjectModelProperty::append_node_path(const NodePath &p_node_path) {
+ node_paths.push_back(p_node_path);
+}
+
+void GLTFObjectModelProperty::append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name) {
+ Vector<StringName> node_names = p_node_path.get_names();
+ Vector<StringName> subpath = p_node_path.get_subnames();
+ subpath.append(p_prop_name);
+ node_paths.push_back(NodePath(node_names, subpath, false));
+}
+
+GLTFAccessor::GLTFAccessorType GLTFObjectModelProperty::get_accessor_type() const {
+ switch (object_model_type) {
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT2:
+ return GLTFAccessor::TYPE_VEC2;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT3:
+ return GLTFAccessor::TYPE_VEC3;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT4:
+ return GLTFAccessor::TYPE_VEC4;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT2X2:
+ return GLTFAccessor::TYPE_MAT2;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT3X3:
+ return GLTFAccessor::TYPE_MAT3;
+ case GLTF_OBJECT_MODEL_TYPE_FLOAT4X4:
+ return GLTFAccessor::TYPE_MAT4;
+ default:
+ return GLTFAccessor::TYPE_SCALAR;
+ }
+}
+
+Ref<Expression> GLTFObjectModelProperty::get_gltf_to_godot_expression() const {
+ return gltf_to_godot_expr;
+}
+
+void GLTFObjectModelProperty::set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr) {
+ gltf_to_godot_expr = p_gltf_to_godot_expr;
+}
+
+Ref<Expression> GLTFObjectModelProperty::get_godot_to_gltf_expression() const {
+ return godot_to_gltf_expr;
+}
+
+void GLTFObjectModelProperty::set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr) {
+ godot_to_gltf_expr = p_godot_to_gltf_expr;
+}
+
+TypedArray<NodePath> GLTFObjectModelProperty::get_node_paths() const {
+ return node_paths;
+}
+
+bool GLTFObjectModelProperty::has_node_paths() const {
+ return !node_paths.is_empty();
+}
+
+void GLTFObjectModelProperty::set_node_paths(TypedArray<NodePath> p_node_paths) {
+ node_paths = p_node_paths;
+}
+
+GLTFObjectModelProperty::GLTFObjectModelType GLTFObjectModelProperty::get_object_model_type() const {
+ return object_model_type;
+}
+
+void GLTFObjectModelProperty::set_object_model_type(GLTFObjectModelType p_type) {
+ object_model_type = p_type;
+}
+
+Vector<PackedStringArray> GLTFObjectModelProperty::get_json_pointers() const {
+ return json_pointers;
+}
+
+bool GLTFObjectModelProperty::has_json_pointers() const {
+ return !json_pointers.is_empty();
+}
+
+void GLTFObjectModelProperty::set_json_pointers(const Vector<PackedStringArray> &p_json_pointers) {
+ json_pointers = p_json_pointers;
+}
+
+TypedArray<PackedStringArray> GLTFObjectModelProperty::get_json_pointers_bind() const {
+ return GLTFTemplateConvert::to_array(json_pointers);
+}
+
+void GLTFObjectModelProperty::set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers) {
+ GLTFTemplateConvert::set_from_array(json_pointers, p_json_pointers);
+}
+
+Variant::Type GLTFObjectModelProperty::get_variant_type() const {
+ return variant_type;
+}
+
+void GLTFObjectModelProperty::set_variant_type(Variant::Type p_variant_type) {
+ variant_type = p_variant_type;
+}
+
+void GLTFObjectModelProperty::set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type) {
+ variant_type = p_variant_type;
+ object_model_type = p_obj_model_type;
+}
diff --git a/modules/gltf/structures/gltf_object_model_property.h b/modules/gltf/structures/gltf_object_model_property.h
new file mode 100644
index 0000000000..d8a4ed420a
--- /dev/null
+++ b/modules/gltf/structures/gltf_object_model_property.h
@@ -0,0 +1,104 @@
+/**************************************************************************/
+/* gltf_object_model_property.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GLTF_OBJECT_MODEL_PROPERTY_H
+#define GLTF_OBJECT_MODEL_PROPERTY_H
+
+#include "core/math/expression.h"
+#include "core/variant/typed_array.h"
+#include "gltf_accessor.h"
+
+// Object model: https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc
+// KHR_animation_pointer: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer
+
+class GLTFObjectModelProperty : public RefCounted {
+ GDCLASS(GLTFObjectModelProperty, RefCounted);
+
+public:
+ enum GLTFObjectModelType {
+ GLTF_OBJECT_MODEL_TYPE_UNKNOWN,
+ GLTF_OBJECT_MODEL_TYPE_BOOL,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT_ARRAY,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT2,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT3,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT4,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT2X2,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT3X3,
+ GLTF_OBJECT_MODEL_TYPE_FLOAT4X4,
+ GLTF_OBJECT_MODEL_TYPE_INT,
+ };
+
+private:
+ Ref<Expression> gltf_to_godot_expr;
+ Ref<Expression> godot_to_gltf_expr;
+ TypedArray<NodePath> node_paths;
+ GLTFObjectModelType object_model_type = GLTF_OBJECT_MODEL_TYPE_UNKNOWN;
+ Vector<PackedStringArray> json_pointers;
+ Variant::Type variant_type = Variant::NIL;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void append_node_path(const NodePath &p_node_path);
+ void append_path_to_property(const NodePath &p_node_path, const StringName &p_prop_name);
+
+ GLTFAccessor::GLTFAccessorType get_accessor_type() const;
+
+ Ref<Expression> get_gltf_to_godot_expression() const;
+ void set_gltf_to_godot_expression(Ref<Expression> p_gltf_to_godot_expr);
+
+ Ref<Expression> get_godot_to_gltf_expression() const;
+ void set_godot_to_gltf_expression(Ref<Expression> p_godot_to_gltf_expr);
+
+ TypedArray<NodePath> get_node_paths() const;
+ bool has_node_paths() const;
+ void set_node_paths(TypedArray<NodePath> p_node_paths);
+
+ GLTFObjectModelType get_object_model_type() const;
+ void set_object_model_type(GLTFObjectModelType p_type);
+
+ Vector<PackedStringArray> get_json_pointers() const;
+ bool has_json_pointers() const;
+ void set_json_pointers(const Vector<PackedStringArray> &p_json_pointers);
+
+ TypedArray<PackedStringArray> get_json_pointers_bind() const;
+ void set_json_pointers_bind(const TypedArray<PackedStringArray> &p_json_pointers);
+
+ Variant::Type get_variant_type() const;
+ void set_variant_type(Variant::Type p_variant_type);
+
+ void set_types(Variant::Type p_variant_type, GLTFObjectModelType p_obj_model_type);
+};
+
+VARIANT_ENUM_CAST(GLTFObjectModelProperty::GLTFObjectModelType);
+
+#endif // GLTF_OBJECT_MODEL_PROPERTY_H
diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h
index 37c8f6925c..73ef02e9f1 100644
--- a/modules/gltf/tests/test_gltf_extras.h
+++ b/modules/gltf/tests/test_gltf_extras.h
@@ -91,7 +91,7 @@ static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) {
options["gltf/naming_version"] = 1;
// Process gltf file, note that this generates `.scn` resource from the 2nd argument.
- err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr);
+ err = import_scene->import(0, p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr);
CHECK_MESSAGE(err == OK, "GLTF import failed.");
ResourceImporterScene::remove_scene_importer(import_gltf);
diff --git a/modules/godot_physics_2d/godot_joints_2d.cpp b/modules/godot_physics_2d/godot_joints_2d.cpp
index 5c76eb9dad..d5a779ebb5 100644
--- a/modules/godot_physics_2d/godot_joints_2d.cpp
+++ b/modules/godot_physics_2d/godot_joints_2d.cpp
@@ -311,7 +311,7 @@ bool GodotPinJoint2D::get_flag(PhysicsServer2D::PinJointFlag p_flag) const {
return motor_enabled;
}
}
- ERR_FAIL_V(0);
+ ERR_FAIL_V(false);
}
GodotPinJoint2D::GodotPinJoint2D(const Vector2 &p_pos, GodotBody2D *p_body_a, GodotBody2D *p_body_b) :
diff --git a/modules/godot_physics_2d/godot_joints_2d.h b/modules/godot_physics_2d/godot_joints_2d.h
index c6a1fdb692..54884e112a 100644
--- a/modules/godot_physics_2d/godot_joints_2d.h
+++ b/modules/godot_physics_2d/godot_joints_2d.h
@@ -70,7 +70,7 @@ public:
body->remove_constraint(this, i);
}
}
- };
+ }
};
class GodotPinJoint2D : public GodotJoint2D {
diff --git a/modules/godot_physics_2d/godot_physics_server_2d.cpp b/modules/godot_physics_2d/godot_physics_server_2d.cpp
index 8df17992ea..2516ed3616 100644
--- a/modules/godot_physics_2d/godot_physics_server_2d.cpp
+++ b/modules/godot_physics_2d/godot_physics_server_2d.cpp
@@ -116,7 +116,7 @@ void GodotPhysicsServer2D::shape_set_data(RID p_shape, const Variant &p_data) {
GodotShape2D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL(shape);
shape->set_data(p_data);
-};
+}
void GodotPhysicsServer2D::shape_set_custom_solver_bias(RID p_shape, real_t p_bias) {
GodotShape2D *shape = shape_owner.get_or_null(p_shape);
@@ -128,14 +128,14 @@ PhysicsServer2D::ShapeType GodotPhysicsServer2D::shape_get_type(RID p_shape) con
const GodotShape2D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL_V(shape, SHAPE_CUSTOM);
return shape->get_type();
-};
+}
Variant GodotPhysicsServer2D::shape_get_data(RID p_shape) const {
const GodotShape2D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL_V(shape, Variant());
ERR_FAIL_COND_V(!shape->is_configured(), Variant());
return shape->get_data();
-};
+}
real_t GodotPhysicsServer2D::shape_get_custom_solver_bias(RID p_shape) const {
const GodotShape2D *shape = shape_owner.get_or_null(p_shape);
@@ -226,7 +226,7 @@ RID GodotPhysicsServer2D::space_create() {
area->set_priority(-1);
return id;
-};
+}
void GodotPhysicsServer2D::space_set_active(RID p_space, bool p_active) {
GodotSpace2D *space = space_owner.get_or_null(p_space);
@@ -445,13 +445,13 @@ void GodotPhysicsServer2D::area_set_param(RID p_area, AreaParameter p_param, con
GodotArea2D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
area->set_param(p_param, p_value);
-};
+}
void GodotPhysicsServer2D::area_set_transform(RID p_area, const Transform2D &p_transform) {
GodotArea2D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
area->set_transform(p_transform);
-};
+}
Variant GodotPhysicsServer2D::area_get_param(RID p_area, AreaParameter p_param) const {
if (space_owner.owns(p_area)) {
@@ -462,14 +462,14 @@ Variant GodotPhysicsServer2D::area_get_param(RID p_area, AreaParameter p_param)
ERR_FAIL_NULL_V(area, Variant());
return area->get_param(p_param);
-};
+}
Transform2D GodotPhysicsServer2D::area_get_transform(RID p_area) const {
GodotArea2D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL_V(area, Transform2D());
return area->get_transform();
-};
+}
void GodotPhysicsServer2D::area_set_pickable(RID p_area, bool p_pickable) {
GodotArea2D *area = area_owner.get_or_null(p_area);
@@ -551,7 +551,7 @@ void GodotPhysicsServer2D::body_set_space(RID p_body, RID p_space) {
body->clear_constraint_list();
body->set_space(space);
-};
+}
RID GodotPhysicsServer2D::body_get_space(RID p_body) const {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -562,7 +562,7 @@ RID GodotPhysicsServer2D::body_get_space(RID p_body) const {
return RID();
}
return space->get_self();
-};
+}
void GodotPhysicsServer2D::body_set_mode(RID p_body, BodyMode p_mode) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -570,14 +570,14 @@ void GodotPhysicsServer2D::body_set_mode(RID p_body, BodyMode p_mode) {
FLUSH_QUERY_CHECK(body);
body->set_mode(p_mode);
-};
+}
PhysicsServer2D::BodyMode GodotPhysicsServer2D::body_get_mode(RID p_body) const {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, BODY_MODE_STATIC);
return body->get_mode();
-};
+}
void GodotPhysicsServer2D::body_add_shape(RID p_body, RID p_shape, const Transform2D &p_transform, bool p_disabled) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -902,7 +902,7 @@ void GodotPhysicsServer2D::body_set_axis_velocity(RID p_body, const Vector2 &p_a
v += p_axis_velocity;
body->set_linear_velocity(v);
body->wakeup();
-};
+}
void GodotPhysicsServer2D::body_add_collision_exception(RID p_body, RID p_body_b) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -910,7 +910,7 @@ void GodotPhysicsServer2D::body_add_collision_exception(RID p_body, RID p_body_b
body->add_exception(p_body_b);
body->wakeup();
-};
+}
void GodotPhysicsServer2D::body_remove_collision_exception(RID p_body, RID p_body_b) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -918,7 +918,7 @@ void GodotPhysicsServer2D::body_remove_collision_exception(RID p_body, RID p_bod
body->remove_exception(p_body_b);
body->wakeup();
-};
+}
void GodotPhysicsServer2D::body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -927,31 +927,31 @@ void GodotPhysicsServer2D::body_get_collision_exceptions(RID p_body, List<RID> *
for (int i = 0; i < body->get_exceptions().size(); i++) {
p_exceptions->push_back(body->get_exceptions()[i]);
}
-};
+}
void GodotPhysicsServer2D::body_set_contacts_reported_depth_threshold(RID p_body, real_t p_threshold) {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL(body);
-};
+}
real_t GodotPhysicsServer2D::body_get_contacts_reported_depth_threshold(RID p_body) const {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, 0);
return 0;
-};
+}
void GodotPhysicsServer2D::body_set_omit_force_integration(RID p_body, bool p_omit) {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL(body);
body->set_omit_force_integration(p_omit);
-};
+}
bool GodotPhysicsServer2D::body_is_omitting_force_integration(RID p_body) const {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, false);
return body->get_omit_force_integration();
-};
+}
void GodotPhysicsServer2D::body_set_max_contacts_reported(RID p_body, int p_contacts) {
GodotBody2D *body = body_owner.get_or_null(p_body);
@@ -1169,8 +1169,8 @@ void GodotPhysicsServer2D::pin_joint_set_flag(RID p_joint, PinJointFlag p_flag,
bool GodotPhysicsServer2D::pin_joint_get_flag(RID p_joint, PinJointFlag p_flag) const {
GodotJoint2D *joint = joint_owner.get_or_null(p_joint);
- ERR_FAIL_NULL_V(joint, 0);
- ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, 0);
+ ERR_FAIL_NULL_V(joint, false);
+ ERR_FAIL_COND_V(joint->get_type() != JOINT_TYPE_PIN, false);
GodotPinJoint2D *pin_joint = static_cast<GodotPinJoint2D *>(joint);
return pin_joint->get_flag(p_flag);
diff --git a/modules/godot_physics_2d/godot_space_2d.cpp b/modules/godot_physics_2d/godot_space_2d.cpp
index 2966818beb..df3c9d8265 100644
--- a/modules/godot_physics_2d/godot_space_2d.cpp
+++ b/modules/godot_physics_2d/godot_space_2d.cpp
@@ -342,7 +342,7 @@ bool GodotPhysicsDirectSpaceState2D::collide_shape(const ShapeParameters &p_para
}
GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
Rect2 aabb = p_parameters.transform.xform(shape->get_aabb());
aabb = aabb.merge(Rect2(aabb.position + p_parameters.motion, aabb.size)); //motion
@@ -439,7 +439,7 @@ static void _rest_cbk_result(const Vector2 &p_point_A, const Vector2 &p_point_B,
bool GodotPhysicsDirectSpaceState2D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) {
GodotShape2D *shape = GodotPhysicsServer2D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE);
diff --git a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
index c53c8481f4..2adbb51297 100644
--- a/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
+++ b/modules/godot_physics_3d/godot_collision_solver_3d_sat.cpp
@@ -76,8 +76,9 @@ struct _CollectorCallback {
Vector3 *prev_axis = nullptr;
_FORCE_INLINE_ void call(const Vector3 &p_point_A, const Vector3 &p_point_B, Vector3 p_normal) {
- if (p_normal.dot(p_point_B - p_point_A) < 0)
+ if (p_normal.dot(p_point_B - p_point_A) < 0) {
p_normal = -p_normal;
+ }
if (swap) {
callback(p_point_B, 0, p_point_A, 0, -p_normal, userdata);
} else {
@@ -175,10 +176,11 @@ static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_
// The normal should be perpendicular to both edges.
Vector3 normal = rel_A.cross(rel_B);
real_t normal_len = normal.length();
- if (normal_len > 1e-3)
+ if (normal_len > 1e-3) {
normal /= normal_len;
- else
+ } else {
normal = p_callback->normal;
+ }
p_callback->call(closest_A, closest_B, normal);
}
@@ -784,8 +786,9 @@ static void analytic_sphere_collision(const Vector3 &p_origin_a, real_t p_radius
// Calculate the sphere overlap, and bail if not overlapping
real_t overlap = p_radius_a + p_radius_b - b_to_a_len;
- if (overlap < 0)
+ if (overlap < 0) {
return;
+ }
// Report collision
p_collector->collided = true;
diff --git a/modules/godot_physics_3d/godot_physics_server_3d.cpp b/modules/godot_physics_3d/godot_physics_server_3d.cpp
index 6d0949acbe..ad55e415e6 100644
--- a/modules/godot_physics_3d/godot_physics_server_3d.cpp
+++ b/modules/godot_physics_3d/godot_physics_server_3d.cpp
@@ -106,7 +106,7 @@ void GodotPhysicsServer3D::shape_set_data(RID p_shape, const Variant &p_data) {
GodotShape3D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL(shape);
shape->set_data(p_data);
-};
+}
void GodotPhysicsServer3D::shape_set_custom_solver_bias(RID p_shape, real_t p_bias) {
GodotShape3D *shape = shape_owner.get_or_null(p_shape);
@@ -118,14 +118,14 @@ PhysicsServer3D::ShapeType GodotPhysicsServer3D::shape_get_type(RID p_shape) con
const GodotShape3D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL_V(shape, SHAPE_CUSTOM);
return shape->get_type();
-};
+}
Variant GodotPhysicsServer3D::shape_get_data(RID p_shape) const {
const GodotShape3D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL_V(shape, Variant());
ERR_FAIL_COND_V(!shape->is_configured(), Variant());
return shape->get_data();
-};
+}
void GodotPhysicsServer3D::shape_set_margin(RID p_shape, real_t p_margin) {
}
@@ -156,7 +156,7 @@ RID GodotPhysicsServer3D::space_create() {
space->set_static_global_body(sgb);
return id;
-};
+}
void GodotPhysicsServer3D::space_set_active(RID p_space, bool p_active) {
GodotSpace3D *space = space_owner.get_or_null(p_space);
@@ -354,13 +354,13 @@ void GodotPhysicsServer3D::area_set_param(RID p_area, AreaParameter p_param, con
GodotArea3D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
area->set_param(p_param, p_value);
-};
+}
void GodotPhysicsServer3D::area_set_transform(RID p_area, const Transform3D &p_transform) {
GodotArea3D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
area->set_transform(p_transform);
-};
+}
Variant GodotPhysicsServer3D::area_get_param(RID p_area, AreaParameter p_param) const {
if (space_owner.owns(p_area)) {
@@ -371,14 +371,14 @@ Variant GodotPhysicsServer3D::area_get_param(RID p_area, AreaParameter p_param)
ERR_FAIL_NULL_V(area, Variant());
return area->get_param(p_param);
-};
+}
Transform3D GodotPhysicsServer3D::area_get_transform(RID p_area) const {
GodotArea3D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL_V(area, Transform3D());
return area->get_transform();
-};
+}
void GodotPhysicsServer3D::area_set_collision_layer(RID p_area, uint32_t p_layer) {
GodotArea3D *area = area_owner.get_or_null(p_area);
@@ -444,7 +444,7 @@ RID GodotPhysicsServer3D::body_create() {
RID rid = body_owner.make_rid(body);
body->set_self(rid);
return rid;
-};
+}
void GodotPhysicsServer3D::body_set_space(RID p_body, RID p_space) {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -462,7 +462,7 @@ void GodotPhysicsServer3D::body_set_space(RID p_body, RID p_space) {
body->clear_constraint_map();
body->set_space(space);
-};
+}
RID GodotPhysicsServer3D::body_get_space(RID p_body) const {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -473,21 +473,21 @@ RID GodotPhysicsServer3D::body_get_space(RID p_body) const {
return RID();
}
return space->get_self();
-};
+}
void GodotPhysicsServer3D::body_set_mode(RID p_body, BodyMode p_mode) {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL(body);
body->set_mode(p_mode);
-};
+}
PhysicsServer3D::BodyMode GodotPhysicsServer3D::body_get_mode(RID p_body) const {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, BODY_MODE_STATIC);
return body->get_mode();
-};
+}
void GodotPhysicsServer3D::body_add_shape(RID p_body, RID p_shape, const Transform3D &p_transform, bool p_disabled) {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -826,7 +826,7 @@ void GodotPhysicsServer3D::body_set_axis_lock(RID p_body, BodyAxis p_axis, bool
bool GodotPhysicsServer3D::body_is_axis_locked(RID p_body, BodyAxis p_axis) const {
const GodotBody3D *body = body_owner.get_or_null(p_body);
- ERR_FAIL_NULL_V(body, 0);
+ ERR_FAIL_NULL_V(body, false);
return body->is_axis_locked(p_axis);
}
@@ -836,7 +836,7 @@ void GodotPhysicsServer3D::body_add_collision_exception(RID p_body, RID p_body_b
body->add_exception(p_body_b);
body->wakeup();
-};
+}
void GodotPhysicsServer3D::body_remove_collision_exception(RID p_body, RID p_body_b) {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -844,7 +844,7 @@ void GodotPhysicsServer3D::body_remove_collision_exception(RID p_body, RID p_bod
body->remove_exception(p_body_b);
body->wakeup();
-};
+}
void GodotPhysicsServer3D::body_get_collision_exceptions(RID p_body, List<RID> *p_exceptions) {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -853,31 +853,31 @@ void GodotPhysicsServer3D::body_get_collision_exceptions(RID p_body, List<RID> *
for (int i = 0; i < body->get_exceptions().size(); i++) {
p_exceptions->push_back(body->get_exceptions()[i]);
}
-};
+}
void GodotPhysicsServer3D::body_set_contacts_reported_depth_threshold(RID p_body, real_t p_threshold) {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL(body);
-};
+}
real_t GodotPhysicsServer3D::body_get_contacts_reported_depth_threshold(RID p_body) const {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, 0);
return 0;
-};
+}
void GodotPhysicsServer3D::body_set_omit_force_integration(RID p_body, bool p_omit) {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL(body);
body->set_omit_force_integration(p_omit);
-};
+}
bool GodotPhysicsServer3D::body_is_omitting_force_integration(RID p_body) const {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, false);
return body->get_omit_force_integration();
-};
+}
void GodotPhysicsServer3D::body_set_max_contacts_reported(RID p_body, int p_contacts) {
GodotBody3D *body = body_owner.get_or_null(p_body);
@@ -1770,4 +1770,4 @@ GodotPhysicsServer3D::GodotPhysicsServer3D(bool p_using_threads) {
GodotBroadPhase3D::create_func = GodotBroadPhase3DBVH::_create;
using_threads = p_using_threads;
-};
+}
diff --git a/modules/godot_physics_3d/godot_shape_3d.cpp b/modules/godot_physics_3d/godot_shape_3d.cpp
index 70b6bcf19e..4356ebe2f2 100644
--- a/modules/godot_physics_3d/godot_shape_3d.cpp
+++ b/modules/godot_physics_3d/godot_shape_3d.cpp
@@ -1133,8 +1133,9 @@ void GodotConvexPolygonShape3D::_setup(const Vector<Vector3> &p_vertices) {
max_support = s;
}
}
- if (!extreme_vertices.has(best_vertex))
+ if (!extreme_vertices.has(best_vertex)) {
extreme_vertices.push_back(best_vertex);
+ }
}
}
}
@@ -1995,7 +1996,11 @@ bool GodotHeightMapShape3D::intersect_segment(const Vector3 &p_begin, const Vect
Vector3 bounds_from = p_begin / BOUNDS_CHUNK_SIZE;
Vector3 bounds_to = p_end / BOUNDS_CHUNK_SIZE;
Vector3 bounds_offset = local_origin / BOUNDS_CHUNK_SIZE;
- return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_grid_width, bounds_grid_depth, bounds_offset, r_point, r_normal);
+ // Plus 1 here to width and depth of the chunk because _intersect_grid_segment() is used by cell level as well,
+ // and in _intersect_grid_segment() the loop will exit 1 early because for cell point triangle lookup, it dose x + 1, z + 1 etc for the vertex.
+ int bounds_width = bounds_grid_width + 1;
+ int bounds_depth = bounds_grid_depth + 1;
+ return _intersect_grid_segment(_heightmap_chunk_cull_segment, bounds_from, bounds_to, bounds_width, bounds_depth, bounds_offset, r_point, r_normal);
}
}
diff --git a/modules/godot_physics_3d/godot_soft_body_3d.cpp b/modules/godot_physics_3d/godot_soft_body_3d.cpp
index 7284076a47..e8be227d09 100644
--- a/modules/godot_physics_3d/godot_soft_body_3d.cpp
+++ b/modules/godot_physics_3d/godot_soft_body_3d.cpp
@@ -1121,7 +1121,7 @@ struct AABBQueryResult {
_FORCE_INLINE_ bool operator()(void *p_data) {
return result_callback(soft_body->get_node_index(p_data), userdata);
- };
+ }
};
void GodotSoftBody3D::query_aabb(const AABB &p_aabb, GodotSoftBody3D::QueryResultCallback p_result_callback, void *p_userdata) {
@@ -1140,7 +1140,7 @@ struct RayQueryResult {
_FORCE_INLINE_ bool operator()(void *p_data) {
return result_callback(soft_body->get_face_index(p_data), userdata);
- };
+ }
};
void GodotSoftBody3D::query_ray(const Vector3 &p_from, const Vector3 &p_to, GodotSoftBody3D::QueryResultCallback p_result_callback, void *p_userdata) {
diff --git a/modules/godot_physics_3d/godot_space_3d.cpp b/modules/godot_physics_3d/godot_space_3d.cpp
index 9a6ba776b4..9f82a87f85 100644
--- a/modules/godot_physics_3d/godot_space_3d.cpp
+++ b/modules/godot_physics_3d/godot_space_3d.cpp
@@ -385,7 +385,7 @@ bool GodotPhysicsDirectSpaceState3D::collide_shape(const ShapeParameters &p_para
}
GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
AABB aabb = p_parameters.transform.xform(shape->get_aabb());
aabb = aabb.grow(p_parameters.margin);
@@ -511,7 +511,7 @@ static void _rest_cbk_result(const Vector3 &p_point_A, int p_index_A, const Vect
bool GodotPhysicsDirectSpaceState3D::rest_info(const ShapeParameters &p_parameters, ShapeRestInfo *r_info) {
GodotShape3D *shape = GodotPhysicsServer3D::godot_singleton->shape_owner.get_or_null(p_parameters.shape_rid);
- ERR_FAIL_NULL_V(shape, 0);
+ ERR_FAIL_NULL_V(shape, false);
real_t margin = MAX(p_parameters.margin, TEST_MOTION_MARGIN_MIN_VALUE);
diff --git a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
index 226f8a0f7f..5f9cf9de49 100644
--- a/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
+++ b/modules/godot_physics_3d/joints/godot_generic_6dof_joint_3d.cpp
@@ -647,7 +647,7 @@ void GodotGeneric6DOFJoint3D::set_flag(Vector3::Axis p_axis, PhysicsServer3D::G6
}
bool GodotGeneric6DOFJoint3D::get_flag(Vector3::Axis p_axis, PhysicsServer3D::G6DOFJointAxisFlag p_flag) const {
- ERR_FAIL_INDEX_V(p_axis, 3, 0);
+ ERR_FAIL_INDEX_V(p_axis, 3, false);
switch (p_flag) {
case PhysicsServer3D::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT: {
return m_linearLimits.enable_limit[p_axis];
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index 4c11565c51..99e3b02dea 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -34,11 +34,14 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
+#include "editor/editor_command_palette.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_bottom_panel.h"
+#include "editor/gui/editor_zoom_widget.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/camera_3d.h"
@@ -84,16 +87,10 @@ void GridMapEditor::_menu_option(int p_option) {
}
if (edit_axis != new_axis) {
- int item1 = options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL);
- int item2 = options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL);
if (edit_axis == Vector3::AXIS_Y) {
- options->get_popup()->set_item_text(item1, TTR("Next Plane"));
- options->get_popup()->set_item_text(item2, TTR("Previous Plane"));
- spin_box_label->set_text(TTR("Plane:"));
+ floor->set_tooltip_text("Change Grid Plane");
} else if (new_axis == Vector3::AXIS_Y) {
- options->get_popup()->set_item_text(item1, TTR("Next Floor"));
- options->get_popup()->set_item_text(item2, TTR("Previous Floor"));
- spin_box_label->set_text(TTR("Floor:"));
+ floor->set_tooltip_text("Change Grid Floor");
}
}
edit_axis = Vector3::Axis(new_axis);
@@ -251,14 +248,22 @@ void GridMapEditor::_menu_option(int p_option) {
void GridMapEditor::_update_cursor_transform() {
cursor_transform = Transform3D();
cursor_transform.origin = cursor_origin;
- cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot);
cursor_transform.basis *= node->get_cell_scale();
cursor_transform = node->get_global_transform() * cursor_transform;
- if (selected_palette >= 0) {
- if (node && !node->get_mesh_library().is_null()) {
+ if (mode_buttons_group->get_pressed_button() == paint_mode_button) {
+ // Rotation is only applied in paint mode, we don't want the cursor box to rotate otherwise.
+ cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot);
+ if (selected_palette >= 0 && node && node->get_mesh_library().is_valid()) {
cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette);
}
+ } else {
+ Transform3D xf;
+ xf.scale(node->get_cell_size());
+ xf.origin.x = node->get_center_x() ? -node->get_cell_size().x / 2 : 0;
+ xf.origin.y = node->get_center_y() ? -node->get_cell_size().y / 2 : 0;
+ xf.origin.z = node->get_center_z() ? -node->get_cell_size().z / 2 : 0;
+ cursor_transform *= xf;
}
if (cursor_instance.is_valid()) {
@@ -301,7 +306,7 @@ void GridMapEditor::_update_selection_transform() {
xf2.basis.scale(scale);
xf2.origin = position;
- RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], xf2);
+ RenderingServer::get_singleton()->instance_set_transform(selection_level_instance[i], node->get_global_transform() * xf2);
}
}
}
@@ -336,25 +341,22 @@ void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const
if (is_visible_in_tree()) {
_update_selection_transform();
}
-
- options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active);
- options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CUT), !selection.active);
- options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_DUPLICATE), !selection.active);
- options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_FILL), !selection.active);
}
bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, bool p_click) {
if (!spatial_editor) {
return false;
}
-
- if (selected_palette < 0 && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) {
+ if (input_action == INPUT_TRANSFORM) {
+ return false;
+ }
+ if (selected_palette < 0 && input_action != INPUT_NONE && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE) {
return false;
}
if (mesh_library.is_null()) {
return false;
}
- if (input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE && !mesh_library->has_item(selected_palette)) {
+ if (input_action != INPUT_NONE && input_action != INPUT_PICK && input_action != INPUT_SELECT && input_action != INPUT_PASTE && !mesh_library->has_item(selected_palette)) {
return false;
}
@@ -405,13 +407,17 @@ bool GridMapEditor::do_input_action(Camera3D *p_camera, const Point2 &p_point, b
cursor_origin = (Vector3(cell[0], cell[1], cell[2]) + Vector3(0.5 * node->get_center_x(), 0.5 * node->get_center_y(), 0.5 * node->get_center_z())) * node->get_cell_size();
cursor_visible = true;
- if (input_action == INPUT_SELECT || input_action == INPUT_PASTE) {
+ if (input_action == INPUT_PASTE) {
cursor_visible = false;
}
_update_cursor_transform();
}
+ if (input_action == INPUT_NONE) {
+ return false;
+ }
+
if (input_action == INPUT_PASTE) {
paste_indicator.current = Vector3i(cell[0], cell[1], cell[2]);
_update_paste_indicator();
@@ -604,7 +610,18 @@ void GridMapEditor::_do_paste() {
}
if (reselect) {
- undo_redo->add_do_method(this, "_set_selection", true, paste_indicator.begin + ofs, paste_indicator.end + ofs);
+ // We need to rotate the paste_indicator to find the selection begin and end:
+ Vector3 temp_end = rot.xform(paste_indicator.end - paste_indicator.begin) + paste_indicator.begin + ofs;
+ Vector3 temp_begin = paste_indicator.begin + ofs;
+ // _set_selection expects that selection_begin is the corner closer to the origin:
+ for (int i = 0; i < 3; ++i) {
+ if (temp_begin[i] > temp_end[i]) {
+ float p = temp_begin[i];
+ temp_begin[i] = temp_end[i];
+ temp_end[i] = p;
+ }
+ }
+ undo_redo->add_do_method(this, "_set_selection", true, temp_begin, temp_end);
undo_redo->add_undo_method(this, "_set_selection", selection.active, selection.begin, selection.end);
}
@@ -613,13 +630,92 @@ void GridMapEditor::_do_paste() {
_clear_clipboard_data();
}
+void GridMapEditor::_show_viewports_transform_gizmo(bool p_value) {
+ Dictionary new_state;
+ new_state["transform_gizmo"] = p_value;
+ for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {
+ Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i);
+ viewport->set_state(new_state);
+ }
+}
+
EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
if (!node) {
return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
- Ref<InputEventMouseButton> mb = p_event;
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ // Transform mode (toggle button):
+ // If we are in Transform mode we pass the events to the 3D editor,
+ // but if the Transform mode shortcut is pressed again, we go back to Selection mode.
+ if (mode_buttons_group->get_pressed_button() == transform_mode_button) {
+ if (transform_mode_button->get_shortcut().is_valid() && transform_mode_button->get_shortcut()->matches_event(p_event)) {
+ select_mode_button->set_pressed(true);
+ accept_event();
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
+ }
+ // Tool modes and tool actions:
+ for (BaseButton *b : viewport_shortcut_buttons) {
+ if (b->is_disabled()) {
+ continue;
+ }
+
+ if (b->get_shortcut().is_valid() && b->get_shortcut()->matches_event(p_event)) {
+ if (b->is_toggle_mode()) {
+ b->set_pressed(b->get_button_group().is_valid() || !b->is_pressed());
+ } else {
+ // Can't press a button without toggle mode, so just emit the signal directly.
+ b->emit_signal(SceneStringName(pressed));
+ }
+ accept_event();
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ // Hard key actions:
+ if (k->get_keycode() == Key::ESCAPE) {
+ if (input_action == INPUT_PASTE) {
+ _clear_clipboard_data();
+ input_action = INPUT_NONE;
+ _update_paste_indicator();
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ } else if (selection.active) {
+ _set_selection(false);
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ } else {
+ input_action = INPUT_NONE;
+ update_palette();
+ _update_cursor_instance();
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ // Options menu shortcuts:
+ Ref<Shortcut> ed_shortcut = ED_GET_SHORTCUT("grid_map/previous_floor");
+ if (ed_shortcut.is_valid() && ed_shortcut->matches_event(p_event)) {
+ accept_event();
+ _menu_option(MENU_OPTION_PREV_LEVEL);
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ ed_shortcut = ED_GET_SHORTCUT("grid_map/next_floor");
+ if (ed_shortcut.is_valid() && ed_shortcut->matches_event(p_event)) {
+ accept_event();
+ _menu_option(MENU_OPTION_NEXT_LEVEL);
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ for (int i = 0; i < options->get_popup()->get_item_count(); ++i) {
+ const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i);
+ if (shortcut.is_valid() && shortcut->matches_event(p_event)) {
+ // Consume input to avoid conflicts with other plugins.
+ accept_event();
+ _menu_option(options->get_popup()->get_item_id(i));
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ }
+ Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP && (mb->is_command_or_control_pressed())) {
if (mb->is_pressed()) {
@@ -645,14 +741,17 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
input_action = INPUT_NONE;
_update_paste_indicator();
return EditorPlugin::AFTER_GUI_INPUT_STOP;
- } else if (mb->is_shift_pressed() && can_edit) {
+ } else if (mode_buttons_group->get_pressed_button() == select_mode_button && can_edit) {
input_action = INPUT_SELECT;
last_selection = selection;
- } else if (mb->is_command_or_control_pressed() && can_edit) {
+ } else if (mode_buttons_group->get_pressed_button() == pick_mode_button && can_edit) {
input_action = INPUT_PICK;
- } else {
+ } else if (mode_buttons_group->get_pressed_button() == paint_mode_button && can_edit) {
input_action = INPUT_PAINT;
set_items.clear();
+ } else if (mode_buttons_group->get_pressed_button() == erase_mode_button && can_edit) {
+ input_action = INPUT_ERASE;
+ set_items.clear();
}
} else if (mb->get_button_index() == MouseButton::RIGHT) {
if (input_action == INPUT_PASTE) {
@@ -663,9 +762,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
} else if (selection.active) {
_set_selection(false);
return EditorPlugin::AFTER_GUI_INPUT_STOP;
- } else {
- input_action = INPUT_ERASE;
- set_items.clear();
}
} else {
return EditorPlugin::AFTER_GUI_INPUT_PASS;
@@ -676,7 +772,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
return EditorPlugin::AFTER_GUI_INPUT_PASS;
} else {
- if ((mb->get_button_index() == MouseButton::RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_PAINT)) {
+ if ((mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_ERASE) || (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_PAINT)) {
if (set_items.size()) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("GridMap Paint"));
@@ -731,42 +827,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
- Ref<InputEventKey> k = p_event;
-
- if (k.is_valid()) {
- if (k->is_pressed()) {
- if (k->get_keycode() == Key::ESCAPE) {
- if (input_action == INPUT_PASTE) {
- _clear_clipboard_data();
- input_action = INPUT_NONE;
- _update_paste_indicator();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- } else if (selection.active) {
- _set_selection(false);
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- } else {
- selected_palette = -1;
- mesh_library_palette->deselect_all();
- update_palette();
- _update_cursor_instance();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- }
-
- // Consume input to avoid conflicts with other plugins.
- if (k.is_valid() && k->is_pressed() && !k->is_echo()) {
- for (int i = 0; i < options->get_popup()->get_item_count(); ++i) {
- const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i);
- if (shortcut.is_valid() && shortcut->matches_event(p_event)) {
- accept_event();
- _menu_option(options->get_popup()->get_item_id(i));
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- }
- }
- }
- }
-
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
if (pan_gesture->is_alt_pressed() && pan_gesture->is_command_or_control_pressed()) {
@@ -833,11 +893,13 @@ void GridMapEditor::_mesh_library_palette_input(const Ref<InputEvent> &p_ie) {
// Zoom in/out using Ctrl + mouse wheel
if (mb.is_valid() && mb->is_pressed() && mb->is_command_or_control_pressed()) {
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
- size_slider->set_value(size_slider->get_value() + 0.2);
+ zoom_widget->set_zoom(zoom_widget->get_zoom() + 0.2);
+ zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom());
}
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
- size_slider->set_value(size_slider->get_value() - 0.2);
+ zoom_widget->set_zoom(zoom_widget->get_zoom() - 0.2);
+ zoom_widget->emit_signal(SNAME("zoom_changed"), zoom_widget->get_zoom());
}
}
}
@@ -855,9 +917,9 @@ void GridMapEditor::update_palette() {
if (display_mode == DISPLAY_THUMBNAIL) {
mesh_library_palette->set_max_columns(0);
mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_TOP);
- mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5));
+ mesh_library_palette->set_fixed_column_width(min_size * MAX(zoom_widget->get_zoom(), 1.5));
} else if (display_mode == DISPLAY_LIST) {
- mesh_library_palette->set_max_columns(1);
+ mesh_library_palette->set_max_columns(0);
mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_LEFT);
mesh_library_palette->set_fixed_column_width(0);
}
@@ -938,6 +1000,11 @@ void GridMapEditor::_update_mesh_library() {
}
update_palette();
+ // Make sure we select the first tile as default possible.
+ if (mesh_library_palette->get_current() == -1 && mesh_library_palette->get_item_count() > 0) {
+ mesh_library_palette->set_current(0);
+ selected_palette = mesh_library_palette->get_item_metadata(0);
+ }
// Update the cursor and grid in case the library is changed or removed.
_update_cursor_instance();
update_grid();
@@ -1058,10 +1125,22 @@ void GridMapEditor::_draw_grids(const Vector3 &cell_size) {
}
void GridMapEditor::_update_theme() {
- options->set_icon(get_theme_icon(SNAME("GridMap"), EditorStringName(EditorIcons)));
+ transform_mode_button->set_button_icon(get_theme_icon(SNAME("ToolMove"), EditorStringName(EditorIcons)));
+ select_mode_button->set_button_icon(get_theme_icon(SNAME("ToolSelect"), EditorStringName(EditorIcons)));
+ erase_mode_button->set_button_icon(get_theme_icon(SNAME("Eraser"), EditorStringName(EditorIcons)));
+ paint_mode_button->set_button_icon(get_theme_icon(SNAME("Paint"), EditorStringName(EditorIcons)));
+ pick_mode_button->set_button_icon(get_theme_icon(SNAME("ColorPick"), EditorStringName(EditorIcons)));
+ fill_action_button->set_button_icon(get_theme_icon(SNAME("Bucket"), EditorStringName(EditorIcons)));
+ move_action_button->set_button_icon(get_theme_icon(SNAME("ActionCut"), EditorStringName(EditorIcons)));
+ duplicate_action_button->set_button_icon(get_theme_icon(SNAME("ActionCopy"), EditorStringName(EditorIcons)));
+ delete_action_button->set_button_icon(get_theme_icon(SNAME("Clear"), EditorStringName(EditorIcons)));
+ rotate_x_button->set_button_icon(get_theme_icon(SNAME("RotateLeft"), EditorStringName(EditorIcons)));
+ rotate_y_button->set_button_icon(get_theme_icon(SNAME("ToolRotate"), EditorStringName(EditorIcons)));
+ rotate_z_button->set_button_icon(get_theme_icon(SNAME("RotateRight"), EditorStringName(EditorIcons)));
search_box->set_right_icon(get_theme_icon(SNAME("Search"), EditorStringName(EditorIcons)));
- mode_thumbnail->set_icon(get_theme_icon(SNAME("FileThumbnail"), EditorStringName(EditorIcons)));
- mode_list->set_icon(get_theme_icon(SNAME("FileList"), EditorStringName(EditorIcons)));
+ mode_thumbnail->set_button_icon(get_theme_icon(SNAME("FileThumbnail"), EditorStringName(EditorIcons)));
+ mode_list->set_button_icon(get_theme_icon(SNAME("FileList"), EditorStringName(EditorIcons)));
+ options->set_button_icon(get_theme_icon(SNAME("Tools"), EditorStringName(EditorIcons)));
}
void GridMapEditor::_notification(int p_what) {
@@ -1076,6 +1155,9 @@ void GridMapEditor::_notification(int p_what) {
RenderingServer::get_singleton()->instance_set_layer_mask(selection_level_instance[i], 1 << Node3DEditorViewport::MISC_TOOL_LAYER);
}
+ cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
+ RenderingServer::get_singleton()->instance_set_layer_mask(cursor_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER);
+ RenderingServer::get_singleton()->instance_set_visible(cursor_instance, false);
selection_instance = RenderingServer::get_singleton()->instance_create2(selection_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
RenderingServer::get_singleton()->instance_set_layer_mask(selection_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER);
paste_instance = RenderingServer::get_singleton()->instance_create2(paste_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
@@ -1097,8 +1179,10 @@ void GridMapEditor::_notification(int p_what) {
RenderingServer::get_singleton()->free(selection_level_instance[i]);
}
+ RenderingServer::get_singleton()->free(cursor_instance);
RenderingServer::get_singleton()->free(selection_instance);
RenderingServer::get_singleton()->free(paste_instance);
+ cursor_instance = RID();
selection_instance = RID();
paste_instance = RID();
} break;
@@ -1144,15 +1228,32 @@ void GridMapEditor::_update_cursor_instance() {
}
cursor_instance = RID();
- if (selected_palette >= 0) {
- if (node && !node->get_mesh_library().is_null()) {
+ if (mode_buttons_group->get_pressed_button() == paint_mode_button) {
+ if (selected_palette >= 0 && node && node->get_mesh_library().is_valid()) {
Ref<Mesh> mesh = node->get_mesh_library()->get_item_mesh(selected_palette);
if (!mesh.is_null() && mesh->get_rid().is_valid()) {
cursor_instance = RenderingServer::get_singleton()->instance_create2(mesh->get_rid(), get_tree()->get_root()->get_world_3d()->get_scenario());
- RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform);
}
}
+ } else if (mode_buttons_group->get_pressed_button() == select_mode_button) {
+ cursor_inner_mat->set_albedo(Color(default_color, 0.2));
+ cursor_outer_mat->set_albedo(Color(default_color, 0.8));
+ cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
+ } else if (mode_buttons_group->get_pressed_button() == erase_mode_button) {
+ cursor_inner_mat->set_albedo(Color(erase_color, 0.2));
+ cursor_outer_mat->set_albedo(Color(erase_color, 0.8));
+ cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
+ } else if (mode_buttons_group->get_pressed_button() == pick_mode_button) {
+ cursor_inner_mat->set_albedo(Color(pick_color, 0.2));
+ cursor_outer_mat->set_albedo(Color(pick_color, 0.8));
+ cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, get_tree()->get_root()->get_world_3d()->get_scenario());
}
+ _update_cursor_transform();
+}
+
+void GridMapEditor::_on_tool_mode_changed() {
+ _show_viewports_transform_gizmo(mode_buttons_group->get_pressed_button() == transform_mode_button);
+ _update_cursor_instance();
}
void GridMapEditor::_item_selected_cbk(int idx) {
@@ -1182,80 +1283,26 @@ void GridMapEditor::_bind_methods() {
}
GridMapEditor::GridMapEditor() {
- ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::Q, true);
- ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::E, true);
- ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), Key::Z, true);
- ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), Key::X, true);
- ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), Key::C, true);
- ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true);
- ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true);
- ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true);
- ED_SHORTCUT("grid_map/cursor_back_rotate_x", TTR("Cursor Back Rotate X"), KeyModifierMask::SHIFT + Key::A, true);
- ED_SHORTCUT("grid_map/cursor_back_rotate_y", TTR("Cursor Back Rotate Y"), KeyModifierMask::SHIFT + Key::S, true);
- ED_SHORTCUT("grid_map/cursor_back_rotate_z", TTR("Cursor Back Rotate Z"), KeyModifierMask::SHIFT + Key::D, true);
- ED_SHORTCUT("grid_map/cursor_clear_rotation", TTR("Cursor Clear Rotation"), Key::W, true);
- ED_SHORTCUT("grid_map/paste_selects", TTR("Paste Selects"));
- ED_SHORTCUT("grid_map/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CTRL + Key::C);
- ED_SHORTCUT("grid_map/cut_selection", TTR("Cut Selection"), KeyModifierMask::CTRL + Key::X);
- ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE);
- ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F);
-
- int mw = EDITOR_GET("editors/grid_map/palette_min_width");
- Control *ec = memnew(Control);
- ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE);
- add_child(ec);
-
- spatial_editor_hb = memnew(HBoxContainer);
- spatial_editor_hb->set_h_size_flags(SIZE_EXPAND_FILL);
- spatial_editor_hb->set_alignment(BoxContainer::ALIGNMENT_END);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(spatial_editor_hb);
-
- spin_box_label = memnew(Label);
- spin_box_label->set_text(TTR("Floor:"));
- spatial_editor_hb->add_child(spin_box_label);
-
- floor = memnew(SpinBox);
- floor->set_min(-32767);
- floor->set_max(32767);
- floor->set_step(1);
- floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 16);
-
- spatial_editor_hb->add_child(floor);
- floor->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_floor_changed));
- floor->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited));
- floor->get_line_edit()->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited));
-
- spatial_editor_hb->add_child(memnew(VSeparator));
+ ED_SHORTCUT("grid_map/previous_floor", TTR("Previous Floor"), Key::KEY_1, true);
+ ED_SHORTCUT("grid_map/next_floor", TTR("Next Floor"), Key::KEY_3, true);
+ ED_SHORTCUT("grid_map/edit_x_axis", TTR("Edit X Axis"), KeyModifierMask::SHIFT + Key::Z, true);
+ ED_SHORTCUT("grid_map/edit_y_axis", TTR("Edit Y Axis"), KeyModifierMask::SHIFT + Key::X, true);
+ ED_SHORTCUT("grid_map/edit_z_axis", TTR("Edit Z Axis"), KeyModifierMask::SHIFT + Key::C, true);
+ ED_SHORTCUT("grid_map/keep_selected", TTR("Keep Selection"));
+ ED_SHORTCUT("grid_map/clear_rotation", TTR("Clear Rotation"));
options = memnew(MenuButton);
- spatial_editor_hb->add_child(options);
- spatial_editor_hb->hide();
-
- options->set_text(TTR("Grid Map"));
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/previous_floor"), MENU_OPTION_PREV_LEVEL);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/next_floor"), MENU_OPTION_NEXT_LEVEL);
+ options->set_theme_type_variation("FlatButton");
options->get_popup()->add_separator();
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
options->get_popup()->add_separator();
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_x"), MENU_OPTION_CURSOR_ROTATE_X);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_y"), MENU_OPTION_CURSOR_ROTATE_Y);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_rotate_z"), MENU_OPTION_CURSOR_ROTATE_Z);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_x"), MENU_OPTION_CURSOR_BACK_ROTATE_X);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_y"), MENU_OPTION_CURSOR_BACK_ROTATE_Y);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_back_rotate_z"), MENU_OPTION_CURSOR_BACK_ROTATE_Z);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cursor_clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
- options->get_popup()->add_separator();
// TRANSLATORS: This is a toggle to select after pasting the new content.
- options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/paste_selects"), MENU_OPTION_PASTE_SELECTS);
- options->get_popup()->add_separator();
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/duplicate_selection"), MENU_OPTION_SELECTION_DUPLICATE);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/cut_selection"), MENU_OPTION_SELECTION_CUT);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_selection"), MENU_OPTION_SELECTION_CLEAR);
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/fill_selection"), MENU_OPTION_SELECTION_FILL);
-
+ options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
+ options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/keep_selected"), MENU_OPTION_PASTE_SELECTS);
+ options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS), true);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
@@ -1275,40 +1322,187 @@ GridMapEditor::GridMapEditor() {
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GridMapEditor::_menu_option));
- HBoxContainer *hb = memnew(HBoxContainer);
- add_child(hb);
- hb->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbar = memnew(HBoxContainer);
+ add_child(toolbar);
+ toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *mode_buttons = memnew(HBoxContainer);
+ toolbar->add_child(mode_buttons);
+ mode_buttons_group.instantiate();
+
+ transform_mode_button = memnew(Button);
+ transform_mode_button->set_theme_type_variation("FlatButton");
+ transform_mode_button->set_toggle_mode(true);
+ transform_mode_button->set_button_group(mode_buttons_group);
+ transform_mode_button->set_shortcut(ED_SHORTCUT("grid_map/transform_tool", TTR("Transform"), Key::T, true));
+ transform_mode_button->connect(SceneStringName(toggled),
+ callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1));
+ mode_buttons->add_child(transform_mode_button);
+ viewport_shortcut_buttons.push_back(transform_mode_button);
+ VSeparator *vsep = memnew(VSeparator);
+ mode_buttons->add_child(vsep);
+
+ select_mode_button = memnew(Button);
+ select_mode_button->set_theme_type_variation("FlatButton");
+ select_mode_button->set_toggle_mode(true);
+ select_mode_button->set_button_group(mode_buttons_group);
+ select_mode_button->set_shortcut(ED_SHORTCUT("grid_map/selection_tool", TTR("Selection"), Key::Q, true));
+ select_mode_button->connect(SceneStringName(toggled),
+ callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1));
+ mode_buttons->add_child(select_mode_button);
+ viewport_shortcut_buttons.push_back(select_mode_button);
+ select_mode_button->set_pressed(true);
+
+ erase_mode_button = memnew(Button);
+ erase_mode_button->set_theme_type_variation("FlatButton");
+ erase_mode_button->set_toggle_mode(true);
+ erase_mode_button->set_button_group(mode_buttons_group);
+ erase_mode_button->set_shortcut(ED_SHORTCUT("grid_map/erase_tool", TTR("Erase"), Key::W, true));
+ mode_buttons->add_child(erase_mode_button);
+ erase_mode_button->connect(SceneStringName(toggled),
+ callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1));
+ viewport_shortcut_buttons.push_back(erase_mode_button);
+
+ paint_mode_button = memnew(Button);
+ paint_mode_button->set_theme_type_variation("FlatButton");
+ paint_mode_button->set_toggle_mode(true);
+ paint_mode_button->set_button_group(mode_buttons_group);
+ paint_mode_button->set_shortcut(ED_SHORTCUT("grid_map/paint_tool", TTR("Paint"), Key::E, true));
+ paint_mode_button->connect(SceneStringName(toggled),
+ callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1));
+ mode_buttons->add_child(paint_mode_button);
+ viewport_shortcut_buttons.push_back(paint_mode_button);
+
+ pick_mode_button = memnew(Button);
+ pick_mode_button->set_theme_type_variation("FlatButton");
+ pick_mode_button->set_toggle_mode(true);
+ pick_mode_button->set_button_group(mode_buttons_group);
+ pick_mode_button->set_shortcut(ED_SHORTCUT("grid_map/pick_tool", TTR("Pick"), Key::R, true));
+ pick_mode_button->connect(SceneStringName(toggled),
+ callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1));
+ mode_buttons->add_child(pick_mode_button);
+ viewport_shortcut_buttons.push_back(pick_mode_button);
+
+ vsep = memnew(VSeparator);
+ toolbar->add_child(vsep);
+
+ HBoxContainer *action_buttons = memnew(HBoxContainer);
+ toolbar->add_child(action_buttons);
+
+ fill_action_button = memnew(Button);
+ fill_action_button->set_theme_type_variation("FlatButton");
+ fill_action_button->set_shortcut(ED_SHORTCUT("grid_map/fill_tool", TTR("Fill"), Key::Z, true));
+ fill_action_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_FILL));
+ action_buttons->add_child(fill_action_button);
+ viewport_shortcut_buttons.push_back(fill_action_button);
+
+ move_action_button = memnew(Button);
+ move_action_button->set_theme_type_variation("FlatButton");
+ move_action_button->set_shortcut(ED_SHORTCUT("grid_map/move_tool", TTR("Move"), Key::X, true));
+ move_action_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CUT));
+ action_buttons->add_child(move_action_button);
+ viewport_shortcut_buttons.push_back(move_action_button);
+
+ duplicate_action_button = memnew(Button);
+ duplicate_action_button->set_theme_type_variation("FlatButton");
+ duplicate_action_button->set_shortcut(ED_SHORTCUT("grid_map/duplicate_tool", TTR("Duplicate"), Key::C, true));
+ duplicate_action_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_DUPLICATE));
+ action_buttons->add_child(duplicate_action_button);
+ viewport_shortcut_buttons.push_back(duplicate_action_button);
+
+ delete_action_button = memnew(Button);
+ delete_action_button->set_theme_type_variation("FlatButton");
+ delete_action_button->set_shortcut(ED_SHORTCUT("grid_map/delete_tool", TTR("Delete"), Key::V, true));
+ delete_action_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_SELECTION_CLEAR));
+ action_buttons->add_child(delete_action_button);
+ viewport_shortcut_buttons.push_back(delete_action_button);
+
+ vsep = memnew(VSeparator);
+ toolbar->add_child(vsep);
+
+ HBoxContainer *rotation_buttons = memnew(HBoxContainer);
+ toolbar->add_child(rotation_buttons);
+
+ rotate_x_button = memnew(Button);
+ rotate_x_button->set_theme_type_variation("FlatButton");
+ rotate_x_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_x", TTR("Cursor Rotate X"), Key::A, true));
+ rotate_x_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_X));
+ rotation_buttons->add_child(rotate_x_button);
+ viewport_shortcut_buttons.push_back(rotate_x_button);
+
+ rotate_y_button = memnew(Button);
+ rotate_y_button->set_theme_type_variation("FlatButton");
+ rotate_y_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_y", TTR("Cursor Rotate Y"), Key::S, true));
+ rotate_y_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Y));
+ rotation_buttons->add_child(rotate_y_button);
+ viewport_shortcut_buttons.push_back(rotate_y_button);
+
+ rotate_z_button = memnew(Button);
+ rotate_z_button->set_theme_type_variation("FlatButton");
+ rotate_z_button->set_shortcut(ED_SHORTCUT("grid_map/cursor_rotate_z", TTR("Cursor Rotate Z"), Key::D, true));
+ rotate_z_button->connect(SceneStringName(pressed),
+ callable_mp(this, &GridMapEditor::_menu_option).bind(MENU_OPTION_CURSOR_ROTATE_Z));
+ rotation_buttons->add_child(rotate_z_button);
+ viewport_shortcut_buttons.push_back(rotate_z_button);
+
+ // Wide empty separation control. (like BoxContainer::add_spacer())
+ Control *c = memnew(Control);
+ c->set_mouse_filter(MOUSE_FILTER_PASS);
+ c->set_h_size_flags(SIZE_EXPAND_FILL);
+ toolbar->add_child(c);
+
+ floor = memnew(SpinBox);
+ floor->set_min(-32767);
+ floor->set_max(32767);
+ floor->set_step(1);
+ floor->set_tooltip_text(
+ vformat(TTR("Change Grid Floor:\nPrevious Plane (%s)\nNext Plane (%s)"),
+ ED_GET_SHORTCUT("grid_map/previous_floor")->get_as_text(),
+ ED_GET_SHORTCUT("grid_map/next_floor")->get_as_text()));
+ toolbar->add_child(floor);
+ floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 2);
+ floor->get_line_edit()->set_context_menu_enabled(false);
+ floor->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_floor_changed));
+ floor->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited));
+ floor->get_line_edit()->connect(SceneStringName(mouse_exited), callable_mp(this, &GridMapEditor::_floor_mouse_exited));
search_box = memnew(LineEdit);
- search_box->set_h_size_flags(SIZE_EXPAND_FILL);
+ search_box->add_theme_constant_override("minimum_character_width", 10);
search_box->set_placeholder(TTR("Filter Meshes"));
search_box->set_clear_button_enabled(true);
- hb->add_child(search_box);
+ toolbar->add_child(search_box);
search_box->connect(SceneStringName(text_changed), callable_mp(this, &GridMapEditor::_text_changed));
search_box->connect(SceneStringName(gui_input), callable_mp(this, &GridMapEditor::_sbox_input));
+ zoom_widget = memnew(EditorZoomWidget);
+ toolbar->add_child(zoom_widget);
+ zoom_widget->setup_zoom_limits(0.2, 4);
+ zoom_widget->set_zoom(1.0);
+ zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
+ zoom_widget->connect("zoom_changed", callable_mp(this, &GridMapEditor::_icon_size_changed));
+ zoom_widget->set_shortcut_context(this);
+
mode_thumbnail = memnew(Button);
mode_thumbnail->set_theme_type_variation("FlatButton");
mode_thumbnail->set_toggle_mode(true);
mode_thumbnail->set_pressed(true);
- hb->add_child(mode_thumbnail);
+ toolbar->add_child(mode_thumbnail);
mode_thumbnail->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_THUMBNAIL));
mode_list = memnew(Button);
mode_list->set_theme_type_variation("FlatButton");
mode_list->set_toggle_mode(true);
mode_list->set_pressed(false);
- hb->add_child(mode_list);
+ toolbar->add_child(mode_list);
mode_list->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_LIST));
- size_slider = memnew(HSlider);
- size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
- size_slider->set_min(0.2f);
- size_slider->set_max(4.0f);
- size_slider->set_step(0.1f);
- size_slider->set_value(1.0f);
- size_slider->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_icon_size_changed));
- add_child(size_slider);
+ toolbar->add_child(options);
mesh_library_palette = memnew(ItemList);
mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
@@ -1330,6 +1524,7 @@ GridMapEditor::GridMapEditor() {
edit_floor[1] = -1;
edit_floor[2] = -1;
+ cursor_mesh = RenderingServer::get_singleton()->mesh_create();
selection_mesh = RenderingServer::get_singleton()->mesh_create();
paste_mesh = RenderingServer::get_singleton()->mesh_create();
@@ -1405,20 +1600,32 @@ GridMapEditor::GridMapEditor() {
Array d;
d.resize(RS::ARRAY_MAX);
+ default_color = Color(0.0, 0.565, 1.0); // blue 0.7, 0.7, 1.0
+ erase_color = Color(1.0, 0.2, 0.2); // red
+ pick_color = Color(1, 0.7, 0); // orange/yellow
+
+ cursor_inner_mat.instantiate();
+ cursor_inner_mat->set_albedo(Color(default_color, 0.2));
+ cursor_inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ cursor_inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ cursor_inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ cursor_outer_mat.instantiate();
+ cursor_outer_mat->set_albedo(Color(default_color, 0.8));
+ cursor_outer_mat->set_on_top_of_alpha();
+ cursor_outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ cursor_outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ cursor_outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+
inner_mat.instantiate();
- inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2));
+ inner_mat->set_albedo(Color(default_color, 0.2));
inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
- d[RS::ARRAY_VERTEX] = triangles;
- RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_TRIANGLES, d);
- RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 0, inner_mat->get_rid());
-
outer_mat.instantiate();
- outer_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.8));
+ outer_mat->set_albedo(Color(default_color, 0.8));
outer_mat->set_on_top_of_alpha();
-
outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
@@ -1429,6 +1636,18 @@ GridMapEditor::GridMapEditor() {
selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ d[RS::ARRAY_VERTEX] = triangles;
+ RenderingServer::get_singleton()->mesh_add_surface_from_arrays(cursor_mesh, RS::PRIMITIVE_TRIANGLES, d);
+ RenderingServer::get_singleton()->mesh_surface_set_material(cursor_mesh, 0, cursor_inner_mat->get_rid());
+
+ d[RS::ARRAY_VERTEX] = lines;
+ RenderingServer::get_singleton()->mesh_add_surface_from_arrays(cursor_mesh, RS::PRIMITIVE_LINES, d);
+ RenderingServer::get_singleton()->mesh_surface_set_material(cursor_mesh, 1, cursor_outer_mat->get_rid());
+
+ d[RS::ARRAY_VERTEX] = triangles;
+ RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_TRIANGLES, d);
+ RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 0, inner_mat->get_rid());
+
d[RS::ARRAY_VERTEX] = lines;
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d);
RenderingServer::get_singleton()->mesh_surface_set_material(selection_mesh, 1, outer_mat->get_rid());
@@ -1471,9 +1690,6 @@ GridMapEditor::~GridMapEditor() {
if (grid_instance[i].is_valid()) {
RenderingServer::get_singleton()->free(grid_instance[i]);
}
- if (cursor_instance.is_valid()) {
- RenderingServer::get_singleton()->free(cursor_instance);
- }
if (selection_level_instance[i].is_valid()) {
RenderingServer::get_singleton()->free(selection_level_instance[i]);
}
@@ -1482,6 +1698,11 @@ GridMapEditor::~GridMapEditor() {
}
}
+ RenderingServer::get_singleton()->free(cursor_mesh);
+ if (cursor_instance.is_valid()) {
+ RenderingServer::get_singleton()->free(cursor_instance);
+ }
+
RenderingServer::get_singleton()->free(selection_mesh);
if (selection_instance.is_valid()) {
RenderingServer::get_singleton()->free(selection_instance);
@@ -1493,24 +1714,6 @@ GridMapEditor::~GridMapEditor() {
}
}
-void GridMapEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
- if (!EditorSettings::get_singleton()->check_changed_settings_in_group("editors/grid_map")) {
- break;
- }
- switch ((int)EDITOR_GET("editors/grid_map/editor_side")) {
- case 0: { // Left.
- Node3DEditor::get_singleton()->move_control_to_left_panel(grid_map_editor);
- } break;
- case 1: { // Right.
- Node3DEditor::get_singleton()->move_control_to_right_panel(grid_map_editor);
- } break;
- }
- } break;
- }
-}
-
void GridMapEditorPlugin::edit(Object *p_object) {
grid_map_editor->edit(Object::cast_to<GridMap>(p_object));
}
@@ -1521,27 +1724,29 @@ bool GridMapEditorPlugin::handles(Object *p_object) const {
void GridMapEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
- grid_map_editor->show();
- grid_map_editor->spatial_editor_hb->show();
+ grid_map_editor->_on_tool_mode_changed();
+ panel_button->show();
+ EditorNode::get_bottom_panel()->make_item_visible(grid_map_editor);
grid_map_editor->set_process(true);
} else {
- grid_map_editor->spatial_editor_hb->hide();
- grid_map_editor->hide();
+ grid_map_editor->_show_viewports_transform_gizmo(true);
+ panel_button->hide();
+ if (grid_map_editor->is_visible_in_tree()) {
+ EditorNode::get_bottom_panel()->hide_bottom_panel();
+ }
grid_map_editor->set_process(false);
}
}
GridMapEditorPlugin::GridMapEditorPlugin() {
grid_map_editor = memnew(GridMapEditor);
- switch ((int)EDITOR_GET("editors/grid_map/editor_side")) {
- case 0: { // Left.
- Node3DEditor::get_singleton()->add_control_to_left_panel(grid_map_editor);
- } break;
- case 1: { // Right.
- Node3DEditor::get_singleton()->add_control_to_right_panel(grid_map_editor);
- } break;
- }
+ grid_map_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ grid_map_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ grid_map_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
grid_map_editor->hide();
+
+ panel_button = EditorNode::get_bottom_panel()->add_item(TTR("GridMap"), grid_map_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_grid_map_bottom_panel", TTR("Toggle GridMap Bottom Panel")));
+ panel_button->hide();
}
GridMapEditorPlugin::~GridMapEditorPlugin() {
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index 4294c93c93..2d43a5c830 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -44,6 +44,9 @@
class ConfirmationDialog;
class MenuButton;
class Node3DEditorPlugin;
+class ButtonGroup;
+class EditorZoomWidget;
+class BaseButton;
class GridMapEditor : public VBoxContainer {
GDCLASS(GridMapEditor, VBoxContainer);
@@ -54,6 +57,7 @@ class GridMapEditor : public VBoxContainer {
enum InputAction {
INPUT_NONE,
+ INPUT_TRANSFORM,
INPUT_PAINT,
INPUT_ERASE,
INPUT_PICK,
@@ -71,11 +75,31 @@ class GridMapEditor : public VBoxContainer {
MenuButton *options = nullptr;
SpinBox *floor = nullptr;
double accumulated_floor_delta = 0.0;
+
+ HBoxContainer *toolbar = nullptr;
+ List<BaseButton *> viewport_shortcut_buttons;
+ Ref<ButtonGroup> mode_buttons_group;
+ // mode
+ Button *transform_mode_button = nullptr;
+ Button *select_mode_button = nullptr;
+ Button *erase_mode_button = nullptr;
+ Button *paint_mode_button = nullptr;
+ Button *pick_mode_button = nullptr;
+ // action
+ Button *fill_action_button = nullptr;
+ Button *move_action_button = nullptr;
+ Button *duplicate_action_button = nullptr;
+ Button *delete_action_button = nullptr;
+ // rotation
+ Button *rotate_x_button = nullptr;
+ Button *rotate_y_button = nullptr;
+ Button *rotate_z_button = nullptr;
+
+ EditorZoomWidget *zoom_widget = nullptr;
Button *mode_thumbnail = nullptr;
Button *mode_list = nullptr;
LineEdit *search_box = nullptr;
HSlider *size_slider = nullptr;
- HBoxContainer *spatial_editor_hb = nullptr;
ConfirmationDialog *settings_dialog = nullptr;
VBoxContainer *settings_vbc = nullptr;
SpinBox *settings_pick_distance = nullptr;
@@ -102,6 +126,7 @@ class GridMapEditor : public VBoxContainer {
RID grid[3];
RID grid_instance[3];
+ RID cursor_mesh;
RID cursor_instance;
RID selection_mesh;
RID selection_instance;
@@ -119,7 +144,12 @@ class GridMapEditor : public VBoxContainer {
List<ClipboardItem> clipboard_items;
+ Color default_color;
+ Color erase_color;
+ Color pick_color;
Ref<StandardMaterial3D> indicator_mat;
+ Ref<StandardMaterial3D> cursor_inner_mat;
+ Ref<StandardMaterial3D> cursor_outer_mat;
Ref<StandardMaterial3D> inner_mat;
Ref<StandardMaterial3D> outer_mat;
Ref<StandardMaterial3D> selection_floor_mat;
@@ -196,6 +226,7 @@ class GridMapEditor : public VBoxContainer {
void _item_selected_cbk(int idx);
void _update_cursor_transform();
void _update_cursor_instance();
+ void _on_tool_mode_changed();
void _update_theme();
void _text_changed(const String &p_text);
@@ -208,6 +239,7 @@ class GridMapEditor : public VBoxContainer {
void _set_clipboard_data();
void _update_paste_indicator();
void _do_paste();
+ void _show_viewports_transform_gizmo(bool p_value);
void _update_selection_transform();
void _validate_selection();
void _set_selection(bool p_active, const Vector3 &p_begin = Vector3(), const Vector3 &p_end = Vector3());
@@ -238,9 +270,7 @@ class GridMapEditorPlugin : public EditorPlugin {
GDCLASS(GridMapEditorPlugin, EditorPlugin);
GridMapEditor *grid_map_editor = nullptr;
-
-protected:
- void _notification(int p_what);
+ Button *panel_button = nullptr;
public:
virtual EditorPlugin::AfterGUIInput forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp
index 71171be3f1..0588ba034a 100644
--- a/modules/gridmap/grid_map.cpp
+++ b/modules/gridmap/grid_map.cpp
@@ -801,8 +801,8 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) {
if (!g.navigation_debug_edge_connections_instance.is_valid()) {
g.navigation_debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!g.navigation_debug_edge_connections_mesh.is_valid()) {
- g.navigation_debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (g.navigation_debug_edge_connections_mesh.is_null()) {
+ g.navigation_debug_edge_connections_mesh.instantiate();
}
_update_octant_navigation_debug_edge_connections_mesh(p_key);
@@ -1386,8 +1386,8 @@ void GridMap::_update_octant_navigation_debug_edge_connections_mesh(const Octant
g.navigation_debug_edge_connections_instance = RenderingServer::get_singleton()->instance_create();
}
- if (!g.navigation_debug_edge_connections_mesh.is_valid()) {
- g.navigation_debug_edge_connections_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ if (g.navigation_debug_edge_connections_mesh.is_null()) {
+ g.navigation_debug_edge_connections_mesh.instantiate();
}
g.navigation_debug_edge_connections_mesh->clear_surfaces();
diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
index 17448724d1..d47d6ecde6 100644
--- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
+++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml
@@ -119,7 +119,7 @@
<param index="0" name="from_clip" type="int" />
<param index="1" name="to_clip" type="int" />
<description>
- Return true if a given transition exists (was added via [method add_transition]).
+ Returns [code]true[/code] if a given transition exists (was added via [method add_transition]).
</description>
</method>
<method name="is_transition_holding_previous" qualifiers="const">
diff --git a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
index fcb477995f..a29646e4a3 100644
--- a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
+++ b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
@@ -396,7 +396,7 @@ void EditorInspectorPluginAudioStreamInteractive::_edit(Object *p_object) {
void EditorInspectorPluginAudioStreamInteractive::parse_end(Object *p_object) {
if (Object::cast_to<AudioStreamInteractive>(p_object)) {
Button *button = EditorInspector::create_inspector_action_button(TTR("Edit Transitions"));
- button->set_icon(audio_stream_interactive_transition_editor->get_editor_theme_icon(SNAME("Blend")));
+ button->set_button_icon(audio_stream_interactive_transition_editor->get_editor_theme_icon(SNAME("Blend")));
button->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorPluginAudioStreamInteractive::_edit).bind(p_object));
add_custom_control(button);
}
diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h
index 5e1da550d7..d24f00723f 100644
--- a/modules/mbedtls/crypto_mbedtls.h
+++ b/modules/mbedtls/crypto_mbedtls.h
@@ -57,7 +57,7 @@ public:
virtual Error save(const String &p_path, bool p_public_only);
virtual String save_to_string(bool p_public_only);
virtual Error load_from_string(const String &p_string_key, bool p_public_only);
- virtual bool is_public_only() const { return public_only; };
+ virtual bool is_public_only() const { return public_only; }
CryptoKeyMbedTLS() {
mbedtls_pk_init(&pkey);
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index b4200410fb..3af66f6d83 100644
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -166,21 +166,24 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in
return OK;
}
- int ret = mbedtls_ssl_write(tls_ctx->get_context(), p_data, p_bytes);
- if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
- // Non blocking IO
- ret = 0;
- } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
- // Clean close
- disconnect_from_stream();
- return ERR_FILE_EOF;
- } else if (ret <= 0) {
- TLSContextMbedTLS::print_mbedtls_error(ret);
- disconnect_from_stream();
- return ERR_CONNECTION_ERROR;
- }
+ do {
+ int ret = mbedtls_ssl_write(tls_ctx->get_context(), &p_data[r_sent], p_bytes - r_sent);
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ // Non blocking IO.
+ break;
+ } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ // Clean close
+ disconnect_from_stream();
+ return ERR_FILE_EOF;
+ } else if (ret <= 0) {
+ TLSContextMbedTLS::print_mbedtls_error(ret);
+ disconnect_from_stream();
+ return ERR_CONNECTION_ERROR;
+ }
+ r_sent += ret;
+
+ } while (r_sent < p_bytes);
- r_sent = ret;
return OK;
}
@@ -209,20 +212,25 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r
r_received = 0;
- int ret = mbedtls_ssl_read(tls_ctx->get_context(), p_buffer, p_bytes);
- if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
- ret = 0; // non blocking io
- } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
- // Clean close
- disconnect_from_stream();
- return ERR_FILE_EOF;
- } else if (ret <= 0) {
- TLSContextMbedTLS::print_mbedtls_error(ret);
- disconnect_from_stream();
- return ERR_CONNECTION_ERROR;
- }
+ do {
+ int ret = mbedtls_ssl_read(tls_ctx->get_context(), &p_buffer[r_received], p_bytes - r_received);
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ // Non blocking IO.
+ break;
+ } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ // Clean close
+ disconnect_from_stream();
+ return ERR_FILE_EOF;
+ } else if (ret <= 0) {
+ TLSContextMbedTLS::print_mbedtls_error(ret);
+ disconnect_from_stream();
+ return ERR_CONNECTION_ERROR;
+ }
+
+ r_received += ret;
+
+ } while (r_received < p_bytes);
- r_received = ret;
return OK;
}
diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp
index 781f928f66..ebfe5d9c5b 100644
--- a/modules/meshoptimizer/register_types.cpp
+++ b/modules/meshoptimizer/register_types.cpp
@@ -40,10 +40,10 @@ void initialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
}
SurfaceTool::optimize_vertex_cache_func = meshopt_optimizeVertexCache;
+ SurfaceTool::optimize_vertex_fetch_remap_func = meshopt_optimizeVertexFetchRemap;
SurfaceTool::simplify_func = meshopt_simplify;
SurfaceTool::simplify_with_attrib_func = meshopt_simplifyWithAttributes;
SurfaceTool::simplify_scale_func = meshopt_simplifyScale;
- SurfaceTool::simplify_sloppy_func = meshopt_simplifySloppy;
SurfaceTool::generate_remap_func = meshopt_generateVertexRemap;
SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer;
SurfaceTool::remap_index_func = meshopt_remapIndexBuffer;
@@ -55,9 +55,9 @@ void uninitialize_meshoptimizer_module(ModuleInitializationLevel p_level) {
}
SurfaceTool::optimize_vertex_cache_func = nullptr;
+ SurfaceTool::optimize_vertex_fetch_remap_func = nullptr;
SurfaceTool::simplify_func = nullptr;
SurfaceTool::simplify_scale_func = nullptr;
- SurfaceTool::simplify_sloppy_func = nullptr;
SurfaceTool::generate_remap_func = nullptr;
SurfaceTool::remap_vertex_func = nullptr;
SurfaceTool::remap_index_func = nullptr;
diff --git a/modules/minimp3/doc_classes/ResourceImporterMP3.xml b/modules/minimp3/doc_classes/ResourceImporterMP3.xml
index 72868623c7..fc0ec3682b 100644
--- a/modules/minimp3/doc_classes/ResourceImporterMP3.xml
+++ b/modules/minimp3/doc_classes/ResourceImporterMP3.xml
@@ -13,15 +13,15 @@
</tutorials>
<members>
<member name="bar_beats" type="int" setter="" getter="" default="4">
- The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member bar_beats] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="beat_count" type="int" setter="" getter="" default="0">
- The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member beat_count] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="bpm" type="float" setter="" getter="" default="0">
- The Beats Per Minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The beats per minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member bpm] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="loop" type="bool" setter="" getter="" default="false">
diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp
index e4b54ef050..f1f0a771ad 100644
--- a/modules/minimp3/resource_importer_mp3.cpp
+++ b/modules/minimp3/resource_importer_mp3.cpp
@@ -115,7 +115,7 @@ Ref<AudioStreamMP3> ResourceImporterMP3::import_mp3(const String &p_path) {
return mp3_stream;
}
-Error ResourceImporterMP3::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+Error ResourceImporterMP3::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
bool loop = p_options["loop"];
float loop_offset = p_options["loop_offset"];
double bpm = p_options["bpm"];
diff --git a/modules/minimp3/resource_importer_mp3.h b/modules/minimp3/resource_importer_mp3.h
index 2df44deaea..35cc761eb4 100644
--- a/modules/minimp3/resource_importer_mp3.h
+++ b/modules/minimp3/resource_importer_mp3.h
@@ -57,7 +57,9 @@ public:
#endif
static Ref<AudioStreamMP3> import_mp3(const String &p_path);
- virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ virtual bool can_import_threaded() const override { return true; }
ResourceImporterMP3();
};
diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp
index f27281866a..e5f1a940ec 100644
--- a/modules/mobile_vr/mobile_vr_interface.cpp
+++ b/modules/mobile_vr/mobile_vr_interface.cpp
@@ -37,11 +37,11 @@
StringName MobileVRInterface::get_name() const {
return "Native mobile";
-};
+}
uint32_t MobileVRInterface::get_capabilities() const {
return XRInterface::XR_STEREO;
-};
+}
Vector3 MobileVRInterface::scale_magneto(const Vector3 &p_magnetometer) {
// Our magnetometer doesn't give us nice clean data.
@@ -98,7 +98,7 @@ Vector3 MobileVRInterface::scale_magneto(const Vector3 &p_magnetometer) {
};
return mag_scaled;
-};
+}
Basis MobileVRInterface::combine_acc_mag(const Vector3 &p_grav, const Vector3 &p_magneto) {
// yup, stock standard cross product solution...
@@ -117,7 +117,7 @@ Basis MobileVRInterface::combine_acc_mag(const Vector3 &p_grav, const Vector3 &p
acc_mag_m3.rows[2] = magneto;
return acc_mag_m3;
-};
+}
void MobileVRInterface::set_position_from_sensors() {
_THREAD_SAFE_METHOD_
@@ -215,7 +215,7 @@ void MobileVRInterface::set_position_from_sensors() {
head_transform.basis = orientation.orthonormalized();
last_ticks = ticks;
-};
+}
void MobileVRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_eye_height", "eye_height"), &MobileVRInterface::set_eye_height);
@@ -280,51 +280,51 @@ Rect2 MobileVRInterface::get_offset_rect() const {
void MobileVRInterface::set_iod(const double p_iod) {
intraocular_dist = p_iod;
-};
+}
double MobileVRInterface::get_iod() const {
return intraocular_dist;
-};
+}
void MobileVRInterface::set_display_width(const double p_display_width) {
display_width = p_display_width;
-};
+}
double MobileVRInterface::get_display_width() const {
return display_width;
-};
+}
void MobileVRInterface::set_display_to_lens(const double p_display_to_lens) {
display_to_lens = p_display_to_lens;
-};
+}
double MobileVRInterface::get_display_to_lens() const {
return display_to_lens;
-};
+}
void MobileVRInterface::set_oversample(const double p_oversample) {
oversample = p_oversample;
-};
+}
double MobileVRInterface::get_oversample() const {
return oversample;
-};
+}
void MobileVRInterface::set_k1(const double p_k1) {
k1 = p_k1;
-};
+}
double MobileVRInterface::get_k1() const {
return k1;
-};
+}
void MobileVRInterface::set_k2(const double p_k2) {
k2 = p_k2;
-};
+}
double MobileVRInterface::get_k2() const {
return k2;
-};
+}
float MobileVRInterface::get_vrs_min_radius() const {
return xr_vrs.get_vrs_min_radius();
@@ -345,7 +345,7 @@ void MobileVRInterface::set_vrs_strength(float p_vrs_strength) {
uint32_t MobileVRInterface::get_view_count() {
// needs stereo...
return 2;
-};
+}
XRInterface::TrackingStatus MobileVRInterface::get_tracking_status() const {
return tracking_state;
@@ -353,7 +353,7 @@ XRInterface::TrackingStatus MobileVRInterface::get_tracking_status() const {
bool MobileVRInterface::is_initialized() const {
return (initialized);
-};
+}
bool MobileVRInterface::initialize() {
XRServer *xr_server = XRServer::get_singleton();
@@ -387,7 +387,7 @@ bool MobileVRInterface::initialize() {
};
return true;
-};
+}
void MobileVRInterface::uninitialize() {
if (initialized) {
@@ -408,7 +408,7 @@ void MobileVRInterface::uninitialize() {
initialized = false;
};
-};
+}
Dictionary MobileVRInterface::get_system_info() {
Dictionary dict;
@@ -442,7 +442,7 @@ Size2 MobileVRInterface::get_render_target_size() {
target_size.y *= oversample;
return target_size;
-};
+}
Transform3D MobileVRInterface::get_camera_transform() {
_THREAD_SAFE_METHOD_
@@ -463,7 +463,7 @@ Transform3D MobileVRInterface::get_camera_transform() {
}
return transform_for_eye;
-};
+}
Transform3D MobileVRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
_THREAD_SAFE_METHOD_
@@ -497,7 +497,7 @@ Transform3D MobileVRInterface::get_transform_for_view(uint32_t p_view, const Tra
};
return transform_for_eye;
-};
+}
Projection MobileVRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
_THREAD_SAFE_METHOD_
@@ -508,7 +508,7 @@ Projection MobileVRInterface::get_projection_for_view(uint32_t p_view, double p_
eye.set_for_hmd(p_view + 1, p_aspect, intraocular_dist, display_width, display_to_lens, oversample, p_z_near, p_z_far);
return eye;
-};
+}
Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
_THREAD_SAFE_METHOD_
@@ -571,7 +571,7 @@ void MobileVRInterface::process() {
head->set_pose("default", head_transform, Vector3(), Vector3(), tracking_confidence);
}
};
-};
+}
RID MobileVRInterface::get_vrs_texture() {
PackedVector2Array eye_foci;
@@ -597,4 +597,4 @@ MobileVRInterface::~MobileVRInterface() {
if (is_initialized()) {
uninitialize();
};
-};
+}
diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h
index 490b1c393c..d054ef14b3 100644
--- a/modules/mobile_vr/mobile_vr_interface.h
+++ b/modules/mobile_vr/mobile_vr_interface.h
@@ -97,19 +97,19 @@ private:
float floor_decimals(const float p_value, const float p_decimals) {
float power_of_10 = pow(10.0f, p_decimals);
return floor(p_value * power_of_10) / power_of_10;
- };
+ }
Vector3 floor_decimals(const Vector3 &p_vector, const float p_decimals) {
return Vector3(floor_decimals(p_vector.x, p_decimals), floor_decimals(p_vector.y, p_decimals), floor_decimals(p_vector.z, p_decimals));
- };
+ }
Vector3 low_pass(const Vector3 &p_vector, const Vector3 &p_last_vector, const float p_factor) {
return p_vector + (p_factor * (p_last_vector - p_vector));
- };
+ }
Vector3 scrub(const Vector3 &p_vector, const Vector3 &p_last_vector, const float p_decimals, const float p_factor) {
return low_pass(floor_decimals(p_vector, p_decimals), p_last_vector, p_factor);
- };
+ }
void set_position_from_sensors();
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 3d12994469..ec9c123f8d 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -500,8 +500,8 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info()
}
_recursion_flag_ = true;
SCOPE_EXIT {
- _recursion_flag_ = false;
- };
+ _recursion_flag_ = false; // clang-format off
+ }; // clang-format on
if (!gdmono || !gdmono->is_runtime_initialized()) {
return Vector<StackInfo>();
@@ -2817,7 +2817,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
if (p_path.begins_with("csharp://")) {
// This is a virtual path used by generic types, extract the real path.
real_path = "res://" + p_path.trim_prefix("csharp://");
- real_path = real_path.substr(0, real_path.rfind(":"));
+ real_path = real_path.substr(0, real_path.rfind_char(':'));
}
Ref<CSharpScript> scr;
@@ -2826,7 +2826,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
} else {
- scr = Ref<CSharpScript>(memnew(CSharpScript));
+ scr.instantiate();
}
#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
index 188972e6fe..5af859c06b 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
@@ -32,9 +32,9 @@ partial class EventSignals
add => backing_MySignal += value;
remove => backing_MySignal -= value;
}
- protected void OnMySignal(string str, int num)
+ protected void EmitSignalMySignal(string @str, int @num)
{
- EmitSignal(SignalName.MySignal, str, num);
+ EmitSignal(SignalName.MySignal, @str, @num);
}
/// <inheritdoc/>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
index c734dc7be1..ffde135930 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
index 0de840aa34..94d447f61a 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
index 053ae36ae7..5654060d38 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
@@ -395,6 +395,11 @@ public class MustBeVariantAnnotatedMethods
public void MethodWithWrongAttribute()
{
}
+
+ [NestedGenericTypeAttributeContainer.NestedGenericTypeAttribute<bool>()]
+ public void MethodWithNestedAttribute()
+ {
+ }
}
[GenericTypeAttribute<bool>()]
@@ -657,3 +662,11 @@ public class ClassNonVariantAnnotated
public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
{
}
+
+public class NestedGenericTypeAttributeContainer
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class NestedGenericTypeAttribute<[MustBeVariant] T> : Attribute
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
index e894e7a86c..a72a8c5880 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
@@ -135,7 +135,7 @@ namespace Godot.SourceGenerators
{
ITypeParameterSymbol? typeParamSymbol = parentSymbol switch
{
- IMethodSymbol methodSymbol when parentSyntax.Parent is AttributeSyntax &&
+ IMethodSymbol methodSymbol when parentSyntax.Ancestors().Any(s => s is AttributeSyntax) &&
methodSymbol.ContainingType.TypeParameters.Length > 0
=> methodSymbol.ContainingType.TypeParameters[typeArgumentIndex],
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index eb9dc35255..ed78353f92 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -447,25 +447,10 @@ namespace Godot.SourceGenerators
if (exportAttr != null && propertySymbol != null)
{
- if (propertySymbol.GetMethod == null)
+ if (propertySymbol.GetMethod == null || propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
{
- // This should never happen, as we filtered WriteOnly properties, but just in case.
- context.ReportDiagnostic(Diagnostic.Create(
- Common.ExportedPropertyIsWriteOnlyRule,
- propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
- propertySymbol.ToDisplayString()
- ));
- return null;
- }
-
- if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
- {
- // This should never happen, as we filtered ReadOnly properties, but just in case.
- context.ReportDiagnostic(Diagnostic.Create(
- Common.ExportedMemberIsReadOnlyRule,
- propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
- propertySymbol.ToDisplayString()
- ));
+ // Exports can be neither read-only nor write-only but the diagnostic errors for properties are already
+ // reported by ScriptPropertyDefValGenerator.cs so just quit early here.
return null;
}
}
@@ -852,7 +837,7 @@ namespace Godot.SourceGenerators
}
}
- hint = PropertyHint.DictionaryType;
+ hint = PropertyHint.TypeString;
hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
return hintString != null;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
index 0dda43ab4c..c7a7415851 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -282,7 +282,7 @@ namespace Godot.SourceGenerators
.Append(" -= value;\n")
.Append("}\n");
- // Generate On{EventName} method to raise the event
+ // Generate EmitSignal{EventName} method to raise the event
var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
int paramCount = invokeMethodSymbol.Parameters.Length;
@@ -291,11 +291,11 @@ namespace Godot.SourceGenerators
"private" :
"protected";
- source.Append($" {raiseMethodModifiers} void On{signalName}(");
+ source.Append($" {raiseMethodModifiers} void EmitSignal{signalName}(");
for (int i = 0; i < paramCount; i++)
{
var paramSymbol = invokeMethodSymbol.Parameters[i];
- source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} {paramSymbol.Name}");
+ source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} @{paramSymbol.Name}");
if (i < paramCount - 1)
{
source.Append(", ");
@@ -310,11 +310,11 @@ namespace Godot.SourceGenerators
if (paramSymbol.Type.TypeKind == TypeKind.Enum)
{
var underlyingType = ((INamedTypeSymbol)paramSymbol.Type).EnumUnderlyingType;
- source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()}){paramSymbol.Name}");
+ source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()})@{paramSymbol.Name}");
continue;
}
- source.Append($", {paramSymbol.Name}");
+ source.Append($", @{paramSymbol.Name}");
}
source.Append(");\n");
source.Append(" }\n");
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 788b46ab9a..74e04b46a1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -177,7 +177,7 @@ namespace GodotTools
private static readonly string[] VsCodeNames =
{
- "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
+ "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss", "codium"
};
[UsedImplicitly]
@@ -330,7 +330,7 @@ namespace GodotTools
args.Add("-b");
args.Add(vscodeBundleId);
- // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
+ // The reusing of existing windows made by the 'open' command might not choose a window that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.Add("-n");
@@ -339,6 +339,28 @@ namespace GodotTools
args.Add("--args");
}
+
+ // Try VSCodium as a fallback if Visual Studio Code can't be found.
+ if (!macOSAppBundleInstalled)
+ {
+ const string VscodiumBundleId = "com.vscodium.codium";
+ macOSAppBundleInstalled = Internal.IsMacOSAppBundleInstalled(VscodiumBundleId);
+
+ if (macOSAppBundleInstalled)
+ {
+ args.Add("-b");
+ args.Add(VscodiumBundleId);
+
+ // The reusing of existing windows made by the 'open' command might not choose a window that is
+ // editing our folder. It's better to ask for a new window and let VSCode do the window management.
+ args.Add("-n");
+
+ // The open process must wait until the application finishes (which is instant in VSCode's case)
+ args.Add("--wait-apps");
+
+ args.Add("--args");
+ }
+ }
}
args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!);
@@ -361,7 +383,7 @@ namespace GodotTools
{
if (!macOSAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
- GD.PushError("Cannot find code editor: VSCode");
+ GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium");
return Error.FileNotFound;
}
@@ -371,7 +393,7 @@ namespace GodotTools
{
if (string.IsNullOrEmpty(_vsCodePath))
{
- GD.PushError("Cannot find code editor: VSCode");
+ GD.PushError("Cannot find code editor: Visual Studio Code or VSCodium");
return Error.FileNotFound;
}
@@ -384,7 +406,7 @@ namespace GodotTools
}
catch (Exception e)
{
- GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'");
+ GD.PushError($"Error when trying to run code editor: Visual Studio Code or VSCodium. Exception message: '{e.Message}'");
}
break;
@@ -550,7 +572,7 @@ namespace GodotTools
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
- $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
+ $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
@@ -558,14 +580,14 @@ namespace GodotTools
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
- $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
+ $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
- $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
+ $",Visual Studio Code and VSCodium:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider and Fleet:{(int)ExternalEditorId.Rider}" +
$",Custom:{(int)ExternalEditorId.CustomEditor}";
}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 89655e0b56..c54d58d6a0 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -190,7 +190,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
int pos = 0;
while (pos < bbcode.length()) {
- int brk_pos = bbcode.find("[", pos);
+ int brk_pos = bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = bbcode.length();
@@ -210,7 +210,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
break;
}
- int brk_end = bbcode.find("]", brk_pos + 1);
+ int brk_end = bbcode.find_char(']', brk_pos + 1);
if (brk_end == -1) {
String text = bbcode.substr(brk_pos, bbcode.length() - brk_pos);
@@ -239,7 +239,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
output.append("[");
pos = brk_pos + 1;
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
- const int tag_end = tag.find(" ");
+ const int tag_end = tag.find_char(' ');
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -385,7 +385,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "url") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -403,7 +403,7 @@ String BindingsGenerator::bbcode_to_text(const String &p_bbcode, const TypeInter
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag == "img") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -455,7 +455,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
int pos = 0;
while (pos < bbcode.length()) {
- int brk_pos = bbcode.find("[", pos);
+ int brk_pos = bbcode.find_char('[', pos);
if (brk_pos < 0) {
brk_pos = bbcode.length();
@@ -488,7 +488,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
break;
}
- int brk_end = bbcode.find("]", brk_pos + 1);
+ int brk_end = bbcode.find_char(']', brk_pos + 1);
if (brk_end == -1) {
if (!line_del) {
@@ -551,7 +551,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("[");
pos = brk_pos + 1;
} else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) {
- const int tag_end = tag.find(" ");
+ const int tag_end = tag.find_char(' ');
const String link_tag = tag.substr(0, tag_end);
const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" ");
@@ -696,7 +696,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "code" || tag.begins_with("code ")) {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -751,7 +751,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "url") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -772,7 +772,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
pos = brk_end + 1;
tag_stack.push_front("url");
} else if (tag == "img") {
- int end = bbcode.find("[", brk_end);
+ int end = bbcode.find_char('[', brk_end);
if (end == -1) {
end = bbcode.length();
}
@@ -1619,7 +1619,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
bool enum_in_static_class = false;
- if (enum_proxy_name.find(".") > 0) {
+ if (enum_proxy_name.find_char('.') > 0) {
enum_in_static_class = true;
String enum_class_name = enum_proxy_name.get_slicec('.', 0);
enum_proxy_name = enum_proxy_name.get_slicec('.', 1);
@@ -2348,9 +2348,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
int method_bind_count = 0;
for (const MethodInterface &imethod : itype.methods) {
- Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output);
+ Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
+ if (imethod.is_internal) {
+ // No need to generate span overloads for internal methods.
+ continue;
+ }
+
+ method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");
}
// Signals
@@ -2776,7 +2784,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
return OK;
}
-Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
+Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span) {
const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");
@@ -2789,6 +2797,35 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
"' from the editor API. Core API cannot have dependencies on the editor API.");
}
+ if (p_imethod.is_virtual && p_use_span) {
+ return OK;
+ }
+
+ bool has_span_argument = false;
+
+ if (p_use_span) {
+ if (p_imethod.is_vararg) {
+ has_span_argument = true;
+ } else {
+ for (const ArgumentInterface &iarg : p_imethod.arguments) {
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
+ ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
+
+ if (arg_type->is_span_compatible) {
+ has_span_argument = true;
+ break;
+ }
+ }
+ }
+
+ if (has_span_argument) {
+ // Span overloads use the same method bind as the array overloads.
+ // Since both overloads are generated one after the other, we can decrease the count here
+ // to ensure the span overload uses the same method bind.
+ p_method_bind_count--;
+ }
+ }
+
String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);
String arguments_sig;
@@ -2835,6 +2872,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
+ bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;
+
// Add the current arguments to the signature
// If the argument has a default value which is not a constant, we will make it Nullable
{
@@ -2846,7 +2885,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += "Nullable<";
}
- arguments_sig += arg_cs_type;
+ if (use_span_for_arg) {
+ arguments_sig += arg_type->c_type_in;
+ } else {
+ arguments_sig += arg_cs_type;
+ }
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
arguments_sig += "> ";
@@ -2856,7 +2899,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += iarg.name;
- if (!p_imethod.is_compat && iarg.default_argument.size()) {
+ if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {
if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
arguments_sig += " = null";
} else {
@@ -2867,7 +2910,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
icall_params += ", ";
- if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) {
+ if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {
// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:
// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;
String arg_or_defval_local = iarg.name;
@@ -2927,6 +2970,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}
+ if (p_use_span && !has_span_argument) {
+ return OK;
+ }
+
// Collect caller name for MethodBind
if (p_imethod.is_vararg) {
icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
@@ -2934,7 +2981,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Generate method
{
- if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
+ if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";
@@ -3228,10 +3275,10 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(CLOSE_BLOCK_L1);
- // Generate On{EventName} method to raise the event.
+ // Generate EmitSignal{EventName} method to raise the event.
if (!p_itype.is_singleton) {
p_output.append(MEMBER_BEGIN "protected void ");
- p_output << "On" << p_isignal.proxy_name;
+ p_output << "EmitSignal" << p_isignal.proxy_name;
if (is_parameterless) {
p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
@@ -4325,9 +4372,45 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
return true;
}
+static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {
+ return String::num_real(p_vec2.x, true) + "f, " +
+ String::num_real(p_vec2.y, true) + "f";
+}
+
+static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {
+ return String::num_real(p_vec3.x, true) + "f, " +
+ String::num_real(p_vec3.y, true) + "f, " +
+ String::num_real(p_vec3.z, true) + "f";
+}
+
+static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {
+ return String::num_real(p_vec4.x, true) + "f, " +
+ String::num_real(p_vec4.y, true) + "f, " +
+ String::num_real(p_vec4.z, true) + "f, " +
+ String::num_real(p_vec4.w, true) + "f";
+}
+
+static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {
+ return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);
+}
+
+static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {
+ return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);
+}
+
+static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {
+ return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);
+}
+
+static String _get_color_cs_ctor_args(const Color &p_color) {
+ return String::num(p_color.r, 4) + "f, " +
+ String::num(p_color.g, 4) + "f, " +
+ String::num(p_color.b, 4) + "f, " +
+ String::num(p_color.a, 4) + "f";
+}
+
bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.def_param_value = p_val;
- r_iarg.default_argument = p_val.operator String();
switch (p_val.get_type()) {
case Variant::NIL:
@@ -4340,10 +4423,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
break;
case Variant::INT:
if (r_iarg.type.cname != name_cache.type_int) {
- r_iarg.default_argument = "(%s)(" + r_iarg.default_argument + ")";
+ r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";
+ } else {
+ r_iarg.default_argument = p_val.operator String();
}
break;
case Variant::FLOAT:
+ r_iarg.default_argument = p_val.operator String();
+
if (r_iarg.type.cname == name_cache.type_float) {
r_iarg.default_argument += "f";
}
@@ -4353,7 +4440,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
case Variant::NODE_PATH:
if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {
if (r_iarg.default_argument.length() > 0) {
- r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
} else {
// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.
@@ -4361,40 +4448,62 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
}
} else {
CRASH_COND(r_iarg.type.cname != name_cache.type_String);
- r_iarg.default_argument = "\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "\"" + p_val.operator String() + "\"";
}
break;
case Variant::PLANE: {
Plane plane = p_val.operator Plane();
- r_iarg.default_argument = "new Plane(new Vector3" + plane.normal.operator String() + ", " + rtos(plane.d) + ")";
+ r_iarg.default_argument = "new Plane(new Vector3(" +
+ _get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::AABB: {
AABB aabb = p_val.operator ::AABB();
- r_iarg.default_argument = "new Aabb(new Vector3" + aabb.position.operator String() + ", new Vector3" + aabb.size.operator String() + ")";
+ r_iarg.default_argument = "new Aabb(new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2: {
Rect2 rect = p_val.operator Rect2();
- r_iarg.default_argument = "new Rect2(new Vector2" + rect.position.operator String() + ", new Vector2" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2(new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2I: {
Rect2i rect = p_val.operator Rect2i();
- r_iarg.default_argument = "new Rect2I(new Vector2I" + rect.position.operator String() + ", new Vector2I" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2I(new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::COLOR:
+ r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2:
+ r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2I:
+ r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3:
+ r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::VECTOR4:
+ r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR4I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
@@ -4444,7 +4553,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (transform == Transform2D()) {
r_iarg.default_argument = "Transform2D.Identity";
} else {
- r_iarg.default_argument = "new Transform2D(new Vector2" + transform.columns[0].operator String() + ", new Vector2" + transform.columns[1].operator String() + ", new Vector2" + transform.columns[2].operator String() + ")";
+ r_iarg.default_argument = "new Transform2D(new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[2]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4454,7 +4566,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "Transform3D.Identity";
} else {
Basis basis = transform.basis;
- r_iarg.default_argument = "new Transform3D(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")";
+ r_iarg.default_argument = "new Transform3D(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(transform.origin) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4463,7 +4579,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (projection == Projection()) {
r_iarg.default_argument = "Projection.Identity";
} else {
- r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")";
+ r_iarg.default_argument = "new Projection(new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[3]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4472,7 +4592,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (basis == Basis()) {
r_iarg.default_argument = "Basis.Identity";
} else {
- r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")";
+ r_iarg.default_argument = "new Basis(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4481,7 +4604,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (quaternion == Quaternion()) {
r_iarg.default_argument = "Quaternion.Identity";
} else {
- r_iarg.default_argument = "new Quaternion" + quaternion.operator String();
+ r_iarg.default_argument = "new Quaternion(" +
+ String::num_real(quaternion.x, false) + "f, " +
+ String::num_real(quaternion.y, false) + "f, " +
+ String::num_real(quaternion.z, false) + "f, " +
+ String::num_real(quaternion.w, false) + "f)";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4734,13 +4861,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype = TypeInterface();
itype.name = "VarArg";
itype.cname = itype.name;
- itype.proxy_name = "Variant[]";
+ itype.proxy_name = "ReadOnlySpan<Variant>";
itype.cs_type = "params Variant[]";
- itype.cs_in_expr = "%0 ?? Array.Empty<Variant>()";
+ itype.cs_in_expr = "%0";
// c_type, c_in and c_arg_in are hard-coded in the generator.
// c_out and c_type_out are not applicable to VarArg.
itype.c_arg_in = "&%s_in";
- itype.c_type_in = "Variant[]";
+ itype.c_type_in = "ReadOnlySpan<Variant>";
+ itype.is_span_compatible = true;
builtin_types.insert(itype.cname, itype);
#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \
@@ -4754,9 +4882,10 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \
itype.c_arg_in = "&%s_in"; \
itype.c_type = #m_managed_type; \
- itype.c_type_in = itype.proxy_name; \
+ itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \
itype.c_type_out = itype.proxy_name; \
itype.c_type_is_disposable_struct = true; \
+ itype.is_span_compatible = true; \
builtin_types.insert(itype.name, itype); \
}
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 556d287af4..1670aca4b3 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -265,6 +265,7 @@ class BindingsGenerator {
bool is_singleton = false;
bool is_singleton_instance = false;
bool is_ref_counted = false;
+ bool is_span_compatible = false;
/**
* Class is a singleton, but can't be declared as a static class as that would
@@ -840,7 +841,7 @@ class BindingsGenerator {
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
- Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
+ Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span);
Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output);
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index ae914e71ef..94222f3d67 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -116,7 +116,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
continue;
}
- String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length());
+ String name = prop.name.substr(prop.name.find_char('/') + 1, prop.name.length());
suggestions.push_back(quoted(name));
}
} break;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 864815866a..db6961fd12 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -174,7 +174,7 @@ namespace Godot.Collections
var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray);
godot_array valuesArray;
- NativeFuncs.godotsharp_dictionary_keys(ref self, out valuesArray);
+ NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray);
var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray);
int count = NativeFuncs.godotsharp_dictionary_count(ref self);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 15b7ce7c73..fc68b11932 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -394,7 +394,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ public static godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ {
+ return ConvertSystemArrayToNativePackedByteArray((ReadOnlySpan<byte>)p_array);
+ }
+
+ public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(ReadOnlySpan<byte> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_byte_array();
@@ -417,7 +422,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ public static godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt32Array((ReadOnlySpan<int>)p_array);
+ }
+
+ public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(ReadOnlySpan<int> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int32_array();
@@ -440,7 +450,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ public static godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt64Array((ReadOnlySpan<long>)p_array);
+ }
+
+ public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(ReadOnlySpan<long> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int64_array();
@@ -463,8 +478,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(Span<float> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat32Array((ReadOnlySpan<float>)p_array);
+ }
+
public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(
- Span<float> p_array)
+ ReadOnlySpan<float> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float32_array();
@@ -487,8 +507,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(Span<double> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat64Array((ReadOnlySpan<double>)p_array);
+ }
+
public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(
- Span<double> p_array)
+ ReadOnlySpan<double> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float64_array();
@@ -512,6 +537,11 @@ namespace Godot.NativeInterop
public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(Span<string> p_array)
{
+ return ConvertSystemArrayToNativePackedStringArray((ReadOnlySpan<string>)p_array);
+ }
+
+ public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(ReadOnlySpan<string> p_array)
+ {
godot_packed_string_array dest = new godot_packed_string_array();
if (p_array.IsEmpty)
@@ -544,8 +574,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(Span<Vector2> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector2Array((ReadOnlySpan<Vector2>)p_array);
+ }
+
public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(
- Span<Vector2> p_array)
+ ReadOnlySpan<Vector2> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector2_array();
@@ -568,8 +603,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(Span<Vector3> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector3Array((ReadOnlySpan<Vector3>)p_array);
+ }
+
public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(
- Span<Vector3> p_array)
+ ReadOnlySpan<Vector3> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector3_array();
@@ -592,8 +632,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(Span<Vector4> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector4Array((ReadOnlySpan<Vector4>)p_array);
+ }
+
public static unsafe godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(
- Span<Vector4> p_array)
+ ReadOnlySpan<Vector4> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector4_array();
@@ -616,7 +661,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ public static godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ {
+ return ConvertSystemArrayToNativePackedColorArray((ReadOnlySpan<Color>)p_array);
+ }
+
+ public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(ReadOnlySpan<Color> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_color_array();
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs
index dc151e2c3e..222ded6895 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs
@@ -534,7 +534,10 @@ namespace Godot.NativeInterop
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Callable ConvertToCallable(in godot_variant p_var)
- => Marshaling.ConvertCallableToManaged(ConvertToNativeCallable(p_var));
+ {
+ using var callable = ConvertToNativeCallable(p_var);
+ return Marshaling.ConvertCallableToManaged(callable);
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_signal ConvertToNativeSignal(in godot_variant p_var)
@@ -542,7 +545,10 @@ namespace Godot.NativeInterop
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Signal ConvertToSignal(in godot_variant p_var)
- => Marshaling.ConvertSignalToManaged(ConvertToNativeSignal(p_var));
+ {
+ using var signal = ConvertToNativeSignal(p_var);
+ return Marshaling.ConvertSignalToManaged(signal);
+ }
public static godot_array ConvertToNativeArray(in godot_variant p_var)
=> p_var.Type == Variant.Type.Array ?
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index ee17a668d7..068ac8b4e1 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -212,7 +212,7 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) {
#ifdef WINDOWS_ENABLED
String get_drive_letter(const String &p_norm_path) {
int idx = p_norm_path.find(":/");
- if (idx != -1 && idx < p_norm_path.find("/")) {
+ if (idx != -1 && idx < p_norm_path.find_char('/')) {
return p_norm_path.substr(0, idx + 1);
}
return String();
diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
index c2d879962c..edcaa3baef 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
@@ -53,7 +53,7 @@
</methods>
<members>
<member name="delta_interval" type="float" setter="set_delta_interval" getter="get_delta_interval" default="0.0">
- Time interval between delta synchronizations. When set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame.
+ Time interval between delta synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE]. If set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame.
</member>
<member name="public_visibility" type="bool" setter="set_visibility_public" getter="is_visibility_public" default="true">
Whether synchronization should be visible to all peers by default. See [method set_visibility_for] and [method add_visibility_filter] for ways of configuring fine-grained visibility options.
@@ -62,7 +62,7 @@
Resource containing which properties to synchronize.
</member>
<member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0">
- Time interval between synchronizations. When set to [code]0.0[/code] (the default), synchronizations happen every network process frame.
+ Time interval between synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ALWAYS]. If set to [code]0.0[/code] (the default), synchronizations happen every network process frame.
</member>
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;..&quot;)">
Node path that replicated properties are relative to.
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index f5f20d6931..dc3a3e3be7 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -45,11 +45,11 @@ void EditorNetworkProfiler::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
if (activate->is_pressed()) {
- activate->set_icon(theme_cache.stop_icon);
+ activate->set_button_icon(theme_cache.stop_icon);
} else {
- activate->set_icon(theme_cache.play_icon);
+ activate->set_button_icon(theme_cache.play_icon);
}
- clear_button->set_icon(theme_cache.clear_icon);
+ clear_button->set_button_icon(theme_cache.clear_icon);
incoming_bandwidth_text->set_right_icon(theme_cache.incoming_bandwidth_icon);
outgoing_bandwidth_text->set_right_icon(theme_cache.outgoing_bandwidth_icon);
@@ -184,10 +184,10 @@ void EditorNetworkProfiler::_activate_pressed() {
void EditorNetworkProfiler::_update_button_text() {
if (activate->is_pressed()) {
- activate->set_icon(theme_cache.stop_icon);
+ activate->set_button_icon(theme_cache.stop_icon);
activate->set_text(TTR("Stop"));
} else {
- activate->set_icon(theme_cache.play_icon);
+ activate->set_button_icon(theme_cache.play_icon);
activate->set_text(TTR("Start"));
}
}
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index 386feae4f9..4d5480eebf 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -353,8 +353,8 @@ void ReplicationEditor::_notification(int p_what) {
}
case NOTIFICATION_ENTER_TREE: {
add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SceneStringName(panel), SNAME("Panel")));
- add_pick_button->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
- pin->set_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons)));
+ add_pick_button->set_button_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ pin->set_button_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons)));
} break;
}
}
@@ -375,7 +375,7 @@ void ReplicationEditor::_add_pressed() {
return;
}
- int idx = np_text.find(":");
+ int idx = np_text.find_char(':');
if (idx == -1) {
np_text = ".:" + np_text;
} else if (idx == 0) {
@@ -554,7 +554,7 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
Ref<Texture2D> icon = _get_class_icon(root_node);
if (root_node) {
- String path = prop.substr(0, prop.find(":"));
+ String path = prop.substr(0, prop.find_char(':'));
String subpath = prop.substr(path.size());
Node *node = root_node->get_node_or_null(path);
if (!node) {
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 682d20022f..69a7545e51 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -97,7 +97,13 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
SpawnableScene sc;
- sc.path = p_path;
+ if (p_path.begins_with("uid://")) {
+ sc.uid = p_path;
+ sc.path = ResourceUID::uid_to_path(p_path);
+ } else {
+ sc.uid = ResourceUID::path_to_uid(p_path);
+ sc.path = p_path;
+ }
if (Engine::get_singleton()->is_editor_hint()) {
ERR_FAIL_COND(!ResourceLoader::exists(p_path));
}
@@ -139,7 +145,7 @@ Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
Vector<String> ss;
ss.resize(spawnable_scenes.size());
for (int i = 0; i < ss.size(); i++) {
- ss.write[i] = spawnable_scenes[i].path;
+ ss.write[i] = spawnable_scenes[i].uid;
}
return ss;
}
diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h
index 0e94b781ea..868efb9e0b 100644
--- a/modules/multiplayer/multiplayer_spawner.h
+++ b/modules/multiplayer/multiplayer_spawner.h
@@ -49,6 +49,7 @@ public:
private:
struct SpawnableScene {
String path;
+ String uid;
Ref<PackedScene> cache;
};
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index e245101eeb..dde14034e6 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -684,9 +684,9 @@ void SceneMultiplayer::_bind_methods() {
SceneMultiplayer::SceneMultiplayer() {
relay_buffer.instantiate();
- cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
- replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this, cache.ptr())));
- rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this, cache.ptr(), replicator.ptr())));
+ cache.instantiate(this);
+ replicator.instantiate(this, cache.ptr());
+ rpc.instantiate(this, cache.ptr(), replicator.ptr());
set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer)));
}
diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h
index 725cb9dbb6..5b668e1651 100644
--- a/modules/multiplayer/scene_multiplayer.h
+++ b/modules/multiplayer/scene_multiplayer.h
@@ -52,14 +52,14 @@ public:
virtual void set_target_peer(int p_peer_id) override {}
virtual int get_packet_peer() const override { return 0; }
- virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; };
+ virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }
virtual int get_packet_channel() const override { return 0; }
virtual void disconnect_peer(int p_peer, bool p_force = false) override {}
virtual bool is_server() const override { return true; }
virtual void poll() override {}
virtual void close() override {}
virtual int get_unique_id() const override { return TARGET_PEER_SERVER; }
- virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; };
+ virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; }
};
class SceneMultiplayer : public MultiplayerAPI {
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 0938d7ef99..b5f3889268 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -73,16 +73,6 @@ int get_packet_len(uint32_t p_node_target, int p_packet_len) {
}
}
-bool SceneRPCInterface::_sort_rpc_names(const Variant &p_l, const Variant &p_r) {
- if (likely(p_l.is_string() && p_r.is_string())) {
- return p_l.operator String() < p_r.operator String();
- }
- bool valid = false;
- Variant res;
- Variant::evaluate(Variant::OP_LESS, p_l, p_r, res, valid);
- return valid ? res.operator bool() : false;
-}
-
void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache) {
if (p_config.get_type() == Variant::NIL) {
return;
@@ -90,7 +80,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no
ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY);
const Dictionary config = p_config;
Array names = config.keys();
- names.sort_custom(callable_mp_static(&SceneRPCInterface::_sort_rpc_names)); // Ensure ID order
+ names.sort_custom(callable_mp_static(&StringLikeVariantOrder::compare)); // Ensure ID order
for (int i = 0; i < names.size(); i++) {
ERR_CONTINUE(!names[i].is_string());
String name = names[i].operator String();
diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h
index 852cef7830..5c9b66d5f5 100644
--- a/modules/multiplayer/scene_rpc_interface.h
+++ b/modules/multiplayer/scene_rpc_interface.h
@@ -91,8 +91,6 @@ private:
#endif
protected:
- static bool _sort_rpc_names(const Variant &p_l, const Variant &p_r);
-
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 78983187c7..a8eb07147c 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -691,11 +691,15 @@ void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<Navig
return;
}
- const Transform2D node_xform = p_source_geometry_data->root_node_transform * Transform2D(0.0, obstacle->get_global_position());
-
+ const Vector2 safe_scale = obstacle->get_global_scale().abs().maxf(0.001);
const float obstacle_radius = obstacle->get_radius();
if (obstacle_radius > 0.0) {
+ // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis.
+ const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
+ const Vector2 uniform_max_scale = Vector2(scaling_max_value, scaling_max_value);
+ const Transform2D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform2D(obstacle->get_global_rotation(), uniform_max_scale, 0.0, obstacle->get_global_position());
+
Vector<Vector2> obstruction_circle_vertices;
// The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding.
@@ -709,12 +713,15 @@ void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<Navig
for (int i = 0; i < circle_points; i++) {
const float angle = i * circle_point_step;
- circle_vertices_ptrw[i] = node_xform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius));
+ circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius));
}
p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh());
}
+ // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
+ const Transform2D node_xform = p_source_geometry_data->root_node_transform * obstacle->get_global_transform();
+
const Vector<Vector2> &obstacle_vertices = obstacle->get_vertices();
if (obstacle_vertices.is_empty()) {
@@ -755,21 +762,19 @@ void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPoly
for (Node *E : parse_nodes) {
generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children);
}
-};
+}
static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPathD *p_polypath_item) {
using namespace Clipper2Lib;
- Vector<Vector2> polygon_vertices;
+ TPPLPoly tp;
+ int size = p_polypath_item->Polygon().size();
+ tp.Init(size);
+ int j = 0;
for (const PointD &polypath_point : p_polypath_item->Polygon()) {
- polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y)));
- }
-
- TPPLPoly tp;
- tp.Init(polygon_vertices.size());
- for (int j = 0; j < polygon_vertices.size(); j++) {
- tp[j] = polygon_vertices[j];
+ tp[j] = Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y));
+ ++j;
}
if (p_polypath_item->IsHole()) {
@@ -842,83 +847,74 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
return;
}
- if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) {
- return;
- }
-
- int outline_count = p_navigation_mesh->get_outline_count();
-
- Vector<Vector<Vector2>> traversable_outlines;
- Vector<Vector<Vector2>> obstruction_outlines;
- Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> projected_obstructions;
-
- p_source_geometry_data->get_data(
- traversable_outlines,
- obstruction_outlines,
- projected_obstructions);
-
- if (outline_count == 0 && traversable_outlines.size() == 0) {
- return;
- }
-
using namespace Clipper2Lib;
-
PathsD traversable_polygon_paths;
PathsD obstruction_polygon_paths;
+ bool empty_projected_obstructions = true;
+ {
+ RWLockRead read_lock(p_source_geometry_data->geometry_rwlock);
- traversable_polygon_paths.reserve(outline_count + traversable_outlines.size());
- obstruction_polygon_paths.reserve(obstruction_outlines.size());
+ const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->traversable_outlines;
+ int outline_count = p_navigation_mesh->get_outline_count();
- for (int i = 0; i < outline_count; i++) {
- const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
- PathD subject_path;
- subject_path.reserve(traversable_outline.size());
- for (const Vector2 &traversable_point : traversable_outline) {
- const PointD &point = PointD(traversable_point.x, traversable_point.y);
- subject_path.push_back(point);
+ if (outline_count == 0 && (!p_source_geometry_data->has_data() || (traversable_outlines.is_empty()))) {
+ return;
}
- traversable_polygon_paths.push_back(subject_path);
- }
- for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
- PathD subject_path;
- subject_path.reserve(traversable_outline.size());
- for (const Vector2 &traversable_point : traversable_outline) {
- const PointD &point = PointD(traversable_point.x, traversable_point.y);
- subject_path.push_back(point);
- }
- traversable_polygon_paths.push_back(subject_path);
- }
+ const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->obstruction_outlines;
+ const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_projected_obstructions;
- for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
- PathD clip_path;
- clip_path.reserve(obstruction_outline.size());
- for (const Vector2 &obstruction_point : obstruction_outline) {
- const PointD &point = PointD(obstruction_point.x, obstruction_point.y);
- clip_path.push_back(point);
+ traversable_polygon_paths.reserve(outline_count + traversable_outlines.size());
+ obstruction_polygon_paths.reserve(obstruction_outlines.size());
+
+ for (int i = 0; i < outline_count; i++) {
+ const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i);
+ PathD subject_path;
+ subject_path.reserve(traversable_outline.size());
+ for (const Vector2 &traversable_point : traversable_outline) {
+ subject_path.emplace_back(traversable_point.x, traversable_point.y);
+ }
+ traversable_polygon_paths.push_back(std::move(subject_path));
}
- obstruction_polygon_paths.push_back(clip_path);
- }
- if (!projected_obstructions.is_empty()) {
- for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
- if (projected_obstruction.carve) {
- continue;
+ for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
+ PathD subject_path;
+ subject_path.reserve(traversable_outline.size());
+ for (const Vector2 &traversable_point : traversable_outline) {
+ subject_path.emplace_back(traversable_point.x, traversable_point.y);
}
- if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) {
- continue;
+ traversable_polygon_paths.push_back(std::move(subject_path));
+ }
+
+ empty_projected_obstructions = projected_obstructions.is_empty();
+ if (!empty_projected_obstructions) {
+ for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
+ if (projected_obstruction.carve) {
+ continue;
+ }
+ if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) {
+ continue;
+ }
+
+ PathD clip_path;
+ clip_path.reserve(projected_obstruction.vertices.size() / 2);
+ for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
+ clip_path.emplace_back(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
+ }
+ if (!IsPositive(clip_path)) {
+ std::reverse(clip_path.begin(), clip_path.end());
+ }
+ obstruction_polygon_paths.push_back(std::move(clip_path));
}
+ }
+ for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
PathD clip_path;
- clip_path.reserve(projected_obstruction.vertices.size() / 2);
- for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
- const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
- clip_path.push_back(point);
+ clip_path.reserve(obstruction_outline.size());
+ for (const Vector2 &obstruction_point : obstruction_outline) {
+ clip_path.emplace_back(obstruction_point.x, obstruction_point.y);
}
- if (!IsPositive(clip_path)) {
- std::reverse(clip_path.begin(), clip_path.end());
- }
- obstruction_polygon_paths.push_back(clip_path);
+ obstruction_polygon_paths.push_back(std::move(clip_path));
}
}
@@ -937,22 +933,23 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
obstruction_polygon_paths = RectClip(clipper_rect, obstruction_polygon_paths);
}
- PathsD path_solution;
-
// first merge all traversable polygons according to user specified fill rule
PathsD dummy_clip_path;
traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero);
// merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry
obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero);
- path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero);
+ PathsD path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero);
real_t agent_radius_offset = p_navigation_mesh->get_agent_radius();
if (agent_radius_offset > 0.0) {
path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon);
}
- if (!projected_obstructions.is_empty()) {
+ // Apply obstructions that are not affected by agent radius, the ones with carve enabled.
+ if (!empty_projected_obstructions) {
+ RWLockRead read_lock(p_source_geometry_data->geometry_rwlock);
+ const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_projected_obstructions;
obstruction_polygon_paths.resize(0);
for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) {
if (!projected_obstruction.carve) {
@@ -965,13 +962,12 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
PathD clip_path;
clip_path.reserve(projected_obstruction.vertices.size() / 2);
for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
- const PointD &point = PointD(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
- clip_path.push_back(point);
+ clip_path.emplace_back(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
}
if (!IsPositive(clip_path)) {
std::reverse(clip_path.begin(), clip_path.end());
}
- obstruction_polygon_paths.push_back(clip_path);
+ obstruction_polygon_paths.push_back(std::move(clip_path));
}
if (obstruction_polygon_paths.size() > 0) {
path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero);
@@ -994,33 +990,11 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
path_solution = RectClip(clipper_rect, path_solution);
}
- Vector<Vector<Vector2>> new_baked_outlines;
-
- for (const PathD &scaled_path : path_solution) {
- Vector<Vector2> polypath;
- for (const PointD &scaled_point : scaled_path) {
- polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
- }
- new_baked_outlines.push_back(polypath);
- }
-
- if (new_baked_outlines.size() == 0) {
+ if (path_solution.size() == 0) {
p_navigation_mesh->clear();
return;
}
- PathsD polygon_paths;
- polygon_paths.reserve(new_baked_outlines.size());
-
- for (const Vector<Vector2> &baked_outline : new_baked_outlines) {
- PathD polygon_path;
- for (const Vector2 &baked_outline_point : baked_outline) {
- const PointD &point = PointD(baked_outline_point.x, baked_outline_point.y);
- polygon_path.push_back(point);
- }
- polygon_paths.push_back(polygon_path);
- }
-
ClipType clipper_cliptype = ClipType::Union;
List<TPPLPoly> tppl_in_polygon, tppl_out_polygon;
@@ -1028,7 +1002,7 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
PolyTreeD polytree;
ClipperD clipper_D;
- clipper_D.AddSubject(polygon_paths);
+ clipper_D.AddSubject(path_solution);
clipper_D.Execute(clipper_cliptype, FillRule::NonZero, polytree);
for (size_t i = 0; i < polytree.Count(); i++) {
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index e92a9d304b..3d0697a7cf 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -595,11 +595,17 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig
return;
}
- const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis(), obstacle->get_global_position());
-
+ const float elevation = obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y;
+ // Prevent non-positive scaling.
+ const Vector3 safe_scale = obstacle->get_global_basis().get_scale().abs().maxf(0.001);
const float obstacle_radius = obstacle->get_radius();
if (obstacle_radius > 0.0) {
+ // Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis.
+ const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
+ const Vector3 uniform_max_scale = Vector3(scaling_max_value, scaling_max_value, scaling_max_value);
+ const Transform3D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(uniform_max_scale), obstacle->get_global_position());
+
Vector<Vector3> obstruction_circle_vertices;
// The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding.
@@ -613,12 +619,15 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig
for (int i = 0; i < circle_points; i++) {
const float angle = i * circle_point_step;
- circle_vertices_ptrw[i] = node_xform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius));
+ circle_vertices_ptrw[i] = obstacle_circle_transform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius));
}
- p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y - obstacle_radius, obstacle_radius, obstacle->get_carve_navigation_mesh());
+ p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, elevation - obstacle_radius, scaling_max_value * obstacle_radius, obstacle->get_carve_navigation_mesh());
}
+ // Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
+ const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis().scaled(safe_scale).rotated(Vector3(0.0, 1.0, 0.0), obstacle->get_global_rotation().y), obstacle->get_global_position());
+
const Vector<Vector3> &obstacle_vertices = obstacle->get_vertices();
if (obstacle_vertices.is_empty()) {
@@ -635,7 +644,7 @@ void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<Navig
obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]);
obstruction_shape_vertices_ptrw[i].y = 0.0;
}
- p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y, obstacle->get_height(), obstacle->get_carve_navigation_mesh());
+ p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, elevation, safe_scale.y * obstacle->get_height(), obstacle->get_carve_navigation_mesh());
}
void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) {
@@ -660,7 +669,7 @@ void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<Navigati
for (Node *parse_node : parse_nodes) {
generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, parse_node, recurse_children);
}
-};
+}
void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data) {
if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) {
diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp
index 70207f86ce..5acc598d43 100644
--- a/modules/navigation/3d/nav_mesh_queries_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp
@@ -234,7 +234,7 @@ Vector<Vector3> NavMeshQueries3D::polygons_get_path(const LocalVector<gd::Polygo
// Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) {
// Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
- for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
+ for (uint32_t connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
const gd::Edge::Connection &connection = edge.connections[connection_index];
// Only consider the connection to another polygon if this polygon is in a region with compatible layers.
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index 7f0cbc7b5e..ed0cdeb287 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -54,8 +54,8 @@ void NavigationMeshEditor::_node_removed(Node *p_node) {
void NavigationMeshEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- button_bake->set_icon(get_theme_icon(SNAME("Bake"), EditorStringName(EditorIcons)));
- button_reset->set_icon(get_theme_icon(SNAME("Reload"), EditorStringName(EditorIcons)));
+ button_bake->set_button_icon(get_theme_icon(SNAME("Bake"), EditorStringName(EditorIcons)));
+ button_reset->set_button_icon(get_theme_icon(SNAME("Reload"), EditorStringName(EditorIcons)));
} break;
}
}
diff --git a/modules/navigation/nav_agent.cpp b/modules/navigation/nav_agent.cpp
index 2dbe57eb4a..037b237328 100644
--- a/modules/navigation/nav_agent.cpp
+++ b/modules/navigation/nav_agent.cpp
@@ -308,7 +308,7 @@ void NavAgent::set_avoidance_priority(real_t p_priority) {
rvo_agent_2d.avoidance_priority_ = avoidance_priority;
}
agent_dirty = true;
-};
+}
bool NavAgent::check_dirty() {
const bool was_dirty = agent_dirty;
diff --git a/modules/navigation/nav_agent.h b/modules/navigation/nav_agent.h
index 18997803f2..d56e053ac4 100644
--- a/modules/navigation/nav_agent.h
+++ b/modules/navigation/nav_agent.h
@@ -67,7 +67,7 @@ class NavAgent : public NavRid {
uint32_t avoidance_mask = 1;
real_t avoidance_priority = 1.0;
- Callable avoidance_callback = Callable();
+ Callable avoidance_callback;
bool agent_dirty = true;
@@ -130,13 +130,13 @@ public:
const Vector3 &get_velocity_forced() const { return velocity_forced; }
void set_avoidance_layers(uint32_t p_layers);
- uint32_t get_avoidance_layers() const { return avoidance_layers; };
+ uint32_t get_avoidance_layers() const { return avoidance_layers; }
void set_avoidance_mask(uint32_t p_mask);
- uint32_t get_avoidance_mask() const { return avoidance_mask; };
+ uint32_t get_avoidance_mask() const { return avoidance_mask; }
void set_avoidance_priority(real_t p_priority);
- real_t get_avoidance_priority() const { return avoidance_priority; };
+ real_t get_avoidance_priority() const { return avoidance_priority; }
void set_paused(bool p_paused);
bool get_paused() const;
diff --git a/modules/navigation/nav_link.cpp b/modules/navigation/nav_link.cpp
index c693cc91c8..61c3b59fab 100644
--- a/modules/navigation/nav_link.cpp
+++ b/modules/navigation/nav_link.cpp
@@ -57,7 +57,7 @@ void NavLink::set_enabled(bool p_enabled) {
// TODO: This should not require a full rebuild as the link has not really changed.
link_dirty = true;
-};
+}
void NavLink::set_bidirectional(bool p_bidirectional) {
if (bidirectional == p_bidirectional) {
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index dd77e81b45..8055dd4bc8 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -427,25 +427,36 @@ void NavMap::sync() {
_new_pm_polygon_count = polygon_count;
// Group all edges per key.
- HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections;
+ connection_pairs_map.clear();
+ connection_pairs_map.reserve(polygons.size());
+ int free_edges_count = 0; // How many ConnectionPairs have only one Connection.
+
for (gd::Polygon &poly : polygons) {
for (uint32_t p = 0; p < poly.points.size(); p++) {
- int next_point = (p + 1) % poly.points.size();
- gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key);
+ const int next_point = (p + 1) % poly.points.size();
+ const gd::EdgeKey ek(poly.points[p].key, poly.points[next_point].key);
- HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey>::Iterator connection = connections.find(ek);
- if (!connection) {
- connections[ek] = Vector<gd::Edge::Connection>();
+ HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey>::Iterator pair_it = connection_pairs_map.find(ek);
+ if (!pair_it) {
+ pair_it = connection_pairs_map.insert(ek, ConnectionPair());
_new_pm_edge_count += 1;
+ ++free_edges_count;
}
- if (connections[ek].size() <= 1) {
+ ConnectionPair &pair = pair_it->value;
+ if (pair.size < 2) {
// Add the polygon/edge tuple to this key.
gd::Edge::Connection new_connection;
new_connection.polygon = &poly;
new_connection.edge = p;
new_connection.pathway_start = poly.points[p].pos;
new_connection.pathway_end = poly.points[next_point].pos;
- connections[ek].push_back(new_connection);
+
+ pair.connections[pair.size] = new_connection;
+ ++pair.size;
+ if (pair.size == 2) {
+ --free_edges_count;
+ }
+
} else {
// The edge is already connected with another edge, skip.
ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001.");
@@ -453,20 +464,23 @@ void NavMap::sync() {
}
}
- Vector<gd::Edge::Connection> free_edges;
- for (KeyValue<gd::EdgeKey, Vector<gd::Edge::Connection>> &E : connections) {
- if (E.value.size() == 2) {
+ free_edges.clear();
+ free_edges.reserve(free_edges_count);
+
+ for (const KeyValue<gd::EdgeKey, ConnectionPair> &pair_it : connection_pairs_map) {
+ const ConnectionPair &pair = pair_it.value;
+ if (pair.size == 2) {
// Connect edge that are shared in different polygons.
- gd::Edge::Connection &c1 = E.value.write[0];
- gd::Edge::Connection &c2 = E.value.write[1];
+ const gd::Edge::Connection &c1 = pair.connections[0];
+ const gd::Edge::Connection &c2 = pair.connections[1];
c1.polygon->edges[c1.edge].connections.push_back(c2);
c2.polygon->edges[c2.edge].connections.push_back(c1);
// Note: The pathway_start/end are full for those connection and do not need to be modified.
_new_pm_edge_merge_count += 1;
} else {
- CRASH_COND_MSG(E.value.size() != 1, vformat("Number of connection != 1. Found: %d", E.value.size()));
- if (use_edge_connections && E.value[0].polygon->owner->get_use_edge_connections()) {
- free_edges.push_back(E.value[0]);
+ CRASH_COND_MSG(pair.size != 1, vformat("Number of connection != 1. Found: %d", pair.size));
+ if (use_edge_connections && pair.connections[0].polygon->owner->get_use_edge_connections()) {
+ free_edges.push_back(pair.connections[0]);
}
}
}
@@ -480,12 +494,14 @@ void NavMap::sync() {
// connection, integration and path finding.
_new_pm_edge_free_count = free_edges.size();
- for (int i = 0; i < free_edges.size(); i++) {
+ const real_t edge_connection_margin_squared = edge_connection_margin * edge_connection_margin;
+
+ for (uint32_t i = 0; i < free_edges.size(); i++) {
const gd::Edge::Connection &free_edge = free_edges[i];
Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos;
Vector3 edge_p2 = free_edge.polygon->points[(free_edge.edge + 1) % free_edge.polygon->points.size()].pos;
- for (int j = 0; j < free_edges.size(); j++) {
+ for (uint32_t j = 0; j < free_edges.size(); j++) {
const gd::Edge::Connection &other_edge = free_edges[j];
if (i == j || free_edge.polygon->owner == other_edge.polygon->owner) {
continue;
@@ -510,7 +526,7 @@ void NavMap::sync() {
} else {
other1 = other_edge_p1.lerp(other_edge_p2, (1.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other1.distance_to(self1) > edge_connection_margin) {
+ if (other1.distance_squared_to(self1) > edge_connection_margin_squared) {
continue;
}
@@ -521,7 +537,7 @@ void NavMap::sync() {
} else {
other2 = other_edge_p1.lerp(other_edge_p2, (0.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other2.distance_to(self2) > edge_connection_margin) {
+ if (other2.distance_squared_to(self2) > edge_connection_margin_squared) {
continue;
}
@@ -549,11 +565,11 @@ void NavMap::sync() {
const Vector3 end = link->get_end_position();
gd::Polygon *closest_start_polygon = nullptr;
- real_t closest_start_distance = link_connection_radius;
+ real_t closest_start_sqr_dist = link_connection_radius * link_connection_radius;
Vector3 closest_start_point;
gd::Polygon *closest_end_polygon = nullptr;
- real_t closest_end_distance = link_connection_radius;
+ real_t closest_end_sqr_dist = link_connection_radius * link_connection_radius;
Vector3 closest_end_point;
// Create link to any polygons within the search radius of the start point.
@@ -564,11 +580,11 @@ void NavMap::sync() {
for (uint32_t start_point_id = 2; start_point_id < start_poly.points.size(); start_point_id += 1) {
const Face3 start_face(start_poly.points[0].pos, start_poly.points[start_point_id - 1].pos, start_poly.points[start_point_id].pos);
const Vector3 start_point = start_face.get_closest_point_to(start);
- const real_t start_distance = start_point.distance_to(start);
+ const real_t sqr_dist = start_point.distance_squared_to(start);
// Pick the polygon that is within our radius and is closer than anything we've seen yet.
- if (start_distance <= link_connection_radius && start_distance < closest_start_distance) {
- closest_start_distance = start_distance;
+ if (sqr_dist < closest_start_sqr_dist) {
+ closest_start_sqr_dist = sqr_dist;
closest_start_point = start_point;
closest_start_polygon = &start_poly;
}
@@ -581,11 +597,11 @@ void NavMap::sync() {
for (uint32_t end_point_id = 2; end_point_id < end_poly.points.size(); end_point_id += 1) {
const Face3 end_face(end_poly.points[0].pos, end_poly.points[end_point_id - 1].pos, end_poly.points[end_point_id].pos);
const Vector3 end_point = end_face.get_closest_point_to(end);
- const real_t end_distance = end_point.distance_to(end);
+ const real_t sqr_dist = end_point.distance_squared_to(end);
// Pick the polygon that is within our radius and is closer than anything we've seen yet.
- if (end_distance <= link_connection_radius && end_distance < closest_end_distance) {
- closest_end_distance = end_distance;
+ if (sqr_dist < closest_end_sqr_dist) {
+ closest_end_sqr_dist = sqr_dist;
closest_end_point = end_point;
closest_end_polygon = &end_poly;
}
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index b9120c04d9..3442b78497 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -128,6 +128,14 @@ class NavMap : public NavRid {
HashMap<NavRegion *, LocalVector<gd::Edge::Connection>> region_external_connections;
+ struct ConnectionPair {
+ gd::Edge::Connection connections[2];
+ int size = 0;
+ };
+
+ HashMap<gd::EdgeKey, ConnectionPair, gd::EdgeKey> connection_pairs_map;
+ LocalVector<gd::Edge::Connection> free_edges;
+
public:
NavMap();
~NavMap();
diff --git a/modules/navigation/nav_obstacle.h b/modules/navigation/nav_obstacle.h
index e231e83836..a6aca9d637 100644
--- a/modules/navigation/nav_obstacle.h
+++ b/modules/navigation/nav_obstacle.h
@@ -92,7 +92,7 @@ public:
bool is_map_changed();
void set_avoidance_layers(uint32_t p_layers);
- uint32_t get_avoidance_layers() const { return avoidance_layers; };
+ uint32_t get_avoidance_layers() const { return avoidance_layers; }
void set_paused(bool p_paused);
bool get_paused() const;
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 2c91b80af2..679997b8e1 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -59,7 +59,7 @@ void NavRegion::set_enabled(bool p_enabled) {
// TODO: This should not require a full rebuild as the region has not really changed.
polygons_dirty = true;
-};
+}
void NavRegion::set_use_edge_connections(bool p_enabled) {
if (use_edge_connections != p_enabled) {
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index c015802b92..d13f666e9a 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -94,7 +94,7 @@ public:
gd::ClosestPointQueryResult get_closest_point_info(const Vector3 &p_point) const;
Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const;
- real_t get_surface_area() const { return surface_area; };
+ real_t get_surface_area() const { return surface_area; }
bool sync();
diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h
index ba4c44b748..c466c82fc7 100644
--- a/modules/navigation/nav_utils.h
+++ b/modules/navigation/nav_utils.h
@@ -94,7 +94,7 @@ struct Edge {
};
/// Connections from this edge to other polygons.
- Vector<Connection> connections;
+ LocalVector<Connection> connections;
};
struct Polygon {
diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml
index f2a6c60376..e29581693b 100644
--- a/modules/noise/doc_classes/FastNoiseLite.xml
+++ b/modules/noise/doc_classes/FastNoiseLite.xml
@@ -91,10 +91,10 @@
Cellular includes both Worley noise and Voronoi diagrams which creates various regions of the same value.
</constant>
<constant name="TYPE_SIMPLEX" value="0" enum="NoiseType">
- As opposed to [constant TYPE_PERLIN], gradients exist in a simplex lattice rather than a grid lattice, avoiding directional artifacts.
+ As opposed to [constant TYPE_PERLIN], gradients exist in a simplex lattice rather than a grid lattice, avoiding directional artifacts. Internally uses FastNoiseLite's OpenSimplex2 noise type.
</constant>
<constant name="TYPE_SIMPLEX_SMOOTH" value="1" enum="NoiseType">
- Modified, higher quality version of [constant TYPE_SIMPLEX], but slower.
+ Modified, higher quality version of [constant TYPE_SIMPLEX], but slower. Internally uses FastNoiseLite's OpenSimplex2S noise type.
</constant>
<constant name="FRACTAL_NONE" value="0" enum="FractalType">
No fractal noise.
@@ -118,7 +118,7 @@
Manhattan distance (taxicab metric) to the nearest point.
</constant>
<constant name="DISTANCE_HYBRID" value="3" enum="CellularDistanceFunction">
- Blend of [constant DISTANCE_EUCLIDEAN] and [constant DISTANCE_MANHATTAN] to give curved cell boundaries
+ Blend of [constant DISTANCE_EUCLIDEAN] and [constant DISTANCE_MANHATTAN] to give curved cell boundaries.
</constant>
<constant name="RETURN_CELL_VALUE" value="0" enum="CellularReturnType">
The cellular distance function will return the same value for all points within a cell.
diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h
index 0d18d66e74..0cccb678ec 100644
--- a/modules/noise/tests/test_noise_texture_2d.h
+++ b/modules/noise/tests/test_noise_texture_2d.h
@@ -44,7 +44,7 @@ class NoiseTextureTester : public RefCounted {
public:
NoiseTextureTester(const NoiseTexture2D *const p_texture) :
- texture{ p_texture } {};
+ texture{ p_texture } {}
Color compute_average_color(const Ref<Image> &p_noise_image) {
Color r_avg_color{};
diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h
index 434cd20a08..78616d478e 100644
--- a/modules/noise/tests/test_noise_texture_3d.h
+++ b/modules/noise/tests/test_noise_texture_3d.h
@@ -44,7 +44,7 @@ class NoiseTexture3DTester : public RefCounted {
public:
NoiseTexture3DTester(const NoiseTexture3D *const p_texture) :
- texture{ p_texture } {};
+ texture{ p_texture } {}
Color compute_average_color(const Ref<Image> &p_noise_image) {
Color r_avg_color{};
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index dd6a921440..48c87bcd59 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -26,7 +26,7 @@ elif env["platform"] == "linuxbsd":
env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"])
if env["wayland"]:
- env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_WAYLAND"])
+ env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_EGL"])
# FIXME: Review what needs to be set for Android and macOS.
env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"])
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 5430a41d6d..f924386ecf 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -576,20 +576,15 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref<OpenXRAction> p
const OpenXRInteractionProfileMetadata::InteractionProfile *profile = OpenXRInteractionProfileMetadata::get_singleton()->get_profile(ip->get_interaction_profile_path());
if (profile != nullptr) {
- for (int j = 0; j < ip->get_binding_count(); j++) {
- Ref<OpenXRIPBinding> binding = ip->get_binding(j);
- if (binding->get_action() == p_action) {
- PackedStringArray paths = binding->get_paths();
-
- for (int k = 0; k < paths.size(); k++) {
- const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(paths[k]);
- if (io_path != nullptr) {
- String top_path = io_path->top_level_path;
-
- if (!arr.has(top_path)) {
- arr.push_back(top_path);
- }
- }
+ Vector<Ref<OpenXRIPBinding>> bindings = ip->get_bindings_for_action(p_action);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ String binding_path = binding->get_binding_path();
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(binding_path);
+ if (io_path != nullptr) {
+ String top_path = io_path->top_level_path;
+
+ if (!arr.has(top_path)) {
+ arr.push_back(top_path);
}
}
}
diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp
index 1266457113..2aab55f6ec 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile.cpp
@@ -35,23 +35,30 @@ void OpenXRIPBinding::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action");
- ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
+ ClassDB::bind_method(D_METHOD("set_binding_path", "binding_path"), &OpenXRIPBinding::set_binding_path);
+ ClassDB::bind_method(D_METHOD("get_binding_path"), &OpenXRIPBinding::get_binding_path);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "binding_path"), "set_binding_path", "get_binding_path");
+
+ // Deprecated
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths);
ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"), "set_paths", "get_paths");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_paths", "get_paths");
+ ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path);
ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path);
ClassDB::bind_method(D_METHOD("remove_path", "path"), &OpenXRIPBinding::remove_path);
+#endif // DISABLE_DEPRECATED
}
-Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) {
// This is a helper function to help build our default action sets
Ref<OpenXRIPBinding> binding;
binding.instantiate();
binding->set_action(p_action);
- binding->parse_paths(String(p_paths));
+ binding->set_binding_path(p_binding_path);
return binding;
}
@@ -65,42 +72,68 @@ Ref<OpenXRAction> OpenXRIPBinding::get_action() const {
return action;
}
-int OpenXRIPBinding::get_path_count() const {
- return paths.size();
+void OpenXRIPBinding::set_binding_path(const String &path) {
+ binding_path = path;
+ emit_changed();
}
-void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) {
- paths = p_paths;
- emit_changed();
+String OpenXRIPBinding::get_binding_path() const {
+ return binding_path;
}
-PackedStringArray OpenXRIPBinding::get_paths() const {
+#ifndef DISABLE_DEPRECATED
+
+void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { // Deprecated, but needed for loading old action maps.
+ // Fallback logic, this should ONLY be called when loading older action maps.
+ // We'll parse this momentarily and extract individual bindings.
+ binding_path = "";
+ for (const String &path : p_paths) {
+ if (!binding_path.is_empty()) {
+ binding_path += ",";
+ }
+ binding_path += path;
+ }
+}
+
+PackedStringArray OpenXRIPBinding::get_paths() const { // Deprecated, but needed for converting old action maps.
+ // Fallback logic, return an array.
+ // If we just loaded an old action map from disc, this will be a comma separated list of actions.
+ // Once parsed there should be only one path in our array.
+ PackedStringArray paths = binding_path.split(",", false);
+
return paths;
}
-void OpenXRIPBinding::parse_paths(const String p_paths) {
- paths = p_paths.split(",", false);
- emit_changed();
+int OpenXRIPBinding::get_path_count() const { // Deprecated.
+ // Fallback logic, we only have one entry.
+ return binding_path.is_empty() ? 0 : 1;
}
-bool OpenXRIPBinding::has_path(const String p_path) const {
- return paths.has(p_path);
+bool OpenXRIPBinding::has_path(const String p_path) const { // Deprecated.
+ // Fallback logic, return true if this is our path.
+ return binding_path == p_path;
}
-void OpenXRIPBinding::add_path(const String p_path) {
- if (!paths.has(p_path)) {
- paths.push_back(p_path);
+void OpenXRIPBinding::add_path(const String p_path) { // Deprecated.
+ // Fallback logic, only assign first time this is called.
+ if (binding_path != p_path) {
+ ERR_FAIL_COND_MSG(!binding_path.is_empty(), "Method add_path has been deprecated. A binding path was already set, create separate binding resources for each path and use set_binding_path instead.");
+
+ binding_path = p_path;
emit_changed();
}
}
-void OpenXRIPBinding::remove_path(const String p_path) {
- if (paths.has(p_path)) {
- paths.erase(p_path);
- emit_changed();
- }
+void OpenXRIPBinding::remove_path(const String p_path) { // Deprecated.
+ ERR_FAIL_COND_MSG(binding_path != p_path, "Method remove_path has been deprecated. Attempt at removing a different binding path, remove the correct binding record from the interaction profile instead.");
+
+ // Fallback logic, clear if this is our path.
+ binding_path = p_path;
+ emit_changed();
}
+#endif // DISABLE_DEPRECATED
+
OpenXRIPBinding::~OpenXRIPBinding() {
action.unref();
}
@@ -151,9 +184,18 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding(int p_index) const {
}
void OpenXRInteractionProfile::set_bindings(Array p_bindings) {
- // TODO add check here that our bindings don't contain duplicate actions
+ bindings.clear();
+
+ for (Ref<OpenXRIPBinding> binding : p_bindings) {
+ String binding_path = binding->get_binding_path();
+ if (binding_path.find_char(',') >= 0) {
+ // Convert old binding approach to new...
+ add_new_binding(binding->get_action(), binding_path);
+ } else {
+ add_binding(binding);
+ }
+ }
- bindings = p_bindings;
emit_changed();
}
@@ -161,10 +203,9 @@ Array OpenXRInteractionProfile::get_bindings() const {
return bindings;
}
-Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<OpenXRAction> p_action) const {
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->get_action() == p_action) {
+Ref<OpenXRIPBinding> OpenXRInteractionProfile::find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action && binding->get_binding_path() == p_binding_path) {
return binding;
}
}
@@ -172,11 +213,23 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<
return Ref<OpenXRIPBinding>();
}
+Vector<Ref<OpenXRIPBinding>> OpenXRInteractionProfile::get_bindings_for_action(const Ref<OpenXRAction> p_action) const {
+ Vector<Ref<OpenXRIPBinding>> ret_bindings;
+
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action) {
+ ret_bindings.push_back(binding);
+ }
+ }
+
+ return ret_bindings;
+}
+
void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) {
ERR_FAIL_COND(p_binding.is_null());
if (!bindings.has(p_binding)) {
- ERR_FAIL_COND_MSG(get_binding_for_action(p_binding->get_action()).is_valid(), "There is already a binding for this action in this interaction profile");
+ ERR_FAIL_COND_MSG(find_binding(p_binding->get_action(), p_binding->get_binding_path()).is_valid(), "There is already a binding for this action and binding path in this interaction profile.");
bindings.push_back(p_binding);
emit_changed();
@@ -191,11 +244,15 @@ void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) {
}
}
-void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths) {
// This is a helper function to help build our default action sets
- Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths);
- add_binding(binding);
+ PackedStringArray paths = p_paths.split(",", false);
+
+ for (const String &path : paths) {
+ Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, path);
+ add_binding(binding);
+ }
}
void OpenXRInteractionProfile::remove_binding_for_action(const Ref<OpenXRAction> p_action) {
diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h
index 479cc3c527..952f87a09d 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.h
+++ b/modules/openxr/action_map/openxr_interaction_profile.h
@@ -41,26 +41,29 @@ class OpenXRIPBinding : public Resource {
private:
Ref<OpenXRAction> action;
- PackedStringArray paths;
+ String binding_path;
protected:
static void _bind_methods();
public:
- static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Helper function for adding a new binding
+ static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path); // Helper function for adding a new binding.
- void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding
- Ref<OpenXRAction> get_action() const; // Get the action for this binding
+ void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding.
+ Ref<OpenXRAction> get_action() const; // Get the action for this binding.
- int get_path_count() const; // Get the number of io paths
- void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource)
- PackedStringArray get_paths() const; // Get our paths (for saving to resource)
+ void set_binding_path(const String &path);
+ String get_binding_path() const;
- void parse_paths(const String p_paths); // Parse a comma separated string of io paths.
-
- bool has_path(const String p_path) const; // Has this io path
- void add_path(const String p_path); // Add an io path
- void remove_path(const String p_path); // Remove an io path
+ // Deprecated.
+#ifndef DISABLE_DEPRECATED
+ void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource), needed for loading old action maps.
+ PackedStringArray get_paths() const; // Get our paths (for saving to resource), needed for converted old action maps.
+ int get_path_count() const; // Get the number of io paths.
+ bool has_path(const String p_path) const; // Has this io path.
+ void add_path(const String p_path); // Add an io path.
+ void remove_path(const String p_path); // Remove an io path.
+#endif // DISABLE_DEPRECATED
// TODO add validation that we can display in the interface that checks if no two paths belong to the same top level path
@@ -88,11 +91,12 @@ public:
void set_bindings(Array p_bindings); // Set the bindings (for loading from a resource)
Array get_bindings() const; // Get the bindings (for saving to a resource)
- Ref<OpenXRIPBinding> get_binding_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
+ Ref<OpenXRIPBinding> find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const; // Get our binding record
+ Vector<Ref<OpenXRIPBinding>> get_bindings_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
void add_binding(Ref<OpenXRIPBinding> p_binding); // Add a binding object
void remove_binding(Ref<OpenXRIPBinding> p_binding); // Remove a binding object
- void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Create a new binding for this profile
+ void add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths); // Create a new binding for this profile
void remove_binding_for_action(const Ref<OpenXRAction> p_action); // Remove all bindings for this action
bool has_binding_for_action(const Ref<OpenXRAction> p_action); // Returns true if we have a binding for this action
diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
index 341b50065c..cfc7cd4d97 100644
--- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
+++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
@@ -29,7 +29,7 @@
<method name="is_natively_supported" qualifiers="const">
<return type="bool" />
<description>
- Returns true if the OpenXR runtime natively supports this composition layer type.
+ Returns [code]true[/code] if the OpenXR runtime natively supports this composition layer type.
[b]Note:[/b] This will only return an accurate result after the OpenXR session has started.
</description>
</method>
diff --git a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
index 813c9d582e..c174ee4d69 100644
--- a/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRExtensionWrapperExtension.xml
@@ -90,6 +90,21 @@
Called right after the main swapchains are (re)created.
</description>
</method>
+ <method name="_on_post_draw_viewport" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="viewport" type="RID" />
+ <description>
+ Called right after the given viewport is rendered.
+ [b]Note:[/b] The draw commands might only be queued at this point, not executed.
+ </description>
+ </method>
+ <method name="_on_pre_draw_viewport" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="viewport" type="RID" />
+ <description>
+ Called right before the given viewport is rendered.
+ </description>
+ </method>
<method name="_on_pre_render" qualifiers="virtual">
<return type="void" />
<description>
@@ -220,7 +235,7 @@
<return type="int" />
<param index="0" name="next_pointer" type="void*" />
<description>
- Adds additional data structures when interogating OpenXR system abilities.
+ Adds additional data structures when querying OpenXR system abilities.
</description>
</method>
<method name="_set_viewport_composition_layer_and_get_next_pointer" qualifiers="virtual">
diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml
index f274f0868e..ddd6fbe268 100644
--- a/modules/openxr/doc_classes/OpenXRIPBinding.xml
+++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml
@@ -4,32 +4,32 @@
Defines a binding between an [OpenXRAction] and an XR input or output.
</brief_description>
<description>
- This binding resource binds an [OpenXRAction] to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger".
+ This binding resource binds an [OpenXRAction] to an input or output. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". This would require two binding entries.
</description>
<tutorials>
</tutorials>
<methods>
- <method name="add_path">
+ <method name="add_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
Add an input/output path to this binding.
</description>
</method>
- <method name="get_path_count" qualifiers="const">
+ <method name="get_path_count" qualifiers="const" deprecated="Binding is for a single path.">
<return type="int" />
<description>
Get the number of input/output paths in this binding.
</description>
</method>
- <method name="has_path" qualifiers="const">
+ <method name="has_path" qualifiers="const" deprecated="Binding is for a single path.">
<return type="bool" />
<param index="0" name="path" type="String" />
<description>
Returns [code]true[/code] if this input/output path is part of this binding.
</description>
</method>
- <method name="remove_path">
+ <method name="remove_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
@@ -39,9 +39,13 @@
</methods>
<members>
<member name="action" type="OpenXRAction" setter="set_action" getter="get_action">
- [OpenXRAction] that is bound to these paths.
+ [OpenXRAction] that is bound to [member binding_path].
</member>
- <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()">
+ <member name="binding_path" type="String" setter="set_binding_path" getter="get_binding_path" default="&quot;&quot;">
+ Binding path that defines the input or output bound to [member action].
+ [b]Note:[/b] Binding paths are suggestions, an XR runtime may choose to bind the action to a different input or output emulating this input or output.
+ </member>
+ <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" deprecated="Use [member binding_path] instead.">
Paths that define the inputs or outputs bound on the device.
</member>
</members>
diff --git a/modules/openxr/editor/openxr_action_editor.cpp b/modules/openxr/editor/openxr_action_editor.cpp
index 63162ba3dc..dd5f6378f4 100644
--- a/modules/openxr/editor/openxr_action_editor.cpp
+++ b/modules/openxr/editor/openxr_action_editor.cpp
@@ -41,7 +41,7 @@ void OpenXRActionEditor::_bind_methods() {
}
void OpenXRActionEditor::_theme_changed() {
- rem_action->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
+ rem_action->set_button_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
}
void OpenXRActionEditor::_notification(int p_what) {
diff --git a/modules/openxr/editor/openxr_action_editor.h b/modules/openxr/editor/openxr_action_editor.h
index 11d1fd657a..b672fe7340 100644
--- a/modules/openxr/editor/openxr_action_editor.h
+++ b/modules/openxr/editor/openxr_action_editor.h
@@ -68,7 +68,7 @@ protected:
void _do_set_action_type(OpenXRAction::ActionType p_action_type);
public:
- Ref<OpenXRAction> get_action() { return action; };
+ Ref<OpenXRAction> get_action() { return action; }
OpenXRActionEditor(Ref<OpenXRAction> p_action);
};
diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp
index 0c55592707..e5e034ef08 100644
--- a/modules/openxr/editor/openxr_action_set_editor.cpp
+++ b/modules/openxr/editor/openxr_action_set_editor.cpp
@@ -46,16 +46,16 @@ void OpenXRActionSetEditor::_bind_methods() {
void OpenXRActionSetEditor::_set_fold_icon() {
if (is_expanded) {
- fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), EditorStringName(EditorIcons)));
+ fold_btn->set_button_icon(get_theme_icon(SNAME("GuiTreeArrowDown"), EditorStringName(EditorIcons)));
} else {
- fold_btn->set_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons)));
+ fold_btn->set_button_icon(get_theme_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons)));
}
}
void OpenXRActionSetEditor::_theme_changed() {
_set_fold_icon();
- add_action->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
- rem_action_set->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
+ add_action->set_button_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ rem_action_set->set_button_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
}
void OpenXRActionSetEditor::_notification(int p_what) {
diff --git a/modules/openxr/editor/openxr_action_set_editor.h b/modules/openxr/editor/openxr_action_set_editor.h
index 3261f3a29e..21437aa631 100644
--- a/modules/openxr/editor/openxr_action_set_editor.h
+++ b/modules/openxr/editor/openxr_action_set_editor.h
@@ -87,7 +87,7 @@ protected:
void _do_remove_action_editor(OpenXRActionEditor *p_action_editor);
public:
- Ref<OpenXRActionSet> get_action_set() { return action_set; };
+ Ref<OpenXRActionSet> get_action_set() { return action_set; }
void set_focus_on_entry();
void remove_all_actions();
diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
index 651171358c..a390919856 100644
--- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp
+++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
@@ -73,17 +73,19 @@ void OpenXRInteractionProfileEditorBase::_add_binding(const String p_action, con
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_null()) {
// create a new binding
binding.instantiate();
binding->set_action(action);
+ binding->set_binding_path(p_path);
+
+ // add it to our interaction profile
interaction_profile->add_binding(binding);
interaction_profile->set_edited(true);
- }
- binding->add_path(p_path);
- binding->set_edited(true);
+ binding->set_edited(true);
+ }
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -98,15 +100,10 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_valid()) {
- binding->remove_path(p_path);
- binding->set_edited(true);
-
- if (binding->get_path_count() == 0) {
- interaction_profile->remove_binding(binding);
- interaction_profile->set_edited(true);
- }
+ interaction_profile->remove_binding(binding);
+ interaction_profile->set_edited(true);
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -116,21 +113,22 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
}
void OpenXRInteractionProfileEditorBase::remove_all_bindings_for_action(Ref<OpenXRAction> p_action) {
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(p_action);
- if (binding.is_valid()) {
+ Vector<Ref<OpenXRIPBinding>> bindings = interaction_profile->get_bindings_for_action(p_action);
+ if (bindings.size() > 0) {
String action_name = p_action->get_name_with_set();
// for our undo/redo we process all paths
undo_redo->create_action(TTR("Remove action from interaction profile"));
- PackedStringArray paths = binding->get_paths();
- for (const String &path : paths) {
- undo_redo->add_do_method(this, "_remove_binding", action_name, path);
- undo_redo->add_undo_method(this, "_add_binding", action_name, path);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ undo_redo->add_do_method(this, "_remove_binding", action_name, binding->get_binding_path());
+ undo_redo->add_undo_method(this, "_add_binding", action_name, binding->get_binding_path());
}
undo_redo->commit_action(false);
// but we take a shortcut here :)
- interaction_profile->remove_binding(binding);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ interaction_profile->remove_binding(binding);
+ }
interaction_profile->set_edited(true);
// Update our toplevel paths
@@ -220,7 +218,7 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
path_hb->add_child(type_label);
Button *path_add = memnew(Button);
- path_add->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
+ path_add->set_button_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
path_add->set_flat(true);
path_add->connect(SceneStringName(pressed), callable_mp(this, &OpenXRInteractionProfileEditor::select_action_for).bind(String(p_io_path->openxr_path)));
path_hb->add_child(path_add);
@@ -228,9 +226,8 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
if (interaction_profile.is_valid()) {
String io_path = String(p_io_path->openxr_path);
Array bindings = interaction_profile->get_bindings();
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->has_path(io_path)) {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_binding_path() == io_path) {
Ref<OpenXRAction> action = binding->get_action();
HBoxContainer *action_hb = memnew(HBoxContainer);
@@ -248,7 +245,7 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
Button *action_rem = memnew(Button);
action_rem->set_flat(true);
- action_rem->set_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
+ action_rem->set_button_icon(get_theme_icon(SNAME("Remove"), EditorStringName(EditorIcons)));
action_rem->connect(SceneStringName(pressed), callable_mp((OpenXRInteractionProfileEditor *)this, &OpenXRInteractionProfileEditor::_on_remove_pressed).bind(action->get_name_with_set(), String(p_io_path->openxr_path)));
action_hb->add_child(action_rem);
}
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index dc30b95b27..1e3490d1ed 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -281,7 +281,7 @@ void OpenXRViewportCompositionLayerProvider::create_android_surface() {
composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface);
if (surface) {
- android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface)));
+ android_surface.surface.instantiate(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface);
}
}
#endif
@@ -341,7 +341,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
}
XrSwapchainSubImage subimage = {
- 0, // swapchain
+ 0, // swapchain // NOLINT(modernize-use-nullptr) - 32-bit uses non-pointer uint64
{ { 0, 0 }, { 0, 0 } }, // imageRect
0, // imageArrayIndex
};
diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
index 10dbe629f7..cb7f72d02d 100644
--- a/modules/openxr/extensions/openxr_debug_utils_extension.cpp
+++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
@@ -173,7 +173,7 @@ void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_nam
const XrDebugUtilsLabelEXT session_active_region_label = {
XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
- NULL, // next
+ nullptr, // next
p_label_name, // labelName
};
@@ -199,7 +199,7 @@ void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) {
const XrDebugUtilsLabelEXT session_active_region_label = {
XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
- NULL, // next
+ nullptr, // next
p_label_name, // labelName
};
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
index 07ca476421..fb8d1b9d69 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp
@@ -51,6 +51,8 @@ void OpenXRExtensionWrapperExtension::_bind_methods() {
GDVIRTUAL_BIND(_on_process);
GDVIRTUAL_BIND(_on_pre_render);
GDVIRTUAL_BIND(_on_main_swapchains_created);
+ GDVIRTUAL_BIND(_on_pre_draw_viewport, "viewport");
+ GDVIRTUAL_BIND(_on_post_draw_viewport, "viewport");
GDVIRTUAL_BIND(_on_session_destroyed);
GDVIRTUAL_BIND(_on_state_idle);
GDVIRTUAL_BIND(_on_state_ready);
@@ -208,6 +210,14 @@ void OpenXRExtensionWrapperExtension::on_session_destroyed() {
GDVIRTUAL_CALL(_on_session_destroyed);
}
+void OpenXRExtensionWrapperExtension::on_pre_draw_viewport(RID p_render_target) {
+ GDVIRTUAL_CALL(_on_pre_draw_viewport, p_render_target);
+}
+
+void OpenXRExtensionWrapperExtension::on_post_draw_viewport(RID p_render_target) {
+ GDVIRTUAL_CALL(_on_post_draw_viewport, p_render_target);
+}
+
void OpenXRExtensionWrapperExtension::on_state_idle() {
GDVIRTUAL_CALL(_on_state_idle);
}
@@ -298,8 +308,7 @@ void OpenXRExtensionWrapperExtension::register_extension_wrapper() {
OpenXRAPI::register_extension_wrapper(this);
}
-OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() :
- Object(), OpenXRExtensionWrapper() {
+OpenXRExtensionWrapperExtension::OpenXRExtensionWrapperExtension() {
openxr_api.instantiate();
}
diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.h b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
index 5cdf288c93..8fc6511277 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper_extension.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.h
@@ -88,6 +88,8 @@ public:
virtual void on_pre_render() override;
virtual void on_main_swapchains_created() override;
virtual void on_session_destroyed() override;
+ virtual void on_pre_draw_viewport(RID p_render_target) override;
+ virtual void on_post_draw_viewport(RID p_render_target) override;
GDVIRTUAL0(_on_register_metadata);
GDVIRTUAL0(_on_before_instance_created);
@@ -98,6 +100,8 @@ public:
GDVIRTUAL0(_on_pre_render);
GDVIRTUAL0(_on_main_swapchains_created);
GDVIRTUAL0(_on_session_destroyed);
+ GDVIRTUAL1(_on_pre_draw_viewport, RID);
+ GDVIRTUAL1(_on_post_draw_viewport, RID);
virtual void on_state_idle() override;
virtual void on_state_ready() override;
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
index caded14ca7..4fcb3f7b75 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
@@ -64,6 +64,9 @@ HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() {
#else
request_extensions[XR_KHR_OPENGL_ENABLE_EXTENSION_NAME] = nullptr;
#endif
+#if defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED) && defined(WAYLAND_ENABLED)
+ request_extensions[XR_MNDX_EGL_ENABLE_EXTENSION_NAME] = &egl_extension_enabled;
+#endif
return request_extensions;
}
@@ -128,9 +131,14 @@ bool OpenXROpenGLExtension::check_graphics_api_support(XrVersion p_desired_versi
XrGraphicsBindingOpenGLWin32KHR OpenXROpenGLExtension::graphics_binding_gl;
#elif defined(ANDROID_ENABLED)
XrGraphicsBindingOpenGLESAndroidKHR OpenXROpenGLExtension::graphics_binding_gl;
-#elif defined(X11_ENABLED)
+#elif defined(LINUXBSD_ENABLED)
+#ifdef X11_ENABLED
XrGraphicsBindingOpenGLXlibKHR OpenXROpenGLExtension::graphics_binding_gl;
#endif
+#if defined(EGL_ENABLED) && defined(WAYLAND_ENABLED)
+XrGraphicsBindingEGLMNDX OpenXROpenGLExtension::graphics_binding_egl;
+#endif
+#endif
void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) {
XrVersion desired_version = XR_MAKE_VERSION(3, 3, 0);
@@ -142,10 +150,6 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
DisplayServer *display_server = DisplayServer::get_singleton();
-#ifdef WAYLAND_ENABLED
- ERR_FAIL_COND_V_MSG(display_server->get_name() == "Wayland", p_next_pointer, "OpenXR is not yet supported on OpenGL Wayland.");
-#endif
-
#ifdef WIN32
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR,
graphics_binding_gl.next = p_next_pointer;
@@ -159,7 +163,23 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
graphics_binding_gl.display = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE);
graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122
graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
-#elif defined(X11_ENABLED)
+#else
+#if defined(EGL_ENABLED) && defined(WAYLAND_ENABLED)
+ if (display_server->get_name() == "Wayland") {
+ ERR_FAIL_COND_V_MSG(!egl_extension_enabled, p_next_pointer, "OpenXR cannot initialize on Wayland without the XR_MNDX_egl_enable extension.");
+
+ graphics_binding_egl.type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX;
+ graphics_binding_egl.next = p_next_pointer;
+
+ graphics_binding_egl.getProcAddress = eglGetProcAddress;
+ graphics_binding_egl.display = (void *)display_server->window_get_native_handle(DisplayServer::EGL_DISPLAY);
+ graphics_binding_egl.config = (void *)display_server->window_get_native_handle(DisplayServer::EGL_CONFIG);
+ graphics_binding_egl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
+
+ return &graphics_binding_egl;
+ }
+#endif
+#if defined(X11_ENABLED)
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
graphics_binding_gl.next = p_next_pointer;
@@ -173,10 +193,15 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
// spec says to use proper values but runtimes don't care
graphics_binding_gl.visualid = 0;
- graphics_binding_gl.glxFBConfig = 0;
+ graphics_binding_gl.glxFBConfig = nullptr;
+#endif
#endif
+#if defined(WIN32) || defined(ANDROID_ENABLED) || defined(X11_ENABLED)
return &graphics_binding_gl;
+#else
+ return p_next_pointer;
+#endif
}
void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) {
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h
index 8da3ca48f4..f4a73c667b 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h
@@ -64,9 +64,18 @@ private:
static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl;
#elif defined(ANDROID_ENABLED)
static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl;
-#else // Linux/X11
+#elif defined(LINUXBSD_ENABLED)
+#ifdef X11_ENABLED
static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl;
#endif
+#if defined(EGL_ENABLED) && defined(WAYLAND_ENABLED)
+ static XrGraphicsBindingEGLMNDX graphics_binding_egl;
+
+ bool egl_extension_enabled = false;
+#endif
+#else
+#error "OpenXR with OpenGL isn't supported on this platform"
+#endif
struct SwapchainGraphicsData {
bool is_multiview;
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index c67be5a2b3..1775541757 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -1294,7 +1294,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
}
return true;
-};
+}
void OpenXRAPI::destroy_session() {
// TODO need to figure out if we're still rendering our current frame
@@ -2036,8 +2036,9 @@ bool OpenXRAPI::poll_events() {
if (local_floor_emulation.enabled) {
local_floor_emulation.should_reset_floor_height = true;
}
- if (event->poseValid && xr_interface) {
- xr_interface->on_pose_recentered();
+
+ if (xr_interface) {
+ xr_interface->on_reference_space_change_pending();
}
} break;
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: {
@@ -2353,7 +2354,7 @@ void OpenXRAPI::post_draw_viewport(RID p_render_target) {
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_post_draw_viewport(p_render_target);
}
-};
+}
void OpenXRAPI::end_frame() {
XrResult result;
@@ -2752,8 +2753,9 @@ void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_l
}
bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const {
- if (XR_SUCCEEDED(result))
+ if (XR_SUCCEEDED(result)) {
return true;
+ }
char resultString[XR_MAX_RESULT_STRING_SIZE];
xrResultToString(instance, result, resultString);
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 0d1e4eb414..ecffce1816 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -396,12 +396,12 @@ private:
}
public:
- XrInstance get_instance() const { return instance; };
- XrSystemId get_system_id() const { return system_id; };
- XrSession get_session() const { return session; };
- OpenXRGraphicsExtensionWrapper *get_graphics_extension() const { return graphics_extension; };
- String get_runtime_name() const { return runtime_name; };
- String get_runtime_version() const { return runtime_version; };
+ XrInstance get_instance() const { return instance; }
+ XrSystemId get_system_id() const { return system_id; }
+ XrSession get_session() const { return session; }
+ OpenXRGraphicsExtensionWrapper *get_graphics_extension() const { return graphics_extension; }
+ String get_runtime_name() const { return runtime_name; }
+ String get_runtime_version() const { return runtime_version; }
// helper method to convert an XrPosef to a Transform3D
Transform3D transform_from_pose(const XrPosef &p_pose);
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 73ac529537..68e04694e3 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -160,11 +160,11 @@ void OpenXRInterface::_bind_methods() {
StringName OpenXRInterface::get_name() const {
return StringName("OpenXR");
-};
+}
uint32_t OpenXRInterface::get_capabilities() const {
return XRInterface::XR_VR + XRInterface::XR_STEREO;
-};
+}
PackedStringArray OpenXRInterface::get_suggested_tracker_names() const {
// These are hardcoded in OpenXR, note that they will only be available if added to our action map
@@ -300,10 +300,7 @@ void OpenXRInterface::_load_action_map() {
continue;
}
- PackedStringArray paths = xr_binding->get_paths();
- for (int k = 0; k < paths.size(); k++) {
- openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]);
- }
+ openxr_api->interaction_profile_add_binding(ip, action->action_rid, xr_binding->get_binding_path());
}
// Now submit our suggestions
@@ -614,7 +611,7 @@ bool OpenXRInterface::initialize_on_startup() const {
bool OpenXRInterface::is_initialized() const {
return initialized;
-};
+}
bool OpenXRInterface::initialize() {
XRServer *xr_server = XRServer::get_singleton();
@@ -1137,6 +1134,12 @@ void OpenXRInterface::process() {
if (head.is_valid()) {
head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence);
}
+
+ if (reference_stage_changing) {
+ // Now that we have updated tracking information in our updated reference space, trigger our pose recentered signal.
+ emit_signal(SNAME("pose_recentered"));
+ reference_stage_changing = false;
+ }
}
void OpenXRInterface::pre_render() {
@@ -1318,8 +1321,8 @@ void OpenXRInterface::on_state_exiting() {
emit_signal(SNAME("instance_exiting"));
}
-void OpenXRInterface::on_pose_recentered() {
- emit_signal(SNAME("pose_recentered"));
+void OpenXRInterface::on_reference_space_change_pending() {
+ reference_stage_changing = true;
}
void OpenXRInterface::on_refresh_rate_changes(float p_new_rate) {
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index f0ee0dc3c4..d1bf2aaf78 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -70,6 +70,7 @@ class OpenXRInterface : public XRInterface {
private:
OpenXRAPI *openxr_api = nullptr;
bool initialized = false;
+ bool reference_stage_changing = false;
XRInterface::TrackingStatus tracking_state;
// At a minimum we need a tracker for our head
@@ -207,7 +208,7 @@ public:
void on_state_stopping();
void on_state_loss_pending();
void on_state_exiting();
- void on_pose_recentered();
+ void on_reference_space_change_pending();
void on_refresh_rate_changes(float p_new_rate);
void tracker_profile_changed(RID p_tracker, RID p_interaction_profile);
diff --git a/modules/openxr/openxr_platform_inc.h b/modules/openxr/openxr_platform_inc.h
index 957a87cbb2..09bc0c89a2 100644
--- a/modules/openxr/openxr_platform_inc.h
+++ b/modules/openxr/openxr_platform_inc.h
@@ -49,6 +49,13 @@
#else
#define XR_USE_GRAPHICS_API_OPENGL
#endif // ANDROID_ENABLED
+#if defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED)
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#else
+#include <EGL/egl.h>
+#endif // GLAD_ENABLED
+#endif // defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED)
#ifdef X11_ENABLED
#define GL_GLEXT_PROTOTYPES 1
#define GL3_PROTOTYPES 1
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index 697369d516..bc429e4632 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -56,6 +56,10 @@ OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_c
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
+ if (openxr_api) {
+ openxr_session_running = openxr_api->is_running();
+ }
+
Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR");
if (openxr_interface.is_valid()) {
openxr_interface->connect("session_begun", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_begun));
diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp
index 94d8b267d1..3609f5a554 100644
--- a/modules/raycast/raycast_occlusion_cull.cpp
+++ b/modules/raycast/raycast_occlusion_cull.cpp
@@ -90,18 +90,11 @@ void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform3D
td.camera_dir = -p_cam_transform.basis.get_column(2);
td.camera_orthogonal = p_cam_orthogonal;
- Projection inv_camera_matrix = p_cam_projection.inverse();
- Vector3 camera_corner_proj = Vector3(-1.0f, -1.0f, -1.0f);
- Vector3 camera_corner_view = inv_camera_matrix.xform(camera_corner_proj);
- td.pixel_corner = p_cam_transform.xform(camera_corner_view);
-
- Vector3 top_corner_proj = Vector3(-1.0f, 1.0f, -1.0f);
- Vector3 top_corner_view = inv_camera_matrix.xform(top_corner_proj);
- Vector3 top_corner_world = p_cam_transform.xform(top_corner_view);
-
- Vector3 left_corner_proj = Vector3(1.0f, -1.0f, -1.0f);
- Vector3 left_corner_view = inv_camera_matrix.xform(left_corner_proj);
- Vector3 left_corner_world = p_cam_transform.xform(left_corner_view);
+ // Calculate the world coordinates of the viewport.
+ Vector2 viewport_half = p_cam_projection.get_viewport_half_extents();
+ td.pixel_corner = p_cam_transform.xform(Vector3(-viewport_half.x, -viewport_half.y, -p_cam_projection.get_z_near()));
+ Vector3 top_corner_world = p_cam_transform.xform(Vector3(-viewport_half.x, viewport_half.y, -p_cam_projection.get_z_near()));
+ Vector3 left_corner_world = p_cam_transform.xform(Vector3(viewport_half.x, -viewport_half.y, -p_cam_projection.get_z_near()));
td.pixel_u_interp = left_corner_world - td.pixel_corner;
td.pixel_v_interp = top_corner_world - td.pixel_corner;
@@ -140,7 +133,7 @@ void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const CameraRa
Vector3 dir;
if (p_data->camera_orthogonal) {
- dir = -p_data->camera_dir;
+ dir = p_data->camera_dir;
tile.ray.org_x[j] = pixel_pos.x - dir.x * p_data->z_near;
tile.ray.org_y[j] = pixel_pos.y - dir.y * p_data->z_near;
tile.ray.org_z[j] = pixel_pos.z - dir.z * p_data->z_near;
@@ -181,17 +174,7 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_di
}
int k = tile_i * TILE_SIZE + tile_j;
int tile_index = i * tile_grid_size.x + j;
- float d = camera_rays[tile_index].ray.tfar[k];
-
- if (!p_orthogonal) {
- const float &dir_x = camera_rays[tile_index].ray.dir_x[k];
- const float &dir_y = camera_rays[tile_index].ray.dir_y[k];
- const float &dir_z = camera_rays[tile_index].ray.dir_z[k];
- float cos_theta = p_camera_dir.x * dir_x + p_camera_dir.y * dir_y + p_camera_dir.z * dir_z;
- d *= cos_theta;
- }
-
- mips[0][y * buffer_size.x + x] = d;
+ mips[0][y * buffer_size.x + x] = camera_rays[tile_index].ray.tfar[k];
}
}
}
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index e12dc43b6f..66e7cd5a0b 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -34,14 +34,14 @@
print(result.get_string("digit"))
# Would print 01 03 0 3f 42
[/codeblock]
- [b]Example of splitting a string using a RegEx:[/b]
+ [b]Example:[/b] Split a string using a RegEx:
[codeblock]
var regex = RegEx.new()
regex.compile("\\S+") # Negated whitespace character class.
var results = []
for result in regex.search_all("One Two \n\tThree"):
results.push_back(result.get_string())
- # The `results` array now contains "One", "Two", "Three".
+ # The `results` array now contains "One", "Two", and "Three".
[/codeblock]
[b]Note:[/b] Godot's regex implementation is based on the [url=https://www.pcre.org/]PCRE2[/url] library. You can view the full pattern reference [url=https://www.pcre.org/current/doc/html/pcre2pattern.html]here[/url].
[b]Tip:[/b] You can use [url=https://regexr.com/]Regexr[/url] to test regular expressions online.
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index d903137195..1f7d5504b6 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -53,7 +53,7 @@ void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &p_colo
int pos = r_string.find(p_prefix);
while (pos != -1) {
pos += prefix_len; // Skip prefix.
- int end_pos = r_string.find("\"", pos);
+ int end_pos = r_string.find_char('"', pos);
ERR_FAIL_COND_MSG(end_pos == -1, vformat("Malformed SVG string after property \"%s\".", p_prefix));
const String color_code = r_string.substr(pos, end_pos - pos);
if (color_code != "none" && !color_code.begins_with("url(")) {
@@ -104,51 +104,33 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
picture->size(width, height);
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- // Note: memalloc here, be sure to memfree before any return.
- uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
+ Vector<uint8_t> buffer;
+ buffer.resize(sizeof(uint32_t) * width * height);
- tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
+ tvg::Result res = sw_canvas->target((uint32_t *)buffer.ptrw(), width, width, height, tvg::SwCanvas::ABGR8888S);
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
}
res = sw_canvas->push(std::move(picture));
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't insert ThorVG picture on canvas.");
}
res = sw_canvas->draw();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas.");
}
res = sw_canvas->sync();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't sync ThorVG canvas.");
}
- Vector<uint8_t> image;
- image.resize(width * height * sizeof(uint32_t));
-
- for (uint32_t y = 0; y < height; y++) {
- for (uint32_t x = 0; x < width; x++) {
- uint32_t n = buffer[y * width + x];
- const size_t offset = sizeof(uint32_t) * width * y + sizeof(uint32_t) * x;
- image.write[offset + 0] = (n >> 16) & 0xff;
- image.write[offset + 1] = (n >> 8) & 0xff;
- image.write[offset + 2] = n & 0xff;
- image.write[offset + 3] = (n >> 24) & 0xff;
- }
- }
+ p_image->set_data(width, height, false, Image::FORMAT_RGBA8, buffer);
res = sw_canvas->clear(true);
- memfree(buffer);
- p_image->set_data(width, height, false, Image::FORMAT_RGBA8, image);
return OK;
}
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index f1dcef6667..f6aa242698 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -477,7 +477,7 @@ if env["builtin_icu4c"]:
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
- icu_data_name = "icudt75l.dat"
+ icu_data_name = "icudt76l.dat"
if env.editor_build:
env_icu.Depends("#thirdparty/icu4c/icudata.gen.h", "#thirdparty/icu4c/" + icu_data_name)
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 4c305f3b3f..fb705eaa50 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -717,7 +717,7 @@ thirdparty_icu_sources = [
]
thirdparty_icu_sources = [thirdparty_icu_dir + file for file in thirdparty_icu_sources]
-icu_data_name = "icudt75l.dat"
+icu_data_name = "icudt76l.dat"
if env["static_icu_data"]:
env_icu.Depends("../../../thirdparty/icu4c/icudata.gen.h", "../../../thirdparty/icu4c/" + icu_data_name)
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 1c6e62650a..8d557aae2a 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -5264,42 +5264,44 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
- const Array &fonts = spans[spans.size() - 1].fonts;
- for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], sd->el_char)) {
- dot_gl_font_rid = fonts[i];
- found_el_char = true;
- break;
- }
- }
- if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- const char32_t u32str[] = { sd->el_char, 0 };
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
- if (rid.is_valid()) {
- dot_gl_font_rid = rid;
- found_el_char = true;
- }
- }
- } else {
- found_el_char = true;
- }
- if (!found_el_char) {
- bool found_dot_char = false;
- dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (add_ellipsis || enforce_ellipsis) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
- found_dot_char = true;
+ found_el_char = true;
break;
}
}
- if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
}
}
}
@@ -5315,8 +5317,8 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
- Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+ int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
+ Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 9c8d75b358..def57b9372 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -96,6 +96,11 @@ using namespace godot;
// Thirdparty headers.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+
#include <unicode/ubidi.h>
#include <unicode/ubrk.h>
#include <unicode/uchar.h>
@@ -109,6 +114,10 @@ using namespace godot;
#include <unicode/ustring.h>
#include <unicode/utypes.h>
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
#ifdef MODULE_FREETYPE_ENABLED
#include <ft2build.h>
#include FT_FREETYPE_H
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index ce95622f09..ae636a3151 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -156,11 +156,11 @@ bool TextServerFallback::_has(const RID &p_rid) {
String TextServerFallback::_get_support_data_filename() const {
return "";
-};
+}
String TextServerFallback::_get_support_data_info() const {
return "Not supported";
-};
+}
bool TextServerFallback::_load_support_data(const String &p_filename) {
return false; // No extra data used.
@@ -4077,42 +4077,44 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
- const Array &fonts = spans[spans.size() - 1].fonts;
- for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], sd->el_char)) {
- dot_gl_font_rid = fonts[i];
- found_el_char = true;
- break;
- }
- }
- if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- const char32_t u32str[] = { sd->el_char, 0 };
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
- if (rid.is_valid()) {
- dot_gl_font_rid = rid;
- found_el_char = true;
- }
- }
- } else {
- found_el_char = true;
- }
- if (!found_el_char) {
- bool found_dot_char = false;
- dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
- if (!_font_has_char(dot_gl_font_rid, '.')) {
+ if (add_ellipsis || enforce_ellipsis) {
+ if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
- if (_font_has_char(fonts[i], '.')) {
+ if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i];
- found_dot_char = true;
+ found_el_char = true;
break;
}
}
- if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
- RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ const char32_t u32str[] = { sd->el_char, 0 };
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
+ found_el_char = true;
+ }
+ }
+ } else {
+ found_el_char = true;
+ }
+ if (!found_el_char) {
+ bool found_dot_char = false;
+ dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+ if (!_font_has_char(dot_gl_font_rid, '.')) {
+ const Array &fonts = spans[spans.size() - 1].fonts;
+ for (int i = 0; i < fonts.size(); i++) {
+ if (_font_has_char(fonts[i], '.')) {
+ dot_gl_font_rid = fonts[i];
+ found_dot_char = true;
+ break;
+ }
+ }
+ if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
+ RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
+ if (rid.is_valid()) {
+ dot_gl_font_rid = rid;
+ }
}
}
}
@@ -4128,8 +4130,8 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
}
}
- int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
- Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+ int32_t dot_gl_idx = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
+ Vector2 dot_adv = ((add_ellipsis || enforce_ellipsis) && dot_gl_font_rid.is_valid()) ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
@@ -4728,7 +4730,7 @@ void TextServerFallback::_update_settings() {
TextServerFallback::TextServerFallback() {
_insert_feature_sets();
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings));
-};
+}
void TextServerFallback::_cleanup() {
for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) {
@@ -4747,4 +4749,4 @@ TextServerFallback::~TextServerFallback() {
FT_Done_FreeType(ft_library);
}
#endif
-};
+}
diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp
index e2bb89811c..b205dbbbf2 100644
--- a/modules/tga/image_loader_tga.cpp
+++ b/modules/tga/image_loader_tga.cpp
@@ -32,6 +32,7 @@
#include "core/error/error_macros.h"
#include "core/io/file_access_memory.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index d964fd7627..372885b0b4 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -31,6 +31,7 @@
#include "video_stream_theora.h"
#include "core/config/project_settings.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "scene/resources/image_texture.h"
@@ -114,7 +115,7 @@ void VideoStreamPlaybackTheora::video_write() {
format = Image::FORMAT_RGBA8;
}
- Ref<Image> img = memnew(Image(size.x, size.y, 0, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation
+ Ref<Image> img = memnew(Image(size.x, size.y, false, Image::FORMAT_RGBA8, frame_data)); //zero copy image creation
texture->update(img); //zero copy send to rendering server
@@ -628,7 +629,7 @@ void VideoStreamPlaybackTheora::_streaming_thread(void *ud) {
#endif
VideoStreamPlaybackTheora::VideoStreamPlaybackTheora() {
- texture = Ref<ImageTexture>(memnew(ImageTexture));
+ texture.instantiate();
#ifdef THEORA_USE_THREAD_STREAMING
int rb_power = nearest_shift(RB_SIZE_KB * 1024);
@@ -644,7 +645,7 @@ VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() {
memdelete(thread_sem);
#endif
clear();
-};
+}
void VideoStreamTheora::_bind_methods() {}
diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp
index 31c1e06dee..b0a977f647 100644
--- a/modules/tinyexr/image_saver_tinyexr.cpp
+++ b/modules/tinyexr/image_saver_tinyexr.cpp
@@ -31,6 +31,7 @@
#include "image_saver_tinyexr.h"
#include "core/math/math_funcs.h"
+#include "core/os/os.h"
#include <zlib.h> // Should come before including tinyexr.
diff --git a/modules/tinyexr/image_saver_tinyexr.h b/modules/tinyexr/image_saver_tinyexr.h
index 058eeae58e..014c2f2e19 100644
--- a/modules/tinyexr/image_saver_tinyexr.h
+++ b/modules/tinyexr/image_saver_tinyexr.h
@@ -31,7 +31,7 @@
#ifndef IMAGE_SAVER_TINYEXR_H
#define IMAGE_SAVER_TINYEXR_H
-#include "core/os/os.h"
+#include "core/io/image.h"
Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale);
diff --git a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
index 8ae63140f5..cede03c73a 100644
--- a/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
+++ b/modules/vorbis/doc_classes/ResourceImporterOggVorbis.xml
@@ -29,15 +29,15 @@
</methods>
<members>
<member name="bar_beats" type="int" setter="" getter="" default="4">
- The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The number of bars within a single beat in the audio track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member bar_beats] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="beat_count" type="int" setter="" getter="" default="0">
- The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The beat count of the audio track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member beat_count] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="bpm" type="float" setter="" getter="" default="0">
- The Beats Per Minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality (not implemented yet), not sound effects.
+ The beats per minute of the audio track. This should match the BPM measure that was used to compose the track. This is only relevant for music that wishes to make use of interactive music functionality, not sound effects.
A more convenient editor for [member bpm] is provided in the [b]Advanced Import Settings[/b] dialog, as it lets you preview your changes without having to reimport the audio.
</member>
<member name="loop" type="bool" setter="" getter="" default="false">
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
index 729a6f5561..a7423e2d7b 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -95,7 +95,7 @@ void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) {
}
#endif
-Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
+Error ResourceImporterOggVorbis::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
bool loop = p_options["loop"];
double loop_offset = p_options["loop_offset"];
double bpm = p_options["bpm"];
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.h b/modules/vorbis/resource_importer_ogg_vorbis.h
index 59ae3378a0..a4e4441d82 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.h
+++ b/modules/vorbis/resource_importer_ogg_vorbis.h
@@ -63,7 +63,9 @@ public:
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
- virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+
+ virtual bool can_import_threaded() const override { return true; }
ResourceImporterOggVorbis();
};
diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp
index 3a2ac5a90e..0284eec574 100644
--- a/modules/webp/webp_common.cpp
+++ b/modules/webp/webp_common.cpp
@@ -149,7 +149,7 @@ Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) {
ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image.");
- Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image));
+ Ref<Image> img = memnew(Image(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image));
return img;
}
diff --git a/modules/webrtc/webrtc_peer_connection_js.cpp b/modules/webrtc/webrtc_peer_connection_js.cpp
index 26256a4e30..19df397002 100644
--- a/modules/webrtc/webrtc_peer_connection_js.cpp
+++ b/modules/webrtc/webrtc_peer_connection_js.cpp
@@ -150,5 +150,5 @@ WebRTCPeerConnectionJS::~WebRTCPeerConnectionJS() {
godot_js_rtc_pc_destroy(_js_id);
_js_id = 0;
}
-};
+}
#endif
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index 0978e1fcee..b2e1cb345b 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -60,7 +60,7 @@
<member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
The inbound buffer size for connected peers. See [member WebSocketPeer.inbound_buffer_size] for more details.
</member>
- <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
+ <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="4096">
The maximum number of queued packets for connected peers. See [member WebSocketPeer.max_queued_packets] for more details.
</member>
<member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">
diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml
index 238dd30536..d329e21b88 100644
--- a/modules/websocket/doc_classes/WebSocketPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketPeer.xml
@@ -155,10 +155,14 @@
The extra HTTP headers to be sent during the WebSocket handshake.
[b]Note:[/b] Not supported in Web exports due to browsers' restrictions.
</member>
+ <member name="heartbeat_interval" type="float" setter="set_heartbeat_interval" getter="get_heartbeat_interval" default="0.0">
+ The interval (in seconds) at which the peer will automatically send WebSocket "ping" control frames. When set to [code]0[/code], no "ping" control frames will be sent.
+ [b]Note:[/b] Has no effect in Web exports due to browser restrictions.
+ </member>
<member name="inbound_buffer_size" type="int" setter="set_inbound_buffer_size" getter="get_inbound_buffer_size" default="65535">
The size of the input buffer in bytes (roughly the maximum amount of memory that will be allocated for the inbound packets).
</member>
- <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="2048">
+ <member name="max_queued_packets" type="int" setter="set_max_queued_packets" getter="get_max_queued_packets" default="4096">
The maximum amount of packets that will be allowed in the queues (both inbound and outbound).
</member>
<member name="outbound_buffer_size" type="int" setter="set_outbound_buffer_size" getter="get_outbound_buffer_size" default="65535">
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
index fe0bc594e6..89dc4fa36e 100644
--- a/modules/websocket/emws_peer.h
+++ b/modules/websocket/emws_peer.h
@@ -84,7 +84,7 @@ public:
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
- virtual int get_max_packet_size() const override { return packet_buffer.size(); };
+ virtual int get_max_packet_size() const override { return packet_buffer.size(); }
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
diff --git a/modules/websocket/packet_buffer.h b/modules/websocket/packet_buffer.h
index f98ee12ef9..4ab0579912 100644
--- a/modules/websocket/packet_buffer.h
+++ b/modules/websocket/packet_buffer.h
@@ -104,6 +104,14 @@ public:
return _queued;
}
+ int payload_space_left() const {
+ return _payload.space_left();
+ }
+
+ int packets_space_left() const {
+ return _packets.size() - _queued;
+ }
+
void clear() {
_payload.resize(0);
_packets.resize(0);
diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp
index 95a1a238e9..5c24b5d082 100644
--- a/modules/websocket/websocket_peer.cpp
+++ b/modules/websocket/websocket_peer.cpp
@@ -70,6 +70,9 @@ void WebSocketPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_max_queued_packets", "buffer_size"), &WebSocketPeer::set_max_queued_packets);
ClassDB::bind_method(D_METHOD("get_max_queued_packets"), &WebSocketPeer::get_max_queued_packets);
+ ClassDB::bind_method(D_METHOD("set_heartbeat_interval", "interval"), &WebSocketPeer::set_heartbeat_interval);
+ ClassDB::bind_method(D_METHOD("get_heartbeat_interval"), &WebSocketPeer::get_heartbeat_interval);
+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "supported_protocols"), "set_supported_protocols", "get_supported_protocols");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "handshake_headers"), "set_handshake_headers", "get_handshake_headers");
@@ -78,6 +81,8 @@ void WebSocketPeer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_queued_packets"), "set_max_queued_packets", "get_max_queued_packets");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "heartbeat_interval"), "set_heartbeat_interval", "get_heartbeat_interval");
+
BIND_ENUM_CONSTANT(WRITE_MODE_TEXT);
BIND_ENUM_CONSTANT(WRITE_MODE_BINARY);
@@ -151,3 +156,12 @@ void WebSocketPeer::set_max_queued_packets(int p_max_queued_packets) {
int WebSocketPeer::get_max_queued_packets() const {
return max_queued_packets;
}
+
+double WebSocketPeer::get_heartbeat_interval() const {
+ return heartbeat_interval_msec / 1000.0;
+}
+
+void WebSocketPeer::set_heartbeat_interval(double p_interval) {
+ ERR_FAIL_COND(p_interval < 0);
+ heartbeat_interval_msec = p_interval * 1000.0;
+}
diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h
index ef0197cf6c..4854122471 100644
--- a/modules/websocket/websocket_peer.h
+++ b/modules/websocket/websocket_peer.h
@@ -71,7 +71,8 @@ protected:
int outbound_buffer_size = DEFAULT_BUFFER_SIZE;
int inbound_buffer_size = DEFAULT_BUFFER_SIZE;
- int max_queued_packets = 2048;
+ int max_queued_packets = 4096;
+ uint64_t heartbeat_interval_msec = 0;
public:
static WebSocketPeer *create(bool p_notify_postinitialize = true) {
@@ -117,6 +118,9 @@ public:
void set_max_queued_packets(int p_max_queued_packets);
int get_max_queued_packets() const;
+ double get_heartbeat_interval() const;
+ void set_heartbeat_interval(double p_interval);
+
WebSocketPeer();
~WebSocketPeer();
};
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 0c0a046805..81e5673583 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -295,6 +295,7 @@ Error WSLPeer::_do_server_handshake() {
resolver.stop();
// Response sent, initialize wslay context.
wslay_event_context_server_init(&wsl_ctx, &_wsl_callbacks, this);
+ wslay_event_config_set_no_buffering(wsl_ctx, 1);
wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
packet_buffer.resize(inbound_buffer_size);
@@ -403,6 +404,7 @@ void WSLPeer::_do_client_handshake() {
ERR_FAIL_MSG("Invalid response headers.");
}
wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this);
+ wslay_event_config_set_no_buffering(wsl_ctx, 1);
wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
packet_buffer.resize(inbound_buffer_size);
@@ -568,8 +570,15 @@ ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data,
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
return -1;
}
+ // Make sure we don't read more than what our buffer can hold.
+ size_t buffer_limit = MIN(peer->in_buffer.payload_space_left(), peer->in_buffer.packets_space_left() * 2); // The minimum size of a websocket message is 2 bytes.
+ size_t to_read = MIN(len, buffer_limit);
+ if (to_read == 0) {
+ wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
+ return -1;
+ }
int read = 0;
- Error err = conn->get_partial_data(data, len, read);
+ Error err = conn->get_partial_data(data, to_read, read);
if (err != OK) {
print_verbose("Websocket get data error: " + itos(err) + ", read (should be 0!): " + itos(read));
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
@@ -582,6 +591,37 @@ ssize_t WSLPeer::_wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data,
return read;
}
+void WSLPeer::_wsl_recv_start_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ uint8_t op = arg->opcode;
+ if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
+ // Get ready to process a data package.
+ PendingMessage &pm = peer->pending_message;
+ pm.opcode = op;
+ pm.payload_size = arg->payload_length;
+ }
+}
+
+void WSLPeer::_wsl_frame_recv_chunk_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ PendingMessage &pm = peer->pending_message;
+ if (pm.opcode != 0) {
+ // Only write the payload.
+ peer->in_buffer.write_packet(arg->data, arg->data_length, nullptr);
+ }
+}
+
+void WSLPeer::_wsl_frame_recv_end_callback(wslay_event_context_ptr ctx, void *user_data) {
+ WSLPeer *peer = (WSLPeer *)user_data;
+ PendingMessage &pm = peer->pending_message;
+ if (pm.opcode != 0) {
+ // Only write the packet (since it's now completed).
+ uint8_t is_string = pm.opcode == WSLAY_TEXT_FRAME ? 1 : 0;
+ peer->in_buffer.write_packet(nullptr, pm.payload_size, &is_string);
+ pm.clear();
+ }
+}
+
ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) {
WSLPeer *peer = (WSLPeer *)user_data;
Ref<StreamPeer> conn = peer->connection;
@@ -627,25 +667,19 @@ void WSLPeer::_wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct w
return;
}
- if (peer->ready_state == STATE_CLOSING) {
- return;
- }
-
- if (op == WSLAY_TEXT_FRAME || op == WSLAY_BINARY_FRAME) {
- // Message.
- uint8_t is_string = arg->opcode == WSLAY_TEXT_FRAME ? 1 : 0;
- peer->in_buffer.write_packet(arg->msg, arg->msg_length, &is_string);
+ if (op == WSLAY_PONG) {
+ peer->heartbeat_waiting = false;
}
- // Ping or pong.
+ // Ping, or message (already parsed in chunks).
}
wslay_event_callbacks WSLPeer::_wsl_callbacks = {
_wsl_recv_callback,
_wsl_send_callback,
_wsl_genmask_callback,
- nullptr, /* on_frame_recv_start_callback */
- nullptr, /* on_frame_recv_callback */
- nullptr, /* on_frame_recv_end_callback */
+ _wsl_recv_start_callback,
+ _wsl_frame_recv_chunk_callback,
+ _wsl_frame_recv_end_callback,
_wsl_msg_recv_callback
};
@@ -680,7 +714,31 @@ void WSLPeer::poll() {
if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) {
ERR_FAIL_NULL(wsl_ctx);
+ uint64_t ticks = OS::get_singleton()->get_ticks_msec();
int err = 0;
+ if (heartbeat_interval_msec != 0 && ticks - last_heartbeat > heartbeat_interval_msec && ready_state == STATE_OPEN) {
+ if (heartbeat_waiting) {
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ }
+ heartbeat_waiting = true;
+ struct wslay_event_msg msg;
+ msg.opcode = WSLAY_PING;
+ msg.msg = nullptr;
+ msg.msg_length = 0;
+ err = wslay_event_queue_msg(wsl_ctx, &msg);
+ if (err == 0) {
+ last_heartbeat = ticks;
+ } else {
+ print_verbose("Websocket (wslay) failed to send ping: " + itos(err));
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ }
+ }
if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) {
// Error close.
print_verbose("Websocket (wslay) poll error: " + itos(err));
@@ -689,12 +747,37 @@ void WSLPeer::poll() {
close(-1);
return;
}
- if (wslay_event_get_close_sent(wsl_ctx) && wslay_event_get_close_received(wsl_ctx)) {
- // Clean close.
- wslay_event_context_free(wsl_ctx);
- wsl_ctx = nullptr;
- close(-1);
- return;
+ if (wslay_event_get_close_sent(wsl_ctx)) {
+ if (wslay_event_get_close_received(wsl_ctx)) {
+ // Clean close.
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ } else if (!wslay_event_get_read_enabled(wsl_ctx)) {
+ // Some protocol error caused wslay to stop processing incoming events, we'll never receive a close from the other peer.
+ close_code = wslay_event_get_status_code_sent(wsl_ctx);
+ switch (close_code) {
+ case WSLAY_CODE_MESSAGE_TOO_BIG:
+ close_reason = "Message too big";
+ break;
+ case WSLAY_CODE_PROTOCOL_ERROR:
+ close_reason = "Protocol error";
+ break;
+ case WSLAY_CODE_ABNORMAL_CLOSURE:
+ close_reason = "Abnormal closure";
+ break;
+ case WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA:
+ close_reason = "Invalid frame payload data";
+ break;
+ default:
+ close_reason = "Unknown";
+ }
+ wslay_event_context_free(wsl_ctx);
+ wsl_ctx = nullptr;
+ close(-1);
+ return;
+ }
}
}
}
@@ -781,8 +864,10 @@ void WSLPeer::close(int p_code, String p_reason) {
}
}
+ heartbeat_waiting = false;
in_buffer.clear();
packet_buffer.resize(0);
+ pending_message.clear();
}
IPAddress WSLPeer::get_connected_host() const {
diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h
index fb01da7ce2..45cca48224 100644
--- a/modules/websocket/wsl_peer.h
+++ b/modules/websocket/wsl_peer.h
@@ -53,6 +53,10 @@ private:
// Callbacks.
static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data);
+ static void _wsl_recv_start_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data);
+ static void _wsl_frame_recv_chunk_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data);
+ static void _wsl_frame_recv_end_callback(wslay_event_context_ptr ctx, void *user_data);
+
static ssize_t _wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
static int _wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
static void _wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
@@ -80,6 +84,16 @@ private:
Resolver() {}
};
+ struct PendingMessage {
+ size_t payload_size = 0;
+ uint8_t opcode = 0;
+
+ void clear() {
+ payload_size = 0;
+ opcode = 0;
+ }
+ };
+
Resolver resolver;
// WebSocket connection state.
@@ -99,6 +113,9 @@ private:
int close_code = -1;
String close_reason;
uint8_t was_string = 0;
+ uint64_t last_heartbeat = 0;
+ bool heartbeat_waiting = false;
+ PendingMessage pending_message;
// WebSocket configuration.
bool use_tls = true;
@@ -127,7 +144,7 @@ public:
virtual int get_available_packet_count() const override;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
- virtual int get_max_packet_size() const override { return packet_buffer.size(); };
+ virtual int get_max_packet_size() const override { return packet_buffer.size(); }
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 352d495dd4..f45135a611 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -271,19 +271,19 @@ void WebXRInterfaceJS::_set_environment_blend_mode(String p_blend_mode_string) {
StringName WebXRInterfaceJS::get_name() const {
return "WebXR";
-};
+}
uint32_t WebXRInterfaceJS::get_capabilities() const {
return XRInterface::XR_STEREO | XRInterface::XR_MONO | XRInterface::XR_VR | XRInterface::XR_AR;
-};
+}
uint32_t WebXRInterfaceJS::get_view_count() {
return godot_webxr_get_view_count();
-};
+}
bool WebXRInterfaceJS::is_initialized() const {
return (initialized);
-};
+}
bool WebXRInterfaceJS::initialize() {
XRServer *xr_server = XRServer::get_singleton();
@@ -333,7 +333,7 @@ bool WebXRInterfaceJS::initialize() {
};
return true;
-};
+}
void WebXRInterfaceJS::uninitialize() {
if (initialized) {
@@ -378,7 +378,7 @@ void WebXRInterfaceJS::uninitialize() {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
initialized = false;
};
-};
+}
Dictionary WebXRInterfaceJS::get_system_info() {
Dictionary dict;
@@ -427,7 +427,7 @@ Size2 WebXRInterfaceJS::get_render_target_size() {
render_targetsize.height = (float)js_size[1];
return render_targetsize;
-};
+}
Transform3D WebXRInterfaceJS::get_camera_transform() {
Transform3D camera_transform;
@@ -445,7 +445,7 @@ Transform3D WebXRInterfaceJS::get_camera_transform() {
}
return camera_transform;
-};
+}
Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
XRServer *xr_server = XRServer::get_singleton();
@@ -464,7 +464,7 @@ Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Tran
transform_for_view.origin *= world_scale;
return p_cam_transform * xr_server->get_reference_frame() * transform_for_view;
-};
+}
Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
Projection view;
@@ -527,7 +527,7 @@ Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, c
texture_storage->render_target_set_reattach_textures(p_render_target, false);
return blit_to_screen;
-};
+}
RID WebXRInterfaceJS::_get_color_texture() {
unsigned int texture_id = godot_webxr_get_color_texture();
@@ -608,7 +608,7 @@ void WebXRInterfaceJS::process() {
_update_input_source(i);
}
};
-};
+}
void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
XRServer *xr_server = XRServer::get_singleton();
@@ -871,13 +871,13 @@ WebXRInterfaceJS::WebXRInterfaceJS() {
initialized = false;
session_mode = "inline";
requested_reference_space_types = "local";
-};
+}
WebXRInterfaceJS::~WebXRInterfaceJS() {
// and make sure we cleanup if we haven't already
if (initialized) {
uninitialize();
};
-};
+}
#endif // WEB_ENABLED