summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/math/geometry_2d.h2
-rw-r--r--doc/classes/@GlobalScope.xml3
-rw-r--r--doc/classes/Geometry2D.xml1
-rw-r--r--editor/doc_tools.cpp19
-rw-r--r--editor/gui/scene_tree_editor.cpp21
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp366
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h20
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp148
-rw-r--r--editor/plugins/node_3d_editor_plugin.h3
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp15
-rw-r--r--editor/scene_tree_dock.cpp118
-rw-r--r--editor/scene_tree_dock.h3
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml1
-rw-r--r--modules/mono/doc_classes/GodotSharp.xml20
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs6
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp11
-rw-r--r--modules/mono/mono_gd/gd_mono.h8
-rw-r--r--modules/mono/register_types.cpp3
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt24
-rw-r--r--platform/windows/windows_terminal_logger.cpp28
-rw-r--r--scene/2d/tile_map_layer.cpp1
-rw-r--r--scene/3d/skeleton_3d.cpp11
-rw-r--r--scene/resources/shader.cpp2
-rw-r--r--servers/rendering/rendering_light_culler.h4
25 files changed, 536 insertions, 304 deletions
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 1502b2807c..83ebdc5a84 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -350,6 +350,8 @@ public:
return triangles;
}
+ // Assumes cartesian coordinate system with +x to the right, +y up.
+ // If using screen coordinates (+x to the right, +y down) the result will need to be flipped.
static bool is_polygon_clockwise(const Vector<Vector2> &p_polygon) {
int c = p_polygon.size();
if (c < 3) {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 4b32acaaa0..bcab80ea94 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1571,9 +1571,6 @@
<member name="Geometry3D" type="Geometry3D" setter="" getter="">
The [Geometry3D] singleton.
</member>
- <member name="GodotSharp" type="GodotSharp" setter="" getter="">
- The [GodotSharp] singleton.
- </member>
<member name="IP" type="IP" setter="" getter="">
The [IP] singleton.
</member>
diff --git a/doc/classes/Geometry2D.xml b/doc/classes/Geometry2D.xml
index dfcf299fe6..f21696d02c 100644
--- a/doc/classes/Geometry2D.xml
+++ b/doc/classes/Geometry2D.xml
@@ -116,6 +116,7 @@
<param index="0" name="polygon" type="PackedVector2Array" />
<description>
Returns [code]true[/code] if [param polygon]'s vertices are ordered in clockwise order, otherwise returns [code]false[/code].
+ [b]Note:[/b] Assumes a Cartesian coordinate system where [code]+x[/code] is right and [code]+y[/code] is up. If using screen coordinates ([code]+y[/code] is down), the result will need to be flipped (i.e. a [code]true[/code] result will indicate counter-clockwise).
</description>
</method>
<method name="line_intersects_line">
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index b58e82b7e7..331dacf6ad 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -344,25 +344,6 @@ void DocTools::merge_from(const DocTools &p_data) {
merge_theme_properties(c.theme_properties, cf.theme_properties);
merge_operators(c.operators, cf.operators);
-
-#ifndef MODULE_MONO_ENABLED
- // The Mono module defines some properties that we want to keep when
- // re-generating docs with a non-Mono build, to prevent pointless diffs
- // (and loss of descriptions) depending on the config of the doc writer.
- // We use a horrible hack to force keeping the relevant properties,
- // hardcoded below. At least it's an ad hoc hack... ¯\_(ツ)_/¯
- // Don't show this to your kids.
- if (c.name == "@GlobalScope") {
- // Retrieve GodotSharp singleton.
- for (int j = 0; j < cf.properties.size(); j++) {
- if (cf.properties[j].name == "GodotSharp") {
- c.properties.push_back(cf.properties[j]);
- c.properties.sort();
- break;
- }
- }
- }
-#endif
}
}
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index a06dd310ea..fa0dad41dc 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1368,22 +1368,35 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d
}
bool scene_drop = true;
+ bool audio_drop = true;
for (int i = 0; i < files.size(); i++) {
String ftype = EditorFileSystem::get_singleton()->get_file_type(files[i]);
if (ftype != "PackedScene") {
scene_drop = false;
- break;
+ }
+ if (audio_drop && !ClassDB::is_parent_class(ftype, "AudioStream")) {
+ audio_drop = false;
}
}
if (scene_drop) {
tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
- } else {
+ return true;
+ }
+
+ if (audio_drop) {
if (files.size() > 1) {
- return false;
+ tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
+ } else {
+ tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
}
- tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM);
+ return true;
+ }
+
+ if (files.size() > 1) {
+ return false;
}
+ tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM);
return true;
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index a44430ca7f..2d47887027 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -46,10 +46,12 @@
#include "editor/scene_tree_dock.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
+#include "scene/2d/audio_stream_player_2d.h"
#include "scene/2d/polygon_2d.h"
#include "scene/2d/skeleton_2d.h"
#include "scene/2d/sprite_2d.h"
#include "scene/2d/touch_screen_button.h"
+#include "scene/gui/base_button.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/separator.h"
@@ -966,7 +968,7 @@ void CanvasItemEditor::_add_node_pressed(int p_result) {
}
}
-void CanvasItemEditor::_node_created(Node *p_node) {
+void CanvasItemEditor::_adjust_new_node_position(Node *p_node) {
if (node_create_position == Point2()) {
return;
}
@@ -5161,7 +5163,7 @@ CanvasItemEditor::CanvasItemEditor() {
editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed));
- SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created));
+ SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position));
SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));
// Add some margin to the sides for better esthetics.
@@ -5697,15 +5699,15 @@ CanvasItemEditorPlugin::~CanvasItemEditorPlugin() {
}
void CanvasItemEditorViewport::_on_mouse_exit() {
- if (!selector->is_visible()) {
+ if (!texture_node_type_selector->is_visible()) {
_remove_preview();
}
}
-void CanvasItemEditorViewport::_on_select_type(Object *selected) {
+void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) {
CheckBox *check = Object::cast_to<CheckBox>(selected);
String type = check->get_text();
- selector->set_title(vformat(TTR("Add %s"), type));
+ texture_node_type_selector->set_title(vformat(TTR("Add %s"), type));
label->set_text(vformat(TTR("Adding %s..."), type));
}
@@ -5717,7 +5719,7 @@ void CanvasItemEditorViewport::_on_change_type_confirmed() {
CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button());
default_texture_node_type = check->get_text();
_perform_drop_data();
- selector->hide();
+ texture_node_type_selector->hide();
}
void CanvasItemEditorViewport::_on_change_type_closed() {
@@ -5728,35 +5730,35 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
bool add_preview = false;
for (int i = 0; i < files.size(); i++) {
Ref<Resource> res = ResourceLoader::load(files[i]);
- ERR_FAIL_COND(res.is_null());
- Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res));
- Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
- if (texture != nullptr || scene != nullptr) {
- String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +
- "\n" + TTR("Hold Shift when dropping to add as child of selected node.") +
- "\n" + TTR("Hold Alt when dropping to add as child of root node.");
-
- if (texture != nullptr) {
- Sprite2D *sprite = memnew(Sprite2D);
- sprite->set_texture(texture);
- sprite->set_modulate(Color(1, 1, 1, 0.7f));
- preview_node->add_child(sprite);
- label->show();
- label_desc->show();
- desc += "\n" + TTR("Hold Alt + Shift when dropping to add as a different node type.");
- label_desc->set_text(desc);
- } else {
- if (scene.is_valid()) {
- Node *instance = scene->instantiate();
- if (instance) {
- preview_node->add_child(instance);
- label_desc->show();
- label_desc->set_text(desc);
- }
- }
+ ERR_CONTINUE(res.is_null());
+
+ Ref<Texture2D> texture = res;
+ if (texture.is_valid()) {
+ Sprite2D *sprite = memnew(Sprite2D);
+ sprite->set_texture(texture);
+ sprite->set_modulate(Color(1, 1, 1, 0.7f));
+ preview_node->add_child(sprite);
+ add_preview = true;
+ }
+
+ Ref<PackedScene> scene = res;
+ if (scene.is_valid()) {
+ Node *instance = scene->instantiate();
+ if (instance) {
+ preview_node->add_child(instance);
}
add_preview = true;
}
+
+ Ref<AudioStream> audio = res;
+ if (audio.is_valid()) {
+ Sprite2D *sprite = memnew(Sprite2D);
+ sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D")));
+ sprite->set_modulate(Color(1, 1, 1, 0.7f));
+ sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE);
+ preview_node->add_child(sprite);
+ add_preview = true;
+ }
}
if (add_preview) {
@@ -5797,44 +5799,46 @@ bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_targe
return false;
}
-void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) {
+void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) {
// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
- String name = path.get_file().get_basename();
- child->set_name(Node::adjust_name_casing(name));
+ const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+ if (!node_name.is_empty()) {
+ p_child->set_name(node_name);
+ }
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- Ref<Texture2D> texture = ResourceCache::get_ref(path);
+ Ref<Texture2D> texture = ResourceCache::get_ref(p_path);
- if (parent) {
- undo_redo->add_do_method(parent, "add_child", child, true);
- undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
- undo_redo->add_do_reference(child);
- undo_redo->add_undo_method(parent, "remove_child", child);
+ if (p_parent) {
+ undo_redo->add_do_method(p_parent, "add_child", p_child, true);
+ undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_reference(p_child);
+ undo_redo->add_undo_method(p_parent, "remove_child", p_child);
} else { // If no parent is selected, set as root node of the scene.
- undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);
- undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
- undo_redo->add_do_reference(child);
+ undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child);
+ undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_reference(p_child);
undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
}
- if (parent) {
- String new_name = parent->validate_child_name(child);
+ if (p_parent) {
+ String new_name = p_parent->validate_child_name(p_child);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
- undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name);
- undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
+ undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_child->get_class(), new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
}
- if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) {
- undo_redo->add_do_property(child, "texture_normal", texture);
+ if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) {
+ undo_redo->add_do_property(p_child, "texture_normal", texture);
} else {
- undo_redo->add_do_property(child, "texture", texture);
+ undo_redo->add_do_property(p_child, "texture", texture);
}
// make visible for certain node type
- if (Object::cast_to<Control>(child)) {
+ if (Object::cast_to<Control>(p_child)) {
Size2 texture_size = texture->get_size();
- undo_redo->add_do_property(child, "size", texture_size);
- } else if (Object::cast_to<Polygon2D>(child)) {
+ undo_redo->add_do_property(p_child, "size", texture_size);
+ } else if (Object::cast_to<Polygon2D>(p_child)) {
Size2 texture_size = texture->get_size();
Vector<Vector2> list = {
Vector2(0, 0),
@@ -5842,7 +5846,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
Vector2(texture_size.width, texture_size.height),
Vector2(0, texture_size.height)
};
- undo_redo->add_do_property(child, "polygon", list);
+ undo_redo->add_do_property(p_child, "polygon", list);
}
// Compute the global position
@@ -5850,21 +5854,68 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
Point2 target_position = xform.affine_inverse().xform(p_point);
// Adjust position for Control and TouchScreenButton
- if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) {
+ if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) {
target_position -= texture->get_size() / 2;
}
- // there's nothing to be used as source position so snapping will work as absolute if enabled
+ // There's nothing to be used as source position, so snapping will work as absolute if enabled.
+ target_position = canvas_item_editor->snap_point(target_position);
+
+ CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
+ Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;
+
+ undo_redo->add_do_method(p_child, "set_position", local_target_pos);
+}
+
+void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {
+ AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D);
+ child->set_stream(ResourceCache::get_ref(p_path));
+
+ // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+ const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+ if (!node_name.is_empty()) {
+ child->set_name(node_name);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+ if (p_parent) {
+ undo_redo->add_do_method(p_parent, "add_child", child, true);
+ undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_reference(child);
+ undo_redo->add_undo_method(p_parent, "remove_child", child);
+ } else { // If no parent is selected, set as root node of the scene.
+ undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);
+ undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_reference(child);
+ undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
+ }
+
+ if (p_parent) {
+ String new_name = p_parent->validate_child_name(child);
+ EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+ undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), child->get_class(), new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
+ }
+
+ // Compute the global position
+ Transform2D xform = canvas_item_editor->get_canvas_transform();
+ Point2 target_position = xform.affine_inverse().xform(p_point);
+
+ // There's nothing to be used as source position, so snapping will work as absolute if enabled.
target_position = canvas_item_editor->snap_point(target_position);
- CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
+ CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;
undo_redo->add_do_method(child, "set_position", local_target_pos);
+
+ EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
+ undo_redo->add_do_method(editor_selection, "add_node", child);
}
-bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
- Ref<PackedScene> sdata = ResourceLoader::load(path);
+bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {
+ Ref<PackedScene> sdata = ResourceLoader::load(p_path);
if (!sdata.is_valid()) { // invalid scene
return false;
}
@@ -5883,27 +5934,27 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons
}
}
- instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path));
+ instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
- undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+ undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);
undo_redo->add_do_reference(instantiated_scene);
- undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+ undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
- String new_name = parent->validate_child_name(instantiated_scene);
+ String new_name = p_parent->validate_child_name(instantiated_scene);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
- undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name);
- undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name));
+ undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name));
CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);
if (instance_ci) {
Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point);
target_pos = canvas_item_editor->snap_point(target_pos);
- CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
+ CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
if (parent_ci) {
target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);
}
@@ -5922,12 +5973,8 @@ void CanvasItemEditorViewport::_perform_drop_data() {
_remove_preview();
if (!target_node) {
- // Without root dropping multiple files is not allowed
- if (selected_files.size() > 1) {
- accept->set_text(TTR("Cannot instantiate multiple nodes without root."));
- accept->popup_centered();
- return;
- }
+ // Should already be handled by `can_drop_data`.
+ ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root.");
const String &path = selected_files[0];
Ref<Resource> res = ResourceLoader::load(path);
@@ -5973,9 +6020,14 @@ void CanvasItemEditorViewport::_perform_drop_data() {
Ref<Texture2D> texture = res;
if (texture.is_valid()) {
Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type));
- _create_nodes(target_node, child, path, drop_pos);
+ _create_texture_node(target_node, child, path, drop_pos);
undo_redo->add_do_method(editor_selection, "add_node", child);
}
+
+ Ref<AudioStream> audio = res;
+ if (audio.is_valid()) {
+ _create_audio_node(target_node, path, drop_pos);
+ }
}
undo_redo->commit_action();
@@ -5994,71 +6046,108 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
}
Vector<String> files = d["files"];
- bool can_instantiate = false;
- bool is_cyclical_dep = false;
- String error_file;
- // Check if at least one of the dragged files is a texture or scene.
- for (int i = 0; i < files.size(); i++) {
- bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene");
- bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture2D");
+ const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
+ if (!edited_scene && files.size() > 1) {
+ canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root.");
+ canvas_item_editor->update_viewport();
+ return false;
+ }
+
+ enum {
+ SCENE = 1 << 0,
+ TEXTURE = 1 << 1,
+ AUDIO = 1 << 2,
+ };
+ int instantiate_type = 0;
- if (is_scene || is_texture) {
- Ref<Resource> res = ResourceLoader::load(files[i]);
- if (res.is_null()) {
+ for (const String &path : files) {
+ const String &res_type = ResourceLoader::get_resource_type(path);
+ String error_message;
+
+ if (ClassDB::is_parent_class(res_type, "PackedScene")) {
+ Ref<PackedScene> scn = ResourceLoader::load(path);
+ ERR_CONTINUE(scn.is_null());
+
+ Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+ if (!instantiated_scene) {
continue;
}
- Ref<PackedScene> scn = res;
- if (scn.is_valid()) {
- Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
- if (!instantiated_scene) {
- continue;
- }
-
- Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
- if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
- memdelete(instantiated_scene);
- can_instantiate = false;
- is_cyclical_dep = true;
- error_file = files[i].get_file();
- break;
- }
- memdelete(instantiated_scene);
+ if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
+ error_message = vformat(TTR("Circular dependency found at %s."), path.get_file());
}
- can_instantiate = true;
+ memdelete(instantiated_scene);
+ instantiate_type |= SCENE;
+ }
+ if (ClassDB::is_parent_class(res_type, "Texture2D")) {
+ instantiate_type |= TEXTURE;
+ }
+ if (ClassDB::is_parent_class(res_type, "AudioStream")) {
+ instantiate_type |= AUDIO;
+ }
+
+ if (!error_message.is_empty()) {
+ // TRANSLATORS: The placeholder is the error message.
+ canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message);
+ canvas_item_editor->update_viewport();
+ return false;
}
}
- if (is_cyclical_dep) {
- canvas_item_editor->message = vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file));
- canvas_item_editor->update_viewport();
+ if (instantiate_type == 0) {
return false;
}
- if (can_instantiate) {
- if (!preview_node->get_parent()) { // create preview only once
- _create_preview(files);
- }
- ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);
- Transform2D trans = canvas_item_editor->get_canvas_transform();
- preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
+ if (!preview_node->get_parent()) { // create preview only once
+ _create_preview(files);
+ }
+ ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);
+
+ const Transform2D trans = canvas_item_editor->get_canvas_transform();
+ preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
+
+ if (!edited_scene && instantiate_type & SCENE) {
String scene_file_path = preview_node->get_child(0)->get_scene_file_path();
- if (scene_file_path.is_empty() || preview_node->get_tree()->get_edited_scene_root()) {
- double snap = EDITOR_GET("interface/inspector/default_float_step");
- int snap_step_decimals = Math::range_step_decimals(snap);
+ // TRANSLATORS: The placeholder is the file path of the scene being instantiated.
+ canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path);
+ } else {
+ double snap = EDITOR_GET("interface/inspector/default_float_step");
+ int snap_step_decimals = Math::range_step_decimals(snap);
#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals)))
- Vector2 preview_node_pos = preview_node->get_global_position();
- canvas_item_editor->message = TTR("Instantiating:") + " (" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";
- label->set_text(vformat(TTR("Adding %s..."), default_texture_node_type));
- } else {
- canvas_item_editor->message = TTR("Creating inherited scene from: ") + scene_file_path;
+ Vector2 preview_node_pos = preview_node->get_global_position();
+ canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";
+ }
+ canvas_item_editor->update_viewport();
+
+ if (instantiate_type & TEXTURE && instantiate_type & AUDIO) {
+ // TRANSLATORS: The placeholders are the types of nodes being instantiated.
+ label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D"));
+ } else {
+ String node_type;
+ if (instantiate_type & TEXTURE) {
+ node_type = default_texture_node_type;
+ } else if (instantiate_type & AUDIO) {
+ node_type = "AudioStreamPlayer2D";
+ }
+ if (!node_type.is_empty()) {
+ // TRANSLATORS: The placeholder is the type of node being instantiated.
+ label->set_text(vformat(TTR("Adding %s..."), node_type));
}
+ }
+ label->set_visible(instantiate_type & ~SCENE);
- canvas_item_editor->update_viewport();
+ String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +
+ "\n" + TTR("Hold Shift when dropping to add as child of selected node.") +
+ "\n" + TTR("Hold Alt when dropping to add as child of root node.");
+ if (instantiate_type & TEXTURE) {
+ desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type.");
}
- return can_instantiate;
+ label_desc->set_text(desc);
+ label_desc->show();
+
+ return true;
}
-void CanvasItemEditorViewport::_show_resource_type_selector() {
+void CanvasItemEditorViewport::_show_texture_node_type_selector() {
_remove_preview();
List<BaseButton *> btn_list;
button_group->get_buttons(&btn_list);
@@ -6067,18 +6156,17 @@ void CanvasItemEditorViewport::_show_resource_type_selector() {
CheckBox *check = Object::cast_to<CheckBox>(btn);
check->set_pressed(check->get_text() == default_texture_node_type);
}
- selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));
- selector->popup_centered();
+ texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));
+ texture_node_type_selector->popup_centered();
}
-bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
+bool CanvasItemEditorViewport::_is_any_texture_selected() const {
for (int i = 0; i < selected_files.size(); ++i) {
- if (ResourceLoader::load(selected_files[i])->get_class() != "PackedScene") {
- return false;
+ if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) {
+ return true;
}
}
-
- return true;
+ return false;
}
void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
@@ -6115,8 +6203,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p
drop_pos = p_point;
- if (is_alt && is_shift && !_only_packed_scenes_selected()) {
- _show_resource_type_selector();
+ if (is_alt && is_shift && _is_any_texture_selected()) {
+ _show_texture_node_type_selector();
} else {
_perform_drop_data();
}
@@ -6176,19 +6264,19 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it
accept = memnew(AcceptDialog);
EditorNode::get_singleton()->get_gui_base()->add_child(accept);
- selector = memnew(AcceptDialog);
- EditorNode::get_singleton()->get_gui_base()->add_child(selector);
- selector->set_title(TTR("Change Default Type"));
- selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));
- selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));
+ texture_node_type_selector = memnew(AcceptDialog);
+ EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector);
+ texture_node_type_selector->set_title(TTR("Change Default Type"));
+ texture_node_type_selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));
+ texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));
VBoxContainer *vbc = memnew(VBoxContainer);
- selector->add_child(vbc);
+ texture_node_type_selector->add_child(vbc);
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE);
- btn_group = memnew(VBoxContainer);
+ VBoxContainer *btn_group = memnew(VBoxContainer);
vbc->add_child(btn_group);
btn_group->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -6197,7 +6285,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it
CheckBox *check = memnew(CheckBox);
btn_group->add_child(check);
check->set_text(texture_node_types[i]);
- check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check));
+ check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check));
check->set_button_group(button_group);
}
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 6d951a77ec..a9de5e9a0b 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -32,10 +32,11 @@
#define CANVAS_ITEM_EDITOR_PLUGIN_H
#include "editor/plugins/editor_plugin.h"
-#include "scene/gui/base_button.h"
#include "scene/gui/box_container.h"
class AcceptDialog;
+class Button;
+class ButtonGroup;
class CanvasItemEditorViewport;
class ConfirmationDialog;
class EditorData;
@@ -409,7 +410,7 @@ private:
void _selection_result_pressed(int);
void _selection_menu_hide();
void _add_node_pressed(int p_result);
- void _node_created(Node *p_node);
+ void _adjust_new_node_position(Node *p_node);
void _reset_create_position();
void _update_editor_settings();
bool _is_grid_visible() const;
@@ -634,15 +635,13 @@ class CanvasItemEditorViewport : public Control {
CanvasItemEditor *canvas_item_editor = nullptr;
Control *preview_node = nullptr;
AcceptDialog *accept = nullptr;
- AcceptDialog *selector = nullptr;
- Label *selector_label = nullptr;
+ AcceptDialog *texture_node_type_selector = nullptr;
Label *label = nullptr;
Label *label_desc = nullptr;
- VBoxContainer *btn_group = nullptr;
Ref<ButtonGroup> button_group;
void _on_mouse_exit();
- void _on_select_type(Object *selected);
+ void _on_select_texture_node_type(Object *selected);
void _on_change_type_confirmed();
void _on_change_type_closed();
@@ -650,11 +649,12 @@ class CanvasItemEditorViewport : public Control {
void _remove_preview();
bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const;
- bool _only_packed_scenes_selected() const;
- void _create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point);
- bool _create_instance(Node *parent, String &path, const Point2 &p_point);
+ bool _is_any_texture_selected() const;
+ void _create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point);
+ void _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point);
+ bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point);
void _perform_drop_data();
- void _show_resource_type_selector();
+ void _show_texture_node_type_selector();
void _update_theme();
protected:
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index ca7ea821e8..c8b229c62a 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -78,12 +78,14 @@
#include "editor/plugins/gizmos/voxel_gi_gizmo_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
#include "editor/scene_tree_dock.h"
+#include "scene/3d/audio_stream_player_3d.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/decal.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
+#include "scene/3d/sprite_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/3d/world_environment.h"
#include "scene/gui/center_container.h"
@@ -4192,27 +4194,37 @@ Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const {
void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const {
bool add_preview = false;
- for (int i = 0; i < files.size(); i++) {
- Ref<Resource> res = ResourceLoader::load(files[i]);
+ for (const String &path : files) {
+ Ref<Resource> res = ResourceLoader::load(path);
ERR_CONTINUE(res.is_null());
- Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
- Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
- if (mesh != nullptr || scene != nullptr) {
- if (mesh != nullptr) {
- MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
- mesh_instance->set_mesh(mesh);
- preview_node->add_child(mesh_instance);
- } else {
- if (scene.is_valid()) {
- Node *instance = scene->instantiate();
- if (instance) {
- instance = _sanitize_preview_node(instance);
- preview_node->add_child(instance);
- }
- }
+
+ Ref<PackedScene> scene = res;
+ if (scene.is_valid()) {
+ Node *instance = scene->instantiate();
+ if (instance) {
+ instance = _sanitize_preview_node(instance);
+ preview_node->add_child(instance);
}
add_preview = true;
}
+
+ Ref<Mesh> mesh = res;
+ if (mesh.is_valid()) {
+ MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
+ mesh_instance->set_mesh(mesh);
+ preview_node->add_child(mesh_instance);
+ add_preview = true;
+ }
+
+ Ref<AudioStream> audio = res;
+ if (audio.is_valid()) {
+ Sprite3D *sprite = memnew(Sprite3D);
+ sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer")));
+ sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
+ sprite->set_pixel_size(0.005);
+ preview_node->add_child(sprite);
+ add_preview = true;
+ }
}
if (add_preview) {
EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);
@@ -4346,12 +4358,12 @@ bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_sc
return false;
}
-bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
- Ref<Resource> res = ResourceLoader::load(path);
+bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {
+ Ref<Resource> res = ResourceLoader::load(p_path);
ERR_FAIL_COND_V(res.is_null(), false);
- Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
- Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
+ Ref<PackedScene> scene = res;
+ Ref<Mesh> mesh = res;
Node *instantiated_scene = nullptr;
@@ -4361,8 +4373,10 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
mesh_instance->set_mesh(mesh);
// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
- String name = path.get_file().get_basename();
- mesh_instance->set_name(Node::adjust_name_casing(name));
+ const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+ if (!node_name.is_empty()) {
+ mesh_instance->set_name(node_name);
+ }
instantiated_scene = mesh_instance;
} else {
@@ -4386,25 +4400,25 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
}
if (scene != nullptr) {
- instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path));
+ instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+ undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());
undo_redo->add_do_reference(instantiated_scene);
- undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+ undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
- String new_name = parent->validate_child_name(instantiated_scene);
+ String new_name = p_parent->validate_child_name(instantiated_scene);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
- undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), path, new_name);
- undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
+ undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);
if (node3d) {
Transform3D parent_tf;
- Node3D *parent_node3d = Object::cast_to<Node3D>(parent);
+ Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
if (parent_node3d) {
parent_tf = parent_node3d->get_global_gizmo_transform();
}
@@ -4419,6 +4433,46 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
return true;
}
+bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {
+ Ref<AudioStream> audio = ResourceLoader::load(p_path);
+ ERR_FAIL_COND_V(audio.is_null(), false);
+
+ AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D);
+ audio_player->set_stream(audio);
+
+ // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+ const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+ if (!node_name.is_empty()) {
+ audio_player->set_name(node_name);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->add_do_method(p_parent, "add_child", audio_player, true);
+ undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_reference(audio_player);
+ undo_redo->add_undo_method(p_parent, "remove_child", audio_player);
+ undo_redo->add_do_method(editor_selection, "add_node", audio_player);
+
+ const String new_name = p_parent->validate_child_name(audio_player);
+ EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+ undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
+
+ Transform3D parent_tf;
+ Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
+ if (parent_node3d) {
+ parent_tf = parent_node3d->get_global_gizmo_transform();
+ }
+
+ Transform3D new_tf = audio_player->get_transform();
+ new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position());
+ new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
+
+ undo_redo->add_do_method(audio_player, "set_transform", new_tf);
+
+ return true;
+}
+
void Node3DEditorViewport::_perform_drop_data() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (spatial_editor->get_preview_material_target().is_valid()) {
@@ -4453,11 +4507,18 @@ void Node3DEditorViewport::_perform_drop_data() {
if (res.is_null()) {
continue;
}
- Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
- Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
- if (mesh != nullptr || scene != nullptr) {
- bool success = _create_instance(target_node, path, drop_pos);
- if (!success) {
+
+ Ref<PackedScene> scene = res;
+ Ref<Mesh> mesh = res;
+ if (mesh.is_valid() || scene.is_valid()) {
+ if (!_create_instance(target_node, path, drop_pos)) {
+ error_files.push_back(path.get_file());
+ }
+ }
+
+ Ref<AudioStream> audio = res;
+ if (audio.is_valid()) {
+ if (!_create_audio_node(target_node, path, drop_pos)) {
error_files.push_back(path.get_file());
}
}
@@ -4488,12 +4549,14 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
bool is_other_valid = false;
// Check if at least one of the dragged files is a mesh, material, texture or scene.
for (int i = 0; i < files.size(); i++) {
- bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene");
- bool is_mesh = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Mesh");
- bool is_material = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Material");
- bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture");
-
- if (is_mesh || is_scene || is_material || is_texture) {
+ const String &res_type = ResourceLoader::get_resource_type(files[i]);
+ bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");
+ bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");
+ bool is_material = ClassDB::is_parent_class(res_type, "Material");
+ bool is_texture = ClassDB::is_parent_class(res_type, "Texture");
+ bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");
+
+ if (is_mesh || is_scene || is_material || is_texture || is_audio) {
Ref<Resource> res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
@@ -4502,6 +4565,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
Ref<Mesh> mesh = res;
Ref<Material> mat = res;
Ref<Texture2D> tex = res;
+ Ref<AudioStream> audio = res;
if (scn.is_valid()) {
Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
if (!instantiated_scene) {
@@ -4537,6 +4601,8 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
spatial_editor->set_preview_material(new_mat);
is_other_valid = true;
continue;
+ } else if (!is_other_valid && audio.is_valid()) {
+ is_other_valid = true;
} else {
continue;
}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index a3e1224cb8..4990b11a47 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -443,7 +443,8 @@ private:
void _reset_preview_material() const;
void _remove_preview_material();
bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const;
- bool _create_instance(Node *parent, String &path, const Point2 &p_point);
+ bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point);
+ bool _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point);
void _perform_drop_data();
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index 2d0e5214db..94cbb371af 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -144,7 +144,8 @@ void GenericTilePolygonEditor::_base_control_draw() {
}
// Draw tile-related things.
- Size2 tile_size = tile_set->get_tile_size();
+ const Size2 base_tile_size = tile_set->get_tile_size();
+ const Size2 tile_size = background_region.size;
Transform2D xform;
xform.set_origin(base_control->get_size() / 2 + panning);
@@ -170,12 +171,16 @@ void GenericTilePolygonEditor::_base_control_draw() {
// Draw grid.
if (current_snap_option == SNAP_GRID) {
- Vector2 spacing = tile_size / snap_subdivision->get_value();
+ Vector2 spacing = base_tile_size / snap_subdivision->get_value();
Vector2 offset = -tile_size / 2;
+ int w = snap_subdivision->get_value() * (tile_size / base_tile_size).x;
+ int h = snap_subdivision->get_value() * (tile_size / base_tile_size).y;
- for (int i = 1; i < snap_subdivision->get_value(); i++) {
- base_control->draw_line(Vector2(spacing.x * i, 0) + offset, Vector2(spacing.x * i, tile_size.y) + offset, Color(1, 1, 1, 0.33));
- base_control->draw_line(Vector2(0, spacing.y * i) + offset, Vector2(tile_size.x, spacing.y * i) + offset, Color(1, 1, 1, 0.33));
+ for (int y = 1; y < h; y++) {
+ for (int x = 1; x < w; x++) {
+ base_control->draw_line(Vector2(spacing.x * x, 0) + offset, Vector2(spacing.x * x, tile_size.y) + offset, Color(1, 1, 1, 0.33));
+ base_control->draw_line(Vector2(0, spacing.y * y) + offset, Vector2(tile_size.x, spacing.y * y) + offset, Color(1, 1, 1, 0.33));
+ }
}
}
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 53145c3450..66d26c0a6c 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -56,6 +56,7 @@
#include "editor/shader_create_dialog.h"
#include "editor/themes/editor_scale.h"
#include "scene/animation/animation_tree.h"
+#include "scene/audio/audio_stream_player.h"
#include "scene/gui/check_box.h"
#include "scene/property_utils.h"
#include "scene/resources/packed_scene.h"
@@ -254,8 +255,8 @@ void SceneTreeDock::instantiate_scenes(const Vector<String> &p_files, Node *p_pa
_perform_instantiate_scenes(p_files, parent, -1);
}
-void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos) {
- ERR_FAIL_NULL(parent);
+void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos) {
+ ERR_FAIL_NULL(p_parent);
Vector<Node *> instances;
@@ -302,25 +303,25 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Instantiate Scene(s)"));
+ undo_redo->create_action_for_history(TTRN("Instantiate Scene", "Instantiate Scenes", instances.size()), editor_data->get_current_edited_scene_history_id());
+ undo_redo->add_do_method(editor_selection, "clear");
for (int i = 0; i < instances.size(); i++) {
Node *instantiated_scene = instances[i];
- undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+ undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
if (p_pos >= 0) {
- undo_redo->add_do_method(parent, "move_child", instantiated_scene, p_pos + i);
+ undo_redo->add_do_method(p_parent, "move_child", instantiated_scene, p_pos + i);
}
undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);
- undo_redo->add_do_method(editor_selection, "clear");
undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
undo_redo->add_do_reference(instantiated_scene);
- undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+ undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
- String new_name = parent->validate_child_name(instantiated_scene);
+ String new_name = p_parent->validate_child_name(instantiated_scene);
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
- undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), p_files[i], new_name);
- undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).path_join(new_name)));
+ undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_files[i], new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name)));
}
undo_redo->commit_action();
@@ -330,6 +331,75 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
}
}
+void SceneTreeDock::_perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos) {
+ ERR_FAIL_NULL(p_parent);
+
+ StringName node_type = "AudioStreamPlayer";
+ if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
+ if (Object::cast_to<Node2D>(p_parent)) {
+ node_type = "AudioStreamPlayer2D";
+ } else if (Object::cast_to<Node3D>(p_parent)) {
+ node_type = "AudioStreamPlayer3D";
+ }
+ }
+
+ Vector<Node *> nodes;
+ bool error = false;
+
+ for (const String &path : p_files) {
+ Ref<AudioStream> stream = ResourceLoader::load(path);
+ if (stream.is_null()) {
+ current_option = -1;
+ accept->set_text(vformat(TTR("Error loading audio stream from %s"), path));
+ accept->popup_centered();
+ error = true;
+ break;
+ }
+
+ Node *player = Object::cast_to<Node>(ClassDB::instantiate(node_type));
+ player->set("stream", stream);
+
+ // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+ const String &node_name = Node::adjust_name_casing(path.get_file().get_basename());
+ if (!node_name.is_empty()) {
+ player->set_name(node_name);
+ }
+
+ nodes.push_back(player);
+ }
+
+ if (error) {
+ for (Node *node : nodes) {
+ memdelete(node);
+ }
+ return;
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action_for_history(TTRN("Create AudioStreamPlayer", "Create AudioStreamPlayers", nodes.size()), editor_data->get_current_edited_scene_history_id());
+ undo_redo->add_do_method(editor_selection, "clear");
+
+ for (int i = 0; i < nodes.size(); i++) {
+ Node *node = nodes[i];
+
+ undo_redo->add_do_method(p_parent, "add_child", node, true);
+ if (p_pos >= 0) {
+ undo_redo->add_do_method(p_parent, "move_child", node, p_pos + i);
+ }
+ undo_redo->add_do_method(node, "set_owner", edited_scene);
+ undo_redo->add_do_method(editor_selection, "add_node", node);
+ undo_redo->add_do_reference(node);
+ undo_redo->add_undo_method(p_parent, "remove_child", node);
+
+ String new_name = p_parent->validate_child_name(node);
+ EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+ undo_redo->add_do_method(ed, "live_debug_create_node", edited_scene->get_path_to(p_parent), node->get_class(), new_name);
+ undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name)));
+ }
+
+ undo_redo->commit_action();
+}
+
void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) {
// `move_child` + `get_index` doesn't really work for internal nodes.
ERR_FAIL_COND_MSG(base->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to replace internal node, this is not supported.");
@@ -3196,15 +3266,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) {
Node *node = get_node(p_to);
ERR_FAIL_NULL(node);
+ ERR_FAIL_COND(p_files.is_empty());
- if (scene_tree->get_scene_tree()->get_drop_mode_flags() & Tree::DROP_MODE_INBETWEEN) {
- // Dropped PackedScene, instance it.
- int to_pos = -1;
- _normalize_drop(node, to_pos, p_type);
- _perform_instantiate_scenes(p_files, node, to_pos);
- } else {
- const String &res_path = p_files[0];
- StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path);
+ const String &res_path = p_files[0];
+ const StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path);
+
+ // Dropping as property when possible.
+ if (p_type == 0 && p_files.size() == 1) {
List<String> valid_properties;
List<PropertyInfo> pinfo;
@@ -3238,10 +3306,22 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to,
menu_properties->reset_size();
menu_properties->set_position(get_screen_position() + get_local_mouse_position());
menu_properties->popup();
- } else if (!valid_properties.is_empty()) {
+ return;
+ }
+ if (!valid_properties.is_empty()) {
_perform_property_drop(node, valid_properties.front()->get(), ResourceLoader::load(res_path));
+ return;
}
}
+
+ // Either instantiate scenes or create AudioStreamPlayers.
+ int to_pos = -1;
+ _normalize_drop(node, to_pos, p_type);
+ if (res_type == "PackedScene") {
+ _perform_instantiate_scenes(p_files, node, to_pos);
+ } else {
+ _perform_create_audio_stream_players(p_files, node, to_pos);
+ }
}
void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) {
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 21e1b00f93..abef990995 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -272,7 +272,8 @@ class SceneTreeDock : public VBoxContainer {
void _filter_option_selected(int option);
void _append_filter_options_to(PopupMenu *p_menu, bool p_include_separator = true);
- void _perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos);
+ void _perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos);
+ void _perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos);
void _replace_with_branch_scene(const String &p_file, Node *base);
void _remote_tree_selected();
diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml
index b559ca20b2..7e14259d5a 100644
--- a/modules/mono/doc_classes/CSharpScript.xml
+++ b/modules/mono/doc_classes/CSharpScript.xml
@@ -5,7 +5,6 @@
</brief_description>
<description>
This class represents a C# script. It is the C# equivalent of the [GDScript] class and is only available in Mono-enabled Godot builds.
- See also [GodotSharp].
</description>
<tutorials>
<link title="C# documentation index">$DOCS_URL/tutorials/scripting/c_sharp/index.html</link>
diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml
deleted file mode 100644
index 969ca14350..0000000000
--- a/modules/mono/doc_classes/GodotSharp.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="GodotSharp" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
- <brief_description>
- Bridge between Godot and the Mono runtime (Mono-enabled builds only).
- </brief_description>
- <description>
- This class is a bridge between Godot and the Mono runtime. It exposes several low-level operations and is only available in Mono-enabled Godot builds.
- See also [CSharpScript].
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="is_runtime_initialized">
- <return type="bool" />
- <description>
- Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise.
- </description>
- </method>
- </methods>
-</class>
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 05dacd28fb..03d8b4eab6 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -143,7 +143,7 @@ bool godot_icall_Internal_IsAssembliesReloadingNeeded() {
void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
- mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload);
+ callable_mp(mono_bind::GodotSharp::get_singleton(), &mono_bind::GodotSharp::reload_assemblies).call_deferred(p_soft_reload);
#endif
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index ab7f8ede44..33f0850a8d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -318,9 +318,9 @@ namespace Godot
Vector3 ofs = _position + halfExtents;
return ofs + new Vector3(
- dir.X > 0f ? -halfExtents.X : halfExtents.X,
- dir.Y > 0f ? -halfExtents.Y : halfExtents.Y,
- dir.Z > 0f ? -halfExtents.Z : halfExtents.Z);
+ dir.X > 0f ? halfExtents.X : -halfExtents.X,
+ dir.Y > 0f ? halfExtents.Y : -halfExtents.Y,
+ dir.Z > 0f ? halfExtents.Z : -halfExtents.Z);
}
/// <summary>
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 0e34616951..48caae8523 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -564,11 +564,7 @@ namespace mono_bind {
GodotSharp *GodotSharp::singleton = nullptr;
-bool GodotSharp::_is_runtime_initialized() {
- return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized();
-}
-
-void GodotSharp::_reload_assemblies(bool p_soft_reload) {
+void GodotSharp::reload_assemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
CRASH_COND(CSharpLanguage::get_singleton() == nullptr);
// This method may be called more than once with `call_deferred`, so we need to check
@@ -579,11 +575,6 @@ void GodotSharp::_reload_assemblies(bool p_soft_reload) {
#endif
}
-void GodotSharp::_bind_methods() {
- ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::_is_runtime_initialized);
- ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies);
-}
-
GodotSharp::GodotSharp() {
singleton = this;
}
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 0cb087db57..614bfc63fb 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -167,18 +167,14 @@ namespace mono_bind {
class GodotSharp : public Object {
GDCLASS(GodotSharp, Object);
- friend class GDMono;
-
- void _reload_assemblies(bool p_soft_reload);
- bool _is_runtime_initialized();
-
protected:
static GodotSharp *singleton;
- static void _bind_methods();
public:
static GodotSharp *get_singleton() { return singleton; }
+ void reload_assemblies(bool p_soft_reload);
+
GodotSharp();
~GodotSharp();
};
diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp
index beaa50ecb2..4d5426d96f 100644
--- a/modules/mono/register_types.cpp
+++ b/modules/mono/register_types.cpp
@@ -49,9 +49,6 @@ void initialize_mono_module(ModuleInitializationLevel p_level) {
_godotsharp = memnew(mono_bind::GodotSharp);
- GDREGISTER_CLASS(mono_bind::GodotSharp);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GodotSharp", mono_bind::GodotSharp::get_singleton()));
-
script_language_cs = memnew(CSharpLanguage);
script_language_cs->set_language_index(ScriptServer::get_language_count());
ScriptServer::register_language(script_language_cs);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index 89fbb9f580..cfbbcf7d0e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -61,6 +61,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private var contextClickInProgress = false
private var pointerCaptureInProgress = false
+ private var lastDragX: Float = 0.0f
+ private var lastDragY: Float = 0.0f
+
override fun onDown(event: MotionEvent): Boolean {
GodotInputHandler.handleMotionEvent(event.source, MotionEvent.ACTION_DOWN, event.buttonState, event.x, event.y, nextDownIsDoubleTap)
nextDownIsDoubleTap = false
@@ -165,6 +168,8 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
pointerCaptureInProgress = false
dragInProgress = false
contextClickInProgress = false
+ lastDragX = 0.0f
+ lastDragY = 0.0f
return true
}
@@ -189,6 +194,17 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
sourceMouseRelative
)
return true
+ } else if (!scaleInProgress) {
+ // The 'onScroll' event is triggered with a long delay.
+ // Force the 'InputEventScreenDrag' event earlier here.
+ // We don't toggle 'dragInProgress' here so that the scaling logic can override the drag operation if needed.
+ // Once the 'onScroll' event kicks-in, 'dragInProgress' will be properly set.
+ if (lastDragX != event.getX(0) || lastDragY != event.getY(0)) {
+ lastDragX = event.getX(0)
+ lastDragY = event.getY(0)
+ GodotInputHandler.handleMotionEvent(event)
+ return true
+ }
}
return false
}
@@ -216,7 +232,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
distanceY: Float
): Boolean {
if (scaleInProgress) {
- if (dragInProgress) {
+ if (dragInProgress || lastDragX != 0.0f || lastDragY != 0.0f) {
if (originEvent != null) {
// Cancel the drag
GodotInputHandler.handleMotionEvent(
@@ -228,6 +244,8 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
)
}
dragInProgress = false
+ lastDragX = 0.0f
+ lastDragY = 0.0f
}
}
@@ -235,8 +253,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
val y = terminusEvent.y
if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) {
GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f)
- } else if (!scaleInProgress){
+ } else if (!scaleInProgress) {
dragInProgress = true
+ lastDragX = terminusEvent.getX(0)
+ lastDragY = terminusEvent.getY(0)
GodotInputHandler.handleMotionEvent(terminusEvent)
}
return true
diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp
index 6fc33afe99..6c54faa13a 100644
--- a/platform/windows/windows_terminal_logger.cpp
+++ b/platform/windows/windows_terminal_logger.cpp
@@ -42,20 +42,30 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er
return;
}
- const unsigned int BUFFER_SIZE = 16384;
- char buf[BUFFER_SIZE + 1]; // +1 for the terminating character
- int len = vsnprintf(buf, BUFFER_SIZE, p_format, p_list);
- if (len <= 0) {
- return;
+ const int static_buffer_size = 1024;
+ char static_buf[static_buffer_size];
+ char *buf = static_buf;
+ va_list list_copy;
+ va_copy(list_copy, p_list);
+ int len = vsnprintf(buf, static_buffer_size, p_format, p_list);
+ if (len >= static_buffer_size) {
+ buf = (char *)memalloc(len + 1);
+ len = vsnprintf(buf, len + 1, p_format, list_copy);
+ }
+ va_end(list_copy);
+
+ String str_buf = String::utf8(buf, len).replace("\r\n", "\n").replace("\n", "\r\n");
+ if (len >= static_buffer_size) {
+ memfree(buf);
}
- if ((unsigned int)len >= BUFFER_SIZE) {
- len = BUFFER_SIZE; // Output is too big, will be truncated
+ CharString cstr_buf = str_buf.utf8();
+ if (cstr_buf.length() == 0) {
+ return;
}
- buf[len] = 0;
DWORD written = 0;
HANDLE h = p_err ? GetStdHandle(STD_ERROR_HANDLE) : GetStdHandle(STD_OUTPUT_HANDLE);
- WriteFile(h, &buf[0], len, &written, nullptr);
+ WriteFile(h, cstr_buf.ptr(), cstr_buf.length(), &written, nullptr);
#ifdef DEBUG_ENABLED
FlushFileBuffers(h);
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 03fd7364c0..fe51171744 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -603,6 +603,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+ rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
} else {
// Clear occluder.
if (occluder.is_valid()) {
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 498e101b3c..a4804e928a 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -847,12 +847,13 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
ERR_FAIL_INDEX(p_bone_idx, bone_size);
Bone *bonesptr = bones.ptrw();
- List<int> bones_to_process = List<int>();
+ thread_local LocalVector<int> bones_to_process;
+ bones_to_process.clear();
bones_to_process.push_back(p_bone_idx);
- while (bones_to_process.size() > 0) {
- int current_bone_idx = bones_to_process.front()->get();
- bones_to_process.erase(current_bone_idx);
+ uint32_t index = 0;
+ while (index < bones_to_process.size()) {
+ int current_bone_idx = bones_to_process[index];
Bone &b = bonesptr[current_bone_idx];
bool bone_enabled = b.enabled && !show_rest_only;
@@ -905,6 +906,8 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(b.child_bones[i]);
}
+
+ index++;
}
}
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 8267e958b4..dfe5bd4a47 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -173,7 +173,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
}
}
#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) {
+ if (EditorHelp::get_doc_data() != nullptr && Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) {
EditorHelp::get_doc_data()->add_doc(class_doc);
}
#endif
diff --git a/servers/rendering/rendering_light_culler.h b/servers/rendering/rendering_light_culler.h
index 0bf975430b..b0437d2310 100644
--- a/servers/rendering/rendering_light_culler.h
+++ b/servers/rendering/rendering_light_culler.h
@@ -181,14 +181,14 @@ private:
}
// Prevent divide by zero.
- if (lc > 0.00001f) {
+ if (lc > 0.001f) {
// If the summed length of the smaller two
// sides is close to the length of the longest side,
// the points are colinear, and the triangle is near degenerate.
float ld = ((la + lb) - lc) / lc;
// ld will be close to zero for colinear tris.
- return ld < 0.00001f;
+ return ld < 0.001f;
}
// Don't create planes from tiny triangles,