Преглед на файлове

Continuation of work on visual particles system

Yuri Roubinsky преди 4 години
родител
ревизия
f632e36ae5

+ 8 - 4
doc/classes/VisualShader.xml

@@ -222,15 +222,19 @@
 		<constant name="TYPE_LIGHT" value="2" enum="Type">
 			A shader for light calculations.
 		</constant>
-		<constant name="TYPE_EMIT" value="3" enum="Type">
+		<constant name="TYPE_START" value="3" enum="Type">
 		</constant>
 		<constant name="TYPE_PROCESS" value="4" enum="Type">
 		</constant>
-		<constant name="TYPE_END" value="5" enum="Type">
+		<constant name="TYPE_COLLIDE" value="5" enum="Type">
 		</constant>
-		<constant name="TYPE_SKY" value="6" enum="Type">
+		<constant name="TYPE_START_CUSTOM" value="6" enum="Type">
 		</constant>
-		<constant name="TYPE_MAX" value="7" enum="Type">
+		<constant name="TYPE_PROCESS_CUSTOM" value="7" enum="Type">
+		</constant>
+		<constant name="TYPE_SKY" value="8" enum="Type">
+		</constant>
+		<constant name="TYPE_MAX" value="9" enum="Type">
 			Represents the size of the [enum Type] enum.
 		</constant>
 		<constant name="NODE_ID_INVALID" value="-1">

+ 25 - 0
doc/classes/VisualShaderNodeParticleAccelerator.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleAccelerator" inherits="VisualShaderNodeOutput" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="VisualShaderNodeParticleAccelerator.Mode" default="0">
+		</member>
+	</members>
+	<constants>
+		<constant name="MODE_LINEAR" value="0" enum="Mode">
+		</constant>
+		<constant name="MODE_RADIAL" value="1" enum="Mode">
+		</constant>
+		<constant name="MODE_TANGENTIAL" value="2" enum="Mode">
+		</constant>
+		<constant name="MODE_MAX" value="3" enum="Mode">
+		</constant>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleBoxEmitter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleBoxEmitter" inherits="VisualShaderNodeParticleEmitter" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleConeVelocity.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleConeVelocity" inherits="VisualShaderNode" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 27 - 0
doc/classes/VisualShaderNodeParticleEmit.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleEmit" inherits="VisualShaderNode" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="flags" type="int" setter="set_flags" getter="get_flags" enum="VisualShaderNodeParticleEmit.EmitFlags" default="31">
+		</member>
+	</members>
+	<constants>
+		<constant name="EMIT_FLAG_POSITION" value="1" enum="EmitFlags">
+		</constant>
+		<constant name="EMIT_FLAG_ROT_SCALE" value="2" enum="EmitFlags">
+		</constant>
+		<constant name="EMIT_FLAG_VELOCITY" value="4" enum="EmitFlags">
+		</constant>
+		<constant name="EMIT_FLAG_COLOR" value="8" enum="EmitFlags">
+		</constant>
+		<constant name="EMIT_FLAG_CUSTOM" value="16" enum="EmitFlags">
+		</constant>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleEmitter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleEmitter" inherits="VisualShaderNode" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 17 - 0
doc/classes/VisualShaderNodeParticleMultiplyByAxisAngle.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleMultiplyByAxisAngle" inherits="VisualShaderNode" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="degrees_mode" type="bool" setter="set_degrees_mode" getter="is_degrees_mode" default="true">
+		</member>
+	</members>
+	<constants>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleOutput.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleOutput" inherits="VisualShaderNodeOutput" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 23 - 0
doc/classes/VisualShaderNodeParticleRandomness.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleRandomness" inherits="VisualShaderNode" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<members>
+		<member name="op_type" type="int" setter="set_op_type" getter="get_op_type" enum="VisualShaderNodeParticleRandomness.OpType" default="0">
+		</member>
+	</members>
+	<constants>
+		<constant name="OP_TYPE_SCALAR" value="0" enum="OpType">
+		</constant>
+		<constant name="OP_TYPE_VECTOR" value="1" enum="OpType">
+		</constant>
+		<constant name="OP_TYPE_MAX" value="2" enum="OpType">
+		</constant>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleRingEmitter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleRingEmitter" inherits="VisualShaderNodeParticleEmitter" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 13 - 0
doc/classes/VisualShaderNodeParticleSphereEmitter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeParticleSphereEmitter" inherits="VisualShaderNodeParticleEmitter" version="4.0">
+	<brief_description>
+	</brief_description>
+	<description>
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 107 - 70
editor/plugins/visual_shader_editor_plugin.cpp

@@ -44,6 +44,7 @@
 #include "scene/gui/panel.h"
 #include "scene/main/window.h"
 #include "scene/resources/visual_shader_nodes.h"
+#include "scene/resources/visual_shader_particle_nodes.h"
 #include "scene/resources/visual_shader_sdf_nodes.h"
 #include "servers/display_server.h"
 #include "servers/rendering/shader_types.h"
@@ -417,6 +418,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
 		}
 	}
 
