2
0
Эх сурвалжийг харах

Support for CPU based particles, which aids compatibility with OpenGL ES 2.0

Juan Linietsky 7 жил өмнө
parent
commit
7dcaabaf19

+ 13 - 1
drivers/gles2/rasterizer_storage_gles2.cpp

@@ -1182,7 +1182,7 @@ RID RasterizerStorageGLES2::multimesh_create() {
 	return RID();
 }
 
-void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format) {
+void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format,VS::MultimeshCustomDataFormat p_data) {
 }
 
 int RasterizerStorageGLES2::multimesh_get_instance_count(RID p_multimesh) const {
@@ -1201,6 +1201,9 @@ void RasterizerStorageGLES2::multimesh_instance_set_transform_2d(RID p_multimesh
 void RasterizerStorageGLES2::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
 }
 
+void RasterizerStorageGLES2::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+}
+
 RID RasterizerStorageGLES2::multimesh_get_mesh(RID p_multimesh) const {
 	return RID();
 }
@@ -1217,6 +1220,15 @@ Color RasterizerStorageGLES2::multimesh_instance_get_color(RID p_multimesh, int
 	return Color();
 }
 
+Color RasterizerStorageGLES2::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+	return Color();
+}
+
+void RasterizerStorageGLES2::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) {
+
+
+}
+
 void RasterizerStorageGLES2::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
 }
 

+ 5 - 1
drivers/gles2/rasterizer_storage_gles2.h

@@ -508,19 +508,23 @@ public:
 
 	virtual RID multimesh_create();
 
-	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format);
+	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format,VS::MultimeshCustomDataFormat p_data=VS::MULTIMESH_CUSTOM_DATA_NONE);
 	virtual int multimesh_get_instance_count(RID p_multimesh) const;
 
 	virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh);
 	virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform);
 	virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform);
 	virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color);
+	virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color);
 
 	virtual RID multimesh_get_mesh(RID p_multimesh) const;
 
 	virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const;
 	virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const;
 	virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const;
+	virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const;
+
+	virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array);
 
 	virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible);
 	virtual int multimesh_get_visible_instances(RID p_multimesh) const;

+ 24 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -1336,7 +1336,7 @@ void RasterizerSceneGLES3::_setup_geometry(RenderList::Element *e, const Transfo
 
 			glBindBuffer(GL_ARRAY_BUFFER, multi_mesh->buffer); //modify the buffer
 
-			int stride = (multi_mesh->xform_floats + multi_mesh->color_floats) * 4;
+			int stride = (multi_mesh->xform_floats + multi_mesh->color_floats + multi_mesh->custom_data_floats) * 4;
 			glEnableVertexAttribArray(8);
 			glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + 0);
 			glVertexAttribDivisor(8, 1);
@@ -1357,6 +1357,8 @@ void RasterizerSceneGLES3::_setup_geometry(RenderList::Element *e, const Transfo
 				color_ofs = 8 * 4;
 			}
 
+			int custom_data_ofs = color_ofs;
+
 			switch (multi_mesh->color_format) {
 
 				case VS::MULTIMESH_COLOR_NONE: {
@@ -1367,12 +1369,33 @@ void RasterizerSceneGLES3::_setup_geometry(RenderList::Element *e, const Transfo
 					glEnableVertexAttribArray(11);
 					glVertexAttribPointer(11, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, ((uint8_t *)NULL) + color_ofs);
 					glVertexAttribDivisor(11, 1);
+					custom_data_ofs += 4;
 
 				} break;
 				case VS::MULTIMESH_COLOR_FLOAT: {
 					glEnableVertexAttribArray(11);
 					glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + color_ofs);
 					glVertexAttribDivisor(11, 1);
+					custom_data_ofs += 4 * 4;
+				} break;
+			}
+
+			switch (multi_mesh->custom_data_format) {
+
+				case VS::MULTIMESH_CUSTOM_DATA_NONE: {
+					glDisableVertexAttribArray(12);
+					glVertexAttrib4f(12, 1, 1, 1, 1);
+				} break;
+				case VS::MULTIMESH_CUSTOM_DATA_8BIT: {
+					glEnableVertexAttribArray(12);
+					glVertexAttribPointer(12, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, ((uint8_t *)NULL) + custom_data_ofs);
+					glVertexAttribDivisor(12, 1);
+
+				} break;
+				case VS::MULTIMESH_CUSTOM_DATA_FLOAT: {
+					glEnableVertexAttribArray(12);
+					glVertexAttribPointer(12, 4, GL_FLOAT, GL_FALSE, stride, ((uint8_t *)NULL) + custom_data_ofs);
+					glVertexAttribDivisor(12, 1);
 				} break;
 			}
 

+ 130 - 10
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -3816,12 +3816,12 @@ RID RasterizerStorageGLES3::multimesh_create() {
 	return multimesh_owner.make_rid(multimesh);
 }
 
-void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format) {
+void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format) {
 
 	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
 	ERR_FAIL_COND(!multimesh);
 
-	if (multimesh->size == p_instances && multimesh->transform_format == p_transform_format && multimesh->color_format == p_color_format)
+	if (multimesh->size == p_instances && multimesh->transform_format == p_transform_format && multimesh->color_format == p_color_format && multimesh->custom_data_format == p_data_format)
 		return;
 
 	if (multimesh->buffer) {
@@ -3832,6 +3832,7 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances
 	multimesh->size = p_instances;
 	multimesh->transform_format = p_transform_format;
 	multimesh->color_format = p_color_format;
+	multimesh->custom_data_format = p_data_format;
 
 	if (multimesh->size) {
 
@@ -3849,11 +3850,22 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances
 			multimesh->color_floats = 4;
 		}
 
-		int format_floats = multimesh->color_floats + multimesh->xform_floats;
+		if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE) {
+			multimesh->custom_data_floats = 0;
+		} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) {
+			multimesh->custom_data_floats = 1;
+		} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_FLOAT) {
+			multimesh->custom_data_floats = 4;
+		}
+
+		int format_floats = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
+
 		multimesh->data.resize(format_floats * p_instances);
+
 		for (int i = 0; i < p_instances; i += format_floats) {
 
 			int color_from = 0;
+			int custom_data_from = 0;
 
 			if (multimesh->transform_format == VS::MULTIMESH_TRANSFORM_2D) {
 				multimesh->data[i + 0] = 1.0;
@@ -3865,6 +3877,7 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances
 				multimesh->data[i + 6] = 0.0;
 				multimesh->data[i + 7] = 0.0;
 				color_from = 8;
+				custom_data_from = 8;
 			} else {
 				multimesh->data[i + 0] = 1.0;
 				multimesh->data[i + 1] = 0.0;
@@ -3879,6 +3892,7 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances
 				multimesh->data[i + 10] = 1.0;
 				multimesh->data[i + 11] = 0.0;
 				color_from = 12;
+				custom_data_from = 12;
 			}
 
 			if (multimesh->color_format == VS::MULTIMESH_COLOR_NONE) {
@@ -3892,12 +3906,33 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances
 
 				cu.colu = 0xFFFFFFFF;
 				multimesh->data[i + color_from + 0] = cu.colf;
+				custom_data_from = color_from + 1;
 
 			} else if (multimesh->color_format == VS::MULTIMESH_COLOR_FLOAT) {
 				multimesh->data[i + color_from + 0] = 1.0;
 				multimesh->data[i + color_from + 1] = 1.0;
 				multimesh->data[i + color_from + 2] = 1.0;
 				multimesh->data[i + color_from + 3] = 1.0;
+				custom_data_from = color_from + 4;
+			}
+
+			if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE) {
+				//none
+			} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) {
+
+				union {
+					uint32_t colu;
+					float colf;
+				} cu;
+
+				cu.colu = 0;
+				multimesh->data[i + custom_data_from + 0] = cu.colf;
+
+			} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_FLOAT) {
+				multimesh->data[i + custom_data_from + 0] = 0.0;
+				multimesh->data[i + custom_data_from + 1] = 0.0;
+				multimesh->data[i + custom_data_from + 2] = 0.0;
+				multimesh->data[i + custom_data_from + 3] = 0.0;
 			}
 		}
 
@@ -3958,7 +3993,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_transform(RID p_multimesh, i
 	ERR_FAIL_INDEX(p_index, multimesh->size);
 	ERR_FAIL_COND(multimesh->transform_format == VS::MULTIMESH_TRANSFORM_2D);
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index];
 
 	dataptr[0] = p_transform.basis.elements[0][0];
@@ -3989,7 +4024,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_transform_2d(RID p_multimesh
 	ERR_FAIL_INDEX(p_index, multimesh->size);
 	ERR_FAIL_COND(multimesh->transform_format == VS::MULTIMESH_TRANSFORM_3D);
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index];
 
 	dataptr[0] = p_transform.elements[0][0];
@@ -4015,7 +4050,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_color(RID p_multimesh, int p
 	ERR_FAIL_INDEX(p_index, multimesh->size);
 	ERR_FAIL_COND(multimesh->color_format == VS::MULTIMESH_COLOR_NONE);
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index + multimesh->xform_floats];
 
 	if (multimesh->color_format == VS::MULTIMESH_COLOR_8BIT) {
@@ -4041,6 +4076,38 @@ void RasterizerStorageGLES3::multimesh_instance_set_color(RID p_multimesh, int p
 	}
 }
 
+void RasterizerStorageGLES3::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data) {
+
+	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
+	ERR_FAIL_COND(!multimesh);
+	ERR_FAIL_INDEX(p_index, multimesh->size);
+	ERR_FAIL_COND(multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE);
+
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
+	float *dataptr = &multimesh->data[stride * p_index + multimesh->xform_floats + multimesh->color_floats];
+
+	if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) {
+
+		uint8_t *data8 = (uint8_t *)dataptr;
+		data8[0] = CLAMP(p_custom_data.r * 255.0, 0, 255);
+		data8[1] = CLAMP(p_custom_data.g * 255.0, 0, 255);
+		data8[2] = CLAMP(p_custom_data.b * 255.0, 0, 255);
+		data8[3] = CLAMP(p_custom_data.a * 255.0, 0, 255);
+
+	} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_FLOAT) {
+		dataptr[0] = p_custom_data.r;
+		dataptr[1] = p_custom_data.g;
+		dataptr[2] = p_custom_data.b;
+		dataptr[3] = p_custom_data.a;
+	}
+
+	multimesh->dirty_data = true;
+	multimesh->dirty_aabb = true;
+
+	if (!multimesh->update_list.in_list()) {
+		multimesh_update_list.add(&multimesh->update_list);
+	}
+}
 RID RasterizerStorageGLES3::multimesh_get_mesh(RID p_multimesh) const {
 
 	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
@@ -4056,7 +4123,7 @@ Transform RasterizerStorageGLES3::multimesh_instance_get_transform(RID p_multime
 	ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform());
 	ERR_FAIL_COND_V(multimesh->transform_format == VS::MULTIMESH_TRANSFORM_2D, Transform());
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index];
 
 	Transform xform;
@@ -4083,7 +4150,7 @@ Transform2D RasterizerStorageGLES3::multimesh_instance_get_transform_2d(RID p_mu
 	ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform2D());
 	ERR_FAIL_COND_V(multimesh->transform_format == VS::MULTIMESH_TRANSFORM_3D, Transform2D());
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index];
 
 	Transform2D xform;
@@ -4105,7 +4172,7 @@ Color RasterizerStorageGLES3::multimesh_instance_get_color(RID p_multimesh, int
 	ERR_FAIL_INDEX_V(p_index, multimesh->size, Color());
 	ERR_FAIL_COND_V(multimesh->color_format == VS::MULTIMESH_COLOR_NONE, Color());
 
-	int stride = multimesh->color_floats + multimesh->xform_floats;
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 	float *dataptr = &multimesh->data[stride * p_index + multimesh->xform_floats];
 
 	if (multimesh->color_format == VS::MULTIMESH_COLOR_8BIT) {
@@ -4131,6 +4198,59 @@ Color RasterizerStorageGLES3::multimesh_instance_get_color(RID p_multimesh, int
 	return Color();
 }
 
+Color RasterizerStorageGLES3::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+
+	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
+	ERR_FAIL_COND_V(!multimesh, Color());
+	ERR_FAIL_INDEX_V(p_index, multimesh->size, Color());
+	ERR_FAIL_COND_V(multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE, Color());
+
+	int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
+	float *dataptr = &multimesh->data[stride * p_index + multimesh->xform_floats + multimesh->color_floats];
+
+	if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) {
+		union {
+			uint32_t colu;
+			float colf;
+		} cu;
+
+		cu.colf = dataptr[0];
+
+		return Color::hex(BSWAP32(cu.colu));
+
+	} else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_FLOAT) {
+		Color c;
+		c.r = dataptr[0];
+		c.g = dataptr[1];
+		c.b = dataptr[2];
+		c.a = dataptr[3];
+
+		return c;
+	}
+
+	return Color();
+}
+
+void RasterizerStorageGLES3::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) {
+
+	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
+	ERR_FAIL_COND(!multimesh);
+
+	int dsize = multimesh->data.size();
+
+	ERR_FAIL_COND(dsize != p_array.size());
+
+	PoolVector<float>::Read r = p_array.read();
+	copymem(multimesh->data.ptrw(), r.ptr(), dsize * sizeof(float));
+
+	multimesh->dirty_data = true;
+	multimesh->dirty_aabb = true;
+
+	if (!multimesh->update_list.in_list()) {
+		multimesh_update_list.add(&multimesh->update_list);
+	}
+}
+
 void RasterizerStorageGLES3::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
 
 	MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh);
