diff options
Diffstat (limited to 'scene')
27 files changed, 1491 insertions, 391 deletions
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 55856403bf..f785ef0ee9 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -304,6 +304,7 @@ void Camera2D::_notification(int p_what) { _interpolation_data.xform_prev = _interpolation_data.xform_curr; } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (is_physics_interpolated_and_enabled()) { _update_scroll(); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 9857401cb1..dfce5d43bd 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -698,6 +698,8 @@ void GPUParticles2D::_notification(int p_what) { RS::get_singleton()->particles_set_subemitter(particles, RID()); } break; + case NOTIFICATION_SUSPENDED: + case NOTIFICATION_UNSUSPENDED: case NOTIFICATION_PAUSED: case NOTIFICATION_UNPAUSED: { if (is_inside_tree()) { diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index a80ad582d7..77f181b28a 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -92,6 +92,7 @@ void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) { rect_cache_dirty = true; RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed); emit_changed(); + update_configuration_warning(); } Vector<Vector2> OccluderPolygon2D::get_polygon() const { diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index 1bbb00c9b7..7972324e1b 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -255,12 +255,20 @@ void NavigationAgent2D::_notification(int p_what) { #endif // DEBUG_ENABLED } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (agent_parent) { NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process()); } } break; + case NOTIFICATION_UNSUSPENDED: { + if (get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case NOTIFICATION_UNPAUSED: { if (agent_parent) { NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process()); diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index b872d73413..7d52041e6a 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -106,6 +106,7 @@ void NavigationObstacle2D::_notification(int p_what) { #endif // DEBUG_ENABLED } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (!can_process()) { map_before_pause = map_current; @@ -117,6 +118,13 @@ void NavigationObstacle2D::_notification(int p_what) { NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process()); } break; + case NOTIFICATION_UNSUSPENDED: { + if (get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case NOTIFICATION_UNPAUSED: { if (!can_process()) { map_before_pause = map_current; diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index af7f94387b..f003442265 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -187,6 +187,7 @@ void TouchScreenButton::_notification(int p_what) { } } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (is_pressed()) { _release(); diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index d67d4e42a0..77665317a8 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -236,6 +236,7 @@ void Camera3D::_notification(int p_what) { } } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) { _physics_interpolation_ensure_transform_calculated(true); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 1626fc1e96..83609dfa91 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -513,6 +513,8 @@ void GPUParticles3D::_notification(int p_what) { RS::get_singleton()->particles_set_subemitter(particles, RID()); } break; + case NOTIFICATION_SUSPENDED: + case NOTIFICATION_UNSUSPENDED: case NOTIFICATION_PAUSED: case NOTIFICATION_UNPAUSED: { if (is_inside_tree()) { diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index 239fe279db..bd88c0e327 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -274,12 +274,20 @@ void NavigationAgent3D::_notification(int p_what) { #endif // DEBUG_ENABLED } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (agent_parent) { NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process()); } } break; + case NOTIFICATION_UNSUSPENDED: { + if (get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case NOTIFICATION_UNPAUSED: { if (agent_parent) { NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process()); diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 85630b8c6b..0ea6533094 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -121,6 +121,7 @@ void NavigationObstacle3D::_notification(int p_what) { #endif // DEBUG_ENABLED } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (!can_process()) { map_before_pause = map_current; @@ -132,6 +133,13 @@ void NavigationObstacle3D::_notification(int p_what) { NavigationServer3D::get_singleton()->obstacle_set_paused(obstacle, !can_process()); } break; + case NOTIFICATION_UNSUSPENDED: { + if (get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case NOTIFICATION_UNPAUSED: { if (!can_process()) { map_before_pause = map_current; diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index 1007dd3df5..0b507847dd 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -114,6 +114,7 @@ void AudioStreamPlayerInternal::notification(int p_what) { stream_playbacks.clear(); } break; + case Node::NOTIFICATION_SUSPENDED: case Node::NOTIFICATION_PAUSED: { if (!node->can_process()) { // Node can't process so we start fading out to silence @@ -121,6 +122,13 @@ void AudioStreamPlayerInternal::notification(int p_what) { } } break; + case Node::NOTIFICATION_UNSUSPENDED: { + if (node->get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case Node::NOTIFICATION_UNPAUSED: { set_stream_paused(false); } break; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 8f0dc9828b..1a1780d737 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -33,20 +33,34 @@ #include "scene_debugger.h" #include "core/debugger/engine_debugger.h" -#include "core/debugger/engine_profiler.h" #include "core/io/marshalls.h" #include "core/object/script_language.h" #include "core/templates/local_vector.h" +#include "scene/2d/physics/collision_object_2d.h" +#include "scene/2d/physics/collision_polygon_2d.h" +#include "scene/2d/physics/collision_shape_2d.h" +#ifndef _3D_DISABLED +#include "scene/3d/label_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/physics/collision_object_3d.h" +#include "scene/3d/physics/collision_shape_3d.h" +#include "scene/3d/sprite_3d.h" +#include "scene/resources/surface_tool.h" +#endif // _3D_DISABLED +#include "scene/gui/popup_menu.h" +#include "scene/main/canvas_layer.h" #include "scene/main/scene_tree.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" - -SceneDebugger *SceneDebugger::singleton = nullptr; +#include "scene/theme/theme_db.h" SceneDebugger::SceneDebugger() { singleton = this; + #ifdef DEBUG_ENABLED LiveEditor::singleton = memnew(LiveEditor); + RuntimeNodeSelect::singleton = memnew(RuntimeNodeSelect); + EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message)); #endif } @@ -58,7 +72,13 @@ SceneDebugger::~SceneDebugger() { memdelete(LiveEditor::singleton); LiveEditor::singleton = nullptr; } + + if (RuntimeNodeSelect::singleton) { + memdelete(RuntimeNodeSelect::singleton); + RuntimeNodeSelect::singleton = nullptr; + } #endif + singleton = nullptr; } @@ -80,10 +100,15 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra if (!scene_tree) { return ERR_UNCONFIGURED; } + LiveEditor *live_editor = LiveEditor::get_singleton(); if (!live_editor) { return ERR_UNCONFIGURED; } + RuntimeNodeSelect *runtime_node_select = RuntimeNodeSelect::get_singleton(); + if (!runtime_node_select) { + return ERR_UNCONFIGURED; + } r_captured = true; if (p_msg == "request_scene_tree") { // Scene tree @@ -101,22 +126,34 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra ObjectID id = p_args[0]; _send_object_id(id); - } else if (p_msg == "override_camera_2D:set") { // Camera + } else if (p_msg == "suspend_changed") { ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); - bool enforce = p_args[0]; - scene_tree->get_root()->enable_canvas_transform_override(enforce); + bool suspended = p_args[0]; + scene_tree->set_suspend(suspended); + runtime_node_select->_update_input_state(); - } else if (p_msg == "override_camera_2D:transform") { - ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); - Transform2D transform = p_args[0]; - scene_tree->get_root()->set_canvas_transform_override(transform); -#ifndef _3D_DISABLED - } else if (p_msg == "override_camera_3D:set") { + } else if (p_msg == "next_frame") { + _next_frame(); + + } else if (p_msg == "override_cameras") { // Camera ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); bool enable = p_args[0]; + bool from_editor = p_args[1]; + scene_tree->get_root()->enable_canvas_transform_override(enable); +#ifndef _3D_DISABLED scene_tree->get_root()->enable_camera_3d_override(enable); +#endif // _3D_DISABLED + runtime_node_select->_set_camera_override_enabled(enable && !from_editor); - } else if (p_msg == "override_camera_3D:transform") { + } else if (p_msg == "transform_camera_2d") { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + Transform2D transform = p_args[0]; + scene_tree->get_root()->set_canvas_transform_override(transform); + + runtime_node_select->_queue_selection_update(); + +#ifndef _3D_DISABLED + } else if (p_msg == "transform_camera_3d") { ERR_FAIL_COND_V(p_args.size() < 5, ERR_INVALID_DATA); Transform3D transform = p_args[0]; bool is_perspective = p_args[1]; @@ -129,95 +166,142 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra scene_tree->get_root()->set_camera_3d_override_orthogonal(size_or_fov, depth_near, depth_far); } scene_tree->get_root()->set_camera_3d_override_transform(transform); + + runtime_node_select->_queue_selection_update(); #endif // _3D_DISABLED + } else if (p_msg == "set_object_property") { ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); _set_object_property(p_args[0], p_args[1], p_args[2]); - } else if (!p_msg.begins_with("live_")) { // Live edits below. - return ERR_SKIP; - } else if (p_msg == "live_set_root") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - live_editor->_root_func(p_args[0], p_args[1]); + runtime_node_select->_queue_selection_update(); + + } else if (p_msg.begins_with("live_")) { /// Live Edit + if (p_msg == "live_set_root") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_root_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_node_path") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_node_path_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_res_path") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_res_path_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_node_prop_res") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_node_prop") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_res_prop_res") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_res_prop") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_node_call") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + LocalVector<Variant> args; + LocalVector<Variant *> argptrs; + args.resize(p_args.size() - 2); + argptrs.resize(args.size()); + for (uint32_t i = 0; i < args.size(); i++) { + args[i] = p_args[i + 2]; + argptrs[i] = &args[i]; + } + live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); + + } else if (p_msg == "live_res_call") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + LocalVector<Variant> args; + LocalVector<Variant *> argptrs; + args.resize(p_args.size() - 2); + argptrs.resize(args.size()); + for (uint32_t i = 0; i < args.size(); i++) { + args[i] = p_args[i + 2]; + argptrs[i] = &args[i]; + } + live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); - } else if (p_msg == "live_node_path") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - live_editor->_node_path_func(p_args[0], p_args[1]); + } else if (p_msg == "live_create_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]); - } else if (p_msg == "live_res_path") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - live_editor->_res_path_func(p_args[0], p_args[1]); + } else if (p_msg == "live_instantiate_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]); - } else if (p_msg == "live_node_prop_res") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]); + } else if (p_msg == "live_remove_node") { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + live_editor->_remove_node_func(p_args[0]); - } else if (p_msg == "live_node_prop") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]); + if (!runtime_node_select->has_selection) { + runtime_node_select->_clear_selection(); + } - } else if (p_msg == "live_res_prop_res") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]); + } else if (p_msg == "live_remove_and_keep_node") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]); - } else if (p_msg == "live_res_prop") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]); + if (!runtime_node_select->has_selection) { + runtime_node_select->_clear_selection(); + } - } else if (p_msg == "live_node_call") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - LocalVector<Variant> args; - LocalVector<Variant *> argptrs; - args.resize(p_args.size() - 2); - argptrs.resize(args.size()); - for (uint32_t i = 0; i < args.size(); i++) { - args[i] = p_args[i + 2]; - argptrs[i] = &args[i]; - } - live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); + } else if (p_msg == "live_restore_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]); - } else if (p_msg == "live_res_call") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - LocalVector<Variant> args; - LocalVector<Variant *> argptrs; - args.resize(p_args.size() - 2); - argptrs.resize(args.size()); - for (uint32_t i = 0; i < args.size(); i++) { - args[i] = p_args[i + 2]; - argptrs[i] = &args[i]; + } else if (p_msg == "live_duplicate_node") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_duplicate_node_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_reparent_node") { + ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA); + live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]); + + } else { + return ERR_SKIP; } - live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size()); - } else if (p_msg == "live_create_node") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]); + } else if (p_msg.begins_with("runtime_node_select_")) { /// Runtime Node Selection + if (p_msg == "runtime_node_select_setup") { + runtime_node_select->_setup(); - } else if (p_msg == "live_instantiate_node") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]); + } else if (p_msg == "runtime_node_select_set_type") { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)(int)p_args[0]; + runtime_node_select->_node_set_type(type); - } else if (p_msg == "live_remove_node") { - ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); - live_editor->_remove_node_func(p_args[0]); + } else if (p_msg == "runtime_node_select_set_mode") { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)(int)p_args[0]; + runtime_node_select->_select_set_mode(mode); - } else if (p_msg == "live_remove_and_keep_node") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]); + } else if (p_msg == "runtime_node_select_set_visible") { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + bool visible = p_args[0]; + runtime_node_select->_set_selection_visible(visible); - } else if (p_msg == "live_restore_node") { - ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); - live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]); + } else if (p_msg == "runtime_node_select_reset_camera_2d") { + runtime_node_select->_reset_camera_2d(); - } else if (p_msg == "live_duplicate_node") { - ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); - live_editor->_duplicate_node_func(p_args[0], p_args[1]); + } else if (p_msg == "runtime_node_select_reset_camera_3d") { + runtime_node_select->_reset_camera_3d(); + + } else { + return ERR_SKIP; + } - } else if (p_msg == "live_reparent_node") { - ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA); - live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]); } else { r_captured = false; } + return OK; } @@ -262,6 +346,9 @@ void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) { return; } + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + RuntimeNodeSelect::get_singleton()->_select_node(node); + Array arr; obj.serialize(arr); EngineDebugger::get_singleton()->send_message("scene:inspect_object", arr); @@ -282,6 +369,16 @@ void SceneDebugger::_set_object_property(ObjectID p_id, const String &p_property obj->set(prop_name, p_value); } +void SceneDebugger::_next_frame() { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree->is_suspended()) { + return; + } + + scene_tree->set_suspend(false); + RenderingServer::get_singleton()->connect("frame_post_draw", callable_mp(scene_tree, &SceneTree::set_suspend).bind(true), Object::CONNECT_ONE_SHOT); +} + void SceneDebugger::add_to_cache(const String &p_filename, Node *p_node) { LiveEditor *debugger = LiveEditor::get_singleton(); if (!debugger) { @@ -582,7 +679,6 @@ void SceneDebuggerTree::deserialize(const Array &p_arr) { } /// LiveEditor -LiveEditor *LiveEditor::singleton = nullptr; LiveEditor *LiveEditor::get_singleton() { return singleton; } @@ -1091,4 +1187,942 @@ void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new } } +/// RuntimeNodeSelect +RuntimeNodeSelect *RuntimeNodeSelect::get_singleton() { + return singleton; +} + +RuntimeNodeSelect::~RuntimeNodeSelect() { + if (selection_list && !selection_list->is_visible()) { + memdelete(selection_list); + } + + if (sbox_2d_canvas.is_valid()) { + RS::get_singleton()->free(sbox_2d_canvas); + RS::get_singleton()->free(sbox_2d_ci); + } + +#ifndef _3D_DISABLED + if (sbox_3d_instance.is_valid()) { + RS::get_singleton()->free(sbox_3d_instance); + RS::get_singleton()->free(sbox_3d_instance_ofs); + RS::get_singleton()->free(sbox_3d_instance_xray); + RS::get_singleton()->free(sbox_3d_instance_xray_ofs); + } +#endif // _3D_DISABLED +} + +void RuntimeNodeSelect::_setup() { + Window *root = SceneTree::get_singleton()->get_root(); + ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input))); + + root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)); + root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED); + + selection_list = memnew(PopupMenu); + selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme()); + selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED); + selection_list->set_force_native(true); + selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list)); + selection_list->connect("popup_hide", callable_mp(Object::cast_to<Node>(root), &Node::remove_child).bind(selection_list)); + + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback)); + + /// 2D Selection Box Generation + + sbox_2d_canvas = RS::get_singleton()->canvas_create(); + sbox_2d_ci = RS::get_singleton()->canvas_item_create(); + RS::get_singleton()->viewport_attach_canvas(root->get_viewport_rid(), sbox_2d_canvas); + RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, sbox_2d_canvas); + +#ifndef _3D_DISABLED + cursor = Cursor(); + + /// 3D Selection Box Generation + // Copied from the Node3DEditor implementation. + + // Use two AABBs to create the illusion of a slightly thicker line. + AABB aabb(Vector3(), Vector3(1, 1, 1)); + + // Create a x-ray (visible through solid surfaces) and standard version of the selection box. + // Both will be drawn at the same position, but with different opacity. + // This lets the user see where the selection is while still having a sense of depth. + Ref<SurfaceTool> st = memnew(SurfaceTool); + Ref<SurfaceTool> st_xray = memnew(SurfaceTool); + + st->begin(Mesh::PRIMITIVE_LINES); + st_xray->begin(Mesh::PRIMITIVE_LINES); + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + + st->add_vertex(a); + st->add_vertex(b); + st_xray->add_vertex(a); + st_xray->add_vertex(b); + } + + Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); + mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + // In the original Node3DEditor, this value would be fetched from the "editors/3d/selection_box_color" editor property, + // but since this is not accessible from here, we will just use the default value. + const Color selection_color_3d = Color(1, 0.5, 0); + mat->set_albedo(selection_color_3d); + mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + st->set_material(mat); + sbox_3d_mesh = st->commit(); + + Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D); + mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); + mat_xray->set_albedo(selection_color_3d * Color(1, 1, 1, 0.15)); + mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + st_xray->set_material(mat_xray); + sbox_3d_mesh_xray = st_xray->commit(); +#endif // _3D_DISABLED + + SceneTree::get_singleton()->connect("process_frame", callable_mp(this, &RuntimeNodeSelect::_process_frame)); + SceneTree::get_singleton()->connect("physics_frame", callable_mp(this, &RuntimeNodeSelect::_physics_frame)); + + // This function will be called before the root enters the tree at first when the Game view is passing its settings to + // the debugger, so queue the update for after it enters. + root->connect(SceneStringName(tree_entered), callable_mp(this, &RuntimeNodeSelect::_update_input_state), Object::CONNECT_ONE_SHOT); +} + +void RuntimeNodeSelect::_node_set_type(NodeType p_type) { + node_select_type = p_type; + _update_input_state(); +} + +void RuntimeNodeSelect::_select_set_mode(SelectMode p_mode) { + node_select_mode = p_mode; +} + +void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) { + camera_override = p_enabled; + + if (p_enabled) { + _update_view_2d(); + } + +#ifndef _3D_DISABLED + if (camera_first_override) { + _reset_camera_2d(); + _reset_camera_3d(); + + camera_first_override = false; + } else if (p_enabled) { + _update_view_2d(); + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); + SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR); + } +#endif // _3D_DISABLED +} + +void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) { + Window *root = SceneTree::get_singleton()->get_root(); + if (node_select_type == NODE_TYPE_NONE || selection_list->is_visible()) { + // Workaround for platforms that don't allow subwindows. + if (selection_list->is_visible() && selection_list->is_embedded()) { + root->set_disable_input_override(false); + selection_list->push_input(p_event); + callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true); + } + + return; + } + + if (camera_override) { + if (node_select_type == NODE_TYPE_2D) { + if (panner->gui_input(p_event, Rect2(Vector2(), root->get_size()))) { + return; + } + } else if (node_select_type == NODE_TYPE_3D) { +#ifndef _3D_DISABLED + if (root->get_camera_3d() && _handle_3d_input(p_event)) { + return; + } +#endif // _3D_DISABLED + } + } + + Ref<InputEventMouseButton> b = p_event; + if (!b.is_valid() || !b->is_pressed()) { + return; + } + + list_shortcut_pressed = node_select_mode == SELECT_MODE_SINGLE && b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed(); + if (list_shortcut_pressed || b->get_button_index() == MouseButton::LEFT) { + selection_position = b->get_position(); + } +} + +void RuntimeNodeSelect::_items_popup_index_pressed(int p_index, PopupMenu *p_popup) { + Object *obj = p_popup->get_item_metadata(p_index).get_validated_object(); + if (!obj) { + return; + } + + Array message; + message.append(obj->get_instance_id()); + EngineDebugger::get_singleton()->send_message("remote_node_clicked", message); +} + +void RuntimeNodeSelect::_update_input_state() { + SceneTree *scene_tree = SceneTree::get_singleton(); + // This function can be called at the very beginning, when the root hasn't entered the tree yet. + // So check first to avoid a crash. + if (!scene_tree->get_root()->is_inside_tree()) { + return; + } + + bool disable_input = scene_tree->is_suspended() || node_select_type != RuntimeNodeSelect::NODE_TYPE_NONE; + Input::get_singleton()->set_disable_input(disable_input); + Input::get_singleton()->set_mouse_mode_override_enabled(disable_input); + scene_tree->get_root()->set_disable_input_override(disable_input); +} + +void RuntimeNodeSelect::_process_frame() { +#ifndef _3D_DISABLED + if (camera_freelook) { + Transform3D transform = _get_cursor_transform(); + Vector3 forward = transform.basis.xform(Vector3(0, 0, -1)); + const Vector3 right = transform.basis.xform(Vector3(1, 0, 0)); + Vector3 up = transform.basis.xform(Vector3(0, 1, 0)); + + Vector3 direction; + + Input *input = Input::get_singleton(); + bool was_input_disabled = input->is_input_disabled(); + if (was_input_disabled) { + input->set_disable_input(false); + } + + if (input->is_physical_key_pressed(Key::A)) { + direction -= right; + } + if (input->is_physical_key_pressed(Key::D)) { + direction += right; + } + if (input->is_physical_key_pressed(Key::W)) { + direction += forward; + } + if (input->is_physical_key_pressed(Key::S)) { + direction -= forward; + } + if (input->is_physical_key_pressed(Key::E)) { + direction += up; + } + if (input->is_physical_key_pressed(Key::Q)) { + direction -= up; + } + + real_t speed = FREELOOK_BASE_SPEED; + if (input->is_physical_key_pressed(Key::SHIFT)) { + speed *= 3.0; + } + if (input->is_physical_key_pressed(Key::ALT)) { + speed *= 0.333333; + } + + if (was_input_disabled) { + input->set_disable_input(true); + } + + if (direction != Vector3()) { + // Calculate the process time manually, as the time scale is frozen. + const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale(); + const Vector3 motion = direction * speed * process_time; + cursor.pos += motion; + cursor.eye_pos += motion; + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); + } + } +#endif // _3D_DISABLED + + if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) { + selection_update_queued = false; + if (has_selection) { + _update_selection(); + } + } +} + +void RuntimeNodeSelect::_physics_frame() { + if (!Math::is_inf(selection_position.x) || !Math::is_inf(selection_position.y)) { + _click_point(); + selection_position = Point2(INFINITY, INFINITY); + } +} + +void RuntimeNodeSelect::_click_point() { + Window *root = SceneTree::get_singleton()->get_root(); + Point2 pos = root->get_screen_transform().affine_inverse().xform(selection_position); + Vector<SelectResult> items; + + if (node_select_type == NODE_TYPE_2D) { + for (int i = 0; i < root->get_child_count(); i++) { + _find_canvas_items_at_pos(pos, root->get_child(i), items); + } + + // Remove possible duplicates. + for (int i = 0; i < items.size(); i++) { + Node *item = items[i].item; + for (int j = 0; j < i; j++) { + if (items[j].item == item) { + items.remove_at(i); + i--; + + break; + } + } + } + } else if (node_select_type == NODE_TYPE_3D) { +#ifndef _3D_DISABLED + _find_3d_items_at_pos(pos, items); +#endif // _3D_DISABLED + } + + if (items.is_empty()) { + return; + } + + items.sort(); + + if ((!list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) || items.size() == 1) { + Array message; + message.append(items[0].item->get_instance_id()); + EngineDebugger::get_singleton()->send_message("remote_node_clicked", message); + } else if (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST) { + if (!selection_list->is_inside_tree()) { + root->add_child(selection_list); + } + + selection_list->clear(); + for (const SelectResult &I : items) { + selection_list->add_item(I.item->get_name()); + selection_list->set_item_metadata(-1, I.item); + } + + selection_list->set_position(selection_list->is_embedded() ? pos : selection_position + root->get_position()); + selection_list->reset_size(); + selection_list->popup(); + // FIXME: Ugly hack that stops the popup from hiding when the button is released. + selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0)); + } +} + +void RuntimeNodeSelect::_select_node(Node *p_node) { + if (p_node == selected_node) { + return; + } + + _clear_selection(); + + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); + if (ci) { + selected_node = p_node; + } else { +#ifndef _3D_DISABLED + Node3D *node_3d = Object::cast_to<Node3D>(p_node); + if (node_3d) { + if (!node_3d->is_inside_world()) { + return; + } + + selected_node = p_node; + + sbox_3d_instance = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario()); + sbox_3d_instance_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario()); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_ofs, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + + sbox_3d_instance_xray = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario()); + sbox_3d_instance_xray_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario()); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray_ofs, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + } +#endif // _3D_DISABLED + } + + has_selection = selected_node; + _queue_selection_update(); +} + +void RuntimeNodeSelect::_queue_selection_update() { + if (has_selection && selection_visible) { + if (SceneTree::get_singleton()->is_suspended()) { + _update_selection(); + } else { + selection_update_queued = true; + } + } +} + +void RuntimeNodeSelect::_update_selection() { + if (has_selection && (!selected_node || !selected_node->is_inside_tree())) { + _clear_selection(); + return; + } + + CanvasItem *ci = Object::cast_to<CanvasItem>(selected_node); + if (ci) { + Window *root = SceneTree::get_singleton()->get_root(); + Transform2D xform; + if (root->is_canvas_transform_override_enabled() && !ci->get_canvas_layer_node()) { + RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, (root->get_canvas_transform_override())); + xform = ci->get_global_transform(); + } else { + RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, Transform2D()); + xform = ci->get_global_transform_with_canvas(); + } + + // Fallback. + Rect2 rect = Rect2(Vector2(), Vector2(10, 10)); + + if (ci->_edit_use_rect()) { + rect = ci->_edit_get_rect(); + } else { + CollisionShape2D *collision_shape = Object::cast_to<CollisionShape2D>(ci); + if (collision_shape) { + Ref<Shape2D> shape = collision_shape->get_shape(); + if (shape.is_valid()) { + rect = shape->get_rect(); + } + } + } + + RS::get_singleton()->canvas_item_set_visible(sbox_2d_ci, selection_visible); + + if (xform == sbox_2d_xform && rect == sbox_2d_rect) { + return; // Nothing changed. + } + sbox_2d_xform = xform; + sbox_2d_rect = rect; + + RS::get_singleton()->canvas_item_clear(sbox_2d_ci); + + const Vector2 endpoints[4] = { + xform.xform(rect.position), + xform.xform(rect.position + Vector2(rect.size.x, 0)), + xform.xform(rect.position + rect.size), + xform.xform(rect.position + Vector2(0, rect.size.y)) + }; + + const Color selection_color_2d = Color(1, 0.6, 0.4, 0.7); + for (int i = 0; i < 4; i++) { + RS::get_singleton()->canvas_item_add_line(sbox_2d_ci, endpoints[i], endpoints[(i + 1) % 4], selection_color_2d, Math::round(2.f)); + } + } else { +#ifndef _3D_DISABLED + Node3D *node_3d = Object::cast_to<Node3D>(selected_node); + + // Fallback. + AABB bounds(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1)); + + VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d); + if (visual_instance) { + bounds = visual_instance->get_aabb(); + } else { + CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(node_3d); + if (collision_shape) { + Ref<Shape3D> shape = collision_shape->get_shape(); + if (shape.is_valid()) { + bounds = shape->get_debug_mesh()->get_aabb(); + } + } + } + + RS::get_singleton()->instance_set_visible(sbox_3d_instance, selection_visible); + RS::get_singleton()->instance_set_visible(sbox_3d_instance_ofs, selection_visible); + RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray, selection_visible); + RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray_ofs, selection_visible); + + Transform3D xform_to_top_level_parent_space = node_3d->get_global_transform().affine_inverse() * node_3d->get_global_transform(); + bounds = xform_to_top_level_parent_space.xform(bounds); + Transform3D t = node_3d->get_global_transform(); + + if (t == sbox_3d_xform && bounds == sbox_3d_bounds) { + return; // Nothing changed. + } + sbox_3d_xform = t; + sbox_3d_bounds = bounds; + + Transform3D t_offset = t; + + // Apply AABB scaling before item's global transform. + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(bounds.size + offset); + t.translate_local(bounds.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(bounds.size + offset); + t_offset.translate_local(bounds.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } + + RS::get_singleton()->instance_set_transform(sbox_3d_instance, t); + RS::get_singleton()->instance_set_transform(sbox_3d_instance_ofs, t_offset); + RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray, t); + RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray_ofs, t_offset); +#endif // _3D_DISABLED + } +} + +void RuntimeNodeSelect::_clear_selection() { + selected_node = nullptr; + has_selection = false; + + if (sbox_2d_canvas.is_valid()) { + RS::get_singleton()->canvas_item_clear(sbox_2d_ci); + } + +#ifndef _3D_DISABLED + if (sbox_3d_instance.is_valid()) { + RS::get_singleton()->free(sbox_3d_instance); + RS::get_singleton()->free(sbox_3d_instance_ofs); + RS::get_singleton()->free(sbox_3d_instance_xray); + RS::get_singleton()->free(sbox_3d_instance_xray_ofs); + } +#endif // _3D_DISABLED +} + +void RuntimeNodeSelect::_set_selection_visible(bool p_visible) { + selection_visible = p_visible; + + if (has_selection) { + _update_selection(); + } +} + +// Copied and trimmed from the CanvasItemEditor implementation. +void RuntimeNodeSelect::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { + if (!p_node || Object::cast_to<Viewport>(p_node)) { + return; + } + + // In the original CanvasItemEditor, this value would be fetched from the "editors/polygon_editor/point_grab_radius" editor property, + // but since this is not accessible from here, we will just use the default value. + const real_t grab_distance = 8; + CanvasItem *ci = Object::cast_to<CanvasItem>(p_node); + + for (int i = p_node->get_child_count() - 1; i >= 0; i--) { + if (ci) { + if (!ci->is_set_as_top_level()) { + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform); + } else { + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform); + } + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); + } + } + + if (ci && ci->is_visible_in_tree()) { + Transform2D xform = p_canvas_xform; + if (!ci->is_set_as_top_level()) { + xform *= p_parent_xform; + } + + Vector2 pos; + // Cameras (overridden or not) don't affect `CanvasLayer`s. + if (!ci->get_canvas_layer_node()) { + Window *root = SceneTree::get_singleton()->get_root(); + pos = (root->is_canvas_transform_override_enabled() ? root->get_canvas_transform_override() : root->get_canvas_transform()).affine_inverse().xform(p_pos); + } else { + pos = p_pos; + } + + xform = (xform * ci->get_transform()).affine_inverse(); + const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / view_2d_zoom; + if (ci->_edit_is_selected_on_click(xform.xform(pos), local_grab_distance)) { + SelectResult res; + res.item = ci; + res.order = ci->get_effective_z_index() + ci->get_canvas_layer(); + r_items.push_back(res); + + // If it's a shape, get the collision object it's from. + // FIXME: If the collision object has multiple shapes, only the topmost will be above it in the list. + if (Object::cast_to<CollisionShape2D>(ci) || Object::cast_to<CollisionPolygon2D>(ci)) { + CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ci->get_parent()); + if (collision_object) { + SelectResult res_col; + res_col.item = ci->get_parent(); + res_col.order = collision_object->get_z_index() + ci->get_canvas_layer(); + r_items.push_back(res_col); + } + } + } + } +} + +void RuntimeNodeSelect::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + view_2d_offset.x -= p_scroll_vec.x / view_2d_zoom; + view_2d_offset.y -= p_scroll_vec.y / view_2d_zoom; + + _update_view_2d(); +} + +// A very shallow copy of the same function inside CanvasItemEditor. +void RuntimeNodeSelect::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + real_t prev_zoom = view_2d_zoom; + view_2d_zoom = CLAMP(view_2d_zoom * p_zoom_factor, VIEW_2D_MIN_ZOOM, VIEW_2D_MAX_ZOOM); + + Vector2 pos = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_origin); + view_2d_offset += pos / prev_zoom - pos / view_2d_zoom; + + // We want to align in-scene pixels to screen pixels, this prevents blurry rendering + // of small details (texts, lines). + // This correction adds a jitter movement when zooming, so we correct only when the + // zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway) + const real_t closest_zoom_factor = Math::round(view_2d_zoom); + if (Math::is_zero_approx(view_2d_zoom - closest_zoom_factor)) { + // Make sure scene pixel at view_offset is aligned on a screen pixel. + Vector2 view_offset_int = view_2d_offset.floor(); + Vector2 view_offset_frac = view_2d_offset - view_offset_int; + view_2d_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor; + } + + _update_view_2d(); +} + +void RuntimeNodeSelect::_reset_camera_2d() { + view_2d_offset = -SceneTree::get_singleton()->get_root()->get_canvas_transform().get_origin(); + view_2d_zoom = 1; + + _update_view_2d(); +} + +void RuntimeNodeSelect::_update_view_2d() { + Transform2D transform = Transform2D(); + transform.scale_basis(Size2(view_2d_zoom, view_2d_zoom)); + transform.columns[2] = -view_2d_offset * view_2d_zoom; + + SceneTree::get_singleton()->get_root()->set_canvas_transform_override(transform); + + _queue_selection_update(); +} + +#ifndef _3D_DISABLED +void RuntimeNodeSelect::_find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items) { + Window *root = SceneTree::get_singleton()->get_root(); + Camera3D *camera = root->get_viewport()->get_camera_3d(); + if (!camera) { + return; + } + + Vector3 ray, pos, to; + if (root->get_viewport()->is_camera_3d_override_enabled()) { + Viewport *vp = root->get_viewport(); + ray = vp->camera_3d_override_project_ray_normal(p_pos); + pos = vp->camera_3d_override_project_ray_origin(p_pos); + to = pos + ray * vp->get_camera_3d_override_properties()["z_far"]; + } else { + ray = camera->project_ray_normal(p_pos); + pos = camera->project_ray_origin(p_pos); + to = pos + ray * camera->get_far(); + } + + // Start with physical objects. + PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state(); + PhysicsDirectSpaceState3D::RayResult result; + HashSet<RID> excluded; + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = pos; + ray_params.to = to; + ray_params.collide_with_areas = true; + while (true) { + ray_params.exclude = excluded; + if (ss->intersect_ray(ray_params, result)) { + SelectResult res; + res.item = Object::cast_to<Node>(result.collider); + res.order = -pos.distance_to(Object::cast_to<Node3D>(res.item)->get_global_transform().xform(result.position)); + + // Fetch collision shapes. + CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider); + if (collision) { + List<uint32_t> owners; + collision->get_shape_owners(&owners); + for (const uint32_t &I : owners) { + SelectResult res_shape; + res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I)); + res_shape.order = res.order; + r_items.push_back(res_shape); + } + } + + r_items.push_back(res); + + excluded.insert(result.rid); + } else { + break; + } + } + + // Then go for the meshes. + Vector<ObjectID> items = RS::get_singleton()->instances_cull_ray(pos, to, root->get_world_3d()->get_scenario()); + for (int i = 0; i < items.size(); i++) { + Object *obj = ObjectDB::get_instance(items[i]); + GeometryInstance3D *geo_instance = nullptr; + Ref<TriangleMesh> mesh_collision; + + MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(obj); + if (mesh_instance) { + if (mesh_instance->get_mesh().is_valid()) { + geo_instance = mesh_instance; + mesh_collision = mesh_instance->get_mesh()->generate_triangle_mesh(); + } + } else { + Label3D *label = Object::cast_to<Label3D>(obj); + if (label) { + geo_instance = label; + mesh_collision = label->generate_triangle_mesh(); + } else { + Sprite3D *sprite = Object::cast_to<Sprite3D>(obj); + if (sprite) { + geo_instance = sprite; + mesh_collision = sprite->generate_triangle_mesh(); + } + } + } + + if (mesh_collision.is_valid()) { + Transform3D gt = geo_instance->get_global_transform(); + Transform3D ai = gt.affine_inverse(); + Vector3 point, normal; + if (mesh_collision->intersect_ray(ai.xform(pos), ai.basis.xform(ray).normalized(), point, normal)) { + SelectResult res; + res.item = Object::cast_to<Node>(obj); + res.order = -pos.distance_to(gt.xform(point)); + r_items.push_back(res); + + continue; + } + } + + items.remove_at(i); + i--; + } +} + +bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> b = p_event; + + if (b.is_valid()) { + const real_t zoom_factor = 1.08 * b->get_factor(); + switch (b->get_button_index()) { + case MouseButton::WHEEL_UP: { + if (!camera_freelook) { + _cursor_scale_distance(1.0 / zoom_factor); + } + + return true; + } break; + case MouseButton::WHEEL_DOWN: { + if (!camera_freelook) { + _cursor_scale_distance(zoom_factor); + } + + return true; + } break; + case MouseButton::RIGHT: { + _set_camera_freelook_enabled(b->is_pressed()); + return true; + } break; + default: { + } + } + } + + Ref<InputEventMouseMotion> m = p_event; + + if (m.is_valid()) { + if (camera_freelook) { + _cursor_look(m); + } else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) { + if (m->is_shift_pressed()) { + _cursor_pan(m); + } else { + _cursor_orbit(m); + } + } + + return true; + } + + Ref<InputEventKey> k = p_event; + + if (k.is_valid()) { + if (k->get_physical_keycode() == Key::ESCAPE) { + _set_camera_freelook_enabled(false); + return true; + } else if (k->is_ctrl_pressed()) { + switch (k->get_physical_keycode()) { + case Key::EQUAL: { + cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE); + SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR); + + return true; + } break; + case Key::MINUS: { + cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE); + SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR); + + return true; + } break; + case Key::KEY_0: { + cursor.fov_scale = 1; + SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV, CAMERA_ZNEAR, CAMERA_ZFAR); + + return true; + } break; + default: { + } + } + } + } + + // TODO: Handle magnify and pan input gestures. + + return false; +} + +void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) { + camera_freelook = p_enabled; + + if (p_enabled) { + // Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos + Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1)); + cursor.eye_pos = cursor.pos - cursor.distance * forward; + + previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position(); + + // Hide mouse like in an FPS (warping doesn't work). + Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_CAPTURED); + + } else { + // Restore mouse. + Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_VISIBLE); + + // Restore the previous mouse position when leaving freelook mode. + // This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor + // due to OS limitations. + Input::get_singleton()->warp_mouse(previous_mouse_position); + } +} + +void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) { + real_t min_distance = MAX(CAMERA_ZNEAR * 4, VIEW_3D_MIN_ZOOM); + real_t max_distance = MIN(CAMERA_ZFAR / 4, VIEW_3D_MAX_ZOOM); + cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance); + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); +} + +void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) { + Window *root = SceneTree::get_singleton()->get_root(); + const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(), root->get_size())); + const Transform3D prev_camera_transform = _get_cursor_transform(); + + cursor.x_rot += relative.y * RADS_PER_PIXEL; + // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. + cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57); + + cursor.y_rot += relative.x * RADS_PER_PIXEL; + + // Look is like the opposite of Orbit: the focus point rotates around the camera. + Transform3D camera_transform = _get_cursor_transform(); + Vector3 pos = camera_transform.xform(Vector3(0, 0, 0)); + Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0)); + Vector3 diff = prev_pos - pos; + cursor.pos += diff; + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); +} + +void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) { + Window *root = SceneTree::get_singleton()->get_root(); + // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen. + const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2))); + const real_t pan_speed = 1 / 150.0; + + Transform3D camera_transform; + camera_transform.translate_local(cursor.pos); + camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); + camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); + + Vector3 translation(1 * -relative.x * pan_speed, relative.y * pan_speed, 0); + translation *= cursor.distance / 4; + camera_transform.translate_local(translation); + cursor.pos = camera_transform.origin; + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); +} + +void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) { + Window *root = SceneTree::get_singleton()->get_root(); + // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen. + const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2))); + + cursor.x_rot += relative.y * RADS_PER_PIXEL; + // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. + cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57); + + cursor.y_rot += relative.x * RADS_PER_PIXEL; + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); +} + +Transform3D RuntimeNodeSelect::_get_cursor_transform() { + Transform3D camera_transform; + camera_transform.translate_local(cursor.pos); + camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); + camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); + camera_transform.translate_local(0, 0, cursor.distance); + + return camera_transform; +} + +void RuntimeNodeSelect::_reset_camera_3d() { + camera_first_override = true; + + Window *root = SceneTree::get_singleton()->get_root(); + Camera3D *camera = root->get_camera_3d(); + if (!camera) { + return; + } + + cursor = Cursor(); + Transform3D transform = camera->get_global_transform(); + transform.translate_local(0, 0, -cursor.distance); + cursor.pos = transform.origin; + + cursor.x_rot = -camera->get_global_rotation().x; + cursor.y_rot = -camera->get_global_rotation().y; + + cursor.fov_scale = CLAMP(camera->get_fov() / CAMERA_BASE_FOV, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE); + + SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform()); + SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR); +} +#endif // _3D_DISABLED #endif diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 70ea020c66..4429a00edc 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -33,19 +33,21 @@ #ifndef SCENE_DEBUGGER_H #define SCENE_DEBUGGER_H -#include "core/object/class_db.h" +#include "core/input/shortcut.h" #include "core/object/ref_counted.h" #include "core/string/ustring.h" #include "core/templates/pair.h" #include "core/variant/array.h" +#include "scene/gui/view_panner.h" +#include "scene/resources/mesh.h" +class PopupMenu; class Script; class Node; class SceneDebugger { -public: private: - static SceneDebugger *singleton; + inline static SceneDebugger *singleton = nullptr; SceneDebugger(); @@ -61,6 +63,7 @@ private: static void _set_node_owner_recursive(Node *p_node, Node *p_owner); static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20); + static void _next_frame(); public: static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); @@ -162,11 +165,161 @@ private: live_edit_root = NodePath("/root"); } - static LiveEditor *singleton; + inline static LiveEditor *singleton = nullptr; public: static LiveEditor *get_singleton(); }; + +class RuntimeNodeSelect : public Object { + GDCLASS(RuntimeNodeSelect, Object); + +public: + enum NodeType { + NODE_TYPE_NONE, + NODE_TYPE_2D, + NODE_TYPE_3D, + NODE_TYPE_MAX + }; + + enum SelectMode { + SELECT_MODE_SINGLE, + SELECT_MODE_LIST, + SELECT_MODE_MAX + }; + +private: + friend class SceneDebugger; + + struct SelectResult { + Node *item = nullptr; + real_t order = 0; + _FORCE_INLINE_ bool operator<(const SelectResult &p_rr) const { return p_rr.order < order; } + }; + + bool has_selection = false; + Node *selected_node = nullptr; + PopupMenu *selection_list = nullptr; + bool selection_visible = true; + bool selection_update_queued = false; + + bool camera_override = false; + + // Values taken from EditorZoomWidget. + const float VIEW_2D_MIN_ZOOM = 1.0 / 128; + const float VIEW_2D_MAX_ZOOM = 128; + + Ref<ViewPanner> panner; + Vector2 view_2d_offset; + real_t view_2d_zoom = 1.0; + + RID sbox_2d_canvas; + RID sbox_2d_ci; + Transform2D sbox_2d_xform; + Rect2 sbox_2d_rect; + +#ifndef _3D_DISABLED + struct Cursor { + Vector3 pos; + real_t x_rot, y_rot, distance, fov_scale; + Vector3 eye_pos; // Used in freelook mode. + + Cursor() { + // These rotations place the camera in +X +Y +Z, aka south east, facing north west. + x_rot = 0.5; + y_rot = -0.5; + distance = 4; + fov_scale = 1.0; + } + }; + Cursor cursor; + + // Values taken from Node3DEditor. + const float VIEW_3D_MIN_ZOOM = 0.01; +#ifdef REAL_T_IS_DOUBLE + const double VIEW_3D_MAX_ZOOM = 1'000'000'000'000; +#else + const float VIEW_3D_MAX_ZOOM = 10'000; +#endif + const float CAMERA_ZNEAR = 0.05; + const float CAMERA_ZFAR = 4'000; + + const float CAMERA_BASE_FOV = 75; + const float CAMERA_MIN_FOV_SCALE = 0.1; + const float CAMERA_MAX_FOV_SCALE = 2.5; + + const float FREELOOK_BASE_SPEED = 4; + const float RADS_PER_PIXEL = 0.004; + + bool camera_first_override = true; + bool camera_freelook = false; + + Vector2 previous_mouse_position; + + Ref<ArrayMesh> sbox_3d_mesh; + Ref<ArrayMesh> sbox_3d_mesh_xray; + RID sbox_3d_instance; + RID sbox_3d_instance_ofs; + RID sbox_3d_instance_xray; + RID sbox_3d_instance_xray_ofs; + Transform3D sbox_3d_xform; + AABB sbox_3d_bounds; +#endif + + Point2 selection_position = Point2(INFINITY, INFINITY); + bool list_shortcut_pressed = false; + + NodeType node_select_type = NODE_TYPE_2D; + SelectMode node_select_mode = SELECT_MODE_SINGLE; + + void _setup(); + + void _node_set_type(NodeType p_type); + void _select_set_mode(SelectMode p_mode); + + void _set_camera_override_enabled(bool p_enabled); + + void _root_window_input(const Ref<InputEvent> &p_event); + void _items_popup_index_pressed(int p_index, PopupMenu *p_popup); + void _update_input_state(); + + void _process_frame(); + void _physics_frame(); + + void _click_point(); + void _select_node(Node *p_node); + void _queue_selection_update(); + void _update_selection(); + void _clear_selection(); + void _set_selection_visible(bool p_visible); + + void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + void _reset_camera_2d(); + void _update_view_2d(); + +#ifndef _3D_DISABLED + void _find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items); + bool _handle_3d_input(const Ref<InputEvent> &p_event); + void _set_camera_freelook_enabled(bool p_enabled); + void _cursor_scale_distance(real_t p_scale); + void _cursor_look(Ref<InputEventWithModifiers> p_event); + void _cursor_pan(Ref<InputEventWithModifiers> p_event); + void _cursor_orbit(Ref<InputEventWithModifiers> p_event); + Transform3D _get_cursor_transform(); + void _reset_camera_3d(); +#endif + + RuntimeNodeSelect() { singleton = this; } + + inline static RuntimeNodeSelect *singleton = nullptr; + +public: + static RuntimeNodeSelect *get_singleton(); + + ~RuntimeNodeSelect(); +}; #endif #endif // SCENE_DEBUGGER_H diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 10db8c387b..da59dac3b9 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -1483,7 +1483,7 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 if (E) { text_rid = E->value; } else { - String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + String fc = String::num_int64(p_line + 1).lpad(line_number_digits, line_number_padding); if (is_localizing_numeral_system()) { fc = TS->format_number(fc); } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 46ff710a6f..692933bbb4 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -69,7 +69,18 @@ void FileDialog::_native_popup() { } else if (access == ACCESS_USERDATA) { root = OS::get_singleton()->get_user_data_dir(); } - DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) { + DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options)); + } else { + DisplayServer::get_singleton()->file_dialog_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb)); + } +} + +bool FileDialog::_can_use_native_popup() { + if (access == ACCESS_RESOURCES || access == ACCESS_USERDATA || options.size() > 0) { + return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA); + } + return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE); } void FileDialog::popup(const Rect2i &p_rect) { @@ -82,7 +93,7 @@ void FileDialog::popup(const Rect2i &p_rect) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { _native_popup(); } else { ConfirmationDialog::popup(p_rect); @@ -101,7 +112,7 @@ void FileDialog::set_visible(bool p_visible) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { if (p_visible) { _native_popup(); } @@ -110,7 +121,11 @@ void FileDialog::set_visible(bool p_visible) { } } -void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) { +void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) { + _native_dialog_cb_with_options(p_ok, p_files, p_filter, Dictionary()); +} + +void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) { if (!p_ok) { file->set_text(""); emit_signal(SNAME("canceled")); @@ -184,7 +199,7 @@ void FileDialog::_notification(int p_what) { #endif // Replace the built-in dialog with the native one if it started visible. - if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { ConfirmationDialog::set_visible(false); _native_popup(); } @@ -1489,7 +1504,7 @@ void FileDialog::set_use_native_dialog(bool p_native) { #endif // Replace the built-in dialog with the native one if it's currently visible. - if (is_inside_tree() && is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (is_inside_tree() && is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { ConfirmationDialog::set_visible(false); _native_popup(); } diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 98164263bc..84a3334503 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -190,8 +190,10 @@ private: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + bool _can_use_native_popup(); void _native_popup(); - void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options); + void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter); + void _native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options); bool _is_open_should_be_disabled(); diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index e35af24b34..ffa703d799 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -180,6 +180,7 @@ void VideoStreamPlayer::_notification(int p_notification) { draw_texture_rect(texture, Rect2(Point2(), s), false); } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (is_playing() && !is_paused()) { paused_from_tree = true; @@ -191,6 +192,13 @@ void VideoStreamPlayer::_notification(int p_notification) { } } break; + case NOTIFICATION_UNSUSPENDED: { + if (get_tree()->is_paused()) { + break; + } + [[fallthrough]]; + } + case NOTIFICATION_UNPAUSED: { if (paused_from_tree) { paused_from_tree = false; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 8d0a3f831f..872757ba70 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -186,6 +186,7 @@ void Node::_notification(int p_notification) { } } break; + case NOTIFICATION_SUSPENDED: case NOTIFICATION_PAUSED: { if (is_physics_interpolated_and_enabled() && is_inside_tree()) { reset_physics_interpolation(); @@ -701,6 +702,16 @@ void Node::_propagate_pause_notification(bool p_enable) { data.blocked--; } +void Node::_propagate_suspend_notification(bool p_enable) { + notification(p_enable ? NOTIFICATION_SUSPENDED : NOTIFICATION_UNSUSPENDED); + + data.blocked++; + for (KeyValue<StringName, Node *> &KV : data.children) { + KV.value->_propagate_suspend_notification(p_enable); + } + data.blocked--; +} + Node::ProcessMode Node::get_process_mode() const { return data.process_mode; } @@ -856,7 +867,7 @@ bool Node::can_process_notification(int p_what) const { bool Node::can_process() const { ERR_FAIL_COND_V(!is_inside_tree(), false); - return _can_process(get_tree()->is_paused()); + return !get_tree()->is_suspended() && _can_process(get_tree()->is_paused()); } bool Node::_can_process(bool p_paused) const { diff --git a/scene/main/node.h b/scene/main/node.h index 33c42c487e..8b42e9ec6e 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -303,6 +303,7 @@ private: void _set_tree(SceneTree *p_tree); void _propagate_pause_notification(bool p_enable); + void _propagate_suspend_notification(bool p_enable); _FORCE_INLINE_ bool _can_process(bool p_paused) const; _FORCE_INLINE_ bool _is_enabled() const; @@ -445,6 +446,8 @@ public: // Editor specific node notifications NOTIFICATION_EDITOR_PRE_SAVE = 9001, NOTIFICATION_EDITOR_POST_SAVE = 9002, + NOTIFICATION_SUSPENDED = 9003, + NOTIFICATION_UNSUSPENDED = 9004 }; /* NODE/TREE */ diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 675c91c766..5d6aa13a9b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -956,11 +956,14 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() { void SceneTree::set_pause(bool p_enabled) { ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Pause can only be set from the main thread."); + ERR_FAIL_COND_MSG(suspended, "Pause state cannot be modified while suspended."); if (p_enabled == paused) { return; } + paused = p_enabled; + #ifndef _3D_DISABLED PhysicsServer3D::get_singleton()->set_active(!p_enabled); #endif // _3D_DISABLED @@ -974,6 +977,30 @@ bool SceneTree::is_paused() const { return paused; } +void SceneTree::set_suspend(bool p_enabled) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Suspend can only be set from the main thread."); + + if (p_enabled == suspended) { + return; + } + + suspended = p_enabled; + + Engine::get_singleton()->set_freeze_time_scale(p_enabled); + +#ifndef _3D_DISABLED + PhysicsServer3D::get_singleton()->set_active(!p_enabled && !paused); +#endif // _3D_DISABLED + PhysicsServer2D::get_singleton()->set_active(!p_enabled && !paused); + if (get_root()) { + get_root()->_propagate_suspend_notification(p_enabled); + } +} + +bool SceneTree::is_suspended() const { + return suspended; +} + void SceneTree::_process_group(ProcessGroup *p_group, bool p_physics) { // When reading this function, keep in mind that this code must work in a way where // if any node is removed, this needs to continue working. diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index d389ddd378..6fbdd4e234 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -145,6 +145,7 @@ private: bool debug_navigation_hint = false; #endif bool paused = false; + bool suspended = false; HashMap<StringName, Group> group_map; bool _quit = false; @@ -346,6 +347,8 @@ public: void set_pause(bool p_enabled); bool is_paused() const; + void set_suspend(bool p_enabled); + bool is_suspended() const; #ifdef DEBUG_ENABLED void set_debug_collisions_hint(bool p_enabled); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 805cbf97d6..9734b1c260 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -3130,7 +3130,7 @@ void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ERR_FAIL_COND(!is_inside_tree()); ERR_FAIL_COND(p_event.is_null()); - if (disable_input) { + if (disable_input || disable_input_override) { return; } @@ -3202,7 +3202,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local local_input_handled = false; - if (disable_input || !_can_consume_input_events()) { + if (disable_input || disable_input_override || !_can_consume_input_events()) { return; } @@ -3305,7 +3305,7 @@ void Viewport::set_disable_input(bool p_disable) { if (p_disable == disable_input) { return; } - if (p_disable) { + if (p_disable && !disable_input_override) { _drop_mouse_focus(); _mouse_leave_viewport(); _gui_cancel_tooltip(); @@ -3318,6 +3318,19 @@ bool Viewport::is_input_disabled() const { return disable_input; } +void Viewport::set_disable_input_override(bool p_disable) { + ERR_MAIN_THREAD_GUARD; + if (p_disable == disable_input_override) { + return; + } + if (p_disable && !disable_input) { + _drop_mouse_focus(); + _mouse_leave_viewport(); + _gui_cancel_tooltip(); + } + disable_input_override = p_disable; +} + Variant Viewport::gui_get_drag_data() const { ERR_READ_THREAD_GUARD_V(Variant()); return get_section_root_viewport()->gui.drag_data; @@ -4244,6 +4257,22 @@ void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, } } +HashMap<StringName, real_t> Viewport::get_camera_3d_override_properties() const { + HashMap<StringName, real_t> props; + + props["size"] = 0; + props["fov"] = 0; + props["z_near"] = 0; + props["z_far"] = 0; + ERR_READ_THREAD_GUARD_V(props); + + props["size"] = camera_3d_override.size; + props["fov"] = camera_3d_override.fov; + props["z_near"] = camera_3d_override.z_near; + props["z_far"] = camera_3d_override.z_far; + return props; +} + void Viewport::set_disable_3d(bool p_disable) { ERR_MAIN_THREAD_GUARD; disable_3d = p_disable; @@ -4277,6 +4306,54 @@ Transform3D Viewport::get_camera_3d_override_transform() const { return Transform3D(); } +Vector3 Viewport::camera_3d_override_project_ray_normal(const Point2 &p_pos) const { + ERR_READ_THREAD_GUARD_V(Vector3()); + Vector3 ray = camera_3d_override_project_local_ray_normal(p_pos); + return camera_3d_override.transform.basis.xform(ray).normalized(); +} + +Vector3 Viewport::camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const { + ERR_READ_THREAD_GUARD_V(Vector3()); + Size2 viewport_size = get_camera_rect_size(); + Vector2 cpos = get_camera_coords(p_pos); + Vector3 ray; + + if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) { + ray = Vector3(0, 0, -1); + } else { + Projection cm; + cm.set_perspective(camera_3d_override.fov, get_visible_rect().size.aspect(), camera_3d_override.z_near, camera_3d_override.z_far, false); + + Vector2 screen_he = cm.get_viewport_half_extents(); + ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -camera_3d_override.z_near).normalized(); + } + + return ray; +} + +Vector3 Viewport::camera_3d_override_project_ray_origin(const Point2 &p_pos) const { + ERR_READ_THREAD_GUARD_V(Vector3()); + Size2 viewport_size = get_camera_rect_size(); + Vector2 cpos = get_camera_coords(p_pos); + ERR_FAIL_COND_V(viewport_size.y == 0, Vector3()); + + if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) { + Vector2 pos = cpos / viewport_size; + real_t vsize, hsize; + hsize = camera_3d_override.size * viewport_size.aspect(); + vsize = camera_3d_override.size; + + Vector3 ray; + ray.x = pos.x * (hsize)-hsize / 2; + ray.y = (1.0 - pos.y) * (vsize)-vsize / 2; + ray.z = -camera_3d_override.z_near; + ray = camera_3d_override.transform.xform(ray); + return ray; + } else { + return camera_3d_override.transform.origin; + }; +} + Ref<World3D> Viewport::get_world_3d() const { ERR_READ_THREAD_GUARD_V(Ref<World3D>()); return world_3d; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index d86ba9d826..31fbefe841 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -404,6 +404,7 @@ private: DefaultCanvasItemTextureRepeat default_canvas_item_texture_repeat = DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED; bool disable_input = false; + bool disable_input_override = false; void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); void _gui_call_notification(Control *p_control, int p_what); @@ -583,6 +584,8 @@ public: void set_disable_input(bool p_disable); bool is_input_disabled() const; + void set_disable_input_override(bool p_disable); + Vector2 get_mouse_position() const; void warp_mouse(const Vector2 &p_position); virtual void update_mouse_cursor_state(); @@ -773,6 +776,11 @@ public: void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far); void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far); + HashMap<StringName, real_t> get_camera_3d_override_properties() const; + + Vector3 camera_3d_override_project_ray_normal(const Point2 &p_pos) const; + Vector3 camera_3d_override_project_ray_origin(const Point2 &p_pos) const; + Vector3 camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const; void set_disable_3d(bool p_disable); bool is_3d_disabled() const; diff --git a/scene/resources/3d/convex_polygon_shape_3d.cpp b/scene/resources/3d/convex_polygon_shape_3d.cpp index dd52586b09..3e6db206ff 100644 --- a/scene/resources/3d/convex_polygon_shape_3d.cpp +++ b/scene/resources/3d/convex_polygon_shape_3d.cpp @@ -37,7 +37,7 @@ Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { Vector<Vector3> poly_points = get_points(); - if (poly_points.size() > 3) { + if (poly_points.size() > 1) { // Need at least 2 points for a line. Vector<Vector3> varr = Variant(poly_points); Geometry3D::MeshData md; Error err = ConvexHullComputer::convex_hull(varr, md); diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index 5badb7c61f..1658eeabef 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -35,108 +35,10 @@ #include "core/io/marshalls.h" #include "core/math/convex_hull.h" #include "core/math/random_pcg.h" -#include "core/math/static_raycaster.h" -#include "scene/resources/animation_library.h" #include "scene/resources/surface_tool.h" #include <cstdint> -void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) { - _split_normals(arrays, p_indices, p_normals); - - for (BlendShape &blend_shape : blend_shape_data) { - _split_normals(blend_shape.arrays, p_indices, p_normals); - } -} - -void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) { - ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX); - - const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX]; - int current_vertex_count = vertices.size(); - int new_vertex_count = p_indices.size(); - int final_vertex_count = current_vertex_count + new_vertex_count; - const int *indices_ptr = p_indices.ptr(); - - for (int i = 0; i < r_arrays.size(); i++) { - if (i == RS::ARRAY_INDEX) { - continue; - } - - if (r_arrays[i].get_type() == Variant::NIL) { - continue; - } - - switch (r_arrays[i].get_type()) { - case Variant::PACKED_VECTOR3_ARRAY: { - PackedVector3Array data = r_arrays[i]; - data.resize(final_vertex_count); - Vector3 *data_ptr = data.ptrw(); - if (i == RS::ARRAY_NORMAL) { - const Vector3 *normals_ptr = p_normals.ptr(); - memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count); - } else { - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - } - r_arrays[i] = data; - } break; - case Variant::PACKED_VECTOR2_ARRAY: { - PackedVector2Array data = r_arrays[i]; - data.resize(final_vertex_count); - Vector2 *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - r_arrays[i] = data; - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - PackedFloat32Array data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - float *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_INT32_ARRAY: { - PackedInt32Array data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - int32_t *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_BYTE_ARRAY: { - PackedByteArray data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - uint8_t *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_COLOR_ARRAY: { - PackedColorArray data = r_arrays[i]; - data.resize(final_vertex_count); - Color *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - r_arrays[i] = data; - } break; - default: { - ERR_FAIL_MSG("Unhandled array type."); - } break; - } - } -} - String ImporterMesh::validate_blend_shape_name(const String &p_name) { String name = p_name; const char *characters = ":"; @@ -308,7 +210,7 @@ void ImporterMesh::optimize_indices_for_cache() { } \ write_array[vert_idx] = transformed_vert; -void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) { +void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transform_array) { if (!SurfaceTool::simplify_scale_func) { return; } @@ -381,8 +283,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle)); - float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f))); - float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle)); const Vector3 *normals_ptr = normals.ptr(); HashMap<Vector3, LocalVector<Pair<int, int>>> unique_vertices; @@ -471,22 +371,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli unsigned int index_target = 12; // Start with the smallest target, 4 triangles unsigned int last_index_count = 0; - // Only used for normal raycasting - int split_vertex_count = vertex_count; - LocalVector<Vector3> split_vertex_normals; - LocalVector<int> split_vertex_indices; - split_vertex_normals.reserve(index_count / 3); - split_vertex_indices.reserve(index_count / 3); - - RandomPCG pcg; - pcg.seed(123456789); // Keep seed constant across imports - - Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>(); - if (raycaster.is_valid()) { - raycaster->add_mesh(vertices, indices, 0); - raycaster->commit(); - } - const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target float mesh_error = 0.0f; @@ -536,173 +420,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } - if (raycaster.is_valid()) { - LocalVector<LocalVector<int>> vertex_corners; - vertex_corners.resize(vertex_count); - - int *ptrw = new_indices.ptrw(); - for (unsigned int j = 0; j < new_index_count; j++) { - vertex_corners[ptrw[j]].push_back(j); - } - - float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); - const float ray_bias = 0.05; - float ray_length = ray_bias + mesh_error * scale * 3.0f; - - Vector<StaticRaycaster::Ray> rays; - LocalVector<Vector2> ray_uvs; - - int32_t *new_indices_ptr = new_indices.ptrw(); - - int current_ray_count = 0; - for (unsigned int j = 0; j < new_index_count; j += 3) { - const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]]; - const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]]; - const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]]; - Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1); - float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care - if (!Math::is_finite(face_area) || face_area == 0) { - WARN_PRINT_ONCE("Ignoring face with non-finite normal in LOD generation."); - continue; - } - - Vector3 dir = face_normal / face_area; - int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64); - - rays.resize(current_ray_count + ray_count); - StaticRaycaster::Ray *rays_ptr = rays.ptrw(); - - ray_uvs.resize(current_ray_count + ray_count); - Vector2 *ray_uvs_ptr = ray_uvs.ptr(); - - for (int k = 0; k < ray_count; k++) { - float u = pcg.randf(); - float v = pcg.randf(); - - if (u + v >= 1.0f) { - u = 1.0f - u; - v = 1.0f - v; - } - - u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge - v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third; - float w = 1.0f - u - v; - - Vector3 org = v0 * w + v1 * u + v2 * v; - org -= dir * ray_bias; - rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length); - rays_ptr[current_ray_count + k].id = j / 3; - ray_uvs_ptr[current_ray_count + k] = Vector2(u, v); - } - - current_ray_count += ray_count; - } - - raycaster->intersect(rays); - - LocalVector<Vector3> ray_normals; - LocalVector<real_t> ray_normal_weights; - - ray_normals.resize(new_index_count); - ray_normal_weights.resize(new_index_count); - - for (unsigned int j = 0; j < new_index_count; j++) { - ray_normal_weights[j] = 0.0f; - } - - const StaticRaycaster::Ray *rp = rays.ptr(); - for (int j = 0; j < rays.size(); j++) { - if (rp[j].geomID != 0) { // Ray missed - continue; - } - - if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face. - continue; - } - - const float &u = rp[j].u; - const float &v = rp[j].v; - const float w = 1.0f - u - v; - - const unsigned int &hit_tri_id = rp[j].primID; - const unsigned int &orig_tri_id = rp[j].id; - - const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]]; - const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]]; - const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]]; - Vector3 normal = n0 * w + n1 * u + n2 * v; - - Vector2 orig_uv = ray_uvs[j]; - const real_t orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y }; - for (int k = 0; k < 3; k++) { - int idx = orig_tri_id * 3 + k; - real_t weight = orig_bary[k]; - ray_normals[idx] += normal * weight; - ray_normal_weights[idx] += weight; - } - } - - for (unsigned int j = 0; j < new_index_count; j++) { - if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess - ray_normals[j] = Vector3(); - } else { - ray_normals[j] /= ray_normal_weights[j]; - } - } - - LocalVector<LocalVector<int>> normal_group_indices; - LocalVector<Vector3> normal_group_averages; - normal_group_indices.reserve(24); - normal_group_averages.reserve(24); - - for (unsigned int j = 0; j < vertex_count; j++) { - const LocalVector<int> &corners = vertex_corners[j]; - const Vector3 &vertex_normal = normals_ptr[j]; - - for (const int &corner_idx : corners) { - const Vector3 &ray_normal = ray_normals[corner_idx]; - - if (ray_normal.length_squared() < CMP_EPSILON2) { - continue; - } - - bool found = false; - for (unsigned int l = 0; l < normal_group_indices.size(); l++) { - LocalVector<int> &group_indices = normal_group_indices[l]; - Vector3 n = normal_group_averages[l] / group_indices.size(); - if (n.dot(ray_normal) > normal_pre_split_threshold) { - found = true; - group_indices.push_back(corner_idx); - normal_group_averages[l] += ray_normal; - break; - } - } - - if (!found) { - normal_group_indices.push_back({ corner_idx }); - normal_group_averages.push_back(ray_normal); - } - } - - for (unsigned int k = 0; k < normal_group_indices.size(); k++) { - LocalVector<int> &group_indices = normal_group_indices[k]; - Vector3 n = normal_group_averages[k] / group_indices.size(); - - if (vertex_normal.dot(n) < normal_split_threshold) { - split_vertex_indices.push_back(j); - split_vertex_normals.push_back(n); - int new_idx = split_vertex_count++; - for (const int &index : group_indices) { - new_indices_ptr[index] = new_idx; - } - } - } - - normal_group_indices.clear(); - normal_group_averages.clear(); - } - } - Surface::LOD lod; lod.distance = MAX(mesh_error * scale, CMP_EPSILON2); lod.indices = new_indices; @@ -715,22 +432,19 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } - if (raycaster.is_valid()) { - surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); - } - surfaces.write[i].lods.sort_custom<Surface::LODComparator>(); for (int j = 0; j < surfaces.write[i].lods.size(); j++) { Surface::LOD &lod = surfaces.write[i].lods.write[j]; unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw(); - SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count); + SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), vertex_count); } } } void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) { - generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array); + // p_normal_split_angle is unused, but kept for compatibility + generate_lods(p_normal_merge_angle, p_skin_pose_transform_array); } bool ImporterMesh::has_mesh() const { diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h index fec474afb7..b4f3a03d27 100644 --- a/scene/resources/3d/importer_mesh.h +++ b/scene/resources/3d/importer_mesh.h @@ -70,9 +70,6 @@ class ImporterMesh : public Resource { return l.distance < r.distance; } }; - - void split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals); - static void _split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals); }; Vector<Surface> surfaces; Vector<String> blend_shapes; @@ -120,7 +117,7 @@ public: void optimize_indices_for_cache(); - void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false); + void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array); void create_shadow_mesh(); Ref<ImporterMesh> get_shadow_mesh() const; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 86b01dae51..9f04ff6761 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1562,7 +1562,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; + shader_code += " COLOR = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } break; default: { shader_code += " COLOR.rgb = vec3(0.0);\n"; |