Преглед изворни кода

Add visualization of 3D particle emission shapes

Co-authored-by: Kasper Arnklit Frandsen <[email protected]>
Patrick Exner пре 8 месеци
родитељ
комит
e689c122a6

+ 7 - 0
doc/classes/ParticleProcessMaterial.xml

@@ -410,6 +410,13 @@
 			A pivot point used to calculate radial and orbital velocity of particles.
 		</member>
 	</members>
+	<signals>
+		<signal name="emission_shape_changed">
+			<description>
+				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.
+			</description>
+		</signal>
+	</signals>
 	<constants>
 		<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter">
 			Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set initial velocity properties.

+ 339 - 0
editor/plugins/gizmos/particles_3d_emission_shape_gizmo_plugin.cpp

@@ -0,0 +1,339 @@
+/**************************************************************************/
+/*  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 "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<GPUParticles3D>(p_spatial) || Object::cast_to<CPUParticles3D>(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 {
+	return "";
+}
+
+Variant Particles3DEmissionShapeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
+	return Variant();
+}
+
+void Particles3DEmissionShapeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
+}
+
+void Particles3DEmissionShapeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
+}
+
+void Particles3DEmissionShapeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+	p_gizmo->clear();
+
+	if (Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d())) {
+		const GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_node_3d());
+
+		if (particles->get_process_material().is_valid()) {
+			const Ref<ParticleProcessMaterial> mat = particles->get_process_material();
+			const ParticleProcessMaterial::EmissionShape shape = mat->get_emission_shape();
+
+			const Ref<Material> material = get_material("particles_emission_shape_material", p_gizmo);
+			const Ref<Material> handles_material = get_material("handles");
+
+			if (shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE || shape == ParticleProcessMaterial::EMISSION_SHAPE_SPHERE_SURFACE) {
+				const Vector3 offset = mat->get_emission_shape_offset();
+				const Vector3 scale = mat->get_emission_shape_scale();
+
+				const float r = mat->get_emission_sphere_radius();
+				Vector<Vector3> points;
+				for (int i = 0; i <= 120; i++) {
+					const float ra = Math::deg_to_rad((float)(i * 3));
+					const float rb = Math::deg_to_rad((float)((i + 1) * 3));
+					const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+					const 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));
+				}
+
+				if (p_gizmo->is_selected()) {
+					p_gizmo->add_lines(points, material);
+				}
+			} else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_BOX) {
+				const Vector3 offset = mat->get_emission_shape_offset();
+				const Vector3 scale = mat->get_emission_shape_scale();
+
+				const Vector3 box_extents = mat->get_emission_box_extents();
+				Ref<BoxMesh> box;
+				box.instantiate();
+				const AABB box_aabb = box->get_aabb();
+				Vector<Vector3> lines;
+
+				for (int i = 0; i < 12; i++) {
+					Vector3 a;
+					Vector3 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);
+				}
+
+				if (p_gizmo->is_selected()) {
+					p_gizmo->add_lines(lines, material);
+				}
+			} else if (shape == ParticleProcessMaterial::EMISSION_SHAPE_RING) {
+				const Vector3 offset = mat->get_emission_shape_offset();
+				const Vector3 scale = mat->get_emission_shape_scale();
+
+				const float ring_height = mat->get_emission_ring_height();
+				const float half_ring_height = ring_height / 2;
+				const float ring_radius = mat->get_emission_ring_radius();
+				const float ring_inner_radius = mat->get_emission_ring_inner_radius();
+				const Vector3 ring_axis = mat->get_emission_ring_axis();
+				const float ring_cone_angle = mat->get_emission_ring_cone_angle();
+				const float ring_radius_top = MAX(ring_radius - Math::tan(Math::deg_to_rad(90.0 - ring_cone_angle)) * ring_height, 0.0);
+				const float ring_inner_radius_top = (ring_inner_radius / ring_radius) * ring_radius_top;
+
+				Vector<Vector3> 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.invert();
+
+				for (int i = 0; i <= 120; i++) {
+					const float ra = Math::deg_to_rad((float)(i * 3));
+					const float ra_sin = Math::sin(ra);
+					const float ra_cos = Math::cos(ra);
+					const float rb = Math::deg_to_rad((float)((i + 1) * 3));
+					const float rb_sin = Math::sin(rb);
+					const float rb_cos = Math::cos(rb);
+					const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
+					const Point2 b = Vector2(rb_sin, rb_cos) * ring_radius;
+					const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
+					const Point2 b2 = Vector2(rb_sin, rb_cos) * ring_radius_top;
+					const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
+					const Point2 inner_b = Vector2(rb_sin, rb_cos) * ring_inner_radius;
+					const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
+					const Point2 inner_b2 = Vector2(rb_sin, rb_cos) * ring_inner_radius_top;
+
+					// Outer top ring cap.
+					points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(b2.x, half_ring_height, b2.y)) * scale + offset);
+
+					// Outer bottom ring cap.
+					points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(b.x, -half_ring_height, b.y)) * scale + offset);
+
+					// Inner top ring cap.
+					points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(inner_b2.x, half_ring_height, inner_b2.y)) * scale + offset);
+
+					// Inner bottom ring cap.
+					points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(inner_b.x, -half_ring_height, inner_b.y)) * scale + offset);
+				}
+
+				for (int i = 0; i <= 120; i = i + 30) {
+					const float ra = Math::deg_to_rad((float)(i * 3));
+					const float ra_sin = Math::sin(ra);
+					const float ra_cos = Math::cos(ra);
+					const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
+					const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
+					const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
+					const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
+
+					// Outer 90 degrees vertical lines.
+					points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(a.x, -half_ring_height, a.y)) * scale + offset);
+
+					// Inner 90 degrees vertical lines.
+					points.push_back(basis.xform(Vector3(inner_a2.x, half_ring_height, inner_a2.y)) * scale + offset);
+					points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)) * scale + offset);
+				}
+
+				if (p_gizmo->is_selected()) {
+					p_gizmo->add_lines(points, material);
+				}
+			}
+		}
+	} else if (Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d())) {
+		const CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(p_gizmo->get_node_3d());
+		const CPUParticles3D::EmissionShape shape = particles->get_emission_shape();
+
+		const Ref<Material> material = get_material("particles_emission_shape_material", p_gizmo);
+		const Ref<Material> handles_material = get_material("handles");
+
+		if (shape == CPUParticles3D::EMISSION_SHAPE_SPHERE || shape == CPUParticles3D::EMISSION_SHAPE_SPHERE_SURFACE) {
+			const float r = particles->get_emission_sphere_radius();
+			Vector<Vector3> points;
+			for (int i = 0; i <= 120; i++) {
+				const float ra = Math::deg_to_rad((float)(i * 3));
+				const float rb = Math::deg_to_rad((float)((i + 1) * 3));
+				const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+				const 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));
+			}
+
+			if (p_gizmo->is_selected()) {
+				p_gizmo->add_lines(points, material);
+			}
+		} else if (shape == CPUParticles3D::EMISSION_SHAPE_BOX) {
+			const Vector3 box_extents = particles->get_emission_box_extents();
+			Ref<BoxMesh> box;
+			box.instantiate();
+			const AABB box_aabb = box->get_aabb();
+			Vector<Vector3> lines;
+
+			for (int i = 0; i < 12; i++) {
+				Vector3 a;
+				Vector3 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);
+			}
+
+			if (p_gizmo->is_selected()) {
+				p_gizmo->add_lines(lines, material);
+			}
+		} else if (shape == CPUParticles3D::EMISSION_SHAPE_RING) {
+			const float ring_height = particles->get_emission_ring_height();
+			const float half_ring_height = ring_height / 2;
+			const float ring_radius = particles->get_emission_ring_radius();
+			const float ring_inner_radius = particles->get_emission_ring_inner_radius();
+			const Vector3 ring_axis = particles->get_emission_ring_axis();
+			const float ring_cone_angle = particles->get_emission_ring_cone_angle();
+			const float ring_radius_top = MAX(ring_radius - Math::tan(Math::deg_to_rad(90.0 - ring_cone_angle)) * ring_height, 0.0);
+			const float ring_inner_radius_top = (ring_inner_radius / ring_radius) * ring_radius_top;
+
+			Vector<Vector3> 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.invert();
+
+			for (int i = 0; i <= 120; i++) {
+				const float ra = Math::deg_to_rad((float)(i * 3));
+				const float ra_sin = Math::sin(ra);
+				const float ra_cos = Math::cos(ra);
+				const float rb = Math::deg_to_rad((float)((i + 1) * 3));
+				const float rb_sin = Math::sin(rb);
+				const float rb_cos = Math::cos(rb);
+				const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
+				const Point2 b = Vector2(rb_sin, rb_cos) * ring_radius;
+				const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
+				const Point2 b2 = Vector2(rb_sin, rb_cos) * ring_radius_top;
+				const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
+				const Point2 inner_b = Vector2(rb_sin, rb_cos) * ring_inner_radius;
+				const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
+				const Point2 inner_b2 = Vector2(rb_sin, rb_cos) * ring_inner_radius_top;
+
+				// Outer top ring cap.
+				points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.y)));
+				points.push_back(basis.xform(Vector3(b2.x, half_ring_height, b2.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_a2.x, half_ring_height, inner_a2.y)));
+				points.push_back(basis.xform(Vector3(inner_b2.x, half_ring_height, inner_b2.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) {
+				const float ra = Math::deg_to_rad((float)(i * 3));
+				const float ra_sin = Math::sin(ra);
+				const float ra_cos = Math::cos(ra);
+				const Point2 a = Vector2(ra_sin, ra_cos) * ring_radius;
+				const Point2 a2 = Vector2(ra_sin, ra_cos) * ring_radius_top;
+				const Point2 inner_a = Vector2(ra_sin, ra_cos) * ring_inner_radius;
+				const Point2 inner_a2 = Vector2(ra_sin, ra_cos) * ring_inner_radius_top;
+
+				// Outer 90 degrees vertical lines.
+				points.push_back(basis.xform(Vector3(a2.x, half_ring_height, a2.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_a2.x, half_ring_height, inner_a2.y)));
+				points.push_back(basis.xform(Vector3(inner_a.x, -half_ring_height, inner_a.y)));
+			}
+
+			if (p_gizmo->is_selected()) {
+				p_gizmo->add_lines(points, material);
+			}
+		}
+	}
+}

+ 57 - 0
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<Gizmo3DHelper> 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

+ 2 - 0
editor/plugins/node_3d_editor_plugin.cpp

@@ -67,6 +67,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"
@@ -8422,6 +8423,7 @@ void Node3DEditor::_register_all_gizmos() {
 	add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));
+	add_gizmo_plugin(Ref<Particles3DEmissionShapeGizmoPlugin>(memnew(Particles3DEmissionShapeGizmoPlugin)));
 	add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
 	add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));

+ 8 - 0
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<Vector3> &p_points) {
@@ -434,22 +437,27 @@ void CPUParticles3D::set_emission_colors(const Vector<Color> &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) {
 	emission_ring_cone_angle = p_angle;
+	update_gizmos();
 }
 
 void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {

+ 13 - 0
scene/3d/gpu_particles_3d.cpp

@@ -127,6 +127,7 @@ void GPUParticles3D::set_process_material(const Ref<Material> &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);
 
@@ -463,6 +464,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) {
@@ -513,6 +522,10 @@ void GPUParticles3D::_notification(int p_what) {
 
 		case NOTIFICATION_EXIT_TREE: {
 			RS::get_singleton()->particles_set_subemitter(particles, RID());
+
+			Ref<ParticleProcessMaterial> 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_SUSPENDED:

+ 32 - 0
scene/resources/particle_process_material.cpp

@@ -1567,16 +1567,25 @@ void ParticleProcessMaterial::set_emission_shape(EmissionShape p_shape) {
 	emission_shape = p_shape;
 	notify_property_list_changed();
 	_queue_shader_change();
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 void ParticleProcessMaterial::set_emission_point_texture(const Ref<Texture2D> &p_points) {
@@ -1606,26 +1615,41 @@ 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) {
 	emission_ring_cone_angle = p_angle;
 	RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) {
@@ -1684,6 +1708,9 @@ 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 Vector3 ParticleProcessMaterial::get_emission_shape_offset() const {
@@ -1693,6 +1720,9 @@ 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);
+#ifdef TOOLS_ENABLED
+	emit_signal("emission_shape_changed");
+#endif
 }
 
 Vector3 ParticleProcessMaterial::get_emission_shape_scale() const {
@@ -2209,6 +2239,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);