@@ -4179,7 +4299,7 @@ void RasterizerStorageGLES3::update_dirty_multimeshes() {
 				mesh_aabb.size += Vector3(0.001, 0.001, 0.001);
 			}
 
-			int stride = multimesh->color_floats + multimesh->xform_floats;
+			int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats;
 			int count = multimesh->data.size();
 			float *data = multimesh->data.ptrw();
 

+ 9 - 1
drivers/gles3/rasterizer_storage_gles3.h

@@ -756,6 +756,7 @@ public:
 		int size;
 		VS::MultimeshTransformFormat transform_format;
 		VS::MultimeshColorFormat color_format;
+		VS::MultimeshCustomDataFormat custom_data_format;
 		Vector<float> data;
 		AABB aabb;
 		SelfList<MultiMesh> update_list;
@@ -765,6 +766,7 @@ public:
 
 		int xform_floats;
 		int color_floats;
+		int custom_data_floats;
 
 		bool dirty_aabb;
 		bool dirty_data;
@@ -776,11 +778,13 @@ public:
 			dirty_data = true;
 			xform_floats = 0;
 			color_floats = 0;
+			custom_data_floats = 0;
 			visible_instances = -1;
 			size = 0;
 			buffer = 0;
 			transform_format = VS::MULTIMESH_TRANSFORM_2D;
 			color_format = VS::MULTIMESH_COLOR_NONE;
+			custom_data_format = VS::MULTIMESH_CUSTOM_DATA_NONE;
 		}
 	};
 
@@ -792,19 +796,23 @@ public:
 
 	virtual RID multimesh_create();
 
-	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format);
+	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format = VS::MULTIMESH_CUSTOM_DATA_NONE);
 	virtual int multimesh_get_instance_count(RID p_multimesh) const;
 
 	virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh);
 	virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform);
 	virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform);
 	virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color);
+	virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color);
 
 	virtual RID multimesh_get_mesh(RID p_multimesh) const;
 
 	virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const;
 	virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const;
 	virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const;
+	virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const;
+
+	virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array);
 
 	virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible);
 	virtual int multimesh_get_visible_instances(RID p_multimesh) const;

+ 2 - 0
editor/editor_node.cpp

@@ -79,6 +79,7 @@
 #include "editor/plugins/collision_polygon_2d_editor_plugin.h"
 #include "editor/plugins/collision_polygon_editor_plugin.h"
 #include "editor/plugins/collision_shape_2d_editor_plugin.h"
+#include "editor/plugins/cpu_particles_editor_plugin.h"
 #include "editor/plugins/cube_grid_theme_editor_plugin.h"
 #include "editor/plugins/curve_editor_plugin.h"
 #include "editor/plugins/editor_preview_plugins.h"
