diff options
author | George L. Albany <Megacake1234@gmail.com> | 2024-10-16 11:39:01 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-16 11:39:01 +0000 |
commit | 0794ae9a6500ac5b25238d9d7c49e397bedbd2e4 (patch) | |
tree | a47887ff121d48bd0d2aa3e48c0de24e22b07923 /editor/plugins | |
parent | f3014b49c7b67f73d3a7f51b6049a6c5c8be82af (diff) | |
parent | 8043cc1e0d288d7b517d13f14c912c636293cc8b (diff) | |
download | redot-engine-0794ae9a6500ac5b25238d9d7c49e397bedbd2e4.tar.gz |
Merge pull request #723 from Spartan322/merge/af77100
Merge commit godotengine/godot@af77100
Diffstat (limited to 'editor/plugins')
-rw-r--r-- | editor/plugins/animation_library_editor.cpp | 4 | ||||
-rw-r--r-- | editor/plugins/control_editor_plugin.cpp | 4 | ||||
-rw-r--r-- | editor/plugins/cpu_particles_2d_editor_plugin.cpp | 311 | ||||
-rw-r--r-- | editor/plugins/cpu_particles_2d_editor_plugin.h | 96 | ||||
-rw-r--r-- | editor/plugins/cpu_particles_3d_editor_plugin.cpp | 219 | ||||
-rw-r--r-- | editor/plugins/cpu_particles_3d_editor_plugin.h | 87 | ||||
-rw-r--r-- | editor/plugins/gpu_particles_2d_editor_plugin.cpp | 429 | ||||
-rw-r--r-- | editor/plugins/gpu_particles_2d_editor_plugin.h | 103 | ||||
-rw-r--r-- | editor/plugins/gpu_particles_3d_editor_plugin.cpp | 463 | ||||
-rw-r--r-- | editor/plugins/gpu_particles_3d_editor_plugin.h | 120 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_plugin.cpp | 85 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_plugin.h | 4 | ||||
-rw-r--r-- | editor/plugins/particles_editor_plugin.cpp | 970 | ||||
-rw-r--r-- | editor/plugins/particles_editor_plugin.h | 218 |
14 files changed, 1265 insertions, 1848 deletions
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index 3c0a7bce35..31c13229b1 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -600,7 +600,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int file_popup->add_separator(); file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY); Rect2 pos = tree->get_item_rect(p_item, 1, 0); - Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)); + Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll(); file_popup->popup(Rect2(popup_pos, Size2())); file_dialog_animation = StringName(); @@ -640,7 +640,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int file_popup->add_separator(); file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION); Rect2 pos = tree->get_item_rect(p_item, 1, 0); - Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)); + Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll(); file_popup->popup(Rect2(popup_pos, Size2())); file_dialog_animation = anim_name; diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp index ec1a27f1ae..c0461b34b8 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -33,7 +33,6 @@ #include "control_editor_plugin.h" #include "editor/editor_node.h" -#include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/canvas_item_editor_plugin.h" @@ -513,6 +512,9 @@ void ControlEditorPopupButton::_notification(int p_what) { case NOTIFICATION_DRAW: { if (arrow_icon.is_valid()) { Vector2 arrow_pos = Point2(26, 0) * EDSCALE; + if (is_layout_rtl()) { + arrow_pos.x = get_size().x - arrow_pos.x - arrow_icon->get_width(); + } arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2; draw_texture(arrow_icon, arrow_pos); } diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp deleted file mode 100644 index bfc85aedd4..0000000000 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ /dev/null @@ -1,311 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_2d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "cpu_particles_2d_editor_plugin.h" - -#include "canvas_item_editor_plugin.h" -#include "core/io/image_loader.h" -#include "editor/editor_node.h" -#include "editor/editor_settings.h" -#include "editor/editor_undo_redo_manager.h" -#include "editor/gui/editor_file_dialog.h" -#include "editor/scene_tree_dock.h" -#include "scene/2d/cpu_particles_2d.h" -#include "scene/2d/gpu_particles_2d.h" -#include "scene/gui/check_box.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/option_button.h" -#include "scene/gui/separator.h" -#include "scene/gui/spin_box.h" -#include "scene/resources/particle_process_material.h" - -void CPUParticles2DEditorPlugin::edit(Object *p_object) { - particles = Object::cast_to<CPUParticles2D>(p_object); -} - -bool CPUParticles2DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("CPUParticles2D"); -} - -void CPUParticles2DEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - toolbar->show(); - } else { - toolbar->hide(); - } -} - -void CPUParticles2DEditorPlugin::_file_selected(const String &p_file) { - source_emission_file = p_file; - emission_mask->popup_centered(); -} - -void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) { - switch (p_idx) { - case MENU_LOAD_EMISSION_MASK: { - file->popup_file_dialog(); - } break; - case MENU_CLEAR_EMISSION_MASK: { - emission_mask->popup_centered(); - } break; - case MENU_RESTART: { - particles->restart(); - } break; - case MENU_CONVERT_TO_GPU_PARTICLES: { - GPUParticles2D *gpu_particles = memnew(GPUParticles2D); - gpu_particles->convert_from_particles(particles); - gpu_particles->set_name(particles->get_name()); - gpu_particles->set_transform(particles->get_transform()); - gpu_particles->set_visible(particles->is_visible()); - gpu_particles->set_process_mode(particles->get_process_mode()); - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, particles); - SceneTreeDock::get_singleton()->replace_node(particles, gpu_particles); - ur->commit_action(false); - } break; - } -} - -void CPUParticles2DEditorPlugin::_generate_emission_mask() { - Ref<Image> img; - img.instantiate(); - Error err = ImageLoader::load_image(source_emission_file, img); - ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'."); - - if (img->is_compressed()) { - img->decompress(); - } - img->convert(Image::FORMAT_RGBA8); - ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8); - Size2i s = img->get_size(); - ERR_FAIL_COND(s.width == 0 || s.height == 0); - - Vector<Point2> valid_positions; - Vector<Point2> valid_normals; - Vector<uint8_t> valid_colors; - - valid_positions.resize(s.width * s.height); - - EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected(); - - if (emode == EMISSION_MODE_BORDER_DIRECTED) { - valid_normals.resize(s.width * s.height); - } - - bool capture_colors = emission_colors->is_pressed(); - - if (capture_colors) { - valid_colors.resize(s.width * s.height * 4); - } - - int vpc = 0; - - { - Vector<uint8_t> img_data = img->get_data(); - const uint8_t *r = img_data.ptr(); - - for (int i = 0; i < s.width; i++) { - for (int j = 0; j < s.height; j++) { - uint8_t a = r[(j * s.width + i) * 4 + 3]; - - if (a > 128) { - if (emode == EMISSION_MODE_SOLID) { - if (capture_colors) { - valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; - valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; - valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; - valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; - } - valid_positions.write[vpc++] = Point2(i, j); - - } else { - bool on_border = false; - for (int x = i - 1; x <= i + 1; x++) { - for (int y = j - 1; y <= j + 1; y++) { - if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { - on_border = true; - break; - } - } - - if (on_border) { - break; - } - } - - if (on_border) { - valid_positions.write[vpc] = Point2(i, j); - - if (emode == EMISSION_MODE_BORDER_DIRECTED) { - Vector2 normal; - for (int x = i - 2; x <= i + 2; x++) { - for (int y = j - 2; y <= j + 2; y++) { - if (x == i && y == j) { - continue; - } - - if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { - normal += Vector2(x - i, y - j).normalized(); - } - } - } - - normal.normalize(); - valid_normals.write[vpc] = normal; - } - - if (capture_colors) { - valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; - valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; - valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; - valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; - } - - vpc++; - } - } - } - } - } - } - - valid_positions.resize(vpc); - if (valid_normals.size()) { - valid_normals.resize(vpc); - } - - ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image..."); - - if (capture_colors) { - PackedColorArray pca; - pca.resize(vpc); - Color *pcaw = pca.ptrw(); - for (int i = 0; i < vpc; i += 1) { - Color color; - color.r = valid_colors[i * 4 + 0] / 255.0f; - color.g = valid_colors[i * 4 + 1] / 255.0f; - color.b = valid_colors[i * 4 + 2] / 255.0f; - color.a = valid_colors[i * 4 + 3] / 255.0f; - pcaw[i] = color; - } - particles->set_emission_colors(pca); - } - - if (valid_normals.size()) { - particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS); - PackedVector2Array norms; - norms.resize(valid_normals.size()); - Vector2 *normsw = norms.ptrw(); - for (int i = 0; i < valid_normals.size(); i += 1) { - normsw[i] = valid_normals[i]; - } - particles->set_emission_normals(norms); - } else { - particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS); - } - - { - Vector2 offset; - if (emission_mask_centered->is_pressed()) { - offset = Vector2(-s.width * 0.5, -s.height * 0.5); - } - - PackedVector2Array points; - points.resize(valid_positions.size()); - Vector2 *pointsw = points.ptrw(); - for (int i = 0; i < valid_positions.size(); i += 1) { - pointsw[i] = valid_positions[i] + offset; - } - particles->set_emission_points(points); - } -} - -void CPUParticles2DEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(file->get_editor_theme_icon(SNAME("CPUParticles2D"))); - file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); - } break; - } -} - -CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { - particles = nullptr; - - toolbar = memnew(HBoxContainer); - add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); - toolbar->hide(); - - menu = memnew(MenuButton); - menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART); - menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK); - menu->get_popup()->add_item(TTR("Convert to GPUParticles2D"), MENU_CONVERT_TO_GPU_PARTICLES); - menu->set_text(TTR("CPUParticles2D")); - menu->set_switch_on_hover(true); - toolbar->add_child(menu); - - file = memnew(EditorFileDialog); - List<String> ext; - ImageLoader::get_recognized_extensions(&ext); - for (const String &E : ext) { - file->add_filter("*." + E, E.to_upper()); - } - file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); - toolbar->add_child(file); - - emission_mask = memnew(ConfirmationDialog); - emission_mask->set_title(TTR("Load Emission Mask")); - VBoxContainer *emvb = memnew(VBoxContainer); - emission_mask->add_child(emvb); - emission_mask_mode = memnew(OptionButton); - emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode); - emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID); - emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER); - emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED); - VBoxContainer *optionsvb = memnew(VBoxContainer); - emvb->add_margin_child(TTR("Options"), optionsvb); - emission_mask_centered = memnew(CheckBox); - emission_mask_centered->set_text(TTR("Centered")); - optionsvb->add_child(emission_mask_centered); - emission_colors = memnew(CheckBox); - emission_colors->set_text(TTR("Capture Colors from Pixel")); - optionsvb->add_child(emission_colors); - - toolbar->add_child(emission_mask); - - emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles2DEditorPlugin::_generate_emission_mask)); -} - -CPUParticles2DEditorPlugin::~CPUParticles2DEditorPlugin() { -} diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h deleted file mode 100644 index 5680b02225..0000000000 --- a/editor/plugins/cpu_particles_2d_editor_plugin.h +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_2d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef CPU_PARTICLES_2D_EDITOR_PLUGIN_H -#define CPU_PARTICLES_2D_EDITOR_PLUGIN_H - -#include "editor/plugins/editor_plugin.h" -#include "scene/2d/cpu_particles_2d.h" -#include "scene/2d/physics/collision_polygon_2d.h" -#include "scene/gui/box_container.h" - -class CheckBox; -class ConfirmationDialog; -class SpinBox; -class EditorFileDialog; -class MenuButton; -class OptionButton; - -class CPUParticles2DEditorPlugin : public EditorPlugin { - GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin); - - enum { - MENU_LOAD_EMISSION_MASK, - MENU_CLEAR_EMISSION_MASK, - MENU_RESTART, - MENU_CONVERT_TO_GPU_PARTICLES, - }; - - enum EmissionMode { - EMISSION_MODE_SOLID, - EMISSION_MODE_BORDER, - EMISSION_MODE_BORDER_DIRECTED - }; - - CPUParticles2D *particles = nullptr; - - EditorFileDialog *file = nullptr; - - HBoxContainer *toolbar = nullptr; - MenuButton *menu = nullptr; - - ConfirmationDialog *emission_mask = nullptr; - OptionButton *emission_mask_mode = nullptr; - CheckBox *emission_mask_centered = nullptr; - CheckBox *emission_colors = nullptr; - - String source_emission_file; - - void _file_selected(const String &p_file); - void _menu_callback(int p_idx); - void _generate_emission_mask(); - -protected: - void _notification(int p_what); - -public: - virtual String get_name() const override { return "CPUParticles2D"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - CPUParticles2DEditorPlugin(); - ~CPUParticles2DEditorPlugin(); -}; - -#endif // CPU_PARTICLES_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp deleted file mode 100644 index 77f5c75d56..0000000000 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_3d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "cpu_particles_3d_editor_plugin.h" - -#include "editor/editor_node.h" -#include "editor/editor_settings.h" -#include "editor/editor_undo_redo_manager.h" -#include "editor/gui/scene_tree_editor.h" -#include "editor/plugins/node_3d_editor_plugin.h" -#include "editor/scene_tree_dock.h" -#include "scene/gui/menu_button.h" - -void CPUParticles3DEditor::_node_removed(Node *p_node) { - if (p_node == node) { - node = nullptr; - hide(); - } -} - -void CPUParticles3DEditor::_notification(int p_notification) { - switch (p_notification) { - case NOTIFICATION_ENTER_TREE: { - options->set_icon(get_editor_theme_icon(SNAME("CPUParticles3D"))); - } break; - } -} - -void CPUParticles3DEditor::_menu_option(int p_option) { - switch (p_option) { - case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { - emission_tree_dialog->popup_scenetree_dialog(); - } break; - - case MENU_OPTION_RESTART: { - node->restart(); - } break; - - case MENU_OPTION_CONVERT_TO_GPU_PARTICLES: { - GPUParticles3D *gpu_particles = memnew(GPUParticles3D); - gpu_particles->convert_from_particles(node); - gpu_particles->set_name(node->get_name()); - gpu_particles->set_transform(node->get_transform()); - gpu_particles->set_visible(node->is_visible()); - gpu_particles->set_process_mode(node->get_process_mode()); - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, node); - SceneTreeDock::get_singleton()->replace_node(node, gpu_particles); - 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(TTR("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) { - base_node = p_particles; - node = p_particles; -} - -void CPUParticles3DEditor::_generate_emission_points() { - /// hacer codigo aca - Vector<Vector3> points; - Vector<Vector3> normals; - - if (!_generate(points, normals)) { - return; - } - - if (normals.size() == 0) { - node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS); - node->set_emission_points(points); - } else { - node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS); - node->set_emission_points(points); - node->set_emission_normals(normals); - } -} - -CPUParticles3DEditor::CPUParticles3DEditor() { - particles_editor_hb = memnew(HBoxContainer); - Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb); - options = memnew(MenuButton); - options->set_switch_on_hover(true); - particles_editor_hb->add_child(options); - particles_editor_hb->hide(); - - options->set_text(TTR("CPUParticles3D")); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), 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(SceneStringName(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(SceneStringName(confirmed), callable_mp(this, &CPUParticles3DEditor::_generate_aabb)); -} - -void CPUParticles3DEditorPlugin::edit(Object *p_object) { - particles_editor->edit(Object::cast_to<CPUParticles3D>(p_object)); -} - -bool CPUParticles3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("CPUParticles3D"); -} - -void CPUParticles3DEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - particles_editor->show(); - particles_editor->particles_editor_hb->show(); - } else { - particles_editor->particles_editor_hb->hide(); - particles_editor->hide(); - particles_editor->edit(nullptr); - } -} - -CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { - particles_editor = memnew(CPUParticles3DEditor); - EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor); - - particles_editor->hide(); -} - -CPUParticles3DEditorPlugin::~CPUParticles3DEditorPlugin() { -} diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h deleted file mode 100644 index 42522ccbdc..0000000000 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_3d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef CPU_PARTICLES_3D_EDITOR_PLUGIN_H -#define CPU_PARTICLES_3D_EDITOR_PLUGIN_H - -#include "editor/plugins/gpu_particles_3d_editor_plugin.h" -#include "scene/3d/cpu_particles_3d.h" - -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; - - virtual void _generate_emission_points() override; - -protected: - void _notification(int p_notification); - void _node_removed(Node *p_node); - -public: - void edit(CPUParticles3D *p_particles); - CPUParticles3DEditor(); -}; - -class CPUParticles3DEditorPlugin : public EditorPlugin { - GDCLASS(CPUParticles3DEditorPlugin, EditorPlugin); - - CPUParticles3DEditor *particles_editor = nullptr; - -public: - virtual String get_name() const override { return "CPUParticles3D"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - CPUParticles3DEditorPlugin(); - ~CPUParticles3DEditorPlugin(); -}; - -#endif // CPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp deleted file mode 100644 index 01762c99ba..0000000000 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ /dev/null @@ -1,429 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_2d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "gpu_particles_2d_editor_plugin.h" - -#include "canvas_item_editor_plugin.h" -#include "core/io/image_loader.h" -#include "editor/editor_node.h" -#include "editor/editor_settings.h" -#include "editor/editor_undo_redo_manager.h" -#include "editor/gui/editor_file_dialog.h" -#include "editor/scene_tree_dock.h" -#include "scene/2d/cpu_particles_2d.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/separator.h" -#include "scene/resources/image_texture.h" -#include "scene/resources/particle_process_material.h" - -void GPUParticles2DEditorPlugin::edit(Object *p_object) { - particles = Object::cast_to<GPUParticles2D>(p_object); -} - -bool GPUParticles2DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("GPUParticles2D"); -} - -void GPUParticles2DEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - toolbar->show(); - } else { - toolbar->hide(); - } -} - -void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) { - source_emission_file = p_file; - emission_mask->popup_centered(); -} - -void GPUParticles2DEditorPlugin::_selection_changed() { - List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); - - if (selected_particles.is_empty() && selected_nodes.is_empty()) { - return; - } - - for (GPUParticles2D *SP : selected_particles) { - SP->set_show_visibility_rect(false); - } - selected_particles.clear(); - - for (Node *P : selected_nodes) { - GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(P); - if (selected_particle != nullptr) { - selected_particle->set_show_visibility_rect(true); - selected_particles.push_back(selected_particle); - } - } -} - -void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { - switch (p_idx) { - case MENU_GENERATE_VISIBILITY_RECT: { - // Add one second to the default generation lifetime, since the progress is updated every second. - generate_seconds->set_value(MAX(1.0, trunc(particles->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_visibility_rect->popup_centered(); - } else { - // Generate the visibility rect immediately. - _generate_visibility_rect(); - } - } break; - case MENU_LOAD_EMISSION_MASK: { - file->popup_file_dialog(); - - } break; - case MENU_CLEAR_EMISSION_MASK: { - emission_mask->popup_centered(); - } break; - case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: { - CPUParticles2D *cpu_particles = memnew(CPUParticles2D); - cpu_particles->convert_from_particles(particles); - cpu_particles->set_name(particles->get_name()); - cpu_particles->set_transform(particles->get_transform()); - cpu_particles->set_visible(particles->is_visible()); - cpu_particles->set_process_mode(particles->get_process_mode()); - cpu_particles->set_z_index(particles->get_z_index()); - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Convert to CPUParticles2D"), UndoRedo::MERGE_DISABLE, particles); - SceneTreeDock::get_singleton()->replace_node(particles, cpu_particles); - ur->commit_action(false); - - } break; - case MENU_RESTART: { - particles->restart(); - } - } -} - -void GPUParticles2DEditorPlugin::_generate_visibility_rect() { - double time = generate_seconds->get_value(); - - float running = 0.0; - - EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time)); - - bool was_emitting = particles->is_emitting(); - if (!was_emitting) { - particles->set_emitting(true); - OS::get_singleton()->delay_usec(1000); - } - - Rect2 rect; - while (running < time) { - uint64_t ticks = OS::get_singleton()->get_ticks_usec(); - ep.step(TTR("Generating..."), int(running), true); - OS::get_singleton()->delay_usec(1000); - - Rect2 capture = particles->capture_rect(); - if (rect == Rect2()) { - rect = capture; - } else { - rect = rect.merge(capture); - } - - running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; - } - - if (!was_emitting) { - particles->set_emitting(false); - } - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Generate Visibility Rect")); - undo_redo->add_do_method(particles, "set_visibility_rect", rect); - undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect()); - undo_redo->commit_action(); -} - -void GPUParticles2DEditorPlugin::_generate_emission_mask() { - Ref<ParticleProcessMaterial> pm = particles->get_process_material(); - if (!pm.is_valid()) { - EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material")); - return; - } - - Ref<Image> img; - img.instantiate(); - Error err = ImageLoader::load_image(source_emission_file, img); - ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'."); - - if (img->is_compressed()) { - img->decompress(); - } - img->convert(Image::FORMAT_RGBA8); - ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8); - Size2i s = img->get_size(); - ERR_FAIL_COND(s.width == 0 || s.height == 0); - - Vector<Point2> valid_positions; - Vector<Point2> valid_normals; - Vector<uint8_t> valid_colors; - - valid_positions.resize(s.width * s.height); - - EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected(); - - if (emode == EMISSION_MODE_BORDER_DIRECTED) { - valid_normals.resize(s.width * s.height); - } - - bool capture_colors = emission_colors->is_pressed(); - - if (capture_colors) { - valid_colors.resize(s.width * s.height * 4); - } - - int vpc = 0; - - { - Vector<uint8_t> img_data = img->get_data(); - const uint8_t *r = img_data.ptr(); - - for (int i = 0; i < s.width; i++) { - for (int j = 0; j < s.height; j++) { - uint8_t a = r[(j * s.width + i) * 4 + 3]; - - if (a > 128) { - if (emode == EMISSION_MODE_SOLID) { - if (capture_colors) { - valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; - valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; - valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; - valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; - } - valid_positions.write[vpc++] = Point2(i, j); - - } else { - bool on_border = false; - for (int x = i - 1; x <= i + 1; x++) { - for (int y = j - 1; y <= j + 1; y++) { - if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { - on_border = true; - break; - } - } - - if (on_border) { - break; - } - } - - if (on_border) { - valid_positions.write[vpc] = Point2(i, j); - - if (emode == EMISSION_MODE_BORDER_DIRECTED) { - Vector2 normal; - for (int x = i - 2; x <= i + 2; x++) { - for (int y = j - 2; y <= j + 2; y++) { - if (x == i && y == j) { - continue; - } - - if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { - normal += Vector2(x - i, y - j).normalized(); - } - } - } - - normal.normalize(); - valid_normals.write[vpc] = normal; - } - - if (capture_colors) { - valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; - valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; - valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; - valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; - } - - vpc++; - } - } - } - } - } - } - - valid_positions.resize(vpc); - if (valid_normals.size()) { - valid_normals.resize(vpc); - } - - ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image..."); - - Vector<uint8_t> texdata; - - int w = 2048; - int h = (vpc / 2048) + 1; - - texdata.resize(w * h * 2 * sizeof(float)); - - { - Vector2 offset; - if (emission_mask_centered->is_pressed()) { - offset = Vector2(-s.width * 0.5, -s.height * 0.5); - } - - uint8_t *tw = texdata.ptrw(); - float *twf = reinterpret_cast<float *>(tw); - for (int i = 0; i < vpc; i++) { - twf[i * 2 + 0] = valid_positions[i].x + offset.x; - twf[i * 2 + 1] = valid_positions[i].y + offset.y; - } - } - - img.instantiate(); - img->set_data(w, h, false, Image::FORMAT_RGF, texdata); - pm->set_emission_point_texture(ImageTexture::create_from_image(img)); - pm->set_emission_point_count(vpc); - - if (capture_colors) { - Vector<uint8_t> colordata; - colordata.resize(w * h * 4); //use RG texture - - { - uint8_t *tw = colordata.ptrw(); - for (int i = 0; i < vpc * 4; i++) { - tw[i] = valid_colors[i]; - } - } - - img.instantiate(); - img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata); - pm->set_emission_color_texture(ImageTexture::create_from_image(img)); - } - - if (valid_normals.size()) { - pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - - Vector<uint8_t> normdata; - normdata.resize(w * h * 2 * sizeof(float)); //use RG texture - - { - uint8_t *tw = normdata.ptrw(); - float *twf = reinterpret_cast<float *>(tw); - for (int i = 0; i < vpc; i++) { - twf[i * 2 + 0] = valid_normals[i].x; - twf[i * 2 + 1] = valid_normals[i].y; - } - } - - img.instantiate(); - img->set_data(w, h, false, Image::FORMAT_RGF, normdata); - pm->set_emission_normal_texture(ImageTexture::create_from_image(img)); - - } else { - pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); - } -} - -void GPUParticles2DEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(menu->get_editor_theme_icon(SNAME("GPUParticles2D"))); - file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); - EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed)); - } break; - } -} - -GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { - particles = nullptr; - - toolbar = memnew(HBoxContainer); - add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); - toolbar->hide(); - - menu = memnew(MenuButton); - menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART); - menu->get_popup()->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT); - menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK); - // menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK); - menu->get_popup()->add_item(TTR("Convert to CPUParticles2D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES); - menu->set_text(TTR("GPUParticles2D")); - menu->set_switch_on_hover(true); - toolbar->add_child(menu); - - file = memnew(EditorFileDialog); - List<String> ext; - ImageLoader::get_recognized_extensions(&ext); - for (const String &E : ext) { - file->add_filter("*." + E, E.to_upper()); - } - file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); - toolbar->add_child(file); - - generate_visibility_rect = memnew(ConfirmationDialog); - generate_visibility_rect->set_title(TTR("Generate Visibility Rect")); - VBoxContainer *genvb = memnew(VBoxContainer); - generate_visibility_rect->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); - - toolbar->add_child(generate_visibility_rect); - - generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect)); - - emission_mask = memnew(ConfirmationDialog); - emission_mask->set_title(TTR("Load Emission Mask")); - VBoxContainer *emvb = memnew(VBoxContainer); - emission_mask->add_child(emvb); - emission_mask_mode = memnew(OptionButton); - emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode); - emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID); - emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER); - emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED); - VBoxContainer *optionsvb = memnew(VBoxContainer); - emvb->add_margin_child(TTR("Options"), optionsvb); - emission_mask_centered = memnew(CheckBox); - emission_mask_centered->set_text(TTR("Centered")); - optionsvb->add_child(emission_mask_centered); - emission_colors = memnew(CheckBox); - emission_colors->set_text(TTR("Capture Colors from Pixel")); - optionsvb->add_child(emission_colors); - - toolbar->add_child(emission_mask); - - emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_emission_mask)); -} - -GPUParticles2DEditorPlugin::~GPUParticles2DEditorPlugin() { -} diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h deleted file mode 100644 index 83c86beafc..0000000000 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_2d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef GPU_PARTICLES_2D_EDITOR_PLUGIN_H -#define GPU_PARTICLES_2D_EDITOR_PLUGIN_H - -#include "editor/plugins/editor_plugin.h" -#include "scene/2d/gpu_particles_2d.h" -#include "scene/2d/physics/collision_polygon_2d.h" -#include "scene/gui/box_container.h" -#include "scene/gui/spin_box.h" - -class CheckBox; -class ConfirmationDialog; -class EditorFileDialog; -class MenuButton; -class OptionButton; - -class GPUParticles2DEditorPlugin : public EditorPlugin { - GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin); - - enum { - MENU_GENERATE_VISIBILITY_RECT, - MENU_LOAD_EMISSION_MASK, - MENU_CLEAR_EMISSION_MASK, - MENU_OPTION_CONVERT_TO_CPU_PARTICLES, - MENU_RESTART - }; - - enum EmissionMode { - EMISSION_MODE_SOLID, - EMISSION_MODE_BORDER, - EMISSION_MODE_BORDER_DIRECTED - }; - - GPUParticles2D *particles = nullptr; - List<GPUParticles2D *> selected_particles; - - EditorFileDialog *file = nullptr; - - HBoxContainer *toolbar = nullptr; - MenuButton *menu = nullptr; - - ConfirmationDialog *generate_visibility_rect = nullptr; - SpinBox *generate_seconds = nullptr; - - ConfirmationDialog *emission_mask = nullptr; - OptionButton *emission_mask_mode = nullptr; - CheckBox *emission_mask_centered = nullptr; - CheckBox *emission_colors = nullptr; - - String source_emission_file; - - void _file_selected(const String &p_file); - void _menu_callback(int p_idx); - void _generate_visibility_rect(); - void _generate_emission_mask(); - void _selection_changed(); - -protected: - void _notification(int p_what); - -public: - virtual String get_name() const override { return "GPUParticles2D"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - GPUParticles2DEditorPlugin(); - ~GPUParticles2DEditorPlugin(); -}; - -#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp deleted file mode 100644 index b19a391e62..0000000000 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_3d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "gpu_particles_3d_editor_plugin.h" - -#include "core/io/resource_loader.h" -#include "editor/editor_node.h" -#include "editor/editor_settings.h" -#include "editor/editor_undo_redo_manager.h" -#include "editor/plugins/node_3d_editor_plugin.h" -#include "editor/scene_tree_dock.h" -#include "scene/3d/cpu_particles_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/gui/menu_button.h" -#include "scene/resources/image_texture.h" -#include "scene/resources/particle_process_material.h" - -bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) { - bool use_normals = emission_fill->get_selected() == 1; - - if (emission_fill->get_selected() < 2) { - float area_accum = 0; - RBMap<float, int> triangle_area_map; - - for (int i = 0; i < geometry.size(); i++) { - float area = geometry[i].get_area(); - if (area < CMP_EPSILON) { - continue; - } - triangle_area_map[area_accum] = i; - area_accum += area; - } - - if (!triangle_area_map.size() || area_accum == 0) { - EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area.")); - return false; - } - - int emissor_count = emission_amount->get_value(); - - for (int i = 0; i < emissor_count; i++) { - float areapos = Math::random(0.0f, area_accum); - - RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); - ERR_FAIL_COND_V(!E, false); - int index = E->value; - ERR_FAIL_INDEX_V(index, geometry.size(), false); - - // ok FINALLY get face - Face3 face = geometry[index]; - //now compute some position inside the face... - - Vector3 pos = face.get_random_point_inside(); - - points.push_back(pos); - - if (use_normals) { - Vector3 normal = face.get_plane().normal; - normals.push_back(normal); - } - } - } else { - int gcount = geometry.size(); - - if (gcount == 0) { - EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces.")); - return false; - } - - const Face3 *r = geometry.ptr(); - - AABB aabb; - - for (int i = 0; i < gcount; i++) { - for (int j = 0; j < 3; j++) { - if (i == 0 && j == 0) { - aabb.position = r[i].vertex[j]; - } else { - aabb.expand_to(r[i].vertex[j]); - } - } - } - - int emissor_count = emission_amount->get_value(); - - for (int i = 0; i < emissor_count; i++) { - int attempts = 5; - - for (int j = 0; j < attempts; j++) { - Vector3 dir; - dir[Math::rand() % 3] = 1.0; - Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position; - - Vector3 ofsv = ofs + aabb.size * dir; - - //space it a little - ofs -= dir; - ofsv += dir; - - float max = -1e7, min = 1e7; - - for (int k = 0; k < gcount; k++) { - const Face3 &f3 = r[k]; - - Vector3 res; - if (f3.intersects_segment(ofs, ofsv, &res)) { - res -= ofs; - float d = dir.dot(res); - - if (d < min) { - min = d; - } - if (d > max) { - max = d; - } - } - } - - if (max < min) { - continue; //lost attempt - } - - float val = min + (max - min) * Math::randf(); - - Vector3 point = ofs + dir * val; - - points.push_back(point); - break; - } - } - } - - return true; -} - -void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) { - Node *sel = get_node(p_path); - if (!sel) { - return; - } - - if (!sel->is_class("Node3D")) { - EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name())); - return; - } - - MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel); - if (!mi || mi->get_mesh().is_null()) { - EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name())); - return; - } - - geometry = mi->get_mesh()->get_faces(); - - if (geometry.size() == 0) { - EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name())); - return; - } - - Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform(); - - int gc = geometry.size(); - Face3 *w = geometry.ptrw(); - - for (int i = 0; i < gc; i++) { - for (int j = 0; j < 3; j++) { - w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]); - } - } - - emission_dialog->popup_centered(Size2(300, 130)); -} - -GPUParticles3DEditorBase::GPUParticles3DEditorBase() { - emission_dialog = memnew(ConfirmationDialog); - emission_dialog->set_title(TTR("Create Emitter")); - add_child(emission_dialog); - VBoxContainer *emd_vb = memnew(VBoxContainer); - emission_dialog->add_child(emd_vb); - - emission_amount = memnew(SpinBox); - emission_amount->set_min(1); - emission_amount->set_max(100000); - emission_amount->set_value(512); - emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount); - - emission_fill = memnew(OptionButton); - emission_fill->add_item(TTR("Surface Points")); - emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); - emission_fill->add_item(TTR("Volume")); - emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill); - - emission_dialog->set_ok_button_text(TTR("Create")); - emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points)); - - emission_tree_dialog = memnew(SceneTreeDialog); - Vector<StringName> valid_types; - valid_types.push_back("MeshInstance3D"); - emission_tree_dialog->set_valid_types(valid_types); - add_child(emission_tree_dialog); - emission_tree_dialog->connect("selected", callable_mp(this, &GPUParticles3DEditorBase::_node_selected)); -} - -void GPUParticles3DEditor::_node_removed(Node *p_node) { - if (p_node == node) { - node = nullptr; - hide(); - } -} - -void GPUParticles3DEditor::_notification(int p_notification) { - switch (p_notification) { - case NOTIFICATION_ENTER_TREE: { - options->set_icon(options->get_popup()->get_editor_theme_icon(SNAME("GPUParticles3D"))); - get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); - } break; - } -} - -void GPUParticles3DEditor::_menu_option(int p_option) { - switch (p_option) { - 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; - case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { - Ref<ParticleProcessMaterial> mat = node->get_process_material(); - if (mat.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required.")); - return; - } - - emission_tree_dialog->popup_scenetree_dialog(); - - } break; - case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: { - CPUParticles3D *cpu_particles = memnew(CPUParticles3D); - cpu_particles->convert_from_particles(node); - cpu_particles->set_name(node->get_name()); - cpu_particles->set_transform(node->get_transform()); - cpu_particles->set_visible(node->is_visible()); - cpu_particles->set_process_mode(node->get_process_mode()); - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Convert to CPUParticles3D"), UndoRedo::MERGE_DISABLE, node); - SceneTreeDock::get_singleton()->replace_node(node, cpu_particles); - ur->commit_action(false); - - } break; - case MENU_OPTION_RESTART: { - node->restart(); - - } break; - } -} - -void GPUParticles3DEditor::_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(TTR("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 GPUParticles3DEditor::edit(GPUParticles3D *p_particles) { - base_node = p_particles; - node = p_particles; -} - -void GPUParticles3DEditor::_generate_emission_points() { - /// hacer codigo aca - Vector<Vector3> points; - Vector<Vector3> normals; - - if (!_generate(points, normals)) { - return; - } - - int point_count = points.size(); - - int w = 2048; - int h = (point_count / 2048) + 1; - - Vector<uint8_t> point_img; - point_img.resize(w * h * 3 * sizeof(float)); - - { - uint8_t *iw = point_img.ptrw(); - memset(iw, 0, w * h * 3 * sizeof(float)); - const Vector3 *r = points.ptr(); - float *wf = reinterpret_cast<float *>(iw); - for (int i = 0; i < point_count; i++) { - wf[i * 3 + 0] = r[i].x; - wf[i * 3 + 1] = r[i].y; - wf[i * 3 + 2] = r[i].z; - } - } - - Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); - Ref<ImageTexture> tex = ImageTexture::create_from_image(image); - - Ref<ParticleProcessMaterial> mat = node->get_process_material(); - ERR_FAIL_COND(mat.is_null()); - - if (normals.size() > 0) { - mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - mat->set_emission_point_count(point_count); - mat->set_emission_point_texture(tex); - - Vector<uint8_t> point_img2; - point_img2.resize(w * h * 3 * sizeof(float)); - - { - uint8_t *iw = point_img2.ptrw(); - memset(iw, 0, w * h * 3 * sizeof(float)); - const Vector3 *r = normals.ptr(); - float *wf = reinterpret_cast<float *>(iw); - for (int i = 0; i < point_count; i++) { - wf[i * 3 + 0] = r[i].x; - wf[i * 3 + 1] = r[i].y; - wf[i * 3 + 2] = r[i].z; - } - } - - Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); - mat->set_emission_normal_texture(ImageTexture::create_from_image(image2)); - } else { - mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); - mat->set_emission_point_count(point_count); - mat->set_emission_point_texture(tex); - } -} - -GPUParticles3DEditor::GPUParticles3DEditor() { - node = nullptr; - particles_editor_hb = memnew(HBoxContainer); - Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb); - options = memnew(MenuButton); - options->set_switch_on_hover(true); - particles_editor_hb->add_child(options); - particles_editor_hb->hide(); - - options->set_text(TTR("GPUParticles3D")); - options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), 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 CPUParticles3D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES); - - options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles3DEditor::_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(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditor::_generate_aabb)); -} - -void GPUParticles3DEditorPlugin::edit(Object *p_object) { - particles_editor->edit(Object::cast_to<GPUParticles3D>(p_object)); -} - -bool GPUParticles3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("GPUParticles3D"); -} - -void GPUParticles3DEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - particles_editor->show(); - particles_editor->particles_editor_hb->show(); - } else { - particles_editor->particles_editor_hb->hide(); - particles_editor->hide(); - particles_editor->edit(nullptr); - } -} - -GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { - particles_editor = memnew(GPUParticles3DEditor); - EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor); - - particles_editor->hide(); -} - -GPUParticles3DEditorPlugin::~GPUParticles3DEditorPlugin() { -} diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h deleted file mode 100644 index 5837eef1e2..0000000000 --- a/editor/plugins/gpu_particles_3d_editor_plugin.h +++ /dev/null @@ -1,120 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_3d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2024-present Redot Engine contributors */ -/* (see REDOT_AUTHORS.md) */ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef GPU_PARTICLES_3D_EDITOR_PLUGIN_H -#define GPU_PARTICLES_3D_EDITOR_PLUGIN_H - -#include "editor/plugins/editor_plugin.h" -#include "scene/3d/gpu_particles_3d.h" -#include "scene/gui/spin_box.h" - -class ConfirmationDialog; -class HBoxContainer; -class MenuButton; -class OptionButton; -class SceneTreeDialog; - -class GPUParticles3DEditorBase : public Control { - GDCLASS(GPUParticles3DEditorBase, Control); - -protected: - Node3D *base_node = nullptr; - Panel *panel = nullptr; - MenuButton *options = nullptr; - HBoxContainer *particles_editor_hb = nullptr; - - SceneTreeDialog *emission_tree_dialog = nullptr; - - ConfirmationDialog *emission_dialog = nullptr; - SpinBox *emission_amount = nullptr; - OptionButton *emission_fill = nullptr; - - Vector<Face3> geometry; - - bool _generate(Vector<Vector3> &points, Vector<Vector3> &normals); - virtual void _generate_emission_points() {} - void _node_selected(const NodePath &p_path); - -public: - GPUParticles3DEditorBase(); -}; - -class GPUParticles3DEditor : public GPUParticles3DEditorBase { - GDCLASS(GPUParticles3DEditor, GPUParticles3DEditorBase); - - ConfirmationDialog *generate_aabb = nullptr; - SpinBox *generate_seconds = nullptr; - GPUParticles3D *node = nullptr; - - enum Menu { - MENU_OPTION_GENERATE_AABB, - MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, - MENU_OPTION_CLEAR_EMISSION_VOLUME, - MENU_OPTION_CONVERT_TO_CPU_PARTICLES, - MENU_OPTION_RESTART, - - }; - - void _generate_aabb(); - - void _menu_option(int); - - friend class GPUParticles3DEditorPlugin; - - virtual void _generate_emission_points() override; - -protected: - void _notification(int p_notification); - void _node_removed(Node *p_node); - -public: - void edit(GPUParticles3D *p_particles); - GPUParticles3DEditor(); -}; - -class GPUParticles3DEditorPlugin : public EditorPlugin { - GDCLASS(GPUParticles3DEditorPlugin, EditorPlugin); - - GPUParticles3DEditor *particles_editor = nullptr; - -public: - virtual String get_name() const override { return "GPUParticles3D"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - GPUParticles3DEditorPlugin(); - ~GPUParticles3DEditorPlugin(); -}; - -#endif // GPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index ed6c407f49..3abc3a8610 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -675,6 +675,7 @@ void Node3DEditorViewport::cancel_transform() { sp->set_global_transform(se->original); } + collision_reposition = false; finish_transform(); set_message(TTR("Transform Aborted."), 3); } @@ -1804,7 +1805,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (b->is_pressed()) { clicked_wants_append = b->is_shift_pressed(); - if (_edit.mode != TRANSFORM_NONE && _edit.instant) { + if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) { commit_transform(); break; // just commit the edit, stop processing the event so we don't deselect the object } @@ -2400,10 +2401,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { cancel_transform(); } if (!is_freelook_active() && !k->is_echo()) { - if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) { if (_edit.mode == TRANSFORM_NONE) { begin_transform(TRANSFORM_TRANSLATE, true); - } else if (_edit.instant) { + } else if (_edit.instant || collision_reposition) { commit_transform(); begin_transform(TRANSFORM_TRANSLATE, true); } @@ -2411,7 +2412,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) { if (_edit.mode == TRANSFORM_NONE) { begin_transform(TRANSFORM_ROTATE, true); - } else if (_edit.instant) { + } else if (_edit.instant || collision_reposition) { commit_transform(); begin_transform(TRANSFORM_ROTATE, true); } @@ -2419,11 +2420,23 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) { if (_edit.mode == TRANSFORM_NONE) { begin_transform(TRANSFORM_SCALE, true); - } else if (_edit.instant) { + } else if (_edit.instant || collision_reposition) { commit_transform(); begin_transform(TRANSFORM_SCALE, true); } } + if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", p_event) && editor_selection->get_selected_node_list().size() == 1 && !collision_reposition) { + if (_edit.mode == TRANSFORM_NONE || _edit.instant) { + if (_edit.mode == TRANSFORM_NONE) { + _compute_edit(_edit.mouse_pos); + } else { + commit_transform(); + _compute_edit(_edit.mouse_pos); + } + _edit.mode = TRANSFORM_TRANSLATE; + collision_reposition = true; + } + } } // Freelook doesn't work in orthogonal mode. @@ -3074,11 +3087,24 @@ void Node3DEditorViewport::_notification(int p_what) { } break; case NOTIFICATION_PHYSICS_PROCESS: { + if (collision_reposition) { + List<Node *> &selection = editor_selection->get_selected_node_list(); + + if (selection.size() == 1) { + Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get()); + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); + set_message(TTR("Translating:") + " (" + String::num(first_selected_node->get_global_position().x, snap_step_decimals) + ", " + + String::num(first_selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(first_selected_node->get_global_position().z, snap_step_decimals) + ")"); + first_selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, first_selected_node))); + } + } + if (!update_preview_node) { return; } if (preview_node->is_inside_tree()) { - preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos)); + preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node)); double snap = EDITOR_GET("interface/inspector/default_float_step"); int snap_step_decimals = Math::range_step_decimals(snap); set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " + @@ -3968,7 +3994,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { return; } - bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible; + bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition; for (int i = 0; i < 3; i++) { Transform3D axis_angle; if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) { @@ -3999,7 +4025,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { xform.orthonormalize(); xform.basis.scale(scale); RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); - RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); + RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); } void Node3DEditorViewport::set_state(const Dictionary &p_state) { @@ -4243,7 +4269,19 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, accept = p_accept; } -Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { +void _insert_rid_recursive(Node *node, HashSet<RID> &rids) { + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node); + if (co) { + rids.insert(co->get_rid()); + } + + for (int i = 0; i < node->get_child_count(); i++) { + Node *child = node->get_child(i); + _insert_rid_recursive(child, rids); + } +} + +Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const { const float MAX_DISTANCE = 50.0; const float FALLBACK_DISTANCE = 5.0; @@ -4252,13 +4290,28 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state(); + HashSet<RID> rids; + + if (!preview_node->is_inside_tree()) { + List<Node *> &selection = editor_selection->get_selected_node_list(); + + Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get()); + + Array children = first_selected_node->get_children(); + + if (first_selected_node) { + _insert_rid_recursive(first_selected_node, rids); + } + } + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.exclude = rids; ray_params.from = world_pos; ray_params.to = world_pos + world_ray * camera->get_far(); PhysicsDirectSpaceState3D::RayResult result; - if (ss->intersect_ray(ray_params, result) && preview_node->get_child_count() > 0) { - // Calculate an offset for the `preview_node` such that the its bounding box is on top of and touching the contact surface's plane. + if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) { + // Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane. // Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal. const Vector3 bb_basis_x = result.normal; @@ -4273,10 +4326,10 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z); // This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible. - const Transform3D bb_transform = Transform3D(bb_basis, preview_node->get_transform().origin); - const AABB preview_node_bb = _calculate_spatial_bounds(preview_node, true, &bb_transform); - // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `preview_node`'s origin at (0, 0, 0) to the correct AABB face. - const float offset_distance = -preview_node_bb.position.x; + const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_transform().origin); + const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform); + // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `p_node`'s origin at (0, 0, 0) to the correct AABB face. + const float offset_distance = -p_node_bb.position.x; // `result_offset` is in global space. const Vector3 result_offset = result.position + result.normal * offset_distance; @@ -4914,6 +4967,7 @@ void Node3DEditorViewport::commit_transform() { } undo_redo->commit_action(); + collision_reposition = false; finish_transform(); set_message(""); } @@ -5511,6 +5565,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p ED_SHORTCUT("spatial_editor/instant_translate", TTR("Begin Translate Transformation")); ED_SHORTCUT("spatial_editor/instant_rotate", TTR("Begin Rotate Transformation")); ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation")); + ED_SHORTCUT("spatial_editor/collision_reposition", TTR("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G); preview_camera = memnew(CheckBox); preview_camera->set_text(TTR("Preview")); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 57c743a1db..a46f054878 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -247,6 +247,7 @@ private: bool auto_orthogonal; bool lock_rotation; bool transform_gizmo_visible = true; + bool collision_reposition = false; real_t gizmo_scale; bool freelook_active; @@ -340,7 +341,6 @@ private: TRANSFORM_ROTATE, TRANSFORM_TRANSLATE, TRANSFORM_SCALE - }; enum TransformPlane { TRANSFORM_VIEW, @@ -473,7 +473,7 @@ private: void _list_select(Ref<InputEventMouseButton> b); Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; - Vector3 _get_instance_position(const Point2 &p_pos) const; + Vector3 _get_instance_position(const Point2 &p_pos, Node3D *p_node) const; static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level = false, const Transform3D *p_bounds_orientation = nullptr); Node *_sanitize_preview_node(Node *p_node) const; diff --git a/editor/plugins/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp new file mode 100644 index 0000000000..fa75cb476b --- /dev/null +++ b/editor/plugins/particles_editor_plugin.cpp @@ -0,0 +1,970 @@ +/**************************************************************************/ +/* particles_editor_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "particles_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "core/io/image_loader.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_file_dialog.h" +#include "editor/scene_tree_dock.h" +#include "scene/2d/cpu_particles_2d.h" +#include "scene/2d/gpu_particles_2d.h" +#include "scene/3d/cpu_particles_3d.h" +#include "scene/3d/gpu_particles_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" +#include "scene/gui/spin_box.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/particle_process_material.h" + +void ParticlesEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (handled_type.ends_with("2D")) { + add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); + } else if (handled_type.ends_with("3D")) { + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, toolbar); + } else { + DEV_ASSERT(false); + } + + menu->set_icon(menu->get_editor_theme_icon(handled_type)); + menu->set_text(handled_type); + + PopupMenu *popup = menu->get_popup(); + popup->add_shortcut(ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R), MENU_RESTART); + _add_menu_options(popup); + popup->add_item(conversion_option_name, MENU_OPTION_CONVERT); + } break; + } +} + +bool ParticlesEditorPlugin::need_show_lifetime_dialog(SpinBox *p_seconds) { + // Add one second to the default generation lifetime, since the progress is updated every second. + p_seconds->set_value(MAX(1.0, trunc(edited_node->get("lifetime").operator double()) + 1.0)); + + if (p_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. + return true; + } else { + // Generate the visibility rect/AABB immediately. + return false; + } +} + +void ParticlesEditorPlugin::_menu_callback(int p_idx) { + switch (p_idx) { + case MENU_OPTION_CONVERT: { + Node *converted_node = _convert_particles(); + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(conversion_option_name, UndoRedo::MERGE_DISABLE, edited_node); + SceneTreeDock::get_singleton()->replace_node(edited_node, converted_node); + ur->commit_action(false); + } break; + + case MENU_RESTART: { + edited_node->call("restart"); + } + } +} + +void ParticlesEditorPlugin::edit(Object *p_object) { + edited_node = Object::cast_to<Node>(p_object); +} + +bool ParticlesEditorPlugin::handles(Object *p_object) const { + return p_object->is_class(handled_type); +} + +void ParticlesEditorPlugin::make_visible(bool p_visible) { + toolbar->set_visible(p_visible); +} + +ParticlesEditorPlugin::ParticlesEditorPlugin() { + toolbar = memnew(HBoxContainer); + toolbar->hide(); + + menu = memnew(MenuButton); + menu->set_switch_on_hover(true); + toolbar->add_child(menu); + menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ParticlesEditorPlugin::_menu_callback)); +} + +// 2D ///////////////////////////////////////////// + +void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { + if (p_idx == MENU_GENERATE_VISIBILITY_RECT) { + if (need_show_lifetime_dialog(generate_seconds)) { + generate_visibility_rect->popup_centered(); + } else { + _generate_visibility_rect(); + } + } else { + Particles2DEditorPlugin::_menu_callback(p_idx); + } +} + +void GPUParticles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) { + Particles2DEditorPlugin::_add_menu_options(p_menu); + p_menu->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT); +} + +void Particles2DEditorPlugin::_file_selected(const String &p_file) { + source_emission_file = p_file; + emission_mask->popup_centered(); +} + +void Particles2DEditorPlugin::_get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size) { + Ref<Image> img; + img.instantiate(); + Error err = ImageLoader::load_image(source_emission_file, img); + ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'."); + + if (img->is_compressed()) { + img->decompress(); + } + img->convert(Image::FORMAT_RGBA8); + ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8); + Size2i s = img->get_size(); + ERR_FAIL_COND(s.width == 0 || s.height == 0); + + r_image_size = s; + + r_valid_positions.resize(s.width * s.height); + + EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected(); + + if (emode == EMISSION_MODE_BORDER_DIRECTED) { + r_valid_normals.resize(s.width * s.height); + } + + bool capture_colors = emission_colors->is_pressed(); + + if (capture_colors) { + r_valid_colors.resize(s.width * s.height * 4); + } + + int vpc = 0; + + { + Vector<uint8_t> img_data = img->get_data(); + const uint8_t *r = img_data.ptr(); + + for (int i = 0; i < s.width; i++) { + for (int j = 0; j < s.height; j++) { + uint8_t a = r[(j * s.width + i) * 4 + 3]; + + if (a > 128) { + if (emode == EMISSION_MODE_SOLID) { + if (capture_colors) { + r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; + r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; + r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; + r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; + } + r_valid_positions.write[vpc++] = Point2(i, j); + + } else { + bool on_border = false; + for (int x = i - 1; x <= i + 1; x++) { + for (int y = j - 1; y <= j + 1; y++) { + if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { + on_border = true; + break; + } + } + + if (on_border) { + break; + } + } + + if (on_border) { + r_valid_positions.write[vpc] = Point2(i, j); + + if (emode == EMISSION_MODE_BORDER_DIRECTED) { + Vector2 normal; + for (int x = i - 2; x <= i + 2; x++) { + for (int y = j - 2; y <= j + 2; y++) { + if (x == i && y == j) { + continue; + } + + if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) { + normal += Vector2(x - i, y - j).normalized(); + } + } + } + + normal.normalize(); + r_valid_normals.write[vpc] = normal; + } + + if (capture_colors) { + r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0]; + r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1]; + r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2]; + r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3]; + } + + vpc++; + } + } + } + } + } + } + + r_valid_positions.resize(vpc); + if (!r_valid_normals.is_empty()) { + r_valid_normals.resize(vpc); + } +} + +Particles2DEditorPlugin::Particles2DEditorPlugin() { + file = memnew(EditorFileDialog); + + List<String> ext; + ImageLoader::get_recognized_extensions(&ext); + for (const String &E : ext) { + file->add_filter("*." + E, E.to_upper()); + } + + file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + EditorNode::get_singleton()->get_gui_base()->add_child(file); + file->connect("file_selected", callable_mp(this, &Particles2DEditorPlugin::_file_selected)); + + emission_mask = memnew(ConfirmationDialog); + emission_mask->set_title(TTR("Load Emission Mask")); + + VBoxContainer *emvb = memnew(VBoxContainer); + emission_mask->add_child(emvb); + + emission_mask_mode = memnew(OptionButton); + emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID); + emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER); + emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED); + emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode); + + VBoxContainer *optionsvb = memnew(VBoxContainer); + emvb->add_margin_child(TTR("Options"), optionsvb); + + emission_mask_centered = memnew(CheckBox(TTR("Centered"))); + optionsvb->add_child(emission_mask_centered); + emission_colors = memnew(CheckBox(TTR("Capture Colors from Pixel"))); + optionsvb->add_child(emission_colors); + + EditorNode::get_singleton()->get_gui_base()->add_child(emission_mask); + + emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &Particles2DEditorPlugin::_generate_emission_mask)); +} + +void GPUParticles2DEditorPlugin::_selection_changed() { + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + if (selected_particles.is_empty() && selected_nodes.is_empty()) { + return; + } + + for (GPUParticles2D *particles : selected_particles) { + particles->set_show_visibility_rect(false); + } + selected_particles.clear(); + + for (Node *node : selected_nodes) { + GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(node); + if (selected_particle) { + selected_particle->set_show_visibility_rect(true); + selected_particles.push_back(selected_particle); + } + } +} + +void GPUParticles2DEditorPlugin::_generate_visibility_rect() { + GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node); + + double time = generate_seconds->get_value(); + + float running = 0.0; + + EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time)); + + bool was_emitting = particles->is_emitting(); + if (!was_emitting) { + particles->set_emitting(true); + OS::get_singleton()->delay_usec(1000); + } + + Rect2 rect; + while (running < time) { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + ep.step(TTR("Generating..."), int(running), true); + OS::get_singleton()->delay_usec(1000); + + Rect2 capture = particles->capture_rect(); + if (rect == Rect2()) { + rect = capture; + } else { + rect = rect.merge(capture); + } + + running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; + } + + if (!was_emitting) { + particles->set_emitting(false); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Generate Visibility Rect")); + undo_redo->add_do_method(particles, "set_visibility_rect", rect); + undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect()); + undo_redo->commit_action(); +} + +void GPUParticles2DEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed)); + } break; + } +} + +void Particles2DEditorPlugin::_menu_callback(int p_idx) { + if (p_idx == MENU_LOAD_EMISSION_MASK) { + file->popup_file_dialog(); + } else { + ParticlesEditorPlugin::_menu_callback(p_idx); + } +} + +void Particles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) { + p_menu->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK); +} + +Node *GPUParticles2DEditorPlugin::_convert_particles() { + GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node); + + CPUParticles2D *cpu_particles = memnew(CPUParticles2D); + cpu_particles->convert_from_particles(particles); + cpu_particles->set_name(particles->get_name()); + cpu_particles->set_transform(particles->get_transform()); + cpu_particles->set_visible(particles->is_visible()); + cpu_particles->set_process_mode(particles->get_process_mode()); + cpu_particles->set_z_index(particles->get_z_index()); + return cpu_particles; +} + +void GPUParticles2DEditorPlugin::_generate_emission_mask() { + GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node); + Ref<ParticleProcessMaterial> pm = particles->get_process_material(); + if (pm.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material")); + return; + } + + PackedVector2Array valid_positions; + PackedVector2Array valid_normals; + PackedByteArray valid_colors; + Vector2i image_size; + _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size); + + ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image..."); + + Vector<uint8_t> texdata; + + int vpc = valid_positions.size(); + int w = 2048; + int h = (vpc / 2048) + 1; + + texdata.resize(w * h * 2 * sizeof(float)); + + { + Vector2 offset; + if (emission_mask_centered->is_pressed()) { + offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5); + } + + uint8_t *tw = texdata.ptrw(); + float *twf = reinterpret_cast<float *>(tw); + for (int i = 0; i < vpc; i++) { + twf[i * 2 + 0] = valid_positions[i].x + offset.x; + twf[i * 2 + 1] = valid_positions[i].y + offset.y; + } + } + + Ref<Image> img; + img.instantiate(); + img->set_data(w, h, false, Image::FORMAT_RGF, texdata); + pm->set_emission_point_texture(ImageTexture::create_from_image(img)); + pm->set_emission_point_count(vpc); + + if (emission_colors->is_pressed()) { + Vector<uint8_t> colordata; + colordata.resize(w * h * 4); //use RG texture + + { + uint8_t *tw = colordata.ptrw(); + for (int i = 0; i < vpc * 4; i++) { + tw[i] = valid_colors[i]; + } + } + + img.instantiate(); + img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata); + pm->set_emission_color_texture(ImageTexture::create_from_image(img)); + } + + if (valid_normals.size()) { + pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + + Vector<uint8_t> normdata; + normdata.resize(w * h * 2 * sizeof(float)); //use RG texture + + { + uint8_t *tw = normdata.ptrw(); + float *twf = reinterpret_cast<float *>(tw); + for (int i = 0; i < vpc; i++) { + twf[i * 2 + 0] = valid_normals[i].x; + twf[i * 2 + 1] = valid_normals[i].y; + } + } + + img.instantiate(); + img->set_data(w, h, false, Image::FORMAT_RGF, normdata); + pm->set_emission_normal_texture(ImageTexture::create_from_image(img)); + + } else { + pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + } +} + +GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { + handled_type = "GPUParticles2D"; // TTR("GPUParticles2D") + conversion_option_name = TTR("Convert to CPUParticles2D"); + + generate_visibility_rect = memnew(ConfirmationDialog); + generate_visibility_rect->set_title(TTR("Generate Visibility Rect")); + + VBoxContainer *genvb = memnew(VBoxContainer); + generate_visibility_rect->add_child(genvb); + + generate_seconds = memnew(SpinBox); + generate_seconds->set_min(0.1); + generate_seconds->set_max(25); + generate_seconds->set_value(2); + genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds); + + EditorNode::get_singleton()->get_gui_base()->add_child(generate_visibility_rect); + + generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect)); +} + +Node *CPUParticles2DEditorPlugin::_convert_particles() { + CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node); + + GPUParticles2D *gpu_particles = memnew(GPUParticles2D); + gpu_particles->convert_from_particles(particles); + gpu_particles->set_name(particles->get_name()); + gpu_particles->set_transform(particles->get_transform()); + gpu_particles->set_visible(particles->is_visible()); + gpu_particles->set_process_mode(particles->get_process_mode()); + return gpu_particles; +} + +void CPUParticles2DEditorPlugin::_generate_emission_mask() { + CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node); + + PackedVector2Array valid_positions; + PackedVector2Array valid_normals; + PackedByteArray valid_colors; + Vector2i image_size; + _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size); + + ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image..."); + + int vpc = valid_positions.size(); + if (emission_colors->is_pressed()) { + PackedColorArray pca; + pca.resize(vpc); + Color *pcaw = pca.ptrw(); + for (int i = 0; i < vpc; i += 1) { + Color color; + color.r = valid_colors[i * 4 + 0] / 255.0f; + color.g = valid_colors[i * 4 + 1] / 255.0f; + color.b = valid_colors[i * 4 + 2] / 255.0f; + color.a = valid_colors[i * 4 + 3] / 255.0f; + pcaw[i] = color; + } + particles->set_emission_colors(pca); + } + + if (valid_normals.size()) { + particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS); + PackedVector2Array norms; + norms.resize(valid_normals.size()); + Vector2 *normsw = norms.ptrw(); + for (int i = 0; i < valid_normals.size(); i += 1) { + normsw[i] = valid_normals[i]; + } + particles->set_emission_normals(norms); + } else { + particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS); + } + + { + Vector2 offset; + if (emission_mask_centered->is_pressed()) { + offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5); + } + + PackedVector2Array points; + points.resize(valid_positions.size()); + Vector2 *pointsw = points.ptrw(); + for (int i = 0; i < valid_positions.size(); i += 1) { + pointsw[i] = valid_positions[i] + offset; + } + particles->set_emission_points(points); + } +} + +CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { + handled_type = "CPUParticles2D"; // TTR("CPUParticles2D") + conversion_option_name = TTR("Convert to GPUParticles2D"); +} + +// 3D ///////////////////////////////////////////// + +void Particles3DEditorPlugin::_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 = edited_node->get("emitting"); + if (!was_emitting) { + edited_node->set("emitting", true); + OS::get_singleton()->delay_usec(1000); + } + + AABB rect; + Callable capture_aabb = Callable(edited_node, "capture_aabb"); + + while (running < time) { + uint64_t ticks = OS::get_singleton()->get_ticks_usec(); + ep.step(TTR("Generating..."), int(running), true); + OS::get_singleton()->delay_usec(1000); + + AABB capture = capture_aabb.call(); + if (rect == AABB()) { + rect = capture; + } else { + rect.merge_with(capture); + } + + running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0; + } + + if (!was_emitting) { + edited_node->set("emitting", false); + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Generate Visibility AABB")); + ur->add_do_property(edited_node, "visibility_aabb", rect); + ur->add_undo_property(edited_node, "visibility_aabb", edited_node->get("visibility_aabb")); + ur->commit_action(); +} + +void Particles3DEditorPlugin::_node_selected(const NodePath &p_path) { + Node *sel = get_node(p_path); + if (!sel) { + return; + } + + if (!sel->is_class("Node3D")) { + EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name())); + return; + } + + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel); + if (!mi || mi->get_mesh().is_null()) { + EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name())); + return; + } + + geometry = mi->get_mesh()->get_faces(); + if (geometry.size() == 0) { + EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name())); + return; + } + + Transform3D geom_xform = edited_node->get("global_transform"); + geom_xform = geom_xform.affine_inverse() * mi->get_global_transform(); + int gc = geometry.size(); + Face3 *w = geometry.ptrw(); + + for (int i = 0; i < gc; i++) { + for (int j = 0; j < 3; j++) { + w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]); + } + } + emission_dialog->popup_centered(Size2(300, 130)); +} + +void Particles3DEditorPlugin::_menu_callback(int p_idx) { + switch (p_idx) { + case MENU_OPTION_GENERATE_AABB: { + if (need_show_lifetime_dialog(generate_seconds)) { + generate_aabb->popup_centered(); + } else { + _generate_aabb(); + } + } break; + + case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: { + if (_can_generate_points()) { + emission_tree_dialog->popup_scenetree_dialog(); + } + } break; + + default: { + ParticlesEditorPlugin::_menu_callback(p_idx); + } + } +} + +void Particles3DEditorPlugin::_add_menu_options(PopupMenu *p_menu) { + p_menu->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB); + p_menu->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE); +} + +bool Particles3DEditorPlugin::_generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals) { + bool use_normals = emission_fill->get_selected() == 1; + + if (emission_fill->get_selected() < 2) { + float area_accum = 0; + RBMap<float, int> triangle_area_map; + + for (int i = 0; i < geometry.size(); i++) { + float area = geometry[i].get_area(); + if (area < CMP_EPSILON) { + continue; + } + triangle_area_map[area_accum] = i; + area_accum += area; + } + + if (!triangle_area_map.size() || area_accum == 0) { + EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area.")); + return false; + } + + int emissor_count = emission_amount->get_value(); + + for (int i = 0; i < emissor_count; i++) { + float areapos = Math::random(0.0f, area_accum); + + RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); + ERR_FAIL_COND_V(!E, false); + int index = E->value; + ERR_FAIL_INDEX_V(index, geometry.size(), false); + + // ok FINALLY get face + Face3 face = geometry[index]; + //now compute some position inside the face... + + Vector3 pos = face.get_random_point_inside(); + + r_points.push_back(pos); + + if (use_normals) { + Vector3 normal = face.get_plane().normal; + r_normals.push_back(normal); + } + } + } else { + int gcount = geometry.size(); + + if (gcount == 0) { + EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces.")); + return false; + } + + const Face3 *r = geometry.ptr(); + + AABB aabb; + + for (int i = 0; i < gcount; i++) { + for (int j = 0; j < 3; j++) { + if (i == 0 && j == 0) { + aabb.position = r[i].vertex[j]; + } else { + aabb.expand_to(r[i].vertex[j]); + } + } + } + + int emissor_count = emission_amount->get_value(); + + for (int i = 0; i < emissor_count; i++) { + int attempts = 5; + + for (int j = 0; j < attempts; j++) { + Vector3 dir; + dir[Math::rand() % 3] = 1.0; + Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position; + + Vector3 ofsv = ofs + aabb.size * dir; + + //space it a little + ofs -= dir; + ofsv += dir; + + float max = -1e7, min = 1e7; + + for (int k = 0; k < gcount; k++) { + const Face3 &f3 = r[k]; + + Vector3 res; + if (f3.intersects_segment(ofs, ofsv, &res)) { + res -= ofs; + float d = dir.dot(res); + + if (d < min) { + min = d; + } + if (d > max) { + max = d; + } + } + } + + if (max < min) { + continue; //lost attempt + } + + float val = min + (max - min) * Math::randf(); + + Vector3 point = ofs + dir * val; + + r_points.push_back(point); + break; + } + } + } + return true; +} + +Particles3DEditorPlugin::Particles3DEditorPlugin() { + 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); + generate_seconds->set_min(0.1); + generate_seconds->set_max(25); + generate_seconds->set_value(2); + genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds); + + EditorNode::get_singleton()->get_gui_base()->add_child(generate_aabb); + + generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_aabb)); + + emission_tree_dialog = memnew(SceneTreeDialog); + Vector<StringName> valid_types; + valid_types.push_back("MeshInstance3D"); + emission_tree_dialog->set_valid_types(valid_types); + EditorNode::get_singleton()->get_gui_base()->add_child(emission_tree_dialog); + emission_tree_dialog->connect("selected", callable_mp(this, &Particles3DEditorPlugin::_node_selected)); + + emission_dialog = memnew(ConfirmationDialog); + emission_dialog->set_title(TTR("Create Emitter")); + EditorNode::get_singleton()->get_gui_base()->add_child(emission_dialog); + + VBoxContainer *emd_vb = memnew(VBoxContainer); + emission_dialog->add_child(emd_vb); + + emission_amount = memnew(SpinBox); + emission_amount->set_min(1); + emission_amount->set_max(100000); + emission_amount->set_value(512); + emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount); + + emission_fill = memnew(OptionButton); + emission_fill->add_item(TTR("Surface Points")); + emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); + emission_fill->add_item(TTR("Volume")); + emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill); + + emission_dialog->set_ok_button_text(TTR("Create")); + emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_emission_points)); +} + +Node *GPUParticles3DEditorPlugin::_convert_particles() { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node); + + CPUParticles3D *cpu_particles = memnew(CPUParticles3D); + cpu_particles->convert_from_particles(particles); + cpu_particles->set_name(particles->get_name()); + cpu_particles->set_transform(particles->get_transform()); + cpu_particles->set_visible(particles->is_visible()); + cpu_particles->set_process_mode(particles->get_process_mode()); + return cpu_particles; +} + +bool GPUParticles3DEditorPlugin::_can_generate_points() const { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node); + Ref<ParticleProcessMaterial> mat = particles->get_process_material(); + if (mat.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required.")); + return false; + } + return true; +} + +void GPUParticles3DEditorPlugin::_generate_emission_points() { + GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node); + + /// hacer codigo aca + Vector<Vector3> points; + Vector<Vector3> normals; + + if (!_generate(points, normals)) { + return; + } + + int point_count = points.size(); + + int w = 2048; + int h = (point_count / 2048) + 1; + + Vector<uint8_t> point_img; + point_img.resize(w * h * 3 * sizeof(float)); + + { + uint8_t *iw = point_img.ptrw(); + memset(iw, 0, w * h * 3 * sizeof(float)); + const Vector3 *r = points.ptr(); + float *wf = reinterpret_cast<float *>(iw); + for (int i = 0; i < point_count; i++) { + wf[i * 3 + 0] = r[i].x; + wf[i * 3 + 1] = r[i].y; + wf[i * 3 + 2] = r[i].z; + } + } + + Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); + Ref<ImageTexture> tex = ImageTexture::create_from_image(image); + + Ref<ParticleProcessMaterial> mat = particles->get_process_material(); + ERR_FAIL_COND(mat.is_null()); + + if (normals.size() > 0) { + mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + mat->set_emission_point_count(point_count); + mat->set_emission_point_texture(tex); + + Vector<uint8_t> point_img2; + point_img2.resize(w * h * 3 * sizeof(float)); + + { + uint8_t *iw = point_img2.ptrw(); + memset(iw, 0, w * h * 3 * sizeof(float)); + const Vector3 *r = normals.ptr(); + float *wf = reinterpret_cast<float *>(iw); + for (int i = 0; i < point_count; i++) { + wf[i * 3 + 0] = r[i].x; + wf[i * 3 + 1] = r[i].y; + wf[i * 3 + 2] = r[i].z; + } + } + + Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); + mat->set_emission_normal_texture(ImageTexture::create_from_image(image2)); + } else { + mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + mat->set_emission_point_count(point_count); + mat->set_emission_point_texture(tex); + } +} + +GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { + handled_type = "GPUParticles3D"; // TTR("GPUParticles3D") + conversion_option_name = TTR("Convert to CPUParticles3D"); +} + +Node *CPUParticles3DEditorPlugin::_convert_particles() { + CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node); + + GPUParticles3D *gpu_particles = memnew(GPUParticles3D); + gpu_particles->convert_from_particles(particles); + gpu_particles->set_name(particles->get_name()); + gpu_particles->set_transform(particles->get_transform()); + gpu_particles->set_visible(particles->is_visible()); + gpu_particles->set_process_mode(particles->get_process_mode()); + return gpu_particles; +} + +void CPUParticles3DEditorPlugin::_generate_emission_points() { + CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node); + + /// hacer codigo aca + Vector<Vector3> points; + Vector<Vector3> normals; + + if (!_generate(points, normals)) { + return; + } + + if (normals.is_empty()) { + particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS); + particles->set_emission_points(points); + } else { + particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS); + particles->set_emission_points(points); + particles->set_emission_normals(normals); + } +} + +CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { + handled_type = "CPUParticles3D"; // TTR("CPUParticles3D") + conversion_option_name = TTR("Convert to GPUParticles3D"); +} diff --git a/editor/plugins/particles_editor_plugin.h b/editor/plugins/particles_editor_plugin.h new file mode 100644 index 0000000000..0dd0f8afd1 --- /dev/null +++ b/editor/plugins/particles_editor_plugin.h @@ -0,0 +1,218 @@ +/**************************************************************************/ +/* particles_editor_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef PARTICLES_EDITOR_PLUGIN_H +#define PARTICLES_EDITOR_PLUGIN_H + +#include "editor/plugins/editor_plugin.h" + +class CheckBox; +class ConfirmationDialog; +class EditorFileDialog; +class GPUParticles2D; +class HBoxContainer; +class MenuButton; +class OptionButton; +class SceneTreeDialog; +class SpinBox; + +class ParticlesEditorPlugin : public EditorPlugin { + GDCLASS(ParticlesEditorPlugin, EditorPlugin); + +private: + enum { + MENU_OPTION_CONVERT, + MENU_RESTART + }; + + HBoxContainer *toolbar = nullptr; + MenuButton *menu = nullptr; + +protected: + String handled_type; + String conversion_option_name; + + Node *edited_node = nullptr; + + void _notification(int p_what); + + bool need_show_lifetime_dialog(SpinBox *p_seconds); + virtual void _menu_callback(int p_idx); + + virtual void _add_menu_options(PopupMenu *p_menu) {} + virtual Node *_convert_particles() = 0; + +public: + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + ParticlesEditorPlugin(); +}; + +// 2D ///////////////////////////////////////////// + +class Particles2DEditorPlugin : public ParticlesEditorPlugin { + GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin); + +protected: + enum { + MENU_LOAD_EMISSION_MASK = 100, + }; + + enum EmissionMode { + EMISSION_MODE_SOLID, + EMISSION_MODE_BORDER, + EMISSION_MODE_BORDER_DIRECTED + }; + + EditorFileDialog *file = nullptr; + ConfirmationDialog *emission_mask = nullptr; + OptionButton *emission_mask_mode = nullptr; + CheckBox *emission_mask_centered = nullptr; + CheckBox *emission_colors = nullptr; + String source_emission_file; + + virtual void _menu_callback(int p_idx) override; + virtual void _add_menu_options(PopupMenu *p_menu) override; + + void _file_selected(const String &p_file); + void _get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size); + virtual void _generate_emission_mask() = 0; + +public: + Particles2DEditorPlugin(); +}; + +class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin { + GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin); + + enum { + MENU_GENERATE_VISIBILITY_RECT = 200, + }; + + List<GPUParticles2D *> selected_particles; + + ConfirmationDialog *generate_visibility_rect = nullptr; + SpinBox *generate_seconds = nullptr; + + void _selection_changed(); + void _generate_visibility_rect(); + +protected: + void _notification(int p_what); + + void _menu_callback(int p_idx) override; + void _add_menu_options(PopupMenu *p_menu) override; + + Node *_convert_particles() override; + + void _generate_emission_mask() override; + +public: + GPUParticles2DEditorPlugin(); +}; + +class CPUParticles2DEditorPlugin : public Particles2DEditorPlugin { + GDCLASS(CPUParticles2DEditorPlugin, Particles2DEditorPlugin); + +protected: + Node *_convert_particles() override; + + void _generate_emission_mask() override; + +public: + CPUParticles2DEditorPlugin(); +}; + +// 3D ///////////////////////////////////////////// + +class Particles3DEditorPlugin : public ParticlesEditorPlugin { + GDCLASS(Particles3DEditorPlugin, ParticlesEditorPlugin); + + enum { + MENU_OPTION_GENERATE_AABB = 300, + MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE, + }; + + ConfirmationDialog *generate_aabb = nullptr; + SpinBox *generate_seconds = nullptr; + + SceneTreeDialog *emission_tree_dialog = nullptr; + ConfirmationDialog *emission_dialog = nullptr; + SpinBox *emission_amount = nullptr; + OptionButton *emission_fill = nullptr; + + void _generate_aabb(); + void _node_selected(const NodePath &p_path); + +protected: + Vector<Face3> geometry; + + virtual void _menu_callback(int p_idx) override; + virtual void _add_menu_options(PopupMenu *p_menu) override; + + bool _generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals); + virtual bool _can_generate_points() const = 0; + virtual void _generate_emission_points() = 0; + +public: + Particles3DEditorPlugin(); +}; + +class GPUParticles3DEditorPlugin : public Particles3DEditorPlugin { + GDCLASS(GPUParticles3DEditorPlugin, Particles3DEditorPlugin); + +protected: + Node *_convert_particles() override; + + bool _can_generate_points() const override; + void _generate_emission_points() override; + +public: + GPUParticles3DEditorPlugin(); +}; + +class CPUParticles3DEditorPlugin : public Particles3DEditorPlugin { + GDCLASS(CPUParticles3DEditorPlugin, Particles3DEditorPlugin); + +protected: + Node *_convert_particles() override; + + bool _can_generate_points() const override { return true; } + void _generate_emission_points() override; + +public: + CPUParticles3DEditorPlugin(); +}; + +#endif // PARTICLES_EDITOR_PLUGIN_H |