+	Ref<VisualShaderNodeParticleEmit> emit = vsnode;
+	if (emit.is_valid()) {
+		node->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
+	}
+
 	Ref<VisualShaderNodeUniform> uniform = vsnode;
 	if (uniform.is_valid()) {
 		VisualShaderEditor::get_singleton()->graph->add_child(node);
@@ -1013,13 +1019,13 @@ bool VisualShaderEditor::_is_available(int p_mode) {
 
 	if (p_mode != -1) {
 		switch (current_mode) {
-			case 0: // Vertex or Emit
+			case 0: // Vertex / Emit
 				current_mode = 1;
 				break;
-			case 1: // Fragment or Process
+			case 1: // Fragment / Process
 				current_mode = 2;
 				break;
-			case 2: // Light or End
+			case 2: // Light / Collide
 				current_mode = 4;
 				break;
 			default:
@@ -1235,22 +1241,29 @@ void VisualShaderEditor::_update_options_menu() {
 
 void VisualShaderEditor::_set_mode(int p_which) {
 	if (p_which == VisualShader::MODE_SKY) {
-		edit_type_standart->set_visible(false);
+		edit_type_standard->set_visible(false);
 		edit_type_particles->set_visible(false);
 		edit_type_sky->set_visible(true);
 		edit_type = edit_type_sky;
+		custom_mode_box->set_visible(false);
 		mode = MODE_FLAGS_SKY;
 	} else if (p_which == VisualShader::MODE_PARTICLES) {
-		edit_type_standart->set_visible(false);
+		edit_type_standard->set_visible(false);
 		edit_type_particles->set_visible(true);
 		edit_type_sky->set_visible(false);
 		edit_type = edit_type_particles;
+		if ((edit_type->get_selected() + 3) > VisualShader::TYPE_PROCESS) {
+			custom_mode_box->set_visible(false);
+		} else {
+			custom_mode_box->set_visible(true);
+		}
 		mode = MODE_FLAGS_PARTICLES;
 	} else {
 		edit_type_particles->set_visible(false);
-		edit_type_standart->set_visible(true);
+		edit_type_standard->set_visible(true);
 		edit_type_sky->set_visible(false);
-		edit_type = edit_type_standart;
+		edit_type = edit_type_standard;
+		custom_mode_box->set_visible(false);
 		mode = MODE_FLAGS_SPATIAL_CANVASITEM;
 	}
 	visual_shader->set_shader_type(get_current_shader_type());
@@ -1403,9 +1416,9 @@ void VisualShaderEditor::_update_graph() {
 VisualShader::Type VisualShaderEditor::get_current_shader_type() const {
 	VisualShader::Type type;
 	if (mode & MODE_FLAGS_PARTICLES) {
-		type = VisualShader::Type(edit_type->get_selected() + 3);
+		type = VisualShader::Type(edit_type->get_selected() + 3 + (custom_mode_enabled ? 3 : 0));
 	} else if (mode & MODE_FLAGS_SKY) {
-		type = VisualShader::Type(edit_type->get_selected() + 6);
+		type = VisualShader::Type(edit_type->get_selected() + 8);
 	} else {
 		type = VisualShader::Type(edit_type->get_selected());
 	}
@@ -3224,8 +3237,18 @@ void VisualShaderEditor::_mode_selected(int p_id) {
 	int offset = 0;
 	if (mode & MODE_FLAGS_PARTICLES) {
 		offset = 3;
+		if (p_id + offset > VisualShader::TYPE_PROCESS) {
+			custom_mode_box->set_visible(false);
+			custom_mode_enabled = false;
+		} else {
+			custom_mode_box->set_visible(true);
+			if (custom_mode_box->is_pressed()) {
+				custom_mode_enabled = true;
+				offset += 3;
+			}
+		}
 	} else if (mode & MODE_FLAGS_SKY) {
-		offset = 6;
+		offset = 8;
 	}
 
 	visual_shader->set_shader_type(VisualShader::Type(p_id + offset));
@@ -3233,6 +3256,21 @@ void VisualShaderEditor::_mode_selected(int p_id) {
 	_update_graph();
 }
 
+void VisualShaderEditor::_custom_mode_toggled(bool p_enabled) {
+	if (!(mode & MODE_FLAGS_PARTICLES)) {
+		return;
+	}
+	custom_mode_enabled = p_enabled;
+	int id = edit_type->get_selected() + 3;
+	if (p_enabled) {
+		visual_shader->set_shader_type(VisualShader::Type(id + 3));
+	} else {
+		visual_shader->set_shader_type(VisualShader::Type(id));
+	}
+	_update_options_menu();
+	_update_graph();
+}
+
 void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, String p_name) {
 	String prev_name = p_input->get_input_name();
 
@@ -3731,17 +3769,23 @@ VisualShaderEditor::VisualShaderEditor() {
 	graph->get_zoom_hbox()->add_child(vs);
 	graph->get_zoom_hbox()->move_child(vs, 0);
 
-	edit_type_standart = memnew(OptionButton);
-	edit_type_standart->add_item(TTR("Vertex"));
-	edit_type_standart->add_item(TTR("Fragment"));
-	edit_type_standart->add_item(TTR("Light"));
-	edit_type_standart->select(1);
-	edit_type_standart->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
+	custom_mode_box = memnew(CheckBox);
+	custom_mode_box->set_text(TTR("Custom"));
+	custom_mode_box->set_pressed(false);
+	custom_mode_box->set_visible(false);
+	custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
+
+	edit_type_standard = memnew(OptionButton);
+	edit_type_standard->add_item(TTR("Vertex"));
+	edit_type_standard->add_item(TTR("Fragment"));
+	edit_type_standard->add_item(TTR("Light"));
+	edit_type_standard->select(1);
+	edit_type_standard->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
 
 	edit_type_particles = memnew(OptionButton);
-	edit_type_particles->add_item(TTR("Emit"));
+	edit_type_particles->add_item(TTR("Start"));
 	edit_type_particles->add_item(TTR("Process"));
-	edit_type_particles->add_item(TTR("End"));
+	edit_type_particles->add_item(TTR("Collide"));
 	edit_type_particles->select(0);
 	edit_type_particles->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
 
@@ -3750,14 +3794,16 @@ VisualShaderEditor::VisualShaderEditor() {
 	edit_type_sky->select(0);
 	edit_type_sky->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected));
 
-	edit_type = edit_type_standart;
+	edit_type = edit_type_standard;
 
+	graph->get_zoom_hbox()->add_child(custom_mode_box);
+	graph->get_zoom_hbox()->move_child(custom_mode_box, 0);
+	graph->get_zoom_hbox()->add_child(edit_type_standard);
+	graph->get_zoom_hbox()->move_child(edit_type_standard, 0);
 	graph->get_zoom_hbox()->add_child(edit_type_particles);
 	graph->get_zoom_hbox()->move_child(edit_type_particles, 0);
 	graph->get_zoom_hbox()->add_child(edit_type_sky);
 	graph->get_zoom_hbox()->move_child(edit_type_sky, 0);
-	graph->get_zoom_hbox()->add_child(edit_type_standart);
-	graph->get_zoom_hbox()->move_child(edit_type_standart, 0);
 
 	add_node = memnew(Button);
 	add_node->set_flat(true);
@@ -3977,9 +4023,10 @@ VisualShaderEditor::VisualShaderEditor() {
 
 	// INPUT
 
+	const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.");
+
 	// SPATIAL-FOR-ALL
 
-	const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.");
 	add_options.push_back(AddOption("Camera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
 	add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
 	add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
@@ -3998,6 +4045,23 @@ VisualShaderEditor::VisualShaderEditor() {
 	add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM));
 	add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM));
 
+	// PARTICLES-FOR-ALL
+
+	add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force"), "attractor_force", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("CustomAlpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES));
+
 	/////////////////
 
 	add_options.push_back(AddOption("Input", "Input", "Common", "VisualShaderNodeInput", TTR("Input parameter.")));
@@ -4010,11 +4074,12 @@ VisualShaderEditor::VisualShaderEditor() {
 	const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode.");
 	const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode.");
 	const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode.");
-	const String input_param_for_emit_shader_mode = TTR("'%s' input parameter for emit shader mode.");
+	const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode.");
 	const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode.");
-	const String input_param_for_end_shader_mode = TTR("'%s' input parameter for end shader mode.");
-	const String input_param_for_emit_and_process_shader_mode = TTR("'%s' input parameter for emit and process shader mode.");
-	const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader mode.");
+	const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode.");
+	const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes.");
+	const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes.");
+	const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes.");
 
 	add_options.push_back(AddOption("Alpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL));
 	add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL));
@@ -4091,50 +4156,6 @@ VisualShaderEditor::VisualShaderEditor() {
 	add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM));
 	add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM));
 
-	// PARTICLES INPUTS
-
-	add_options.push_back(AddOption("Active", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Alpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Color", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Custom", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("CustomAlpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Delta", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("EmissionTransform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Index", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("LifeTime", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Restart", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Time", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Transform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Velocity", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
-
-	add_options.push_back(AddOption("Active", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Alpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Color", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Custom", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("CustomAlpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Delta", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("EmissionTransform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Index", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("LifeTime", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Restart", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Time", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Transform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Velocity", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
-
-	add_options.push_back(AddOption("Active", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Alpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Color", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Custom", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("CustomAlpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Delta", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("EmissionTransform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Index", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("LifeTime", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Restart", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Time", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Transform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-	add_options.push_back(AddOption("Velocity", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES));
-
 	// SKY INPUTS
 
 	add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY));
@@ -4167,6 +4188,22 @@ VisualShaderEditor::VisualShaderEditor() {
 	add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY));
 	add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY));
 
+	// PARTICLES
+
+	add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth"), "collision_depth", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal"), "collision_normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+
+	add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", -1, -1, -1, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
+
+	add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+	add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
+	add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
 	// SCALAR
 
 	add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), -1, VisualShaderNode::PORT_TYPE_SCALAR));

+ 7 - 2
editor/plugins/visual_shader_editor_plugin.h

@@ -142,9 +142,11 @@ class VisualShaderEditor : public VBoxContainer {
 	Button *preview_shader;
 
 	OptionButton *edit_type = nullptr;
-	OptionButton *edit_type_standart;
+	OptionButton *edit_type_standard;
 	OptionButton *edit_type_particles;
 	OptionButton *edit_type_sky;
+	CheckBox *custom_mode_box;
+	bool custom_mode_enabled = false;
 
 	bool pending_update_preview;
 	bool shader_error;
@@ -189,7 +191,9 @@ class VisualShaderEditor : public VBoxContainer {
 	enum ParticlesTypeFlags {
 		TYPE_FLAGS_EMIT = 1,
 		TYPE_FLAGS_PROCESS = 2,
-		TYPE_FLAGS_END = 4
+		TYPE_FLAGS_COLLIDE = 4,
+		TYPE_FLAGS_EMIT_CUSTOM = 8,
+		TYPE_FLAGS_PROCESS_CUSTOM = 16,
 	};
 
 	enum SkyTypeFlags {
@@ -385,6 +389,7 @@ class VisualShaderEditor : public VBoxContainer {
 	Ref<VisualShaderGraphPlugin> graph_plugin;
 
 	void _mode_selected(int p_id);
+	void _custom_mode_toggled(bool p_enabled);
 
 	void _input_select_item(Ref<VisualShaderNodeInput> input, String name);
 	void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name);

+ 12 - 0
scene/register_scene_types.cpp

@@ -183,6 +183,7 @@
 #include "scene/resources/video_stream.h"
 #include "scene/resources/visual_shader.h"
 #include "scene/resources/visual_shader_nodes.h"
+#include "scene/resources/visual_shader_particle_nodes.h"
 #include "scene/resources/visual_shader_sdf_nodes.h"
 #include "scene/resources/world_2d.h"
 #include "scene/resources/world_3d.h"
@@ -613,6 +614,17 @@ void register_scene_types() {
 	ClassDB::register_class<VisualShaderNodeTextureSDFNormal>();
 	ClassDB::register_class<VisualShaderNodeSDFRaymarch>();
 
+	ClassDB::register_class<VisualShaderNodeParticleOutput>();
+	ClassDB::register_virtual_class<VisualShaderNodeParticleEmitter>();
+	ClassDB::register_class<VisualShaderNodeParticleSphereEmitter>();
+	ClassDB::register_class<VisualShaderNodeParticleBoxEmitter>();
+	ClassDB::register_class<VisualShaderNodeParticleRingEmitter>();
+	ClassDB::register_class<VisualShaderNodeParticleMultiplyByAxisAngle>();
+	ClassDB::register_class<VisualShaderNodeParticleConeVelocity>();
+	ClassDB::register_class<VisualShaderNodeParticleRandomness>();
+	ClassDB::register_class<VisualShaderNodeParticleAccelerator>();
+	ClassDB::register_class<VisualShaderNodeParticleEmit>();
+
 	ClassDB::register_class<ShaderMaterial>();
 	ClassDB::register_virtual_class<CanvasItem>();
 	ClassDB::register_class<CanvasTexture>();

+ 251 - 83
scene/resources/visual_shader.cpp

@@ -33,6 +33,7 @@
 #include "core/templates/vmap.h"
 #include "servers/rendering/shader_types.h"
 #include "visual_shader_nodes.h"
+#include "visual_shader_particle_nodes.h"
 #include "visual_shader_sdf_nodes.h"
 
 bool VisualShaderNode::is_simple_decl() const {
@@ -1079,9 +1080,11 @@ static const char *type_string[VisualShader::TYPE_MAX] = {
 	"vertex",
 	"fragment",
 	"light",
-	"emit",
+	"start",
 	"process",
-	"end",
+	"collide",
+	"start_custom",
+	"process_custom",
 	"sky",
 };
 
@@ -1357,7 +1360,8 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
 		return OK;
 	}
 
-	code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n";
+	String node_name = "// " + vsnode->get_caption() + ":" + itos(node) + "\n";
+	String node_code;
 	Vector<String> input_vars;
 
 	input_vars.resize(vsnode->get_input_port_count());
@@ -1428,19 +1432,19 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
 			if (defval.get_type() == Variant::FLOAT) {
 				float val = defval;
 				inputs[i] = "n_in" + itos(node) + "p" + itos(i);
-				code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n";
+				node_code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n";
 			} else if (defval.get_type() == Variant::INT) {
 				int val = defval;
 				inputs[i] = "n_in" + itos(node) + "p" + itos(i);
-				code += "\tint " + inputs[i] + " = " + itos(val) + ";\n";
+				node_code += "\tint " + inputs[i] + " = " + itos(val) + ";\n";
 			} else if (defval.get_type() == Variant::BOOL) {
 				bool val = defval;
 				inputs[i] = "n_in" + itos(node) + "p" + itos(i);
-				code += "\tbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n";
+				node_code += "\tbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n";
 			} else if (defval.get_type() == Variant::VECTOR3) {
 				Vector3 val = defval;
 				inputs[i] = "n_in" + itos(node) + "p" + itos(i);
-				code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z);
+				node_code += "\tvec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z);
 			} else if (defval.get_type() == Variant::TRANSFORM3D) {
 				Transform3D val = defval;
 				val.basis.transpose();
@@ -1455,7 +1459,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
 				values.push_back(val.origin.y);
 				values.push_back(val.origin.z);
 				bool err = false;
-				code += "\tmat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err);
+				node_code += "\tmat4 " + inputs[i] + " = " + String("mat4(vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 0.0), vec4(%.5f, %.5f, %.5f, 1.0));\n").sprintf(values, &err);
 			} else {
 				//will go empty, node is expected to know what it is doing at this point and handle it
 			}
@@ -1543,7 +1547,12 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
 		}
 	}
 
-	code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview);
+	node_code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview);
+	if (node_code != String()) {
+		code += node_name;
+		code += node_code;
+		code += "\n";
+	}
 
 	for (int i = 0; i < output_count; i++) {
 		bool new_line_inserted = false;
@@ -1593,7 +1602,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
 bool VisualShader::has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const {
 	if (!ShaderTypes::get_singleton()->get_functions(p_mode).has(p_func_name)) {
 		if (p_mode == RenderingServer::ShaderMode::SHADER_PARTICLES) {
-			if (p_func_name == "emit" || p_func_name == "process" || p_func_name == "end") {
+			if (p_func_name == "start_custom" || p_func_name == "process_custom" || p_func_name == "collide") {
 				return true;
 			}
 		}
@@ -1674,11 +1683,12 @@ void VisualShader::_update_shader() const {
 		global_code += "render_mode " + render_mode + ";\n\n";
 	}
 
-	static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "emit", "process", "end", "sky" };
+	static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light", "start", "process", "collide", "start_custom", "process_custom", "sky" };
 
 	String global_expressions;
 	Set<String> used_uniform_names;
 	List<VisualShaderNodeUniform *> uniforms;
+	Map<int, List<int>> emitters;
 
 	for (int i = 0, index = 0; i < TYPE_MAX; i++) {
 		if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) {
@@ -1703,6 +1713,19 @@ void VisualShader::_update_shader() const {
 			if (uniform.is_valid()) {
 				uniforms.push_back(uniform.ptr());
 			}
+			Ref<VisualShaderNodeParticleEmit> emit_particle = Object::cast_to<VisualShaderNodeParticleEmit>(E->get().node.ptr());
+			if (emit_particle.is_valid()) {
+				if (!emitters.has(i)) {
+					emitters.insert(i, List<int>());
+				}
+
+				for (Map<int, Node>::Element *M = graph[i].nodes.front(); M; M = M->next()) {
+					if (M->get().node == emit_particle.ptr()) {
+						emitters[i].push_back(M->key());
+						break;
+					}
+				}
+			}
 		}
 	}
 
@@ -1751,6 +1774,13 @@ void VisualShader::_update_shader() const {
 		Error err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false, classes);
 		ERR_FAIL_COND(err != OK);
 
+		if (emitters.has(i)) {
+			for (List<int>::Element *E = emitters[i].front(); E; E = E->next()) {
+				err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, func_code, default_tex_params, input_connections, output_connections, E->get(), processed, false, classes);
+				ERR_FAIL_COND(err != OK);
+			}
+		}
+
 		if (shader_mode == Shader::MODE_PARTICLES) {
 			code_map.insert(i, func_code);
 		} else {
@@ -1759,19 +1789,130 @@ void VisualShader::_update_shader() const {
 		}
 	}
 
+	String global_compute_code;
+
 	if (shader_mode == Shader::MODE_PARTICLES) {
-		code += "\nvoid compute() {\n";
-		code += "\tif (RESTART) {\n";
-		code += code_map[TYPE_EMIT];
-		code += "\t} else {\n";
-		code += code_map[TYPE_PROCESS];
+		bool has_start = !code_map[TYPE_START].is_empty();
+		bool has_start_custom = !code_map[TYPE_START_CUSTOM].is_empty();
+		bool has_process = !code_map[TYPE_PROCESS].is_empty();
+		bool has_process_custom = !code_map[TYPE_PROCESS_CUSTOM].is_empty();
+		bool has_collide = !code_map[TYPE_COLLIDE].is_empty();
+
+		code += "void start() {\n";
+		if (has_start || has_start_custom) {
+			code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n";
+			code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n";
+			code += "\tfloat __radians;\n";
+			code += "\tvec3 __vec3_buff1;\n";
+			code += "\tvec3 __vec3_buff2;\n";
+			code += "\tfloat __scalar_buff1;\n";
+			code += "\tfloat __scalar_buff2;\n";
+			code += "\tvec3 __ndiff = normalize(__diff);\n\n";
+		}
+		if (has_start) {
+			code += "\t{\n";
+			code += code_map[TYPE_START].replace("\n\t", "\n\t\t");
+			code += "\t}\n";
+			if (has_start_custom) {
+				code += "\t\n";
+			}
+		}
+		if (has_start_custom) {
+			code += "\t{\n";
+			code += code_map[TYPE_START_CUSTOM].replace("\n\t", "\n\t\t");
+			code += "\t}\n";
+		}
+		code += "}\n\n";
+		code += "void process() {\n";
+		if (has_process || has_process_custom || has_collide) {
+			code += "\tuint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n";
+			code += "\tvec3 __vec3_buff1;\n";
+			code += "\tvec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n";
+			code += "\tvec3 __ndiff = normalize(__diff);\n\n";
+		}
+		code += "\t{\n";
+		String tab = "\t";
+		if (has_collide) {
+			code += "\t\tif (COLLIDED) {\n\n";
+			code += code_map[TYPE_COLLIDE].replace("\n\t", "\n\t\t\t");
+			if (has_process) {
+				code += "\t\t} else {\n\n";
+				tab += "\t";
+			}
+		}
+		if (has_process) {
+			code += code_map[TYPE_PROCESS].replace("\n\t", "\n\t" + tab);
+		}
+		if (has_collide) {
+			code += "\t\t}\n";
+		}
 		code += "\t}\n";
-		code += "}\n";
+
+		if (has_process_custom) {
+			code += "\t{\n\n";
+			code += code_map[TYPE_PROCESS_CUSTOM].replace("\n\t", "\n\t\t");
+			code += "\t}\n";
+		}
+
+		code += "}\n\n";
+
+		global_compute_code += "float __rand_from_seed(inout uint seed) {\n";
+		global_compute_code += "\tint k;\n";
+		global_compute_code += "\tint s = int(seed);\n";
+		global_compute_code += "\tif (s == 0)\n";
+		global_compute_code += "\ts = 305420679;\n";
+		global_compute_code += "\tk = s / 127773;\n";
+		global_compute_code += "\ts = 16807 * (s - k * 127773) - 2836 * k;\n";
+		global_compute_code += "\tif (s < 0)\n";
+		global_compute_code += "\t\ts += 2147483647;\n";
+		global_compute_code += "\tseed = uint(s);\n";
+		global_compute_code += "\treturn float(seed % uint(65536)) / 65535.0;\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "float __rand_from_seed_m1_p1(inout uint seed) {\n";
+		global_compute_code += "\treturn __rand_from_seed(seed) * 2.0 - 1.0;\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "float __randf_range(inout uint seed, float from, float to) {\n";
+		global_compute_code += "\treturn __rand_from_seed(seed) * (to - from) + from;\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "vec3 __randv_range(inout uint seed, vec3 from, vec3 to) {\n";
+		global_compute_code += "\treturn vec3(__randf_range(seed, from.x, to.x), __randf_range(seed, from.y, to.y), __randf_range(seed, from.z, to.z));\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "uint __hash(uint x) {\n";
+		global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n";
+		global_compute_code += "\tx = ((x >> uint(16)) ^ x) * uint(73244475);\n";
+		global_compute_code += "\tx = (x >> uint(16)) ^ x;\n";
+		global_compute_code += "\treturn x;\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "mat3 __build_rotation_mat3(vec3 axis, float angle) {\n";
+		global_compute_code += "\taxis = normalize(axis);\n";
+		global_compute_code += "\tfloat s = sin(angle);\n";
+		global_compute_code += "\tfloat c = cos(angle);\n";
+		global_compute_code += "\tfloat oc = 1.0 - c;\n";
+		global_compute_code += "\treturn mat3(vec3(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s), vec3(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s), vec3(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c));\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "mat4 __build_rotation_mat4(vec3 axis, float angle) {\n";
+		global_compute_code += "\taxis = normalize(axis);\n";
+		global_compute_code += "\tfloat s = sin(angle);\n";
+		global_compute_code += "\tfloat c = cos(angle);\n";
+		global_compute_code += "\tfloat oc = 1.0 - c;\n";
+		global_compute_code += "\treturn mat4(vec4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0), vec4(oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0), vec4(oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0), vec4(0, 0, 0, 1));\n";
+		global_compute_code += "}\n\n";
+
+		global_compute_code += "vec3 __get_random_unit_vec3(inout uint seed) {\n";
+		global_compute_code += "\treturn normalize(vec3(__rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed), __rand_from_seed_m1_p1(seed)));\n";
+		global_compute_code += "}\n\n";
 	}
 
 	//set code secretly
 	global_code += "\n\n";
 	String final_code = global_code;
+	final_code += global_compute_code;
 	final_code += global_code_per_node;
 	final_code += global_expressions;
 	String tcode = code;
@@ -1862,9 +2003,11 @@ void VisualShader::_bind_methods() {
 	BIND_ENUM_CONSTANT(TYPE_VERTEX);
 	BIND_ENUM_CONSTANT(TYPE_FRAGMENT);
 	BIND_ENUM_CONSTANT(TYPE_LIGHT);
-	BIND_ENUM_CONSTANT(TYPE_EMIT);
+	BIND_ENUM_CONSTANT(TYPE_START);
 	BIND_ENUM_CONSTANT(TYPE_PROCESS);
-	BIND_ENUM_CONSTANT(TYPE_END);
+	BIND_ENUM_CONSTANT(TYPE_COLLIDE);
+	BIND_ENUM_CONSTANT(TYPE_START_CUSTOM);
+	BIND_ENUM_CONSTANT(TYPE_PROCESS_CUSTOM);
 	BIND_ENUM_CONSTANT(TYPE_SKY);
 	BIND_ENUM_CONSTANT(TYPE_MAX);
 
@@ -1875,11 +2018,20 @@ void VisualShader::_bind_methods() {
 VisualShader::VisualShader() {
 	dirty.set();
 	for (int i = 0; i < TYPE_MAX; i++) {
-		Ref<VisualShaderNodeOutput> output;
-		output.instance();
-		output->shader_type = Type(i);
-		output->shader_mode = shader_mode;
-		graph[i].nodes[NODE_ID_OUTPUT].node = output;
+		if (i > (int)TYPE_LIGHT && i < (int)TYPE_SKY) {
+			Ref<VisualShaderNodeParticleOutput> output;
+			output.instance();
+			output->shader_type = Type(i);
+			output->shader_mode = shader_mode;
+			graph[i].nodes[NODE_ID_OUTPUT].node = output;
+		} else {
+			Ref<VisualShaderNodeOutput> output;
+			output.instance();
+			output->shader_type = Type(i);
+			output->shader_mode = shader_mode;
+			graph[i].nodes[NODE_ID_OUTPUT].node = output;
+		}
+
 		graph[i].nodes[NODE_ID_OUTPUT].position = Vector2(400, 150);
 	}
 }
@@ -2008,27 +2160,45 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular_shininess", "SPECULAR_SHININESS.rgb" },
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "specular_shininess_alpha", "SPECULAR_SHININESS.a" },
 
-	// Particles, Emit
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	// Particles, Start
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+	// Particles, Start (Custom)
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
 
 	// Particles, Process
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
@@ -2038,20 +2208,39 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
 	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
 
-	// Particles, End
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "restart", "float(RESTART ? 1.0 : 0.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "active", "float(ACTIVE ? 1.0 : 0.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	// Particles, Process (Custom)
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+	// Particles, Collide
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "attractor_force", "ATTRACTOR_FORCE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "collision_depth", "COLLISION_DEPTH" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "collision_normal", "COLLISION_NORMAL" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR_INT, "index", "INDEX" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
 
 	// Sky, Sky
 	{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_cubemap_pass", "AT_CUBEMAP_PASS" },
@@ -2127,11 +2316,13 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = {
 
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV, 0.0)" },
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
-	// Particles, Vertex
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "vec3(0.0, 0.0, 1.0)" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+
+	// Particles
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
+	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" },
 	{ Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr },
 };
 
@@ -2624,30 +2815,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
 	// Canvas Item, Light
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT.rgb" },
 	{ Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_alpha", "LIGHT.a" },
-	// Particles, Emit
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_EMIT, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
-	// Particles, Process
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
-	// Particles, End
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "velocity", "VELOCITY" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_VECTOR, "custom", "CUSTOM.rgb" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_SCALAR, "custom_alpha", "CUSTOM.a" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" },
-	{ Shader::MODE_PARTICLES, VisualShader::TYPE_END, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" },
+
 	// Sky, Sky
 	{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR" },
 	{ Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "ALPHA" },

+ 4 - 2
scene/resources/visual_shader.h

@@ -51,9 +51,11 @@ public:
 		TYPE_VERTEX,
 		TYPE_FRAGMENT,
 		TYPE_LIGHT,
-		TYPE_EMIT,
+		TYPE_START,
 		TYPE_PROCESS,
-		TYPE_END,
+		TYPE_COLLIDE,
+		TYPE_START_CUSTOM,
+		TYPE_PROCESS_CUSTOM,
 		TYPE_SKY,
 		TYPE_MAX
 	};

+ 1 - 1
scene/resources/visual_shader_nodes.cpp

@@ -860,7 +860,7 @@ String VisualShaderNodeCurveTexture::generate_code(Shader::Mode p_mode, VisualSh
 	}
 	String id = make_unique_id(p_type, p_id, "curve");
 	String code;
-	code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ", 0.0)).r;\n";
+	code += "\t" + p_output_vars[0] + " = texture(" + id + ", vec2(" + p_input_vars[0] + ")).r;\n";
 	return code;
 }
 

+ 1025 - 0
scene/resources/visual_shader_particle_nodes.cpp

@@ -0,0 +1,1025 @@
+/*************************************************************************/
+/*  visual_shader_particle_nodes.cpp                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 "visual_shader_particle_nodes.h"
+
+// VisualShaderNodeParticleEmitter
+
+int VisualShaderNodeParticleEmitter::get_output_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleEmitter::PortType VisualShaderNodeParticleEmitter::get_output_port_type(int p_port) const {
+	return PORT_TYPE_VECTOR;
+}
+
+String VisualShaderNodeParticleEmitter::get_output_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "position";
+	}
+	return String();
+}
+
+VisualShaderNodeParticleEmitter::VisualShaderNodeParticleEmitter() {
+}
+
+// VisualShaderNodeParticleSphereEmitter
+
+String VisualShaderNodeParticleSphereEmitter::get_caption() const {
+	return "SphereEmitter";
+}
+
+int VisualShaderNodeParticleSphereEmitter::get_input_port_count() const {
+	return 2;
+}
+
+VisualShaderNodeParticleSphereEmitter::PortType VisualShaderNodeParticleSphereEmitter::get_input_port_type(int p_port) const {
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleSphereEmitter::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "radius";
+	} else if (p_port == 1) {
+		return "inner_radius";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleSphereEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
+	String code;
+	code += "vec3 __get_random_point_in_sphere(inout uint seed, float radius, float inner_radius) {\n";
+	code += "\treturn __get_random_unit_vec3(seed) * __randf_range(seed, inner_radius, radius);\n";
+	code += "}\n\n";
+	return code;
+}
+
+String VisualShaderNodeParticleSphereEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	code += "\t" + p_output_vars[0] + " = __get_random_point_in_sphere(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n";
+	return code;
+}
+
+VisualShaderNodeParticleSphereEmitter::VisualShaderNodeParticleSphereEmitter() {
+	set_input_port_default_value(0, 10.0);
+	set_input_port_default_value(1, 0.0);
+}
+
+// VisualShaderNodeParticleBoxEmitter
+
+String VisualShaderNodeParticleBoxEmitter::get_caption() const {
+	return "BoxEmitter";
+}
+
+int VisualShaderNodeParticleBoxEmitter::get_input_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleBoxEmitter::PortType VisualShaderNodeParticleBoxEmitter::get_input_port_type(int p_port) const {
+	if (p_port == 0) {
+		return PORT_TYPE_VECTOR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleBoxEmitter::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "extents";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleBoxEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
+	String code;
+	code += "vec3 __get_random_point_in_box(inout uint seed, vec3 extents) {\n";
+	code += "\tvec3 half_extents = extents / 2.0;\n";
+	code += "\treturn vec3(__randf_range(seed, -half_extents.x, half_extents.x), __randf_range(seed, -half_extents.y, half_extents.y), __randf_range(seed, -half_extents.z, half_extents.z));\n";
+	code += "}\n\n";
+	return code;
+}
+
+String VisualShaderNodeParticleBoxEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	code += "\t" + p_output_vars[0] + " = __get_random_point_in_box(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ");\n";
+	return code;
+}
+
+VisualShaderNodeParticleBoxEmitter::VisualShaderNodeParticleBoxEmitter() {
+	set_input_port_default_value(0, Vector3(1.0, 1.0, 1.0));
+}
+
+// VisualShaderNodeParticleRingEmitter
+
+String VisualShaderNodeParticleRingEmitter::get_caption() const {
+	return "RingEmitter";
+}
+
+int VisualShaderNodeParticleRingEmitter::get_input_port_count() const {
+	return 3;
+}
+
+VisualShaderNodeParticleRingEmitter::PortType VisualShaderNodeParticleRingEmitter::get_input_port_type(int p_port) const {
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleRingEmitter::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "radius";
+	} else if (p_port == 1) {
+		return "inner_radius";
+	} else if (p_port == 2) {
+		return "height";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleRingEmitter::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const {
+	String code;
+	code += "vec3 __get_random_point_on_ring(inout uint seed, float radius, float inner_radius, float height) {\n";
+	code += "\tfloat angle = __rand_from_seed(seed) * PI * 2.0;\n";
+	code += "\tvec2 ring = vec2(sin(angle), cos(angle)) * __randf_range(seed, inner_radius, radius);\n";
+	code += "\treturn vec3(ring.x, __randf_range(seed, min(0.0, height), max(0.0, height)), ring.y);\n";
+	code += "}\n\n";
+	return code;
+}
+
+String VisualShaderNodeParticleRingEmitter::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	code = "\t" + p_output_vars[0] + " = __get_random_point_on_ring(__seed, " + (p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0]) + ", " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ");\n";
+	return code;
+}
+
+VisualShaderNodeParticleRingEmitter::VisualShaderNodeParticleRingEmitter() {
+	set_input_port_default_value(0, 10.0);
+	set_input_port_default_value(1, 0.0);
+	set_input_port_default_value(2, 0.0);
+}
+
+// VisualShaderNodeParticleMultiplyByAxisAngle
+
+void VisualShaderNodeParticleMultiplyByAxisAngle::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_degrees_mode", "enabled"), &VisualShaderNodeParticleMultiplyByAxisAngle::set_degrees_mode);
+	ClassDB::bind_method(D_METHOD("is_degrees_mode"), &VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode);
+
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "degrees_mode"), "set_degrees_mode", "is_degrees_mode");
+}
+
+String VisualShaderNodeParticleMultiplyByAxisAngle::get_caption() const {
+	return "MultiplyByAxisAngle";
+}
+
+int VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_count() const {
+	return 3;
+}
+
+VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_type(int p_port) const {
+	if (p_port == 0 || p_port == 1) { // position, rotation_axis
+		return PORT_TYPE_VECTOR;
+	}
+	return PORT_TYPE_SCALAR; // angle (degrees/radians)
+}
+
+String VisualShaderNodeParticleMultiplyByAxisAngle::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "position";
+	}
+	if (p_port == 1) {
+		return "axis";
+	}
+	if (p_port == 2) {
+		if (degrees_mode) {
+			return "angle (degrees)";
+		} else {
+			return "angle (radians)";
+		}
+	}
+	return String();
+}
+
+bool VisualShaderNodeParticleMultiplyByAxisAngle::is_show_prop_names() const {
+	return true;
+}
+
+int VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleMultiplyByAxisAngle::PortType VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_type(int p_port) const {
+	return PORT_TYPE_VECTOR;
+}
+
+String VisualShaderNodeParticleMultiplyByAxisAngle::get_output_port_name(int p_port) const {
+	return "position";
+}
+
+String VisualShaderNodeParticleMultiplyByAxisAngle::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	if (degrees_mode) {
+		code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", radians(" + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ")) * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n";
+	} else {
+		code += "\t" + p_output_vars[0] + " = __build_rotation_mat3(" + (p_input_vars[1].is_empty() ? ("vec3" + (String)get_input_port_default_value(1)) : p_input_vars[1]) + ", " + (p_input_vars[2].is_empty() ? (String)get_input_port_default_value(2) : p_input_vars[2]) + ") * " + (p_input_vars[0].is_empty() ? "vec3(0.0)" : p_input_vars[0]) + ";\n";
+	}
+	return code;
+}
+
+void VisualShaderNodeParticleMultiplyByAxisAngle::set_degrees_mode(bool p_enabled) {
+	degrees_mode = p_enabled;
+	emit_changed();
+}
+
+bool VisualShaderNodeParticleMultiplyByAxisAngle::is_degrees_mode() const {
+	return degrees_mode;
+}
+
+Vector<StringName> VisualShaderNodeParticleMultiplyByAxisAngle::get_editable_properties() const {
+	Vector<StringName> props;
+	props.push_back("degrees_mode");
+	props.push_back("axis_amount");
+	return props;
+}
+
+VisualShaderNodeParticleMultiplyByAxisAngle::VisualShaderNodeParticleMultiplyByAxisAngle() {
+	set_input_port_default_value(1, Vector3(1, 0, 0));
+	set_input_port_default_value(2, 0.0);
+}
+
+// VisualShaderNodeParticleConeVelocity
+
+String VisualShaderNodeParticleConeVelocity::get_caption() const {
+	return "ConeVelocity";
+}
+
+int VisualShaderNodeParticleConeVelocity::get_input_port_count() const {
+	return 2;
+}
+
+VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_input_port_type(int p_port) const {
+	if (p_port == 0) {
+		return PORT_TYPE_VECTOR;
+	} else if (p_port == 1) {
+		return PORT_TYPE_SCALAR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleConeVelocity::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "direction";
+	} else if (p_port == 1) {
+		return "spread(degrees)";
+	}
+	return String();
+}
+
+int VisualShaderNodeParticleConeVelocity::get_output_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleConeVelocity::PortType VisualShaderNodeParticleConeVelocity::get_output_port_type(int p_port) const {
+	return PORT_TYPE_VECTOR;
+}
+
+String VisualShaderNodeParticleConeVelocity::get_output_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "velocity";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleConeVelocity::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	code += "\t__radians = radians(" + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ");\n";
+	code += "\t__scalar_buff1 = __rand_from_seed_m1_p1(__seed) * __radians;\n";
+	code += "\t__scalar_buff2 = __rand_from_seed_m1_p1(__seed) * __radians;\n";
+	code += "\t__vec3_buff1 = " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + ";\n";
+	code += "\t__scalar_buff1 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.x, __vec3_buff1.z) : sign(__vec3_buff1.x) * (PI / 2.0);\n";
+	code += "\t__scalar_buff2 += __vec3_buff1.z != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.z)) : (__vec3_buff1.x != 0.0 ? atan(__vec3_buff1.y, abs(__vec3_buff1.x)) : sign(__vec3_buff1.y) * (PI / 2.0));\n";
+	code += "\t__vec3_buff1 = vec3(sin(__scalar_buff1), 0.0, cos(__scalar_buff1));\n";
+	code += "\t__vec3_buff2 = vec3(0.0, sin(__scalar_buff2), cos(__scalar_buff2));\n";
+	code += "\t__vec3_buff2.z = __vec3_buff2.z / max(0.0001, sqrt(abs(__vec3_buff2.z)));\n";
+	code += "\t" + p_output_vars[0] + " = normalize(vec3(__vec3_buff1.x * __vec3_buff2.z, __vec3_buff2.y, __vec3_buff1.z * __vec3_buff2.z));\n";
+	return code;
+}
+
+VisualShaderNodeParticleConeVelocity::VisualShaderNodeParticleConeVelocity() {
+	set_input_port_default_value(0, Vector3(1, 0, 0));
+	set_input_port_default_value(1, 45.0);
+}
+
+// VisualShaderNodeParticleRandomness
+
+void VisualShaderNodeParticleRandomness::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_op_type", "type"), &VisualShaderNodeParticleRandomness::set_op_type);
+	ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeParticleRandomness::get_op_type);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector"), "set_op_type", "get_op_type");
+
+	BIND_ENUM_CONSTANT(OP_TYPE_SCALAR);
+	BIND_ENUM_CONSTANT(OP_TYPE_VECTOR);
+	BIND_ENUM_CONSTANT(OP_TYPE_MAX);
+}
+
+Vector<StringName> VisualShaderNodeParticleRandomness::get_editable_properties() const {
+	Vector<StringName> props;
+	props.push_back("op_type");
+	return props;
+}
+
+String VisualShaderNodeParticleRandomness::get_caption() const {
+	return "ParticleRandomness";
+}
+
+int VisualShaderNodeParticleRandomness::get_output_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_output_port_type(int p_port) const {
+	if (op_type == OP_TYPE_VECTOR) {
+		return PORT_TYPE_VECTOR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleRandomness::get_output_port_name(int p_port) const {
+	return "random";
+}
+
+int VisualShaderNodeParticleRandomness::get_input_port_count() const {
+	return 2;
+}
+
+VisualShaderNodeParticleRandomness::PortType VisualShaderNodeParticleRandomness::get_input_port_type(int p_port) const {
+	if (op_type == OP_TYPE_VECTOR) {
+		return PORT_TYPE_VECTOR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleRandomness::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "min";
+	} else if (p_port == 1) {
+		return "max";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleRandomness::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	if (op_type == OP_TYPE_SCALAR) {
+		code += vformat("\t%s = __randf_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]);
+	} else if (op_type == OP_TYPE_VECTOR) {
+		code += vformat("\t%s = __randv_range(__seed, %s, %s);\n", p_output_vars[0], p_input_vars[0].is_empty() ? (String)get_input_port_default_value(0) : p_input_vars[0], p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]);
+	}
+	return code;
+}
+
+void VisualShaderNodeParticleRandomness::set_op_type(OpType p_op_type) {
+	ERR_FAIL_INDEX((int)p_op_type, OP_TYPE_MAX);
+	if (p_op_type != op_type) {
+		if (p_op_type == OP_TYPE_SCALAR) {
+			set_input_port_default_value(0, 0.0);
+			set_input_port_default_value(1, 1.0);
+		} else {
+			set_input_port_default_value(0, Vector3(-1.0, -1.0, -1.0));
+			set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0));
+		}
+	}
+	op_type = p_op_type;
+	emit_changed();
+}
+
+VisualShaderNodeParticleRandomness::OpType VisualShaderNodeParticleRandomness::get_op_type() const {
+	return op_type;
+}
+
+VisualShaderNodeParticleRandomness::VisualShaderNodeParticleRandomness() {
+	set_input_port_default_value(0, 0.0);
+	set_input_port_default_value(1, 1.0);
+}
+
+// VisualShaderNodeParticleAccelerator
+
+void VisualShaderNodeParticleAccelerator::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VisualShaderNodeParticleAccelerator::set_mode);
+	ClassDB::bind_method(D_METHOD("get_mode"), &VisualShaderNodeParticleAccelerator::get_mode);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Linear,Radial,Tangential"), "set_mode", "get_mode");
+
+	BIND_ENUM_CONSTANT(MODE_LINEAR);
+	BIND_ENUM_CONSTANT(MODE_RADIAL)
+	BIND_ENUM_CONSTANT(MODE_TANGENTIAL);
+	BIND_ENUM_CONSTANT(MODE_MAX);
+}
+
+Vector<StringName> VisualShaderNodeParticleAccelerator::get_editable_properties() const {
+	Vector<StringName> props;
+	props.push_back("mode");
+	return props;
+}
+
+String VisualShaderNodeParticleAccelerator::get_caption() const {
+	return "ParticleAccelerator";
+}
+
+int VisualShaderNodeParticleAccelerator::get_output_port_count() const {
+	return 1;
+}
+
+VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_output_port_type(int p_port) const {
+	return PORT_TYPE_VECTOR;
+}
+
+String VisualShaderNodeParticleAccelerator::get_output_port_name(int p_port) const {
+	return String();
+}
+
+int VisualShaderNodeParticleAccelerator::get_input_port_count() const {
+	return 3;
+}
+
+VisualShaderNodeParticleAccelerator::PortType VisualShaderNodeParticleAccelerator::get_input_port_type(int p_port) const {
+	if (p_port == 0) {
+		return PORT_TYPE_VECTOR;
+	} else if (p_port == 1) {
+		return PORT_TYPE_SCALAR;
+	} else if (p_port == 2) {
+		return PORT_TYPE_VECTOR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleAccelerator::get_input_port_name(int p_port) const {
+	if (p_port == 0) {
+		return "amount";
+	} else if (p_port == 1) {
+		return "randomness";
+	} else if (p_port == 2) {
+		return "axis";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleAccelerator::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	switch (mode) {
+		case MODE_LINEAR:
+			code += "\t" + p_output_vars[0] + " = length(VELOCITY) > 0.0 ? " + "normalize(VELOCITY) * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n";
+			break;
+		case MODE_RADIAL:
+			code += "\t" + p_output_vars[0] + " = length(__diff) > 0.0 ? __ndiff * " + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ") : vec3(0.0);\n";
+			break;
+		case MODE_TANGENTIAL:
+			code += "\t__vec3_buff1 = cross(__ndiff, normalize(" + (p_input_vars[2].is_empty() ? "vec3" + (String)get_input_port_default_value(2) : p_input_vars[2]) + "));\n";
+			code += "\t" + p_output_vars[0] + " = length(__vec3_buff1) > 0.0 ? normalize(__vec3_buff1) * (" + (p_input_vars[0].is_empty() ? "vec3" + (String)get_input_port_default_value(0) : p_input_vars[0]) + " * mix(1.0, __rand_from_seed(__seed), " + (p_input_vars[1].is_empty() ? (String)get_input_port_default_value(1) : p_input_vars[1]) + ")) : vec3(0.0);\n";
+			break;
+		case MODE_MAX:
+			break;
+		default:
+			break;
+	}
+
+	return code;
+}
+
+void VisualShaderNodeParticleAccelerator::set_mode(Mode p_mode) {
+	mode = p_mode;
+	emit_changed();
+}
+
+VisualShaderNodeParticleAccelerator::Mode VisualShaderNodeParticleAccelerator::get_mode() const {
+	return mode;
+}
+
+VisualShaderNodeParticleAccelerator::VisualShaderNodeParticleAccelerator() {
+	set_input_port_default_value(0, Vector3(1, 1, 1));
+	set_input_port_default_value(1, 0.0);
+	set_input_port_default_value(2, Vector3(0, -9.8, 0));
+}
+
+// VisualShaderNodeParticleOutput
+
+String VisualShaderNodeParticleOutput::get_caption() const {
+	if (shader_type == VisualShader::TYPE_START) {
+		return "StartOutput";
+	} else if (shader_type == VisualShader::TYPE_PROCESS) {
+		return "ProcessOutput";
+	} else if (shader_type == VisualShader::TYPE_COLLIDE) {
+		return "CollideOutput";
+	} else if (shader_type == VisualShader::TYPE_START_CUSTOM) {
+		return "CustomStartOutput";
+	} else if (shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+		return "CustomProcessOutput";
+	}
+	return String();
+}
+
+int VisualShaderNodeParticleOutput::get_input_port_count() const {
+	if (shader_type == VisualShader::TYPE_START) {
+		return 8;
+	} else if (shader_type == VisualShader::TYPE_COLLIDE) {
+		return 5;
+	} else if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+		return 6;
+	} else { // TYPE_PROCESS
+		return 7;
+	}
+	return 0;
+}
+
+VisualShaderNodeParticleOutput::PortType VisualShaderNodeParticleOutput::get_input_port_type(int p_port) const {
+	switch (p_port) {
+		case 0:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				return PORT_TYPE_VECTOR; // custom.rgb
+			}
+			return PORT_TYPE_BOOLEAN; // active
+		case 1:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				break; // custom.a (scalar)
+			}
+			return PORT_TYPE_VECTOR; // velocity
+		case 2:
+			return PORT_TYPE_VECTOR; // color & velocity
+		case 3:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				return PORT_TYPE_VECTOR; // color
+			}
+			break; // alpha (scalar)
+		case 4:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				break; // alpha
+			}
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				break; // scale
+			}
+			if (shader_type == VisualShader::TYPE_COLLIDE) {
+				return PORT_TYPE_TRANSFORM; // transform
+			}
+			return PORT_TYPE_VECTOR; // position
+		case 5:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				return PORT_TYPE_TRANSFORM; // transform
+			}
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				return PORT_TYPE_VECTOR; // rotation_axis
+			}
+			break; // scale (scalar)
+		case 6:
+			if (shader_type == VisualShader::TYPE_START) {
+				return PORT_TYPE_VECTOR; // rotation_axis
+			}
+			break;
+		case 7:
+			break; // angle (scalar)
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleOutput::get_input_port_name(int p_port) const {
+	String port_name;
+	switch (p_port) {
+		case 0:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "custom";
+				break;
+			}
+			port_name = "active";
+			break;
+		case 1:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "custom_alpha";
+				break;
+			}
+			port_name = "velocity";
+			break;
+		case 2:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "velocity";
+				break;
+			}
+			port_name = "color";
+			break;
+		case 3:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "color";
+				break;
+			}
+			port_name = "alpha";
+			break;
+		case 4:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "alpha";
+				break;
+			}
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				port_name = "scale";
+				break;
+			}
+			if (shader_type == VisualShader::TYPE_COLLIDE) {
+				port_name = "transform";
+				break;
+			}
+			port_name = "position";
+			break;
+		case 5:
+			if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+				port_name = "transform";
+				break;
+			}
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				port_name = "rotation_axis";
+				break;
+			}
+			port_name = "scale";
+			break;
+		case 6:
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				port_name = "angle_in_radians";
+				break;
+			}
+			port_name = "rotation_axis";
+			break;
+		case 7:
+			port_name = "angle_in_radians";
+			break;
+		default:
+			break;
+	}
+	if (!port_name.is_empty()) {
+		return port_name.capitalize();
+	}
+	return String();
+}
+
+bool VisualShaderNodeParticleOutput::is_port_separator(int p_index) const {
+	if (shader_type == VisualShader::TYPE_START || shader_type == VisualShader::TYPE_PROCESS) {
+		String name = get_input_port_name(p_index);
+		return bool(name == "Scale");
+	}
+	if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+		String name = get_input_port_name(p_index);
+		return bool(name == "Velocity");
+	}
+	return false;
+}
+
+String VisualShaderNodeParticleOutput::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	String tab = "\t";
+
+	if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) {
+		if (!p_input_vars[0].is_empty()) { // custom.rgb
+			code += tab + "CUSTOM.rgb = " + p_input_vars[0] + ";\n";
+		}
+		if (!p_input_vars[1].is_empty()) { // custom.a
+			code += tab + "CUSTOM.a = " + p_input_vars[1] + ";\n";
+		}
+		if (!p_input_vars[2].is_empty()) { // velocity
+			code += tab + "VELOCITY = " + p_input_vars[2] + ";\n";
+		}
+		if (!p_input_vars[3].is_empty()) { // color.rgb
+			code += tab + "COLOR.rgb = " + p_input_vars[3] + ";\n";
+		}
+		if (!p_input_vars[4].is_empty()) { // color.a
+			code += tab + "COLOR.a = " + p_input_vars[4] + ";\n";
+		}
+		if (!p_input_vars[5].is_empty()) { // transform
+			code += tab + "TRANSFORM = " + p_input_vars[5] + ";\n";
+		}
+	} else {
+		if (!p_input_vars[0].is_empty()) { // active (begin)
+			code += tab + "ACTIVE = " + p_input_vars[0] + ";\n";
+			code += tab + "if(ACTIVE) {\n";
+			tab += "\t";
+		}
+		if (!p_input_vars[1].is_empty()) { // velocity
+			code += tab + "VELOCITY = " + p_input_vars[1] + ";\n";
+		}
+		if (!p_input_vars[2].is_empty()) { // color
+			code += tab + "COLOR.rgb = " + p_input_vars[2] + ";\n";
+		}
+		if (!p_input_vars[3].is_empty()) { // alpha
+			code += tab + "COLOR.a = " + p_input_vars[3] + ";\n";
+		}
+
+		// position
+		if (shader_type == VisualShader::TYPE_START) {
+			code += tab + "if (RESTART_POSITION) {\n";
+			if (!p_input_vars[4].is_empty()) {
+				code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(" + p_input_vars[4] + ", 1.0));\n";
+			} else {
+				code += tab + "\tTRANSFORM = mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+			}
+			code += tab + "\tif (RESTART_VELOCITY) {\n";
+			code += tab + "\t\tVELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
+			code += tab + "\t}\n";
+			code += tab + "\tTRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
+			code += tab + "}\n";
+		} else if (shader_type == VisualShader::TYPE_COLLIDE) { // position
+			if (!p_input_vars[4].is_empty()) {
+				code += tab + "TRANSFORM = " + p_input_vars[4] + ";\n";
+			}
+		}
+
+		if (shader_type == VisualShader::TYPE_START || shader_type == VisualShader::TYPE_PROCESS) {
+			int scale = 5;
+			int rotation_axis = 6;
+			int rotation = 7;
+			if (shader_type == VisualShader::TYPE_PROCESS) {
+				scale = 4;
+				rotation_axis = 5;
+				rotation = 6;
+			}
+			String op;
+			if (shader_type == VisualShader::TYPE_START) {
+				op = "*=";
+			} else {
+				op = "=";
+			}
+
+			if (!p_input_vars[rotation].is_empty()) { // rotation_axis & angle_in_radians
+				String axis;
+				if (p_input_vars[rotation_axis].is_empty()) {
+					axis = "vec3(0, 1, 0)";
+				} else {
+					axis = p_input_vars[rotation_axis];
+				}
+				code += tab + "TRANSFORM " + op + " __build_rotation_mat4(" + axis + ", " + p_input_vars[rotation] + ");\n";
+			}
+			if (!p_input_vars[scale].is_empty()) { // scale
+				code += tab + "TRANSFORM " + op + " mat4(vec4(" + p_input_vars[scale] + ", 0, 0, 0), vec4(0, " + p_input_vars[scale] + ", 0, 0), vec4(0, 0, " + p_input_vars[scale] + ", 0), vec4(0, 0, 0, 1));\n";
+			}
+		}
+		if (!p_input_vars[0].is_empty()) { // active (end)
+			code += "\t}\n";
+		}
+	}
+	return code;
+}
+
+VisualShaderNodeParticleOutput::VisualShaderNodeParticleOutput() {
+}
+
+// EmitParticle
+
+Vector<StringName> VisualShaderNodeParticleEmit::get_editable_properties() const {
+	Vector<StringName> props;
+	props.push_back("flags");
+	return props;
+}
+
+void VisualShaderNodeParticleEmit::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_flags", "flags"), &VisualShaderNodeParticleEmit::set_flags);
+	ClassDB::bind_method(D_METHOD("get_flags"), &VisualShaderNodeParticleEmit::get_flags);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Position,RotScale,Velocity,Color,Custom"), "set_flags", "get_flags");
+
+	BIND_ENUM_CONSTANT(EMIT_FLAG_POSITION);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_ROT_SCALE);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_VELOCITY);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_COLOR);
+	BIND_ENUM_CONSTANT(EMIT_FLAG_CUSTOM);
+}
+
+String VisualShaderNodeParticleEmit::get_caption() const {
+	return "EmitParticle";
+}
+
+int VisualShaderNodeParticleEmit::get_input_port_count() const {
+	return 7;
+}
+
+VisualShaderNodeParticleEmit::PortType VisualShaderNodeParticleEmit::get_input_port_type(int p_port) const {
+	switch (p_port) {
+		case 0:
+			return PORT_TYPE_BOOLEAN;
+		case 1:
+			return PORT_TYPE_TRANSFORM;
+		case 2:
+			return PORT_TYPE_VECTOR;
+		case 3:
+			return PORT_TYPE_VECTOR;
+		case 4:
+			return PORT_TYPE_SCALAR;
+		case 5:
+			return PORT_TYPE_VECTOR;
+		case 6:
+			return PORT_TYPE_SCALAR;
+	}
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleEmit::get_input_port_name(int p_port) const {
+	switch (p_port) {
+		case 0:
+			return "condition";
+		case 1:
+			return "transform";
+		case 2:
+			return "velocity";
+		case 3:
+			return "color";
+		case 4:
+			return "alpha";
+		case 5:
+			return "custom";
+		case 6:
+			return "custom_alpha";
+	}
+	return String();
+}
+
+int VisualShaderNodeParticleEmit::get_output_port_count() const {
+	return 0;
+}
+
+VisualShaderNodeParticleEmit::PortType VisualShaderNodeParticleEmit::get_output_port_type(int p_port) const {
+	return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeParticleEmit::get_output_port_name(int p_port) const {
+	return String();
+}
+
+void VisualShaderNodeParticleEmit::add_flag(EmitFlags p_flag) {
+	flags |= p_flag;
+	emit_changed();
+}
+
+bool VisualShaderNodeParticleEmit::has_flag(EmitFlags p_flag) const {
+	return flags & p_flag;
+}
+
+void VisualShaderNodeParticleEmit::set_flags(EmitFlags p_flags) {
+	flags = (int)p_flags;
+	emit_changed();
+}
+
+VisualShaderNodeParticleEmit::EmitFlags VisualShaderNodeParticleEmit::get_flags() const {
+	return EmitFlags(flags);
+}
+
+bool VisualShaderNodeParticleEmit::is_show_prop_names() const {
+	return true;
+}
+
+bool VisualShaderNodeParticleEmit::is_generate_input_var(int p_port) const {
+	if (p_port == 0) {
+		if (!is_input_port_connected(0)) {
+			return false;
+		}
+	}
+	return true;
+}
+
+String VisualShaderNodeParticleEmit::get_input_port_default_hint(int p_port) const {
+	switch (p_port) {
+		case 1:
+			return "default";
+		case 2:
+			return "default";
+		case 3:
+			return "default";
+		case 4:
+			return "default";
+		case 5:
+			return "default";
+		case 6:
+			return "default";
+	}
+	return String();
+}
+
+String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	String code;
+	String tab;
+	bool default_condition = false;
+
+	if (!is_input_port_connected(0)) {
+		default_condition = true;
+		if (get_input_port_default_value(0)) {
+			tab = "\t";
+		} else {
+			return code;
+		}
+	} else {
+		tab = "\t\t";
+	}
+
+	String transform;
+	if (p_input_vars[1].is_empty()) {
+		transform = "TRANSFORM";
+	} else {
+		transform = p_input_vars[1];
+	}
+
+	String velocity;
+	if (p_input_vars[2].is_empty()) {
+		velocity = "VELOCITY";
+	} else {
+		velocity = p_input_vars[2];
+	}
+
+	String color;
+	if (p_input_vars[3].is_empty()) {
+		color = "COLOR.rgb";
+	} else {
+		color = p_input_vars[3];
+	}
+
+	String alpha;
+	if (p_input_vars[4].is_empty()) {
+		alpha = "COLOR.a";
+	} else {
+		alpha = p_input_vars[4];
+	}
+
+	String custom;
+	if (p_input_vars[5].is_empty()) {
+		custom = "CUSTOM.rgb";
+	} else {
+		custom = p_input_vars[5];
+	}
+
+	String custom_alpha;
+	if (p_input_vars[6].is_empty()) {
+		custom_alpha = "CUSTOM.a";
+	} else {
+		custom_alpha = p_input_vars[6];
+	}
+
+	List<String> flags_arr;
+
+	if (has_flag(EmitFlags::EMIT_FLAG_POSITION)) {
+		flags_arr.push_back("FLAG_EMIT_POSITION");
+	}
+	if (has_flag(EmitFlags::EMIT_FLAG_ROT_SCALE)) {
+		flags_arr.push_back("FLAG_EMIT_ROT_SCALE");
+	}
+	if (has_flag(EmitFlags::EMIT_FLAG_VELOCITY)) {
+		flags_arr.push_back("FLAG_EMIT_VELOCITY");
+	}
+	if (has_flag(EmitFlags::EMIT_FLAG_COLOR)) {
+		flags_arr.push_back("FLAG_EMIT_COLOR");
+	}
+	if (has_flag(EmitFlags::EMIT_FLAG_CUSTOM)) {
+		flags_arr.push_back("FLAG_EMIT_CUSTOM");
+	}
+
+	String flags;
+
+	for (int i = 0; i < flags_arr.size(); i++) {
+		if (i > 0) {
+			flags += "|";
+		}
+		flags += flags_arr[i];
+	}
+
+	if (flags.is_empty()) {
+		flags = "uint(0)";
+	}
+
+	if (!default_condition) {
+		code += "\tif (" + p_input_vars[0] + ") {\n";
+	}
+
+	code += tab + "emit_subparticle(" + transform + ", " + velocity + ", vec4(" + color + ", " + alpha + "), vec4(" + custom + ", " + custom_alpha + "), " + flags + ");\n";
+
+	if (!default_condition) {
+		code += "\t}\n";
+	}
+
+	return code;
+}
+
+VisualShaderNodeParticleEmit::VisualShaderNodeParticleEmit() {
+	set_input_port_default_value(0, true);
+}

+ 285 - 0
scene/resources/visual_shader_particle_nodes.h

@@ -0,0 +1,285 @@
+/*************************************************************************/
+/*  visual_shader_particle_nodes.h                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* 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 VISUAL_SHADER_PARTICLE_NODES_H
+#define VISUAL_SHADER_PARTICLE_NODES_H
+
+#include "scene/resources/visual_shader.h"
+
+// Emit nodes
+
+class VisualShaderNodeParticleEmitter : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeParticleEmitter, VisualShaderNode);
+
+public:
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	VisualShaderNodeParticleEmitter();
+};
+
+class VisualShaderNodeParticleSphereEmitter : public VisualShaderNodeParticleEmitter {
+	GDCLASS(VisualShaderNodeParticleSphereEmitter, VisualShaderNodeParticleEmitter);
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleSphereEmitter();
+};
+
+class VisualShaderNodeParticleBoxEmitter : public VisualShaderNodeParticleEmitter {
+	GDCLASS(VisualShaderNodeParticleBoxEmitter, VisualShaderNodeParticleEmitter);
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleBoxEmitter();
+};
+
+class VisualShaderNodeParticleRingEmitter : public VisualShaderNodeParticleEmitter {
+	GDCLASS(VisualShaderNodeParticleRingEmitter, VisualShaderNodeParticleEmitter);
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleRingEmitter();
+};
+
+class VisualShaderNodeParticleMultiplyByAxisAngle : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeParticleMultiplyByAxisAngle, VisualShaderNode);
+	bool degrees_mode = true;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+	virtual bool is_show_prop_names() const override;
+
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	void set_degrees_mode(bool p_enabled);
+	bool is_degrees_mode() const;
+	Vector<StringName> get_editable_properties() const override;
+
+	VisualShaderNodeParticleMultiplyByAxisAngle();
+};
+
+class VisualShaderNodeParticleConeVelocity : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeParticleConeVelocity, VisualShaderNode);
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleConeVelocity();
+};
+
+class VisualShaderNodeParticleRandomness : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeParticleRandomness, VisualShaderNode);
+
+public:
+	enum OpType {
+		OP_TYPE_SCALAR,
+		OP_TYPE_VECTOR,
+		OP_TYPE_MAX,
+	};
+
+private:
+	OpType op_type = OP_TYPE_SCALAR;
+
+protected:
+	static void _bind_methods();
+
+public:
+	Vector<StringName> get_editable_properties() const override;
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	void set_op_type(OpType p_type);
+	OpType get_op_type() const;
+
+	VisualShaderNodeParticleRandomness();
+};
+
+VARIANT_ENUM_CAST(VisualShaderNodeParticleRandomness::OpType)
+
+// Process nodes
+
+class VisualShaderNodeParticleAccelerator : public VisualShaderNodeOutput {
+	GDCLASS(VisualShaderNodeParticleAccelerator, VisualShaderNodeOutput);
+
+public:
+	enum Mode {
+		MODE_LINEAR,
+		MODE_RADIAL,
+		MODE_TANGENTIAL,
+		MODE_MAX,
+	};
+
+private:
+	Mode mode = MODE_LINEAR;
+
+protected:
+	static void _bind_methods();
+
+public:
+	Vector<StringName> get_editable_properties() const override;
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	void set_mode(Mode p_mode);
+	Mode get_mode() const;
+
+	VisualShaderNodeParticleAccelerator();
+};
+
+VARIANT_ENUM_CAST(VisualShaderNodeParticleAccelerator::Mode)
+
+// Common nodes
+
+class VisualShaderNodeParticleOutput : public VisualShaderNodeOutput {
+	GDCLASS(VisualShaderNodeParticleOutput, VisualShaderNodeOutput);
+
+public:
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+	virtual bool is_port_separator(int p_index) const override;
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleOutput();
+};
+
+class VisualShaderNodeParticleEmit : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeParticleEmit, VisualShaderNode);
+
+public:
+	enum EmitFlags {
+		EMIT_FLAG_POSITION = 1,
+		EMIT_FLAG_ROT_SCALE = 2,
+		EMIT_FLAG_VELOCITY = 4,
+		EMIT_FLAG_COLOR = 8,
+		EMIT_FLAG_CUSTOM = 16,
+	};
+
+protected:
+	int flags = EMIT_FLAG_POSITION | EMIT_FLAG_ROT_SCALE | EMIT_FLAG_VELOCITY | EMIT_FLAG_COLOR | EMIT_FLAG_CUSTOM;
+	static void _bind_methods();
+
+public:
+	Vector<StringName> get_editable_properties() const override;
+	virtual String get_caption() const override;
+
+	virtual int get_input_port_count() const override;
+	virtual PortType get_input_port_type(int p_port) const override;
+	virtual String get_input_port_name(int p_port) const override;
+
+	virtual int get_output_port_count() const override;
+	virtual PortType get_output_port_type(int p_port) const override;
+	virtual String get_output_port_name(int p_port) const override;
+
+	void add_flag(EmitFlags p_flag);
+	bool has_flag(EmitFlags p_flag) const;
+
+	void set_flags(EmitFlags p_flags);
+	EmitFlags get_flags() const;
+
+	virtual bool is_show_prop_names() const override;
+	virtual bool is_generate_input_var(int p_port) const override;
+	virtual String get_input_port_default_hint(int p_port) const override;
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+	VisualShaderNodeParticleEmit();
+};
+
+VARIANT_ENUM_CAST(VisualShaderNodeParticleEmit::EmitFlags)
+
+#endif