@@ -5383,6 +5384,7 @@ EditorNode::EditorNode() {
 	add_editor_plugin(memnew(SpriteEditorPlugin(this)));
 	add_editor_plugin(memnew(Skeleton2DEditorPlugin(this)));
 	add_editor_plugin(memnew(ParticlesEditorPlugin(this)));
+	add_editor_plugin(memnew(CPUParticlesEditorPlugin(this)));
 	add_editor_plugin(memnew(ResourcePreloaderEditorPlugin(this)));
 	add_editor_plugin(memnew(ItemListEditorPlugin(this)));
 	add_editor_plugin(memnew(Polygon3DEditorPlugin(this)));

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 52 - 0
editor/icons/icon_c_p_u_particles.svg


+ 114 - 0
editor/plugins/cpu_particles_editor_plugin.cpp

@@ -0,0 +1,114 @@
+#include "cpu_particles_editor_plugin.h"
+#include "editor/plugins/spatial_editor_plugin.h"
+
+void CPUParticlesEditor::_node_removed(Node *p_node) {
+
+	if (p_node == node) {
+		node = NULL;
+		hide();
+	}
+}
+
+void CPUParticlesEditor::_notification(int p_notification) {
+
+	if (p_notification == NOTIFICATION_ENTER_TREE) {
+		options->set_icon(options->get_popup()->get_icon("CPUParticles", "EditorIcons"));
+	}
+}
+
+void CPUParticlesEditor::_menu_option(int p_option) {
+
+	switch (p_option) {
+
+		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: {
+
+			emission_file_dialog->popup_centered_ratio();
+
+		} break;
+
+		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
+
+			emission_tree_dialog->popup_centered_ratio();
+
+		} break;
+	}
+}
+
+void CPUParticlesEditor::edit(CPUParticles *p_particles) {
+
+	base_node = p_particles;
+	node = p_particles;
+}
+
+void CPUParticlesEditor::_generate_emission_points() {
+
+	/// hacer codigo aca
+	PoolVector<Vector3> points;
+	PoolVector<Vector3> normals;
+
+	if (!_generate(points, normals)) {
+		return;
+	}
+
+	if (normals.size() == 0) {
+		node->set_emission_shape(CPUParticles::EMISSION_SHAPE_POINTS);
+		node->set_emission_points(points);
+	} else {
+		node->set_emission_shape(CPUParticles::EMISSION_SHAPE_DIRECTED_POINTS);
+		node->set_emission_points(points);
+		node->set_emission_normals(normals);
+	}
+}
+
+void CPUParticlesEditor::_bind_methods() {
+
+	ClassDB::bind_method("_menu_option", &CPUParticlesEditor::_menu_option);
+}
+
+CPUParticlesEditor::CPUParticlesEditor() {
+
+	particles_editor_hb = memnew(HBoxContainer);
+	SpatialEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
+	options = memnew(MenuButton);
+	particles_editor_hb->add_child(options);
+	particles_editor_hb->hide();
+
+	options->set_text(TTR("CPUParticles"));
+	options->get_popup()->add_item(TTR("Create Emission Points From Mesh"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH);
+	options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
+	options->get_popup()->connect("id_pressed", this, "_menu_option");
+}
+
+void CPUParticlesEditorPlugin::edit(Object *p_object) {
+
+	particles_editor->edit(Object::cast_to<CPUParticles>(p_object));
+}
+
+bool CPUParticlesEditorPlugin::handles(Object *p_object) const {
+
+	return p_object->is_class("CPUParticles");
+}
+
+void CPUParticlesEditorPlugin::make_visible(bool p_visible) {
+
+	if (p_visible) {
+		particles_editor->show();
+		particles_editor->particles_editor_hb->show();
+	} else {
+		particles_editor->particles_editor_hb->hide();
+		particles_editor->hide();
+		particles_editor->edit(NULL);
+	}
+}
+
+CPUParticlesEditorPlugin::CPUParticlesEditorPlugin(EditorNode *p_node) {
+
+	editor = p_node;
+	particles_editor = memnew(CPUParticlesEditor);
+	editor->get_viewport()->add_child(particles_editor);
+
+	particles_editor->hide();
+}
+
+CPUParticlesEditorPlugin::~CPUParticlesEditorPlugin() {
+}

+ 55 - 0
editor/plugins/cpu_particles_editor_plugin.h

@@ -0,0 +1,55 @@
+#ifndef CPU_PARTICLES_EDITOR_PLUGIN_H
+#define CPU_PARTICLES_EDITOR_PLUGIN_H
+
+#include "editor/plugins/particles_editor_plugin.h"
+#include "scene/3d/cpu_particles.h"
+
+class CPUParticlesEditor : public ParticlesEditorBase {
+
+	GDCLASS(CPUParticlesEditor, ParticlesEditorBase);
+
+	enum Menu {
+
+		MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
+		MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH,
+		MENU_OPTION_CLEAR_EMISSION_VOLUME,
+
+	};
+
+	CPUParticles *node;
+
+	void _menu_option(int);
+
+	friend class CPUParticlesEditorPlugin;
+
+	virtual void _generate_emission_points();
+
+protected:
+	void _notification(int p_notification);
+	void _node_removed(Node *p_node);
+	static void _bind_methods();
+
+public:
+	void edit(CPUParticles *p_particles);
+	CPUParticlesEditor();
+};
+
+class CPUParticlesEditorPlugin : public EditorPlugin {
+
+	GDCLASS(CPUParticlesEditorPlugin, EditorPlugin);
+
+	CPUParticlesEditor *particles_editor;
+	EditorNode *editor;
+
+public:
+	virtual String get_name() const { return "CPUParticles"; }
+	bool has_main_screen() const { return false; }
+	virtual void edit(Object *p_object);
+	virtual bool handles(Object *p_object) const;
+	virtual void make_visible(bool p_visible);
+
+	CPUParticlesEditorPlugin(EditorNode *p_node);
+	~CPUParticlesEditorPlugin();
+};
+
+#endif // CPU_PARTICLES_EDITOR_PLUGIN_H

+ 206 - 184
editor/plugins/particles_editor_plugin.cpp

@@ -32,129 +32,9 @@
 #include "editor/plugins/spatial_editor_plugin.h"
 #include "io/resource_loader.h"
 
-void ParticlesEditor::_node_removed(Node *p_node) {
-
-	if (p_node == node) {
-		node = NULL;
-		hide();
-	}
-}
-
-void ParticlesEditor::_node_selected(const NodePath &p_path) {
-
-	Node *sel = get_node(p_path);
-	if (!sel)
-		return;
-
-	VisualInstance *vi = Object::cast_to<VisualInstance>(sel);
-	if (!vi) {
-
-		err_dialog->set_text(TTR("Node does not contain geometry."));
-		err_dialog->popup_centered_minsize();
-		return;
-	}
-
-	geometry = vi->get_faces(VisualInstance::FACES_SOLID);
-
-	if (geometry.size() == 0) {
-
-		err_dialog->set_text(TTR("Node does not contain geometry (faces)."));
-		err_dialog->popup_centered_minsize();
-		return;
-	}
-
-	Transform geom_xform = node->get_global_transform().affine_inverse() * vi->get_global_transform();
-
-	int gc = geometry.size();
-	PoolVector<Face3>::Write w = geometry.write();
-
-	for (int i = 0; i < gc; i++) {
-		for (int j = 0; j < 3; j++) {
-			w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
-		}
-	}
-
-	w = PoolVector<Face3>::Write();
-
-	emission_dialog->popup_centered(Size2(300, 130));
-}
-
-void ParticlesEditor::_notification(int p_notification) {
-
-	if (p_notification == NOTIFICATION_ENTER_TREE) {
-		options->set_icon(options->get_popup()->get_icon("Particles", "EditorIcons"));
-	}
-}
-
-void ParticlesEditor::_menu_option(int p_option) {
-
-	switch (p_option) {
-
-		case MENU_OPTION_GENERATE_AABB: {
-			generate_aabb->popup_centered_minsize();
-		} break;
-		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: {
-
-			Ref<ParticlesMaterial> material = node->get_process_material();
-			if (material.is_null()) {
-				EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required."));
-				return;
-			}
-			emission_file_dialog->popup_centered_ratio();
-
-		} break;
-
-		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
-			Ref<ParticlesMaterial> material = node->get_process_material();
-			if (material.is_null()) {
-				EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required."));
-				return;
-			}
-
-			emission_tree_dialog->popup_centered_ratio();
-
-		} break;
-	}
-}
-
-void ParticlesEditor::_generate_aabb() {
-
-	float time = generate_seconds->get_value();
-
-	float running = 0.0;
-
-	EditorProgress ep("gen_aabb", TTR("Generating AABB"), int(time));
-
-	AABB 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);
-
-		AABB capture = node->capture_aabb();
-		if (rect == AABB())
-			rect = capture;
-		else
-			rect.merge_with(capture);
-
-		running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
-	}
-
-	node->set_visibility_aabb(rect);
-}
+bool ParticlesEditorBase::_generate(PoolVector<Vector3> &points, PoolVector<Vector3> &normals) {
 
-void ParticlesEditor::edit(Particles *p_particles) {
-
-	node = p_particles;
-}
-
-void ParticlesEditor::_generate_emission_points() {
-
-	/// hacer codigo aca
-	PoolVector<float> points;
 	bool use_normals = emission_fill->get_selected() == 1;
-	PoolVector<float> normals;
 
 	if (emission_fill->get_selected() < 2) {
 
@@ -175,7 +55,7 @@ void ParticlesEditor::_generate_emission_points() {
 
 			err_dialog->set_text(TTR("Faces contain no area!"));
 			err_dialog->popup_centered_minsize();
-			return;
+			return false;
 		}
 
 		int emissor_count = emission_amount->get_value();
@@ -185,9 +65,9 @@ void ParticlesEditor::_generate_emission_points() {
 			float areapos = Math::random(0.0f, area_accum);
 
 			Map<float, int>::Element *E = triangle_area_map.find_closest(areapos);
-			ERR_FAIL_COND(!E)
+			ERR_FAIL_COND_V(!E, false)
 			int index = E->get();
-			ERR_FAIL_INDEX(index, geometry.size());
+			ERR_FAIL_INDEX_V(index, geometry.size(), false);
 
 			// ok FINALLY get face
 			Face3 face = geometry[index];
@@ -195,15 +75,11 @@ void ParticlesEditor::_generate_emission_points() {
 
 			Vector3 pos = face.get_random_point_inside();
 
-			points.push_back(pos.x);
-			points.push_back(pos.y);
-			points.push_back(pos.z);
+			points.push_back(pos);
 
 			if (use_normals) {
 				Vector3 normal = face.get_plane().normal;
-				normals.push_back(normal.x);
-				normals.push_back(normal.y);
-				normals.push_back(normal.z);
+				normals.push_back(normal);
 			}
 		}
 	} else {
@@ -214,7 +90,7 @@ void ParticlesEditor::_generate_emission_points() {
 
 			err_dialog->set_text(TTR("No faces!"));
 			err_dialog->popup_centered_minsize();
-			return;
+			return false;
 		}
 
 		PoolVector<Face3>::Read r = geometry.read();
@@ -276,15 +152,195 @@ void ParticlesEditor::_generate_emission_points() {
 
 				Vector3 point = ofs + dir * val;
 
-				points.push_back(point.x);
-				points.push_back(point.y);
-				points.push_back(point.z);
+				points.push_back(point);
 				break;
 			}
 		}
 	}
 
-	int point_count = points.size() / 3;
+	return true;
+}
+
+void ParticlesEditorBase::_node_selected(const NodePath &p_path) {
+
+	Node *sel = get_node(p_path);
+	if (!sel)
+		return;
+
+	VisualInstance *vi = Object::cast_to<VisualInstance>(sel);
+	if (!vi) {
+
+		err_dialog->set_text(TTR("Node does not contain geometry."));
+		err_dialog->popup_centered_minsize();
+		return;
+	}
+
+	geometry = vi->get_faces(VisualInstance::FACES_SOLID);
+
+	if (geometry.size() == 0) {
+
+		err_dialog->set_text(TTR("Node does not contain geometry (faces)."));
+		err_dialog->popup_centered_minsize();
+		return;
+	}
+
+	Transform geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform();
+
+	int gc = geometry.size();
+	PoolVector<Face3>::Write w = geometry.write();
+
+	for (int i = 0; i < gc; i++) {
+		for (int j = 0; j < 3; j++) {
+			w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
+		}
+	}
+
+	w = PoolVector<Face3>::Write();
+
+	emission_dialog->popup_centered(Size2(300, 130));
+}
+
+void ParticlesEditorBase::_bind_methods() {
+
+	ClassDB::bind_method("_node_selected", &ParticlesEditorBase::_node_selected);
+	ClassDB::bind_method("_generate_emission_points", &ParticlesEditorBase::_generate_emission_points);
+}
+
+ParticlesEditorBase::ParticlesEditorBase() {
+
+	emission_dialog = memnew(ConfirmationDialog);
+	emission_dialog->set_title(TTR("Create Emitter"));
+	add_child(emission_dialog);
+	VBoxContainer *emd_vb = memnew(VBoxContainer);
+	emission_dialog->add_child(emd_vb);
+
+	emission_amount = memnew(SpinBox);
+	emission_amount->set_min(1);
+	emission_amount->set_max(100000);
+	emission_amount->set_value(512);
+	emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
+
+	emission_fill = memnew(OptionButton);
+	emission_fill->add_item(TTR("Surface Points"));
+	emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
+	emission_fill->add_item(TTR("Volume"));
+	emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill);
+
+	emission_dialog->get_ok()->set_text(TTR("Create"));
+	emission_dialog->connect("confirmed", this, "_generate_emission_points");
+
+	err_dialog = memnew(ConfirmationDialog);
+	add_child(err_dialog);
+
+	emission_file_dialog = memnew(EditorFileDialog);
+	add_child(emission_file_dialog);
+	emission_file_dialog->connect("file_selected", this, "_resource_seleted");
+	emission_tree_dialog = memnew(SceneTreeDialog);
+	add_child(emission_tree_dialog);
+	emission_tree_dialog->connect("selected", this, "_node_selected");
+
+	List<String> extensions;
+	ResourceLoader::get_recognized_extensions_for_type("Mesh", &extensions);
+
+	emission_file_dialog->clear_filters();
+	for (int i = 0; i < extensions.size(); i++) {
+
+		emission_file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
+	}
+
+	emission_file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
+}
+
+void ParticlesEditor::_node_removed(Node *p_node) {
+
+	if (p_node == node) {
+		node = NULL;
+		hide();
+	}
+}
+
+void ParticlesEditor::_notification(int p_notification) {
+
+	if (p_notification == NOTIFICATION_ENTER_TREE) {
+		options->set_icon(options->get_popup()->get_icon("Particles", "EditorIcons"));
+	}
+}
+
+void ParticlesEditor::_menu_option(int p_option) {
+
+	switch (p_option) {
+
+		case MENU_OPTION_GENERATE_AABB: {
+			generate_aabb->popup_centered_minsize();
+		} break;
+		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_MESH: {
+
+			Ref<ParticlesMaterial> material = node->get_process_material();
+			if (material.is_null()) {
+				EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required."));
+				return;
+			}
+			emission_file_dialog->popup_centered_ratio();
+
+		} break;
+
+		case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
+			Ref<ParticlesMaterial> material = node->get_process_material();
+			if (material.is_null()) {
+				EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticlesMaterial' is required."));
+				return;
+			}
+
+			emission_tree_dialog->popup_centered_ratio();
+
+		} break;
+	}
+}
+
+void ParticlesEditor::_generate_aabb() {
+
+	float time = generate_seconds->get_value();
+
+	float running = 0.0;
+
+	EditorProgress ep("gen_aabb", TTR("Generating AABB"), int(time));
+
+	AABB 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);
+
+		AABB capture = node->capture_aabb();
+		if (rect == AABB())
+			rect = capture;
+		else
+			rect.merge_with(capture);
+
+		running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+	}
+
+	node->set_visibility_aabb(rect);
+}
+
+void ParticlesEditor::edit(Particles *p_particles) {
+
+	base_node = p_particles;
+	node = p_particles;
+}
+
+void ParticlesEditor::_generate_emission_points() {
+
+	/// hacer codigo aca
+	PoolVector<Vector3> points;
+	PoolVector<Vector3> normals;
+
+	if (!_generate(points, normals)) {
+		return;
+	}
+
+	int point_count = points.size();
 
 	int w = 2048;
 	int h = (point_count / 2048) + 1;
@@ -295,8 +351,13 @@ void ParticlesEditor::_generate_emission_points() {
 	{
 		PoolVector<uint8_t>::Write iw = point_img.write();
 		zeromem(iw.ptr(), w * h * 3 * sizeof(float));
-		PoolVector<float>::Read r = points.read();
-		copymem(iw.ptr(), r.ptr(), point_count * sizeof(float) * 3);
+		PoolVector<Vector3>::Read r = points.read();
+		float *wf = (float *)iw.ptr();
+		for (int i = 0; i < point_count; i++) {
+			wf[i * 3 + 0] = r[i].x;
+			wf[i * 3 + 1] = r[i].y;
+			wf[i * 3 + 2] = r[i].z;
+		}
 	}
 
 	Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
@@ -308,7 +369,7 @@ void ParticlesEditor::_generate_emission_points() {
 	Ref<ParticlesMaterial> material = node->get_process_material();
 	ERR_FAIL_COND(material.is_null());
 
-	if (use_normals) {
+	if (normals.size() > 0) {
 
 		material->set_emission_shape(ParticlesMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
 		material->set_emission_point_count(point_count);
@@ -320,8 +381,13 @@ void ParticlesEditor::_generate_emission_points() {
 		{
 			PoolVector<uint8_t>::Write iw = point_img2.write();
 			zeromem(iw.ptr(), w * h * 3 * sizeof(float));
-			PoolVector<float>::Read r = normals.read();
-			copymem(iw.ptr(), r.ptr(), point_count * sizeof(float) * 3);
+			PoolVector<Vector3>::Read r = normals.read();
+			float *wf = (float *)iw.ptr();
+			for (int i = 0; i < point_count; i++) {
+				wf[i * 3 + 0] = r[i].x;
+				wf[i * 3 + 1] = r[i].y;
+				wf[i * 3 + 2] = r[i].z;
+			}
 		}
 
 		Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
@@ -342,8 +408,6 @@ void ParticlesEditor::_generate_emission_points() {
 void ParticlesEditor::_bind_methods() {
 
 	ClassDB::bind_method("_menu_option", &ParticlesEditor::_menu_option);
-	ClassDB::bind_method("_node_selected", &ParticlesEditor::_node_selected);
-	ClassDB::bind_method("_generate_emission_points", &ParticlesEditor::_generate_emission_points);
 	ClassDB::bind_method("_generate_aabb", &ParticlesEditor::_generate_aabb);
 }
 
@@ -362,48 +426,6 @@ ParticlesEditor::ParticlesEditor() {
 	options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
 	options->get_popup()->connect("id_pressed", this, "_menu_option");
 
-	emission_dialog = memnew(ConfirmationDialog);
-	emission_dialog->set_title(TTR("Create Emitter"));
-	add_child(emission_dialog);
-	VBoxContainer *emd_vb = memnew(VBoxContainer);
-	emission_dialog->add_child(emd_vb);
-
-	emission_amount = memnew(SpinBox);
-	emission_amount->set_min(1);
-	emission_amount->set_max(100000);
-	emission_amount->set_value(512);
-	emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
-
-	emission_fill = memnew(OptionButton);
-	emission_fill->add_item(TTR("Surface Points"));
-	emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
-	emission_fill->add_item(TTR("Volume"));
-	emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill);
-
-	emission_dialog->get_ok()->set_text(TTR("Create"));
-	emission_dialog->connect("confirmed", this, "_generate_emission_points");
-
-	err_dialog = memnew(ConfirmationDialog);
-	add_child(err_dialog);
-
-	emission_file_dialog = memnew(EditorFileDialog);
-	add_child(emission_file_dialog);
-	emission_file_dialog->connect("file_selected", this, "_resource_seleted");
-	emission_tree_dialog = memnew(SceneTreeDialog);
-	add_child(emission_tree_dialog);
-	emission_tree_dialog->connect("selected", this, "_node_selected");
-
-	List<String> extensions;
-	ResourceLoader::get_recognized_extensions_for_type("Mesh", &extensions);
-
-	emission_file_dialog->clear_filters();
-	for (int i = 0; i < extensions.size(); i++) {
-
-		emission_file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
-	}
-
-	emission_file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
-
 	generate_aabb = memnew(ConfirmationDialog);
 	generate_aabb->set_title(TTR("Generate Visibility AABB"));
 	VBoxContainer *genvb = memnew(VBoxContainer);

+ 23 - 10
editor/plugins/particles_editor_plugin.h

@@ -40,14 +40,14 @@
 	@author Juan Linietsky <[email protected]>
 */
 
-class ParticlesEditor : public Control {
-
-	GDCLASS(ParticlesEditor, Control);
+class ParticlesEditorBase : public Control {
 
+	GDCLASS(ParticlesEditorBase, Control)
+protected:
+	Spatial *base_node;
 	Panel *panel;
 	MenuButton *options;
 	HBoxContainer *particles_editor_hb;
-	Particles *node;
 
 	EditorFileDialog *emission_file_dialog;
 	SceneTreeDialog *emission_tree_dialog;
@@ -58,8 +58,25 @@ class ParticlesEditor : public Control {
 	SpinBox *emission_amount;
 	OptionButton *emission_fill;
 
+	PoolVector<Face3> geometry;
+
+	bool _generate(PoolVector<Vector3> &points, PoolVector<Vector3> &normals);
+	virtual void _generate_emission_points() = 0;
+	void _node_selected(const NodePath &p_path);
+
+	static void _bind_methods();
+
+public:
+	ParticlesEditorBase();
+};
+
+class ParticlesEditor : public ParticlesEditorBase {
+
+	GDCLASS(ParticlesEditor, ParticlesEditorBase);
+
 	ConfirmationDialog *generate_aabb;
 	SpinBox *generate_seconds;
+	Particles *node;
 
 	enum Menu {
 
@@ -70,18 +87,14 @@ class ParticlesEditor : public Control {
 
 	};
 
-	PoolVector<Face3> geometry;
-
 	void _generate_aabb();
-	void _generate_emission_points();
-	void _node_selected(const NodePath &p_path);
 
 	void _menu_option(int);
 
-	void _populate();
-
 	friend class ParticlesEditorPlugin;
 
+	virtual void _generate_emission_points();
+
 protected:
 	void _notification(int p_notification);
 	void _node_removed(Node *p_node);

+ 1338 - 0
scene/3d/cpu_particles.cpp

@@ -0,0 +1,1338 @@
+#include "cpu_particles.h"
+
+#include "scene/3d/camera.h"
+#include "scene/main/viewport.h"
+#include "scene/resources/surface_tool.h"
+#include "servers/visual_server.h"
+
+AABB CPUParticles::get_aabb() const {
+
+	return AABB();
+}
+PoolVector<Face3> CPUParticles::get_faces(uint32_t p_usage_flags) const {
+
+	return PoolVector<Face3>();
+}
+
+void CPUParticles::set_emitting(bool p_emitting) {
+
+	emitting = p_emitting;
+	if (!is_processing_internal()) {
+		set_process_internal(true);
+		if (is_inside_tree()) {
+#ifndef NO_THREADS
+			update_mutex->lock();
+#endif
+			VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread");
+#ifndef NO_THREADS
+			update_mutex->unlock();
+#endif
+		}
+	}
+}
+
+void CPUParticles::set_amount(int p_amount) {
+
+	ERR_FAIL_COND(p_amount < 1);
+
+	particles.resize(p_amount);
+	{
+		PoolVector<Particle>::Write w = particles.write();
+
+		for (int i = 0; i < p_amount; i++) {
+			w[i].active = false;
+		}
+	}
+
+	particle_data.resize((12 + 4 + 1) * p_amount);
+	VS::get_singleton()->multimesh_allocate(multimesh, p_amount, VS::MULTIMESH_TRANSFORM_3D, VS::MULTIMESH_COLOR_8BIT, VS::MULTIMESH_CUSTOM_DATA_FLOAT);
+
+	particle_order.resize(p_amount);
+}
+void CPUParticles::set_lifetime(float p_lifetime) {
+
+	ERR_FAIL_COND(p_lifetime <= 0);
+	lifetime = p_lifetime;
+}
+
+void CPUParticles::set_one_shot(bool p_one_shot) {
+
+	one_shot = p_one_shot;
+}
+
+void CPUParticles::set_pre_process_time(float p_time) {
+
+	pre_process_time = p_time;
+}
+void CPUParticles::set_explosiveness_ratio(float p_ratio) {
+
+	explosiveness_ratio = p_ratio;
+}
+void CPUParticles::set_randomness_ratio(float p_ratio) {
+
+	randomness_ratio = p_ratio;
+}
+void CPUParticles::set_use_local_coordinates(bool p_enable) {
+
+	local_coords = p_enable;
+}
+void CPUParticles::set_speed_scale(float p_scale) {
+
+	speed_scale = p_scale;
+}
+
+bool CPUParticles::is_emitting() const {
+
+	return emitting;
+}
+int CPUParticles::get_amount() const {
+
+	return particles.size();
+}
+float CPUParticles::get_lifetime() const {
+
+	return lifetime;
+}
+bool CPUParticles::get_one_shot() const {
+
+	return one_shot;
+}
+
+float CPUParticles::get_pre_process_time() const {
+
+	return pre_process_time;
+}
+float CPUParticles::get_explosiveness_ratio() const {
+
+	return explosiveness_ratio;
+}
+float CPUParticles::get_randomness_ratio() const {
+
+	return randomness_ratio;
+}
+
+bool CPUParticles::get_use_local_coordinates() const {
+
+	return local_coords;
+}
+
+float CPUParticles::get_speed_scale() const {
+
+	return speed_scale;
+}
+
+void CPUParticles::set_draw_order(DrawOrder p_order) {
+
+	draw_order = p_order;
+}
+
+CPUParticles::DrawOrder CPUParticles::get_draw_order() const {
+
+	return draw_order;
+}
+
+void CPUParticles::set_mesh(const Ref<Mesh> &p_mesh) {
+
+	mesh = p_mesh;
+	if (mesh.is_valid()) {
+		VS::get_singleton()->multimesh_set_mesh(multimesh, mesh->get_rid());
+	} else {
+		VS::get_singleton()->multimesh_set_mesh(multimesh, RID());
+	}
+}
+
+Ref<Mesh> CPUParticles::get_mesh() const {
+
+	return mesh;
+}
+
+void CPUParticles::set_fixed_fps(int p_count) {
+	fixed_fps = p_count;
+}
+
+int CPUParticles::get_fixed_fps() const {
+	return fixed_fps;
+}
+
+void CPUParticles::set_fractional_delta(bool p_enable) {
+	fractional_delta = p_enable;
+}
+
+bool CPUParticles::get_fractional_delta() const {
+	return fractional_delta;
+}
+
+String CPUParticles::get_configuration_warning() const {
+
+	String warnings;
+
+	return warnings;
+}
+
+void CPUParticles::restart() {
+
+	time = 0;
+	inactive_time = 0;
+	frame_remainder = 0;
+	cycle = 0;
+
+	{
+		int pc = particles.size();
+		PoolVector<Particle>::Write w = particles.write();
+
+		for (int i = 0; i < pc; i++) {
+			w[i].active = false;
+		}
+	}
+}
+
+void CPUParticles::set_spread(float p_spread) {
+
+	spread = p_spread;
+}
+
+float CPUParticles::get_spread() const {
+
+	return spread;
+}
+
+void CPUParticles::set_flatness(float p_flatness) {
+
+	flatness = p_flatness;
+}
+float CPUParticles::get_flatness() const {
+
+	return flatness;
+}
+
+void CPUParticles::set_param(Parameter p_param, float p_value) {
+
+	ERR_FAIL_INDEX(p_param, PARAM_MAX);
+
+	parameters[p_param] = p_value;
+}
+float CPUParticles::get_param(Parameter p_param) const {
+
+	ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
+
+	return parameters[p_param];
+}
+
+void CPUParticles::set_param_randomness(Parameter p_param, float p_value) {
+
+	ERR_FAIL_INDEX(p_param, PARAM_MAX);
+
+	randomness[p_param] = p_value;
+}
+float CPUParticles::get_param_randomness(Parameter p_param) const {
+
+	ERR_FAIL_INDEX_V(p_param, PARAM_MAX, 0);
+
+	return randomness[p_param];
+}
+
+static void _adjust_curve_range(const Ref<Curve> &p_curve, float p_min, float p_max) {
+
+	Ref<Curve> curve = p_curve;
+	if (!curve.is_valid())
+		return;
+
+	curve->ensure_default_setup(p_min, p_max);
+}
+
+void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) {
+
+	ERR_FAIL_INDEX(p_param, PARAM_MAX);
+
+	curve_parameters[p_param] = p_curve;
+
+	switch (p_param) {
+		case PARAM_INITIAL_LINEAR_VELOCITY: {
+			//do none for this one
+		} break;
+		case PARAM_ANGULAR_VELOCITY: {
+			_adjust_curve_range(p_curve, -360, 360);
+		} break;
+		/*case PARAM_ORBIT_VELOCITY: {
+			_adjust_curve_range(p_curve, -500, 500);
+		} break;*/
+		case PARAM_LINEAR_ACCEL: {
+			_adjust_curve_range(p_curve, -200, 200);
+		} break;
+		case PARAM_RADIAL_ACCEL: {
+			_adjust_curve_range(p_curve, -200, 200);
+		} break;
+		case PARAM_TANGENTIAL_ACCEL: {
+			_adjust_curve_range(p_curve, -200, 200);
+		} break;
+		case PARAM_DAMPING: {
+			_adjust_curve_range(p_curve, 0, 100);
+		} break;
+		case PARAM_ANGLE: {
+			_adjust_curve_range(p_curve, -360, 360);
+		} break;
+		case PARAM_SCALE: {
+
+		} break;
+		case PARAM_HUE_VARIATION: {
+			_adjust_curve_range(p_curve, -1, 1);
+		} break;
+		case PARAM_ANIM_SPEED: {
+			_adjust_curve_range(p_curve, 0, 200);
+		} break;
+		case PARAM_ANIM_OFFSET: {
+		} break;
+		default: {}
+	}
+}
+Ref<Curve> CPUParticles::get_param_curve(Parameter p_param) const {
+
+	ERR_FAIL_INDEX_V(p_param, PARAM_MAX, Ref<Curve>());
+
+	return curve_parameters[p_param];
+}
+
+void CPUParticles::set_color(const Color &p_color) {
+
+	color = p_color;
+}
+
+Color CPUParticles::get_color() const {
+
+	return color;
+}
+
+void CPUParticles::set_color_ramp(const Ref<Gradient> &p_ramp) {
+
+	color_ramp = p_ramp;
+}
+
+Ref<Gradient> CPUParticles::get_color_ramp() const {
+
+	return color_ramp;
+}
+
+void CPUParticles::set_particle_flag(Flags p_flag, bool p_enable) {
+	ERR_FAIL_INDEX(p_flag, FLAG_MAX);
+	flags[p_flag] = p_enable;
+	if (p_flag == FLAG_DISABLE_Z) {
+		_change_notify();
+	}
+}
+
+bool CPUParticles::get_particle_flag(Flags p_flag) const {
+	ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false);
+	return flags[p_flag];
+}
+
+void CPUParticles::set_emission_shape(EmissionShape p_shape) {
+
+	emission_shape = p_shape;
+}
+
+void CPUParticles::set_emission_sphere_radius(float p_radius) {
+
+	emission_sphere_radius = p_radius;
+}
+
+void CPUParticles::set_emission_box_extents(Vector3 p_extents) {
+
+	emission_box_extents = p_extents;
+}
+
+void CPUParticles::set_emission_points(const PoolVector<Vector3> &p_points) {
+
+	emission_points = p_points;
+}
+
+void CPUParticles::set_emission_normals(const PoolVector<Vector3> &p_normals) {
+
+	emission_normals = p_normals;
+}
+
+void CPUParticles::set_emission_colors(const PoolVector<Color> &p_colors) {
+
+	emission_colors = p_colors;
+}
+
+float CPUParticles::get_emission_sphere_radius() const {
+
+	return emission_sphere_radius;
+}
+Vector3 CPUParticles::get_emission_box_extents() const {
+
+	return emission_box_extents;
+}
+PoolVector<Vector3> CPUParticles::get_emission_points() const {
+
+	return emission_points;
+}
+PoolVector<Vector3> CPUParticles::get_emission_normals() const {
+
+	return emission_normals;
+}
+
+PoolVector<Color> CPUParticles::get_emission_colors() const {
+
+	return emission_colors;
+}
+
+CPUParticles::EmissionShape CPUParticles::get_emission_shape() const {
+	return emission_shape;
+}
+void CPUParticles::set_gravity(const Vector3 &p_gravity) {
+
+	gravity = p_gravity;
+}
+
+Vector3 CPUParticles::get_gravity() const {
+
+	return gravity;
+}
+
+void CPUParticles::_validate_property(PropertyInfo &property) const {
+
+	if (property.name == "color" && color_ramp.is_valid()) {
+		property.usage = 0;
+	}
+
+	if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) {
+		property.usage = 0;
+	}
+
+	if (property.name == "emission_box_extents" && emission_shape != EMISSION_SHAPE_BOX) {
+		property.usage = 0;
+	}
+
+	if ((property.name == "emission_point_texture" || property.name == "emission_color_texture") && (emission_shape < EMISSION_SHAPE_POINTS)) {
+		property.usage = 0;
+	}
+
+	if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) {
+		property.usage = 0;
+	}
+	/*
+	if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) {
+		property.usage = 0;
+	}
+	*/
+}
+
+static uint32_t idhash(uint32_t x) {
+
+	x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b);
+	x = ((x >> uint32_t(16)) ^ x) * uint32_t(0x45d9f3b);
+	x = (x >> uint32_t(16)) ^ x;
+	return x;
+}
+
+static float rand_from_seed(uint32_t &seed) {
+	int k;
+	int s = int(seed);
+	if (s == 0)
+		s = 305420679;
+	k = s / 127773;
+	s = 16807 * (s - k * 127773) - 2836 * k;
+	if (s < 0)
+		s += 2147483647;
+	seed = uint32_t(s);
+	return float(seed % uint32_t(65536)) / 65535.0;
+}
+
+float rand_from_seed_m1_p1(uint32_t &seed) {
+	return rand_from_seed(seed) * 2.0 - 1.0;
+}
+
+void CPUParticles::_particles_process(float p_delta) {
+
+	int pcount = particles.size();
+	PoolVector<Particle>::Write w = particles.write();
+
+	Particle *parray = w.ptr();
+
+	float prev_time = time;
+	time += p_delta;
+	if (time > lifetime) {
+		time = Math::fmod(time, lifetime);
+		cycle++;
+		if (one_shot && cycle > 0) {
+			emitting = false;
+		}
+	}
+
+	Transform emission_xform;
+	Basis velocity_xform;
+	if (!local_coords) {
+		emission_xform = get_global_transform();
+		velocity_xform = emission_xform.basis.inverse().transposed();
+	}
+
+	for (int i = 0; i < pcount; i++) {
+
+		Particle &p = parray[i];
+
+		if (!emitting && !p.active)
+			continue;
+
+		float restart_time = float(i) / float(pcount);
+		float local_delta = p_delta;
+
+		if (randomness_ratio > 0.0) {
+			uint32_t seed = cycle;
+			if (restart_time >= time) {
+				seed -= uint32_t(1);
+			}
+			seed *= uint32_t(pcount);
+			seed += uint32_t(i);
+			float random = float(idhash(seed) % uint32_t(65536)) / 65536.0;
+			restart_time += randomness_ratio * random * 1.0 / float(pcount);
+		}
+
+		restart_time *= (1.0 - explosiveness_ratio);
+		bool restart = false;
+
+		if (time > prev_time) {
+			// restart_time >= prev_time is used so particles emit in the first frame they are processed
+
+			if (restart_time >= prev_time && restart_time < time) {
+				restart = true;
+				if (fractional_delta) {
+					local_delta = (time - restart_time) * lifetime;
+				}
+			}
+
+		} else if (local_delta > 0.0) {
+			if (restart_time >= prev_time) {
+				restart = true;
+				if (fractional_delta) {
+					local_delta = (1.0 - restart_time + time) * lifetime;
+				}
+
+			} else if (restart_time < time) {
+				restart = true;
+				if (fractional_delta) {
+					local_delta = (time - restart_time) * lifetime;
+				}
+			}
+		}
+
+		if (restart) {
+
+			if (!emitting) {
+				p.active = false;
+				continue;
+			}
+			p.active = true;
+
+			/*float tex_linear_velocity = 0;
+			if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
+				tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(0);
+			}*/
+
+			float tex_angle = 0.0;
+			if (curve_parameters[PARAM_ANGLE].is_valid()) {
+				tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(0);
+			}
+
+			float tex_anim_offset = 0.0;
+			if (curve_parameters[PARAM_ANGLE].is_valid()) {
+				tex_anim_offset = curve_parameters[PARAM_ANGLE]->interpolate(0);
+			}
+
+			p.seed = Math::rand();
+
+			p.angle_rand = Math::randf();
+			p.scale_rand = Math::randf();
+			p.hue_rot_rand = Math::randf();
+			p.anim_offset_rand = Math::randf();
+
+			float angle1_rad;
+			float angle2_rad;
+
+			if (flags[FLAG_DISABLE_Z]) {
+
+				angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0;
+				Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0);
+				p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]);
+
+			} else {
+				//initiate velocity spread in 3D
+				angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0;
+				angle2_rad = (Math::randf() * 2.0 - 1.0) * (1.0 - flatness) * Math_PI * spread / 180.0;
+
+				Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad));
+				Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad));
+				direction_yz.z = direction_yz.z / Math::sqrt(direction_yz.z); //better uniform distribution
+				Vector3 direction = Vector3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);
+				direction.normalize();
+				p.velocity = direction * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]);
+			}
+
+			float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]);
+			p.custom[0] = Math::deg2rad(base_angle); //angle
+			p.custom[1] = 0.0; //phase
+			p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1)
+			p.transform = Transform();
+			p.time = 0;
+			p.base_color = Color(1, 1, 1, 1);
+
+			switch (emission_shape) {
+				case EMISSION_SHAPE_POINT: {
+					//do none
+				} break;
+				case EMISSION_SHAPE_SPHERE: {
+					p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0).normalized() * emission_sphere_radius;
+				} break;
+				case EMISSION_SHAPE_BOX: {
+					p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_box_extents;
+				} break;
+				case EMISSION_SHAPE_POINTS:
+				case EMISSION_SHAPE_DIRECTED_POINTS: {
+
+					int pc = emission_points.size();
+					if (pc == 0)
+						break;
+
+					int random_idx = Math::rand() % pc;
+
+					p.transform.origin = emission_points.get(random_idx);
+
+					if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS && emission_normals.size() == pc) {
+						if (flags[FLAG_DISABLE_Z]) {
+							/*
+							mat2 rotm;
+							";
+									rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;
+							rotm[1] = rotm[0].yx * vec2(1.0, -1.0);
+							VELOCITY.xy = rotm * VELOCITY.xy;
+							*/
+						} else {
+							Vector3 normal = emission_normals.get(random_idx);
+							Vector3 v0 = Math::abs(normal.z) < 0.999 ? Vector3(0.0, 0.0, 1.0) : Vector3(0, 1.0, 0.0);
+							Vector3 tangent = v0.cross(normal).normalized();
+							Vector3 bitangent = tangent.cross(normal).normalized();
+							Basis m3;
+							m3.set_axis(0, tangent);
+							m3.set_axis(1, bitangent);
+							m3.set_axis(2, normal);
+							p.velocity = m3.xform(p.velocity);
+						}
+					}
+
+					if (emission_colors.size() == pc) {
+						p.base_color = emission_colors.get(random_idx);
+					}
+				} break;
+			}
+
+			if (!local_coords) {
+				p.velocity = velocity_xform.xform(p.velocity);
+				p.transform = emission_xform * p.transform;
+			}
+
+			if (flags[FLAG_DISABLE_Z]) {
+				p.velocity.z = 0.0;
+				p.velocity.z = 0.0;
+			}
+
+		} else if (!p.active) {
+			continue;
+		} else {
+
+			uint32_t alt_seed = p.seed;
+
+			p.time += local_delta;
+			p.custom[1] += p.time / lifetime;
+
+			float tex_linear_velocity = 0.0;
+			if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
+				tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]);
+			}
+			/*
+			float tex_orbit_velocity = 0.0;
+
+			if (flags[FLAG_DISABLE_Z]) {
+
+				if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) {
+					tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]);
+				}
+			}
+*/
+			float tex_angular_velocity = 0.0;
+			if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
+				tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]);
+			}
+
+			float tex_linear_accel = 0.0;
+			if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) {
+				tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(p.custom[1]);
+			}
+
+			float tex_tangential_accel = 0.0;
+			if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) {
+				tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(p.custom[1]);
+			}
+
+			float tex_radial_accel = 0.0;
+			if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) {
+				tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(p.custom[1]);
+			}
+
+			float tex_damping = 0.0;
+			if (curve_parameters[PARAM_DAMPING].is_valid()) {
+				tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(p.custom[1]);
+			}
+
+			float tex_angle = 0.0;
+			if (curve_parameters[PARAM_ANGLE].is_valid()) {
+				tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(p.custom[1]);
+			}
+			float tex_anim_speed = 0.0;
+			if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) {
+				tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(p.custom[1]);
+			}
+
+			float tex_anim_offset = 0.0;
+			if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) {
+				tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(p.custom[1]);
+			}
+
+			Vector3 force = gravity;
+			Vector3 pos = p.transform.origin;
+			if (flags[FLAG_DISABLE_Z]) {
+				pos.z = 0.0;
+			}
+			//apply linear acceleration
+			force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3();
+			//apply radial acceleration
+			Vector3 org = emission_xform.origin;
+			Vector3 diff = pos - org;
+			force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3();
+			//apply tangential acceleration;
+			if (flags[FLAG_DISABLE_Z]) {
+
+				Vector3 yx = Vector3(diff.y, 0, diff.x);
+				force += yx.length() > 0.0 ? (yx * Vector3(-1.0, 0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3();
+
+			} else {
+				Vector3 crossDiff = diff.normalized().cross(gravity.normalized());
+				force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3();
+			}
+			//apply attractor forces
+			p.velocity += force * local_delta;
+			//orbit velocity
+#if 0
+			if (flags[FLAG_DISABLE_Z]) {
+
+				float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random);
+				if (orbit_amount != 0.0) {
+					float ang = orbit_amount * DELTA * pi * 2.0;
+					mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));
+					TRANSFORM[3].xy -= diff.xy;
+					TRANSFORM[3].xy += rot * diff.xy;
+				}
+			}
+#endif
+			if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
+				p.velocity = p.velocity.normalized() * tex_linear_velocity;
+			}
+			if (parameters[PARAM_DAMPING] + tex_damping > 0.0) {
+
+				float v = p.velocity.length();
+				float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]);
+				v -= damp * local_delta;
+				if (v < 0.0) {
+					p.velocity = Vector3();
+				} else {
+					p.velocity = p.velocity.normalized() * v;
+				}
+			}
+			float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]);
+			base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]);
+			p.custom[0] = Math::deg2rad(base_angle); //angle
+			p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + p.custom[1] * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle
+			if (flags[FLAG_ANIM_LOOP]) {
+				p.custom[2] = Math::fmod(p.custom[2], 1.0f); //loop
+
+			} else {
+				p.custom[2] = CLAMP(p.custom[2], 0.0f, 1.0); //0 to 1 only
+			}
+		}
+		//apply color
+		//apply hue rotation
+
+		float tex_scale = 1.0;
+		if (curve_parameters[PARAM_SCALE].is_valid()) {
+			tex_scale = curve_parameters[PARAM_SCALE]->interpolate(p.custom[1]);
+		}
+
+		float tex_hue_variation = 0.0;
+		if (curve_parameters[PARAM_HUE_VARIATION].is_valid()) {
+			tex_hue_variation = curve_parameters[PARAM_HUE_VARIATION]->interpolate(p.custom[1]);
+		}
+
+		float hue_rot_angle = (parameters[PARAM_HUE_VARIATION] + tex_hue_variation) * Math_PI * 2.0 * Math::lerp(1.0f, p.hue_rot_rand * 2.0f - 1.0f, randomness[PARAM_HUE_VARIATION]);
+		float hue_rot_c = Math::cos(hue_rot_angle);
+		float hue_rot_s = Math::sin(hue_rot_angle);
+
+		Basis hue_rot_mat;
+		{
+			Basis mat1(0.299, 0.587, 0.114, 0.299, 0.587, 0.114, 0.299, 0.587, 0.114);
+			Basis mat2(0.701, -0.587, -0.114, -0.299, 0.413, -0.114, -0.300, -0.588, 0.886);
+			Basis mat3(0.168, 0.330, -0.497, -0.328, 0.035, 0.292, 1.250, -1.050, -0.203);
+
+			for (int j = 0; j < 3; j++) {
+				hue_rot_mat[j] = mat1[j] + mat2[j] * hue_rot_c + mat3[j] * hue_rot_s;
+			}
+		}
+
+		if (color_ramp.is_valid()) {
+			p.color = color_ramp->get_color_at_offset(p.custom[1]) * color;
+		} else {
+			p.color = color;
+		}
+
+		Vector3 color_rgb = hue_rot_mat.xform_inv(Vector3(p.color.r, p.color.g, p.color.b));
+		p.color.r = color_rgb.x;
+		p.color.g = color_rgb.y;
+		p.color.b = color_rgb.z;
+
+		p.color *= p.base_color;
+
+		if (flags[FLAG_DISABLE_Z]) {
+
+			if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) {
+				if (p.velocity.length() > 0.0) {
+					p.transform.basis.set_axis(1, p.velocity.normalized());
+				} else {
+					p.transform.basis.set_axis(1, p.transform.basis.get_axis(1));
+				}
+				p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized());
+				p.transform.basis.set_axis(2, Vector3(0, 0, 1));
+
+			} else {
+				p.transform.basis.set_axis(0, Vector3(Math::cos(p.custom[0]), -Math::sin(p.custom[0]), 0.0));
+				p.transform.basis.set_axis(1, Vector3(Math::sin(p.custom[0]), Math::cos(p.custom[0]), 0.0));
+				p.transform.basis.set_axis(2, Vector3(0, 0, 1));
+			}
+
+		} else {
+			//orient particle Y towards velocity
+			if (flags[FLAG_ALIGN_Y_TO_VELOCITY]) {
+				if (p.velocity.length() > 0.0) {
+					p.transform.basis.set_axis(1, p.velocity.normalized());
+				} else {
+					p.transform.basis.set_axis(1, p.transform.basis.get_axis(1).normalized());
+				}
+				if (p.transform.basis.get_axis(1) == p.transform.basis.get_axis(0)) {
+					p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized());
+					p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized());
+				} else {
+					p.transform.basis.set_axis(2, p.transform.basis.get_axis(0).cross(p.transform.basis.get_axis(1)).normalized());
+					p.transform.basis.set_axis(0, p.transform.basis.get_axis(1).cross(p.transform.basis.get_axis(2)).normalized());
+				}
+			} else {
+				p.transform.basis.orthonormalize();
+			}
+
+			//turn particle by rotation in Y
+			if (flags[FLAG_ROTATE_Y]) {
+				Basis rot_y(Vector3(0, 1, 0), p.custom[0]);
+				p.transform.basis = p.transform.basis * rot_y;
+			}
+		}
+
+		//scale by scale
+		float base_scale = Math::lerp(parameters[PARAM_SCALE] * tex_scale, 1.0f, p.scale_rand * randomness[PARAM_SCALE]);
+		if (base_scale == 0.0) base_scale = 0.000001;
+
+		p.transform.basis.scale(Vector3(1, 1, 1) * base_scale);
+
+		if (flags[FLAG_DISABLE_Z]) {
+			p.velocity.z = 0.0;
+			p.transform.origin.z = 0.0;
+		}
+
+		p.transform.origin += p.velocity * local_delta;
+	}
+}
+
+void CPUParticles::_update_particle_data_buffer() {
+#ifndef NO_THREADS
+	update_mutex->lock();
+#endif
+
+	{
+
+		int pc = particles.size();
+
+		PoolVector<int>::Write ow;
+		int *order = NULL;
+
+		PoolVector<float>::Write w = particle_data.write();
+		PoolVector<Particle>::Read r = particles.read();
+		float *ptr = w.ptr();
+
+		Transform un_transform;
+		if (!local_coords) {
+			un_transform = get_global_transform().affine_inverse();
+		}
+
+		if (draw_order != DRAW_ORDER_INDEX) {
+			ow = particle_order.write();
+			order = ow.ptr();
+
+			for (int i = 0; i < pc; i++) {
+				order[i] = i;
+			}
+			if (draw_order == DRAW_ORDER_LIFETIME) {
+				SortArray<int, SortLifetime> sorter;
+				sorter.compare.particles = r.ptr();
+				sorter.sort(order, pc);
+			} else if (draw_order == DRAW_ORDER_VIEW_DEPTH) {
+				Camera *c = get_viewport()->get_camera();
+				if (c) {
+					Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close
+
+					if (local_coords) {
+						dir = un_transform.basis.xform(dir).normalized();
+					}
+
+					SortArray<int, SortAxis> sorter;
+					sorter.compare.particles = r.ptr();
+					sorter.compare.axis = dir;
+					sorter.sort(order, pc);
+				}
+			}
+		}
+
+		for (int i = 0; i < pc; i++) {
+
+			int idx = order ? order[i] : i;
+
+			Transform t = r[idx].transform;
+
+			if (!local_coords) {
+				t = un_transform * t;
+			}
+
+			//			print_line(" particle " + itos(i) + ": " + String(r[idx].active ? "[x]" : "[ ]") + "\n\txform " + r[idx].transform + "\n\t" + r[idx].velocity + "\n\tcolor: " + r[idx].color);
+
+			if (r[idx].active) {
+				ptr[0] = t.basis.elements[0][0];
+				ptr[1] = t.basis.elements[0][1];
+				ptr[2] = t.basis.elements[0][2];
+				ptr[3] = t.origin.x;
+				ptr[4] = t.basis.elements[1][0];
+				ptr[5] = t.basis.elements[1][1];
+				ptr[6] = t.basis.elements[1][2];
+				ptr[7] = t.origin.y;
+				ptr[8] = t.basis.elements[2][0];
+				ptr[9] = t.basis.elements[2][1];
+				ptr[10] = t.basis.elements[2][2];
+				ptr[11] = t.origin.z;
+			} else {
+				zeromem(ptr, sizeof(float) * 12);
+			}
+
+			Color c = r[idx].color;
+			uint8_t *data8 = (uint8_t *)&ptr[12];
+			data8[0] = CLAMP(c.r * 255.0, 0, 255);
+			data8[1] = CLAMP(c.g * 255.0, 0, 255);
+			data8[2] = CLAMP(c.b * 255.0, 0, 255);
+			data8[3] = CLAMP(c.a * 255.0, 0, 255);
+
+			ptr[13] = r[idx].custom[0];
+			ptr[14] = r[idx].custom[1];
+			ptr[15] = r[idx].custom[2];
+			ptr[16] = r[idx].custom[3];
+
+			ptr += 17;
+		}
+	}
+
+#ifndef NO_THREADS
+	update_mutex->unlock();
+#endif
+}
+
+void CPUParticles::_update_render_thread() {
+
+#ifndef NO_THREADS
+	update_mutex->lock();
+#endif
+
+	VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
+
+#ifndef NO_THREADS
+	update_mutex->unlock();
+#endif
+}
+
+void CPUParticles::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_ENTER_TREE) {
+		if (is_processing_internal()) {
+
+#ifndef NO_THREADS
+			update_mutex->lock();
+#endif
+			VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread");
+#ifndef NO_THREADS
+			update_mutex->unlock();
+#endif
+		}
+	}
+
+	if (p_what == NOTIFICATION_EXIT_TREE) {
+		if (is_processing_internal()) {
+
+#ifndef NO_THREADS
+			update_mutex->lock();
+#endif
+			VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread");
+#ifndef NO_THREADS
+			update_mutex->unlock();
+#endif
+		}
+	}
+
+	if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) {
+	}
+
+	if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
+
+		if (particles.size() == 0)
+			return;
+
+		float delta = get_process_delta_time();
+		if (emitting) {
+
+			inactive_time = 0;
+		} else {
+			inactive_time += delta;
+			if (inactive_time > lifetime * 1.2) {
+				set_process_internal(false);
+#ifndef NO_THREADS
+				update_mutex->lock();
+#endif
+				VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread");
+#ifndef NO_THREADS
+				update_mutex->unlock();
+#endif
+				//reset variables
+				time = 0;
+				inactive_time = 0;
+				frame_remainder = 0;
+				cycle = 0;
+				return;
+			}
+		}
+
+		if (time == 0 && pre_process_time > 0.0) {
+
+			float frame_time;
+			if (fixed_fps > 0)
+				frame_time = 1.0 / fixed_fps;
+			else
+				frame_time = 1.0 / 30.0;
+
+			float todo = pre_process_time;
+
+			while (todo >= 0) {
+				_particles_process(frame_time);
+				todo -= frame_time;
+			}
+		}
+
+		if (fixed_fps > 0) {
+			float frame_time = 1.0 / fixed_fps;
+			float decr = frame_time;
+
+			float ldelta = delta;
+			if (ldelta > 0.1) { //avoid recursive stalls if fps goes below 10
+				ldelta = 0.1;
+			} else if (ldelta <= 0.0) { //unlikely but..
+				ldelta = 0.001;
+			}
+			float todo = frame_remainder + ldelta;
+
+			while (todo >= frame_time) {
+				_particles_process(frame_time);
+				todo -= decr;
+			}
+
+			frame_remainder = todo;
+
+		} else {
+			_particles_process(delta);
+		}
+
+		_update_particle_data_buffer();
+	}
+}
+
+void CPUParticles::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &CPUParticles::set_emitting);
+	ClassDB::bind_method(D_METHOD("set_amount", "amount"), &CPUParticles::set_amount);
+	ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &CPUParticles::set_lifetime);
+	ClassDB::bind_method(D_METHOD("set_one_shot", "enable"), &CPUParticles::set_one_shot);
+	ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles::set_pre_process_time);
+	ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles::set_explosiveness_ratio);
+	ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles::set_randomness_ratio);
+	ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles::set_use_local_coordinates);
+	ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles::set_fixed_fps);
+	ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &CPUParticles::set_fractional_delta);
+	ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &CPUParticles::set_speed_scale);
+
+	ClassDB::bind_method(D_METHOD("is_emitting"), &CPUParticles::is_emitting);
+	ClassDB::bind_method(D_METHOD("get_amount"), &CPUParticles::get_amount);
+	ClassDB::bind_method(D_METHOD("get_lifetime"), &CPUParticles::get_lifetime);
+	ClassDB::bind_method(D_METHOD("get_one_shot"), &CPUParticles::get_one_shot);
+	ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles::get_pre_process_time);
+	ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles::get_explosiveness_ratio);
+	ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles::get_randomness_ratio);
+	ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles::get_use_local_coordinates);
+	ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles::get_fixed_fps);
+	ClassDB::bind_method(D_METHOD("get_fractional_delta"), &CPUParticles::get_fractional_delta);
+	ClassDB::bind_method(D_METHOD("get_speed_scale"), &CPUParticles::get_speed_scale);
+
+	ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &CPUParticles::set_draw_order);
+
+	ClassDB::bind_method(D_METHOD("get_draw_order"), &CPUParticles::get_draw_order);
+
+	ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CPUParticles::set_mesh);
+	ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles::get_mesh);
+
+	ClassDB::bind_method(D_METHOD("restart"), &CPUParticles::restart);
+
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount");
+	ADD_GROUP("Time", "");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01"), "set_lifetime", "get_lifetime");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_speed_scale", "get_speed_scale");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
+	ADD_GROUP("Drawing", "");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,View Depth"), "set_draw_order", "get_draw_order");
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
+
+	BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX);
+	BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
+	BIND_ENUM_CONSTANT(DRAW_ORDER_VIEW_DEPTH);
+
+	////////////////////////////////
+
+	ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles::set_spread);
+	ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles::get_spread);
+
+	ClassDB::bind_method(D_METHOD("set_flatness", "amount"), &CPUParticles::set_flatness);
+	ClassDB::bind_method(D_METHOD("get_flatness"), &CPUParticles::get_flatness);
+
+	ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &CPUParticles::set_param);
+	ClassDB::bind_method(D_METHOD("get_param", "param"), &CPUParticles::get_param);
+
+	ClassDB::bind_method(D_METHOD("set_param_randomness", "param", "randomness"), &CPUParticles::set_param_randomness);
+	ClassDB::bind_method(D_METHOD("get_param_randomness", "param"), &CPUParticles::get_param_randomness);
+
+	ClassDB::bind_method(D_METHOD("set_param_curve", "param", "curve"), &CPUParticles::set_param_curve);
+	ClassDB::bind_method(D_METHOD("get_param_curve", "param"), &CPUParticles::get_param_curve);
+
+	ClassDB::bind_method(D_METHOD("set_color", "color"), &CPUParticles::set_color);
+	ClassDB::bind_method(D_METHOD("get_color"), &CPUParticles::get_color);
+
+	ClassDB::bind_method(D_METHOD("set_color_ramp", "ramp"), &CPUParticles::set_color_ramp);
+	ClassDB::bind_method(D_METHOD("get_color_ramp"), &CPUParticles::get_color_ramp);
+
+	ClassDB::bind_method(D_METHOD("set_particle_flag", "flag", "enable"), &CPUParticles::set_particle_flag);
+	ClassDB::bind_method(D_METHOD("get_particle_flag", "flag"), &CPUParticles::get_particle_flag);
+
+	ClassDB::bind_method(D_METHOD("set_emission_shape", "shape"), &CPUParticles::set_emission_shape);
+	ClassDB::bind_method(D_METHOD("get_emission_shape"), &CPUParticles::get_emission_shape);
+
+	ClassDB::bind_method(D_METHOD("set_emission_sphere_radius", "radius"), &CPUParticles::set_emission_sphere_radius);
+	ClassDB::bind_method(D_METHOD("get_emission_sphere_radius"), &CPUParticles::get_emission_sphere_radius);
+
+	ClassDB::bind_method(D_METHOD("set_emission_box_extents", "extents"), &CPUParticles::set_emission_box_extents);
+	ClassDB::bind_method(D_METHOD("get_emission_box_extents"), &CPUParticles::get_emission_box_extents);
+
+	ClassDB::bind_method(D_METHOD("set_emission_points", "array"), &CPUParticles::set_emission_points);
+	ClassDB::bind_method(D_METHOD("get_emission_points"), &CPUParticles::get_emission_points);
+
+	ClassDB::bind_method(D_METHOD("set_emission_normals", "array"), &CPUParticles::set_emission_normals);
+	ClassDB::bind_method(D_METHOD("get_emission_normals"), &CPUParticles::get_emission_normals);
+
+	ClassDB::bind_method(D_METHOD("set_emission_colors", "array"), &CPUParticles::set_emission_colors);
+	ClassDB::bind_method(D_METHOD("get_emission_colors"), &CPUParticles::get_emission_colors);
+
+	ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles::get_gravity);
+	ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles::set_gravity);
+
+	ClassDB::bind_method(D_METHOD("_update_render_thread"), &CPUParticles::_update_render_thread);
+
+	ADD_GROUP("Emission Shape", "emission_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape");
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents");
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "emission_points"), "set_emission_points", "get_emission_points");
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
+	ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
+	ADD_GROUP("Flags", "flag_");
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_particle_flag", "get_particle_flag", FLAG_ALIGN_Y_TO_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_rotate_y"), "set_particle_flag", "get_particle_flag", FLAG_ROTATE_Y);
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_disable_z"), "set_particle_flag", "get_particle_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");
+	ADD_GROUP("Gravity", "");
+	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity");
+	ADD_GROUP("Initial Velocity", "initial_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY);
+	ADD_GROUP("Angular Velocity", "angular_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-360,360,0.01"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY);
+	/*
+	ADD_GROUP("Orbit Velocity", "orbit_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY);
+*/
+	ADD_GROUP("Linear Accel", "linear_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_LINEAR_ACCEL);
+	ADD_GROUP("Radial Accel", "radial_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_RADIAL_ACCEL);
+	ADD_GROUP("Tangential Accel", "tangential_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_TANGENTIAL_ACCEL);
+	ADD_GROUP("Damping", "");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_DAMPING);
+	ADD_GROUP("Angle", "");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGLE);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGLE);
+	ADD_GROUP("Scale", "");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE);
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE);
+	ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_param_curve", "get_param_curve", PARAM_SCALE);
+	ADD_GROUP("Color", "");
+	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_color_ramp", "get_color_ramp");
+
+	ADD_GROUP("Hue Variation", "hue_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "hue_variation", PROPERTY_HINT_RANGE, "-1,1,0.1"), "set_param", "get_param", PARAM_HUE_VARIATION);
+	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, "Curve"), "set_param_curve", "get_param_curve", PARAM_HUE_VARIATION);
+	ADD_GROUP("Animation", "anim_");
+	ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "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, "Curve"), "set_param_curve", "get_param_curve", 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, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANIM_OFFSET);
+	ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "anim_loop"), "set_particle_flag", "get_particle_flag", FLAG_ANIM_LOOP);
+
+	BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY);
+	BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY);
+	//BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY);
+	BIND_ENUM_CONSTANT(PARAM_LINEAR_ACCEL);
+	BIND_ENUM_CONSTANT(PARAM_RADIAL_ACCEL);
+	BIND_ENUM_CONSTANT(PARAM_TANGENTIAL_ACCEL);
+	BIND_ENUM_CONSTANT(PARAM_DAMPING);
+	BIND_ENUM_CONSTANT(PARAM_ANGLE);
+	BIND_ENUM_CONSTANT(PARAM_SCALE);
+	BIND_ENUM_CONSTANT(PARAM_HUE_VARIATION);
+	BIND_ENUM_CONSTANT(PARAM_ANIM_SPEED);
+	BIND_ENUM_CONSTANT(PARAM_ANIM_OFFSET);
+	BIND_ENUM_CONSTANT(PARAM_MAX);
+
+	BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY);
+	BIND_ENUM_CONSTANT(FLAG_ROTATE_Y);
+	BIND_ENUM_CONSTANT(FLAG_MAX);
+
+	BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT);
+	BIND_ENUM_CONSTANT(EMISSION_SHAPE_SPHERE);
+	BIND_ENUM_CONSTANT(EMISSION_SHAPE_BOX);
+	BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS);
+	BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS);
+}
+
+CPUParticles::CPUParticles() {
+
+	time = 0;
+	inactive_time = 0;
+	frame_remainder = 0;
+	cycle = 0;
+
+	multimesh = VisualServer::get_singleton()->multimesh_create();
+	set_base(multimesh);
+
+	set_emitting(true);
+	set_one_shot(false);
+	set_amount(8);
+	set_lifetime(1);
+	set_fixed_fps(0);
+	set_fractional_delta(true);
+	set_pre_process_time(0);
+	set_explosiveness_ratio(0);
+	set_randomness_ratio(0);
+	set_use_local_coordinates(true);
+
+	set_draw_order(DRAW_ORDER_INDEX);
+	set_speed_scale(1);
+
+	set_spread(45);
+	set_flatness(0);
+	set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1);
+	//set_param(PARAM_ORBIT_VELOCITY, 0);
+	set_param(PARAM_LINEAR_ACCEL, 0);
+	set_param(PARAM_RADIAL_ACCEL, 0);
+	set_param(PARAM_TANGENTIAL_ACCEL, 0);
+	set_param(PARAM_DAMPING, 0);
+	set_param(PARAM_ANGLE, 0);
+	set_param(PARAM_SCALE, 1);
+	set_param(PARAM_HUE_VARIATION, 0);
+	set_param(PARAM_ANIM_SPEED, 0);
+	set_param(PARAM_ANIM_OFFSET, 0);
+	set_emission_shape(EMISSION_SHAPE_POINT);
+	set_emission_sphere_radius(1);
+	set_emission_box_extents(Vector3(1, 1, 1));
+
+	set_gravity(Vector3(0, -9.8, 0));
+
+	for (int i = 0; i < PARAM_MAX; i++) {
+		set_param_randomness(Parameter(i), 0);
+	}
+
+	for (int i = 0; i < FLAG_MAX; i++) {
+		flags[i] = false;
+	}
+
+	set_color(Color(1, 1, 1, 1));
+
+#ifndef NO_THREADS
+	update_mutex = Mutex::create();
+#endif
+}
+
+CPUParticles::~CPUParticles() {
+	VS::get_singleton()->free(multimesh);
+
+#ifndef NO_THREADS
+	memdelete(update_mutex);
+#endif
+}

