diff options
34 files changed, 532 insertions, 83 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 4fd6ab9028..d3cbae2f29 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1430,6 +1430,9 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); + GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true); + GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres"); GLOBAL_DEF_RST("audio/general/text_to_speech", false); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 27404b68bb..a6f85e7fe5 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -309,6 +309,10 @@ <member name="tangential_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0"> Minimum tangent acceleration. </member> + <member name="visibility_aabb" type="AABB" setter="set_visibility_aabb" getter="get_visibility_aabb" default="AABB(-4, -4, -4, 8, 8, 8)"> + The [AABB] that determines the node's region which needs to be visible on screen for the particle system to be active. + Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/b] editor tool. + </member> </members> <signals> <signal name="finished"> diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 5539213619..cd6397137b 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -92,7 +92,11 @@ </member> <member name="color_array" type="PackedColorArray" setter="_set_color_array" getter="_get_color_array" deprecated="Use [method set_instance_color] instead."> </member> + <member name="custom_aabb" type="AABB" setter="set_custom_aabb" getter="get_custom_aabb" default="AABB(0, 0, 0, 0, 0, 0)"> + Custom AABB for this MultiMesh resource. Setting this manually prevents costly runtime AABB recalculations. + </member> <member name="custom_data_array" type="PackedColorArray" setter="_set_custom_data_array" getter="_get_custom_data_array" deprecated="Use [method set_instance_custom_data] instead."> + See [method set_instance_custom_data]. </member> <member name="instance_count" type="int" setter="set_instance_count" getter="get_instance_count" default="0"> Number of instances that will get drawn. This clears and (re)sizes the buffers. Setting data format or flags afterwards will have no effect. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index f8e91c94a0..2df34b0488 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -238,6 +238,12 @@ </method> </methods> <members> + <member name="animation/warnings/check_angle_interpolation_type_conflicting" type="bool" setter="" getter="" default="true"> + If [code]true[/code], [AnimationMixer] prints the warning of interpolation being forced to choose the shortest rotation path due to multiple angle interpolation types being mixed in the [AnimationMixer] cache. + </member> + <member name="animation/warnings/check_invalid_track_paths" type="bool" setter="" getter="" default="true"> + If [code]true[/code], [AnimationMixer] prints the warning of no matching object of the track path in the scene. + </member> <member name="application/boot_splash/bg_color" type="Color" setter="" getter="" default="Color(0.14, 0.14, 0.14, 1)"> Background color for the boot splash. </member> diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml index 73e594218d..d3875ddfc2 100644 --- a/doc/classes/PropertyTweener.xml +++ b/doc/classes/PropertyTweener.xml @@ -43,6 +43,25 @@ [/codeblock] </description> </method> + <method name="set_custom_interpolator"> + <return type="PropertyTweener" /> + <param index="0" name="interpolator_method" type="Callable" /> + <description> + Allows interpolating the value with a custom easing function. The provided [param interpolator_method] will be called with a value ranging from [code]0.0[/code] to [code]1.0[/code] and is expected to return a value within the same range (values outside the range can be used for overshoot). The return value of the method is then used for interpolation between initial and final value. Note that the parameter passed to the method is still subject to the tweener's own easing. + [b]Example:[/b] + [codeblock] + @export var curve: Curve + + func _ready(): + var tween = create_tween() + # Interpolate the value using a custom curve. + tween.tween_property(self, "position:x", 300, 1).as_relative().set_custom_interpolator(tween_curve) + + func tween_curve(v): + return curve.sample_baked(v) + [/codeblock] + </description> + </method> <method name="set_delay"> <return type="PropertyTweener" /> <param index="0" name="delay" type="float" /> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 4597e50f37..54a11af629 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -2332,6 +2332,13 @@ [b]Note:[/b] If the buffer is in the engine's internal cache, it will have to be fetched from GPU memory and possibly decompressed. This means [method multimesh_get_buffer] is potentially a slow operation and should be avoided whenever possible. </description> </method> + <method name="multimesh_get_custom_aabb" qualifiers="const"> + <return type="AABB" /> + <param index="0" name="multimesh" type="RID" /> + <description> + Returns the custom AABB defined for this MultiMesh resource. + </description> + </method> <method name="multimesh_get_instance_count" qualifiers="const"> <return type="int" /> <param index="0" name="multimesh" type="RID" /> @@ -2442,6 +2449,14 @@ [/codeblock] </description> </method> + <method name="multimesh_set_custom_aabb"> + <return type="void" /> + <param index="0" name="multimesh" type="RID" /> + <param index="1" name="aabb" type="AABB" /> + <description> + Sets the custom AABB for this MultiMesh resource. + </description> + </method> <method name="multimesh_set_mesh"> <return type="void" /> <param index="0" name="multimesh" type="RID" /> diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 928e4e23ce..6c4bef10d5 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1607,6 +1607,9 @@ void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, b void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances) { ERR_FAIL_COND(multimesh->mesh.is_null()); + if (multimesh->custom_aabb != AABB()) { + return; + } AABB aabb; AABB mesh_aabb = mesh_get_aabb(multimesh->mesh); for (int i = 0; i < p_instances; i++) { @@ -1749,9 +1752,25 @@ RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const { return multimesh->mesh; } +void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL(multimesh); + multimesh->custom_aabb = p_aabb; + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); +} + +AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, AABB()); + return multimesh->custom_aabb; +} + AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL_V(multimesh, AABB()); + if (multimesh->custom_aabb != AABB()) { + return multimesh->custom_aabb; + } if (multimesh->aabb_dirty) { const_cast<MeshStorage *>(this)->_update_dirty_multimeshes(); } @@ -1943,8 +1962,10 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - _multimesh_re_create_aabb(multimesh, data, multimesh->instances); - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, multimesh->instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } @@ -2091,9 +2112,11 @@ void MeshStorage::_update_dirty_multimeshes() { } if (multimesh->aabb_dirty && multimesh->mesh.is_valid()) { - _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->aabb_dirty = false; - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, visible_instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index cea81baa0b..d246e7725c 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -189,6 +189,7 @@ struct MultiMesh { bool uses_custom_data = false; int visible_instances = -1; AABB aabb; + AABB custom_aabb; bool aabb_dirty = false; bool buffer_set = false; uint32_t stride_cache = 0; @@ -505,6 +506,8 @@ public: virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override; virtual RID multimesh_get_mesh(RID p_multimesh) const override; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override; virtual AABB multimesh_get_aabb(RID p_multimesh) const override; virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 1ff403291f..b7757eb435 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -77,10 +77,10 @@ void AnimationTrackKeyEdit::_fix_node_path(Variant &value) { Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -601,8 +601,8 @@ void AnimationTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const case Animation::TYPE_ANIMATION: { String animations; - if (root_path && root_path->has_node(animation->track_get_path(track))) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(track))); if (ap) { List<StringName> anims; ap->get_animation_list(&anims); @@ -663,10 +663,10 @@ void AnimationMultiTrackKeyEdit::_fix_node_path(Variant &value, NodePath &base) Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - Node *np_node = root->get_node(np); + Node *np_node = root->get_node_or_null(np); ERR_FAIL_NULL(np_node); - Node *edited_node = root->get_node(base); + Node *edited_node = root->get_node_or_null(base); ERR_FAIL_NULL(edited_node); value = edited_node->get_path_to(np_node); @@ -1207,8 +1207,8 @@ void AnimationMultiTrackKeyEdit::_get_property_list(List<PropertyInfo> *p_list) String animations; - if (root_path && root_path->has_node(animation->track_get_path(first_track))) { - AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(first_track))); + if (root_path) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node_or_null(animation->track_get_path(first_track))); if (ap) { List<StringName> anims; ap->get_animation_list(&anims); @@ -1940,8 +1940,8 @@ void AnimationTrackEdit::_notification(int p_what) { NodePath anim_path = animation->track_get_path(track); Node *node = nullptr; - if (root && root->has_node(anim_path)) { - node = root->get_node(anim_path); + if (root) { + node = root->get_node_or_null(anim_path); } String text; @@ -2481,10 +2481,9 @@ void AnimationTrackEdit::_path_submitted(const String &p_text) { } bool AnimationTrackEdit::_is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const { - if (root == nullptr) { + if (root == nullptr || !root->has_node_and_resource(animation->track_get_path(track))) { return false; } - Ref<Resource> res; Vector<StringName> leftover_path; Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path); @@ -2774,11 +2773,11 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { AnimationPlayer *ap = ape->get_player(); if (ap) { NodePath npath = animation->track_get_path(track); - Node *a_ap_root_node = ap->get_node(ap->get_root_node()); + Node *a_ap_root_node = ap->get_node_or_null(ap->get_root_node()); Node *nd = nullptr; // We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node. if (a_ap_root_node) { - nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names())); + nd = a_ap_root_node->get_node_or_null(NodePath(npath.get_concatenated_names())); } if (nd) { StringName prop = npath.get_concatenated_subnames(); @@ -3310,8 +3309,8 @@ void AnimationTrackEditGroup::_notification(int p_what) { int separation = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); Color color = get_theme_color(SNAME("font_color"), SNAME("Label")); - if (root && root->has_node(node)) { - Node *n = root->get_node(node); + if (root) { + Node *n = root->get_node_or_null(node); if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); } @@ -3353,7 +3352,10 @@ void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) { if (node_name_rect.has_point(pos)) { EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->clear(); - editor_selection->add_node(root->get_node(node)); + Node *n = root->get_node_or_null(node); + if (n) { + editor_selection->add_node(n); + } } } } @@ -4474,8 +4476,8 @@ void AnimationTrackEditor::_update_tracks() { if (use_filter) { NodePath path = animation->track_get_path(i); - if (root && root->has_node(path)) { - Node *node = root->get_node(path); + if (root) { + Node *node = root->get_node_or_null(path); if (!node) { continue; // No node, no filter. } @@ -4527,8 +4529,8 @@ void AnimationTrackEditor::_update_tracks() { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } if (node && Object::cast_to<AnimationPlayer>(node)) { @@ -4557,8 +4559,8 @@ void AnimationTrackEditor::_update_tracks() { Ref<Texture2D> icon = get_editor_theme_icon(SNAME("Node")); String name = base_path; String tooltip; - if (root && root->has_node(base_path)) { - Node *n = root->get_node(base_path); + if (root) { + Node *n = root->get_node_or_null(base_path); if (n) { icon = EditorNode::get_singleton()->get_object_icon(n, "Node"); name = n->get_name(); @@ -4808,7 +4810,7 @@ void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) { void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { ERR_FAIL_NULL(root); - Node *node = get_node(p_path); + Node *node = get_node_or_null(p_path); ERR_FAIL_NULL(node); NodePath path_to = root->get_path_to(node, true); @@ -5129,7 +5131,8 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(p_track)); + Node *base = root->get_node_or_null(animation->track_get_path(p_track)); + ERR_FAIL_NULL(base); method_selector->select_method_from_instance(base); @@ -5182,7 +5185,8 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); return; } - Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track)); + Node *base = root->get_node_or_null(animation->track_get_path(insert_key_from_track_call_track)); + ERR_FAIL_NULL(base); List<MethodInfo> minfo; base->get_method_list(&minfo); @@ -5963,8 +5967,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { NodePath path = animation->track_get_path(i); Node *node = nullptr; - if (root && root->has_node(path)) { - node = root->get_node(path); + if (root) { + node = root->get_node_or_null(path); } String text; @@ -6085,7 +6089,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { if (root) { NodePath np = track_clipboard[i].full_path; - exists = root->get_node(np); + exists = root->get_node_or_null(np); if (exists) { path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false); } @@ -6587,15 +6591,17 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { for (int i = 0; i < p_animation->get_track_count(); i++) { - bool prop_exists = false; - Variant::Type valid_type = Variant::NIL; - Object *obj = nullptr; - + if (!root->has_node_and_resource(p_animation->track_get_path(i))) { + continue; + } Ref<Resource> res; Vector<StringName> leftover_path; - Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); + bool prop_exists = false; + Variant::Type valid_type = Variant::NIL; + Object *obj = nullptr; + if (res.is_valid()) { obj = res.ptr(); } else if (node) { @@ -6772,9 +6778,9 @@ void AnimationTrackEditor::_pick_track_select_recursive(TreeItem *p_item, const } NodePath np = p_item->get_metadata(0); - Node *node = get_node(np); + Node *node = get_node_or_null(np); - if (!p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { + if (node && !p_filter.is_empty() && ((String)node->get_name()).findn(p_filter) != -1) { p_select_candidates.push_back(node); } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index dde7ddd32d..ac83460542 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -584,7 +584,7 @@ void FileSystemDock::_notification(int p_what) { file_list_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search"))); file_list_button_sort->set_icon(get_editor_theme_icon(SNAME("Sort"))); - button_dock_placement->set_icon(get_editor_theme_icon(SNAME("GuiTabMenu"))); + button_dock_placement->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); if (is_layout_rtl()) { button_hist_next->set_icon(get_editor_theme_icon(SNAME("Back"))); diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index 7e5fa70f3f..0fd980ff10 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -77,7 +77,60 @@ void CPUParticles3DEditor::_menu_option(int p_option) { ur->commit_action(false); } break; + case MENU_OPTION_GENERATE_AABB: { + // Add one second to the default generation lifetime, since the progress is updated every second. + generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0)); + + if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) { + // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it. + generate_aabb->popup_centered(); + } else { + // Generate the visibility AABB immediately. + _generate_aabb(); + } + } break; + } +} + +void CPUParticles3DEditor::_generate_aabb() { + double time = generate_seconds->get_value(); + + double running = 0.0; + + EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time)); + + bool was_emitting = node->is_emitting(); + if (!was_emitting) { + node->set_emitting(true); + OS::get_singleton()->delay_usec(1000); + } + + AABB rect; + + while (running < time) { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + ep.step("Generating...", int(running), true); + OS::get_singleton()->delay_usec(1000); + + AABB capture = node->capture_aabb(); + if (rect == AABB()) { + rect = capture; + } else { + rect.merge_with(capture); + } + + running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; + } + + if (!was_emitting) { + node->set_emitting(false); } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Generate Visibility AABB")); + ur->add_do_method(node, "set_visibility_aabb", rect); + ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb()); + ur->commit_action(); } void CPUParticles3DEditor::edit(CPUParticles3D *p_particles) { @@ -117,9 +170,24 @@ CPUParticles3DEditor::CPUParticles3DEditor() { options->set_text(TTR("CPUParticles3D")); options->get_popup()->add_item(TTR("Restart"), MENU_OPTION_RESTART); + options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB); options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES); options->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles3DEditor::_menu_option)); + + generate_aabb = memnew(ConfirmationDialog); + generate_aabb->set_title(TTR("Generate Visibility AABB")); + VBoxContainer *genvb = memnew(VBoxContainer); + generate_aabb->add_child(genvb); + generate_seconds = memnew(SpinBox); + genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds); + generate_seconds->set_min(0.1); + generate_seconds->set_max(25); + generate_seconds->set_value(2); + + add_child(generate_aabb); + + generate_aabb->connect("confirmed", callable_mp(this, &CPUParticles3DEditor::_generate_aabb)); } void CPUParticles3DEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h index 6de23fc2b8..99178b7fde 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ b/editor/plugins/cpu_particles_3d_editor_plugin.h @@ -38,14 +38,19 @@ class CPUParticles3DEditor : public GPUParticles3DEditorBase { GDCLASS(CPUParticles3DEditor, GPUParticles3DEditorBase); enum Menu { + MENU_OPTION_GENERATE_AABB, MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, MENU_OPTION_CLEAR_EMISSION_VOLUME, MENU_OPTION_RESTART, MENU_OPTION_CONVERT_TO_GPU_PARTICLES, }; + ConfirmationDialog *generate_aabb = nullptr; + SpinBox *generate_seconds = nullptr; CPUParticles3D *node = nullptr; + void _generate_aabb(); + void _menu_option(int); friend class CPUParticles3DEditorPlugin; diff --git a/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp index 3745b407a3..fe5d8e92d1 100644 --- a/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.cpp @@ -31,11 +31,16 @@ #include "cpu_particles_3d_gizmo_plugin.h" #include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/cpu_particles_3d.h" CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() { + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4)); + create_material("particles_material", gizmo_color); + gizmo_color.a = MAX((gizmo_color.a - 0.2) * 0.02, 0.0); + create_material("particles_solid_material", gizmo_color); create_icon_material("particles_icon", EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("GizmoCPUParticles3D"), EditorStringName(EditorIcons))); } @@ -56,6 +61,48 @@ bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const { } void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + Vector<Vector3> lines; + AABB aabb = particles->get_visibility_aabb(); + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + Vector<Vector3> handles; + + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = aabb.position[i] + aabb.size[i]; + ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5; + ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5; + handles.push_back(ax); + } + + Vector3 center = aabb.get_center(); + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = 1.0; + handles.push_back(center + ax); + lines.push_back(center); + lines.push_back(center + ax); + } + + Ref<Material> material = get_material("particles_material", p_gizmo); + + p_gizmo->add_lines(lines, material); + + if (p_gizmo->is_selected()) { + Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo); + p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_center()); + } + Ref<Material> icon = get_material("particles_icon", p_gizmo); p_gizmo->add_unscaled_billboard(icon, 0.05); } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 4e87dd42dd..85846335f7 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -127,14 +127,6 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { } } -int OS_LinuxBSD::get_low_processor_usage_mode_sleep_usec() const { - if (DisplayServer::get_singleton() == nullptr || DisplayServer::get_singleton()->get_name() != "Wayland" || is_in_low_processor_usage_mode()) { - return OS::get_low_processor_usage_mode_sleep_usec(); - } - - return 500; // Roughly 2000 FPS, improves frame time when emulating VSync. -} - void OS_LinuxBSD::initialize() { crash_handler.initialize(); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index d4ffc0e9b4..9084061eb9 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -127,8 +127,6 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual int get_low_processor_usage_mode_sleep_usec() const override; - virtual bool _check_internal_feature_support(const String &p_feature) override; void run(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 85bbfe546a..c957dea32d 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -867,11 +867,11 @@ bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const { } bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const { - return frame; + return !suspended; } bool DisplayServerWayland::can_any_window_draw() const { - return frame; + return !suspended; } void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) { @@ -1143,7 +1143,32 @@ void DisplayServerWayland::process_events() { wayland_thread.keyboard_echo_keys(); - frame = wayland_thread.get_reset_frame(); + if (!suspended) { + if (emulate_vsync) { + // Due to various reasons, we manually handle display synchronization by + // waiting for a frame event (request to draw) or, if available, the actual + // window's suspend status. When a window is suspended, we can avoid drawing + // altogether, either because the compositor told us that we don't need to or + // because the pace of the frame events became unreliable. + bool frame = wayland_thread.wait_frame_suspend_ms(1000); + if (!frame) { + suspended = true; + } + } else { + if (wayland_thread.is_suspended()) { + suspended = true; + } + } + + if (suspended) { + DEBUG_LOG_WAYLAND("Window suspended."); + } + } else { + if (wayland_thread.get_reset_frame()) { + // At last, a sign of life! We're no longer suspended. + suspended = false; + } + } wayland_thread.mutex.unlock(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index d4da80a55f..e42967eb7b 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -117,7 +117,7 @@ class DisplayServerWayland : public DisplayServer { Context context; - bool frame = false; + bool suspended = false; bool emulate_vsync = false; String rendering_driver; diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index ae1d96a3b1..ebb21722e2 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -469,7 +469,7 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re } if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(5, (int)version))); + registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(6, (int)version))); registry->xdg_wm_base_name = name; xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr); @@ -1063,9 +1063,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; uint32_t *state = nullptr; wl_array_for_each(state, states) { @@ -1078,6 +1079,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } break; + case XDG_TOPLEVEL_STATE_SUSPENDED: { + ws->suspended = true; + } break; + default: { // We don't care about the other states (for now). } break; @@ -1176,9 +1181,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE; - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; if (libdecor_configuration_get_window_state(configuration, &window_state)) { if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { @@ -1188,6 +1194,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } + + if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) { + ws->suspended = true; + } } window_state_update_size(ws, width, height); @@ -3872,6 +3882,102 @@ bool WaylandThread::get_reset_frame() { return old_frame; } +// Dispatches events until a frame event is received, a window is reported as +// suspended or the timeout expires. +bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { + if (main_window.suspended) { + // The window is suspended! The compositor is telling us _explicitly_ that we + // don't need to draw, without letting us guess through the frame event's + // timing and stuff like that. Our job here is done. + return false; + } + + if (frame) { + // We already have a frame! Probably it got there while the caller locked :D + frame = false; + return true; + } + + struct pollfd poll_fd; + poll_fd.fd = wl_display_get_fd(wl_display); + poll_fd.events = POLLIN | POLLHUP; + + int begin_ms = OS::get_singleton()->get_ticks_msec(); + int remaining_ms = p_timeout; + + while (remaining_ms > 0) { + // Empty the event queue while it's full. + while (wl_display_prepare_read(wl_display) != 0) { + if (wl_display_dispatch_pending(wl_display) == -1) { + // Oh no. We'll check and handle any display error below. + break; + } + + if (main_window.suspended) { + return false; + } + + if (frame) { + // We had a frame event in the queue :D + frame = false; + return true; + } + } + + int werror = wl_display_get_error(wl_display); + + if (werror) { + if (werror == EPROTO) { + struct wl_interface *wl_interface = nullptr; + uint32_t id = 0; + + int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id); + CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); + } else { + CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); + } + } + + wl_display_flush(wl_display); + + // Wait for the event file descriptor to have new data. + poll(&poll_fd, 1, remaining_ms); + + if (poll_fd.revents | POLLIN) { + // Load the queues with fresh new data. + wl_display_read_events(wl_display); + } else { + // Oh well... Stop signaling that we want to read. + wl_display_cancel_read(wl_display); + + // We've got no new events :( + // We won't even bother with checking the frame flag. + return false; + } + + // Let's try dispatching now... + wl_display_dispatch_pending(wl_display); + + if (main_window.suspended) { + return false; + } + + if (frame) { + frame = false; + return true; + } + + remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms; + } + + DEBUG_LOG_WAYLAND_THREAD("Frame timeout."); + return false; +} + +bool WaylandThread::is_suspended() const { + return main_window.suspended; +} + void WaylandThread::destroy() { if (!initialized) { return; diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 86033c1a09..f3e3c3a2ac 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -177,6 +177,7 @@ public: Rect2i rect; DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED; + bool suspended = false; // These are true by default as it isn't guaranteed that we'll find an // xdg-shell implementation with wm_capabilities available. If and once we @@ -939,6 +940,9 @@ public: void set_frame(); bool get_reset_frame(); + bool wait_frame_suspend_ms(int p_timeout); + + bool is_suspended() const; Error init(); void destroy(); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index fa8599a0a2..32d68f7f78 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -101,6 +101,12 @@ void CPUParticles3D::set_randomness_ratio(real_t p_ratio) { randomness_ratio = p_ratio; } +void CPUParticles3D::set_visibility_aabb(const AABB &p_aabb) { + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, p_aabb); + visibility_aabb = p_aabb; + update_gizmos(); +} + void CPUParticles3D::set_lifetime_randomness(double p_random) { lifetime_randomness = p_random; } @@ -141,6 +147,10 @@ real_t CPUParticles3D::get_randomness_ratio() const { return randomness_ratio; } +AABB CPUParticles3D::get_visibility_aabb() const { + return visibility_aabb; +} + double CPUParticles3D::get_lifetime_randomness() const { return lifetime_randomness; } @@ -520,6 +530,11 @@ bool CPUParticles3D::get_split_scale() { return split_scale; } +AABB CPUParticles3D::capture_aabb() const { + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, AABB()); + return RS::get_singleton()->multimesh_get_aabb(multimesh); +} + void CPUParticles3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "emission_sphere_radius" && (emission_shape != EMISSION_SHAPE_SPHERE && emission_shape != EMISSION_SHAPE_SPHERE_SURFACE)) { p_property.usage = PROPERTY_USAGE_NONE; @@ -1341,6 +1356,7 @@ void CPUParticles3D::convert_from_particles(Node *p_particles) { set_pre_process_time(gpu_particles->get_pre_process_time()); set_explosiveness_ratio(gpu_particles->get_explosiveness_ratio()); set_randomness_ratio(gpu_particles->get_randomness_ratio()); + set_visibility_aabb(gpu_particles->get_visibility_aabb()); set_use_local_coordinates(gpu_particles->get_use_local_coordinates()); set_fixed_fps(gpu_particles->get_fixed_fps()); set_fractional_delta(gpu_particles->get_fractional_delta()); @@ -1420,6 +1436,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles3D::set_pre_process_time); ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles3D::set_explosiveness_ratio); ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles3D::set_randomness_ratio); + ClassDB::bind_method(D_METHOD("set_visibility_aabb", "aabb"), &CPUParticles3D::set_visibility_aabb); ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "random"), &CPUParticles3D::set_lifetime_randomness); ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles3D::set_use_local_coordinates); ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles3D::set_fixed_fps); @@ -1433,6 +1450,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles3D::get_pre_process_time); ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles3D::get_explosiveness_ratio); ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles3D::get_randomness_ratio); + ClassDB::bind_method(D_METHOD("get_visibility_aabb"), &CPUParticles3D::get_visibility_aabb); ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &CPUParticles3D::get_lifetime_randomness); ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles3D::get_use_local_coordinates); ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles3D::get_fixed_fps); @@ -1461,6 +1479,7 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "visibility_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_visibility_aabb", "get_visibility_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,View Depth"), "set_draw_order", "get_draw_order"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); @@ -1665,6 +1684,7 @@ CPUParticles3D::CPUParticles3D() { set_emitting(true); set_amount(8); + set_visibility_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8))); set_param_min(PARAM_INITIAL_LINEAR_VELOCITY, 0); set_param_min(PARAM_ANGULAR_VELOCITY, 0); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index e05bd65fba..e9b75d9140 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -138,6 +138,7 @@ private: real_t randomness_ratio = 0.0; double lifetime_randomness = 0.0; double speed_scale = 1.0; + AABB visibility_aabb; bool local_coords = false; int fixed_fps = 0; bool fractional_delta = true; @@ -210,6 +211,7 @@ public: void set_pre_process_time(double p_time); void set_explosiveness_ratio(real_t p_ratio); void set_randomness_ratio(real_t p_ratio); + void set_visibility_aabb(const AABB &p_aabb); void set_lifetime_randomness(double p_random); void set_use_local_coordinates(bool p_enable); void set_speed_scale(double p_scale); @@ -221,6 +223,7 @@ public: double get_pre_process_time() const; real_t get_explosiveness_ratio() const; real_t get_randomness_ratio() const; + AABB get_visibility_aabb() const; double get_lifetime_randomness() const; bool get_use_local_coordinates() const; double get_speed_scale() const; @@ -308,6 +311,8 @@ public: void convert_from_particles(Node *p_particles); + AABB capture_aabb() const; + CPUParticles3D(); ~CPUParticles3D(); }; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 9f954fd6c0..2f99bdda40 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -32,6 +32,7 @@ #include "animation_mixer.compat.inc" #include "core/config/engine.h" +#include "core/config/project_settings.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" @@ -597,6 +598,9 @@ bool AnimationMixer::_update_caches() { List<StringName> sname; get_animation_list(&sname); + bool check_path = GLOBAL_GET("animation/warnings/check_invalid_track_paths"); + bool check_angle_interpolation = GLOBAL_GET("animation/warnings/check_angle_interpolation_type_conflicting"); + Node *parent = get_node_or_null(root_node); if (!parent) { cache_valid = false; @@ -645,10 +649,19 @@ bool AnimationMixer::_update_caches() { if (!track) { Ref<Resource> resource; Vector<StringName> leftover_path; - Node *child = parent->get_node_and_resource(path, resource, leftover_path); + if (!parent->has_node_and_resource(path)) { + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } + continue; + } + + Node *child = parent->get_node_and_resource(path, resource, leftover_path); if (!child) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'."); + if (check_path) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', couldn't resolve track: '" + String(path) + "'. This warning can be disabled in Project Settings."); + } continue; } @@ -657,7 +670,7 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_VALUE: { // If a value track without a key is cached first, the initial value cannot be determined. // It is a corner case, but which may cause problems with blending. - ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); + ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); TrackCacheValue *track_value = memnew(TrackCacheValue); @@ -698,7 +711,7 @@ bool AnimationMixer::_update_caches() { Node3D *node_3d = Object::cast_to<Node3D>(child); if (!node_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); continue; } @@ -764,20 +777,20 @@ bool AnimationMixer::_update_caches() { case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED if (path.get_subname_count() != 1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); continue; } MeshInstance3D *mesh_3d = Object::cast_to<MeshInstance3D>(child); if (!mesh_3d) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); continue; } StringName blend_shape_name = path.get_subname(0); int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); if (blend_shape_idx == -1) { - ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); + ERR_PRINT(mixer_name + ": '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); continue; } @@ -855,7 +868,6 @@ bool AnimationMixer::_update_caches() { } else if (track_cache_type == Animation::TYPE_VALUE) { // If it has at least one angle interpolation, it also uses angle interpolation for blending. TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); - bool was_continuous = track_value->is_continuous; bool was_using_angle = track_value->is_using_angle; if (track_src_type == Animation::TYPE_VALUE) { @@ -868,23 +880,17 @@ bool AnimationMixer::_update_caches() { // TODO: Currently, misc type cannot be blended. // In the future, it should have a separate blend weight, just as bool is converted to 0 and 1. // Then, it should provide the correct precedence value. - bool skip_update_mode_warning = false; if (track_value->is_continuous) { if (!Animation::is_variant_interpolatable(track_value->init_value)) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE."); track_value->is_continuous = false; - skip_update_mode_warning = true; } if (track_value->init_value.is_string()) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); } } - - if (!skip_update_mode_warning && was_continuous != track_value->is_continuous) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different update modes between some animations which may be blended together. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treats UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST."); - } - if (was_using_angle != track_value->is_using_angle) { - WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); + if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) { + WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value. If you do not want further warnings, you can turn off the checking for the angle interpolation type conflicting in Project Settings."); } } diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 34fc83500f..2e5fd6180c 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -536,6 +536,11 @@ Ref<PropertyTweener> PropertyTweener::set_ease(Tween::EaseType p_ease) { return this; } +Ref<PropertyTweener> PropertyTweener::set_custom_interpolator(const Callable &p_method) { + custom_method = p_method; + return this; +} + Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) { delay = p_delay; return this; @@ -589,7 +594,23 @@ bool PropertyTweener::step(double &r_delta) { double time = MIN(elapsed_time - delay, duration); if (time < duration) { - target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); + if (custom_method.is_valid()) { + const Variant t = tween->interpolate_variant(0.0, 1.0, time, duration, trans_type, ease_type); + const Variant *argptr = &t; + + Variant result; + Callable::CallError ce; + custom_method.callp(&argptr, 1, result, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_FAIL_V_MSG(false, "Error calling custom method from PropertyTweener: " + Variant::get_callable_error_text(custom_method, &argptr, 1, ce) + "."); + } else if (result.get_type() != Variant::FLOAT) { + ERR_FAIL_V_MSG(false, vformat("Wrong return type in PropertyTweener custom method. Expected float, got %s.", Variant::get_type_name(result.get_type()))); + } + + target_instance->set_indexed(property, Animation::interpolate_variant(initial_val, final_val, result)); + } else { + target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); + } r_delta = 0; return true; } else { @@ -617,6 +638,7 @@ void PropertyTweener::_bind_methods() { ClassDB::bind_method(D_METHOD("as_relative"), &PropertyTweener::as_relative); ClassDB::bind_method(D_METHOD("set_trans", "trans"), &PropertyTweener::set_trans); ClassDB::bind_method(D_METHOD("set_ease", "ease"), &PropertyTweener::set_ease); + ClassDB::bind_method(D_METHOD("set_custom_interpolator", "interpolator_method"), &PropertyTweener::set_custom_interpolator); ClassDB::bind_method(D_METHOD("set_delay", "delay"), &PropertyTweener::set_delay); } @@ -693,7 +715,7 @@ bool CallbackTweener::step(double &r_delta) { Callable::CallError ce; callback.callp(nullptr, 0, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce) + "."); } finished = true; @@ -773,7 +795,7 @@ bool MethodTweener::step(double &r_delta) { Callable::CallError ce; callback.callp(argptr, 1, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce) + "."); } if (time < duration) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 053b4fac46..8dcc3ad7b6 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -197,6 +197,7 @@ public: Ref<PropertyTweener> as_relative(); Ref<PropertyTweener> set_trans(Tween::TransitionType p_trans); Ref<PropertyTweener> set_ease(Tween::EaseType p_ease); + Ref<PropertyTweener> set_custom_interpolator(const Callable &p_method); Ref<PropertyTweener> set_delay(double p_delay); void set_tween(const Ref<Tween> &p_tween) override; @@ -222,6 +223,7 @@ private: double duration = 0; Tween::TransitionType trans_type = Tween::TRANS_MAX; // This is set inside set_tween(); Tween::EaseType ease_type = Tween::EASE_MAX; + Callable custom_method; double delay = 0; bool do_continue = true; diff --git a/scene/resources/compressed_texture.cpp b/scene/resources/compressed_texture.cpp index f4395d5c7d..588a2b967b 100644 --- a/scene/resources/compressed_texture.cpp +++ b/scene/resources/compressed_texture.cpp @@ -808,6 +808,7 @@ RID CompressedTextureLayered::get_rid() const { Ref<Image> CompressedTextureLayered::get_layer_data(int p_layer) const { if (texture.is_valid()) { + ERR_FAIL_INDEX_V(p_layer, get_layers(), Ref<Image>()); return RS::get_singleton()->texture_2d_layer_get(texture, p_layer); } else { return Ref<Image>(); diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index a9df766196..8cddfb5840 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -269,6 +269,16 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const { return RenderingServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance); } +void MultiMesh::set_custom_aabb(const AABB &p_custom) { + custom_aabb = p_custom; + RS::get_singleton()->multimesh_set_custom_aabb(multimesh, custom_aabb); + emit_changed(); +} + +AABB MultiMesh::get_custom_aabb() const { + return custom_aabb; +} + AABB MultiMesh::get_aabb() const { return RenderingServer::get_singleton()->multimesh_get_aabb(multimesh); } @@ -326,6 +336,8 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color); ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data); ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data); + ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &MultiMesh::set_custom_aabb); + ClassDB::bind_method(D_METHOD("get_custom_aabb"), &MultiMesh::get_custom_aabb); ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb); ClassDB::bind_method(D_METHOD("get_buffer"), &MultiMesh::get_buffer); @@ -334,6 +346,7 @@ void MultiMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_colors"), "set_use_colors", "is_using_colors"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_data"), "set_use_custom_data", "is_using_custom_data"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::INT, "instance_count", PROPERTY_HINT_RANGE, "0,16384,1,or_greater"), "set_instance_count", "get_instance_count"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_instance_count", PROPERTY_HINT_RANGE, "-1,16384,1,or_greater"), "set_visible_instance_count", "get_visible_instance_count"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index 98885db0cc..d7bcb13162 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -48,6 +48,7 @@ private: Ref<Mesh> mesh; RID multimesh; TransformFormat transform_format = TRANSFORM_2D; + AABB custom_aabb; bool use_colors = false; bool use_custom_data = false; int instance_count = 0; @@ -103,6 +104,9 @@ public: void set_instance_custom_data(int p_instance, const Color &p_custom_data); Color get_instance_custom_data(int p_instance) const; + void set_custom_aabb(const AABB &p_custom); + AABB get_custom_aabb() const; + virtual AABB get_aabb() const; virtual RID get_rid() const override; diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h index ce0e80e8c8..d98b2e2ee7 100644 --- a/servers/rendering/dummy/storage/mesh_storage.h +++ b/servers/rendering/dummy/storage/mesh_storage.h @@ -87,6 +87,12 @@ public: s->index_count = p_surface.index_count; s->aabb = p_surface.aabb; s->skin_data = p_surface.skin_data; + s->lods = p_surface.lods; + s->bone_aabbs = p_surface.bone_aabbs; + s->mesh_to_skeleton_xform = p_surface.mesh_to_skeleton_xform; + s->blend_shape_data = p_surface.blend_shape_data; + s->uv_scale = p_surface.uv_scale; + s->material = p_surface.material; } virtual int mesh_get_blend_shape_count(RID p_mesh) const override { return 0; } @@ -153,6 +159,9 @@ public: virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {} virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {} + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {} + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); } + virtual RID multimesh_get_mesh(RID p_multimesh) const override { return RID(); } virtual AABB multimesh_get_aabb(RID p_multimesh) const override { return AABB(); } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 05929a862a..21787b3fcf 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1660,6 +1660,9 @@ void MeshStorage::_multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, b void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p_data, int p_instances) { ERR_FAIL_COND(multimesh->mesh.is_null()); + if (multimesh->custom_aabb != AABB()) { + return; + } AABB aabb; AABB mesh_aabb = mesh_get_aabb(multimesh->mesh); for (int i = 0; i < p_instances; i++) { @@ -1960,8 +1963,10 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b //if we have a mesh set, we need to re-generate the AABB from the new data const float *data = p_buffer.ptr(); - _multimesh_re_create_aabb(multimesh, data, multimesh->instances); - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, multimesh->instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } @@ -2015,9 +2020,26 @@ int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const { return multimesh->visible_instances; } +void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL(multimesh); + multimesh->custom_aabb = p_aabb; + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); +} + +AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, AABB()); + return multimesh->custom_aabb; +} + AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL_V(multimesh, AABB()); + if (multimesh->custom_aabb != AABB()) { + return multimesh->custom_aabb; + } + if (multimesh->aabb_dirty) { const_cast<MeshStorage *>(this)->_update_dirty_multimeshes(); } @@ -2064,9 +2086,11 @@ void MeshStorage::_update_dirty_multimeshes() { if (multimesh->aabb_dirty) { //aabb is dirty.. - _multimesh_re_create_aabb(multimesh, data, visible_instances); multimesh->aabb_dirty = false; - multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + if (multimesh->custom_aabb != AABB()) { + _multimesh_re_create_aabb(multimesh, data, visible_instances); + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB); + } } } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index 771ac6d380..5491f637bc 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -220,6 +220,7 @@ private: bool uses_custom_data = false; int visible_instances = -1; AABB aabb; + AABB custom_aabb; bool aabb_dirty = false; bool buffer_set = false; bool motion_vectors_enabled = false; @@ -646,6 +647,9 @@ public: virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override; virtual int multimesh_get_visible_instances(RID p_multimesh) const override; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override; + virtual AABB multimesh_get_aabb(RID p_multimesh) const override; void _update_dirty_multimeshes(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 3bb6ba1c51..577e9accc0 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -335,6 +335,9 @@ public: FUNC3(multimesh_instance_set_color, RID, int, const Color &) FUNC3(multimesh_instance_set_custom_data, RID, int, const Color &) + FUNC2(multimesh_set_custom_aabb, RID, const AABB &) + FUNC1RC(AABB, multimesh_get_custom_aabb, RID) + FUNC1RC(RID, multimesh_get_mesh, RID) FUNC1RC(AABB, multimesh_get_aabb, RID) diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h index 3c1b2b495d..39fd4f393d 100644 --- a/servers/rendering/storage/mesh_storage.h +++ b/servers/rendering/storage/mesh_storage.h @@ -104,6 +104,9 @@ public: virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0; virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0; + virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0; + virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const = 0; + virtual RID multimesh_get_mesh(RID p_multimesh) const = 0; virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 99b0b1886c..d03f8113f8 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2427,6 +2427,8 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("multimesh_instance_set_custom_data", "multimesh", "index", "custom_data"), &RenderingServer::multimesh_instance_set_custom_data); ClassDB::bind_method(D_METHOD("multimesh_get_mesh", "multimesh"), &RenderingServer::multimesh_get_mesh); ClassDB::bind_method(D_METHOD("multimesh_get_aabb", "multimesh"), &RenderingServer::multimesh_get_aabb); + ClassDB::bind_method(D_METHOD("multimesh_set_custom_aabb", "multimesh", "aabb"), &RenderingServer::multimesh_set_custom_aabb); + ClassDB::bind_method(D_METHOD("multimesh_get_custom_aabb", "multimesh"), &RenderingServer::multimesh_get_custom_aabb); ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform", "multimesh", "index"), &RenderingServer::multimesh_instance_get_transform); ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform_2d", "multimesh", "index"), &RenderingServer::multimesh_instance_get_transform_2d); ClassDB::bind_method(D_METHOD("multimesh_instance_get_color", "multimesh", "index"), &RenderingServer::multimesh_instance_get_color); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index d63283ddbe..19b6c35339 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -413,6 +413,9 @@ public: virtual RID multimesh_get_mesh(RID p_multimesh) const = 0; virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0; + virtual void multimesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) = 0; + virtual AABB multimesh_get_custom_aabb(RID p_mesh) const = 0; + virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0; virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0; |
