From b0d5d82c429759ef899478712c2a0f3e2fec1f22 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Mon, 7 Jul 2025 00:33:28 -0700 Subject: [PATCH] Move 2D and 3D particle editors to the 2D and 3D folders --- editor/register_editor_types.cpp | 3 +- .../scene/2d/particles_2d_editor_plugin.cpp | 540 ++++++++++ editor/scene/2d/particles_2d_editor_plugin.h | 109 ++ .../scene/3d/particles_3d_editor_plugin.cpp | 461 +++++++++ editor/scene/3d/particles_3d_editor_plugin.h | 92 ++ editor/scene/particles_editor_plugin.cpp | 928 ------------------ editor/scene/particles_editor_plugin.h | 140 --- 7 files changed, 1204 insertions(+), 1069 deletions(-) create mode 100644 editor/scene/2d/particles_2d_editor_plugin.cpp create mode 100644 editor/scene/2d/particles_2d_editor_plugin.h create mode 100644 editor/scene/3d/particles_3d_editor_plugin.cpp create mode 100644 editor/scene/3d/particles_3d_editor_plugin.h diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index b46299bcd09..7204e711b78 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -77,6 +77,7 @@ #include "editor/scene/2d/camera_2d_editor_plugin.h" #include "editor/scene/2d/light_occluder_2d_editor_plugin.h" #include "editor/scene/2d/line_2d_editor_plugin.h" +#include "editor/scene/2d/particles_2d_editor_plugin.h" #include "editor/scene/2d/path_2d_editor_plugin.h" #include "editor/scene/2d/physics/cast_2d_editor_plugin.h" #include "editor/scene/2d/physics/collision_polygon_2d_editor_plugin.h" @@ -93,6 +94,7 @@ #include "editor/scene/3d/multimesh_editor_plugin.h" #include "editor/scene/3d/node_3d_editor_gizmos.h" #include "editor/scene/3d/occluder_instance_3d_editor_plugin.h" +#include "editor/scene/3d/particles_3d_editor_plugin.h" #include "editor/scene/3d/path_3d_editor_plugin.h" #include "editor/scene/3d/physics/physical_bone_3d_editor_plugin.h" #include "editor/scene/3d/polygon_3d_editor_plugin.h" @@ -108,7 +110,6 @@ #include "editor/scene/gui/theme_editor_plugin.h" #include "editor/scene/material_editor_plugin.h" #include "editor/scene/packed_scene_editor_plugin.h" -#include "editor/scene/particles_editor_plugin.h" #include "editor/scene/resource_preloader_editor_plugin.h" #include "editor/scene/sprite_frames_editor_plugin.h" #include "editor/scene/texture/bit_map_editor_plugin.h" diff --git a/editor/scene/2d/particles_2d_editor_plugin.cpp b/editor/scene/2d/particles_2d_editor_plugin.cpp new file mode 100644 index 00000000000..04e191aab14 --- /dev/null +++ b/editor/scene/2d/particles_2d_editor_plugin.cpp @@ -0,0 +1,540 @@ +/**************************************************************************/ +/* 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 "particles_2d_editor_plugin.h" + +#include "core/io/image_loader.h" +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_file_dialog.h" +#include "scene/2d/cpu_particles_2d.h" +#include "scene/2d/gpu_particles_2d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/option_button.h" +#include "scene/gui/popup_menu.h" +#include "scene/gui/spin_box.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/particle_process_material.h" + +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 Particles2DEditorPlugin::_set_show_gizmos(Node *p_node, bool p_show) { + GPUParticles2D *gpu_particles = Object::cast_to(p_node); + if (gpu_particles) { + gpu_particles->set_show_gizmos(p_show); + } + CPUParticles2D *cpu_particles = Object::cast_to(p_node); + if (cpu_particles) { + cpu_particles->set_show_gizmos(p_show); + } + + // The `selection_changed` signal is deferred. A node could be deleted before the signal is emitted. + if (p_show) { + p_node->connect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed).bind(p_node)); + } else { + p_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed)); + } +} + +void Particles2DEditorPlugin::_selection_changed() { + List current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list(); + if (selected_particles.is_empty() && current_selection.is_empty()) { + return; + } + + // Turn gizmos off for nodes that are no longer selected. + for (List::Element *E = selected_particles.front(); E;) { + Node *node = E->get(); + List::Element *N = E->next(); + if (current_selection.find(node) == nullptr) { + _set_show_gizmos(node, false); + selected_particles.erase(E); + } + E = N; + } + + // Turn gizmos on for nodes that are newly selected. + for (Node *node : current_selection) { + if (selected_particles.find(node) == nullptr) { + _set_show_gizmos(node, true); + selected_particles.push_back(node); + } + } +} + +void Particles2DEditorPlugin::_node_removed(Node *p_node) { + List::Element *E = selected_particles.find(p_node); + if (E) { + _set_show_gizmos(E->get(), false); + selected_particles.erase(E); + } +} + +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 Particles2DEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &Particles2DEditorPlugin::_selection_changed)); + } break; + } +} + +void Particles2DEditorPlugin::_menu_callback(int p_idx) { + if (p_idx == MENU_LOAD_EMISSION_MASK) { + GPUParticles2D *particles = Object::cast_to(edited_node); + if (particles && particles->get_process_material().is_null()) { + EditorNode::get_singleton()->show_warning(TTR("Loading emission mask requires ParticleProcessMaterial.")); + return; + } + + 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(); + ERR_FAIL_COND(pm.is_null()); + + 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..."); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Load Emission Mask")); + ParticleProcessMaterial *pmptr = pm.ptr(); + + 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); + undo_redo->add_do_property(pmptr, "emission_point_texture", ImageTexture::create_from_image(img)); + undo_redo->add_undo_property(pmptr, "emission_point_texture", pm->get_emission_point_texture()); + undo_redo->add_do_property(pmptr, "emission_point_count", vpc); + undo_redo->add_undo_property(pmptr, "emission_point_count", pm->get_emission_point_count()); + + 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); + undo_redo->add_do_property(pmptr, "emission_color_texture", ImageTexture::create_from_image(img)); + undo_redo->add_undo_property(pmptr, "emission_color_texture", pm->get_emission_color_texture()); + } + + if (!valid_normals.is_empty()) { + undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape()); + 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); + undo_redo->add_do_property(pmptr, "emission_normal_texture", ImageTexture::create_from_image(img)); + undo_redo->add_undo_property(pmptr, "emission_normal_texture", pm->get_emission_normal_texture()); + } else { + undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape()); + } + undo_redo->commit_action(); +} + +GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { + handled_type = TTRC("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..."); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Load Emission Mask")); + + 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; + } + undo_redo->add_do_property(particles, "emission_colors", pca); + undo_redo->add_undo_property(particles, "emission_colors", particles->get_emission_colors()); + } + + if (!valid_normals.is_empty()) { + undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS); + undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); + 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]; + } + undo_redo->add_do_property(particles, "emission_normals", norms); + undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals()); + } else { + undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_POINTS); + undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); + } + + { + 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; + } + undo_redo->add_do_property(particles, "emission_points", points); + undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_points()); + } + undo_redo->commit_action(); +} + +CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { + handled_type = TTRC("CPUParticles2D"); + conversion_option_name = TTR("Convert to GPUParticles2D"); +} diff --git a/editor/scene/2d/particles_2d_editor_plugin.h b/editor/scene/2d/particles_2d_editor_plugin.h new file mode 100644 index 00000000000..f4b2f1c95e2 --- /dev/null +++ b/editor/scene/2d/particles_2d_editor_plugin.h @@ -0,0 +1,109 @@ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "editor/scene/particles_editor_plugin.h" + +class EditorFileDialog; + +class Particles2DEditorPlugin : public ParticlesEditorPlugin { + GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin); + +protected: + enum { + MENU_LOAD_EMISSION_MASK = 100, + }; + + List selected_particles; + + 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; + void _notification(int p_what); + void _set_show_gizmos(Node *p_node, bool p_show); + void _selection_changed(); + void _node_removed(Node *p_node); + +public: + Particles2DEditorPlugin(); +}; + +class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin { + GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin); + + enum { + MENU_GENERATE_VISIBILITY_RECT = 200, + }; + + ConfirmationDialog *generate_visibility_rect = nullptr; + SpinBox *generate_seconds = nullptr; + + void _generate_visibility_rect(); + +protected: + 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(); +}; diff --git a/editor/scene/3d/particles_3d_editor_plugin.cpp b/editor/scene/3d/particles_3d_editor_plugin.cpp new file mode 100644 index 00000000000..ff372333622 --- /dev/null +++ b/editor/scene/3d/particles_3d_editor_plugin.cpp @@ -0,0 +1,461 @@ +/**************************************************************************/ +/* 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 "particles_3d_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/scene/scene_tree_editor.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/option_button.h" +#include "scene/gui/spin_box.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/particle_process_material.h" + +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.is_empty()) { + 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_accessibility_name(TTRC("Generation Time (sec)")); + 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_accessibility_name(TTRC("Emission Points:")); + 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->set_accessibility_name(TTRC("Emission Source:")); + 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()); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Create Emission Points")); + ParticleProcessMaterial *matptr = mat.ptr(); + + if (!normals.is_empty()) { + undo_redo->add_do_property(matptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + undo_redo->add_undo_property(matptr, "emission_shape", matptr->get_emission_shape()); + + 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)); + undo_redo->add_do_property(matptr, "emission_normal_texture", ImageTexture::create_from_image(image2)); + undo_redo->add_undo_property(matptr, "emission_normal_texture", matptr->get_emission_normal_texture()); + } else { + undo_redo->add_do_property(matptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + undo_redo->add_undo_property(matptr, "emission_shape", matptr->get_emission_shape()); + } + undo_redo->add_do_property(matptr, "emission_point_count", point_count); + undo_redo->add_undo_property(matptr, "emission_point_count", matptr->get_emission_point_count()); + undo_redo->add_do_property(matptr, "emission_point_texture", tex); + undo_redo->add_undo_property(matptr, "emission_point_texture", matptr->get_emission_point_texture()); + undo_redo->commit_action(); +} + +GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { + handled_type = TTRC("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; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Create Emission Points")); + + if (normals.is_empty()) { + undo_redo->add_do_property(particles, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); + undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); + } else { + undo_redo->add_do_property(particles, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); + undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); + undo_redo->add_do_property(particles, "emission_normals", normals); + undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals()); + } + undo_redo->add_do_property(particles, "emission_points", points); + undo_redo->add_undo_property(particles, "emission_points", particles->get_emission_points()); + undo_redo->commit_action(); +} + +CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { + handled_type = TTRC("CPUParticles3D"); + conversion_option_name = TTR("Convert to GPUParticles3D"); +} diff --git a/editor/scene/3d/particles_3d_editor_plugin.h b/editor/scene/3d/particles_3d_editor_plugin.h new file mode 100644 index 00000000000..74f8bd77bef --- /dev/null +++ b/editor/scene/3d/particles_3d_editor_plugin.h @@ -0,0 +1,92 @@ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#pragma once + +#include "editor/scene/particles_editor_plugin.h" + +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(); +}; diff --git a/editor/scene/particles_editor_plugin.cpp b/editor/scene/particles_editor_plugin.cpp index 6bd97057205..f48a5bf6a76 100644 --- a/editor/scene/particles_editor_plugin.cpp +++ b/editor/scene/particles_editor_plugin.cpp @@ -30,23 +30,12 @@ #include "particles_editor_plugin.h" -#include "canvas_item_editor_plugin.h" -#include "core/io/image_loader.h" #include "editor/docks/scene_tree_dock.h" -#include "editor/editor_node.h" #include "editor/editor_undo_redo_manager.h" -#include "editor/gui/editor_file_dialog.h" #include "editor/settings/editor_settings.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/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) { @@ -123,920 +112,3 @@ ParticlesEditorPlugin::ParticlesEditorPlugin() { 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 Particles2DEditorPlugin::_set_show_gizmos(Node *p_node, bool p_show) { - GPUParticles2D *gpu_particles = Object::cast_to(p_node); - if (gpu_particles) { - gpu_particles->set_show_gizmos(p_show); - } - CPUParticles2D *cpu_particles = Object::cast_to(p_node); - if (cpu_particles) { - cpu_particles->set_show_gizmos(p_show); - } - - // The `selection_changed` signal is deferred. A node could be deleted before the signal is emitted. - if (p_show) { - p_node->connect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed).bind(p_node)); - } else { - p_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Particles2DEditorPlugin::_node_removed)); - } -} - -void Particles2DEditorPlugin::_selection_changed() { - List current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list(); - if (selected_particles.is_empty() && current_selection.is_empty()) { - return; - } - - // Turn gizmos off for nodes that are no longer selected. - for (List::Element *E = selected_particles.front(); E;) { - Node *node = E->get(); - List::Element *N = E->next(); - if (current_selection.find(node) == nullptr) { - _set_show_gizmos(node, false); - selected_particles.erase(E); - } - E = N; - } - - // Turn gizmos on for nodes that are newly selected. - for (Node *node : current_selection) { - if (selected_particles.find(node) == nullptr) { - _set_show_gizmos(node, true); - selected_particles.push_back(node); - } - } -} - -void Particles2DEditorPlugin::_node_removed(Node *p_node) { - List::Element *E = selected_particles.find(p_node); - if (E) { - _set_show_gizmos(E->get(), false); - selected_particles.erase(E); - } -} - -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 Particles2DEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &Particles2DEditorPlugin::_selection_changed)); - } break; - } -} - -void Particles2DEditorPlugin::_menu_callback(int p_idx) { - if (p_idx == MENU_LOAD_EMISSION_MASK) { - GPUParticles2D *particles = Object::cast_to(edited_node); - if (particles && particles->get_process_material().is_null()) { - EditorNode::get_singleton()->show_warning(TTR("Loading emission mask requires ParticleProcessMaterial.")); - return; - } - - 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(); - ERR_FAIL_COND(pm.is_null()); - - 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..."); - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Load Emission Mask")); - ParticleProcessMaterial *pmptr = pm.ptr(); - - 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); - undo_redo->add_do_property(pmptr, "emission_point_texture", ImageTexture::create_from_image(img)); - undo_redo->add_undo_property(pmptr, "emission_point_texture", pm->get_emission_point_texture()); - undo_redo->add_do_property(pmptr, "emission_point_count", vpc); - undo_redo->add_undo_property(pmptr, "emission_point_count", pm->get_emission_point_count()); - - 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); - undo_redo->add_do_property(pmptr, "emission_color_texture", ImageTexture::create_from_image(img)); - undo_redo->add_undo_property(pmptr, "emission_color_texture", pm->get_emission_color_texture()); - } - - if (!valid_normals.is_empty()) { - undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape()); - 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); - undo_redo->add_do_property(pmptr, "emission_normal_texture", ImageTexture::create_from_image(img)); - undo_redo->add_undo_property(pmptr, "emission_normal_texture", pm->get_emission_normal_texture()); - } else { - undo_redo->add_do_property(pmptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); - undo_redo->add_undo_property(pmptr, "emission_shape", pm->get_emission_shape()); - } - undo_redo->commit_action(); -} - -GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { - handled_type = TTRC("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..."); - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Load Emission Mask")); - - 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; - } - undo_redo->add_do_property(particles, "emission_colors", pca); - undo_redo->add_undo_property(particles, "emission_colors", particles->get_emission_colors()); - } - - if (!valid_normals.is_empty()) { - undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS); - undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); - 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]; - } - undo_redo->add_do_property(particles, "emission_normals", norms); - undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals()); - } else { - undo_redo->add_do_property(particles, "emission_shape", CPUParticles2D::EMISSION_SHAPE_POINTS); - undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); - } - - { - 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; - } - undo_redo->add_do_property(particles, "emission_points", points); - undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_points()); - } - undo_redo->commit_action(); -} - -CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { - handled_type = TTRC("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.is_empty()) { - 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_accessibility_name(TTRC("Generation Time (sec)")); - 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_accessibility_name(TTRC("Emission Points:")); - 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->set_accessibility_name(TTRC("Emission Source:")); - 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()); - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Create Emission Points")); - ParticleProcessMaterial *matptr = mat.ptr(); - - if (!normals.is_empty()) { - undo_redo->add_do_property(matptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - undo_redo->add_undo_property(matptr, "emission_shape", matptr->get_emission_shape()); - - 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)); - undo_redo->add_do_property(matptr, "emission_normal_texture", ImageTexture::create_from_image(image2)); - undo_redo->add_undo_property(matptr, "emission_normal_texture", matptr->get_emission_normal_texture()); - } else { - undo_redo->add_do_property(matptr, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); - undo_redo->add_undo_property(matptr, "emission_shape", matptr->get_emission_shape()); - } - undo_redo->add_do_property(matptr, "emission_point_count", point_count); - undo_redo->add_undo_property(matptr, "emission_point_count", matptr->get_emission_point_count()); - undo_redo->add_do_property(matptr, "emission_point_texture", tex); - undo_redo->add_undo_property(matptr, "emission_point_texture", matptr->get_emission_point_texture()); - undo_redo->commit_action(); -} - -GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { - handled_type = TTRC("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; - } - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Create Emission Points")); - - if (normals.is_empty()) { - undo_redo->add_do_property(particles, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_POINTS); - undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); - } else { - undo_redo->add_do_property(particles, "emission_shape", ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS); - undo_redo->add_undo_property(particles, "emission_shape", particles->get_emission_shape()); - undo_redo->add_do_property(particles, "emission_normals", normals); - undo_redo->add_undo_property(particles, "emission_normals", particles->get_emission_normals()); - } - undo_redo->add_do_property(particles, "emission_points", points); - undo_redo->add_undo_property(particles, "emission_points", particles->get_emission_points()); - undo_redo->commit_action(); -} - -CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { - handled_type = TTRC("CPUParticles3D"); - conversion_option_name = TTR("Convert to GPUParticles3D"); -} diff --git a/editor/scene/particles_editor_plugin.h b/editor/scene/particles_editor_plugin.h index d2f6187ac17..a3da0e210b7 100644 --- a/editor/scene/particles_editor_plugin.h +++ b/editor/scene/particles_editor_plugin.h @@ -34,9 +34,6 @@ class CheckBox; class ConfirmationDialog; -class EditorFileDialog; -class GPUParticles2D; -class CPUParticles2D; class HBoxContainer; class MenuButton; class OptionButton; @@ -76,140 +73,3 @@ public: ParticlesEditorPlugin(); }; - -// 2D ///////////////////////////////////////////// - -class Particles2DEditorPlugin : public ParticlesEditorPlugin { - GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin); - -protected: - enum { - MENU_LOAD_EMISSION_MASK = 100, - }; - - List selected_particles; - - 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; - void _notification(int p_what); - void _set_show_gizmos(Node *p_node, bool p_show); - void _selection_changed(); - void _node_removed(Node *p_node); - -public: - Particles2DEditorPlugin(); -}; - -class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin { - GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin); - - enum { - MENU_GENERATE_VISIBILITY_RECT = 200, - }; - - ConfirmationDialog *generate_visibility_rect = nullptr; - SpinBox *generate_seconds = nullptr; - - void _generate_visibility_rect(); - -protected: - 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(); -};