+ 256 - 0
scene/3d/cpu_particles.h

@@ -0,0 +1,256 @@
+#ifndef CPU_PARTICLES_H
+#define CPU_PARTICLES_H
+#include "rid.h"
+#include "scene/3d/visual_instance.h"
+#include "scene/main/timer.h"
+#include "scene/resources/material.h"
+
+/**
+	@author Juan Linietsky <[email protected]>
+*/
+
+class CPUParticles : public GeometryInstance {
+private:
+	GDCLASS(CPUParticles, GeometryInstance);
+
+public:
+	enum DrawOrder {
+		DRAW_ORDER_INDEX,
+		DRAW_ORDER_LIFETIME,
+		DRAW_ORDER_VIEW_DEPTH,
+	};
+
+	enum Parameter {
+
+		PARAM_INITIAL_LINEAR_VELOCITY,
+		PARAM_ANGULAR_VELOCITY,
+		//PARAM_ORBIT_VELOCITY,
+		PARAM_LINEAR_ACCEL,
+		PARAM_RADIAL_ACCEL,
+		PARAM_TANGENTIAL_ACCEL,
+		PARAM_DAMPING,
+		PARAM_ANGLE,
+		PARAM_SCALE,
+		PARAM_HUE_VARIATION,
+		PARAM_ANIM_SPEED,
+		PARAM_ANIM_OFFSET,
+		PARAM_MAX
+	};
+
+	enum Flags {
+		FLAG_ALIGN_Y_TO_VELOCITY,
+		FLAG_ROTATE_Y,
+		FLAG_DISABLE_Z,
+		FLAG_ANIM_LOOP,
+		FLAG_MAX
+	};
+
+	enum EmissionShape {
+		EMISSION_SHAPE_POINT,
+		EMISSION_SHAPE_SPHERE,
+		EMISSION_SHAPE_BOX,
+		EMISSION_SHAPE_POINTS,
+		EMISSION_SHAPE_DIRECTED_POINTS,
+	};
+
+private:
+	bool emitting;
+
+	struct Particle {
+		Transform transform;
+		Color color;
+		float custom[4];
+		Vector3 velocity;
+		bool active;
+		float angle_rand;
+		float scale_rand;
+		float hue_rot_rand;
+		float anim_offset_rand;
+		float time;
+		Color base_color;
+
+		uint32_t seed;
+	};
+
+	float time;
+	float inactive_time;
+	float frame_remainder;
+	int cycle;
+
+	RID multimesh;
+
+	PoolVector<Particle> particles;
+	PoolVector<float> particle_data;
+	PoolVector<int> particle_order;
+
+	struct SortLifetime {
+		const Particle *particles;
+
+		bool operator()(int p_a, int p_b) const {
+			return particles[p_a].time < particles[p_b].time;
+		}
+	};
+
+	struct SortAxis {
+		const Particle *particles;
+		Vector3 axis;
+		bool operator()(int p_a, int p_b) const {
+
+			return axis.dot(particles[p_a].transform.origin) < axis.dot(particles[p_b].transform.origin);
+		}
+	};
+
+	//
+
+	bool one_shot;
+
+	float lifetime;
+	float pre_process_time;
+	float explosiveness_ratio;
+	float randomness_ratio;
+	float speed_scale;
+	bool local_coords;
+	int fixed_fps;
+	bool fractional_delta;
+
+	DrawOrder draw_order;
+
+	Ref<Mesh> mesh;
+
+	////////
+
+	float spread;
+	float flatness;
+
+	float parameters[PARAM_MAX];
+	float randomness[PARAM_MAX];
+
+	Ref<Curve> curve_parameters[PARAM_MAX];
+	Color color;
+	Ref<Gradient> color_ramp;
+
+	bool flags[FLAG_MAX];
+
+	EmissionShape emission_shape;
+	float emission_sphere_radius;
+	Vector3 emission_box_extents;
+	PoolVector<Vector3> emission_points;
+	PoolVector<Vector3> emission_normals;
+	PoolVector<Color> emission_colors;
+	int emission_point_count;
+
+	bool anim_loop;
+	Vector3 gravity;
+
+	void _particles_process(float p_delta);
+	void _update_particle_data_buffer();
+
+	Mutex *update_mutex;
+
+	void _update_render_thread();
+
+protected:
+	static void _bind_methods();
+	void _notification(int p_what);
+	virtual void _validate_property(PropertyInfo &property) const;
+
+public:
+	AABB get_aabb() const;
+	PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
+
+	void set_emitting(bool p_emitting);
+	void set_amount(int p_amount);
+	void set_lifetime(float p_lifetime);
+	void set_one_shot(bool p_one_shot);
+	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_aabb(const AABB &p_aabb);
+	void set_use_local_coordinates(bool p_enable);
+	void set_speed_scale(float p_scale);
+
+	bool is_emitting() const;
+	int get_amount() const;
+	float get_lifetime() const;
+	bool get_one_shot() const;
+	float get_pre_process_time() const;
+	float get_explosiveness_ratio() const;
+	float get_randomness_ratio() const;
+	AABB get_visibility_aabb() const;
+	bool get_use_local_coordinates() const;
+	float get_speed_scale() const;
+
+	void set_fixed_fps(int p_count);
+	int get_fixed_fps() const;
+
+	void set_fractional_delta(bool p_enable);
+	bool get_fractional_delta() const;
+
+	void set_draw_order(DrawOrder p_order);
+	DrawOrder get_draw_order() const;
+
+	void set_draw_passes(int p_count);
+	int get_draw_passes() const;
+
+	void set_mesh(const Ref<Mesh> &p_mesh);
+	Ref<Mesh> get_mesh() const;
+
+	///////////////////
+
+	void set_spread(float p_spread);
+	float get_spread() const;
+
+	void set_flatness(float p_flatness);
+	float get_flatness() const;
+
+	void set_param(Parameter p_param, float p_value);
+	float get_param(Parameter p_param) const;
+
+	void set_param_randomness(Parameter p_param, float p_value);
+	float get_param_randomness(Parameter p_param) const;
+
+	void set_param_curve(Parameter p_param, const Ref<Curve> &p_curve);
+	Ref<Curve> get_param_curve(Parameter p_param) const;
+
+	void set_color(const Color &p_color);
+	Color get_color() const;
+
+	void set_color_ramp(const Ref<Gradient> &p_texture);
+	Ref<Gradient> get_color_ramp() const;
+
+	void set_particle_flag(Flags p_flag, bool p_enable);
+	bool get_particle_flag(Flags p_flag) const;
+
+	void set_emission_shape(EmissionShape p_shape);
+	void set_emission_sphere_radius(float p_radius);
+	void set_emission_box_extents(Vector3 p_extents);
+	void set_emission_points(const PoolVector<Vector3> &p_points);
+	void set_emission_normals(const PoolVector<Vector3> &p_normals);
+	void set_emission_colors(const PoolVector<Color> &p_colors);
+	void set_emission_point_count(int p_count);
+
+	EmissionShape get_emission_shape() const;
+	float get_emission_sphere_radius() const;
+	Vector3 get_emission_box_extents() const;
+	PoolVector<Vector3> get_emission_points() const;
+	PoolVector<Vector3> get_emission_normals() const;
+	PoolVector<Color> get_emission_colors() const;
+	int get_emission_point_count() const;
+
+	void set_gravity(const Vector3 &p_gravity);
+	Vector3 get_gravity() const;
+
+	virtual String get_configuration_warning() const;
+
+	void restart();
+
+	CPUParticles();
+	~CPUParticles();
+};
+
+VARIANT_ENUM_CAST(CPUParticles::DrawOrder)
+VARIANT_ENUM_CAST(CPUParticles::Parameter)
+VARIANT_ENUM_CAST(CPUParticles::Flags)
+VARIANT_ENUM_CAST(CPUParticles::EmissionShape)
+
+#endif // CPU_PARTICLES_H

