diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index d7770f2cd51..04ee95457c4 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -11,6 +11,12 @@ $DOCS_URL/tutorials/3d/particles/index.html + + + + Returns the axis-aligned bounding box that contains all the particles that are active in the current frame. + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d88fb134f16..95b3c30d1b0 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6737,10 +6737,6 @@ EditorNode::EditorNode() { ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G); ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G); - // Used in the GPUParticles/CPUParticles 2D/3D editor plugins. - // The shortcut is Ctrl + R even on macOS, as Cmd + R is used to run the current scene on macOS. - ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R); - FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename")); _update_vsync_mode(); 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 4869a202d76..00000000000 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_2d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "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(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 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 valid_positions; - Vector valid_normals; - Vector 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 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 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 645e6d83450..00000000000 --- a/editor/plugins/cpu_particles_2d_editor_plugin.h +++ /dev/null @@ -1,94 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_2d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef 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 e0ce330813a..00000000000 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_3d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "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 points; - Vector 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(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 2a1ea93ac8d..00000000000 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ /dev/null @@ -1,85 +0,0 @@ -/**************************************************************************/ -/* cpu_particles_3d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef 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 1b68b55ff66..00000000000 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_2d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "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(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 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(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 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 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 valid_positions; - Vector valid_normals; - Vector 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 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 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(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 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 normdata; - normdata.resize(w * h * 2 * sizeof(float)); //use RG texture - - { - uint8_t *tw = normdata.ptrw(); - float *twf = reinterpret_cast(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 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 658e4d87e5f..00000000000 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_2d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef 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 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 e711f44ccf4..00000000000 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ /dev/null @@ -1,461 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_3d_editor_plugin.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "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 &points, Vector &normals) { - bool use_normals = emission_fill->get_selected() == 1; - - if (emission_fill->get_selected() < 2) { - float area_accum = 0; - RBMap 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::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(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 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 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 points; - Vector normals; - - if (!_generate(points, normals)) { - return; - } - - int point_count = points.size(); - - int w = 2048; - int h = (point_count / 2048) + 1; - - Vector 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(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 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); - Ref tex = ImageTexture::create_from_image(image); - - Ref 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 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(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 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(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 1295836b5f4..00000000000 --- a/editor/plugins/gpu_particles_3d_editor_plugin.h +++ /dev/null @@ -1,118 +0,0 @@ -/**************************************************************************/ -/* gpu_particles_3d_editor_plugin.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef 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 geometry; - - bool _generate(Vector &points, Vector &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/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp new file mode 100644 index 00000000000..34f5dcf9635 --- /dev/null +++ b/editor/plugins/particles_editor_plugin.cpp @@ -0,0 +1,968 @@ +/**************************************************************************/ +/* particles_editor_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "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(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 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 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 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 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(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(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(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(edited_node); + Ref 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 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(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 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 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 normdata; + normdata.resize(w * h * 2 * sizeof(float)); //use RG texture + + { + uint8_t *tw = normdata.ptrw(); + float *twf = reinterpret_cast(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(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(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(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 &r_points, Vector &r_normals) { + bool use_normals = emission_fill->get_selected() == 1; + + if (emission_fill->get_selected() < 2) { + float area_accum = 0; + RBMap 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::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 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(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(edited_node); + Ref 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(edited_node); + + /// hacer codigo aca + Vector points; + Vector normals; + + if (!_generate(points, normals)) { + return; + } + + int point_count = points.size(); + + int w = 2048; + int h = (point_count / 2048) + 1; + + Vector 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(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 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); + Ref tex = ImageTexture::create_from_image(image); + + Ref 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 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(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 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(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(edited_node); + + /// hacer codigo aca + Vector points; + Vector 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 00000000000..d4459765c46 --- /dev/null +++ b/editor/plugins/particles_editor_plugin.h @@ -0,0 +1,216 @@ +/**************************************************************************/ +/* particles_editor_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef 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 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 geometry; + + virtual void _menu_callback(int p_idx) override; + virtual void _add_menu_options(PopupMenu *p_menu) override; + + bool _generate(Vector &r_points, Vector &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 diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 19aeeb0612d..c4ebca7308b 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -76,15 +76,11 @@ #include "editor/plugins/collision_polygon_2d_editor_plugin.h" #include "editor/plugins/collision_shape_2d_editor_plugin.h" #include "editor/plugins/control_editor_plugin.h" -#include "editor/plugins/cpu_particles_2d_editor_plugin.h" -#include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/plugins/font_config_plugin.h" -#include "editor/plugins/gpu_particles_2d_editor_plugin.h" -#include "editor/plugins/gpu_particles_3d_editor_plugin.h" #include "editor/plugins/gpu_particles_collision_sdf_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" #include "editor/plugins/gradient_texture_2d_editor_plugin.h" @@ -105,6 +101,7 @@ #include "editor/plugins/occluder_instance_3d_editor_plugin.h" #include "editor/plugins/packed_scene_editor_plugin.h" #include "editor/plugins/parallax_background_editor_plugin.h" +#include "editor/plugins/particles_editor_plugin.h" #include "editor/plugins/path_2d_editor_plugin.h" #include "editor/plugins/path_3d_editor_plugin.h" #include "editor/plugins/physical_bone_3d_editor_plugin.h" diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index acbc443a93b..5b84bf903f1 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -1479,6 +1479,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh); ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart); + ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");