Selaa lähdekoodia

Merge pull request #101911 from rburing/fti_2d_particles_on_target

`CPUParticles2D` - Add ability to follow physics interpolated target
Thaddeus Crews 7 kuukautta sitten
vanhempi
commit
a7363946b2

+ 1 - 0
doc/classes/CPUParticles2D.xml

@@ -241,6 +241,7 @@
 		<member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false">
 			Align Y axis of particle with the direction of its velocity.
 		</member>
+		<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
 		<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0">
 			Particle system starts as if it had already run for this many seconds.
 		</member>

+ 3 - 1
scene/2d/camera_2d.cpp

@@ -315,7 +315,9 @@ void Camera2D::_notification(int p_what) {
 			}
 			if (is_physics_interpolated_and_enabled()) {
 				_ensure_update_interpolation_data();
-				_interpolation_data.xform_curr = get_camera_transform();
+				if (Engine::get_singleton()->is_in_physics_frame()) {
+					_interpolation_data.xform_curr = get_camera_transform();
+				}
 			}
 		} break;
 

+ 73 - 29
scene/2d/cpu_particles_2d.cpp

@@ -32,6 +32,7 @@
 #include "cpu_particles_2d.compat.inc"
 
 #include "core/math/random_number_generator.h"
+#include "core/math/transform_interpolator.h"
 #include "scene/2d/gpu_particles_2d.h"
 #include "scene/resources/atlas_texture.h"
 #include "scene/resources/canvas_item_material.h"
