Juan Linietsky 8 лет назад
Родитель
Сommit
95560e02c5

+ 163 - 0
drivers/gles3/rasterizer_canvas_gles3.cpp

@@ -28,6 +28,7 @@
 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 /*************************************************************************/
 #include "rasterizer_canvas_gles3.h"
+#include "servers/visual/visual_server_raster.h"
 
 #include "global_config.h"
 #include "os/os.h"
@@ -607,6 +608,133 @@ void RasterizerCanvasGLES3::_canvas_item_render_commands(Item *p_item, Item *cur
 				}
 				_draw_polygon(polygon->indices.ptr(), polygon->count, polygon->points.size(), polygon->points.ptr(), polygon->uvs.ptr(), polygon->colors.ptr(), polygon->colors.size() == 1);
 
+			} break;
+			case Item::Command::TYPE_PARTICLES: {
+
+				Item::CommandParticles *particles_cmd = static_cast<Item::CommandParticles *>(c);
+
+				RasterizerStorageGLES3::Particles *particles = storage->particles_owner.getornull(particles_cmd->particles);
+				if (!particles)
+					break;
+
+				glVertexAttrib4f(VS::ARRAY_COLOR, 1, 1, 1, 1); //not used, so keep white
+
+				VisualServerRaster::redraw_request();
+
+				storage->particles_request_process(particles_cmd->particles);
+				//enable instancing
+
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, true);
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_PARTICLES, true);
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, true);
+				//reset shader and force rebind
+				state.using_texture_rect = true;
+				_set_texture_rect_mode(false);
+
+				RasterizerStorageGLES3::Texture *texture = _bind_canvas_texture(particles_cmd->texture, particles_cmd->normal_map);
+
+				if (texture) {
+					Size2 texpixel_size(1.0 / (texture->width / particles_cmd->h_frames), 1.0 / (texture->height / particles_cmd->v_frames));
+					state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, texpixel_size);
+				} else {
+					state.canvas_shader.set_uniform(CanvasShaderGLES3::COLOR_TEXPIXEL_SIZE, Vector2(1.0, 1.0));
+				}
+
+				if (!particles->use_local_coords) {
+
+					Transform2D inv_xf;
+					inv_xf.set_axis(0, Vector2(particles->emission_transform.basis.get_axis(0).x, particles->emission_transform.basis.get_axis(0).y));
+					inv_xf.set_axis(1, Vector2(particles->emission_transform.basis.get_axis(1).x, particles->emission_transform.basis.get_axis(1).y));
+					inv_xf.set_origin(Vector2(particles->emission_transform.get_origin().x, particles->emission_transform.get_origin().y));
+					inv_xf.affine_invert();
+
+					state.canvas_shader.set_uniform(CanvasShaderGLES3::MODELVIEW_MATRIX, state.final_transform * inv_xf);
+				}
+
+				state.canvas_shader.set_uniform(CanvasShaderGLES3::H_FRAMES, particles_cmd->h_frames);
+				state.canvas_shader.set_uniform(CanvasShaderGLES3::V_FRAMES, particles_cmd->v_frames);
+
+				glBindVertexArray(data.particle_quad_array); //use particle quad array
+				glBindBuffer(GL_ARRAY_BUFFER, particles->particle_buffers[0]); //bind particle buffer
+
+				int stride = sizeof(float) * 4 * 6;
+
+				int amount = particles->amount;
+
+				if (particles->draw_order != VS::PARTICLES_DRAW_ORDER_LIFETIME) {
+
+					glEnableVertexAttribArray(8); //xform x
+					glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 3);
+					glVertexAttribDivisor(8, 1);
+					glEnableVertexAttribArray(9); //xform y
+					glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 4);
+					glVertexAttribDivisor(9, 1);
+					glEnableVertexAttribArray(10); //xform z
+					glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 5);
+					glVertexAttribDivisor(10, 1);
+					glEnableVertexAttribArray(11); //color
+					glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + 0);
+					glVertexAttribDivisor(11, 1);
+					glEnableVertexAttribArray(12); //custom
+					glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 2);
+					glVertexAttribDivisor(12, 1);
+
+					glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, amount);
+				} else {
+					//split
+
+					int stride = sizeof(float) * 4 * 6;
+					int split = int(Math::ceil(particles->phase * particles->amount));
+
+					if (amount - split > 0) {
+						glEnableVertexAttribArray(8); //xform x
+						glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + stride * split + sizeof(float) * 4 * 3);
+						glVertexAttribDivisor(8, 1);
+						glEnableVertexAttribArray(9); //xform y
+						glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + stride * split + sizeof(float) * 4 * 4);
+						glVertexAttribDivisor(9, 1);
+						glEnableVertexAttribArray(10); //xform z
+						glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + stride * split + sizeof(float) * 4 * 5);
+						glVertexAttribDivisor(10, 1);
+						glEnableVertexAttribArray(11); //color
+						glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + stride * split + 0);
+						glVertexAttribDivisor(11, 1);
+						glEnableVertexAttribArray(12); //custom
+						glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + stride * split + sizeof(float) * 4 * 2);
+						glVertexAttribDivisor(12, 1);
+
+						glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, amount - split);
+					}
+
+					if (split > 0) {
+						glEnableVertexAttribArray(8); //xform x
+						glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 3);
+						glVertexAttribDivisor(8, 1);
+						glEnableVertexAttribArray(9); //xform y
+						glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 4);
+						glVertexAttribDivisor(9, 1);
+						glEnableVertexAttribArray(10); //xform z
+						glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 5);
+						glVertexAttribDivisor(10, 1);
+						glEnableVertexAttribArray(11); //color
+						glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + 0);
+						glVertexAttribDivisor(11, 1);
+						glEnableVertexAttribArray(12); //custom
+						glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + sizeof(float) * 4 * 2);
+						glVertexAttribDivisor(12, 1);
+
+						glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, split);
+					}
+				}
+
+				glBindVertexArray(0);
+
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCE_CUSTOM, false);
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_INSTANCING, false);
+				state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_PARTICLES, false);
+				state.using_texture_rect = true;
+				_set_texture_rect_mode(false);
+
 			} break;
 			case Item::Command::TYPE_CIRCLE: {
 
@@ -1351,7 +1479,39 @@ void RasterizerCanvasGLES3::initialize() {
 		glBindVertexArray(0);
 		glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
 	}
+	{
+		//particle quad buffers
+
+		glGenBuffers(1, &data.particle_quad_vertices);
+		glBindBuffer(GL_ARRAY_BUFFER, data.particle_quad_vertices);
+		{
+			//quad of size 1, with pivot on the center for particles, then regular UVS. Color is general plus fetched from particle
+			const float qv[16] = {
+				-0.5, -0.5,
+				0.0, 0.0,
+				-0.5, 0.5,
+				0.0, 1.0,
+				0.5, 0.5,
+				1.0, 1.0,
+				0.5, -0.5,
+				1.0, 0.0
+			};
+
+			glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 16, qv, GL_STATIC_DRAW);
+		}
 
+		glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
+
+		glGenVertexArrays(1, &data.particle_quad_array);
+		glBindVertexArray(data.particle_quad_array);
+		glBindBuffer(GL_ARRAY_BUFFER, data.particle_quad_vertices);
+		glEnableVertexAttribArray(VS::ARRAY_VERTEX);
+		glVertexAttribPointer(VS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0);
+		glEnableVertexAttribArray(VS::ARRAY_TEX_UV);
+		glVertexAttribPointer(VS::ARRAY_TEX_UV, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (float *)0 + 2);
+		glBindVertexArray(0);
+		glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind
+	}
 	{
 
 		uint32_t poly_size = GLOBAL_DEF("rendering/buffers/canvas_polygon_buffer_size_kb", 128);
@@ -1428,6 +1588,9 @@ void RasterizerCanvasGLES3::finalize() {
 	glDeleteBuffers(1, &data.canvas_quad_vertices);
 	glDeleteVertexArrays(1, &data.canvas_quad_array);
 
+	glDeleteBuffers(1, &data.canvas_quad_vertices);
+	glDeleteVertexArrays(1, &data.canvas_quad_array);
+
 	glDeleteVertexArrays(1, &data.polygon_buffer_pointer_array);
 }
 

+ 4 - 0
drivers/gles3/rasterizer_canvas_gles3.h

@@ -51,6 +51,10 @@ public:
 		GLuint polygon_buffer_quad_arrays[4];
 		GLuint polygon_buffer_pointer_array;
 		GLuint polygon_index_buffer;
+
+		GLuint particle_quad_vertices;
+		GLuint particle_quad_array;
+
 		uint32_t polygon_buffer_size;
 
 	} data;