+ 3 - 0
scene/register_scene_types.cpp

@@ -51,6 +51,7 @@
 #include "scene/2d/parallax_background.h"
 #include "scene/2d/parallax_layer.h"
 #include "scene/2d/particles_2d.h"
+
 #include "scene/2d/path_2d.h"
 #include "scene/2d/physics_body_2d.h"
 #include "scene/2d/polygon_2d.h"
@@ -161,6 +162,7 @@
 #include "scene/resources/world_2d.h"
 #include "scene/scene_string_names.h"
 
+#include "scene/3d/cpu_particles.h"
 #include "scene/3d/particles.h"
 #include "scene/3d/scenario_fx.h"
 #include "scene/3d/spatial.h"
@@ -383,6 +385,7 @@ void register_scene_types() {
 	ClassDB::register_class<BakedLightmapData>();
 	ClassDB::register_class<AnimationTreePlayer>();
 	ClassDB::register_class<Particles>();
+	ClassDB::register_class<CPUParticles>();
 	ClassDB::register_class<Position3D>();
 	ClassDB::register_class<NavigationMeshInstance>();
 	ClassDB::register_class<NavigationMesh>();

+ 10 - 0
scene/resources/curve.cpp

@@ -479,6 +479,16 @@ real_t Curve::interpolate_baked(real_t offset) {
 	}
 }
 
