summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/astcenc/config.py4
-rw-r--r--modules/csg/csg_shape.cpp64
-rw-r--r--modules/csg/csg_shape.h5
-rw-r--r--modules/csg/doc_classes/CSGShape3D.xml19
-rw-r--r--modules/csg/editor/csg_gizmos.cpp146
-rw-r--r--modules/csg/editor/csg_gizmos.h36
-rw-r--r--modules/dds/texture_loader_dds.cpp188
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp211
-rw-r--r--modules/etcpak/image_compress_etcpak.h2
-rw-r--r--modules/fbx/fbx_document.cpp3
-rw-r--r--modules/fbx/fbx_document.h3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml10
-rw-r--r--modules/gdscript/gdscript.cpp3
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp204
-rw-r--r--modules/gdscript/gdscript_analyzer.h19
-rw-r--r--modules/gdscript/gdscript_cache.cpp45
-rw-r--r--modules/gdscript/gdscript_cache.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp28
-rw-r--r--modules/gdscript/gdscript_editor.cpp39
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser.out3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd12
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd20
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd11
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd62
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out26
-rw-r--r--modules/gltf/README.md12
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml12
-rw-r--r--modules/gltf/doc_classes/GLTFAnimation.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFBufferView.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFCamera.xml18
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml10
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml20
-rw-r--r--modules/gltf/doc_classes/GLTFLight.xml8
-rw-r--r--modules/gltf/doc_classes/GLTFMesh.xml8
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml32
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsBody.xml10
-rw-r--r--modules/gltf/doc_classes/GLTFPhysicsShape.xml10
-rw-r--r--modules/gltf/doc_classes/GLTFSkeleton.xml4
-rw-r--r--modules/gltf/doc_classes/GLTFSpecGloss.xml6
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml48
-rw-r--r--modules/gltf/doc_classes/GLTFTexture.xml2
-rw-r--r--modules/gltf/doc_classes/GLTFTextureSampler.xml4
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp39
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp3
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h1
-rw-r--r--modules/gltf/extensions/gltf_light.cpp8
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp20
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_body.cpp16
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.cpp2
-rw-r--r--modules/gltf/gltf_document.cpp35
-rw-r--r--modules/gltf/structures/gltf_camera.cpp12
-rw-r--r--modules/gltf/structures/gltf_camera.h4
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp1
-rw-r--r--modules/hdr/image_loader_hdr.cpp32
-rw-r--r--modules/hdr/image_loader_hdr.h1
-rw-r--r--modules/interactive_music/doc_classes/AudioStreamSynchronized.xml2
-rw-r--r--modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp6
-rw-r--r--modules/mobile_vr/doc_classes/MobileVRInterface.xml2
-rw-r--r--modules/mono/csharp_script.cpp15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs18
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs49
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs51
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs25
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs93
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs9
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp9
-rw-r--r--modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj2
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSpawner.xml4
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp2
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp31
-rw-r--r--modules/multiplayer/scene_cache_interface.h12
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp29
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp30
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.cpp27
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.h1
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp3
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.h1
-rw-r--r--modules/navigation/nav_map.cpp41
-rw-r--r--modules/navigation/nav_map.h8
-rw-r--r--modules/navigation/nav_region.cpp21
-rw-r--r--modules/navigation/nav_region.h8
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml2
-rw-r--r--modules/openxr/editor/openxr_select_runtime.cpp3
-rw-r--r--modules/openxr/editor/openxr_select_runtime.h1
-rw-r--r--modules/openxr/extensions/openxr_mxink_extension.cpp83
-rw-r--r--modules/openxr/extensions/openxr_mxink_extension.h48
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.cpp22
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.h6
-rw-r--r--modules/openxr/openxr_api.cpp2
-rw-r--r--modules/openxr/openxr_interface.cpp3
-rw-r--r--modules/openxr/register_types.cpp2
-rw-r--r--modules/text_server_adv/SCsub10
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp33
-rw-r--r--modules/text_server_adv/text_server_adv.h1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp33
-rw-r--r--modules/text_server_fb/text_server_fb.h1
-rw-r--r--modules/websocket/emws_peer.cpp4
-rw-r--r--modules/websocket/wsl_peer.cpp9
123 files changed, 1742 insertions, 668 deletions
diff --git a/modules/astcenc/config.py b/modules/astcenc/config.py
index eb565b85b9..37bb65f8ae 100644
--- a/modules/astcenc/config.py
+++ b/modules/astcenc/config.py
@@ -1,5 +1,7 @@
def can_build(env, platform):
- return env.editor_build
+ # Godot only uses it in the editor, but ANGLE depends on it and we had
+ # to remove the copy from prebuilt ANGLE libs to solve symbol clashes.
+ return env.editor_build or env.get("angle_libs")
def configure(env):
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 296cda627a..8777651545 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -460,27 +460,31 @@ void CSGShape3D::_update_shape() {
_update_collision_faces();
}
-void CSGShape3D::_update_collision_faces() {
- if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
- CSGBrush *n = _get_brush();
- ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
- Vector<Vector3> physics_faces;
- physics_faces.resize(n->faces.size() * 3);
- Vector3 *physicsw = physics_faces.ptrw();
-
- for (int i = 0; i < n->faces.size(); i++) {
- int order[3] = { 0, 1, 2 };
+Vector<Vector3> CSGShape3D::_get_brush_collision_faces() {
+ Vector<Vector3> collision_faces;
+ CSGBrush *n = _get_brush();
+ ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush.");
+ collision_faces.resize(n->faces.size() * 3);
+ Vector3 *collision_faces_ptrw = collision_faces.ptrw();
- if (n->faces[i].invert) {
- SWAP(order[1], order[2]);
- }
+ for (int i = 0; i < n->faces.size(); i++) {
+ int order[3] = { 0, 1, 2 };
- physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
- physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
- physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
+ if (n->faces[i].invert) {
+ SWAP(order[1], order[2]);
}
- root_collision_shape->set_faces(physics_faces);
+ collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]];
+ collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]];
+ collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]];
+ }
+
+ return collision_faces;
+}
+
+void CSGShape3D::_update_collision_faces() {
+ if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
+ root_collision_shape->set_faces(_get_brush_collision_faces());
if (_is_debug_collision_shape_visible()) {
_update_debug_collision_shape();
@@ -488,6 +492,26 @@ void CSGShape3D::_update_collision_faces() {
}
}
+Ref<ArrayMesh> CSGShape3D::bake_static_mesh() {
+ Ref<ArrayMesh> baked_mesh;
+ if (is_root_shape() && root_mesh.is_valid()) {
+ baked_mesh = root_mesh;
+ }
+ return baked_mesh;
+}
+
+Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
+ Ref<ConcavePolygonShape3D> baked_collision_shape;
+ if (is_root_shape() && root_collision_shape.is_valid()) {
+ baked_collision_shape.instantiate();
+ baked_collision_shape->set_faces(root_collision_shape->get_faces());
+ } else if (is_root_shape()) {
+ baked_collision_shape.instantiate();
+ baked_collision_shape->set_faces(_get_brush_collision_faces());
+ }
+ return baked_collision_shape;
+}
+
bool CSGShape3D::_is_debug_collision_shape_visible() {
return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
}
@@ -704,6 +728,9 @@ void CSGShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
+ ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh);
+ ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
@@ -934,7 +961,8 @@ CSGBrush *CSGMesh3D::_build_brush() {
void CSGMesh3D::_mesh_changed() {
_make_dirty();
- update_gizmos();
+
+ callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred();
}
void CSGMesh3D::set_material(const Ref<Material> &p_material) {
diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h
index bb7c8be431..8f23ae2f9e 100644
--- a/modules/csg/csg_shape.h
+++ b/modules/csg/csg_shape.h
@@ -113,6 +113,7 @@ private:
void _update_debug_collision_shape();
void _clear_debug_collision_shape();
void _on_transform_changed();
+ Vector<Vector3> _get_brush_collision_faces();
protected:
void _notification(int p_what);
@@ -161,6 +162,10 @@ public:
bool is_calculating_tangents() const;
bool is_root_shape() const;
+
+ Ref<ArrayMesh> bake_static_mesh();
+ Ref<ConcavePolygonShape3D> bake_collision_shape();
+
CSGShape3D();
~CSGShape3D();
};
diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml
index f9017e47c7..ac62d8dd83 100644
--- a/modules/csg/doc_classes/CSGShape3D.xml
+++ b/modules/csg/doc_classes/CSGShape3D.xml
@@ -5,12 +5,29 @@
</brief_description>
<description>
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
- [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
+ [b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost.
+ Consider baking final CSG operation results into static geometry that replaces the CSG nodes.
+ Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected.
+ Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision.
+ Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<methods>
+ <method name="bake_collision_shape">
+ <return type="ConcavePolygonShape3D" />
+ <description>
+ Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry.
+ [b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving.
+ </description>
+ </method>
+ <method name="bake_static_mesh">
+ <return type="ArrayMesh" />
+ <description>
+ Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry.
+ </description>
+ </method>
<method name="get_collision_layer_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index ea7b6d225e..72676f4a40 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -38,6 +38,135 @@
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/physics/collision_shape_3d.h"
+#include "scene/3d/physics/static_body_3d.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/menu_button.h"
+
+void CSGShapeEditor::_node_removed(Node *p_node) {
+ if (p_node == node) {
+ node = nullptr;
+ options->hide();
+ }
+}
+
+void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
+ node = p_csg_shape;
+ if (node) {
+ options->show();
+ } else {
+ options->hide();
+ }
+}
+
+void CSGShapeEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ options->set_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
+ } break;
+ }
+}
+
+void CSGShapeEditor::_menu_option(int p_option) {
+ Array meshes = node->get_meshes();
+ if (meshes.is_empty()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty array."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ switch (p_option) {
+ case MENU_OPTION_BAKE_MESH_INSTANCE: {
+ _create_baked_mesh_instance();
+ } break;
+ case MENU_OPTION_BAKE_COLLISION_SHAPE: {
+ _create_baked_collision_shape();
+ } break;
+ }
+}
+
+void CSGShapeEditor::_create_baked_mesh_instance() {
+ if (node == get_tree()->get_edited_scene_root()) {
+ err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ Ref<ArrayMesh> mesh = node->bake_static_mesh();
+ if (mesh.is_null()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty mesh."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Create baked CSGShape3D Mesh Instance"));
+
+ Node *owner = get_tree()->get_edited_scene_root();
+
+ MeshInstance3D *mi = memnew(MeshInstance3D);
+ mi->set_mesh(mesh);
+ mi->set_name("CSGBakedMeshInstance3D");
+ mi->set_transform(node->get_transform());
+ ur->add_do_method(node, "add_sibling", mi, true);
+ ur->add_do_method(mi, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi);
+
+ ur->add_do_reference(mi);
+ ur->add_undo_method(node->get_parent(), "remove_child", mi);
+
+ ur->commit_action();
+}
+
+void CSGShapeEditor::_create_baked_collision_shape() {
+ if (node == get_tree()->get_edited_scene_root()) {
+ err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ Ref<Shape3D> shape = node->bake_collision_shape();
+ if (shape.is_null()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty shape."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Create baked CSGShape3D Collision Shape"));
+
+ Node *owner = get_tree()->get_edited_scene_root();
+
+ CollisionShape3D *cshape = memnew(CollisionShape3D);
+ cshape->set_shape(shape);
+ cshape->set_name("CSGBakedCollisionShape3D");
+ cshape->set_transform(node->get_transform());
+ ur->add_do_method(node, "add_sibling", cshape, true);
+ ur->add_do_method(cshape, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape);
+
+ ur->add_do_reference(cshape);
+ ur->add_undo_method(node->get_parent(), "remove_child", cshape);
+
+ ur->commit_action();
+}
+
+CSGShapeEditor::CSGShapeEditor() {
+ options = memnew(MenuButton);
+ options->hide();
+ options->set_text(TTR("CSG"));
+ options->set_switch_on_hover(true);
+ Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
+
+ options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE);
+ options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE);
+
+ options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option));
+
+ err_dialog = memnew(AcceptDialog);
+ add_child(err_dialog);
+}
///////////
@@ -393,9 +522,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
}
}
+void EditorPluginCSG::edit(Object *p_object) {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
+ if (csg_shape && csg_shape->is_root_shape()) {
+ csg_shape_editor->edit(csg_shape);
+ } else {
+ csg_shape_editor->edit(nullptr);
+ }
+}
+
+bool EditorPluginCSG::handles(Object *p_object) const {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
+ return csg_shape && csg_shape->is_root_shape();
+}
+
EditorPluginCSG::EditorPluginCSG() {
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
+
+ csg_shape_editor = memnew(CSGShapeEditor);
+ EditorNode::get_singleton()->get_main_screen_control()->add_child(csg_shape_editor);
}
#endif // TOOLS_ENABLED
diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h
index de19b33e7d..c562fe9fe7 100644
--- a/modules/csg/editor/csg_gizmos.h
+++ b/modules/csg/editor/csg_gizmos.h
@@ -37,8 +37,11 @@
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
+#include "scene/gui/control.h"
+class AcceptDialog;
class Gizmo3DHelper;
+class MenuButton;
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
@@ -62,10 +65,43 @@ public:
~CSGShape3DGizmoPlugin();
};
+class CSGShapeEditor : public Control {
+ GDCLASS(CSGShapeEditor, Control);
+
+ enum Menu {
+ MENU_OPTION_BAKE_MESH_INSTANCE,
+ MENU_OPTION_BAKE_COLLISION_SHAPE,
+ };
+
+ CSGShape3D *node = nullptr;
+ MenuButton *options = nullptr;
+ AcceptDialog *err_dialog = nullptr;
+
+ void _menu_option(int p_option);
+
+ void _create_baked_mesh_instance();
+ void _create_baked_collision_shape();
+
+protected:
+ void _node_removed(Node *p_node);
+
+ void _notification(int p_what);
+
+public:
+ void edit(CSGShape3D *p_csg_shape);
+ CSGShapeEditor();
+};
+
class EditorPluginCSG : public EditorPlugin {
GDCLASS(EditorPluginCSG, EditorPlugin);
+ CSGShapeEditor *csg_shape_editor = nullptr;
+
public:
+ virtual String get_name() const override { return "CSGShape3D"; }
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+
EditorPluginCSG();
};
diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp
index b2de6b656e..6ea44c5fc3 100644
--- a/modules/dds/texture_loader_dds.cpp
+++ b/modules/dds/texture_loader_dds.cpp
@@ -42,14 +42,18 @@ enum {
DDSD_PITCH = 0x00000008,
DDSD_LINEARSIZE = 0x00080000,
DDSD_MIPMAPCOUNT = 0x00020000,
- DDPF_FOURCC = 0x00000004,
DDPF_ALPHAPIXELS = 0x00000001,
- DDPF_RGB = 0x00000040
+ DDPF_ALPHAONLY = 0x00000002,
+ DDPF_FOURCC = 0x00000004,
+ DDPF_RGB = 0x00000040,
+ DDPF_RG_SNORM = 0x00080000
};
enum DDSFourCC {
DDFCC_DXT1 = PF_FOURCC("DXT1"),
+ DDFCC_DXT2 = PF_FOURCC("DXT2"),
DDFCC_DXT3 = PF_FOURCC("DXT3"),
+ DDFCC_DXT4 = PF_FOURCC("DXT4"),
DDFCC_DXT5 = PF_FOURCC("DXT5"),
DDFCC_ATI1 = PF_FOURCC("ATI1"),
DDFCC_BC4U = PF_FOURCC("BC4U"),
@@ -68,17 +72,25 @@ enum DDSFourCC {
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
enum DXGIFormat {
DXGI_R32G32B32A32_FLOAT = 2,
+ DXGI_R32G32B32_FLOAT = 6,
DXGI_R16G16B16A16_FLOAT = 10,
DXGI_R32G32_FLOAT = 16,
DXGI_R10G10B10A2_UNORM = 24,
DXGI_R8G8B8A8_UNORM = 28,
+ DXGI_R8G8B8A8_UNORM_SRGB = 29,
DXGI_R16G16_FLOAT = 34,
DXGI_R32_FLOAT = 41,
+ DXGI_R8G8_UNORM = 49,
DXGI_R16_FLOAT = 54,
+ DXGI_R8_UNORM = 61,
+ DXGI_A8_UNORM = 65,
DXGI_R9G9B9E5 = 67,
DXGI_BC1_UNORM = 71,
+ DXGI_BC1_UNORM_SRGB = 72,
DXGI_BC2_UNORM = 74,
+ DXGI_BC2_UNORM_SRGB = 75,
DXGI_BC3_UNORM = 77,
+ DXGI_BC3_UNORM_SRGB = 78,
DXGI_BC4_UNORM = 80,
DXGI_BC5_UNORM = 83,
DXGI_B5G6R5_UNORM = 85,
@@ -87,6 +99,7 @@ enum DXGIFormat {
DXGI_BC6H_UF16 = 95,
DXGI_BC6H_SF16 = 96,
DXGI_BC7_UNORM = 98,
+ DXGI_BC7_UNORM_SRGB = 99,
DXGI_B4G4R4A4_UNORM = 115
};
@@ -100,25 +113,29 @@ enum DDSFormat {
DDS_ATI2,
DDS_BC6U,
DDS_BC6S,
- DDS_BC7U,
+ DDS_BC7,
DDS_R16F,
DDS_RG16F,
DDS_RGBA16F,
DDS_R32F,
DDS_RG32F,
+ DDS_RGB32F,
DDS_RGBA32F,
DDS_RGB9E5,
- DDS_BGRA8,
- DDS_BGR8,
- DDS_RGBA8,
DDS_RGB8,
+ DDS_RGBA8,
+ DDS_BGR8,
+ DDS_BGRA8,
DDS_BGR5A1,
DDS_BGR565,
+ DDS_B2GR3,
+ DDS_B2GR3A8,
DDS_BGR10A2,
DDS_RGB10A2,
DDS_BGRA4,
DDS_LUMINANCE,
DDS_LUMINANCE_ALPHA,
+ DDS_LUMINANCE_ALPHA_4,
DDS_MAX
};
@@ -132,31 +149,35 @@ struct DDSFormatInfo {
static const DDSFormatInfo dds_format_info[DDS_MAX] = {
{ "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 },
- { "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
- { "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
+ { "DXT2/DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
+ { "DXT4/DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
{ "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R },
{ "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG },
- { "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
- { "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF },
- { "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA },
+ { "BC6UF", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
+ { "BC6SF", true, 4, 16, Image::FORMAT_BPTC_RGBF },
+ { "BC7", true, 4, 16, Image::FORMAT_BPTC_RGBA },
{ "R16F", false, 1, 2, Image::FORMAT_RH },
{ "RG16F", false, 1, 4, Image::FORMAT_RGH },
{ "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH },
{ "R32F", false, 1, 4, Image::FORMAT_RF },
{ "RG32F", false, 1, 8, Image::FORMAT_RGF },
+ { "RGB32F", false, 1, 12, Image::FORMAT_RGBF },
{ "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF },
{ "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 },
- { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
- { "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
- { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
{ "RGB8", false, 1, 3, Image::FORMAT_RGB8 },
+ { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
+ { "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
+ { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
{ "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 },
{ "BGR565", false, 1, 2, Image::FORMAT_RGB8 },
+ { "B2GR3", false, 1, 1, Image::FORMAT_RGB8 },
+ { "B2GR3A8", false, 1, 2, Image::FORMAT_RGBA8 },
{ "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 },
{ "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 },
{ "BGRA4", false, 1, 2, Image::FORMAT_RGBA8 },
{ "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 },
- { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }
+ { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 },
+ { "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 }
};
static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
@@ -164,6 +185,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
case DXGI_R32G32B32A32_FLOAT: {
return DDS_RGBA32F;
}
+ case DXGI_R32G32B32_FLOAT: {
+ return DDS_RGB32F;
+ }
case DXGI_R16G16B16A16_FLOAT: {
return DDS_RGBA16F;
}
@@ -173,7 +197,8 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
case DXGI_R10G10B10A2_UNORM: {
return DDS_RGB10A2;
}
- case DXGI_R8G8B8A8_UNORM: {
+ case DXGI_R8G8B8A8_UNORM:
+ case DXGI_R8G8B8A8_UNORM_SRGB: {
return DDS_RGBA8;
}
case DXGI_R16G16_FLOAT: {
@@ -182,19 +207,29 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
case DXGI_R32_FLOAT: {
return DDS_R32F;
}
+ case DXGI_R8_UNORM:
+ case DXGI_A8_UNORM: {
+ return DDS_LUMINANCE;
+ }
case DXGI_R16_FLOAT: {
return DDS_R16F;
}
+ case DXGI_R8G8_UNORM: {
+ return DDS_LUMINANCE_ALPHA;
+ }
case DXGI_R9G9B9E5: {
return DDS_RGB9E5;
}
- case DXGI_BC1_UNORM: {
+ case DXGI_BC1_UNORM:
+ case DXGI_BC1_UNORM_SRGB: {
return DDS_DXT1;
}
- case DXGI_BC2_UNORM: {
+ case DXGI_BC2_UNORM:
+ case DXGI_BC2_UNORM_SRGB: {
return DDS_DXT3;
}
- case DXGI_BC3_UNORM: {
+ case DXGI_BC3_UNORM:
+ case DXGI_BC3_UNORM_SRGB: {
return DDS_DXT5;
}
case DXGI_BC4_UNORM: {
@@ -218,8 +253,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
case DXGI_BC6H_SF16: {
return DDS_BC6S;
}
- case DXGI_BC7_UNORM: {
- return DDS_BC7U;
+ case DXGI_BC7_UNORM:
+ case DXGI_BC7_UNORM_SRGB: {
+ return DDS_BC7;
}
case DXGI_B4G4R4A4_UNORM: {
return DDS_BGRA4;
@@ -299,9 +335,11 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
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;
@@ -363,6 +401,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
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 {
@@ -373,18 +413,38 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
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_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
- dds_format = DDS_LUMINANCE_ALPHA;
- } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) {
+ 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 + "'.");
@@ -433,10 +493,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
}
// Calculate the space these formats will take up after decoding.
- if (dds_format == DDS_BGR565) {
- size = size * 3 / 2;
- } else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) {
- size = size * 2;
+ switch (dds_format) {
+ case DDS_BGR565:
+ size = size * 3 / 2;
+ break;
+
+ case DDS_BGR5A1:
+ case DDS_BGRA4:
+ case DDS_B2GR3A8:
+ case DDS_LUMINANCE_ALPHA_4:
+ size = size * 2;
+ break;
+
+ case DDS_B2GR3:
+ size = size * 3;
+ break;
+
+ default:
+ break;
}
src_data.resize(size);
@@ -503,6 +577,44 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
}
} break;
+ case DDS_B2GR3: {
+ // To RGB8.
+ int colcount = size / 3;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i;
+ int dst_ofs = i * 3;
+
+ uint8_t b = (wb[src_ofs] & 0x3) << 6;
+ uint8_t g = (wb[src_ofs] & 0x1C) << 3;
+ uint8_t r = (wb[src_ofs] & 0xE0);
+
+ wb[dst_ofs] = r;
+ wb[dst_ofs + 1] = g;
+ wb[dst_ofs + 2] = b;
+ }
+
+ } break;
+ case DDS_B2GR3A8: {
+ // To RGBA8.
+ int colcount = size / 4;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i * 2;
+ int dst_ofs = i * 4;
+
+ uint8_t b = (wb[src_ofs] & 0x3) << 6;
+ uint8_t g = (wb[src_ofs] & 0x1C) << 3;
+ uint8_t r = (wb[src_ofs] & 0xE0);
+ uint8_t a = wb[src_ofs + 1];
+
+ wb[dst_ofs] = r;
+ wb[dst_ofs + 1] = g;
+ wb[dst_ofs + 2] = b;
+ wb[dst_ofs + 3] = a;
+ }
+
+ } break;
case DDS_RGB10A2: {
// To RGBA8.
int colcount = size / 4;
@@ -549,6 +661,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
}
} break;
+
+ // Channel-swapped.
case DDS_BGRA8: {
// To RGBA8.
int colcount = size / 4;
@@ -568,6 +682,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
} break;
+ // Grayscale.
+ case DDS_LUMINANCE_ALPHA_4: {
+ // To LA8.
+ int colcount = size / 2;
+
+ for (int i = colcount - 1; i >= 0; i--) {
+ int src_ofs = i;
+ int dst_ofs = i * 2;
+
+ uint8_t l = wb[src_ofs] & 0x0F;
+ uint8_t a = wb[src_ofs] & 0xF0;
+
+ wb[dst_ofs] = (l << 4) | l;
+ wb[dst_ofs + 1] = a | (a >> 4);
+ }
+
+ } break;
+
default: {
}
}
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index 95ed462070..14887ce469 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -50,6 +50,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
+
default:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
}
@@ -69,6 +70,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
return EtcpakType::ETCPAK_TYPE_DXT1;
case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_DXT5;
+
default:
return EtcpakType::ETCPAK_TYPE_DXT5;
}
@@ -79,71 +81,86 @@ void _compress_etc1(Image *r_img) {
}
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
- EtcpakType type = _determine_etc_type(p_channels);
- _compress_etcpak(type, r_img);
+ _compress_etcpak(_determine_etc_type(p_channels), r_img);
}
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
- EtcpakType type = _determine_dxt_type(p_channels);
- _compress_etcpak(type, r_img);
+ _compress_etcpak(_determine_dxt_type(p_channels), r_img);
}
-void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
+void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
- Image::Format img_format = r_img->get_format();
- if (Image::is_format_compressed(img_format)) {
- return; // Do not compress, already compressed.
- }
- if (img_format > Image::FORMAT_RGBA8) {
- // TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually
+ // The image is already compressed, return.
+ if (r_img->is_compressed()) {
return;
}
- // Use RGBA8 to convert.
- if (img_format != Image::FORMAT_RGBA8) {
- r_img->convert(Image::FORMAT_RGBA8);
- }
+ // Convert to RGBA8 for compression.
+ r_img->convert(Image::FORMAT_RGBA8);
// Determine output format based on Etcpak type.
Image::Format target_format = Image::FORMAT_RGBA8;
- if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
- target_format = Image::FORMAT_ETC;
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
- target_format = Image::FORMAT_ETC2_RGB8;
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) {
- target_format = Image::FORMAT_ETC2_R11;
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
- target_format = Image::FORMAT_ETC2_RG11;
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
- target_format = Image::FORMAT_ETC2_RA_AS_RG;
- r_img->convert_rg_to_ra_rgba8();
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) {
- target_format = Image::FORMAT_ETC2_RGBA8;
- r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
- target_format = Image::FORMAT_DXT1;
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
- target_format = Image::FORMAT_DXT5_RA_AS_RG;
- r_img->convert_rg_to_ra_rgba8();
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
- target_format = Image::FORMAT_DXT5;
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
- target_format = Image::FORMAT_RGTC_R;
- } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
- target_format = Image::FORMAT_RGTC_RG;
- } else {
- ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
+
+ switch (p_compress_type) {
+ case EtcpakType::ETCPAK_TYPE_ETC1:
+ target_format = Image::FORMAT_ETC;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2:
+ target_format = Image::FORMAT_ETC2_RGB8;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
+ target_format = Image::FORMAT_ETC2_RGBA8;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_R:
+ target_format = Image::FORMAT_ETC2_R11;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_RG:
+ target_format = Image::FORMAT_ETC2_RG11;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
+ target_format = Image::FORMAT_ETC2_RA_AS_RG;
+ r_img->convert_rg_to_ra_rgba8();
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_DXT1:
+ target_format = Image::FORMAT_DXT1;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_DXT5:
+ target_format = Image::FORMAT_DXT5;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_RGTC_R:
+ target_format = Image::FORMAT_RGTC_R;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_RGTC_RG:
+ target_format = Image::FORMAT_RGTC_RG;
+ break;
+
+ case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
+ target_format = Image::FORMAT_DXT5_RA_AS_RG;
+ r_img->convert_rg_to_ra_rgba8();
+ break;
+
+ default:
+ ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
+ break;
}
- // Compress image data and (if required) mipmaps.
+ // It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats.
+ if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) {
+ r_img->convert_rgba8_to_bgra8();
+ }
- const bool mipmaps = r_img->has_mipmaps();
+ // Compress image data and (if required) mipmaps.
+ const bool has_mipmaps = r_img->has_mipmaps();
int width = r_img->get_width();
int height = r_img->get_height();
@@ -164,109 +181,115 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from,
the surface pitch, which can encompass additional padding beyond the physical surface size.
*/
- int next_width = width <= 2 ? width : (width + 3) & ~3;
- int next_height = height <= 2 ? height : (height + 3) & ~3;
- if (next_width != width || next_height != height) {
- r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS);
- width = r_img->get_width();
- height = r_img->get_height();
+
+ if (width % 4 != 0 || height % 4 != 0) {
+ width = width <= 2 ? width : (width + 3) & ~3;
+ height = height <= 2 ? height : (height + 3) & ~3;
}
- // ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed.
+
// Multiple-of-4 should be guaranteed by above.
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
// which are individually compressed Image objects that violate the above rule.
// Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
- const uint8_t *src_read = r_img->get_data().ptr();
-
- print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
-
- int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
+ // Create the buffer for compressed image data.
Vector<uint8_t> dest_data;
- dest_data.resize(dest_size);
+ dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps));
uint8_t *dest_write = dest_data.ptrw();
- int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
+ const uint8_t *src_read = r_img->get_data().ptr();
+
+ const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
Vector<uint32_t> padded_src;
for (int i = 0; i < mip_count + 1; i++) {
// Get write mip metrics for target image.
- int orig_mip_w, orig_mip_h;
- int64_t mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h);
+ int dest_mip_w, dest_mip_h;
+ int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h);
+
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
- ERR_FAIL_COND(mip_ofs % 8 != 0);
- uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs];
+ ERR_FAIL_COND(dest_mip_ofs % 8 != 0);
+ uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs);
- // Block size. Align stride to multiple of 4 (RGBA8).
- int mip_w = (orig_mip_w + 3) & ~3;
- int mip_h = (orig_mip_h + 3) & ~3;
- const uint32_t blocks = mip_w * mip_h / 16;
+ // Block size.
+ dest_mip_w = (dest_mip_w + 3) & ~3;
+ dest_mip_h = (dest_mip_h + 3) & ~3;
+ const uint32_t blocks = dest_mip_w * dest_mip_h / 16;
// Get mip data from source image for reading.
- int64_t src_mip_ofs = r_img->get_mipmap_offset(i);
- const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs];
+ int64_t src_mip_ofs, src_mip_size;
+ int src_mip_w, src_mip_h;
+
+ r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
+
+ const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs);
// Pad textures to nearest block by smearing.
- if (mip_w != orig_mip_w || mip_h != orig_mip_h) {
- padded_src.resize(mip_w * mip_h);
+ if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) {
+ // Reserve the buffer for padded image data.
+ padded_src.resize(dest_mip_w * dest_mip_h);
uint32_t *ptrw = padded_src.ptrw();
+
int x = 0, y = 0;
- for (y = 0; y < orig_mip_h; y++) {
- for (x = 0; x < orig_mip_w; x++) {
- ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x];
+ for (y = 0; y < src_mip_h; y++) {
+ for (x = 0; x < src_mip_w; x++) {
+ ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x];
}
+
// First, smear in x.
- for (; x < mip_w; x++) {
- ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1];
+ for (; x < dest_mip_w; x++) {
+ ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1];
}
}
+
// Then, smear in y.
- for (; y < mip_h; y++) {
- for (x = 0; x < mip_w; x++) {
- ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w];
+ for (; y < dest_mip_h; y++) {
+ for (x = 0; x < dest_mip_w; x++) {
+ ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w];
}
}
+
// Override the src_mip_read pointer to our temporary Vector.
src_mip_read = padded_src.ptr();
}
- switch (p_compresstype) {
+ switch (p_compress_type) {
case EtcpakType::ETCPAK_TYPE_ETC1:
- CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_ETC2:
- CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
+ CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
- CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
+ CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_R:
- CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
- CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_DXT1:
- CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_DXT5:
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
- CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressDxt5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_RGTC_R:
- CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
- CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
+ CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
default:
@@ -276,7 +299,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
}
// Replace original image with compressed one.
- r_img->set_data(width, height, mipmaps, target_format, dest_data);
+ r_img->set_data(width, height, has_mipmaps, target_format, dest_data);
print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}
diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h
index 9d5343740b..d50b322fe4 100644
--- a/modules/etcpak/image_compress_etcpak.h
+++ b/modules/etcpak/image_compress_etcpak.h
@@ -51,6 +51,6 @@ void _compress_etc1(Image *r_img);
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels);
void _compress_bc(Image *r_img, Image::UsedChannels p_channels);
-void _compress_etcpak(EtcpakType p_compresstype, Image *r_img);
+void _compress_etcpak(EtcpakType p_compress_type, Image *r_img);
#endif // IMAGE_COMPRESS_ETCPAK_H
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index b9d9ec7b6c..4d3f7554c0 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -2114,9 +2114,6 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess>
return OK;
}
-void FBXDocument::_bind_methods() {
-}
-
Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
Ref<FBXState> state = p_state;
ERR_FAIL_COND_V(state.is_null(), nullptr);
diff --git a/modules/fbx/fbx_document.h b/modules/fbx/fbx_document.h
index 4a3bb176c2..96f1905881 100644
--- a/modules/fbx/fbx_document.h
+++ b/modules/fbx/fbx_document.h
@@ -61,9 +61,6 @@ public:
PackedByteArray generate_buffer(Ref<GLTFState> p_state) override;
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) override;
-protected:
- static void _bind_methods();
-
private:
String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const;
void _process_uv_set(PackedVector2Array &uv_array);
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 6e7ac0dec9..104fc15a8c 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -52,9 +52,9 @@
<description>
Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code).
[codeblock]
- a = char(65) # a is "A"
- a = char(65 + 32) # a is "a"
- a = char(8364) # a is "€"
+ var upper = char(65) # upper is "A"
+ var lower = char(65 + 32) # lower is "a"
+ var euro = char(8364) # euro is "€"
[/codeblock]
</description>
</method>
@@ -150,10 +150,10 @@
<description>
Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
[codeblock]
- a = [1, 2, 3, 4]
+ var a = [1, 2, 3, 4]
len(a) # Returns 4
- b = "Hello!"
+ var b = "Hello!"
len(b) # Returns 6
[/codeblock]
</description>
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index eaf2565e69..7bf5e946fb 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2857,8 +2857,11 @@ GDScriptLanguage::GDScriptLanguage() {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
+#ifdef DEBUG_ENABLED
profiling = false;
+ profile_native_calls = false;
script_frame_time = 0;
+#endif
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 4e78fbe302..6527a0ea4d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -459,9 +459,11 @@ class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptFunction;
SelfList<GDScriptFunction>::List function_list;
+#ifdef DEBUG_ENABLED
bool profiling;
bool profile_native_calls;
uint64_t script_frame_time;
+#endif
HashMap<String, ObjectID> orphan_subclasses;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index bfc78d2605..e98cae765b 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -285,7 +285,7 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C
return OK;
}
-void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) {
+void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source) {
ERR_FAIL_NULL(p_node);
ERR_FAIL_NULL(p_list);
@@ -299,11 +299,15 @@ void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::Clas
// Prioritize node base type over its outer class
if (p_node->base_type.class_type != nullptr) {
- get_class_node_current_scope_classes(p_node->base_type.class_type, p_list);
+ // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here.
+ ensure_cached_external_parser_for_class(p_node->base_type.class_type, p_node, "Trying to fetch classes in the current scope", p_source);
+ get_class_node_current_scope_classes(p_node->base_type.class_type, p_list, p_source);
}
if (p_node->outer != nullptr) {
- get_class_node_current_scope_classes(p_node->outer, p_list);
+ // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here.
+ ensure_cached_external_parser_for_class(p_node->outer, p_node, "Trying to fetch classes in the current scope", p_source);
+ get_class_node_current_scope_classes(p_node->outer, p_list, p_source);
}
}
@@ -312,6 +316,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
p_source = p_class;
}
+ Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class inheritance", p_source);
+ Finally finally([&]() {
+ for (GDScriptParser::ClassNode *look_class = p_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
+ ensure_cached_external_parser_for_class(look_class->base_type.class_type, look_class, "Trying to resolve class inheritance", p_source);
+ }
+ });
+
if (p_class->base_type.is_resolving()) {
push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source);
return ERR_PARSE_ERROR;
@@ -323,21 +334,17 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
}
if (!parser->has_class(p_class)) {
- String script_path = p_class->get_datatype().script_path;
- Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path);
if (parser_ref.is_null()) {
- push_error(vformat(R"(Could not find script "%s".)", script_path), p_source);
+ // Error already pushed.
return ERR_PARSE_ERROR;
}
Error err = parser_ref->raise_status(GDScriptParserRef::PARSED);
if (err) {
- push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source);
+ push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source);
return ERR_PARSE_ERROR;
}
- ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)");
-
GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer();
GDScriptParser *other_parser = parser_ref->get_parser();
@@ -471,7 +478,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
// Look for other classes in script.
bool found = false;
List<GDScriptParser::ClassNode *> script_classes;
- get_class_node_current_scope_classes(p_class, &script_classes);
+ get_class_node_current_scope_classes(p_class, &script_classes, id);
for (GDScriptParser::ClassNode *look_class : script_classes) {
if (look_class->identifier && look_class->identifier->name == name) {
if (!look_class->get_datatype().is_set()) {
@@ -763,7 +770,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
bool found = false;
- get_class_node_current_scope_classes(parser->current_class, &script_classes);
+ get_class_node_current_scope_classes(parser->current_class, &script_classes, p_type);
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (found) {
break;
@@ -883,6 +890,15 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
p_source = member.get_source_node();
}
+ Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class member", p_source);
+ 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);
+ }
+ });
+
if (member.get_datatype().is_resolving()) {
push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source);
return;
@@ -892,42 +908,39 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
return;
}
+ // If it's already resolving, that's ok.
+ if (!p_class->base_type.is_resolving()) {
+ Error err = resolve_class_inheritance(p_class);
+ if (err) {
+ return;
+ }
+ }
+
if (!parser->has_class(p_class)) {
- String script_path = p_class->get_datatype().script_path;
- Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path);
if (parser_ref.is_null()) {
- push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source);
+ // Error already pushed.
return;
}
Error err = parser_ref->raise_status(GDScriptParserRef::PARSED);
if (err) {
- push_error(vformat(R"(Could not resolve script "%s": %s (While resolving "%s").)", script_path, error_names[err], member.get_name()), p_source);
+ push_error(vformat(R"(Could not parse script "%s": %s (While resolving external class member "%s").)", p_class->get_datatype().script_path, error_names[err], member.get_name()), p_source);
return;
}
- ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)");
-
GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer();
GDScriptParser *other_parser = parser_ref->get_parser();
int error_count = other_parser->errors.size();
other_analyzer->resolve_class_member(p_class, p_index);
if (other_parser->errors.size() > error_count) {
- push_error(vformat(R"(Could not resolve member "%s".)", member.get_name()), p_source);
+ push_error(vformat(R"(Could not resolve external class member "%s".)", member.get_name()), p_source);
+ return;
}
return;
}
- // If it's already resolving, that's ok.
- if (!p_class->base_type.is_resolving()) {
- Error err = resolve_class_inheritance(p_class);
- if (err) {
- return;
- }
- }
-
GDScriptParser::ClassNode *previous_class = parser->current_class;
parser->current_class = p_class;
@@ -1170,27 +1183,25 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
p_source = p_class;
}
+ Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class interface", p_source);
+
if (!p_class->resolved_interface) {
#ifdef DEBUG_ENABLED
bool has_static_data = p_class->has_static_data;
#endif
if (!parser->has_class(p_class)) {
- String script_path = p_class->get_datatype().script_path;
- Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path);
if (parser_ref.is_null()) {
- push_error(vformat(R"(Could not find script "%s".)", script_path), p_source);
+ // Error already pushed.
return;
}
Error err = parser_ref->raise_status(GDScriptParserRef::PARSED);
if (err) {
- push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source);
+ push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source);
return;
}
- ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)");
-
GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer();
GDScriptParser *other_parser = parser_ref->get_parser();
@@ -1198,6 +1209,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
other_analyzer->resolve_class_interface(p_class);
if (other_parser->errors.size() > error_count) {
push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source);
+ return;
}
return;
@@ -1261,26 +1273,24 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
p_source = p_class;
}
+ Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class body", p_source);
+
if (p_class->resolved_body) {
return;
}
if (!parser->has_class(p_class)) {
- String script_path = p_class->get_datatype().script_path;
- Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path);
if (parser_ref.is_null()) {
- push_error(vformat(R"(Could not find script "%s".)", script_path), p_source);
+ // Error already pushed.
return;
}
Error err = parser_ref->raise_status(GDScriptParserRef::PARSED);
if (err) {
- push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source);
+ push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source);
return;
}
- ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)");
-
GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer();
GDScriptParser *other_parser = parser_ref->get_parser();
@@ -1288,6 +1298,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
other_analyzer->resolve_class_body(p_class);
if (other_parser->errors.size() > error_count) {
push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source);
+ return;
}
return;
@@ -3645,12 +3656,116 @@ 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) {
+ if (p_class == nullptr) {
+ return nullptr;
+ }
+
+ if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(p_class)) {
+ return E->value;
+ }
+
+ if (parser->has_class(p_class)) {
+ return nullptr;
+ }
+
+ if (p_from_class == nullptr) {
+ 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)) {
+ parser_ref = find_cached_external_parser_for_class(p_class, parser);
+ if (parser_ref.is_valid()) {
+ break;
+ }
+ }
+
+ if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(look_class)) {
+ parser_ref = find_cached_external_parser_for_class(p_class, E->value);
+ if (parser_ref.is_valid()) {
+ break;
+ }
+ }
+
+ String look_class_script_path = look_class->get_datatype().script_path;
+ if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = parser->depended_parsers.find(look_class_script_path)) {
+ parser_ref = find_cached_external_parser_for_class(p_class, E->value);
+ if (parser_ref.is_valid()) {
+ break;
+ }
+ }
+ }
+
+ if (parser_ref.is_null()) {
+ push_error(vformat(R"(Parser bug (please report): Could not find external parser for class "%s". (%s))", p_class->fqcn, p_context), p_source);
+ // A null parser will be inserted into the cache, so this error won't spam for the same class.
+ // This is ok, the values of external_class_parser_cache are not assumed to be valid references.
+ }
+
+ external_class_parser_cache.insert(p_class, parser_ref);
+ return parser_ref;
+}
+
+Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser) {
+ if (p_dependant_parser.is_null()) {
+ return nullptr;
+ }
+
+ if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->get_analyzer()->external_class_parser_cache.find(p_class)) {
+ if (E->value.is_valid()) {
+ // Silently ensure it's parsed.
+ E->value->raise_status(GDScriptParserRef::PARSED);
+ if (E->value->get_parser()->has_class(p_class)) {
+ return E->value;
+ }
+ }
+ }
+
+ if (p_dependant_parser->get_parser()->has_class(p_class)) {
+ return p_dependant_parser;
+ }
+
+ // Silently ensure it's parsed.
+ p_dependant_parser->raise_status(GDScriptParserRef::PARSED);
+ return find_cached_external_parser_for_class(p_class, p_dependant_parser->get_parser());
+}
+
+Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser) {
+ if (p_dependant_parser == nullptr) {
+ return nullptr;
+ }
+
+ String script_path = p_class->get_datatype().script_path;
+ if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->depended_parsers.find(script_path)) {
+ if (E->value.is_valid()) {
+ // Silently ensure it's parsed.
+ E->value->raise_status(GDScriptParserRef::PARSED);
+ if (E->value->get_parser()->has_class(p_class)) {
+ return E->value;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+Ref<GDScript> GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) {
+ // To keep a local cache of the parser for resolving external nodes later.
+ parser->get_depended_parser_for(p_path);
+ Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path);
+ return scr;
+}
+
void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) {
ERR_FAIL_NULL(p_identifier);
p_identifier->set_datatype(p_identifier_datatype);
Error err = OK;
- Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path);
+ Ref<GDScript> scr = get_depended_shallow_script(p_identifier_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier);
return;
@@ -3767,7 +3882,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
bool is_base = true;
if (base_class != nullptr) {
- get_class_node_current_scope_classes(base_class, &script_classes);
+ get_class_node_current_scope_classes(base_class, &script_classes, p_identifier);
}
bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new");
@@ -4340,7 +4455,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
const String &res_type = ResourceLoader::get_resource_type(p_preload->resolved_path);
if (res_type == "GDScript") {
Error err = OK;
- Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path);
+ Ref<GDScript> res = get_depended_shallow_script(p_preload->resolved_path, err);
p_preload->resource = res;
if (err != OK) {
push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path);
@@ -4361,6 +4476,11 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
p_preload->is_constant = true;
p_preload->reduced_value = p_preload->resource;
p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload));
+
+ // TODO: Not sure if this is necessary anymore.
+ // 'type_from_variant()' should call 'resolve_class_inheritance()' which would call 'ensure_cached_external_parser_for_class()'
+ // Better safe than sorry.
+ ensure_cached_external_parser_for_class(p_preload->get_datatype().class_type, nullptr, "Trying to resolve preload", p_preload);
}
void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) {
@@ -4916,7 +5036,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
Ref<Script> script_type = p_element_datatype.script_type;
if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
- Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path);
+ Ref<GDScript> scr = get_depended_shallow_script(p_element_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node);
return array;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 922000df52..25e5aa9a2c 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -41,9 +41,22 @@
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
+ template <typename Fn>
+ class Finally {
+ Fn fn;
+
+ public:
+ Finally(Fn p_fn) :
+ fn(p_fn) {}
+ ~Finally() {
+ fn();
+ }
+ };
+
const GDScriptParser::EnumNode *current_enum = nullptr;
GDScriptParser::LambdaNode *current_lambda = nullptr;
List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas;
+ HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>> external_class_parser_cache;
bool static_context = false;
// Tests for detecting invalid overloading of script members
@@ -52,7 +65,7 @@ class GDScriptAnalyzer {
Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string);
Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node);
- void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list);
+ void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source);
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive);
@@ -132,6 +145,10 @@ class GDScriptAnalyzer {
void resolve_pending_lambda_bodies();
bool class_exists(const StringName &p_class) const;
void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
+ Ref<GDScriptParserRef> 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);
+ Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser);
+ Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser);
+ Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error);
#ifdef DEBUG_ENABLED
void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope);
#endif
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 7c9fba799d..3b6526ffd9 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -135,7 +135,11 @@ void GDScriptParserRef::clear() {
GDScriptParserRef::~GDScriptParserRef() {
clear();
- GDScriptCache::remove_parser(path);
+
+ if (!abandoned) {
+ MutexLock lock(GDScriptCache::singleton->mutex);
+ GDScriptCache::singleton->parser_map.erase(path);
+ }
}
GDScriptCache *GDScriptCache::singleton = nullptr;
@@ -151,15 +155,7 @@ void GDScriptCache::move_script(const String &p_from, const String &p_to) {
return;
}
- if (singleton->parser_map.has(p_from) && !p_from.is_empty()) {
- singleton->parser_map[p_to] = singleton->parser_map[p_from];
- }
- singleton->parser_map.erase(p_from);
-
- if (singleton->parser_inverse_dependencies.has(p_from) && !p_from.is_empty()) {
- singleton->parser_inverse_dependencies[p_to] = singleton->parser_inverse_dependencies[p_from];
- }
- singleton->parser_inverse_dependencies.erase(p_from);
+ remove_parser(p_from);
if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from];
@@ -183,6 +179,17 @@ void GDScriptCache::remove_script(const String &p_path) {
return;
}
+ if (HashMap<String, Vector<ObjectID>>::Iterator E = singleton->abandoned_parser_map.find(p_path)) {
+ for (ObjectID parser_ref_id : E->value) {
+ Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) };
+ if (parser_ref.is_valid()) {
+ parser_ref->clear();
+ }
+ }
+ }
+
+ singleton->abandoned_parser_map.erase(p_path);
+
if (singleton->parser_map.has(p_path)) {
singleton->parser_map[p_path]->clear();
}
@@ -229,6 +236,13 @@ bool GDScriptCache::has_parser(const String &p_path) {
void GDScriptCache::remove_parser(const String &p_path) {
MutexLock lock(singleton->mutex);
+
+ if (singleton->parser_map.has(p_path)) {
+ GDScriptParserRef *parser_ref = singleton->parser_map[p_path];
+ parser_ref->abandoned = true;
+ singleton->abandoned_parser_map[p_path].push_back(parser_ref->get_instance_id());
+ }
+
// Can't clear the parser because some other parser might be currently using it in the chain of calls.
singleton->parser_map.erase(p_path);
@@ -432,6 +446,17 @@ void GDScriptCache::clear() {
singleton->parser_inverse_dependencies.clear();
+ for (const KeyValue<String, Vector<ObjectID>> &KV : singleton->abandoned_parser_map) {
+ for (ObjectID parser_ref_id : KV.value) {
+ Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) };
+ if (parser_ref.is_valid()) {
+ parser_ref->clear();
+ }
+ }
+ }
+
+ singleton->abandoned_parser_map.clear();
+
RBSet<Ref<GDScriptParserRef>> parser_map_refs;
for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) {
parser_map_refs.insert(E.value);
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index c927317e19..f7f2cd90e9 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -59,6 +59,7 @@ private:
String path;
uint32_t source_hash = 0;
bool clearing = false;
+ bool abandoned = false;
friend class GDScriptCache;
friend class GDScript;
@@ -79,6 +80,7 @@ public:
class GDScriptCache {
// String key is full path.
HashMap<String, GDScriptParserRef *> parser_map;
+ HashMap<String, Vector<ObjectID>> abandoned_parser_map;
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, Ref<GDScript>> static_gdscript_cache;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index b0ac4aa800..d8b44a558f 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1064,6 +1064,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// Get at (potential) root stack pos, so it can be returned.
GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base);
+ const bool base_known_type = base.type.has_type;
+ const bool base_is_shared = Variant::is_type_shared(base.type.builtin_type);
if (r_error) {
return GDScriptCodeGenerator::Address();
@@ -1074,7 +1076,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
// In case the base has a setter, don't use the address directly, as we want to call that setter.
// So use a temp value instead and call the setter at the end.
GDScriptCodeGenerator::Address base_temp;
- if (base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) {
+ if ((!base_known_type || !base_is_shared) && base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) {
base_temp = codegen.add_temporary(base.type);
gen->write_assign(base_temp, base);
prev_base = base_temp;
@@ -1229,8 +1231,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
}
} else if (base_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
+ if (!base_known_type) {
+ gen->write_jump_if_shared(base);
+ }
// Save the temp value back to the base by calling its setter.
gen->write_call(GDScriptCodeGenerator::Address(), base, member_property_setter_function, { assigned });
+ if (!base_known_type) {
+ gen->write_end_jump_if_shared();
+ }
}
if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@@ -1893,7 +1901,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope.
+ codegen.start_block(); // Add an extra block, since @special locals belong to the match scope.
// Evaluate the match expression.
GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
@@ -1931,7 +1939,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::MatchBranchNode *branch = match->branches[j];
- codegen.start_block(); // Create an extra block around for binds.
+ codegen.start_block(); // Add an extra block, since binds belong to the match branch scope.
// Add locals in block before patterns, so temporaries don't use the stack address for binds.
List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block);
@@ -1983,13 +1991,15 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
_clear_block_locals(codegen, branch_locals);
- codegen.end_block(); // Get out of extra block.
+ codegen.end_block(); // Get out of extra block for binds.
}
// End all nested `if`s.
for (int j = 0; j < match->branches.size(); j++) {
gen->write_endif();
}
+
+ codegen.end_block(); // Get out of extra block for match's @special locals.
} break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
@@ -2023,7 +2033,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::FOR: {
const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
- codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope.
+ // Add an extra block, since the iterator and @special locals belong to the loop scope.
+ // Also we use custom logic to clear block locals.
+ codegen.start_block();
GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script));
@@ -2056,11 +2068,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
_clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
- codegen.end_block(); // Get out of extra block.
+ codegen.end_block(); // Get out of extra block for loop iterator, @special locals, and custom locals clearing.
} break;
case GDScriptParser::Node::WHILE: {
const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
+ codegen.start_block(); // Add an extra block, since we use custom logic to clear block locals.
+
gen->start_while_condition();
GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, while_n->condition);
@@ -2087,6 +2101,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->write_endwhile();
_clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
+
+ codegen.end_block(); // Get out of extra block for custom locals clearing.
} break;
case GDScriptParser::Node::BREAK: {
gen->write_break();
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index b1ffc02e4b..822fc412b4 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -402,7 +402,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
}
const Variant &var = gl_array[E.value];
- if (Object *obj = var) {
+ bool freed = false;
+ const Object *obj = var.get_validated_object_with_check(freed);
+ if (obj && !freed) {
if (Object::cast_to<GDScriptNativeClass>(obj)) {
continue;
}
@@ -796,7 +798,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
case GDScriptParser::Node::CALL: {
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer);
if (call->is_constant && call->reduced) {
- def_val = call->function_name.operator String() + call->reduced_value.operator String();
+ def_val = call->reduced_value.get_construct_string();
} else {
def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
}
@@ -804,7 +806,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer);
if (arr->is_constant && arr->reduced) {
- def_val = arr->reduced_value.operator String();
+ def_val = arr->reduced_value.get_construct_string();
} else {
def_val = arr->elements.is_empty() ? "[]" : "[...]";
}
@@ -812,24 +814,17 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer);
if (dict->is_constant && dict->reduced) {
- def_val = dict->reduced_value.operator String();
+ def_val = dict->reduced_value.get_construct_string();
} else {
def_val = dict->elements.is_empty() ? "{}" : "{...}";
}
} break;
case GDScriptParser::Node::SUBSCRIPT: {
const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer);
- if (sub->is_constant) {
- if (sub->datatype.kind == GDScriptParser::DataType::ENUM) {
- def_val = sub->get_datatype().to_string();
- } else if (sub->reduced) {
- const Variant::Type vt = sub->reduced_value.get_type();
- if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) {
- def_val = sub->reduced_value.operator String();
- } else {
- def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String();
- }
- }
+ if (sub->is_attribute && sub->datatype.kind == GDScriptParser::DataType::ENUM && !sub->datatype.is_meta_type) {
+ def_val = sub->get_datatype().to_string() + "." + sub->attribute->name;
+ } else if (sub->is_constant && sub->reduced) {
+ def_val = sub->reduced_value.get_construct_string();
}
} break;
default:
@@ -2759,6 +2754,20 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
base_type = base_type.class_type->base_type;
} break;
+ case GDScriptParser::DataType::SCRIPT: {
+ if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) {
+ r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx);
+ return;
+ }
+ Ref<Script> base_script = base_type.script_type->get_base_script();
+ if (base_script.is_valid()) {
+ base_type.script_type = base_script;
+ } else {
+ base_type.kind = GDScriptParser::DataType::NATIVE;
+ base_type.builtin_type = Variant::OBJECT;
+ base_type.native_type = base_type.script_type->get_instance_base_type();
+ }
+ } break;
case GDScriptParser::DataType::NATIVE: {
StringName class_name = base_type.native_type;
if (!ClassDB::class_exists(class_name)) {
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index fbfa4a0a79..025fcbd32a 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -622,7 +622,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
enable_stdout();
result.status = GDTEST_COMPILER_ERROR;
result.output = get_text_for_status(result.status) + "\n";
- result.output = compiler.get_error();
+ result.output += compiler.get_error() + "\n";
if (!p_is_generating) {
result.passed = check_output(result.output);
}
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out
index 64a6bd417d..878e827d04 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Could not resolve member "v".
+Could not resolve external class member "v".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd
new file mode 100644
index 0000000000..ad265a7365
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd
@@ -0,0 +1,17 @@
+extends "external_parser_base1.notest.gd"
+
+const External1 = preload("external_parser_script1.notest.gd")
+
+func baz(e1: External1) -> void:
+ print(e1.e1c.bar)
+ print(e1.baz)
+
+func test_external_base_parser_type_resolve(_v: TypeFromBase):
+ pass
+
+func test():
+ var ext := External1.new()
+ print(ext.array[0].test2)
+ print(ext.get_external2().get_external3().test3)
+ # TODO: This actually produces a runtime error, but we're testing the analyzer here
+ #baz(ext)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.out b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out
new file mode 100644
index 0000000000..26c2527fbc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+test2
+test3
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd
new file mode 100644
index 0000000000..d27d8cef3b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd
@@ -0,0 +1 @@
+extends "external_parser_base2.notest.gd"
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd
new file mode 100644
index 0000000000..f8de818485
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd
@@ -0,0 +1,2 @@
+class TypeFromBase:
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd
new file mode 100644
index 0000000000..e1221bb138
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd
@@ -0,0 +1,12 @@
+extends "external_parser_script1_base.notest.gd"
+
+const External2 = preload("external_parser_script2.notest.gd")
+const External1c = preload("external_parser_script1c.notest.gd")
+
+@export var e1c: External1c
+
+var array: Array[External2] = [ External2.new() ]
+var baz: int
+
+func get_external2() -> External2:
+ return External2.new()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd
new file mode 100644
index 0000000000..4249efa1f9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd
@@ -0,0 +1 @@
+extends Resource
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd
new file mode 100644
index 0000000000..34a48cba6f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd
@@ -0,0 +1 @@
+extends "external_parser_script1d.notest.gd"
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd
new file mode 100644
index 0000000000..43bc6f3e53
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd
@@ -0,0 +1,5 @@
+extends "external_parser_script1f.notest.gd"
+
+const External1e = preload("external_parser_script1e.notest.gd")
+
+var bar: Array[External1e]
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd
new file mode 100644
index 0000000000..4249efa1f9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd
@@ -0,0 +1 @@
+extends Resource
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd
new file mode 100644
index 0000000000..885f813503
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd
@@ -0,0 +1,6 @@
+const External3 = preload("external_parser_script3.notest.gd")
+
+var test2 := "test2"
+
+func get_external3() -> External3:
+ return External3.new()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd
new file mode 100644
index 0000000000..3e62974256
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd
@@ -0,0 +1 @@
+var test3 := "test3"
diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd
new file mode 100644
index 0000000000..3724c8c713
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd
@@ -0,0 +1,9 @@
+# https://github.com/godotengine/godot/issues/90086
+
+class MyObj:
+ var obj: WeakRef
+
+func test():
+ var obj_1 = MyObj.new()
+ var obj_2 = MyObj.new()
+ obj_1.obj = obj_2
diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out
new file mode 100644
index 0000000000..dfca5b1eca
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/invalid_property_assignment.gd
+>> 9
+>> Invalid assignment of property or key 'obj' with value of type 'RefCounted (MyObj)' on a base object of type 'RefCounted (MyObj)'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd
index c774ebf83c..df639a7b4d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd
@@ -1,6 +1,5 @@
# GH-77666
-
-func test():
+func test_exit_if():
var ref := RefCounted.new()
print(ref.get_reference_count())
@@ -8,3 +7,20 @@ func test():
var _temp := ref
print(ref.get_reference_count())
+
+# GH-94654
+func test_exit_while():
+ var slots_data := []
+
+ while true:
+ @warning_ignore("confusable_local_declaration")
+ var slot = 42
+ slots_data.append(slot)
+ break
+
+ var slot: int = slots_data[0]
+ print(slot)
+
+func test():
+ test_exit_if()
+ test_exit_while()
diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out
index 04b4638adf..164eb24963 100644
--- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out
+++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out
@@ -1,3 +1,4 @@
GDTEST_OK
1
1
+42
diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
new file mode 100644
index 0000000000..e1aba83507
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
@@ -0,0 +1,11 @@
+# https://github.com/godotengine/godot/issues/90086
+
+class MyObj:
+ var obj : WeakRef
+
+func test():
+ var obj_1 = MyObj.new()
+ var obj_2 = MyObj.new()
+ assert(obj_2.get_reference_count() == 1)
+ obj_1.set(&"obj", obj_2)
+ assert(obj_2.get_reference_count() == 1)
diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd
new file mode 100644
index 0000000000..f70b521e1a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd
@@ -0,0 +1,62 @@
+# GH-94667
+
+class Inner:
+ var subprop: Vector2:
+ set(value):
+ prints("subprop setter", value)
+ subprop = value
+ get:
+ print("subprop getter")
+ return subprop
+
+ func _to_string() -> String:
+ return "<Inner>"
+
+var prop1:
+ set(value):
+ prints("prop1 setter", value)
+ prop1 = value
+
+var prop2: Inner:
+ set(value):
+ prints("prop2 setter", value)
+ prop2 = value
+
+var prop3:
+ set(value):
+ prints("prop3 setter", value)
+ prop3 = value
+ get:
+ print("prop3 getter")
+ return prop3
+
+var prop4: Inner:
+ set(value):
+ prints("prop4 setter", value)
+ prop4 = value
+ get:
+ print("prop4 getter")
+ return prop4
+
+func test():
+ print("===")
+ prop1 = Vector2()
+ prop1.x = 1.0
+ print("---")
+ prop1 = Inner.new()
+ prop1.subprop.x = 1.0
+
+ print("===")
+ prop2 = Inner.new()
+ prop2.subprop.x = 1.0
+
+ print("===")
+ prop3 = Vector2()
+ prop3.x = 1.0
+ print("---")
+ prop3 = Inner.new()
+ prop3.subprop.x = 1.0
+
+ print("===")
+ prop4 = Inner.new()
+ prop4.subprop.x = 1.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
new file mode 100644
index 0000000000..c51759f481
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
@@ -0,0 +1,26 @@
+GDTEST_OK
+===
+prop1 setter (0, 0)
+prop1 setter (1, 0)
+---
+prop1 setter <Inner>
+subprop getter
+subprop setter (1, 0)
+===
+prop2 setter <Inner>
+subprop getter
+subprop setter (1, 0)
+===
+prop3 setter (0, 0)
+prop3 getter
+prop3 setter (1, 0)
+---
+prop3 setter <Inner>
+prop3 getter
+subprop getter
+subprop setter (1, 0)
+===
+prop4 setter <Inner>
+prop4 getter
+subprop getter
+subprop setter (1, 0)
diff --git a/modules/gltf/README.md b/modules/gltf/README.md
index 5d8966b201..2d1e92e070 100644
--- a/modules/gltf/README.md
+++ b/modules/gltf/README.md
@@ -1,11 +1,11 @@
-# Godot GLTF import and export module
+# Godot glTF import and export module
-In a nutshell, the GLTF module works like this:
+In a nutshell, the glTF module works like this:
-* The [`structures/`](structures/) folder contains GLTF structures, the
- small pieces that make up a GLTF file, represented as C++ classes.
-* The [`extensions/`](extensions/) folder contains GLTF extensions, which
- are optional features that build on top of the base GLTF spec.
+* The [`structures/`](structures/) folder contains glTF structures, the
+ small pieces that make up a glTF file, represented as C++ classes.
+* The [`extensions/`](extensions/) folder contains glTF extensions, which
+ are optional features that build on top of the base glTF spec.
* [`GLTFState`](gltf_state.h) holds collections of structures and extensions.
* [`GLTFDocument`](gltf_document.h) operates on GLTFState and its elements.
* The [`editor/`](editor/) folder uses GLTFDocument to import and export 3D models.
diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml
index dd059e6b79..bc142797a3 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFAccessor" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF accessor.
+ Represents a glTF accessor.
</brief_description>
<description>
- GLTFAccessor is a data structure representing GLTF a [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view.
- Most custom data stored in GLTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU.
+ GLTFAccessor is a data structure representing a glTF [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view.
+ Most custom data stored in glTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU.
</description>
<tutorials>
<link title="Buffers, BufferViews, and Accessors in Khronos glTF specification">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md</link>
@@ -13,7 +13,7 @@
</tutorials>
<members>
<member name="accessor_type" type="int" setter="set_accessor_type" getter="get_accessor_type" enum="GLTFAccessor.GLTFAccessorType" default="0">
- The GLTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4".
+ The glTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4".
</member>
<member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1">
The index of the buffer view this accessor is referencing. If [code]-1[/code], this accessor is not referencing any buffer view.
@@ -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. 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.
</member>
<member name="count" type="int" setter="set_count" getter="get_count" default="0">
The number of elements referenced by this accessor.
@@ -55,7 +55,7 @@
The offset relative to the start of the bufferView in bytes.
</member>
<member name="type" type="int" setter="set_type" getter="get_type" deprecated="Use [member accessor_type] instead.">
- The GLTF accessor type as an enum. Use [member accessor_type] instead.
+ The glTF accessor type as an enum. Use [member accessor_type] instead.
</member>
</members>
<constants>
diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml
index 2c70f4461f..d269145bbd 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 null.
</description>
</method>
<method name="set_additional_data">
@@ -22,7 +22,7 @@
<param index="1" name="additional_data" type="Variant" />
<description>
Sets 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 first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
+ The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want.
</description>
</method>
</methods>
diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml
index e191935fc9..b7f499ad72 100644
--- a/modules/gltf/doc_classes/GLTFBufferView.xml
+++ b/modules/gltf/doc_classes/GLTFBufferView.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFBufferView" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF buffer view.
+ Represents a glTF buffer view.
</brief_description>
<description>
- GLTFBufferView is a data structure representing GLTF a [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer.
+ GLTFBufferView is a data structure representing a glTF [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer.
Most custom uses of buffers only need to use the [member buffer], [member byte_length], and [member byte_offset]. The [member byte_stride] and [member indices] properties are for more advanced use cases such as interleaved mesh data encoded for the GPU.
</description>
<tutorials>
diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml
index b334bf2867..9fce21659c 100644
--- a/modules/gltf/doc_classes/GLTFCamera.xml
+++ b/modules/gltf/doc_classes/GLTFCamera.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFCamera" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF camera.
+ Represents a glTF camera.
</brief_description>
<description>
- Represents a camera as defined by the base GLTF spec.
+ Represents a camera as defined by the base glTF spec.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link>
- <link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link>
+ <link title="glTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link>
+ <link title="glTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
@@ -41,19 +41,19 @@
</methods>
<members>
<member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0">
- The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]zfar[/code] property.
+ The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]zfar[/code] property.
</member>
<member name="depth_near" type="float" setter="set_depth_near" getter="get_depth_near" default="0.05">
- 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.
+ 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 true.
</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.
+ 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.
</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 false.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 1b51def28e..ebeed015e9 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene.
- All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects.
+ All of the data in a glTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects.
GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported.
</description>
<tutorials>
@@ -21,7 +21,7 @@
<param index="2" name="state" type="GLTFState" />
<param index="3" name="flags" type="int" default="0" />
<description>
- Takes a [PackedByteArray] defining a GLTF and imports the data to the given [GLTFState] object through the [param state] parameter.
+ Takes a [PackedByteArray] defining a glTF and imports the data to the given [GLTFState] object through the [param state] parameter.
[b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty.
</description>
</method>
@@ -32,7 +32,7 @@
<param index="2" name="flags" type="int" default="0" />
<param index="3" name="base_path" type="String" default="&quot;&quot;" />
<description>
- Takes a path to a GLTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter.
+ Takes a path to a glTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter.
[b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty.
</description>
</method>
@@ -49,7 +49,7 @@
<return type="PackedByteArray" />
<param index="0" name="state" type="GLTFState" />
<description>
- Takes a [GLTFState] object through the [param state] parameter and returns a GLTF [PackedByteArray].
+ Takes a [GLTFState] object through the [param state] parameter and returns a glTF [PackedByteArray].
</description>
</method>
<method name="generate_scene">
@@ -91,7 +91,7 @@
</methods>
<members>
<member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default="&quot;PNG&quot;">
- The user-friendly name of the export image format. This is used when exporting the GLTF file, including writing to a file and writing to a byte array.
+ The user-friendly name of the export image format. This is used when exporting the glTF file, including writing to a file and writing to a byte array.
By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes.
</member>
<member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75">
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index 0eabcb5022..5c548c472f 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -4,7 +4,7 @@
[GLTFDocument] extension class.
</brief_description>
<description>
- Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export.
+ Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of glTF import or export.
To use, make a new class extending GLTFDocumentExtension, override any methods you need, make an instance of your class, and register it using [method GLTFDocument.register_gltf_document_extension].
[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>
@@ -30,7 +30,7 @@
<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 [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.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
@@ -38,7 +38,7 @@
<param index="0" name="state" type="GLTFState" />
<description>
Part of the export process. This method is run last, after all other parts of the export process.
- This method can be used to modify the final JSON of the generated GLTF file.
+ This method can be used to modify the final JSON of the generated glTF file.
</description>
</method>
<method name="_export_preflight" qualifiers="virtual">
@@ -47,7 +47,7 @@
<param index="1" name="root" type="Node" />
<description>
Part of the export process. This method is run first, before all other parts of the export process.
- The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
+ The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given glTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
</description>
</method>
<method name="_export_preserialize" qualifiers="virtual">
@@ -86,7 +86,7 @@
<return type="PackedStringArray" />
<description>
Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions].
- Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded.
+ Returns an array of the glTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a glTF file with required extensions can be loaded.
</description>
</method>
<method name="_import_node" qualifiers="virtual">
@@ -123,7 +123,7 @@
<param index="1" name="extensions" type="PackedStringArray" />
<description>
Part of the import process. This method is run first, before all other parts of the import process.
- The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given GLTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
+ The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given glTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned.
</description>
</method>
<method name="_parse_image_data" qualifiers="virtual">
@@ -134,7 +134,7 @@
<param index="3" name="ret_image" type="Image" />
<description>
Part of the import process. This method is run after [method _parse_node_extensions] and before [method _parse_texture_json].
- Runs when parsing image data from a GLTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array.
+ Runs when parsing image data from a glTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array.
</description>
</method>
<method name="_parse_node_extensions" qualifiers="virtual">
@@ -154,7 +154,7 @@
<param index="2" name="ret_gltf_texture" type="GLTFTexture" />
<description>
Part of the import process. This method is run after [method _parse_image_data] and before [method _generate_scene_node].
- Runs when parsing the texture JSON from the GLTF textures array. This can be used to set the source image index to use as the texture.
+ Runs when parsing the texture JSON from the glTF textures array. This can be used to set the source image index to use as the texture.
</description>
</method>
<method name="_save_image_at_path" qualifiers="virtual">
@@ -166,7 +166,7 @@
<param index="4" name="lossy_quality" type="float" />
<description>
Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json].
- This method is run when saving images separately from the GLTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
+ This method is run when saving images separately from the glTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
</description>
</method>
<method name="_serialize_image_to_bytes" qualifiers="virtual">
@@ -178,7 +178,7 @@
<param index="4" name="lossy_quality" type="float" />
<description>
Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json].
- This method is run when embedding images in the GLTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
+ This method is run when embedding images in the glTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter.
This method must set the image MIME type in the [param image_dict] with the [code]"mimeType"[/code] key. For example, for a PNG image, it would be set to [code]"image/png"[/code]. The return value must be a [PackedByteArray] containing the image data.
</description>
</method>
diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml
index 87ea159e7c..e07d24a144 100644
--- a/modules/gltf/doc_classes/GLTFLight.xml
+++ b/modules/gltf/doc_classes/GLTFLight.xml
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFLight" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF light.
+ Represents a glTF light.
</brief_description>
<description>
- Represents a light as defined by the [code]KHR_lights_punctual[/code] GLTF extension.
+ Represents a light as defined by the [code]KHR_lights_punctual[/code] glTF extension.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link>
+ <link title="KHR_lights_punctual glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
@@ -70,7 +70,7 @@
At this angle, the light drops off to zero brightness. Between the inner and outer cone angles, there is a transition from full brightness to zero brightness. If this angle is a half turn, then the spotlight emits in all directions. When creating a Godot [SpotLight3D], the outer cone angle is used as the angle of the spotlight.
</member>
<member name="range" type="float" setter="set_range" getter="get_range" default="inf">
- The range of the light, beyond which the light has no effect. GLTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096.
+ The range of the light, beyond which the light has no effect. glTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml
index b4c3db7618..da73c20c1d 100644
--- a/modules/gltf/doc_classes/GLTFMesh.xml
+++ b/modules/gltf/doc_classes/GLTFMesh.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFMesh" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- GLTFMesh represents a GLTF mesh.
+ GLTFMesh represents a glTF mesh.
</brief_description>
<description>
- GLTFMesh handles 3D mesh data imported from GLTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself.
+ GLTFMesh handles 3D mesh data imported from glTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
@@ -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 null.
</description>
</method>
<method name="set_additional_data">
@@ -24,7 +24,7 @@
<param index="1" name="additional_data" type="Variant" />
<description>
Sets 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 first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
+ The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want.
</description>
</method>
</methods>
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index 4a7570e4bc..2786c25e9a 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFNode" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- GLTF node class.
+ glTF node class.
</brief_description>
<description>
- Represents a GLTF node. GLTF nodes may have names, transforms, children (other GLTF nodes), and more specialized properties (represented by their own classes).
- GLTF nodes generally exist inside of [GLTFState] which represents all data of a GLTF file. Most of GLTFNode's properties are indices of other data in the GLTF file. You can extend a GLTF node with additional properties by using [method get_additional_data] and [method set_additional_data].
+ Represents a glTF node. glTF nodes may have names, transforms, children (other glTF nodes), and more specialized properties (represented by their own classes).
+ glTF nodes generally exist inside of [GLTFState] which represents all data of a glTF file. Most of GLTFNode's properties are indices of other data in the glTF file. You can extend a glTF node with additional properties by using [method get_additional_data] and [method set_additional_data].
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link>
+ <link title="glTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link>
</tutorials>
<methods>
<method name="get_additional_data">
@@ -17,7 +17,7 @@
<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 null.
</description>
</method>
<method name="set_additional_data">
@@ -26,25 +26,25 @@
<param index="1" name="additional_data" type="Variant" />
<description>
Sets 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 first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
+ The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want.
</description>
</method>
</methods>
<members>
<member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1">
- If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera.
+ If this glTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera.
</member>
<member name="children" type="PackedInt32Array" setter="set_children" getter="get_children" default="PackedInt32Array()">
- The indices of the child nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array.
+ The indices of the child nodes in the [GLTFState]. If this glTF node has no children, this will be an empty array.
</member>
<member name="height" type="int" setter="set_height" getter="get_height" default="-1">
How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated.
</member>
<member name="light" type="int" setter="set_light" getter="get_light" default="-1">
- If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light.
+ If this glTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light.
</member>
<member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1">
- If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh.
+ If this glTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh.
</member>
<member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default="&quot;&quot;">
The original name of the node.
@@ -53,22 +53,22 @@
The index of the parent node in the [GLTFState]. If -1, this node is a root node.
</member>
<member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)">
- The position of the GLTF node relative to its parent.
+ The position of the glTF node relative to its parent.
</member>
<member name="rotation" type="Quaternion" setter="set_rotation" getter="get_rotation" default="Quaternion(0, 0, 0, 1)">
- The rotation of the GLTF node relative to its parent.
+ The rotation of the glTF node relative to its parent.
</member>
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)">
- The scale of the GLTF node relative to its parent.
+ The scale of the glTF node relative to its parent.
</member>
<member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1">
- If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton.
+ If this glTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton.
</member>
<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
- If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin.
+ If this glTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin.
</member>
<member name="xform" type="Transform3D" setter="set_xform" getter="get_xform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)">
- The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred.
+ The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred.
</member>
</members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
index cd701e2f2f..1a76b190ba 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFPhysicsBody" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF physics body.
+ Represents a glTF physics body.
</brief_description>
<description>
- Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="OMI_physics_body GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link>
+ <link title="OMI_physics_body glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
<return type="GLTFPhysicsBody" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
- Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format.
+ Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] glTF extension format.
</description>
</method>
<method name="from_node" qualifiers="static">
@@ -28,7 +28,7 @@
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
- Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension.
+ Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] glTF extension.
</description>
</method>
<method name="to_node" qualifiers="const">
diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
index a4aaf9415c..53872a942f 100644
--- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml
+++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFPhysicsShape" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF physics shape.
+ Represents a glTF physics shape.
</brief_description>
<description>
- Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
+ Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] glTF extensions. This class is an intermediary between the glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link>
- <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link>
+ <link title="OMI_physics_shape glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link>
+ <link title="OMI_collider glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
@@ -66,7 +66,7 @@
This is the only variable not used in the [method to_node] method, it's intended to be used alongside when deciding where to add the generated node as a child.
</member>
<member name="mesh_index" type="int" setter="set_mesh_index" getter="get_mesh_index" default="-1">
- The index of the shape's mesh in the GLTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh).
+ The index of the shape's mesh in the glTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh).
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
The radius of the shape, in meters. This is only used when the shape type is "capsule", "cylinder", or "sphere". This value should not be negative.
diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml
index ac03a6ee9e..2dd3a37413 100644
--- a/modules/gltf/doc_classes/GLTFSkeleton.xml
+++ b/modules/gltf/doc_classes/GLTFSkeleton.xml
@@ -22,7 +22,7 @@
<method name="get_godot_bone_node">
<return type="Dictionary" />
<description>
- Returns a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes.
+ Returns a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes.
</description>
</method>
<method name="get_godot_skeleton">
@@ -39,7 +39,7 @@
<return type="void" />
<param index="0" name="godot_bone_node" type="Dictionary" />
<description>
- Sets a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes.
+ Sets a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes.
</description>
</method>
<method name="set_unique_names">
diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml
index 722fa5e9ae..11151f53d0 100644
--- a/modules/gltf/doc_classes/GLTFSpecGloss.xml
+++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFSpecGloss" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Archived GLTF extension for specular/glossy materials.
+ Archived glTF extension for specular/glossy materials.
</brief_description>
<description>
- KHR_materials_pbrSpecularGlossiness is an archived GLTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files.
+ KHR_materials_pbrSpecularGlossiness is an archived glTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="KHR_materials_pbrSpecularGlossiness GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link>
+ <link title="KHR_materials_pbrSpecularGlossiness glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link>
</tutorials>
<members>
<member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color(1, 1, 1, 1)">
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index 21a0527813..c049acf557 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFState" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents all data of a GLTF file.
+ Represents all data of a glTF file.
</brief_description>
<description>
- Contains all nodes and resources of a GLTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless.
- GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a GLTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the GLTF file or for custom data to be converted to/from Godot nodes.
+ Contains all nodes and resources of a glTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless.
+ GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a glTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the glTF file or for custom data to be converted to/from Godot nodes.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
- <link title="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link>
+ <link title="glTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link>
</tutorials>
<methods>
<method name="add_used_extension">
@@ -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 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.
</description>
</method>
<method name="append_data_to_buffers">
@@ -38,27 +38,27 @@
<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 null.
</description>
</method>
<method name="get_animation_player">
<return type="AnimationPlayer" />
<param index="0" name="idx" type="int" />
<description>
- Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations.
+ Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations.
</description>
</method>
<method name="get_animation_players_count">
<return type="int" />
<param index="0" name="idx" type="int" />
<description>
- Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations.
+ Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations.
</description>
</method>
<method name="get_animations">
<return type="GLTFAnimation[]" />
<description>
- Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes.
+ Returns an array of all [GLTFAnimation]s in the glTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes.
</description>
</method>
<method name="get_buffer_views">
@@ -69,7 +69,7 @@
<method name="get_cameras">
<return type="GLTFCamera[]" />
<description>
- Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to.
+ Returns an array of all [GLTFCamera]s in the glTF file. These are the cameras that the [member GLTFNode.camera] index refers to.
</description>
</method>
<method name="get_handle_binary_image">
@@ -80,13 +80,13 @@
<method name="get_images">
<return type="Texture2D[]" />
<description>
- Gets the images of the GLTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to.
+ Gets the images of the glTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to.
</description>
</method>
<method name="get_lights">
<return type="GLTFLight[]" />
<description>
- Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to.
+ Returns an array of all [GLTFLight]s in the glTF file. These are the lights that the [member GLTFNode.light] index refers to.
</description>
</method>
<method name="get_materials">
@@ -97,7 +97,7 @@
<method name="get_meshes">
<return type="GLTFMesh[]" />
<description>
- Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to.
+ Returns an array of all [GLTFMesh]es in the glTF file. These are the meshes that the [member GLTFNode.mesh] index refers to.
</description>
</method>
<method name="get_node_index">
@@ -111,7 +111,7 @@
<method name="get_nodes">
<return type="GLTFNode[]" />
<description>
- Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes.
+ Returns an array of all [GLTFNode]s in the glTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes.
</description>
</method>
<method name="get_scene_node">
@@ -125,19 +125,19 @@
<method name="get_skeletons">
<return type="GLTFSkeleton[]" />
<description>
- Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to.
+ Returns an array of all [GLTFSkeleton]s in the glTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to.
</description>
</method>
<method name="get_skins">
<return type="GLTFSkin[]" />
<description>
- Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to.
+ Returns an array of all [GLTFSkin]s in the glTF file. These are the skins that the [member GLTFNode.skin] index refers to.
</description>
</method>
<method name="get_texture_samplers">
<return type="GLTFTextureSampler[]" />
<description>
- Retrieves the array of texture samplers that are used by the textures contained in the GLTF.
+ Retrieves the array of texture samplers that are used by the textures contained in the glTF.
</description>
</method>
<method name="get_textures">
@@ -169,7 +169,7 @@
<param index="1" name="additional_data" type="Variant" />
<description>
Sets 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 first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want.
+ The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want.
</description>
</method>
<method name="set_animations">
@@ -250,7 +250,7 @@
<return type="void" />
<param index="0" name="texture_samplers" type="GLTFTextureSampler[]" />
<description>
- Sets the array of texture samplers that are used by the textures contained in the GLTF.
+ Sets the array of texture samplers that are used by the textures contained in the glTF.
</description>
</method>
<method name="set_textures">
@@ -279,17 +279,17 @@
The baking fps of the animation for either import or export.
</member>
<member name="base_path" type="String" setter="set_base_path" getter="get_base_path" default="&quot;&quot;">
- The folder path associated with this GLTF data. This is used to find other files the GLTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file.
+ The folder path associated with this glTF data. This is used to find other files the glTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file.
</member>
<member name="buffers" type="PackedByteArray[]" setter="set_buffers" getter="get_buffers" default="[]">
</member>
<member name="copyright" type="String" setter="set_copyright" getter="get_copyright" default="&quot;&quot;">
- The copyright string in the asset header of the GLTF file. This is set during import if present and export if non-empty. See the GLTF asset header documentation for more information.
+ The copyright string in the asset header of the glTF file. This is set during import if present and export if non-empty. See the glTF asset header documentation for more information.
</member>
<member name="create_animations" type="bool" setter="set_create_animations" getter="get_create_animations" default="true">
</member>
<member name="filename" type="String" setter="set_filename" getter="get_filename" default="&quot;&quot;">
- The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string.
+ The file name associated with this glTF data. If it ends with [code].gltf[/code], this is text-based glTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string.
</member>
<member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()">
The binary buffer attached to a .glb file.
@@ -305,10 +305,10 @@
<member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0">
</member>
<member name="root_nodes" type="PackedInt32Array" setter="set_root_nodes" getter="get_root_nodes" default="PackedInt32Array()">
- The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene.
+ The root nodes of the glTF file. Typically, a glTF file will only have one scene, and therefore one root node. However, a glTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene.
</member>
<member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default="&quot;&quot;">
- The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file.
+ The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the glTF file.
</member>
<member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false">
</member>
diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml
index 9ad7c0f4c6..2a868a8ba3 100644
--- a/modules/gltf/doc_classes/GLTFTexture.xml
+++ b/modules/gltf/doc_classes/GLTFTexture.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFTexture" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- GLTFTexture represents a texture in a GLTF file.
+ GLTFTexture represents a texture in a glTF file.
</brief_description>
<description>
</description>
diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml
index 2b5bad6724..d00ab463c2 100644
--- a/modules/gltf/doc_classes/GLTFTextureSampler.xml
+++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFTextureSampler" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
- Represents a GLTF texture sampler
+ Represents a glTF texture sampler
</brief_description>
<description>
- Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object.
+ Represents a texture sampler as defined by the base glTF spec. Texture samplers in glTF specify how to sample data from the texture's base image, when rendering the texture on an object.
</description>
<tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index f70e440781..4201dcd2ad 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -57,7 +57,7 @@
static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
if (!FileAccess::exists(p_path)) {
if (r_err) {
- *r_err = TTR("Path does not contain a Blender installation.");
+ *r_err = TTR("Path does not point to a valid executable.");
}
return false;
}
@@ -67,14 +67,14 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
Error err = OS::get_singleton()->execute(p_path, args, &pipe);
if (err != OK) {
if (r_err) {
- *r_err = TTR("Can't execute Blender binary.");
+ *r_err = TTR("Couldn't run Blender executable.");
}
return false;
}
int bl = pipe.find("Blender ");
if (bl == -1) {
if (r_err) {
- *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path);
+ *r_err = vformat(TTR("Unexpected --version output from Blender executable at: %s."), p_path);
}
return false;
}
@@ -83,7 +83,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
int pp = pipe.find(".");
if (pp == -1) {
if (r_err) {
- *r_err = TTR("Path supplied lacks a Blender binary.");
+ *r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path);
}
return false;
}
@@ -91,7 +91,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
r_major = v.to_int();
if (r_major < 3) {
if (r_err) {
- *r_err = TTR("This Blender installation is too old for this importer (not 3.0+).");
+ *r_err = vformat(TTR("Found Blender version %d.x, which is too old for this importer (3.0+ is required)."), r_major);
}
return false;
}
@@ -227,6 +227,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
} else {
parameters_map["export_normals"] = false;
}
+
+ if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 1)) {
+ if (p_options.has(SNAME("blender/meshes/export_geometry_nodes_instances")) && p_options[SNAME("blender/meshes/export_geometry_nodes_instances")]) {
+ parameters_map["export_gn_mesh"] = true;
+ if (blender_major_version == 4 && blender_minor_version == 1) {
+ // There is a bug in Blender 4.1 where it can't export lights and geometry nodes at the same time, one must be disabled.
+ parameters_map["export_lights"] = false;
+ }
+ } else {
+ parameters_map["export_gn_mesh"] = false;
+ }
+ }
if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) {
parameters_map["export_tangents"] = true;
} else {
@@ -350,6 +362,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li
ADD_OPTION_BOOL("blender/meshes/colors", false);
ADD_OPTION_BOOL("blender/meshes/uvs", true);
ADD_OPTION_BOOL("blender/meshes/normals", true);
+ ADD_OPTION_BOOL("blender/meshes/export_geometry_nodes_instances", false);
ADD_OPTION_BOOL("blender/meshes/tangents", true);
ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL);
ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false);
@@ -392,9 +405,9 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path
if (_test_blender_path(p_path, &error)) {
success = true;
if (auto_detected_path == p_path) {
- error = TTR("Path to Blender installation is valid (Autodetected).");
+ error = TTR("Path to Blender executable is valid (Autodetected).");
} else {
- error = TTR("Path to Blender installation is valid.");
+ error = TTR("Path to Blender executable is valid.");
}
}
}
@@ -490,11 +503,15 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
if (!configure_blender_dialog) {
configure_blender_dialog = memnew(ConfirmationDialog);
configure_blender_dialog->set_title(TTR("Configure Blender Importer"));
- configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally .
+ configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally.
configure_blender_dialog->set_close_on_escape(false);
+ String select_exec_label = TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender executable.");
+#ifdef MACOS_ENABLED
+ select_exec_label += "\n" + TTR("On macOS, this should be the `Contents/MacOS/blender` file within the Blender `.app` folder.");
+#endif
VBoxContainer *vb = memnew(VBoxContainer);
- vb->add_child(memnew(Label(TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender installation:"))));
+ vb->add_child(memnew(Label(select_exec_label)));
HBoxContainer *hb = memnew(HBoxContainer);
@@ -528,8 +545,8 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
browse_dialog = memnew(EditorFileDialog);
browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
- browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
- browse_dialog->connect("dir_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install));
+ browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ browse_dialog->connect("file_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install));
EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog);
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 07faee3dfc..676f764c11 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -34,9 +34,6 @@
#include "scene/3d/mesh_instance_3d.h"
#include "scene/resources/3d/importer_mesh.h"
-void GLTFDocumentExtensionConvertImporterMesh::_bind_methods() {
-}
-
void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object, Object *p_dst_object) {
List<StringName> meta_list;
p_src_object->get_meta_list(&meta_list);
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h
index ca10444eb5..b216a47a7f 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h
@@ -37,7 +37,6 @@ class GLTFDocumentExtensionConvertImporterMesh : public GLTFDocumentExtension {
GDCLASS(GLTFDocumentExtensionConvertImporterMesh, GLTFDocumentExtension);
protected:
- static void _bind_methods();
static void _copy_meta(Object *p_src_object, Object *p_dst_object);
public:
diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp
index c1d2fea98b..f6e91c1635 100644
--- a/modules/gltf/extensions/gltf_light.cpp
+++ b/modules/gltf/extensions/gltf_light.cpp
@@ -170,7 +170,7 @@ Light3D *GLTFLight::to_node() const {
}
Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) {
- ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse GLTF light, missing required field 'type'.");
+ ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse glTF light, missing required field 'type'.");
Ref<GLTFLight> light;
light.instantiate();
const String &type = p_dictionary["type"];
@@ -181,7 +181,7 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) {
if (arr.size() == 3) {
light->color = Color(arr[0], arr[1], arr[2]).linear_to_srgb();
} else {
- ERR_PRINT("Error parsing GLTF light: The color must have exactly 3 numbers.");
+ ERR_PRINT("Error parsing glTF light: The color must have exactly 3 numbers.");
}
}
if (p_dictionary.has("intensity")) {
@@ -195,10 +195,10 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) {
light->inner_cone_angle = spot["innerConeAngle"];
light->outer_cone_angle = spot["outerConeAngle"];
if (light->inner_cone_angle >= light->outer_cone_angle) {
- ERR_PRINT("Error parsing GLTF light: The inner angle must be smaller than the outer angle.");
+ ERR_PRINT("Error parsing glTF light: The inner angle must be smaller than the outer angle.");
}
} else if (type != "point" && type != "directional") {
- ERR_PRINT("Error parsing GLTF light: Light type '" + type + "' is unknown.");
+ ERR_PRINT("Error parsing glTF light: Light type '" + type + "' is unknown.");
}
return light;
}
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 7e52cde059..5c26a1686b 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -88,7 +88,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
// "collider" is the index of the collider in the state colliders array.
int node_collider_index = node_collider_ext["collider"];
Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes"));
- ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ").");
+ ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ").");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]);
} else {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext));
@@ -103,7 +103,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
int node_shape_index = node_collider.get("shape", -1);
if (node_shape_index != -1) {
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()) + ").");
+ 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]);
} else {
// If this node is a collider but does not have a collider
@@ -117,7 +117,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
int node_shape_index = node_trigger.get("shape", -1);
if (node_shape_index != -1) {
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()) + ").");
+ 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]);
} else {
// If this node is a trigger but does not have a trigger shape,
@@ -150,7 +150,7 @@ void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref
return; // The mesh resource is already set up.
}
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes();
- ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
+ ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "glTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index];
ERR_FAIL_COND(gltf_mesh.is_null());
importer_mesh = gltf_mesh->get_mesh();
@@ -164,12 +164,12 @@ CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNod
bool is_trigger = p_physics_shape->get_is_trigger();
// This method is used for the case where we must generate a parent body.
// This is can happen for multiple reasons. One possibility is that this
- // GLTF file is using OMI_collider but not OMI_physics_body, or at least
+ // glTF file is using OMI_collider but not OMI_physics_body, or at least
// this particular node is not using it. Another possibility is that the
- // physics body information is set up on the same GLTF node, not a parent.
+ // physics body information is set up on the same glTF node, not a parent.
CollisionObject3D *body;
if (p_physics_body.is_valid()) {
- // This code is run when the physics body is on the same GLTF node.
+ // This code is run when the physics body is on the same glTF node.
body = p_physics_body->to_node();
if (is_trigger && (p_physics_body->get_body_type() != "trigger")) {
// Edge case: If the body's trigger and the collider's trigger
@@ -266,7 +266,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state
Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape"));
if (gltf_physics_shape.is_valid()) {
_setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape);
- // If this GLTF node specifies both a shape and a body, generate both.
+ // If this glTF node specifies both a shape and a body, generate both.
if (gltf_physics_body.is_valid()) {
return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body);
}
@@ -309,7 +309,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state
}
} else if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) {
if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) {
- // If the GLTF file wants this node to group solid shapes together,
+ // If the glTF file wants this node to group solid shapes together,
// and there is no parent body, we need to create a static body.
ancestor_col_obj = memnew(StaticBody3D);
ret = ancestor_col_obj;
@@ -386,7 +386,7 @@ void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Re
if (cast_to<CollisionShape3D>(p_scene_node)) {
CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node);
Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape);
- ERR_FAIL_COND_MSG(gltf_shape.is_null(), "GLTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?");
+ ERR_FAIL_COND_MSG(gltf_shape.is_null(), "glTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?");
{
Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh();
if (importer_mesh.is_valid()) {
diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp
index 7929b46542..c11aa5d2ff 100644
--- a/modules/gltf/extensions/physics/gltf_physics_body.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp
@@ -108,7 +108,7 @@ void GLTFPhysicsBody::set_body_type(String p_body_type) {
} else if (p_body_type == "trigger") {
body_type = PhysicsBodyType::TRIGGER;
} else {
- ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\".");
+ ERR_PRINT("Error setting glTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\".");
}
}
@@ -194,7 +194,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_
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.");
+ 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;
@@ -289,7 +289,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
physics_body->body_type = PhysicsBodyType::TRIGGER;
#endif // DISABLE_DEPRECATED
} else {
- ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized.");
+ ERR_PRINT("Error parsing glTF physics body: The body type in the glTF file \"" + body_type_string + "\" was not recognized.");
}
}
if (motion.has("mass")) {
@@ -300,7 +300,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
if (arr.size() == 3) {
physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers.");
+ ERR_PRINT("Error parsing glTF physics body: The linear velocity vector must have exactly 3 numbers.");
}
}
if (motion.has("angularVelocity")) {
@@ -308,7 +308,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
if (arr.size() == 3) {
physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
+ ERR_PRINT("Error parsing glTF physics body: The angular velocity vector must have exactly 3 numbers.");
}
}
if (motion.has("centerOfMass")) {
@@ -316,7 +316,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
if (arr.size() == 3) {
physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers.");
+ ERR_PRINT("Error parsing glTF physics body: The center of mass vector must have exactly 3 numbers.");
}
}
if (motion.has("inertiaDiagonal")) {
@@ -324,7 +324,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
if (arr.size() == 3) {
physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers.");
+ ERR_PRINT("Error parsing glTF physics body: The inertia diagonal vector must have exactly 3 numbers.");
}
}
if (motion.has("inertiaOrientation")) {
@@ -332,7 +332,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction
if (arr.size() == 4) {
physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3]));
} else {
- ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers.");
+ ERR_PRINT("Error parsing glTF physics body: The inertia orientation quaternion must have exactly 4 numbers.");
}
}
return physics_body;
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
index 6897bdbd3a..0340eb11b5 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
@@ -134,7 +134,7 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
Ref<ImporterMesh> _convert_hull_points_to_mesh(const Vector<Vector3> &p_hull_points) {
Ref<ImporterMesh> importer_mesh;
- ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls.");
+ ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to glTF, since it uses a mesh to represent convex hulls.");
if (p_hull_points.size() > 255) {
WARN_PRINT("GLTFPhysicsShape: Convex hull has more points (" + itos(p_hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines.");
}
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index dff1e62e82..cd25b93e6c 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -620,7 +620,7 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
Error err = ext->parse_node_extensions(p_state, node, extensions);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing.");
+ ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing.");
}
}
@@ -3353,7 +3353,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
ERR_CONTINUE(image.is_null());
if (image->is_compressed()) {
image->decompress();
- ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed.");
+ ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "glTF: Image was compressed, but could not be decompressed.");
}
if (p_state->filename.to_lower().ends_with("gltf")) {
@@ -3374,7 +3374,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
if (_image_save_extension.is_valid()) {
img_name = img_name + _image_save_extension->get_image_file_extension();
Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality);
- ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, "glTF: Failed to save image in '" + _image_format + "' format as a separate file.");
} else if (_image_format == "PNG") {
img_name = img_name + ".png";
image->save_png(full_texture_dir.path_join(img_name));
@@ -3382,7 +3382,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
img_name = img_name + ".jpg";
image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality);
} else {
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'.");
}
image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode();
} else {
@@ -3412,9 +3412,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) {
buffer = image->save_jpg_to_buffer(_lossy_quality);
image_dict["mimeType"] = "image/jpeg";
} else {
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'.");
+ ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'.");
}
- ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format.");
+ ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "glTF: Failed to save image in '" + _image_format + "' format.");
bv->byte_length = buffer.size();
p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length);
@@ -3445,7 +3445,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing.");
+ ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing.");
if (!r_image->is_empty()) {
r_file_extension = ext->get_image_file_extension();
return r_image;
@@ -3736,13 +3736,13 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) {
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture);
- ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing.");
+ ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing.");
if (gltf_texture->get_src_image() != -1) {
break;
}
}
if (gltf_texture->get_src_image() == -1) {
- // No extensions handled it, so use the base GLTF source.
+ // No extensions handled it, so use the base glTF source.
// This may be the fallback, or the only option anyway.
ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR);
gltf_texture->set_src_image(texture_dict["source"]);
@@ -5631,7 +5631,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
// If none of our GLTFDocumentExtension classes generated us a node, we generate one.
if (!current_node) {
if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) {
- // GLTF specifies that skinned meshes should ignore their node transforms,
+ // glTF specifies that skinned meshes should ignore their node transforms,
// only being controlled by the skeleton, so Godot will reparent a skinned
// mesh to its skeleton. However, we still need to ensure any child nodes
// keep their place in the tree, so if there are any child nodes, the skinned
@@ -5658,6 +5658,15 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
if (p_scene_root == nullptr) {
// If the root node argument is null, this is the root node.
p_scene_root = current_node;
+ // If multiple nodes were generated under the root node, ensure they have the owner set.
+ if (unlikely(current_node->get_child_count() > 0)) {
+ Array args;
+ args.append(p_scene_root);
+ for (int i = 0; i < current_node->get_child_count(); i++) {
+ Node *child = current_node->get_child(i);
+ child->propagate_call(StringName("set_owner"), args);
+ }
+ }
} else {
// Add the node we generated and set the owner to the scene root.
p_scene_parent->add_child(current_node, true);
@@ -7138,9 +7147,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) {
HashMap<ObjectID, SkinSkeletonIndex> skeleton_map;
Error err = SkinTool::_create_skeletons(p_state->unique_names, p_state->skins, p_state->nodes,
skeleton_map, p_state->skeletons, p_state->scene_nodes);
- ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons.");
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skeletons.");
err = _create_skins(p_state);
- ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins.");
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skins.");
// Generate the node tree.
Node *single_root;
if (p_state->extensions_used.has("GODOT_single_root")) {
@@ -7459,7 +7468,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
Error ret = OK;
for (int i = 0; i < p_state->extensions_required.size(); i++) {
if (!supported_extensions.has(p_state->extensions_required[i])) {
- ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?");
+ ERR_PRINT("glTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?");
ret = ERR_UNAVAILABLE;
}
}
diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp
index d56f67a092..863e1df967 100644
--- a/modules/gltf/structures/gltf_camera.cpp
+++ b/modules/gltf/structures/gltf_camera.cpp
@@ -62,9 +62,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) {
c.instantiate();
ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null.");
c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE);
- // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees.
+ // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees.
c->set_fov(Math::deg_to_rad(p_camera->get_fov()));
- // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters.
+ // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters.
c->set_size_mag(p_camera->get_size() * 0.5f);
c->set_depth_far(p_camera->get_far());
c->set_depth_near(p_camera->get_near());
@@ -74,9 +74,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) {
Camera3D *GLTFCamera::to_node() const {
Camera3D *camera = memnew(Camera3D);
camera->set_projection(perspective ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL);
- // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees.
+ // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees.
camera->set_fov(Math::rad_to_deg(fov));
- // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters.
+ // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters.
camera->set_size(size_mag * 2.0f);
camera->set_near(depth_near);
camera->set_far(depth_far);
@@ -84,7 +84,7 @@ Camera3D *GLTFCamera::to_node() const {
}
Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) {
- ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse GLTF camera, missing required field 'type'.");
+ ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse glTF camera, missing required field 'type'.");
Ref<GLTFCamera> camera;
camera.instantiate();
const String &type = p_dictionary["type"];
@@ -107,7 +107,7 @@ Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) {
camera->set_depth_near(ortho["znear"]);
}
} else {
- ERR_PRINT("Error parsing GLTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic.");
+ ERR_PRINT("Error parsing glTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic.");
}
return camera;
}
diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h
index ef55b06a76..1a583c82cc 100644
--- a/modules/gltf/structures/gltf_camera.h
+++ b/modules/gltf/structures/gltf_camera.h
@@ -42,8 +42,8 @@ class GLTFCamera : public Resource {
GDCLASS(GLTFCamera, Resource);
private:
- // GLTF has no default camera values, they should always be specified in
- // the GLTF file. Here we default to Godot's default camera settings.
+ // glTF has no default camera values, they should always be specified in
+ // the glTF file. Here we default to Godot's default camera settings.
bool perspective = true;
real_t fov = Math::deg_to_rad(75.0);
real_t size_mag = 0.5;
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index f402e2a583..ea63e07104 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -643,6 +643,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
_do_paste();
input_action = INPUT_NONE;
_update_paste_indicator();
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (mb->is_shift_pressed() && can_edit) {
input_action = INPUT_SELECT;
last_selection = selection;
diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp
index c49c62a08b..ba59bb25ee 100644
--- a/modules/hdr/image_loader_hdr.cpp
+++ b/modules/hdr/image_loader_hdr.cpp
@@ -68,9 +68,11 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
imgdata.resize(height * width * (int)sizeof(uint32_t));
{
- uint8_t *w = imgdata.ptrw();
+ uint8_t *ptr = imgdata.ptrw();
- uint8_t *ptr = (uint8_t *)w;
+ Vector<uint8_t> temp_read_data;
+ temp_read_data.resize(128);
+ uint8_t *temp_read_ptr = temp_read_data.ptrw();
if (width < 8 || width >= 32768) {
// Read flat data
@@ -113,8 +115,9 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
}
} else {
// Dump
+ f->get_buffer(temp_read_ptr, count);
for (int z = 0; z < count; ++z) {
- ptr[(j * width + i++) * 4 + k] = f->get_8();
+ ptr[(j * width + i++) * 4 + k] = temp_read_ptr[z];
}
}
}
@@ -122,20 +125,27 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
}
}
+ const bool force_linear = p_flags & FLAG_FORCE_LINEAR;
+
//convert
for (int i = 0; i < width * height; i++) {
- float exp = pow(2.0f, ptr[3] - 128.0f);
+ int e = ptr[3] - 128;
+
+ if (force_linear || (e < -15 || e > 15)) {
+ float exp = pow(2.0f, e);
+ Color c(ptr[0] * exp / 255.0, ptr[1] * exp / 255.0, ptr[2] * exp / 255.0);
- Color c(
- ptr[0] * exp / 255.0,
- ptr[1] * exp / 255.0,
- ptr[2] * exp / 255.0);
+ if (force_linear) {
+ c = c.srgb_to_linear();
+ }
- if (p_flags & FLAG_FORCE_LINEAR) {
- c = c.srgb_to_linear();
+ *(uint32_t *)ptr = c.to_rgbe9995();
+ } else {
+ // https://github.com/george-steel/rgbe-rs/blob/e7cc33b7f42b4eb3272c166dac75385e48687c92/src/types.rs#L123-L129
+ uint32_t e5 = (uint32_t)(e + 15);
+ *(uint32_t *)ptr = ((e5 << 27) | ((uint32_t)ptr[2] << 19) | ((uint32_t)ptr[1] << 10) | ((uint32_t)ptr[0] << 1));
}
- *(uint32_t *)ptr = c.to_rgbe9995();
ptr += 4;
}
}
diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h
index 9821db059e..0a8e91fb9e 100644
--- a/modules/hdr/image_loader_hdr.h
+++ b/modules/hdr/image_loader_hdr.h
@@ -37,6 +37,7 @@ class ImageLoaderHDR : public ImageFormatLoader {
public:
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
+
ImageLoaderHDR();
};
diff --git a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml
index ea914715a3..5353dc7376 100644
--- a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml
+++ b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml
@@ -47,7 +47,7 @@
</members>
<constants>
<constant name="MAX_STREAMS" value="32">
- Maximum amount of streams that can be synchrohized.
+ Maximum amount of streams that can be synchronized.
</constant>
</constants>
</class>
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 e29cc753c9..fcb477995f 100644
--- a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
+++ b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/themes/editor_scale.h"
#include "scene/gui/check_box.h"
#include "scene/gui/option_button.h"
#include "scene/gui/spin_box.h"
@@ -290,7 +291,6 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) {
}
int min_w = header_font->get_string_size(name + "XX").width;
- tree->set_column_expand(cell_index, false);
tree->set_column_custom_minimum_width(cell_index, min_w);
max_w = MAX(max_w, min_w);
@@ -314,11 +314,10 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) {
}
}
- tree->set_column_expand(header_index, false);
tree->set_column_custom_minimum_width(header_index, max_w);
selection_order.clear();
_update_selection();
- popup_centered_ratio(0.6);
+ popup_centered_clamped(Size2(900, 450) * EDSCALE);
updating = false;
_update_transitions();
}
@@ -332,6 +331,7 @@ AudioStreamInteractiveTransitionEditor::AudioStreamInteractiveTransitionEditor()
tree->set_hide_root(true);
tree->add_theme_constant_override("draw_guides", 1);
tree->set_select_mode(Tree::SELECT_MULTI);
+ tree->set_custom_minimum_size(Size2(400, 0) * EDSCALE);
split->add_child(tree);
tree->set_h_size_flags(Control::SIZE_EXPAND_FILL);
diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml
index 0dbe06d220..61d802aea3 100644
--- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml
+++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml
@@ -10,7 +10,7 @@
[codeblock]
var interface = XRServer.find_interface("Native mobile")
if interface and interface.initialize():
- get_viewport().xr = true
+ get_viewport().use_xr = true
[/codeblock]
</description>
<tutorials>
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 36c8a40ed9..6d561c1566 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -148,7 +148,7 @@ void CSharpLanguage::finalize() {
finalizing = true;
- // Make sure all script binding gchandles are released before finalizing GDMono
+ // Make sure all script binding gchandles are released before finalizing GDMono.
for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
CSharpScriptBinding &script_binding = E.value;
@@ -156,6 +156,10 @@ void CSharpLanguage::finalize() {
script_binding.gchandle.release();
script_binding.inited = false;
}
+
+ // Make sure we clear all the instance binding callbacks so they don't get called
+ // after finalizing the C# language.
+ script_binding.owner->free_instance_binding(this);
}
if (gdmono) {
@@ -1227,6 +1231,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin
}
GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, void *p_binding, GDExtensionBool p_reference) {
+ // Instance bindings callbacks can only be called if the C# language is available.
+ // Failing this assert usually means that we didn't clear the instance binding in some Object
+ // and the C# language has already been finalized.
+ DEV_ASSERT(CSharpLanguage::get_singleton() != nullptr);
+
CRASH_COND(!p_binding);
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)p_binding)->get();
@@ -1662,7 +1671,7 @@ bool CSharpInstance::_reference_owner_unsafe() {
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: _unreference_owner_unsafe()
- // May not me referenced yet, so we must use init_ref() instead of reference()
+ // May not be referenced yet, so we must use init_ref() instead of reference()
if (static_cast<RefCounted *>(owner)->init_ref()) {
CSharpLanguage::get_singleton()->post_unsafe_reference(owner);
unsafe_referenced = true;
@@ -2351,8 +2360,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
if (!ok) {
// Important to clear this before destroying the script instance here
instance->script = Ref<CSharpScript>();
- instance->owner = nullptr;
p_owner->set_script_instance(nullptr);
+ instance->owner = nullptr;
return nullptr;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
index 74623a60ba..ee624a443d 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -7,7 +7,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.NET.Sdk</PackageId>
- <Version>4.3.0</Version>
+ <Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs
index 217f467637..9a8b3ea846 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs
@@ -11,11 +11,27 @@ partial class ExportDiagnostics_GD0107_OK
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues()
{
- var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(2);
+ var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(10);
global::Godot.Node __NodeProperty_default_value = default;
values.Add(PropertyName.@NodeProperty, global::Godot.Variant.From<global::Godot.Node>(__NodeProperty_default_value));
+ global::Godot.Node[] __SystemArrayOfNodesProperty_default_value = default;
+ values.Add(PropertyName.@SystemArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesProperty_default_value));
+ global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesProperty_default_value = default;
+ values.Add(PropertyName.@GodotArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesProperty_default_value));
+ global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyProperty_default_value = default;
+ values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyProperty_default_value));
+ global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueProperty_default_value = default;
+ values.Add(PropertyName.@GodotDictionaryWithNodeAsValueProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueProperty_default_value));
global::Godot.Node __NodeField_default_value = default;
values.Add(PropertyName.@NodeField, global::Godot.Variant.From<global::Godot.Node>(__NodeField_default_value));
+ global::Godot.Node[] __SystemArrayOfNodesField_default_value = default;
+ values.Add(PropertyName.@SystemArrayOfNodesField, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesField_default_value));
+ global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesField_default_value = default;
+ values.Add(PropertyName.@GodotArrayOfNodesField, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesField_default_value));
+ global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyField_default_value = default;
+ values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyField_default_value));
+ global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueField_default_value = default;
+ values.Add(PropertyName.@GodotDictionaryWithNodeAsValueField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueField_default_value));
return values;
}
#endif // TOOLS
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs
index 067783ea66..4613d883c2 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs
@@ -1,4 +1,5 @@
using Godot;
+using Godot.Collections;
public partial class ExportDiagnostics_GD0107_OK : Node
{
@@ -6,7 +7,31 @@ public partial class ExportDiagnostics_GD0107_OK : Node
public Node NodeField;
[Export]
+ public Node[] SystemArrayOfNodesField;
+
+ [Export]
+ public Array<Node> GodotArrayOfNodesField;
+
+ [Export]
+ public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyField;
+
+ [Export]
+ public Dictionary<string, Node> GodotDictionaryWithNodeAsValueField;
+
+ [Export]
public Node NodeProperty { get; set; }
+
+ [Export]
+ public Node[] SystemArrayOfNodesProperty { get; set; }
+
+ [Export]
+ public Array<Node> GodotArrayOfNodesProperty { get; set; }
+
+ [Export]
+ public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyProperty { get; set; }
+
+ [Export]
+ public Dictionary<string, Node> GodotDictionaryWithNodeAsValueProperty { get; set; }
}
public partial class ExportDiagnostics_GD0107_KO : Resource
@@ -15,5 +40,29 @@ public partial class ExportDiagnostics_GD0107_KO : Resource
public Node {|GD0107:NodeField|};
[Export]
+ public Node[] {|GD0107:SystemArrayOfNodesField|};
+
+ [Export]
+ public Array<Node> {|GD0107:GodotArrayOfNodesField|};
+
+ [Export]
+ public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyField|};
+
+ [Export]
+ public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueField|};
+
+ [Export]
public Node {|GD0107:NodeProperty|} { get; set; }
+
+ [Export]
+ public Node[] {|GD0107:SystemArrayOfNodesProperty|} { get; set; }
+
+ [Export]
+ public Array<Node> {|GD0107:GodotArrayOfNodesProperty|} { get; set; }
+
+ [Export]
+ public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyProperty|} { get; set; }
+
+ [Export]
+ public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueProperty|} { get; set; }
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
index 1aa2979e76..8e407da7a6 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -9,7 +9,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.SourceGenerators</PackageId>
- <Version>4.3.0</Version>
+ <Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
index efe88d8468..626f51ecae 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -196,16 +196,13 @@ namespace Godot.SourceGenerators
continue;
}
- if (marshalType == MarshalType.GodotObjectOrDerived)
+ if (!isNode && MemberHasNodeType(propertyType, marshalType.Value))
{
- if (!isNode && propertyType.InheritsFrom("GodotSharp", GodotClasses.Node))
- {
- context.ReportDiagnostic(Diagnostic.Create(
- Common.OnlyNodesShouldExportNodesRule,
- property.Locations.FirstLocationWithSourceTreeOrDefault()
- ));
- continue;
- }
+ context.ReportDiagnostic(Diagnostic.Create(
+ Common.OnlyNodesShouldExportNodesRule,
+ property.Locations.FirstLocationWithSourceTreeOrDefault()
+ ));
+ continue;
}
var propertyDeclarationSyntax = property.DeclaringSyntaxReferences
@@ -315,16 +312,13 @@ namespace Godot.SourceGenerators
continue;
}
- if (marshalType == MarshalType.GodotObjectOrDerived)
+ if (!isNode && MemberHasNodeType(fieldType, marshalType.Value))
{
- if (!isNode && fieldType.InheritsFrom("GodotSharp", GodotClasses.Node))
- {
- context.ReportDiagnostic(Diagnostic.Create(
- Common.OnlyNodesShouldExportNodesRule,
- field.Locations.FirstLocationWithSourceTreeOrDefault()
- ));
- continue;
- }
+ context.ReportDiagnostic(Diagnostic.Create(
+ Common.OnlyNodesShouldExportNodesRule,
+ field.Locations.FirstLocationWithSourceTreeOrDefault()
+ ));
+ continue;
}
EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences
@@ -424,6 +418,27 @@ namespace Godot.SourceGenerators
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
+ private static bool MemberHasNodeType(ITypeSymbol memberType, MarshalType marshalType)
+ {
+ if (marshalType == MarshalType.GodotObjectOrDerived)
+ {
+ return memberType.InheritsFrom("GodotSharp", GodotClasses.Node);
+ }
+ if (marshalType == MarshalType.GodotObjectOrDerivedArray)
+ {
+ var elementType = ((IArrayTypeSymbol)memberType).ElementType;
+ return elementType.InheritsFrom("GodotSharp", GodotClasses.Node);
+ }
+ if (memberType is INamedTypeSymbol { IsGenericType: true } genericType)
+ {
+ return genericType.TypeArguments
+ .Any(static typeArgument
+ => typeArgument.InheritsFrom("GodotSharp", GodotClasses.Node));
+ }
+
+ return false;
+ }
+
private struct ExportedPropertyMetadata
{
public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index d3720dcb72..ede0600ac1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -355,24 +355,23 @@ namespace GodotTools.Export
if (outputPaths.Count > 2)
{
// lipo the simulator binaries together
- // TODO: Move this to the native lipo implementation we have in the macos export plugin.
- var lipoArgs = new List<string>();
- lipoArgs.Add("-create");
- lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")));
- lipoArgs.Add("-output");
- lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"));
- int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
- if (lipoExitCode != 0)
- throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
+ string outputPath = Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib");
+ string[] files = outputPaths
+ .Skip(1)
+ .Select(path => Path.Combine(path, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))
+ .ToArray();
+
+ if (!Internal.LipOCreateFile(outputPath, files))
+ {
+ throw new InvalidOperationException($"Failed to 'lipo' simulator binaries.");
+ }
outputPaths.RemoveRange(2, outputPaths.Count - 2);
}
- var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
- $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
- if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
- Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
+ string xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
+ if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, xcFrameworkPath))
{
throw new InvalidOperationException("Failed to generate xcframework.");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
deleted file mode 100644
index 023f46b685..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.IO;
-
-namespace GodotTools.Export
-{
- public static class XcodeHelper
- {
- private static string? _XcodePath = null;
-
- public static string XcodePath
- {
- get
- {
- if (_XcodePath == null)
- {
- _XcodePath = FindXcode();
-
- if (_XcodePath == null)
- throw new FileNotFoundException("Could not find Xcode.");
- }
-
- return _XcodePath;
- }
- }
-
- private static string? FindSelectedXcode()
- {
- var outputWrapper = new Godot.Collections.Array();
-
- int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper);
-
- if (exitCode == 0)
- {
- string output = (string)outputWrapper[0];
- return output.Trim();
- }
-
- Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}");
-
- return null;
- }
-
- public static string? FindXcode()
- {
- string? selectedXcode = FindSelectedXcode();
- if (selectedXcode != null)
- {
- if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
- return selectedXcode;
-
- // The path already pointed to Contents/Developer
- var dirInfo = new DirectoryInfo(selectedXcode);
- if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" })
- {
- Console.WriteLine(Path.GetDirectoryName(selectedXcode));
- Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name);
- Console.Error.WriteLine("Unrecognized path for selected Xcode");
- }
- else
- {
- return System.IO.Path.GetFullPath($"{selectedXcode}/../..");
- }
- }
- else
- {
- Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path");
- }
-
- const string XcodeHintPath = "/Applications/Xcode.app";
-
- if (Directory.Exists(XcodeHintPath))
- {
- if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer")))
- return XcodeHintPath;
-
- Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory");
- }
-
- return null;
- }
-
- public static string FindXcodeTool(string toolName)
- {
- string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain");
-
- string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName);
- if (File.Exists(path))
- return path;
-
- throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}");
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 175bb78051..225ac4073b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -35,6 +35,13 @@ namespace GodotTools.Internals
return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn);
}
+ public static bool LipOCreateFile(string outputPath, string[] files)
+ {
+ using godot_string outputPathIn = Marshaling.ConvertStringToNative(outputPath);
+ using godot_packed_string_array filesIn = Marshaling.ConvertSystemArrayToNativePackedStringArray(files);
+ return godot_icall_Internal_LipOCreateFile(outputPathIn, filesIn);
+ }
+
public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble();
@@ -121,6 +128,8 @@ namespace GodotTools.Internals
private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId);
+ private static partial bool godot_icall_Internal_LipOCreateFile(in godot_string outputPath, in godot_packed_string_array files);
+
private static partial bool godot_icall_Internal_GodotIs32Bits();
private static partial bool godot_icall_Internal_GodotIsRealTDouble();
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 03d8b4eab6..7322a47630 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
+#include "editor/export/lipo.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
@@ -117,6 +118,13 @@ bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle
#endif
}
+bool godot_icall_Internal_LipOCreateFile(const godot_string *p_output_path, const godot_packed_array *p_files) {
+ String output_path = *reinterpret_cast<const String *>(p_output_path);
+ PackedStringArray files = *reinterpret_cast<const PackedStringArray *>(p_files);
+ LipO lip;
+ return lip.create_file(output_path, files);
+}
+
bool godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
@@ -258,6 +266,7 @@ static const void *unmanaged_callbacks[]{
(void *)godot_icall_EditorProgress_Step,
(void *)godot_icall_Internal_FullExportTemplatesDir,
(void *)godot_icall_Internal_IsMacOSAppBundleInstalled,
+ (void *)godot_icall_Internal_LipOCreateFile,
(void *)godot_icall_Internal_GodotIs32Bits,
(void *)godot_icall_Internal_GodotIsRealTDouble,
(void *)godot_icall_Internal_GodotMainIteration,
diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs
index cd335934db..ece1ab44a2 100644
--- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs
+++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs
@@ -3,6 +3,8 @@
using _BINDINGS_NAMESPACE_;
using System;
+[Tool]
+[GlobalClass]
public partial class VisualShaderNode_CLASS_ : _BASE_
{
public override string _GetName()
@@ -20,37 +22,37 @@ public partial class VisualShaderNode_CLASS_ : _BASE_
return "";
}
- public override long _GetReturnIconType()
+ public override VisualShaderNode.PortType _GetReturnIconType()
{
return 0;
}
- public override long _GetInputPortCount()
+ public override int _GetInputPortCount()
{
return 0;
}
- public override string _GetInputPortName(long port)
+ public override string _GetInputPortName(int port)
{
return "";
}
- public override long _GetInputPortType(long port)
+ public override VisualShaderNode.PortType _GetInputPortType(int port)
{
return 0;
}
- public override long _GetOutputPortCount()
+ public override int _GetOutputPortCount()
{
return 1;
}
- public override string _GetOutputPortName(long port)
+ public override string _GetOutputPortName(int port)
{
return "result";
}
- public override long _GetOutputPortType(long port)
+ public override VisualShaderNode.PortType _GetOutputPortType(int port)
{
return 0;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
index 563a6abe9b..1fc6e54e09 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
#nullable enable
@@ -51,7 +52,7 @@ namespace Godot
/// </summary>
/// <param name="instance">The instance to check.</param>
/// <returns>If the instance is a valid object.</returns>
- public static bool IsInstanceValid(GodotObject? instance)
+ public static bool IsInstanceValid([NotNullWhen(true)] GodotObject? instance)
{
return instance != null && instance.NativeInstance != IntPtr.Zero;
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 50bf56d832..f5b64ff81b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -869,7 +869,7 @@ namespace Godot
}
/// <summary>
- /// Multiplies each component of the <see cref="Vector2"/>
+ /// Divides each component of the <see cref="Vector2"/>
/// by the given <see cref="real_t"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index 27f2713efa..ef66d5bbe0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
@@ -692,10 +692,18 @@ namespace Godot
// Zero length vectors have no angle, so the best we can do is either lerp or throw an error.
return Lerp(to, weight);
}
+ Vector3 axis = Cross(to);
+ real_t axisLengthSquared = axis.LengthSquared();
+ if (axisLengthSquared == 0.0)
+ {
+ // Colinear vectors have no rotation axis or angle between them, so the best we can do is lerp.
+ return Lerp(to, weight);
+ }
+ axis /= Mathf.Sqrt(axisLengthSquared);
real_t startLength = Mathf.Sqrt(startLengthSquared);
real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight);
real_t angle = AngleTo(to);
- return Rotated(Cross(to).Normalized(), angle * weight) * (resultLength / startLength);
+ return Rotated(axis, angle * weight) * (resultLength / startLength);
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 6b25087c93..b838f8eac7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -17,7 +17,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>GodotSharp</PackageId>
- <Version>4.3.0</Version>
+ <Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 4561fdaf2b..65b4824f94 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -15,7 +15,7 @@
<Authors>Godot Engine contributors</Authors>
<PackageId>GodotSharpEditor</PackageId>
- <Version>4.3.0</Version>
+ <Version>4.4.0</Version>
<PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
index 3da245f806..b620292519 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
@@ -48,11 +48,11 @@
</methods>
<members>
<member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function">
- Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
+ Method called on all peers when a custom [method spawn] is requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
[b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
</member>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
- Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
+ Maximum number of nodes allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
When set to [code]0[/code] (the default), there is no limit.
</member>
<member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath(&quot;&quot;)">
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index d242b06c29..851ad85876 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -430,7 +430,7 @@ void ReplicationEditor::_tree_item_edited() {
undo_redo->add_do_method(config.ptr(), "property_set_spawn", prop, value);
undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, !value);
undo_redo->add_do_method(this, "_update_value", prop, column, value ? 1 : 0);
- undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 1 : 0);
+ undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 0 : 1);
undo_redo->commit_action();
} else if (column == 2) {
undo_redo->create_action(TTR("Set sync property"));
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index af43123b29..99c8930e92 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -54,11 +54,14 @@ void SceneCacheInterface::_remove_node_cache(ObjectID p_oid) {
if (nc->cache_id) {
assigned_ids.erase(nc->cache_id);
}
+#if 0
+ // TODO: Find a way to cleanup recv_nodes without breaking visibility and RPCs interactions.
for (KeyValue<int, int> &E : nc->recv_ids) {
PeerInfo *pinfo = peers_info.getptr(E.key);
ERR_CONTINUE(!pinfo);
pinfo->recv_nodes.erase(E.value);
}
+#endif
for (KeyValue<int, bool> &E : nc->confirmed_peers) {
PeerInfo *pinfo = peers_info.getptr(E.key);
ERR_CONTINUE(!pinfo);
@@ -73,9 +76,12 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
} else {
PeerInfo *pinfo = peers_info.getptr(p_id);
ERR_FAIL_NULL(pinfo); // Bug.
- for (KeyValue<int, ObjectID> E : pinfo->recv_nodes) {
- NodeCache *nc = nodes_cache.getptr(E.value);
- ERR_CONTINUE(!nc);
+ for (KeyValue<int, RecvNode> E : pinfo->recv_nodes) {
+ NodeCache *nc = nodes_cache.getptr(E.value.oid);
+ if (!nc) {
+ // Node might have already been deleted locally.
+ continue;
+ }
nc->recv_ids.erase(p_id);
}
for (const ObjectID &oid : pinfo->sent_nodes) {
@@ -115,7 +121,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path);
}
- peers_info[p_from].recv_nodes.insert(id, node->get_instance_id());
+ peers_info[p_from].recv_nodes.insert(id, RecvNode(node->get_instance_id(), path));
NodeCache &cache = _track(node);
cache.recv_ids.insert(p_from, id);
@@ -269,14 +275,21 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r
}
Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) {
- Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
- ERR_FAIL_NULL_V(root_node, nullptr);
PeerInfo *pinfo = peers_info.getptr(p_from);
ERR_FAIL_NULL_V(pinfo, nullptr);
- const ObjectID *oid = pinfo->recv_nodes.getptr(p_cache_id);
- ERR_FAIL_NULL_V_MSG(oid, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from));
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid));
+ RecvNode *recv_node = pinfo->recv_nodes.getptr(p_cache_id);
+ ERR_FAIL_NULL_V_MSG(recv_node, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from));
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(recv_node->oid));
+ if (!node) {
+ // Fallback to path lookup.
+ Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
+ ERR_FAIL_NULL_V(root_node, nullptr);
+ node = root_node->get_node(recv_node->path);
+ if (node) {
+ recv_node->oid = node->get_instance_id();
+ }
+ }
ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to get cached node from peer %d with cache ID %d.", p_from, p_cache_id));
return node;
}
diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h
index 73d6bde6ef..fbe618f4ad 100644
--- a/modules/multiplayer/scene_cache_interface.h
+++ b/modules/multiplayer/scene_cache_interface.h
@@ -49,8 +49,18 @@ private:
HashMap<int, bool> confirmed_peers; // peer id, confirmed
};
+ struct RecvNode {
+ ObjectID oid;
+ NodePath path;
+
+ RecvNode(const ObjectID &p_oid, const NodePath &p_path) {
+ oid = p_oid;
+ path = p_path;
+ }
+ };
+
struct PeerInfo {
- HashMap<int, ObjectID> recv_nodes; // remote cache id, ObjectID
+ HashMap<int, RecvNode> recv_nodes; // remote cache id, (ObjectID, NodePath)
HashSet<ObjectID> sent_nodes;
};
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 99aba680cc..e245101eeb 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -307,8 +307,10 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
int len = p_packet_len - SYS_CMD_SIZE;
bool should_process = false;
if (get_unique_id() == 1) { // I am the server.
- // Direct messages to server should not go through relay.
- ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer));
+ // The requested target might have disconnected while the packet was in transit.
+ if (unlikely(peer > 0 && !connected_peers.has(peer))) {
+ return;
+ }
// Send relay packet.
relay_buffer->seek(0);
relay_buffer->put_u8(NETWORK_COMMAND_SYS);
@@ -319,21 +321,24 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
multiplayer_peer->set_transfer_mode(p_mode);
multiplayer_peer->set_transfer_channel(p_channel);
if (peer > 0) {
+ // Single destination.
multiplayer_peer->set_target_peer(peer);
_send(data.ptr(), relay_buffer->get_position());
} else {
+ // Multiple destinations.
for (const int &P : connected_peers) {
// Not to sender, nor excluded.
- if (P == p_from || (peer < 0 && P != -peer)) {
+ if (P == p_from || P == -peer) {
continue;
}
multiplayer_peer->set_target_peer(P);
_send(data.ptr(), relay_buffer->get_position());
}
- }
- if (peer == 0 || peer == -1) {
- should_process = true;
- peer = p_from; // Process as the source.
+ if (peer != -1) {
+ // The server is one of the targets, process the packet with sender as source.
+ should_process = true;
+ peer = p_from;
+ }
}
} else {
ERR_FAIL_COND(p_from != 1); // Bug.
@@ -425,11 +430,11 @@ void SceneMultiplayer::_del_peer(int p_id) {
void SceneMultiplayer::disconnect_peer(int p_id) {
ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED);
- if (pending_peers.has(p_id)) {
- pending_peers.erase(p_id);
- } else if (connected_peers.has(p_id)) {
- connected_peers.erase(p_id);
- }
+ // Block signals to avoid emitting peer_disconnected.
+ bool blocking = is_blocking_signals();
+ set_block_signals(true);
+ _del_peer(p_id);
+ set_block_signals(blocking);
multiplayer_peer->disconnect_peer(p_id);
}
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 8c2fb42463..33b92f6266 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -1042,10 +1042,32 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
TPPLPartition tpart;
- if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
- ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths.");
- p_navigation_mesh->clear();
- return;
+
+ NavigationPolygon::SamplePartitionType sample_partition_type = p_navigation_mesh->get_sample_partition_type();
+
+ switch (sample_partition_type) {
+ case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_CONVEX_PARTITION:
+ if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) {
+ ERR_PRINT("NavigationPolygon polygon convex partition failed. Unable to create a valid navigation mesh polygon layout from provided source geometry.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+ break;
+ case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_TRIANGULATE:
+ if (tpart.Triangulate_EC(&tppl_in_polygon, &tppl_out_polygon) == 0) {
+ ERR_PRINT("NavigationPolygon polygon triangulation failed. Unable to create a valid navigation mesh polygon layout from provided source geometry.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+ break;
+ default: {
+ ERR_PRINT("NavigationPolygon polygon partitioning failed. Unrecognized partition type.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
}
Vector<Vector2> new_vertices;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp
index 430d527844..11a5de608b 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.cpp
+++ b/modules/navigation/3d/godot_navigation_server_3d.cpp
@@ -509,22 +509,31 @@ void GodotNavigationServer3D::region_bake_navigation_mesh(Ref<NavigationMesh> p_
int GodotNavigationServer3D::region_get_connections_count(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, 0);
-
- return region->get_connections_count();
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connections_count(region);
+ }
+ return 0;
}
Vector3 GodotNavigationServer3D::region_get_connection_pathway_start(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
-
- return region->get_connection_pathway_start(p_connection_id);
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connection_pathway_start(region, p_connection_id);
+ }
+ return Vector3();
}
Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
-
- return region->get_connection_pathway_end(p_connection_id);
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connection_pathway_end(region, p_connection_id);
+ }
+ return Vector3();
}
Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
@@ -1298,6 +1307,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) {
int _new_pm_edge_merge_count = 0;
int _new_pm_edge_connection_count = 0;
int _new_pm_edge_free_count = 0;
+ int _new_pm_obstacle_count = 0;
// In c++ we can't be sure that this is performed in the main thread
// even with mutable functions.
@@ -1315,6 +1325,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) {
_new_pm_edge_merge_count += active_maps[i]->get_pm_edge_merge_count();
_new_pm_edge_connection_count += active_maps[i]->get_pm_edge_connection_count();
_new_pm_edge_free_count += active_maps[i]->get_pm_edge_free_count();
+ _new_pm_obstacle_count += active_maps[i]->get_pm_obstacle_count();
// Emit a signal if a map changed.
const uint32_t new_map_iteration_id = active_maps[i]->get_iteration_id();
@@ -1332,6 +1343,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) {
pm_edge_merge_count = _new_pm_edge_merge_count;
pm_edge_connection_count = _new_pm_edge_connection_count;
pm_edge_free_count = _new_pm_edge_free_count;
+ pm_obstacle_count = _new_pm_obstacle_count;
}
void GodotNavigationServer3D::init() {
@@ -1566,6 +1578,9 @@ int GodotNavigationServer3D::get_process_info(ProcessInfo p_info) const {
case INFO_EDGE_FREE_COUNT: {
return pm_edge_free_count;
} break;
+ case INFO_OBSTACLE_COUNT: {
+ return pm_obstacle_count;
+ } break;
}
return 0;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h
index 5ba7ed1088..12a1132f07 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.h
+++ b/modules/navigation/3d/godot_navigation_server_3d.h
@@ -95,6 +95,7 @@ class GodotNavigationServer3D : public NavigationServer3D {
int pm_edge_merge_count = 0;
int pm_edge_connection_count = 0;
int pm_edge_free_count = 0;
+ int pm_obstacle_count = 0;
public:
GodotNavigationServer3D();
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index d07d3cdff5..f37ed9b168 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -126,9 +126,6 @@ void NavigationMeshEditor::edit(NavigationRegion3D *p_nav_region) {
node = p_nav_region;
}
-void NavigationMeshEditor::_bind_methods() {
-}
-
NavigationMeshEditor::NavigationMeshEditor() {
bake_hbox = memnew(HBoxContainer);
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h
index 6114c62ebf..f5a471d531 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.h
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h
@@ -60,7 +60,6 @@ class NavigationMeshEditor : public Control {
protected:
void _node_removed(Node *p_node);
- static void _bind_methods();
void _notification(int p_what);
public:
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index f917c988ea..0c91e8dea3 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -907,6 +907,7 @@ void NavMap::sync() {
int _new_pm_edge_merge_count = pm_edge_merge_count;
int _new_pm_edge_connection_count = pm_edge_connection_count;
int _new_pm_edge_free_count = pm_edge_free_count;
+ int _new_pm_obstacle_count = obstacles.size();
// Check if we need to update the links.
if (regenerate_polygons) {
@@ -936,8 +937,9 @@ void NavMap::sync() {
_new_pm_edge_free_count = 0;
// Remove regions connections.
+ region_external_connections.clear();
for (NavRegion *region : regions) {
- region->get_connections().clear();
+ region_external_connections[region] = LocalVector<gd::Edge::Connection>();
}
// Resize the polygon count.
@@ -1071,7 +1073,7 @@ void NavMap::sync() {
free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection);
// Add the connection to the region_connection map.
- ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection);
+ region_external_connections[(NavRegion *)free_edge.polygon->owner].push_back(new_connection);
_new_pm_edge_connection_count += 1;
}
}
@@ -1219,6 +1221,7 @@ void NavMap::sync() {
pm_edge_merge_count = _new_pm_edge_merge_count;
pm_edge_connection_count = _new_pm_edge_connection_count;
pm_edge_free_count = _new_pm_edge_free_count;
+ pm_obstacle_count = _new_pm_obstacle_count;
}
void NavMap::_update_rvo_obstacles_tree_2d() {
@@ -1426,6 +1429,40 @@ void NavMap::_update_merge_rasterizer_cell_dimensions() {
merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
}
+int NavMap::get_region_connections_count(NavRegion *p_region) const {
+ ERR_FAIL_NULL_V(p_region, 0);
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ return found_connections->value.size();
+ }
+ return 0;
+}
+
+Vector3 NavMap::get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const {
+ ERR_FAIL_NULL_V(p_region, Vector3());
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3());
+ return found_connections->value[p_connection_id].pathway_start;
+ }
+
+ return Vector3();
+}
+
+Vector3 NavMap::get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const {
+ ERR_FAIL_NULL_V(p_region, Vector3());
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3());
+ return found_connections->value[p_connection_id].pathway_end;
+ }
+
+ return Vector3();
+}
+
NavMap::NavMap() {
avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads");
avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads");
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index d6215ea57f..82e8854b7a 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -123,6 +123,9 @@ class NavMap : public NavRid {
int pm_edge_merge_count = 0;
int pm_edge_connection_count = 0;
int pm_edge_free_count = 0;
+ int pm_obstacle_count = 0;
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>> region_external_connections;
public:
NavMap();
@@ -216,6 +219,11 @@ public:
int get_pm_edge_merge_count() const { return pm_edge_merge_count; }
int get_pm_edge_connection_count() const { return pm_edge_connection_count; }
int get_pm_edge_free_count() const { return pm_edge_free_count; }
+ int get_pm_obstacle_count() const { return pm_obstacle_count; }
+
+ int get_region_connections_count(NavRegion *p_region) const;
+ Vector3 get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const;
+ Vector3 get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const;
private:
void compute_single_step(uint32_t index, NavAgent **agent);
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index f30855d697..85510bd416 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -44,8 +44,6 @@ void NavRegion::set_map(NavMap *p_map) {
map = p_map;
polygons_dirty = true;
- connections.clear();
-
if (map) {
map->add_region(this);
}
@@ -105,25 +103,6 @@ void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
polygons_dirty = true;
}
-int NavRegion::get_connections_count() const {
- if (!map) {
- return 0;
- }
- return connections.size();
-}
-
-Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const {
- ERR_FAIL_NULL_V(map, Vector3());
- ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
- return connections[p_connection_id].pathway_start;
-}
-
-Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const {
- ERR_FAIL_NULL_V(map, Vector3());
- ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
- return connections[p_connection_id].pathway_end;
-}
-
Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
if (!get_enabled()) {
return Vector3();
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index ebc082bd2f..662a32c47a 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -40,7 +40,6 @@
class NavRegion : public NavBase {
NavMap *map = nullptr;
Transform3D transform;
- Vector<gd::Edge::Connection> connections;
bool enabled = true;
bool use_edge_connections = true;
@@ -85,13 +84,6 @@ public:
void set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh);
- Vector<gd::Edge::Connection> &get_connections() {
- return connections;
- }
- int get_connections_count() const;
- Vector3 get_connection_pathway_start(int p_connection_id) const;
- Vector3 get_connection_pathway_end(int p_connection_id) const;
-
LocalVector<gd::Polygon> const &get_polygons() const {
return polygons;
}
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index 309cbe0d72..ed5810da3c 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -176,7 +176,7 @@
<param index="0" name="refresh_rate" type="float" />
<description>
Informs the user the HMD refresh rate has changed.
- [b]Node:[/b] Only emitted if XR runtime supports the refresh rate extension.
+ [b]Note:[/b] Only emitted if XR runtime supports the refresh rate extension.
</description>
</signal>
<signal name="session_begun">
diff --git a/modules/openxr/editor/openxr_select_runtime.cpp b/modules/openxr/editor/openxr_select_runtime.cpp
index 026797c6e0..4d95b079e2 100644
--- a/modules/openxr/editor/openxr_select_runtime.cpp
+++ b/modules/openxr/editor/openxr_select_runtime.cpp
@@ -35,9 +35,6 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
-void OpenXRSelectRuntime::_bind_methods() {
-}
-
void OpenXRSelectRuntime::_update_items() {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
OS *os = OS::get_singleton();
diff --git a/modules/openxr/editor/openxr_select_runtime.h b/modules/openxr/editor/openxr_select_runtime.h
index 60b5137f67..9a3487439c 100644
--- a/modules/openxr/editor/openxr_select_runtime.h
+++ b/modules/openxr/editor/openxr_select_runtime.h
@@ -40,7 +40,6 @@ public:
OpenXRSelectRuntime();
protected:
- static void _bind_methods();
void _notification(int p_notification);
private:
diff --git a/modules/openxr/extensions/openxr_mxink_extension.cpp b/modules/openxr/extensions/openxr_mxink_extension.cpp
new file mode 100644
index 0000000000..fe48583c27
--- /dev/null
+++ b/modules/openxr/extensions/openxr_mxink_extension.cpp
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* openxr_mxink_extension.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 "openxr_mxink_extension.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+
+// Not in base XR libs needs def
+#define XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME "XR_LOGITECH_mx_ink_stylus_interaction"
+
+HashMap<String, bool *> OpenXRMxInkExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+bool OpenXRMxInkExtension::is_available() {
+ return available;
+}
+
+void OpenXRMxInkExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Logitech MX Ink Stylus
+ metadata->register_interaction_profile("Logitech MX Ink Stylus", "/interaction_profiles/logitech/mx_ink_stylus_logitech", XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/left", "/user/hand/left/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/left", "/user/hand/left/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/left", "/user/hand/left/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/left", "/user/hand/left/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/right", "/user/hand/right/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/right", "/user/hand/right/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/right", "/user/hand/right/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/right", "/user/hand/right/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+}
diff --git a/modules/openxr/extensions/openxr_mxink_extension.h b/modules/openxr/extensions/openxr_mxink_extension.h
new file mode 100644
index 0000000000..fe0cf866aa
--- /dev/null
+++ b/modules/openxr/extensions/openxr_mxink_extension.h
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* openxr_mxink_extension.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 OPENXR_MXINK_EXTENSION_H
+#define OPENXR_MXINK_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRMxInkExtension : public OpenXRExtensionWrapper {
+public:
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+ virtual void on_register_metadata() override;
+
+private:
+ bool available = false;
+};
+
+#endif // OPENXR_MXINK_EXTENSION_H
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
index d92084a220..de4a9e4b8e 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
@@ -56,11 +56,6 @@
// feature off.
// See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt
-// On OpenGLES this is not defined in our standard headers..
-#ifndef GL_FRAMEBUFFER_SRGB
-#define GL_FRAMEBUFFER_SRGB 0x8DB9
-#endif
-
HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
@@ -196,23 +191,6 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d
p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24);
}
-void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) {
- if (srgb_ext_is_available) {
- hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB);
- if (hw_linear_to_srgb_is_enabled) {
- // Disable this.
- glDisable(GL_FRAMEBUFFER_SRGB);
- }
- }
-}
-
-void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) {
- if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) {
- // Re-enable this.
- glEnable(GL_FRAMEBUFFER_SRGB);
- }
-}
-
bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) {
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
ERR_FAIL_NULL_V(texture_storage, false);
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h
index a3052d3f53..8da3ca48f4 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h
@@ -49,9 +49,6 @@ public:
virtual void on_instance_created(const XrInstance p_instance) override;
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override;
- virtual void on_pre_draw_viewport(RID p_render_target) override;
- virtual void on_post_draw_viewport(RID p_render_target) override;
-
virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override;
virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override;
virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override;
@@ -76,9 +73,6 @@ private:
Vector<RID> texture_rids;
};
- bool srgb_ext_is_available = true;
- bool hw_linear_to_srgb_is_enabled = false;
-
bool check_graphics_api_support(XrVersion p_desired_version);
#ifdef ANDROID_ENABLED
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 98e5484157..c6fd20dac7 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -525,7 +525,7 @@ bool OpenXRAPI::create_instance() {
1, // applicationVersion, we don't currently have this
"Godot Game Engine", // engineName
VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc.
- XR_CURRENT_API_VERSION // apiVersion
+ XR_API_VERSION_1_0 // apiVersion
};
void *next_pointer = nullptr;
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index cce9c09361..73ac529537 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -36,7 +36,8 @@
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_hand_interaction_extension.h"
-#include "thirdparty/openxr/include/openxr/openxr.h"
+
+#include <openxr/openxr.h>
void OpenXRInterface::_bind_methods() {
// lifecycle signals
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 85514737f2..64381ae1c7 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -57,6 +57,7 @@
#include "extensions/openxr_local_floor_extension.h"
#include "extensions/openxr_meta_controller_extension.h"
#include "extensions/openxr_ml2_controller_extension.h"
+#include "extensions/openxr_mxink_extension.h"
#include "extensions/openxr_palm_pose_extension.h"
#include "extensions/openxr_pico_controller_extension.h"
#include "extensions/openxr_wmr_controller_extension.h"
@@ -126,6 +127,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRMxInkExtension));
// register gated extensions
if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 68a5d499d4..4112b81622 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -142,7 +142,14 @@ if env["builtin_harfbuzz"]:
env_harfbuzz.Append(CCFLAGS=["-DHAVE_ICU"])
if env["builtin_icu4c"]:
env_harfbuzz.Prepend(CPPPATH=["#thirdparty/icu4c/common/", "#thirdparty/icu4c/i18n/"])
- env_harfbuzz.Append(CCFLAGS=["-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DHAVE_ICU_BUILTIN"])
+ env_harfbuzz.Append(
+ CCFLAGS=[
+ "-DU_STATIC_IMPLEMENTATION",
+ "-DU_HAVE_LIB_SUFFIX=1",
+ "-DU_LIB_SUFFIX_C_NAME=_godot",
+ "-DHAVE_ICU_BUILTIN",
+ ]
+ )
if freetype_enabled:
env_harfbuzz.Append(
@@ -499,6 +506,7 @@ if env["builtin_icu4c"]:
)
env_text_server_adv.Append(
CXXFLAGS=[
+ "-DU_STATIC_IMPLEMENTATION",
"-DU_HAVE_LIB_SUFFIX=1",
"-DU_LIB_SUFFIX_C_NAME=_godot",
"-DICU_DATA_NAME=" + icu_data_name,
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index d0d13fec3f..effed1e772 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -415,6 +415,7 @@ if env["platform"] == "android" or env["platform"] == "linuxbsd":
env_harfbuzz.Append(
CCFLAGS=[
+ "-DU_STATIC_IMPLEMENTATION",
"-DU_HAVE_LIB_SUFFIX=1",
"-DU_LIB_SUFFIX_C_NAME=_godot",
"-DHAVE_ICU_BUILTIN",
@@ -746,6 +747,7 @@ env_icu.Append(
)
env.Append(
CXXFLAGS=[
+ "-DU_STATIC_IMPLEMENTATION",
"-DU_HAVE_LIB_SUFFIX=1",
"-DU_LIB_SUFFIX_C_NAME=_godot",
"-DICU_DATA_NAME=" + icu_data_name,
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 499ddb703b..d0c22e9e4d 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -51,7 +51,7 @@ using namespace godot;
#include "core/error/error_macros.h"
#include "core/object/worker_thread_pool.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
@@ -3528,6 +3528,37 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
return chars;
}
+PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_font_rid) const {
+ FontAdvanced *fd = _get_font_data(p_font_rid);
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
+
+ MutexLock lock(fd->mutex);
+ if (fd->cache.is_empty()) {
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ }
+ FontForSizeAdvanced *at_size = fd->cache.begin()->value;
+
+ PackedInt32Array glyphs;
+#ifdef MODULE_FREETYPE_ENABLED
+ if (at_size && at_size->face) {
+ FT_UInt gindex;
+ FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ while (gindex != 0) {
+ glyphs.push_back(gindex);
+ charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ }
+ return glyphs;
+ }
+#endif
+ if (at_size) {
+ const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ glyphs.push_back(E.key);
+ }
+ }
+ return glyphs;
+}
+
void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 92bdb93bcf..fdebb8e4cd 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -871,6 +871,7 @@ public:
MODBIND2RC(bool, font_has_char, const RID &, int64_t);
MODBIND1RC(String, font_get_supported_chars, const RID &);
+ MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &);
MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t);
MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index b45c004011..a7ddfc719e 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -52,7 +52,7 @@ using namespace godot;
#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
@@ -2477,6 +2477,37 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
return chars;
}
+PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_font_rid) const {
+ FontFallback *fd = _get_font_data(p_font_rid);
+ ERR_FAIL_NULL_V(fd, PackedInt32Array());
+
+ MutexLock lock(fd->mutex);
+ if (fd->cache.is_empty()) {
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ }
+ FontForSizeFallback *at_size = fd->cache.begin()->value;
+
+ PackedInt32Array glyphs;
+#ifdef MODULE_FREETYPE_ENABLED
+ if (at_size && at_size->face) {
+ FT_UInt gindex;
+ FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ while (gindex != 0) {
+ glyphs.push_back(gindex);
+ charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ }
+ return glyphs;
+ }
+#endif
+ if (at_size) {
+ const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ glyphs.push_back(E.key);
+ }
+ }
+ return glyphs;
+}
+
void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 2235247b31..1b76c6fa0f 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -739,6 +739,7 @@ public:
MODBIND2RC(bool, font_has_char, const RID &, int64_t);
MODBIND1RC(String, font_get_supported_chars, const RID &);
+ MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &);
MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t);
MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t);
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 329e5bd532..03a530909b 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -59,8 +59,10 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int
}
Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_options) {
+ ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE);
+
_clear();
String host;
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index d0232a7a2c..0a9a4053e3 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -99,6 +99,8 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) {
p_tcp->poll();
StreamPeerTCP::Status status = p_tcp->get_status();
if (status == StreamPeerTCP::STATUS_CONNECTED) {
+ // On Windows, setting TCP_NODELAY may fail if the socket is still connecting.
+ p_tcp->set_no_delay(true);
ip_candidates.clear();
return;
} else if (status == StreamPeerTCP::STATUS_CONNECTING) {
@@ -112,7 +114,6 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) {
while (ip_candidates.size()) {
Error err = p_tcp->connect_to_host(ip_candidates.pop_front(), port);
if (err == OK) {
- p_tcp->set_no_delay(true);
return;
} else {
p_tcp->disconnect_from_host();
@@ -124,8 +125,8 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) {
/// Server functions
///
Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) {
- ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_stream.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE);
_clear();
@@ -311,7 +312,7 @@ void WSLPeer::_do_client_handshake() {
ERR_FAIL_COND(tcp.is_null());
// Try to connect to candidates.
- if (resolver.has_more_candidates()) {
+ if (resolver.has_more_candidates() || tcp->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
resolver.try_next_candidate(tcp);
if (resolver.has_more_candidates()) {
return; // Still pending.
@@ -472,9 +473,9 @@ bool WSLPeer::_verify_server_response() {
}
Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) {
- ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE);
_clear();