diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 28c60194c8a2..92ee10a1545e 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 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.
+
+
+
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..6dd21d0ebb19
--- /dev/null
+++ b/editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp
@@ -0,0 +1,765 @@
+/**************************************************************************/
+/* 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 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(-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());
+ }
+ 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);
+
+ 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 <= 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;
+
+ 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 * scale.x + offset.x, offset.y, offset.z));
+ ids.push_back(0);
+
+ 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();
+
+ 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;
+ 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();
+
+ 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();
+ offset = basis.xform(offset);
+ scale = basis.xform(scale);
+ basis = basis.inverse();
+
+ 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;
+ 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 <= 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;
+
+ // 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(basis.xform(Vector3(offset.x, half_ring_height * scale.z + offset.y, offset.z)));
+ ids.push_back(1);
+ handles.push_back(basis.xform(Vector3(offset.x, offset.y, -ring_radius * scale.z + offset.z)));
+ ids.push_back(2);
+ handles.push_back(basis.xform(Vector3(offset.x, offset.y, -ring_inner_radius * scale.z + offset.z)));
+ ids.push_back(3);
+
+ 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())) {
+ 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 <= 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;
+
+ 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);
+
+ 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);
+ 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);
+
+ 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;
+ 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 <= 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;
+ 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 <= 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;
+
+ // 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);
+
+ if (p_gizmo->is_selected()) {
+ 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..10d7e1db001b 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();
@@ -127,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);
@@ -459,6 +461,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 +479,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) {
@@ -509,6 +519,10 @@ void GPUParticles3D::_notification(int p_what) {
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);