+void Curve::ensure_default_setup(float p_min, float p_max) {
+	if (_points.size() == 0 && _min_value == 0 && _max_value == 1) {
+
+		add_point(Vector2(0, 1));
+		add_point(Vector2(1, 1));
+		set_min_value(p_min);
+		set_max_value(p_max);
+	}
+}
+
 void Curve::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("add_point", "position", "left_tangent", "right_tangent", "left_mode", "right_mode"), &Curve::add_point, DEFVAL(0), DEFVAL(0), DEFVAL(TANGENT_FREE), DEFVAL(TANGENT_FREE));

+ 2 - 0
scene/resources/curve.h

@@ -128,6 +128,8 @@ public:
 	void set_bake_resolution(int p_resolution);
 	real_t interpolate_baked(real_t offset);
 
+	void ensure_default_setup(float p_min, float p_max);
+
 protected:
 	static void _bind_methods();
 

+ 5 - 1
servers/visual/rasterizer.h

@@ -282,19 +282,23 @@ public:
 
 	virtual RID multimesh_create() = 0;
 
-	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format) = 0;
+	virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) = 0;
 	virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
 
 	virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
 	virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) = 0;
 	virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
 	virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
+	virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
 
 	virtual RID multimesh_get_mesh(RID p_multimesh) const = 0;
 
 	virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
 	virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
 	virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
