diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index e52e7274006..646e025b115 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -241,6 +241,7 @@ Align Y axis of particle with the direction of its velocity. + 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 434671a1c7f..2ac935adda0 100644 --- a/scene/2d/camera_2d.cpp +++ b/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; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index ba0232945ca..aa449beb1b9 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/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 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_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; + 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); - } - - ptr += 16; + 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() { diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index f6f1d8a00ae..4c0ffff417a 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/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); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 4c0f877f2e7..304e29253e6 100644 --- a/scene/2d/node_2d.cpp +++ b/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(); } diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 2cbe9acb3e8..e978099a826 100644 --- a/scene/main/canvas_item.cpp +++ b/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()); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index e3c154b0176..ece1aa2cf47 100644 --- a/scene/main/canvas_item.h +++ b/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; diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 8d97537ceab..c9175466104 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/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); diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index 2c7e4128584..167ae792217 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/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); diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h index 07bbcffdb4f..ef98af8f907 100644 --- a/servers/rendering/renderer_canvas_render.h +++ b/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(); diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index 14794f7bab7..8e5faaf8381 100644 --- a/servers/rendering/rendering_server_default.h +++ b/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 &, const Vector &, float, bool) diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 1206911e4b2..4880da43933 100644 --- a/servers/rendering_server.h +++ b/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,