From c3df21e13bf64e2a62677aae34d428037171738a Mon Sep 17 00:00:00 2001 From: Patrick Exner Date: Fri, 5 Jan 2024 00:11:00 +0100 Subject: [PATCH 1/2] Add emission shape gizmo plugin for CPU and GPU 3D Particles Co-authored-by: Kasper Frandsen --- doc/classes/ParticleProcessMaterial.xml | 7 + ...rticles_3d_emission_shape_gizmo_plugin.cpp | 748 ++++++++++++++++++ ...particles_3d_emission_shape_gizmo_plugin.h | 57 ++ editor/plugins/node_3d_editor_plugin.cpp | 2 + scene/3d/cpu_particles_3d.cpp | 7 + scene/3d/gpu_particles_3d.cpp | 19 +- scene/resources/particle_process_material.cpp | 11 + 7 files changed, 850 insertions(+), 1 deletion(-) create mode 100644 editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp create mode 100644 editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.h diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 28c60194c8a2..1408e6f4f159 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -409,6 +409,13 @@ A pivot point used to calculate radial and orbital velocity of particles. + + + + Emitted when the emission shape of the material has been changed in its offset, size, scale, or orientation. + + + Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set initial velocity properties. diff --git a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp new file mode 100644 index 000000000000..ebc1ee322460 --- /dev/null +++ b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp @@ -0,0 +1,748 @@ +/**************************************************************************/ +/* particles_3d_emission_shape_gizmo_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_emission_shape_gizmo_plugin.h" + +#include "core/math/transform_3d.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_string_names.h" +#include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/cpu_particles_3d.h" +#include "scene/3d/gpu_particles_3d.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/particle_process_material.h" + +Particles3DEmissionShapeGizmoPlugin::Particles3DEmissionShapeGizmoPlugin() { + helper.instantiate(); + + Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/particles_emission_shape", Color(0.5, 0.7, 1)); + create_material("particles_emission_shape_material", gizmo_color); + + create_handle_material("handles"); +} + +bool Particles3DEmissionShapeGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) || Object::cast_to(p_spatial) != nullptr; +} + +String Particles3DEmissionShapeGizmoPlugin::get_gizmo_name() const { + return "Particles3DEmissionShape"; +} + +int Particles3DEmissionShapeGizmoPlugin::get_priority() const { + return -1; +} + +bool Particles3DEmissionShapeGizmoPlugin::is_selectable_when_hidden() const { + return true; +} + +String Particles3DEmissionShapeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + if (Object::cast_to(p_gizmo->get_node_3d())) { + GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + Ref mat = particles->get_process_material(); + ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); + + if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { + return "Radius"; + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { + switch (p_id) { + case 0: + case 1: + return "Size X"; + case 2: + case 3: + return "Size Y"; + case 4: + case 5: + return "Size Z"; + } + return ""; + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { + if (p_id == 0) { + return "Axis"; + } else if (p_id == 1) { + return "Height"; + } else if (p_id == 2) { + return "Radius"; + } else { + return "Inner Radius"; + } + } + } else if (Object::cast_to(p_gizmo->get_node_3d())) { + CPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + CPUParticles3D::EmissionShape shape = particles->get_emission_shape(); + + if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { + return "Radius"; + } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { + switch (p_id) { + case 0: + case 1: + return "Size X"; + case 2: + case 3: + return "Size Y"; + case 4: + case 5: + return "Size Z"; + } + return ""; + } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { + if (p_id == 0) { + return "Axis"; + } else if (p_id == 1) { + return "Height"; + } else if (p_id == 2) { + return "Radius"; + } else { + return "Inner Radius"; + } + } + } + + return ""; +} + +Variant Particles3DEmissionShapeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + if (Object::cast_to(p_gizmo->get_node_3d())) { + GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + Ref mat = particles->get_process_material(); + ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); + + if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { + return mat->get_emission_sphere_radius(); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { + return mat->get_emission_box_extents(); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { + if (p_id == 0) { + return mat->get_emission_ring_axis(); + } else if (p_id == 1) { + return mat->get_emission_ring_height(); + } else if (p_id == 2) { + return mat->get_emission_ring_radius(); + } else { + return mat->get_emission_ring_inner_radius(); + } + } + } else if (Object::cast_to(p_gizmo->get_node_3d())) { + CPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + CPUParticles3D::EmissionShape shape = particles->get_emission_shape(); + + if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { + return particles->get_emission_sphere_radius(); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { + return particles->get_emission_box_extents(); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { + if (p_id == 0) { + return particles->get_emission_ring_axis(); + } else if (p_id == 1) { + return particles->get_emission_ring_height(); + } else if (p_id == 2) { + return particles->get_emission_ring_radius(); + } else { + return particles->get_emission_ring_inner_radius(); + } + } + } + + return Variant(); +} + +void Particles3DEmissionShapeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + if (Object::cast_to(p_gizmo->get_node_3d())) { + GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + Ref mat = particles->get_process_material(); + ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); + + Transform3D gt = particles->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + Vector3 ra, rb; + float d; + + if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + mat->set_emission_sphere_radius(d); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { + Vector3 initial_size = mat->get_emission_box_extents(); + int axis = p_id / 2; + int sign = p_id % 2 * -2 + 1; + float neg_end = initial_size[axis] * -0.5; + float pos_end = initial_size[axis] * 0.5; + + Vector3 axis_segment[2] = { Vector3(), Vector3() }; + axis_segment[0][axis] = 4096.0; + axis_segment[1][axis] = -4096.0; + Geometry3D::get_closest_points_between_segments(axis_segment[0], axis_segment[1], s[0], s[1], ra, rb); + + Vector3 r_box_size = initial_size; + ra[axis] = ra[axis] * 0.5; + if (Input::get_singleton()->is_key_pressed(Key::ALT)) { + r_box_size[axis] = ra[axis] * sign * 2; + } else { + r_box_size[axis] = sign > 0 ? ra[axis] - neg_end : pos_end - ra[axis]; + } + + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + r_box_size[axis] = Math::snapped(r_box_size[axis], Node3DEditor::get_singleton()->get_translate_snap()); + } + r_box_size[axis] = MAX(r_box_size[axis], 0.001); + + Vector3 offset; + offset[axis] = (pos_end + neg_end) * 0.5; + Vector3 r_box_position = gt.xform(offset); + + mat->set_emission_box_extents(r_box_size); + mat->set_emission_shape_offset(r_box_position); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { + Vector3 ring_axis = mat->get_emission_ring_axis(); + Basis axis_basis; + axis_basis.rows[1] = ring_axis.normalized(); + axis_basis.rows[0] = Vector3(axis_basis[1][1], -axis_basis[1][2], -axis_basis[1][0]).normalized(); + axis_basis.rows[0] = (axis_basis[0] - axis_basis[0].dot(axis_basis[1]) * axis_basis[1]).normalized(); + axis_basis[2] = axis_basis[0].cross(axis_basis[1]).normalized(); + + if (p_id == 1) { + Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -4096), Vector3(0, 0, 4096), s[0], s[1], ra, rb); + d = ra.z; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + // Times 2 because of using the half_height for drawing + mat->set_emission_ring_height(2.0 * d); + } else if (p_id == 2) { + Geometry3D::get_closest_points_between_segments(Vector3(-4096, 0, 0), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + mat->set_emission_ring_radius(d); + } else { + Geometry3D::get_closest_points_between_segments(Vector3(-4096, 0, 0), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + mat->set_emission_ring_inner_radius(d); + } + } + + } else if (Object::cast_to(p_gizmo->get_node_3d())) { + CPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + CPUParticles3D::EmissionShape shape = particles->get_emission_shape(); + + Transform3D gt = particles->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + Vector3 ra, rb; + float d; + + if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + particles->set_emission_sphere_radius(d); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { + Vector3 initial_size = particles->get_emission_box_extents(); + int axis = p_id / 2; + int sign = p_id % 2 * -2 + 1; + float neg_end = initial_size[axis] * -0.5; + float pos_end = initial_size[axis] * 0.5; + + Vector3 axis_segment[2] = { Vector3(), Vector3() }; + axis_segment[0][axis] = 4096.0; + axis_segment[1][axis] = -4096.0; + Geometry3D::get_closest_points_between_segments(axis_segment[0], axis_segment[1], s[0], s[1], ra, rb); + + Vector3 r_box_size = initial_size; + ra[axis] = ra[axis] * 0.5; + if (Input::get_singleton()->is_key_pressed(Key::ALT)) { + r_box_size[axis] = ra[axis] * sign * 2; + } else { + r_box_size[axis] = sign > 0 ? ra[axis] - neg_end : pos_end - ra[axis]; + } + + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + r_box_size[axis] = Math::snapped(r_box_size[axis], Node3DEditor::get_singleton()->get_translate_snap()); + } + r_box_size[axis] = MAX(r_box_size[axis], 0.001); + + // TODO: Remove unused variable + //Vector3 offset; + //offset[axis] = (pos_end + neg_end) * 0.5; + //Vector3 r_box_position = gt.xform(offset); + + particles->set_emission_box_extents(r_box_size); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { + Vector3 ring_axis = particles->get_emission_ring_axis(); + Basis axis_basis; + axis_basis.rows[1] = ring_axis.normalized(); + axis_basis.rows[0] = Vector3(axis_basis[1][1], -axis_basis[1][2], -axis_basis[1][0]).normalized(); + axis_basis.rows[0] = (axis_basis[0] - axis_basis[0].dot(axis_basis[1]) * axis_basis[1]).normalized(); + axis_basis[2] = axis_basis[0].cross(axis_basis[1]).normalized(); + + if (p_id == 1) { + Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -4096), Vector3(0, 0, 4096), s[0], s[1], ra, rb); + d = ra.z; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + // Times 2 because of using the half_height for drawing + particles->set_emission_ring_height(2.0 * d); + } else if (p_id == 2) { + Geometry3D::get_closest_points_between_segments(Vector3(-4096, 0, 0), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + particles->set_emission_ring_radius(d); + } else { + Geometry3D::get_closest_points_between_segments(Vector3(-4096, 0, 0), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = ra.x; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + d = MAX(d, 0.001); + particles->set_emission_ring_inner_radius(d); + } + } + } +} + +void Particles3DEmissionShapeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + if (Object::cast_to(p_gizmo->get_node_3d())) { + GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + Ref mat = particles->get_process_material(); + ParticleProcessMaterial *process_mat = Object::cast_to(mat.ptr()); + ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); + + if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { + if (p_cancel) { + mat->set_emission_sphere_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission sphere radius")); + ur->add_do_property(process_mat, "emission_sphere_radius", process_mat->get_emission_sphere_radius()); + ur->add_undo_property(process_mat, "emission_sphere_radius", p_restore); + ur->commit_action(); + } + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { + if (p_cancel) { + mat->set_emission_box_extents(p_restore); + return; + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission box extents")); + ur->add_do_property(process_mat, "emission_box_extents", process_mat->get_emission_box_extents()); + ur->add_undo_property(process_mat, "emission_box_extents", p_restore); + ur->commit_action(); + } + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { + if (p_id == 0) { + if (p_cancel) { + mat->set_emission_ring_axis(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring axis")); + ur->add_do_property(process_mat, "emission_ring_axis", process_mat->get_emission_ring_axis()); + ur->add_undo_property(process_mat, "emission_ring_axis", p_restore); + ur->commit_action(); + } + } else if (p_id == 1) { + if (p_cancel) { + mat->set_emission_ring_height(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring height")); + ur->add_do_property(process_mat, "emission_ring_height", process_mat->get_emission_ring_height()); + ur->add_undo_property(process_mat, "emission_ring_height", p_restore); + ur->commit_action(); + } + } else if (p_id == 2) { + if (p_cancel) { + mat->set_emission_ring_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring radius")); + ur->add_do_property(process_mat, "emission_ring_radius", process_mat->get_emission_ring_radius()); + ur->add_undo_property(process_mat, "emission_ring_radius", p_restore); + ur->commit_action(); + } + } else { + if (p_cancel) { + mat->set_emission_ring_inner_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring inner radius")); + ur->add_do_property(process_mat, "emission_ring_inner_radius", process_mat->get_emission_ring_inner_radius()); + ur->add_undo_property(process_mat, "emission_ring_inner_radius", p_restore); + ur->commit_action(); + } + } + } + } else if (Object::cast_to(p_gizmo->get_node_3d())) { + CPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + CPUParticles3D::EmissionShape shape = particles->get_emission_shape(); + + if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { + if (p_cancel) { + particles->set_emission_sphere_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission sphere radius")); + ur->add_do_property(particles, "emission_sphere_radius", particles->get_emission_sphere_radius()); + ur->add_undo_property(particles, "emission_sphere_radius", p_restore); + ur->commit_action(); + } + } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { + if (p_cancel) { + particles->set_emission_box_extents(p_restore); + return; + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission box extents")); + ur->add_do_property(particles, "emission_box_extents", particles->get_emission_box_extents()); + ur->add_undo_property(particles, "emission_box_extents", p_restore); + ur->commit_action(); + } + } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { + if (p_id == 0) { + if (p_cancel) { + particles->set_emission_ring_axis(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring axis")); + ur->add_do_property(particles, "emission_ring_axis", particles->get_emission_ring_axis()); + ur->add_undo_property(particles, "emission_ring_axis", p_restore); + ur->commit_action(); + } + } else if (p_id == 1) { + if (p_cancel) { + particles->set_emission_ring_height(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring height")); + ur->add_do_property(particles, "emission_ring_height", particles->get_emission_ring_height()); + ur->add_undo_property(particles, "emission_ring_height", p_restore); + ur->commit_action(); + } + } else if (p_id == 2) { + if (p_cancel) { + particles->set_emission_ring_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring radius")); + ur->add_do_property(particles, "emission_ring_radius", particles->get_emission_ring_radius()); + ur->add_undo_property(particles, "emission_ring_radius", p_restore); + ur->commit_action(); + } + } else { + if (p_cancel) { + particles->set_emission_ring_inner_radius(p_restore); + } else { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Change emission ring inner radius")); + ur->add_do_property(particles, "emission_ring_inner_radius", particles->get_emission_ring_inner_radius()); + ur->add_undo_property(particles, "emission_ring_inner_radius", p_restore); + ur->commit_action(); + } + } + } + } +} + +void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + p_gizmo->clear(); + + if (Object::cast_to(p_gizmo->get_node_3d())) { + GPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + + if (particles->get_process_material() != nullptr) { + Ref mat = particles->get_process_material(); + ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape(); + + const Ref material = get_material("particles_emission_shape_material", p_gizmo); + Ref handles_material = get_material("handles"); + + if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { + Vector3 offset = mat->get_emission_shape_offset(); + Vector3 scale = mat->get_emission_shape_scale(); + + float r = mat->get_emission_sphere_radius(); + Vector points; + for (int i = 0; i <= 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + points.push_back(Vector3(a.x * scale.x + offset.x, offset.y, a.y * scale.z + offset.z)); + points.push_back(Vector3(b.x * scale.x + offset.x, offset.y, b.y * scale.z + offset.z)); + points.push_back(Vector3(offset.x, a.x * scale.y + offset.y, a.y * scale.z + offset.z)); + points.push_back(Vector3(offset.x, b.x * scale.y + offset.y, b.y * scale.z + offset.z)); + points.push_back(Vector3(a.x * scale.x + offset.x, a.y * scale.y + offset.y, offset.z)); + points.push_back(Vector3(b.x * scale.x + offset.x, b.y * scale.y + offset.y, offset.z)); + } + + Vector handles; + Vector ids; + handles.push_back(Vector3(r, 0, 0)); + ids.push_back(0); + + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { + Vector3 offset = mat->get_emission_shape_offset(); + Vector3 scale = mat->get_emission_shape_scale(); + + Vector3 box_extents = mat->get_emission_box_extents(); + Ref box = memnew(BoxMesh); + AABB box_aabb = box->get_aabb(); + Vector lines; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + box_aabb.get_edge(i, a, b); + // Multiplication by 2 due to the extents being only half of the box size + lines.push_back(a * 2.0 * scale * box_extents + offset); + lines.push_back(b * 2.0 * scale * box_extents + offset); + } + + Vector handles = helper->box_get_handles(mat->get_emission_box_extents() * 2.0); + + p_gizmo->add_lines(lines, material); + p_gizmo->add_handles(handles, handles_material); + } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { + Vector3 offset = mat->get_emission_shape_offset(); + Vector3 scale = mat->get_emission_shape_scale(); + + float ring_height = mat->get_emission_ring_height(); + float half_ring_height = ring_height / 2; + float ring_radius = mat->get_emission_ring_radius(); + float ring_inner_radius = mat->get_emission_ring_inner_radius(); + Vector3 ring_axis = mat->get_emission_ring_axis(); + + Vector points; + + Basis basis; + basis.rows[1] = ring_axis.normalized(); + basis.rows[0] = Vector3(basis[1][1], -basis[1][2], -basis[1][0]).normalized(); + basis.rows[0] = (basis[0] - basis[0].dot(basis[1]) * basis[1]).normalized(); + basis[2] = basis[0].cross(basis[1]).normalized(); + basis = basis.inverse(); + + for (int i = 0; i <= 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_radius; + Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; + Point2 inner_b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_inner_radius; + + // outer top ring cap + points.push_back(basis.xform(Vector3(a.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(b.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, b.y * scale.z + offset.z))); + // outer bottom ring cap + points.push_back(basis.xform(Vector3(a.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(b.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, b.y * scale.z + offset.z))); + + // inner top ring cap + points.push_back(basis.xform(Vector3(inner_a.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, inner_a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(inner_b.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, inner_b.y * scale.z + offset.z))); + // inner bottom ring cap + points.push_back(basis.xform(Vector3(inner_a.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, inner_a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(inner_b.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, inner_b.y * scale.z + offset.z))); + } + + for (int i = 0; i <= 360; i = i + 90) { + float ra = Math::deg_to_rad((float)i); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; + Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; + + // outer 90 degrees vertical lines + points.push_back(basis.xform(Vector3(a.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(a.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, a.y * scale.z + offset.z))); + + // inner 90 degrees vertical lines + points.push_back(basis.xform(Vector3(inner_a.x * scale.x + offset.x, half_ring_height * scale.y + offset.y, inner_a.y * scale.z + offset.z))); + points.push_back(basis.xform(Vector3(inner_a.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, inner_a.y * scale.z + offset.z))); + } + + Vector handles; + Vector ids; + handles.push_back(Vector3(0, 0, half_ring_height)); + ids.push_back(1); + handles.push_back(Vector3(ring_radius, 0, 0)); + ids.push_back(2); + handles.push_back(Vector3(ring_inner_radius, 0, 0)); + ids.push_back(3); + + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } + } + } else if (Object::cast_to(p_gizmo->get_node_3d())) { + CPUParticles3D *particles = Object::cast_to(p_gizmo->get_node_3d()); + CPUParticles3D::EmissionShape shape = particles->get_emission_shape(); + + const Ref material = get_material("particles_emission_shape_material", p_gizmo); + Ref handles_material = get_material("handles"); + + if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { + float r = particles->get_emission_sphere_radius(); + Vector points; + for (int i = 0; i <= 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + points.push_back(Vector3(a.x, 0.0, a.y)); + points.push_back(Vector3(b.x, 0.0, b.y)); + points.push_back(Vector3(0.0, a.x, a.y)); + points.push_back(Vector3(0.0, b.x, b.y)); + points.push_back(Vector3(a.x, a.y, 0.0)); + points.push_back(Vector3(b.x, b.y, 0.0)); + } + + Vector handles; + Vector ids; + handles.push_back(Vector3(r, 0, 0)); + ids.push_back(0); + + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { + Vector3 box_extents = particles->get_emission_box_extents(); + Ref box = memnew(BoxMesh); + AABB box_aabb = box->get_aabb(); + Vector lines; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + box_aabb.get_edge(i, a, b); + // Multiplication by 2 due to the extents being only half of the box size + lines.push_back(a * 2.0 * box_extents); + lines.push_back(b * 2.0 * box_extents); + } + + Vector handles = helper->box_get_handles(particles->get_emission_box_extents() * 2.0); + + p_gizmo->add_lines(lines, material); + p_gizmo->add_handles(handles, handles_material); + } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { + float ring_height = particles->get_emission_ring_height(); + float half_ring_height = ring_height / 2; + float ring_radius = particles->get_emission_ring_radius(); + float ring_inner_radius = particles->get_emission_ring_inner_radius(); + Vector3 ring_axis = particles->get_emission_ring_axis(); + + Vector points; + + Basis basis; + basis.rows[1] = ring_axis.normalized(); + basis.rows[0] = Vector3(basis[1][1], -basis[1][2], -basis[1][0]).normalized(); + basis.rows[0] = (basis[0] - basis[0].dot(basis[1]) * basis[1]).normalized(); + basis[2] = basis[0].cross(basis[1]).normalized(); + basis = basis.inverse(); + + for (int i = 0; i <= 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_radius; + Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; + Point2 inner_b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_inner_radius; + + // outer top ring cap + points.push_back(basis.xform(Vector3(a.x, half_ring_height, a.y))); + points.push_back(basis.xform(Vector3(b.x, half_ring_height, b.y))); + // outer bottom ring cap + points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y))); + points.push_back(basis.xform(Vector3(b.x, -half_ring_height, b.y))); + + // inner top ring cap + points.push_back(basis.xform(Vector3(inner_a.x, half_ring_height, inner_a.y))); + points.push_back(basis.xform(Vector3(inner_b.x, half_ring_height, inner_b.y))); + // inner bottom ring cap + points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y))); + points.push_back(basis.xform(Vector3(inner_b.x, -half_ring_height, inner_b.y))); + } + + for (int i = 0; i <= 360; i = i + 90) { + float ra = Math::deg_to_rad((float)i); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; + Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; + + // outer 90 degrees vertical lines + points.push_back(basis.xform(Vector3(a.x, half_ring_height, a.y))); + points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y))); + + // inner 90 degrees vertical lines + points.push_back(basis.xform(Vector3(inner_a.x, half_ring_height, inner_a.y))); + points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y))); + } + + Vector handles; + Vector ids; + handles.push_back(Vector3(0, 0, half_ring_height)); + ids.push_back(1); + handles.push_back(Vector3(ring_radius, 0, 0)); + ids.push_back(2); + handles.push_back(Vector3(ring_inner_radius, 0, 0)); + ids.push_back(3); + + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } + } +} diff --git a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.h b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.h new file mode 100644 index 000000000000..45b725af063a --- /dev/null +++ b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* particles_3d_emission_shape_gizmo_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_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H +#define PARTICLES_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H + +#include "editor/plugins/gizmos/gizmo_3d_helper.h" +#include "editor/plugins/node_3d_editor_gizmos.h" + +class Particles3DEmissionShapeGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Particles3DEmissionShapeGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref helper; + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + bool is_selectable_when_hidden() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + Particles3DEmissionShapeGizmoPlugin(); +}; + +#endif // PARTICLES_3D_EMISSION_SHAPE_GIZMO_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 44673e7224cc..cf550c915822 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -66,6 +66,7 @@ #include "editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/navigation_region_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/occluder_instance_3d_gizmo_plugin.h" +#include "editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.h" #include "editor/plugins/gizmos/physics_bone_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/ray_cast_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/reflection_probe_gizmo_plugin.h" @@ -8367,6 +8368,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref(memnew(VisibleOnScreenNotifier3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(GPUParticles3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(GPUParticlesCollision3DGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(Particles3DEmissionShapeGizmoPlugin))); add_gizmo_plugin(Ref(memnew(CPUParticles3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(ReflectionProbeGizmoPlugin))); add_gizmo_plugin(Ref(memnew(DecalGizmoPlugin))); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index acbc443a93b7..05e7c76a08da 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -410,14 +410,17 @@ bool CPUParticles3D::get_particle_flag(ParticleFlags p_particle_flag) const { void CPUParticles3D::set_emission_shape(EmissionShape p_shape) { ERR_FAIL_INDEX(p_shape, EMISSION_SHAPE_MAX); emission_shape = p_shape; + update_gizmos(); } void CPUParticles3D::set_emission_sphere_radius(real_t p_radius) { emission_sphere_radius = p_radius; + update_gizmos(); } void CPUParticles3D::set_emission_box_extents(Vector3 p_extents) { emission_box_extents = p_extents; + update_gizmos(); } void CPUParticles3D::set_emission_points(const Vector &p_points) { @@ -434,18 +437,22 @@ void CPUParticles3D::set_emission_colors(const Vector &p_colors) { void CPUParticles3D::set_emission_ring_axis(Vector3 p_axis) { emission_ring_axis = p_axis; + update_gizmos(); } void CPUParticles3D::set_emission_ring_height(real_t p_height) { emission_ring_height = p_height; + update_gizmos(); } void CPUParticles3D::set_emission_ring_radius(real_t p_radius) { emission_ring_radius = p_radius; + update_gizmos(); } void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; + update_gizmos(); } void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) { diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 2cef607d2968..290fd8838e64 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -34,6 +34,7 @@ #include "scene/resources/curve_texture.h" #include "scene/resources/gradient_texture.h" #include "scene/resources/particle_process_material.h" +#include "scene/scene_string_names.h" AABB GPUParticles3D::get_aabb() const { return AABB(); @@ -459,6 +460,14 @@ void GPUParticles3D::_notification(int p_what) { // Use internal process when emitting and one_shot is on so that when // the shot ends the editor can properly update. case NOTIFICATION_INTERNAL_PROCESS: { + const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } + previous_position = get_global_position(); + if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { @@ -469,7 +478,7 @@ void GPUParticles3D::_notification(int p_what) { } if (time > active_time) { if (active && !signal_canceled) { - emit_signal(SceneStringName(finished)); + emit_signal(SceneStringNames::get_singleton()->finished); } active = false; if (!emitting) { @@ -505,10 +514,18 @@ void GPUParticles3D::_notification(int p_what) { previous_position = get_global_transform().origin; set_process_internal(true); set_physics_process_internal(true); + + Ref material = get_process_material(); + ERR_FAIL_COND(material.is_null()); + material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); } break; case NOTIFICATION_EXIT_TREE: { RS::get_singleton()->particles_set_subemitter(particles, RID()); + + Ref material = get_process_material(); + ERR_FAIL_COND(material.is_null()); + material->disconnect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); } break; case NOTIFICATION_PAUSED: diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 8cfe4c92b72e..430a3dbd45a2 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -1565,6 +1565,7 @@ bool ParticleProcessMaterial::get_particle_flag(ParticleFlags p_particle_flag) c void ParticleProcessMaterial::set_emission_shape(EmissionShape p_shape) { ERR_FAIL_INDEX(p_shape, EMISSION_SHAPE_MAX); emission_shape = p_shape; + emit_signal("emission_shape_changed"); notify_property_list_changed(); _queue_shader_change(); } @@ -1572,11 +1573,13 @@ void ParticleProcessMaterial::set_emission_shape(EmissionShape p_shape) { void ParticleProcessMaterial::set_emission_sphere_radius(real_t p_radius) { emission_sphere_radius = p_radius; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_sphere_radius, p_radius); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_box_extents(Vector3 p_extents) { emission_box_extents = p_extents; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_box_extents, p_extents); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_point_texture(const Ref &p_points) { @@ -1606,21 +1609,25 @@ void ParticleProcessMaterial::set_emission_point_count(int p_count) { void ParticleProcessMaterial::set_emission_ring_axis(Vector3 p_axis) { emission_ring_axis = p_axis; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_axis, p_axis); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_ring_height(real_t p_height) { emission_ring_height = p_height; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_height, p_height); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_ring_radius(real_t p_radius) { emission_ring_radius = p_radius; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_radius, p_radius); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius); + emit_signal("emission_shape_changed"); } void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) { @@ -1684,6 +1691,7 @@ real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const { void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) { emission_shape_offset = p_emission_shape_offset; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset); + emit_signal("emission_shape_changed"); } Vector3 ParticleProcessMaterial::get_emission_shape_offset() const { @@ -1693,6 +1701,7 @@ Vector3 ParticleProcessMaterial::get_emission_shape_offset() const { void ParticleProcessMaterial::set_emission_shape_scale(const Vector3 &p_emission_shape_scale) { emission_shape_scale = p_emission_shape_scale; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_scale, p_emission_shape_scale); + emit_signal("emission_shape_changed"); } Vector3 ParticleProcessMaterial::get_emission_shape_scale() const { @@ -2209,6 +2218,8 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_collision", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_collision", "get_sub_emitter_amount_at_collision"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); + ADD_SIGNAL(MethodInfo("emission_shape_changed")); + BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); From a6d6e33aa51054f77ffbae9950f82709cb0b97e7 Mon Sep 17 00:00:00 2001 From: Patrick Exner Date: Fri, 3 May 2024 16:13:36 +0200 Subject: [PATCH 2/2] Disable gizmos when node is unselected and fix shape gizmos --- doc/classes/ParticleProcessMaterial.xml | 2 +- ...rticles_3d_emission_shape_gizmo_plugin.cpp | 99 +++++++++++-------- scene/3d/gpu_particles_3d.cpp | 5 +- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 1408e6f4f159..92ee10a1545e 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -412,7 +412,7 @@ - Emitted when the emission shape of the material has been changed in its offset, size, scale, or orientation. + Emitted when this material's emission shape is changed in any way. This includes changes to [member emission_shape], [member emission_shape_scale], or [member emission_sphere_radius], and any other property that affects the emission shape's offset, size, scale, or orientation. diff --git a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp index ebc1ee322460..6dd21d0ebb19 100644 --- a/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp @@ -189,13 +189,15 @@ void Particles3DEmissionShapeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_ Vector3 ray_from = p_camera->project_ray_origin(p_point); Vector3 ray_dir = p_camera->project_ray_normal(p_point); Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + Vector3 s_offset = mat->get_emission_shape_offset(); + Vector3 s_scale = mat->get_emission_shape_scale(); Vector3 ra, rb; float d; if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) { - Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), s[0], s[1], ra, rb); - d = ra.x; + Geometry3D::get_closest_points_between_segments(Vector3(-4096, 0, 0), Vector3(4096, 0, 0), s[0], s[1], ra, rb); + d = (ra.x - s_offset.x) / s_scale.x; if (Node3DEditor::get_singleton()->is_snap_enabled()) { d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } @@ -314,11 +316,6 @@ void Particles3DEmissionShapeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_ } r_box_size[axis] = MAX(r_box_size[axis], 0.001); - // TODO: Remove unused variable - //Vector3 offset; - //offset[axis] = (pos_end + neg_end) * 0.5; - //Vector3 r_box_position = gt.xform(offset); - particles->set_emission_box_extents(r_box_size); } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { Vector3 ring_axis = particles->get_emission_ring_axis(); @@ -519,9 +516,9 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { float r = mat->get_emission_sphere_radius(); Vector points; - for (int i = 0; i <= 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); + for (int i = 0; i <= 120; i++) { + float ra = Math::deg_to_rad((float)(i * 3)); + float rb = Math::deg_to_rad((float)((i + 1) * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -535,11 +532,13 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector handles; Vector ids; - handles.push_back(Vector3(r, 0, 0)); + handles.push_back(Vector3(r * scale.x + offset.x, offset.y, offset.z)); ids.push_back(0); - p_gizmo->add_lines(points, material); - p_gizmo->add_handles(handles, handles_material, ids); + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) { Vector3 offset = mat->get_emission_shape_offset(); Vector3 scale = mat->get_emission_shape_scale(); @@ -557,10 +556,18 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { lines.push_back(b * 2.0 * scale * box_extents + offset); } - Vector handles = helper->box_get_handles(mat->get_emission_box_extents() * 2.0); - - p_gizmo->add_lines(lines, material); - p_gizmo->add_handles(handles, handles_material); + Vector handles; + handles.push_back(Vector3(box_extents[0] * scale[0] + offset[0], offset[1], offset[2])); + handles.push_back(Vector3(-box_extents[0] * scale[0] + offset[0], offset[1], offset[2])); + handles.push_back(Vector3(offset[0], box_extents[1] * scale[1] + offset[1], offset[2])); + handles.push_back(Vector3(offset[0], -box_extents[1] * scale[1] + offset[1], offset[2])); + handles.push_back(Vector3(offset[0], offset[1], box_extents[2] * scale[2] + offset[2])); + handles.push_back(Vector3(offset[0], offset[1], -box_extents[2] * scale[2] + offset[2])); + + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(lines, material); + p_gizmo->add_handles(handles, handles_material); + } } else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) { Vector3 offset = mat->get_emission_shape_offset(); Vector3 scale = mat->get_emission_shape_scale(); @@ -578,11 +585,13 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { basis.rows[0] = Vector3(basis[1][1], -basis[1][2], -basis[1][0]).normalized(); basis.rows[0] = (basis[0] - basis[0].dot(basis[1]) * basis[1]).normalized(); basis[2] = basis[0].cross(basis[1]).normalized(); + offset = basis.xform(offset); + scale = basis.xform(scale); basis = basis.inverse(); - for (int i = 0; i <= 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); + for (int i = 0; i <= 120; i++) { + float ra = Math::deg_to_rad((float)(i * 3)); + float rb = Math::deg_to_rad((float)((i + 1) * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_radius; Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; @@ -603,8 +612,8 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { points.push_back(basis.xform(Vector3(inner_b.x * scale.x + offset.x, -half_ring_height * scale.y + offset.y, inner_b.y * scale.z + offset.z))); } - for (int i = 0; i <= 360; i = i + 90) { - float ra = Math::deg_to_rad((float)i); + for (int i = 0; i <= 120; i = i + 30) { + float ra = Math::deg_to_rad((float)(i * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; @@ -619,15 +628,17 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector handles; Vector ids; - handles.push_back(Vector3(0, 0, half_ring_height)); + handles.push_back(basis.xform(Vector3(offset.x, half_ring_height * scale.z + offset.y, offset.z))); ids.push_back(1); - handles.push_back(Vector3(ring_radius, 0, 0)); + handles.push_back(basis.xform(Vector3(offset.x, offset.y, -ring_radius * scale.z + offset.z))); ids.push_back(2); - handles.push_back(Vector3(ring_inner_radius, 0, 0)); + handles.push_back(basis.xform(Vector3(offset.x, offset.y, -ring_inner_radius * scale.z + offset.z))); ids.push_back(3); - p_gizmo->add_lines(points, material); - p_gizmo->add_handles(handles, handles_material, ids); + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } } } } else if (Object::cast_to(p_gizmo->get_node_3d())) { @@ -640,9 +651,9 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) { float r = particles->get_emission_sphere_radius(); Vector points; - for (int i = 0; i <= 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); + for (int i = 0; i <= 120; i++) { + float ra = Math::deg_to_rad((float)(i * 3)); + float rb = Math::deg_to_rad((float)((i + 1) * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; @@ -659,8 +670,10 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(Vector3(r, 0, 0)); ids.push_back(0); - p_gizmo->add_lines(points, material); - p_gizmo->add_handles(handles, handles_material, ids); + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } } else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) { Vector3 box_extents = particles->get_emission_box_extents(); Ref box = memnew(BoxMesh); @@ -677,8 +690,10 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector handles = helper->box_get_handles(particles->get_emission_box_extents() * 2.0); - p_gizmo->add_lines(lines, material); - p_gizmo->add_handles(handles, handles_material); + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(lines, material); + p_gizmo->add_handles(handles, handles_material); + } } else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) { float ring_height = particles->get_emission_ring_height(); float half_ring_height = ring_height / 2; @@ -695,9 +710,9 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { basis[2] = basis[0].cross(basis[1]).normalized(); basis = basis.inverse(); - for (int i = 0; i <= 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); + for (int i = 0; i <= 120; i++) { + float ra = Math::deg_to_rad((float)(i * 3)); + float rb = Math::deg_to_rad((float)((i + 1) * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * ring_radius; Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; @@ -718,8 +733,8 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { points.push_back(basis.xform(Vector3(inner_b.x, -half_ring_height, inner_b.y))); } - for (int i = 0; i <= 360; i = i + 90) { - float ra = Math::deg_to_rad((float)i); + for (int i = 0; i <= 120; i = i + 30) { + float ra = Math::deg_to_rad((float)(i * 3)); Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_radius; Point2 inner_a = Vector2(Math::sin(ra), Math::cos(ra)) * ring_inner_radius; @@ -741,8 +756,10 @@ void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(Vector3(ring_inner_radius, 0, 0)); ids.push_back(3); - p_gizmo->add_lines(points, material); - p_gizmo->add_handles(handles, handles_material, ids); + if (p_gizmo->is_selected()) { + p_gizmo->add_lines(points, material); + p_gizmo->add_handles(handles, handles_material, ids); + } } } } diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 290fd8838e64..10d7e1db001b 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -128,6 +128,7 @@ void GPUParticles3D::set_process_material(const Ref &p_material) { RID material_rid; if (process_material.is_valid()) { material_rid = process_material->get_rid(); + process_material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); } RS::get_singleton()->particles_set_process_material(particles, material_rid); @@ -514,10 +515,6 @@ void GPUParticles3D::_notification(int p_what) { previous_position = get_global_transform().origin; set_process_internal(true); set_physics_process_internal(true); - - Ref material = get_process_material(); - ERR_FAIL_COND(material.is_null()); - material->connect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); } break; case NOTIFICATION_EXIT_TREE: {