+	virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+
+	virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) = 0;
 
 	virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
 	virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;

+ 3 - 1
servers/visual/visual_server_raster.cpp

@@ -97,6 +97,8 @@ void VisualServerRaster::draw(bool p_swap_buffers) {
 
 	changes = 0;
 
+	VS::get_singleton()->emit_signal("frame_pre_draw");
+
 	VSG::rasterizer->begin_frame();
 
 	VSG::scene->update_dirty_instances(); //update scene stuff
@@ -122,7 +124,7 @@ void VisualServerRaster::draw(bool p_swap_buffers) {
 		frame_drawn_callbacks.pop_front();
 	}
 
-	emit_signal("frame_drawn_in_thread");
+	VS::get_singleton()->emit_signal("frame_post_draw");
 }
 void VisualServerRaster::sync() {
 }

+ 5 - 1
servers/visual/visual_server_raster.h

@@ -246,13 +246,14 @@ public:
 
 	BIND0R(RID, multimesh_create)
 
-	BIND4(multimesh_allocate, RID, int, MultimeshTransformFormat, MultimeshColorFormat)
+	BIND5(multimesh_allocate, RID, int, MultimeshTransformFormat, MultimeshColorFormat, MultimeshCustomDataFormat)
 	BIND1RC(int, multimesh_get_instance_count, RID)
 
 	BIND2(multimesh_set_mesh, RID, RID)
 	BIND3(multimesh_instance_set_transform, RID, int, const Transform &)
 	BIND3(multimesh_instance_set_transform_2d, RID, int, const Transform2D &)
 	BIND3(multimesh_instance_set_color, RID, int, const Color &)