+ 3 - 0
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -2326,6 +2326,9 @@ void RasterizerStorageGLES3::_update_material(Material *material) {
 			if (E->get().order < 0)
 				continue; // texture, does not go here
 
+			//if (material->shader->mode == VS::SHADER_PARTICLES) {
+			//	print_line("uniform " + String(E->key()) + " order " + itos(E->get().order) + " offset " + itos(material->shader->ubo_offsets[E->get().order]));
+			//}
 			//regular uniform
 			uint8_t *data = &local_ubo[material->shader->ubo_offsets[E->get().order]];
 

+ 2 - 0
drivers/gles3/shader_gles3.cpp

@@ -361,6 +361,8 @@ ShaderGLES3::Version *ShaderGLES3::get_current_version() {
 		ERR_FAIL_V(NULL);
 	}
 
+	//_display_error_with_code("pepo", strings);
+
 	/* FRAGMENT SHADER */
 
 	strings.resize(strings_base_size);

+ 43 - 1
drivers/gles3/shaders/canvas.glsl

@@ -11,11 +11,26 @@ uniform vec4 src_rect;
 
 #else
 
+#ifdef USE_INSTANCING
+
+layout(location=8) in highp vec4 instance_xform0;
+layout(location=9) in highp vec4 instance_xform1;
+layout(location=10) in highp vec4 instance_xform2;
+layout(location=11) in lowp vec4 instance_color;
+
+#ifdef USE_INSTANCE_CUSTOM
+layout(location=12) in highp vec4 instance_custom_data;
+#endif
+
+#endif
+
 layout(location=4) in highp vec2 uv_attrib;
 
 //skeletn
 #endif
 
+uniform highp vec2 color_texpixel_size;
+
 
 layout(std140) uniform CanvasItemData { //ubo:0
 
@@ -64,7 +79,10 @@ const bool at_light_pass = true;
 const bool at_light_pass = false;
 #endif
 
-
+#ifdef USE_PARTICLES
+uniform int h_frames;
+uniform int v_frames;
+#endif
 
 VERTEX_SHADER_GLOBALS
 
@@ -82,6 +100,12 @@ void main() {
 
 	vec4 vertex_color = color_attrib;
 
+#ifdef USE_INSTANCING
+	mat4 extra_matrix2 = extra_matrix * transpose(mat4(instance_xform0,instance_xform1,instance_xform2,vec4(0.0,0.0,0.0,1.0)));
+	vertex_color*=instance_color;
+#else
+	mat4 extra_matrix2 = extra_matrix;
+#endif
 
 #ifdef USE_TEXTURE_RECT
 
@@ -95,6 +119,22 @@ void main() {
 #endif
 
 
+#ifdef USE_PARTICLES
+	//scale by texture size
+	outvec.xy/=color_texpixel_size;
+
+	//compute h and v frames and adjust UV interp for animation
+	int total_frames = h_frames * v_frames;
+	int frame = min(int(float(total_frames) *instance_custom_data.z),total_frames-1);
+	float frame_w = 1.0/float(h_frames);
+	float frame_h = 1.0/float(v_frames);
+	uv_interp.x = uv_interp.x * frame_w + frame_w * float(frame % h_frames);
+	uv_interp.y = uv_interp.y * frame_h + frame_h * float(frame / v_frames);
+
+#endif
+
+#define extra_matrix extra_matrix2
+
 {
 	vec2 src_vtx=outvec.xy;
 
@@ -107,6 +147,8 @@ VERTEX_SHADER_CODE
 	outvec = modelview_matrix * outvec;
 #endif
 
+#undef extra_matrix
+
 	color_interp = vertex_color;
 
 #ifdef USE_PIXEL_SNAP

BIN
editor/icons/icon_audio_player.png


+ 262 - 41
editor/plugins/particles_2d_editor_plugin.cpp

@@ -31,8 +31,8 @@
 
 #include "canvas_item_editor_plugin.h"
 #include "io/image_loader.h"
+#include "scene/3d/particles.h"
 #include "scene/gui/separator.h"
-
 void Particles2DEditorPlugin::edit(Object *p_object) {
 
 	if (p_object) {
@@ -62,78 +62,264 @@ void Particles2DEditorPlugin::_file_selected(const String &p_file) {
 
 	print_line("file: " + p_file);
 
-	int epc = epoints->get_value();
+	source_emission_file = p_file;
+	emission_mask->popup_centered_minsize();
+}
+
+void Particles2DEditorPlugin::_menu_callback(int p_idx) {
+
+	switch (p_idx) {
+		case MENU_GENERATE_VISIBILITY_RECT: {
+			generate_aabb->popup_centered_minsize();
+		} break;
+		case MENU_LOAD_EMISSION_MASK: {
+
+			file->popup_centered_ratio();
+
+		} break;
+		case MENU_CLEAR_EMISSION_MASK: {
+
+			emission_mask->popup_centered_minsize();
+
+			/*undo_redo->create_action(TTR("Clear Emission Mask"));
+			undo_redo->add_do_method(particles, "set_emission_points", PoolVector<Vector2>());
+			undo_redo->add_undo_method(particles, "set_emission_points", particles->get_emission_points());
+			undo_redo->commit_action();*/
+		} break;
+	}
+}
+
+void Particles2DEditorPlugin::_generate_visibility_rect() {
+
+	float time = generate_seconds->get_value();
+
+	float running = 0.0;
+
+	EditorProgress ep("gen_aabb", TTR("Generating AABB"), int(time));
+
+	Rect2 rect;
+	while (running < time) {
+
+		uint64_t ticks = OS::get_singleton()->get_ticks_usec();
+		ep.step("Generating..", int(running), true);
+		OS::get_singleton()->delay_usec(1000);
+
+		Rect2 capture = particles->capture_rect();
+		if (rect == Rect2())
+			rect = capture;
+		else
+			rect = rect.merge(capture);
+
+		running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+	}
+
+	particles->set_visibility_rect(rect);
+}
+
+void Particles2DEditorPlugin::_generate_emission_mask() {
+
+	Ref<ParticlesMaterial> pm = particles->get_process_material();
+	if (!pm.is_valid()) {
+		EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticlesMaterial process material"));
+		return;
+	}
 
 	Ref<Image> img;
 	img.instance();
-	Error err = ImageLoader::load_image(p_file, img);
-	ERR_EXPLAIN(TTR("Error loading image:") + " " + p_file);
+	Error err = ImageLoader::load_image(source_emission_file, img);
+	ERR_EXPLAIN(TTR("Error loading image:") + " " + source_emission_file);
 	ERR_FAIL_COND(err != OK);
 
-	img->convert(Image::FORMAT_LA8);
-	ERR_FAIL_COND(img->get_format() != Image::FORMAT_LA8);
+	if (img->is_compressed()) {
+		img->decompress();
+	}
+	img->convert(Image::FORMAT_RGBA8);
+	ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
 	Size2i s = Size2(img->get_width(), img->get_height());
 	ERR_FAIL_COND(s.width == 0 || s.height == 0);
 
-	PoolVector<uint8_t> data = img->get_data();
-	PoolVector<uint8_t>::Read r = data.read();
+	Vector<Point2> valid_positions;
+	Vector<Point2> valid_normals;
+	Vector<uint8_t> valid_colors;
 
-	Vector<Point2i> valid_positions;
 	valid_positions.resize(s.width * s.height);
+
+	EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
+
+	if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+		valid_normals.resize(s.width * s.height);
+	}
+
+	bool capture_colors = emission_colors->is_pressed();
+
+	if (capture_colors) {
+		valid_colors.resize(s.width * s.height * 4);
+	}
+
 	int vpc = 0;
 
-	for (int i = 0; i < s.width * s.height; i++) {
+	{
+		PoolVector<uint8_t> data = img->get_data();
+		PoolVector<uint8_t>::Read r = data.read();
+
+		for (int i = 0; i < s.width; i++) {
+			for (int j = 0; j < s.height; j++) {
+
+				uint8_t a = r[(j * s.width + i) * 4 + 3];
+
+				if (a > 128) {
+
+					if (emode == EMISSION_MODE_SOLID) {
+
+						if (capture_colors) {
+							valid_colors[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+							valid_colors[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+							valid_colors[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+							valid_colors[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+						}
+						valid_positions[vpc++] = Point2(i, j);
+
+					} else {
 
-		uint8_t a = r[i * 2 + 1];
-		if (a > 128) {
-			valid_positions[vpc++] = Point2i(i % s.width, i / s.width);
+						bool on_border = false;
+						for (int x = i - 1; x <= i + 1; x++) {
+							for (int y = j - 1; y <= j + 1; y++) {
+
+								if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+									on_border = true;
+									break;
+								}
+							}
+
+							if (on_border)
+								break;
+						}
+
+						if (on_border) {
+							valid_positions[vpc] = Point2(i, j);
+
+							if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+								Vector2 normal;
+								for (int x = i - 2; x <= i + 2; x++) {
+									for (int y = j - 2; y <= j + 2; y++) {
+
+										if (x == i && y == j)
+											continue;
+
+										if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+											normal += Vector2(x - i, y - j).normalized();
+										}
+									}
+								}
+
+								normal.normalize();
+								valid_normals[vpc] = normal;
+							}
+
+							if (capture_colors) {
+								valid_colors[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+								valid_colors[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+								valid_colors[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+								valid_colors[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+							}
+
+							vpc++;
+						}
+					}
+				}
+			}
 		}
 	}
 
 	valid_positions.resize(vpc);
+	if (valid_normals.size()) {
+		valid_normals.resize(vpc);
+	}
 
 	ERR_EXPLAIN(TTR("No pixels with transparency > 128 in image.."));
 	ERR_FAIL_COND(valid_positions.size() == 0);
 
-	PoolVector<Point2> epoints;
-	epoints.resize(epc);
-	PoolVector<Point2>::Write w = epoints.write();
+	PoolVector<uint8_t> texdata;
+
+	int w = 2048;
+	int h = (vpc / 2048) + 1;
 
-	Size2 extents = Size2(img->get_width() * 0.5, img->get_height() * 0.5);
+	texdata.resize(w * h * 2 * sizeof(float));
 
-	for (int i = 0; i < epc; i++) {
+	{
+		PoolVector<uint8_t>::Write tw = texdata.write();
+		float *twf = (float *)tw.ptr();
+		for (int i = 0; i < vpc; i++) {
 
-		Point2 p = valid_positions[Math::rand() % vpc];
-		p -= s / 2;
-		w[i] = p / extents;
+			twf[i * 2 + 0] = valid_positions[i].x;
+			twf[i * 2 + 1] = valid_positions[i].y;
+		}
 	}
 
-	w = PoolVector<Point2>::Write();
+	img.instance();
+	img->create(w, h, false, Image::FORMAT_RGF, texdata);
 
-	undo_redo->create_action(TTR("Set Emission Mask"));
-	undo_redo->add_do_method(particles, "set_emission_points", epoints);
-	undo_redo->add_do_method(particles, "set_emission_half_extents", extents);
-	undo_redo->add_undo_method(particles, "set_emission_points", particles->get_emission_points());
-	undo_redo->add_undo_method(particles, "set_emission_half_extents", particles->get_emission_half_extents());
-	undo_redo->commit_action();
-}
+	Ref<ImageTexture> imgt;
+	imgt.instance();
+	imgt->create_from_image(img, 0);
 
-void Particles2DEditorPlugin::_menu_callback(int p_idx) {
+	pm->set_emission_point_texture(imgt);
+	pm->set_emission_point_count(vpc);
 
-	switch (p_idx) {
-		case MENU_LOAD_EMISSION_MASK: {
+	if (capture_colors) {
 
-			file->popup_centered_ratio();
+		PoolVector<uint8_t> colordata;
+		colordata.resize(w * h * 4); //use RG texture
 
-		} break;
-		case MENU_CLEAR_EMISSION_MASK: {
+		{
+			PoolVector<uint8_t>::Write tw = colordata.write();
+			for (int i = 0; i < vpc * 4; i++) {
 
-			undo_redo->create_action(TTR("Clear Emission Mask"));
-			undo_redo->add_do_method(particles, "set_emission_points", PoolVector<Vector2>());
-			undo_redo->add_undo_method(particles, "set_emission_points", particles->get_emission_points());
-			undo_redo->commit_action();
-		} break;
+				tw[i] = valid_colors[i];
+			}
+		}
+
+		img.instance();
+		img->create(w, h, false, Image::FORMAT_RGBA8, colordata);
+
+		imgt.instance();
+		imgt->create_from_image(img, 0);
+		pm->set_emission_color_texture(imgt);
 	}
+
+	if (valid_normals.size()) {
+		pm->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
+
+		PoolVector<uint8_t> normdata;
+		normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
+
+		{
+			PoolVector<uint8_t>::Write tw = normdata.write();
+			float *twf = (float *)tw.ptr();
+			for (int i = 0; i < vpc; i++) {
+				twf[i * 2 + 0] = valid_normals[i].x;
+				twf[i * 2 + 1] = valid_normals[i].y;
+			}
+		}
+
+		img.instance();
+		img->create(w, h, false, Image::FORMAT_RGF, normdata);
+
+		imgt.instance();
+		imgt->create_from_image(img, 0);
+		pm->set_emission_normal_texture(imgt);
+
+	} else {
+		pm->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_POINTS);
+	}
+
+	/*undo_redo->create_action(TTR("Set Emission Mask"));
+	undo_redo->add_do_method(particles, "set_emission_points", epoints);
+	undo_redo->add_do_method(particles, "set_emission_half_extents", extents);
+	undo_redo->add_undo_method(particles, "set_emission_points", particles->get_emission_points());
+	undo_redo->add_undo_method(particles, "set_emission_half_extents", particles->get_emission_half_extents());
+	undo_redo->commit_action();
+	*/
 }
 
 void Particles2DEditorPlugin::_notification(int p_what) {
@@ -150,6 +336,8 @@ void Particles2DEditorPlugin::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("_menu_callback"), &Particles2DEditorPlugin::_menu_callback);
 	ClassDB::bind_method(D_METHOD("_file_selected"), &Particles2DEditorPlugin::_file_selected);
+	ClassDB::bind_method(D_METHOD("_generate_visibility_rect"), &Particles2DEditorPlugin::_generate_visibility_rect);
+	ClassDB::bind_method(D_METHOD("_generate_emission_mask"), &Particles2DEditorPlugin::_generate_emission_mask);
 }
 
 Particles2DEditorPlugin::Particles2DEditorPlugin(EditorNode *p_node) {
@@ -165,8 +353,10 @@ Particles2DEditorPlugin::Particles2DEditorPlugin(EditorNode *p_node) {
 	toolbar->add_child(memnew(VSeparator));
 
 	menu = memnew(MenuButton);
+	menu->get_popup()->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
+	menu->get_popup()->add_separator();
 	menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
-	menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK);
+	//	menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK);
 	menu->set_text("Particles");
 	toolbar->add_child(menu);
 
@@ -185,6 +375,37 @@ Particles2DEditorPlugin::Particles2DEditorPlugin(EditorNode *p_node) {
 	epoints->set_step(1);
 	epoints->set_value(512);
 	file->get_vbox()->add_margin_child(TTR("Generated Point Count:"), epoints);
+
+	generate_aabb = memnew(ConfirmationDialog);
+	generate_aabb->set_title(TTR("Generate Visibility Rect"));
+	VBoxContainer *genvb = memnew(VBoxContainer);
+	generate_aabb->add_child(genvb);
+	generate_seconds = memnew(SpinBox);
+	genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
+	generate_seconds->set_min(0.1);
+	generate_seconds->set_max(25);
+	generate_seconds->set_value(2);
+
+	toolbar->add_child(generate_aabb);
+
+	generate_aabb->connect("confirmed", this, "_generate_visibility_rect");
+
+	emission_mask = memnew(ConfirmationDialog);
+	emission_mask->set_title(TTR("Generate Visibility Rect"));
+	VBoxContainer *emvb = memnew(VBoxContainer);
+	emission_mask->add_child(emvb);
+	emission_mask_mode = memnew(OptionButton);
+	emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
+	emission_mask_mode->add_item("Solid Pixels", EMISSION_MODE_SOLID);
+	emission_mask_mode->add_item("Border Pixels", EMISSION_MODE_BORDER);
+	emission_mask_mode->add_item("Directed Border Pixels", EMISSION_MODE_BORDER_DIRECTED);
+	emission_colors = memnew(CheckBox);
+	emission_colors->set_text(TTR("Capture from Pixel"));
+	emvb->add_margin_child(TTR("Emission Colors"), emission_colors);
+
+	toolbar->add_child(emission_mask);
+
+	emission_mask->connect("confirmed", this, "_generate_emission_mask");
 }
 
 Particles2DEditorPlugin::~Particles2DEditorPlugin() {

+ 18 - 0
editor/plugins/particles_2d_editor_plugin.h

@@ -44,10 +44,17 @@ class Particles2DEditorPlugin : public EditorPlugin {
 
 	enum {
 
+		MENU_GENERATE_VISIBILITY_RECT,
 		MENU_LOAD_EMISSION_MASK,
 		MENU_CLEAR_EMISSION_MASK
 	};
 
+	enum EmissionMode {
+		EMISSION_MODE_SOLID,
+		EMISSION_MODE_BORDER,
+		EMISSION_MODE_BORDER_DIRECTED
+	};
+
 	Particles2D *particles;
 
 	EditorFileDialog *file;
@@ -58,9 +65,20 @@ class Particles2DEditorPlugin : public EditorPlugin {
 
 	SpinBox *epoints;
 
+	ConfirmationDialog *generate_aabb;
+	SpinBox *generate_seconds;
+
+	ConfirmationDialog *emission_mask;
+	OptionButton *emission_mask_mode;
+	CheckBox *emission_colors;
+
+	String source_emission_file;
+
 	UndoRedo *undo_redo;
 	void _file_selected(const String &p_file);
 	void _menu_callback(int p_idx);
+	void _generate_visibility_rect();
+	void _generate_emission_mask();
 
 protected:
 	void _notification(int p_what);

+ 11 - 3
scene/2d/canvas_item.cpp

@@ -417,14 +417,22 @@ void CanvasItem::draw_line(const Point2 &p_from, const Point2 &p_to, const Color
 	VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width, p_antialiased);
 }
 
-void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color) {
+void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled) {
 
 	if (!drawing) {
 		ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
 		ERR_FAIL();
 	}
 
-	VisualServer::get_singleton()->canvas_item_add_rect(canvas_item, p_rect, p_color);
+	if (p_filled) {
+
+		VisualServer::get_singleton()->canvas_item_add_rect(canvas_item, p_rect, p_color);
+	} else {
+		VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position, p_rect.position + Size2(p_rect.size.width, 0), p_color);
+		VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position, p_rect.position + Size2(0, p_rect.size.height), p_color);
+		VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position + Point2(0, p_rect.size.height), p_rect.position + p_rect.size, p_color);
+		VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position + Point2(p_rect.size.width, 0), p_rect.position + p_rect.size, p_color);
+	}
 }
 
 void CanvasItem::draw_circle(const Point2 &p_pos, float p_radius, const Color &p_color) {
@@ -754,7 +762,7 @@ void CanvasItem::_bind_methods() {
 	//ClassDB::bind_method(D_METHOD("get_transform"),&CanvasItem::get_transform);
 
 	ClassDB::bind_method(D_METHOD("draw_line", "from", "to", "color", "width", "antialiased"), &CanvasItem::draw_line, DEFVAL(1.0), DEFVAL(false));
-	ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color"), &CanvasItem::draw_rect);
+	ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled"), &CanvasItem::draw_rect, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("draw_circle", "pos", "radius", "color"), &CanvasItem::draw_circle);
 	ClassDB::bind_method(D_METHOD("draw_texture", "texture:Texture", "pos", "modulate", "normal_map:Texture"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture:Texture", "rect", "tile", "modulate", "transpose", "normal_map:Texture"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(Variant()));

+ 1 - 1
scene/2d/canvas_item.h

@@ -156,7 +156,7 @@ public:
 	/* DRAWING API */
 
 	void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, float p_width = 1.0, bool p_antialiased = false);
-	void draw_rect(const Rect2 &p_rect, const Color &p_color);
+	void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true);
 	void draw_circle(const Point2 &p_pos, float p_radius, const Color &p_color);
 	void draw_texture(const Ref<Texture> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1), const Ref<Texture> &p_normal_map = Ref<Texture>());
 	void draw_texture_rect(const Ref<Texture> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref<Texture> &p_normal_map = Ref<Texture>());

Разница между файлами не показана из-за своего большого размера
+ 123 - 792
scene/2d/particles_2d.cpp


+ 55 - 192
scene/2d/particles_2d.h

@@ -34,235 +34,98 @@
 #include "scene/resources/color_ramp.h"
 #include "scene/resources/texture.h"
 
-class Particles2D;
-class ParticleAttractor2D : public Node2D {
-
-	GDCLASS(ParticleAttractor2D, Node2D);
-
-	friend class Particles2D;
-	bool enabled;
-	float radius;
-	float disable_radius;
-	float gravity;
-	float absorption;
-	NodePath path;
-
-	Particles2D *owner;
-
-	void _update_owner();
-	void _owner_exited();
-	void _set_owner(Particles2D *p_owner);
-
-	void _notification(int p_what);
-	static void _bind_methods();
-
-public:
-	void set_enabled(bool p_enabled);
-	bool is_enabled() const;
-
-	void set_radius(float p_radius);
-	float get_radius() const;
-
-	void set_disable_radius(float p_disable_radius);
-	float get_disable_radius() const;
-
-	void set_gravity(float p_gravity);
-	float get_gravity() const;
-
-	void set_absorption(float p_absorption);
-	float get_absorption() const;
-
-	void set_particles_path(NodePath p_path);
-	NodePath get_particles_path() const;
-
-	virtual String get_configuration_warning() const;
-
-	ParticleAttractor2D();
-};
-
 class Particles2D : public Node2D {
-
-	GDCLASS(Particles2D, Node2D);
+private:
+	GDCLASS(Particles2D, Node2D)
 
 public:
-	enum Parameter {
-		PARAM_DIRECTION,
-		PARAM_SPREAD,
-		PARAM_LINEAR_VELOCITY,
-		PARAM_SPIN_VELOCITY,
-		PARAM_ORBIT_VELOCITY,
-		PARAM_GRAVITY_DIRECTION,
-		PARAM_GRAVITY_STRENGTH,
-		PARAM_RADIAL_ACCEL,
-		PARAM_TANGENTIAL_ACCEL,
-		PARAM_DAMPING,
-		PARAM_INITIAL_ANGLE,
-		PARAM_INITIAL_SIZE,
-		PARAM_FINAL_SIZE,
-		PARAM_HUE_VARIATION,
-		PARAM_ANIM_SPEED_SCALE,
-		PARAM_ANIM_INITIAL_POS,
-		PARAM_MAX
-	};
-
-	enum {
-		MAX_COLOR_PHASES = 4
-	};
-
-	enum ProcessMode {
-		PROCESS_FIXED,
-		PROCESS_IDLE,
+	enum DrawOrder {
+		DRAW_ORDER_INDEX,
+		DRAW_ORDER_LIFETIME,
 	};
 
 private:
-	float param[PARAM_MAX];
-	float randomness[PARAM_MAX];
+	RID particles;
 
-	struct Particle {
-		bool active;
-		Point2 pos;
-		Vector2 velocity;
-		float rot;
-		float frame;
-		uint64_t seed;
-		Particle() {
-			active = false;
-			seed = 123465789;
-			rot = 0;
-			frame = 0;
-		}
-	};
-
-	Vector<Particle> particles;
-
-	struct AttractorCache {
-
-		Vector2 pos;
-		ParticleAttractor2D *attractor;
-	};
-
-	Vector<AttractorCache> attractor_cache;
-
-	float explosiveness;
-	float preprocess;
-	float lifetime;
 	bool emitting;
-	bool local_space;
-	float emit_timeout;
-	float time_to_live;
-	float time_scale;
-	bool flip_h;
-	bool flip_v;
-	int h_frames;
+	int amount;
+	float lifetime;
+	float pre_process_time;
+	float explosiveness_ratio;
+	float randomness_ratio;
+	float speed_scale;
+	Rect2 visibility_rect;
+	bool local_coords;
+	int fixed_fps;
+	bool fractional_delta;
 	int v_frames;
-	Point2 emissor_offset;
-	Vector2 initial_velocity;
-	Vector2 extents;
-	PoolVector<Vector2> emission_points;
+	int h_frames;
 
-	ProcessMode process_mode;
+	Ref<Material> process_material;
 
-	float time;
-	int active_count;
+	DrawOrder draw_order;
 
 	Ref<Texture> texture;
+	Ref<Texture> normal_map;
 
-	//If no color ramp is set then default color is used. Created as simple alternative to color_ramp.
-	Color default_color;
-	Ref<Gradient> gradient;
-
-	void _process_particles(float p_delta);
-	friend class ParticleAttractor2D;
-
-	Set<ParticleAttractor2D *> attractors;
+	void _update_particle_emission_transform();
 
 protected:
-	void _notification(int p_what);
 	static void _bind_methods();
+	virtual void _validate_property(PropertyInfo &property) const;
+	void _notification(int p_what);
 
 public:
 	void set_emitting(bool p_emitting);
-	bool is_emitting() const;
-
-	void set_process_mode(ProcessMode p_mode);
-	ProcessMode get_process_mode() const;
-
 	void set_amount(int p_amount);
-	int get_amount() const;
-
 	void set_lifetime(float p_lifetime);
-	float get_lifetime() const;
-
-	void set_time_scale(float p_time_scale);
-	float get_time_scale() const;
+	void set_pre_process_time(float p_time);
+	void set_explosiveness_ratio(float p_ratio);
+	void set_randomness_ratio(float p_ratio);
+	void set_visibility_rect(const Rect2 &p_aabb);
+	void set_use_local_coordinates(bool p_enable);
+	void set_process_material(const Ref<Material> &p_material);
+	void set_speed_scale(float p_scale);
 
-	void set_pre_process_time(float p_pre_process_time);
+	bool is_emitting() const;
+	int get_amount() const;
+	float get_lifetime() const;
 	float get_pre_process_time() const;
+	float get_explosiveness_ratio() const;
+	float get_randomness_ratio() const;
+	Rect2 get_visibility_rect() const;
+	bool get_use_local_coordinates() const;
+	Ref<Material> get_process_material() const;
+	float get_speed_scale() const;
 
-	void set_emit_timeout(float p_timeout);
-	float get_emit_timeout() const;
-
-	void set_emission_half_extents(const Vector2 &p_extents);
-	Vector2 get_emission_half_extents() const;
+	void set_fixed_fps(int p_count);
+	int get_fixed_fps() const;
 
-	void set_param(Parameter p_param, float p_value);
-	float get_param(Parameter p_param) const;
+	void set_fractional_delta(bool p_enable);
+	bool get_fractional_delta() const;
 
-	void set_randomness(Parameter p_randomness, float p_value);
-	float get_randomness(Parameter p_randomness) const;
-
-	void set_explosiveness(float p_value);
-	float get_explosiveness() const;
-
-	void set_flip_h(bool p_flip);
-	bool is_flipped_h() const;
-
-	void set_flip_v(bool p_flip);
-	bool is_flipped_v() const;
-
-	void set_h_frames(int p_frames);
-	int get_h_frames() const;
-
-	void set_v_frames(int p_frames);
-	int get_v_frames() const;
-
-	void set_color_phases(int p_phases);
-	int get_color_phases() const;
-
-	void set_color_phase_color(int p_phase, const Color &p_color);
-	Color get_color_phase_color(int p_phase) const;
-
-	void set_color_phase_pos(int p_phase, float p_pos);
-	float get_color_phase_pos(int p_phase) const;
+	void set_draw_order(DrawOrder p_order);
+	DrawOrder get_draw_order() const;
 
 	void set_texture(const Ref<Texture> &p_texture);
 	Ref<Texture> get_texture() const;
 
-	void set_color(const Color &p_color);
-	Color get_color() const;
-
-	void set_gradient(const Ref<Gradient> &p_texture);
-	Ref<Gradient> get_gradient() const;
-
-	void set_emissor_offset(const Point2 &p_offset);
-	Point2 get_emissor_offset() const;
+	void set_normal_map(const Ref<Texture> &p_normal_map);
+	Ref<Texture> get_normal_map() const;
 
-	void set_use_local_space(bool p_use);
-	bool is_using_local_space() const;
-
-	void set_initial_velocity(const Vector2 &p_velocity);
-	Vector2 get_initial_velocity() const;
+	virtual String get_configuration_warning() const;
 
-	void set_emission_points(const PoolVector<Vector2> &p_points);
-	PoolVector<Vector2> get_emission_points() const;
+	void set_v_frames(int p_count);
+	int get_v_frames() const;
 
-	void pre_process(float p_delta);
-	void reset();
+	void set_h_frames(int p_count);
+	int get_h_frames() const;
 
+	Rect2 capture_rect() const;
 	Particles2D();
+	~Particles2D();
 };
 
-VARIANT_ENUM_CAST(Particles2D::ProcessMode);
-VARIANT_ENUM_CAST(Particles2D::Parameter);
+VARIANT_ENUM_CAST(Particles2D::DrawOrder)
 
 #endif // PARTICLES_FRAME_H

+ 139 - 57
scene/3d/particles.cpp

@@ -404,6 +404,7 @@ void ParticlesMaterial::init_shaders() {
 	shader_names->emission_texture_point_count = "emission_texture_point_count";
 	shader_names->emission_texture_points = "emission_texture_points";
 	shader_names->emission_texture_normal = "emission_texture_normal";
+	shader_names->emission_texture_color = "emission_texture_color";
 
 	shader_names->trail_divisor = "trail_divisor";
 	shader_names->trail_size_modifier = "trail_size_modifier";
@@ -481,6 +482,28 @@ void ParticlesMaterial::_update_shader() {
 	code += "uniform float anim_speed_random;\n";
 	code += "uniform float anim_offset_random;\n";
 
+	switch (emission_shape) {
+		case EMISSION_SHAPE_POINT: {
+			//do none
+		} break;
+		case EMISSION_SHAPE_SPHERE: {
+			code += "uniform float emission_sphere_radius;\n";
+		} break;
+		case EMISSION_SHAPE_BOX: {
+			code += "uniform vec3 emission_box_extents;\n";
+		} break;
+		case EMISSION_SHAPE_DIRECTED_POINTS: {
+			code += "uniform sampler2D emission_texture_normal : hint_black;\n";
+		} //fallthrough
+		case EMISSION_SHAPE_POINTS: {
+			code += "uniform sampler2D emission_texture_points : hint_black;\n";
+			code += "uniform int emission_texture_point_count;\n";
+			if (emission_color_texture.is_valid()) {
+				code += "uniform sampler2D emission_texture_color : hint_white;\n";
+			}
+		} break;
+	}
+
 	code += "uniform vec4 color_value : hint_color;\n";
 
 	code += "uniform int trail_divisor;\n";
@@ -515,25 +538,6 @@ void ParticlesMaterial::_update_shader() {
 	if (tex_parameters[PARAM_ANIM_OFFSET].is_valid())
 		code += "uniform sampler2D anim_offset_texture;\n";
 
-	switch (emission_shape) {
-		case EMISSION_SHAPE_POINT: {
-			//do none
-		} break;
-		case EMISSION_SHAPE_SPHERE: {
-			code += "uniform float emission_sphere_radius;\n";
-		} break;
-		case EMISSION_SHAPE_BOX: {
-			code += "uniform vec3 emission_box_extents;\n";
-		} break;
-		case EMISSION_SHAPE_DIRECTED_POINTS: {
-			code += "uniform sampler2D emission_texture_normal : hint_black;\n";
-		} //fallthrough
-		case EMISSION_SHAPE_POINTS: {
-			code += "uniform sampler2D emission_texture_points : hint_black;\n";
-			code += "uniform int emission_texture_point_count;\n";
-		} break;
-	}
-
 	if (trail_size_modifier.is_valid()) {
 		code += "uniform sampler2D trail_size_modifier;\n";
 	}
@@ -576,6 +580,11 @@ void ParticlesMaterial::_update_shader() {
 	code += "\n";
 	code += "\n";
 	code += "\n";
+	if (emission_shape >= EMISSION_SHAPE_POINTS) {
+		code += " int point = min(emission_texture_point_count-1,int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n";
+		code += " ivec2 emission_tex_size = textureSize( emission_texture_points, 0 );\n";
+		code += " ivec2 emission_tex_ofs = ivec2( point % emission_tex_size.x, point / emission_tex_size.x );\n";
+	}
 	code += " if (RESTART) {\n";
 
 	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid())
@@ -593,11 +602,21 @@ void ParticlesMaterial::_update_shader() {
 	else
 		code += "    float tex_anim_offset = 0.0;\n";
 
-	code += "    float angle1 = rand_from_seed(alt_seed)*spread*3.1416;\n";
-	code += "    float angle2 = rand_from_seed(alt_seed)*20.0*3.1416; // make it more random like\n";
-	code += "    vec3 rot_xz=vec3( sin(angle1), 0.0, cos(angle1) );\n";
-	code += "    vec3 rot = vec3( cos(angle2)*rot_xz.x,sin(angle2)*rot_xz.x, rot_xz.z);\n";
-	code += "    VELOCITY=(rot*initial_linear_velocity+rot*initial_linear_velocity_random*rand_from_seed(alt_seed));\n";
+	if (flags[FLAG_DISABLE_Z]) {
+
+		code += "    float angle1 = rand_from_seed(alt_seed)*spread*3.1416;\n";
+		code += "    vec3 rot=vec3( cos(angle1), sin(angle1),0.0 );\n";
+		code += "    VELOCITY=(rot*initial_linear_velocity+rot*initial_linear_velocity_random*rand_from_seed(alt_seed));\n";
+
+	} else {
+		//initiate velocity spread in 3D
+		code += "    float angle1 = rand_from_seed(alt_seed)*spread*3.1416;\n";
+		code += "    float angle2 = rand_from_seed(alt_seed)*20.0*3.1416; // make it more random like\n";
+		code += "    vec3 rot_xz=vec3( sin(angle1), 0.0, cos(angle1) );\n";
+		code += "    vec3 rot = vec3( cos(angle2)*rot_xz.x,sin(angle2)*rot_xz.x, rot_xz.z);\n";
+		code += "    VELOCITY=(rot*initial_linear_velocity+rot*initial_linear_velocity_random*rand_from_seed(alt_seed));\n";
+	}
+
 	code += "    float base_angle=(initial_angle+tex_angle)*mix(1.0,angle_rand,initial_angle_random);\n";
 	code += "    CUSTOM.x=base_angle*3.1416/180.0;\n"; //angle
 	code += "    CUSTOM.y=0.0;\n"; //phase
@@ -614,21 +633,31 @@ void ParticlesMaterial::_update_shader() {
 		} break;
 		case EMISSION_SHAPE_POINTS:
 		case EMISSION_SHAPE_DIRECTED_POINTS: {
-			code += "    int point = min(emission_texture_point_count-1,int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n";
-			code += "    ivec2 tex_size = textureSize( emission_texture_points, 0 );\n";
-			code += "    ivec2 tex_ofs = ivec2( point % tex_size.x, point / tex_size.x );\n";
-			code += "    TRANSFORM[3].xyz = texelFetch(emission_texture_points, tex_ofs,0).xyz;\n";
+			code += "    TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs,0).xyz;\n";
+
 			if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
-				code += "    vec3 normal = texelFetch(emission_texture_normal, tex_ofs,0).xyz;\n";
-				code += "    vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0, 1.0, 0.0);\n";
-				code += "    vec3 tangent = normalize(cross(v0, normal));\n";
-				code += "    vec3 bitangent = normalize(cross(tangent, normal));\n";
-				code += "    VELOCITY = mat3(tangent,bitangent,normal) * VELOCITY;\n";
+				if (flags[FLAG_DISABLE_Z]) {
+
+					code += "    mat2 rotm;";
+					code += "    rotm[0]=texelFetch(emission_texture_normal, emission_tex_ofs,0).xy;\n";
+					code += "    rotm[1]=rotm[0].yx * vec2(1.0,-1.0);\n";
+					code += "    VELOCITY.xy = rotm * VELOCITY.xy;\n";
+				} else {
+					code += "    vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs,0).xyz;\n";
+					code += "    vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0, 1.0, 0.0);\n";
+					code += "    vec3 tangent = normalize(cross(v0, normal));\n";
+					code += "    vec3 bitangent = normalize(cross(tangent, normal));\n";
+					code += "    VELOCITY = mat3(tangent,bitangent,normal) * VELOCITY;\n";
+				}
 			}
 		} break;
 	}
 	code += "    VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY,0.0)).xyz;\n";
 	code += "    TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
+	if (flags[FLAG_DISABLE_Z]) {
+		code += "    VELOCITY.z=0.0;\n";
+		code += "    TRANSFORM[3].z=0.0;\n";
+	}
 
 	code += " } else {\n";
 
@@ -685,6 +714,9 @@ void ParticlesMaterial::_update_shader() {
 
 	code += "    vec3 force = gravity; \n";
 	code += "    vec3 pos = TRANSFORM[3].xyz; \n";
+	if (flags[FLAG_DISABLE_Z]) {
+		code += "    pos.z=0.0; \n";
+	}
 	code += "    //apply linear acceleration\n";
 	code += "    force+=normalize(VELOCITY) * (linear_accel+tex_linear_accel)*mix(1.0,rand_from_seed(alt_seed),linear_accel_random);\n";
 	code += "    //apply radial acceleration\n";
@@ -693,11 +725,17 @@ void ParticlesMaterial::_update_shader() {
 	code += "	//org=p_transform.origin;\n";
 	code += "    force+=normalize(pos-org) * (radial_accel+tex_radial_accel)*mix(1.0,rand_from_seed(alt_seed),radial_accel_random);\n";
 	code += "    //apply tangential acceleration;\n";
-	code += "    force+=normalize(cross(normalize(pos-org),normalize(gravity))) * ((tangent_accel+tex_tangent_accel)*mix(1.0,rand_from_seed(alt_seed),radial_accel_random));\n";
+	if (flags[FLAG_DISABLE_Z]) {
+		code += "    force+=vec3(normalize((pos-org).yx * vec2(-1.0,1.0)),0.0) * ((tangent_accel+tex_tangent_accel)*mix(1.0,rand_from_seed(alt_seed),radial_accel_random));\n";
+
+	} else {
+		code += "    force+=normalize(cross(normalize(pos-org),normalize(gravity))) * ((tangent_accel+tex_tangent_accel)*mix(1.0,rand_from_seed(alt_seed),radial_accel_random));\n";
+	}
 	code += "    //apply attractor forces\n";
 	code += "    VELOCITY+=force * DELTA;\n";
-	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid())
+	if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
 		code += "    VELOCITY=normalize(VELOCITY)*tex_linear_velocity;\n";
+	}
 	code += "    if (damping+tex_damping>0.0) {\n";
 	code += "    \n";
 	code += "    	float v = length(VELOCITY);\n";
@@ -709,9 +747,16 @@ void ParticlesMaterial::_update_shader() {
 	code += "    		VELOCITY=normalize(VELOCITY) * v;\n";
 	code += "	}\n";
 	code += "    }\n";
-	code += "    float base_angle=(initial_angle+tex_angle)*mix(1.0,angle_rand,initial_angle_random)*3.1416/180.0;\n";
-	code += "    CUSTOM.x=((base_angle+tex_angle)+CUSTOM.y*LIFETIME*(angular_velocity+tex_angular_velocity)*mix(1.0,rand_from_seed(alt_seed)*2.0-1.0,angular_velocity_random))*3.1416/180.0;\n"; //angle
-	code += "    CUSTOM.z=(anim_offset+tex_anim_offset)*mix(1.0,anim_offset_rand,anim_offset_random)+CUSTOM.y*LIFETIME*(anim_speed+tex_anim_speed)*mix(1.0,rand_from_seed(alt_seed),anim_speed_random);\n"; //angle
+	code += "    float base_angle=(initial_angle+tex_angle)*mix(1.0,angle_rand,initial_angle_random);\n";
+	code += "    base_angle+=CUSTOM.y*LIFETIME*(angular_velocity+tex_angular_velocity)*mix(1.0,rand_from_seed(alt_seed)*2.0-1.0,angular_velocity_random);\n";
+	code += "    CUSTOM.x=base_angle*3.1416/180.0;\n"; //angle
+	code += "    CUSTOM.z=(anim_offset+tex_anim_offset)*mix(1.0,anim_offset_rand,anim_offset_random)+CUSTOM.y*(anim_speed+tex_anim_speed)*mix(1.0,rand_from_seed(alt_seed),anim_speed_random);\n"; //angle
+	if (flags[FLAG_ANIM_LOOP]) {
+		code += "    CUSTOM.z=mod(CUSTOM.z,1.0);\n"; //loop
+
+	} else {
+		code += "    CUSTOM.z=clamp(CUSTOM.z,0.0,1.0);\n"; //0 to 1 only
+	}
 	code += "  }\n";
 	//apply color
 	//apply hue rotation
@@ -747,28 +792,40 @@ void ParticlesMaterial::_update_shader() {
 	} else {
 		code += " COLOR = color_value * hue_rot_mat;\n";
 	}
+	if (emission_color_texture.is_valid() && emission_shape >= EMISSION_SHAPE_POINTS) {
+		code += " COLOR*= texelFetch(emission_texture_color,emission_tex_ofs,0);\n";
+	}
 	if (trail_color_modifier.is_valid()) {
 		code += "if (trail_divisor>1) { COLOR*=textureLod(trail_color_modifier,vec2(float(int(NUMBER)%trail_divisor)/float(trail_divisor-1),0.0),0.0); }\n";
 	}
 	code += "\n";
-	//orient particle Y towards velocity
-	if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) {
-		code += " if (length(VELOCITY)>0.0) {TRANSFORM[1].xyz=normalize(VELOCITY);} else {TRANSFORM[1].xyz=normalize(TRANSFORM[1].xyz);}\n";
-		code += " if (TRANSFORM[1].xyz==normalize(TRANSFORM[0].xyz)) {\n";
-		code += "\tTRANSFORM[0].xyz=normalize(cross(normalize(TRANSFORM[1].xyz),normalize(TRANSFORM[2].xyz)));\n";
-		code += "\tTRANSFORM[2].xyz=normalize(cross(normalize(TRANSFORM[0].xyz),normalize(TRANSFORM[1].xyz)));\n";
-		code += " } else {\n";
-		code += "\tTRANSFORM[2].xyz=normalize(cross(normalize(TRANSFORM[0].xyz),normalize(TRANSFORM[1].xyz)));\n";
-		code += "\tTRANSFORM[0].xyz=normalize(cross(normalize(TRANSFORM[1].xyz),normalize(TRANSFORM[2].xyz)));\n";
-		code += " }\n";
+
+	if (flags[FLAG_DISABLE_Z]) {
+
+		code += " TRANSFORM[0]=vec4(cos(CUSTOM.x),-sin(CUSTOM.x),0.0,0.0);\n";
+		code += " TRANSFORM[1]=vec4(sin(CUSTOM.x),cos(CUSTOM.x),0.0,0.0);\n";
+		code += " TRANSFORM[2]=vec4(0.0,0.0,1.0,0.0);\n";
+
 	} else {
-		code += "\tTRANSFORM[0].xyz=normalize(TRANSFORM[0].xyz);\n";
-		code += "\tTRANSFORM[1].xyz=normalize(TRANSFORM[1].xyz);\n";
-		code += "\tTRANSFORM[2].xyz=normalize(TRANSFORM[2].xyz);\n";
-	}
-	//turn particle by rotation in Y
-	if (flags[FLAG_ROTATE_Y]) {
-		code += "\tTRANSFORM = TRANSFORM * mat4( vec4(cos(CUSTOM.x),0.0,-sin(CUSTOM.x),0.0), vec4(0.0,1.0,0.0,0.0),vec4(sin(CUSTOM.x),0.0,cos(CUSTOM.x),0.0),vec4(0.0,0.0,0.0,1.0));\n";
+		//orient particle Y towards velocity
+		if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) {
+			code += " if (length(VELOCITY)>0.0) {TRANSFORM[1].xyz=normalize(VELOCITY);} else {TRANSFORM[1].xyz=normalize(TRANSFORM[1].xyz);}\n";
+			code += " if (TRANSFORM[1].xyz==normalize(TRANSFORM[0].xyz)) {\n";
+			code += "\tTRANSFORM[0].xyz=normalize(cross(normalize(TRANSFORM[1].xyz),normalize(TRANSFORM[2].xyz)));\n";
+			code += "\tTRANSFORM[2].xyz=normalize(cross(normalize(TRANSFORM[0].xyz),normalize(TRANSFORM[1].xyz)));\n";
+			code += " } else {\n";
+			code += "\tTRANSFORM[2].xyz=normalize(cross(normalize(TRANSFORM[0].xyz),normalize(TRANSFORM[1].xyz)));\n";
+			code += "\tTRANSFORM[0].xyz=normalize(cross(normalize(TRANSFORM[1].xyz),normalize(TRANSFORM[2].xyz)));\n";
+			code += " }\n";
+		} else {
+			code += "\tTRANSFORM[0].xyz=normalize(TRANSFORM[0].xyz);\n";
+			code += "\tTRANSFORM[1].xyz=normalize(TRANSFORM[1].xyz);\n";
+			code += "\tTRANSFORM[2].xyz=normalize(TRANSFORM[2].xyz);\n";
+		}
+		//turn particle by rotation in Y
+		if (flags[FLAG_ROTATE_Y]) {
+			code += "\tTRANSFORM = TRANSFORM * mat4( vec4(cos(CUSTOM.x),0.0,-sin(CUSTOM.x),0.0), vec4(0.0,1.0,0.0,0.0),vec4(sin(CUSTOM.x),0.0,cos(CUSTOM.x),0.0),vec4(0.0,0.0,0.0,1.0));\n";
+		}
 	}
 	//scale by scale
 	code += " float base_scale=mix(scale*tex_scale,1.0,scale_random*scale_rand);\n";
@@ -779,6 +836,10 @@ void ParticlesMaterial::_update_shader() {
 	code += " TRANSFORM[0].xyz*=base_scale;\n";
 	code += " TRANSFORM[1].xyz*=base_scale;\n";
 	code += " TRANSFORM[2].xyz*=base_scale;\n";
+	if (flags[FLAG_DISABLE_Z]) {
+		code += " VELOCITY.z=0.0;\n";
+		code += " TRANSFORM[3].z=0.0;\n";
+	}
 	code += "}\n";
 	code += "\n";
 
@@ -1130,6 +1191,16 @@ void ParticlesMaterial::set_emission_normal_texture(const Ref<Texture> &p_normal
 	VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, texture);
 }
 
+void ParticlesMaterial::set_emission_color_texture(const Ref<Texture> &p_colors) {
+
+	emission_color_texture = p_colors;
+	RID texture;
+	if (p_colors.is_valid())
+		texture = p_colors->get_rid();
+	VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, texture);
+	_queue_shader_change();
+}
+
 void ParticlesMaterial::set_emission_point_count(int p_count) {
 
 	emission_point_count = p_count;
@@ -1158,6 +1229,11 @@ Ref<Texture> ParticlesMaterial::get_emission_normal_texture() const {
 	return emission_normal_texture;
 }
 
+Ref<Texture> ParticlesMaterial::get_emission_color_texture() const {
+
+	return emission_color_texture;
+}
+
 int ParticlesMaterial::get_emission_point_count() const {
 
 	return emission_point_count;
@@ -1247,7 +1323,7 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const {
 		property.usage = 0;
 	}
 
-	if (property.name == "emission_point_texture" && (emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS)) {
+	if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) {
 		property.usage = 0;
 	}
 
@@ -1301,6 +1377,9 @@ void ParticlesMaterial::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_emission_normal_texture", "texture:Texture"), &ParticlesMaterial::set_emission_normal_texture);
 	ClassDB::bind_method(D_METHOD("get_emission_normal_texture:Texture"), &ParticlesMaterial::get_emission_normal_texture);
 
+	ClassDB::bind_method(D_METHOD("set_emission_color_texture", "texture:Texture"), &ParticlesMaterial::set_emission_color_texture);
+	ClassDB::bind_method(D_METHOD("get_emission_color_texture:Texture"), &ParticlesMaterial::get_emission_color_texture);
+
 	ClassDB::bind_method(D_METHOD("set_emission_point_count", "point_count"), &ParticlesMaterial::set_emission_point_count);
 	ClassDB::bind_method(D_METHOD("get_emission_point_count"), &ParticlesMaterial::get_emission_point_count);
 
@@ -1326,10 +1405,12 @@ void ParticlesMaterial::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_point_texture", "get_emission_point_texture");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_normal_texture", "get_emission_normal_texture");
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_color_texture", "get_emission_color_texture");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count");
 	ADD_GROUP("Flags", "flag_");
 	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_flag", "get_flag", FLAG_ALIGN_Y_TO_VELOCITY);
 	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_rotate_y"), "set_flag", "get_flag", FLAG_ROTATE_Y);
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_disable_z"), "set_flag", "get_flag", FLAG_DISABLE_Z);
 	ADD_GROUP("Spread", "");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness");
@@ -1379,12 +1460,13 @@ void ParticlesMaterial::_bind_methods() {
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION);
 	ADD_GROUP("Animation", "anim_");
-	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_SPEED);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_param", "get_param", PARAM_ANIM_SPEED);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET);
 	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_offset_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_OFFSET);
 	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET);
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "anim_loop"), "set_flag", "get_flag", FLAG_ANIM_LOOP);
 
 	BIND_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY);
 	BIND_CONSTANT(PARAM_ANGULAR_VELOCITY);

+ 11 - 1
scene/3d/particles.h

@@ -154,6 +154,8 @@ public:
 	enum Flags {
 		FLAG_ALIGN_Y_TO_VELOCITY,
 		FLAG_ROTATE_Y,
+		FLAG_DISABLE_Z,
+		FLAG_ANIM_LOOP,
 		FLAG_MAX
 	};
 
@@ -171,11 +173,12 @@ private:
 		struct {
 			uint32_t texture_mask : 16;
 			uint32_t texture_color : 1;
-			uint32_t flags : 2;
+			uint32_t flags : 4;
 			uint32_t emission_shape : 2;
 			uint32_t trail_size_texture : 1;
 			uint32_t trail_color_texture : 1;
 			uint32_t invalid_key : 1;
+			uint32_t has_emission_color : 1;
 		};
 
 		uint32_t key;
@@ -213,6 +216,7 @@ private:
 		mk.emission_shape = emission_shape;
 		mk.trail_color_texture = trail_color_modifier.is_valid() ? 1 : 0;
 		mk.trail_size_texture = trail_size_modifier.is_valid() ? 1 : 0;
+		mk.has_emission_color = emission_shape >= EMISSION_SHAPE_POINTS && emission_color_texture.is_valid();
 
 		return mk;
 	}
@@ -269,6 +273,7 @@ private:
 		StringName emission_texture_point_count;
 		StringName emission_texture_points;
 		StringName emission_texture_normal;
+		StringName emission_texture_color;
 
 		StringName trail_divisor;
 		StringName trail_size_modifier;
@@ -302,8 +307,11 @@ private:
 	Vector3 emission_box_extents;
 	Ref<Texture> emission_point_texture;
 	Ref<Texture> emission_normal_texture;
+	Ref<Texture> emission_color_texture;
 	int emission_point_count;
 
+	bool anim_loop;
+
 	int trail_divisor;
 
 	Ref<CurveTexture> trail_size_modifier;
@@ -347,6 +355,7 @@ public:
 	void set_emission_box_extents(Vector3 p_extents);
 	void set_emission_point_texture(const Ref<Texture> &p_points);
 	void set_emission_normal_texture(const Ref<Texture> &p_normals);
+	void set_emission_color_texture(const Ref<Texture> &p_colors);
 	void set_emission_point_count(int p_count);
 
 	EmissionShape get_emission_shape() const;
@@ -354,6 +363,7 @@ public:
 	Vector3 get_emission_box_extents() const;
 	Ref<Texture> get_emission_point_texture() const;
 	Ref<Texture> get_emission_normal_texture() const;
+	Ref<Texture> get_emission_color_texture() const;
 	int get_emission_point_count() const;
 
 	void set_trail_divisor(int p_divisor);

+ 1 - 1
scene/register_scene_types.cpp

@@ -471,7 +471,7 @@ void register_scene_types() {
 	ClassDB::register_virtual_class<CanvasItem>();
 	ClassDB::register_class<Node2D>();
 	ClassDB::register_class<Particles2D>();
-	ClassDB::register_class<ParticleAttractor2D>();
+	//ClassDB::register_class<ParticleAttractor2D>();
 	ClassDB::register_class<Sprite>();
 	//ClassDB::register_type<ViewportSprite>();
 	ClassDB::register_class<SpriteFrames>();

+ 20 - 0
servers/visual/rasterizer.h

@@ -612,6 +612,7 @@ public:
 				TYPE_POLYGON,
 				TYPE_MESH,
 				TYPE_MULTIMESH,
+				TYPE_PARTICLES,
 				TYPE_CIRCLE,
 				TYPE_TRANSFORM,
 				TYPE_CLIP_IGNORE,
@@ -707,6 +708,16 @@ public:
 			CommandMultiMesh() { type = TYPE_MULTIMESH; }
 		};
 
+		struct CommandParticles : public Command {
+
+			RID particles;
+			RID texture;
+			RID normal_map;
+			int h_frames;
+			int v_frames;
+			CommandParticles() { type = TYPE_PARTICLES; }
+		};
+
 		struct CommandCircle : public Command {
 
 			Point2 pos;
@@ -844,6 +855,15 @@ public:
 
 						r = Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
 
+					} break;
+					case Item::Command::TYPE_PARTICLES: {
+
+						const Item::CommandParticles *particles_cmd = static_cast<const Item::CommandParticles *>(c);
+						if (particles_cmd->particles.is_valid()) {
+							Rect3 aabb = RasterizerStorage::base_singleton->particles_get_aabb(particles_cmd->particles);
+							r = Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y);
+						}
+
 					} break;
 					case Item::Command::TYPE_CIRCLE: {
 

+ 19 - 0
servers/visual/visual_server_canvas.cpp

@@ -637,6 +637,25 @@ void VisualServerCanvas::canvas_item_add_mesh(RID p_item, const RID &p_mesh, RID
 
 	canvas_item->commands.push_back(m);
 }
+void VisualServerCanvas::canvas_item_add_particles(RID p_item, RID p_particles, RID p_texture, RID p_normal, int p_h_frames, int p_v_frames) {
+
+	Item *canvas_item = canvas_item_owner.getornull(p_item);
+	ERR_FAIL_COND(!canvas_item);
+
+	Item::CommandParticles *part = memnew(Item::CommandParticles);
+	ERR_FAIL_COND(!part);
+	part->particles = p_particles;
+	part->texture = p_texture;
+	part->normal_map = p_normal;
+	part->h_frames = p_h_frames;
+	part->v_frames = p_v_frames;
+
+	//take the chance and request processing for them, at least once until they become visible again
+	VSG::storage->particles_request_process(p_particles);
+
+	canvas_item->commands.push_back(part);
+}
+
 void VisualServerCanvas::canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p_skeleton) {
 
 	Item *canvas_item = canvas_item_owner.getornull(p_item);

+ 1 - 0
servers/visual/visual_server_canvas.h

@@ -173,6 +173,7 @@ public:
 	void canvas_item_add_triangle_array(RID p_item, const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID(), int p_count = -1, RID p_normal_map = RID());
 	void canvas_item_add_mesh(RID p_item, const RID &p_mesh, RID p_skeleton = RID());
 	void canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p_skeleton = RID());
+	void canvas_item_add_particles(RID p_item, RID p_particles, RID p_texture, RID p_normal, int p_h_frames, int p_v_frames);
 	void canvas_item_add_set_transform(RID p_item, const Transform2D &p_transform);
 	void canvas_item_add_clip_ignore(RID p_item, bool p_ignore);
 	void canvas_item_set_sort_children_by_y(RID p_item, bool p_enable);

+ 3 - 1
servers/visual/visual_server_raster.h

@@ -877,7 +877,8 @@ public:
 	BIND2(particles_set_draw_passes, RID, int)
 	BIND3(particles_set_draw_pass_mesh, RID, int, RID)
 
-	BIND1R(Rect3, particles_get_current_aabb, RID);
+	BIND1R(Rect3, particles_get_current_aabb, RID)
+	BIND2(particles_set_emission_transform, RID, const Transform &)
 
 #undef BINDBASE
 //from now on, calls forwarded to this singleton
@@ -1049,6 +1050,7 @@ public:
 	BIND8(canvas_item_add_triangle_array, RID, const Vector<int> &, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, int, RID)
 	BIND3(canvas_item_add_mesh, RID, const RID &, RID)
 	BIND3(canvas_item_add_multimesh, RID, RID, RID)
+	BIND6(canvas_item_add_particles, RID, RID, RID, RID, int, int)
 	BIND2(canvas_item_add_set_transform, RID, const Transform2D &)
 	BIND2(canvas_item_add_clip_ignore, RID, bool)
 	BIND2(canvas_item_set_sort_children_by_y, RID, bool)

+ 2 - 0
servers/visual/visual_server_wrap_mt.h

@@ -320,6 +320,7 @@ public:
 
 	FUNC2(particles_set_draw_passes, RID, int)
 	FUNC3(particles_set_draw_pass_mesh, RID, int, RID)
+	FUNC2(particles_set_emission_transform, RID, const Transform &)
 
 	FUNC1R(Rect3, particles_get_current_aabb, RID)
 
@@ -476,6 +477,7 @@ public:
 	FUNC8(canvas_item_add_triangle_array, RID, const Vector<int> &, const Vector<Point2> &, const Vector<Color> &, const Vector<Point2> &, RID, int, RID)
 	FUNC3(canvas_item_add_mesh, RID, const RID &, RID)
 	FUNC3(canvas_item_add_multimesh, RID, RID, RID)
+	FUNC6(canvas_item_add_particles, RID, RID, RID, RID, int, int)
 	FUNC2(canvas_item_add_set_transform, RID, const Transform2D &)
 	FUNC2(canvas_item_add_clip_ignore, RID, bool)
 	FUNC2(canvas_item_set_sort_children_by_y, RID, bool)

+ 3 - 0
servers/visual_server.h

@@ -501,6 +501,8 @@ public:
 
 	virtual Rect3 particles_get_current_aabb(RID p_particles) = 0;
 
+	virtual void particles_set_emission_transform(RID p_particles, const Transform &p_transform) = 0; //this is only used for 2D, in 3D it's automatic
+
 	/* CAMERA API */
 
 	virtual RID camera_create() = 0;
@@ -793,6 +795,7 @@ public:
 	virtual void canvas_item_add_triangle_array(RID p_item, const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), RID p_texture = RID(), int p_count = -1, RID p_normal_map = RID()) = 0;
 	virtual void canvas_item_add_mesh(RID p_item, const RID &p_mesh, RID p_skeleton = RID()) = 0;
 	virtual void canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p_skeleton = RID()) = 0;
+	virtual void canvas_item_add_particles(RID p_item, RID p_particles, RID p_texture, RID p_normal_map, int p_h_frames, int p_v_frames) = 0;
 	virtual void canvas_item_add_set_transform(RID p_item, const Transform2D &p_transform) = 0;
 	virtual void canvas_item_add_clip_ignore(RID p_item, bool p_ignore) = 0;
 	virtual void canvas_item_set_sort_children_by_y(RID p_item, bool p_enable) = 0;

Некоторые файлы не были показаны из-за большого количества измененных файлов