diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index 0c4b274b9..d058fa1b9 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -229,6 +229,7 @@ Orbital velocity randomness ratio. + Particle system starts as if it had already run for this many seconds. diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 316d699cd..4b83cd758 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -304,7 +304,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; case NOTIFICATION_ENTER_TREE: { diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index e72c6bd78..cb12e0b9a 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -497,6 +497,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 (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::_toplevel_raise_self() { if (!is_inside_tree()) { return; diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 6f82afc3a..09e23a3dd 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -231,6 +231,7 @@ protected: void item_rect_changed(bool p_size_changed = true); void set_canvas_item_use_identity_transform(bool p_enable); + Transform2D get_global_transform_const() const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index e1cc3bdd1..4cf8d85de 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -32,6 +32,9 @@ #include "core/containers/rid.h" #include "core/core_string_names.h" #include "core/os/os.h" +#include "core/containers/fixed_array.h" +#include "core/math/transform_interpolator.h" + #include "scene/2d/canvas_item.h" #include "scene/resources/particles_material.h" #include "scene/resources/texture.h" @@ -121,6 +124,10 @@ void CPUParticles2D::set_use_local_coordinates(bool p_enable) { // When not using legacy, there is never a need for NOTIFICATION_TRANSFORM_CHANGED, // so we leave it at the default (false). 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); #endif } @@ -757,7 +764,11 @@ void CPUParticles2D::_particles_process(float 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(); } @@ -1085,7 +1096,14 @@ void CPUParticles2D::_refresh_interpolation_state() { } bool interpolated = is_physics_interpolated_and_enabled(); - if (_interpolated == interpolated) { + // The logic for whether to do an interpolated follow. + // This is rather complex, but basically: + // If project setting interpolation is ON but this particle system is switched OFF, + // and 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 = !interpolated && !local_coords && get_tree()->is_physics_interpolation_enabled(); + + if ((_interpolated == interpolated) && (follow == _interpolation_data.interpolated_follow)) { return; } @@ -1096,14 +1114,13 @@ void CPUParticles2D::_refresh_interpolation_state() { _set_redraw(false); _interpolated = interpolated; + _interpolation_data.interpolated_follow = follow; -#ifdef PANDEMONIUM_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY // Refresh local coords state, blank inv_emission_transform. set_use_local_coordinates(local_coords); -#endif set_process_internal(!_interpolated); - set_physics_process_internal(_interpolated); + set_physics_process_internal(_interpolated || _interpolation_data.interpolated_follow); // Re-establish all connections. _set_redraw(curr_redraw); @@ -1165,6 +1182,13 @@ void CPUParticles2D::_notification(int p_what) { if (_interpolated) { _update_internal(true); } + + // If we are interpolated following, then reset physics interpolation + // when first appearing. This won't be called by canvas item, as in + // following mode, is_interpolated() is actually FALSE. + if (_interpolation_data.interpolated_follow) { + notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } } if (p_what == NOTIFICATION_EXIT_TREE) { @@ -1199,7 +1223,15 @@ void CPUParticles2D::_notification(int p_what) { } if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - _update_internal(true); + if (_interpolated) { + _update_internal(true); + } + if (_interpolation_data.interpolated_follow) { + // Keep the interpolated follow target updated. + DEV_CHECK_ONCE(!_interpolated); + _interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr; + _interpolation_data.global_xform_curr = get_global_transform(); + } } #ifdef PANDEMONIUM_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { @@ -1232,6 +1264,20 @@ void CPUParticles2D::_notification(int p_what) { } } } +#else + if (p_what == 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(); + } + } + } + if (p_what == 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; + } #endif } @@ -1450,6 +1496,7 @@ CPUParticles2D::CPUParticles2D() { redraw = false; emitting = false; _interpolated = false; + _interpolation_data.interpolated_follow = false; mesh = RID_PRIME(RenderingServer::get_singleton()->mesh_create()); multimesh = RID_PRIME(RenderingServer::get_singleton()->multimesh_create()); @@ -1501,6 +1548,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() { diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 4832458c4..0d4f0715a 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -35,7 +35,7 @@ class RID; class Texture; -#define PANDEMONIUM_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY +//#define PANDEMONIUM_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY class CPUParticles2D : public Node2D { private: @@ -200,8 +200,18 @@ private: void _update_particle_data_buffer(); Mutex update_mutex; + // Whether this particle system is interpolated. bool _interpolated; + struct InterpolationData { + // Whether this particle is non-interpolated, but following an interpolated parent. + bool interpolated_follow; + + // 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(); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index b87cd5539..e1fc62bb6 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1189,9 +1189,7 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) { } void Node::reset_physics_interpolation() { - if (is_physics_interpolated_and_enabled()) { - propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); - } + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); } float Node::get_physics_process_delta_time() const {