@@ -96,7 +97,14 @@ void CPUParticles2D::set_lifetime_randomness(double p_random) {
 
 void CPUParticles2D::set_use_local_coordinates(bool p_enable) {
 	local_coords = p_enable;
-	set_notify_transform(!p_enable);
+
+	// Prevent sending item transforms when using global coords,
+	// and inform the RenderingServer to use identity mode.
+	set_canvas_item_use_identity_transform(!local_coords);
+
+	// We only need NOTIFICATION_TRANSFORM_CHANGED
+	// when following an interpolated target.
+	set_notify_transform(_interpolation_data.interpolated_follow);
 }
 
 void CPUParticles2D::set_speed_scale(double p_scale) {
@@ -228,6 +236,27 @@ void CPUParticles2D::_texture_changed() {
 	}
 }
 
+void CPUParticles2D::_refresh_interpolation_state() {
+	if (!is_inside_tree()) {
+		return;
+	}
+
+	// The logic for whether to do an interpolated follow.
+	// This is rather complex, but basically:
+	// If project setting interpolation is ON and this particle system is in global mode,
+	// we will follow the INTERPOLATED position rather than the actual position.
+	// This is so that particles aren't generated AHEAD of the interpolated parent.
+	bool follow = !local_coords && get_tree()->is_physics_interpolation_enabled();
+
+	if (follow == _interpolation_data.interpolated_follow) {
+		return;
+	}
+
+	_interpolation_data.interpolated_follow = follow;
+
+	set_physics_process_internal(_interpolation_data.interpolated_follow);
+}
+
 Ref<Texture2D> CPUParticles2D::get_texture() const {
 	return texture;
 }
@@ -600,6 +629,9 @@ void CPUParticles2D::_update_internal() {
 		return;
 	}
 
+	// Change update mode?
+	_refresh_interpolation_state();
+
 	double delta = get_process_delta_time();
 	if (!active && !emitting) {
 		set_process_internal(false);
@@ -679,7 +711,11 @@ void CPUParticles2D::_particles_process(double p_delta) {
 	Transform2D emission_xform;
 	Transform2D velocity_xform;
 	if (!local_coords) {
-		emission_xform = get_global_transform();
+		if (!_interpolation_data.interpolated_follow) {
+			emission_xform = get_global_transform();
+		} else {
+			TransformInterpolator::interpolate_transform_2d(_interpolation_data.global_xform_prev, _interpolation_data.global_xform_curr, emission_xform, Engine::get_singleton()->get_physics_interpolation_fraction());
+		}
 		velocity_xform = emission_xform;
 		velocity_xform[2] = Vector2();
 	}
@@ -1142,6 +1178,17 @@ void CPUParticles2D::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			set_process_internal(emitting);
+
+			_refresh_interpolation_state();
+
+			set_physics_process_internal(emitting && _interpolation_data.interpolated_follow);
+
+			// If we are interpolated following, then reset physics interpolation
+			// when first appearing. This won't be called by canvas item, as in the
+			// following mode, is_physics_interpolated() is actually FALSE.
+			if (_interpolation_data.interpolated_follow) {
+				notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+			}
 		} break;
 
 		case NOTIFICATION_EXIT_TREE: {
@@ -1170,37 +1217,28 @@ void CPUParticles2D::_notification(int p_what) {
 			_update_internal();
 		} break;
 
-		case NOTIFICATION_TRANSFORM_CHANGED: {
-			inv_emission_transform = get_global_transform().affine_inverse();
-
-			if (!local_coords) {
-				int pc = particles.size();
-
-				float *w = particle_data.ptrw();
-				const Particle *r = particles.ptr();
-				float *ptr = w;
-
-				for (int i = 0; i < pc; i++) {
-					Transform2D t = inv_emission_transform * r[i].transform;
-
-					if (r[i].active) {
-						ptr[0] = t.columns[0][0];
-						ptr[1] = t.columns[1][0];
-						ptr[2] = 0;
-						ptr[3] = t.columns[2][0];
-						ptr[4] = t.columns[0][1];
-						ptr[5] = t.columns[1][1];
-						ptr[6] = 0;
-						ptr[7] = t.columns[2][1];
-
-					} else {
-						memset(ptr, 0, sizeof(float) * 8);
-					}
+		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+			if (_interpolation_data.interpolated_follow) {
+				// Keep the interpolated follow target updated.
+				_interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr;
+				_interpolation_data.global_xform_curr = get_global_transform();
+			}
+		} break;
 
-					ptr += 16;
+		case NOTIFICATION_TRANSFORM_CHANGED: {
+			if (_interpolation_data.interpolated_follow) {
+				// If the transform has been updated AFTER the physics tick, keep data flowing.
+				if (Engine::get_singleton()->is_in_physics_frame()) {
+					_interpolation_data.global_xform_curr = get_global_transform();
 				}
 			}
 		} break;
+
+		case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+			// Make sure current is up to date with any pending global transform changes.
+			_interpolation_data.global_xform_curr = get_global_transform_const();
+			_interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr;
+		} break;
 	}
 }
 
@@ -1559,6 +1597,12 @@ CPUParticles2D::CPUParticles2D() {
 	set_color(Color(1, 1, 1, 1));
 
 	_update_mesh_texture();
+
+	// CPUParticles2D defaults to interpolation off.
+	// This is because the result often looks better when the particles are updated every frame.
+	// Note that children will need to explicitly turn back on interpolation if they want to use it,
+	// rather than relying on inherit mode.
+	set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
 }
 
 CPUParticles2D::~CPUParticles2D() {

+ 11 - 0
scene/2d/cpu_particles_2d.h

@@ -189,6 +189,15 @@ private:
 
 	Mutex update_mutex;
 
+	struct InterpolationData {
+		// Whether this particle is non-interpolated, but following an interpolated parent.
+		bool interpolated_follow = false;
+
+		// If doing interpolated follow, we need to keep these updated per tick.
+		Transform2D global_xform_curr;
+		Transform2D global_xform_prev;
+	} _interpolation_data;
+
 	void _update_render_thread();
 
 	void _update_mesh_texture();
@@ -197,6 +206,8 @@ private:
 
 	void _texture_changed();
 
+	void _refresh_interpolation_state();
+
 protected:
 	static void _bind_methods();
 	void _notification(int p_what);

+ 3 - 1
scene/2d/node_2d.cpp

@@ -374,7 +374,9 @@ void Node2D::set_transform(const Transform2D &p_transform) {
 	transform = p_transform;
 	_set_xform_dirty(true);
 
-	RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
+	if (!_is_using_identity_transform()) {
+		RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
+	}
 
 	_notify_transform();
 }

+ 32 - 0
scene/main/canvas_item.cpp

@@ -188,6 +188,20 @@ Transform2D CanvasItem::get_global_transform() const {
 	return global_transform;
 }
 
+// Same as get_global_transform() but no reset for `global_invalid`.
+Transform2D CanvasItem::get_global_transform_const() const {
+	if (_is_global_invalid()) {
+		const CanvasItem *pi = get_parent_item();
+		if (pi) {
+			global_transform = pi->get_global_transform_const() * get_transform();
+		} else {
+			global_transform = get_transform();
+		}
+	}
+
+	return global_transform;
+}
+
 void CanvasItem::_set_global_invalid(bool p_invalid) const {
 	if (is_group_processing()) {
 		if (p_invalid) {
@@ -1039,6 +1053,24 @@ void CanvasItem::_physics_interpolated_changed() {
 	RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated());
 }
 
+void CanvasItem::set_canvas_item_use_identity_transform(bool p_enable) {
+	// Prevent sending item transforms to RenderingServer when using global coords.
+	_set_use_identity_transform(p_enable);
+
+	// Let RenderingServer know not to concatenate the parent transform during the render.
+	RenderingServer::get_singleton()->canvas_item_set_use_identity_transform(get_canvas_item(), p_enable);
+
+	if (is_inside_tree()) {
+		if (p_enable) {
+			// Make sure item is using identity transform in server.
+			RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), Transform2D());
+		} else {
+			// Make sure item transform is up to date in server if switching identity transform off.
+			RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), get_transform());
+		}
+	}
+}
+
 Rect2 CanvasItem::get_viewport_rect() const {
 	ERR_READ_THREAD_GUARD_V(Rect2());
 	ERR_FAIL_COND_V(!is_inside_tree(), Rect2());

+ 3 - 0
scene/main/canvas_item.h

@@ -170,6 +170,8 @@ protected:
 
 	void item_rect_changed(bool p_size_changed = true);
 
+	void set_canvas_item_use_identity_transform(bool p_enable);
+
 	void _notification(int p_what);
 	static void _bind_methods();
 
@@ -339,6 +341,7 @@ public:
 	virtual Transform2D get_transform() const = 0;
 
 	virtual Transform2D get_global_transform() const;
+	virtual Transform2D get_global_transform_const() const;
 	virtual Transform2D get_global_transform_with_canvas() const;
 	virtual Transform2D get_screen_transform() const;
 

+ 24 - 3
servers/rendering/renderer_canvas_cull.cpp

@@ -70,6 +70,11 @@ void RendererCanvasCull::_dependency_deleted(const RID &p_dependency, Dependency
 void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info) {
 	RENDER_TIMESTAMP("Cull CanvasItem Tree");
 
+	// This is used to avoid passing the camera transform down the rendering
+	// function calls, as it won't be used in 99% of cases, because the camera
+	// transform is normally concatenated with the item global transform.
+	_current_camera_transform = p_transform;
+
 	memset(z_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
 	memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
 
@@ -242,14 +247,14 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
 	}
 
 	if (((ci->commands != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(p_global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
-		//something to draw?
+		// Something to draw?
 
 		if (ci->update_when_visible) {
 			RenderingServerDefault::redraw_request();
 		}
 
 		if (ci->commands != nullptr || ci->copy_back_buffer) {
-			ci->final_transform = p_transform;
+			ci->final_transform = !ci->use_identity_transform ? p_transform : _current_camera_transform;
 			ci->final_modulate = p_modulate * ci->self_modulate;
 			ci->global_rect_cache = p_global_rect;
 			ci->global_rect_cache.position -= p_clip_rect.position;
@@ -322,6 +327,10 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 		}
 	}
 
+	// Always calculate final transform as if not using identity xform.
+	// This is so the expected transform is passed to children.
+	// However, if use_identity_xform is set,
+	// we can override the transform for rendering purposes for this item only.
 	Transform2D self_xform;
 	Transform2D final_xform;
 	if (p_is_already_y_sorted) {
@@ -360,7 +369,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
 		ci->repeat_source_item = repeat_source_item;
 	}
 
-	Rect2 global_rect = final_xform.xform(rect);
+	Rect2 global_rect;
+	if (!p_canvas_item->use_identity_transform) {
+		global_rect = final_xform.xform(rect);
+	} else {
+		global_rect = _current_camera_transform.xform(rect);
+	}
 	if (repeat_source_item && (repeat_size.x || repeat_size.y)) {
 		// Top-left repeated rect.
 		Rect2 corner_rect = global_rect;
@@ -686,6 +700,13 @@ void RendererCanvasCull::canvas_item_set_draw_behind_parent(RID p_item, bool p_e
 	canvas_item->behind = p_enable;
 }
 
+void RendererCanvasCull::canvas_item_set_use_identity_transform(RID p_item, bool p_enable) {
+	Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+	ERR_FAIL_NULL(canvas_item);
+
+	canvas_item->use_identity_transform = p_enable;
+}
+
 void RendererCanvasCull::canvas_item_set_update_when_visible(RID p_item, bool p_update) {
 	Item *canvas_item = canvas_item_owner.get_or_null(p_item);
 	ERR_FAIL_NULL(canvas_item);

+ 3 - 0
servers/rendering/renderer_canvas_cull.h

@@ -217,6 +217,8 @@ private:
 	RendererCanvasRender::Item **z_list;
 	RendererCanvasRender::Item **z_last_list;
 
+	Transform2D _current_camera_transform;
+
 public:
 	void render_canvas(RID p_render_target, Canvas *p_canvas, const Transform2D &p_transform, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, const Rect2 &p_clip_rect, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_transforms_to_pixel, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr);
 
@@ -250,6 +252,7 @@ public:
 	void canvas_item_set_self_modulate(RID p_item, const Color &p_color);
 
 	void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable);
+	void canvas_item_set_use_identity_transform(RID p_item, bool p_enable);
 
 	void canvas_item_set_update_when_visible(RID p_item, bool p_update);
 

+ 2 - 0
servers/rendering/renderer_canvas_render.h

@@ -321,6 +321,7 @@ public:
 		bool update_when_visible : 1;
 		bool on_interpolate_transform_list : 1;
 		bool interpolated : 1;
+		bool use_identity_transform : 1;
 
 		struct CanvasGroup {
 			RS::CanvasGroupMode mode;
@@ -486,6 +487,7 @@ public:
 			repeat_source = false;
 			on_interpolate_transform_list = false;
 			interpolated = true;
+			use_identity_transform = false;
 		}
 		virtual ~Item() {
 			clear();

+ 1 - 0
servers/rendering/rendering_server_default.h

@@ -962,6 +962,7 @@ public:
 	FUNC2(canvas_item_set_self_modulate, RID, const Color &)
 
 	FUNC2(canvas_item_set_draw_behind_parent, RID, bool)
+	FUNC2(canvas_item_set_use_identity_transform, RID, bool)
 
 	FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool)
 	FUNC5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)

+ 1 - 0
servers/rendering_server.h

@@ -1545,6 +1545,7 @@ public:
 	virtual void canvas_item_set_visibility_layer(RID p_item, uint32_t p_visibility_layer) = 0;
 
 	virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0;
+	virtual void canvas_item_set_use_identity_transform(RID p_item, bool p_enabled) = 0;
 
 	enum NinePatchAxisMode {
 		NINE_PATCH_STRETCH,