+	BIND3(multimesh_instance_set_custom_data, RID, int, const Color &)
 
 	BIND1RC(RID, multimesh_get_mesh, RID)
 	BIND1RC(AABB, multimesh_get_aabb, RID)
@@ -260,6 +261,9 @@ public:
 	BIND2RC(Transform, multimesh_instance_get_transform, RID, int)
 	BIND2RC(Transform2D, multimesh_instance_get_transform_2d, RID, int)
 	BIND2RC(Color, multimesh_instance_get_color, RID, int)
+	BIND2RC(Color, multimesh_instance_get_custom_data, RID, int)
+
+	BIND2(multimesh_set_as_bulk_array, RID, const PoolVector<float> &)
 
 	BIND2(multimesh_set_visible_instances, RID, int)
 	BIND1RC(int, multimesh_get_visible_instances, RID)

+ 5 - 1
servers/visual/visual_server_wrap_mt.h

@@ -180,13 +180,14 @@ public:
 
 	FUNCRID(multimesh)
 
-	FUNC4(multimesh_allocate, RID, int, MultimeshTransformFormat, MultimeshColorFormat)
+	FUNC5(multimesh_allocate, RID, int, MultimeshTransformFormat, MultimeshColorFormat, MultimeshCustomDataFormat)
 	FUNC1RC(int, multimesh_get_instance_count, RID)
 
 	FUNC2(multimesh_set_mesh, RID, RID)
 	FUNC3(multimesh_instance_set_transform, RID, int, const Transform &)
 	FUNC3(multimesh_instance_set_transform_2d, RID, int, const Transform2D &)
 	FUNC3(multimesh_instance_set_color, RID, int, const Color &)
+	FUNC3(multimesh_instance_set_custom_data, RID, int, const Color &)
 
 	FUNC1RC(RID, multimesh_get_mesh, RID)
 	FUNC1RC(AABB, multimesh_get_aabb, RID)
@@ -194,6 +195,9 @@ public:
 	FUNC2RC(Transform, multimesh_instance_get_transform, RID, int)
 	FUNC2RC(Transform2D, multimesh_instance_get_transform_2d, RID, int)
 	FUNC2RC(Color, multimesh_instance_get_color, RID, int)
+	FUNC2RC(Color, multimesh_instance_get_custom_data, RID, int)
+
+	FUNC2(multimesh_set_as_bulk_array, RID, const PoolVector<float> &)
 
 	FUNC2(multimesh_set_visible_instances, RID, int)
 	FUNC1RC(int, multimesh_get_visible_instances, RID)

+ 6 - 2
servers/visual_server.cpp

@@ -1587,19 +1587,22 @@ void VisualServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("mesh_get_custom_aabb", "mesh"), &VisualServer::mesh_get_custom_aabb);
 	ClassDB::bind_method(D_METHOD("mesh_clear", "mesh"), &VisualServer::mesh_clear);
 
-	ClassDB::bind_method(D_METHOD("multimesh_allocate", "multimesh", "instances", "transform_format", "color_format"), &VisualServer::multimesh_allocate);
+	ClassDB::bind_method(D_METHOD("multimesh_allocate", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &VisualServer::multimesh_allocate, DEFVAL(MULTIMESH_CUSTOM_DATA_NONE));
 	ClassDB::bind_method(D_METHOD("multimesh_get_instance_count", "multimesh"), &VisualServer::multimesh_get_instance_count);
 	ClassDB::bind_method(D_METHOD("multimesh_set_mesh", "multimesh", "mesh"), &VisualServer::multimesh_set_mesh);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_set_transform", "multimesh", "index", "transform"), &VisualServer::multimesh_instance_set_transform);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_set_transform_2d", "multimesh", "index", "transform"), &VisualServer::multimesh_instance_set_transform_2d);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_set_color", "multimesh", "index", "color"), &VisualServer::multimesh_instance_set_color);
+	ClassDB::bind_method(D_METHOD("multimesh_instance_set_custom_data", "multimesh", "index", "custom_data"), &VisualServer::multimesh_instance_set_custom_data);
 	ClassDB::bind_method(D_METHOD("multimesh_get_mesh", "multimesh"), &VisualServer::multimesh_get_mesh);
 	ClassDB::bind_method(D_METHOD("multimesh_get_aabb", "multimesh"), &VisualServer::multimesh_get_aabb);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform", "multimesh", "index"), &VisualServer::multimesh_instance_get_transform);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_get_transform_2d", "multimesh", "index"), &VisualServer::multimesh_instance_get_transform_2d);
 	ClassDB::bind_method(D_METHOD("multimesh_instance_get_color", "multimesh", "index"), &VisualServer::multimesh_instance_get_color);
+	ClassDB::bind_method(D_METHOD("multimesh_instance_get_custom_data", "multimesh", "index"), &VisualServer::multimesh_instance_get_custom_data);
 	ClassDB::bind_method(D_METHOD("multimesh_set_visible_instances", "multimesh", "visible"), &VisualServer::multimesh_set_visible_instances);
 	ClassDB::bind_method(D_METHOD("multimesh_get_visible_instances", "multimesh"), &VisualServer::multimesh_get_visible_instances);
+	ClassDB::bind_method(D_METHOD("multimesh_set_as_bulk_array", "multimesh", "array"), &VisualServer::multimesh_set_as_bulk_array);
 
 	ClassDB::bind_method(D_METHOD("immediate_create"), &VisualServer::immediate_create);
 	ClassDB::bind_method(D_METHOD("immediate_begin", "immediate", "primitive", "texture"), &VisualServer::immediate_begin, DEFVAL(RID()));
@@ -2143,7 +2146,8 @@ void VisualServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(ENV_SSAO_BLUR_2x2);
 	BIND_ENUM_CONSTANT(ENV_SSAO_BLUR_3x3);
 
-	ADD_SIGNAL(MethodInfo("frame_drawn_in_thread"));
+	ADD_SIGNAL(MethodInfo("frame_pre_draw"));
+	ADD_SIGNAL(MethodInfo("frame_post_draw"));
 }
 
 void VisualServer::_canvas_item_add_style_box(RID p_item, const Rect2 &p_rect, const Rect2 &p_source, RID p_texture, const Vector<float> &p_margins, const Color &p_modulate) {

+ 12 - 1
servers/visual_server.h

@@ -309,13 +309,20 @@ public:
 		MULTIMESH_COLOR_FLOAT,
 	};
 
-	virtual void multimesh_allocate(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, MultimeshColorFormat p_color_format) = 0;
+	enum MultimeshCustomDataFormat {
+		MULTIMESH_CUSTOM_DATA_NONE,
+		MULTIMESH_CUSTOM_DATA_8BIT,
+		MULTIMESH_CUSTOM_DATA_FLOAT,
+	};
+
+	virtual void multimesh_allocate(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, MultimeshColorFormat p_color_format, MultimeshCustomDataFormat p_data_format = MULTIMESH_CUSTOM_DATA_NONE) = 0;
 	virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
 
 	virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
 	virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) = 0;
 	virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
 	virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
+	virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
 
 	virtual RID multimesh_get_mesh(RID p_multimesh) const = 0;
 	virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0;
@@ -323,6 +330,9 @@ public:
 	virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
 	virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
 	virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
+	virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+
+	virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) = 0;
 
 	virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
 	virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;
@@ -1023,6 +1033,7 @@ VARIANT_ENUM_CAST(VisualServer::RenderInfo);
 VARIANT_ENUM_CAST(VisualServer::Features);
 VARIANT_ENUM_CAST(VisualServer::MultimeshTransformFormat);
 VARIANT_ENUM_CAST(VisualServer::MultimeshColorFormat);
+VARIANT_ENUM_CAST(VisualServer::MultimeshCustomDataFormat);
 VARIANT_ENUM_CAST(VisualServer::LightOmniShadowMode);
 VARIANT_ENUM_CAST(VisualServer::LightOmniShadowDetail);
 VARIANT_ENUM_CAST(VisualServer::LightDirectionalShadowMode);

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно