Skip to content

Commit a736394

Browse files
committed
Merge pull request #101911 from rburing/fti_2d_particles_on_target
`CPUParticles2D` - Add ability to follow physics interpolated target
2 parents a5015ca + 56fc0fd commit a736394

File tree

12 files changed

+157
-34
lines changed

12 files changed

+157
-34
lines changed

doc/classes/CPUParticles2D.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false">
242242
Align Y axis of particle with the direction of its velocity.
243243
</member>
244+
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
244245
<member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time" default="0.0">
245246
Particle system starts as if it had already run for this many seconds.
246247
</member>

scene/2d/camera_2d.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,9 @@ void Camera2D::_notification(int p_what) {
315315
}
316316
if (is_physics_interpolated_and_enabled()) {
317317
_ensure_update_interpolation_data();
318-
_interpolation_data.xform_curr = get_camera_transform();
318+
if (Engine::get_singleton()->is_in_physics_frame()) {
319+
_interpolation_data.xform_curr = get_camera_transform();
320+
}
319321
}
320322
} break;
321323

scene/2d/cpu_particles_2d.cpp

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "cpu_particles_2d.compat.inc"
3333

3434
#include "core/math/random_number_generator.h"
35+
#include "core/math/transform_interpolator.h"
3536
#include "scene/2d/gpu_particles_2d.h"
3637
#include "scene/resources/atlas_texture.h"
3738
#include "scene/resources/canvas_item_material.h"
@@ -96,7 +97,14 @@ void CPUParticles2D::set_lifetime_randomness(double p_random) {
9697

9798
void CPUParticles2D::set_use_local_coordinates(bool p_enable) {
9899
local_coords = p_enable;
99-
set_notify_transform(!p_enable);
100+
101+
// Prevent sending item transforms when using global coords,
102+
// and inform the RenderingServer to use identity mode.
103+
set_canvas_item_use_identity_transform(!local_coords);
104+
105+
// We only need NOTIFICATION_TRANSFORM_CHANGED
106+
// when following an interpolated target.
107+
set_notify_transform(_interpolation_data.interpolated_follow);
100108
}
101109

102110
void CPUParticles2D::set_speed_scale(double p_scale) {
@@ -228,6 +236,27 @@ void CPUParticles2D::_texture_changed() {
228236
}
229237
}
230238

239+
void CPUParticles2D::_refresh_interpolation_state() {
240+
if (!is_inside_tree()) {
241+
return;
242+
}
243+
244+
// The logic for whether to do an interpolated follow.
245+
// This is rather complex, but basically:
246+
// If project setting interpolation is ON and this particle system is in global mode,
247+
// we will follow the INTERPOLATED position rather than the actual position.
248+
// This is so that particles aren't generated AHEAD of the interpolated parent.
249+
bool follow = !local_coords && get_tree()->is_physics_interpolation_enabled();
250+
251+
if (follow == _interpolation_data.interpolated_follow) {
252+
return;
253+
}
254+
255+
_interpolation_data.interpolated_follow = follow;
256+
257+
set_physics_process_internal(_interpolation_data.interpolated_follow);
258+
}
259+
231260
Ref<Texture2D> CPUParticles2D::get_texture() const {
232261
return texture;
233262
}
@@ -600,6 +629,9 @@ void CPUParticles2D::_update_internal() {
600629
return;
601630
}
602631

632+
// Change update mode?
633+
_refresh_interpolation_state();
634+
603635
double delta = get_process_delta_time();
604636
if (!active && !emitting) {
605637
set_process_internal(false);
@@ -679,7 +711,11 @@ void CPUParticles2D::_particles_process(double p_delta) {
679711
Transform2D emission_xform;
680712
Transform2D velocity_xform;
681713
if (!local_coords) {
682-
emission_xform = get_global_transform();
714+
if (!_interpolation_data.interpolated_follow) {
715+
emission_xform = get_global_transform();
716+
} else {
717+
TransformInterpolator::interpolate_transform_2d(_interpolation_data.global_xform_prev, _interpolation_data.global_xform_curr, emission_xform, Engine::get_singleton()->get_physics_interpolation_fraction());
718+
}
683719
velocity_xform = emission_xform;
684720
velocity_xform[2] = Vector2();
685721
}
@@ -1142,6 +1178,17 @@ void CPUParticles2D::_notification(int p_what) {
11421178
switch (p_what) {
11431179
case NOTIFICATION_ENTER_TREE: {
11441180
set_process_internal(emitting);
1181+
1182+
_refresh_interpolation_state();
1183+
1184+
set_physics_process_internal(emitting && _interpolation_data.interpolated_follow);
1185+
1186+
// If we are interpolated following, then reset physics interpolation
1187+
// when first appearing. This won't be called by canvas item, as in the
1188+
// following mode, is_physics_interpolated() is actually FALSE.
1189+
if (_interpolation_data.interpolated_follow) {
1190+
notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
1191+
}
11451192
} break;
11461193

11471194
case NOTIFICATION_EXIT_TREE: {
@@ -1170,37 +1217,28 @@ void CPUParticles2D::_notification(int p_what) {
11701217
_update_internal();
11711218
} break;
11721219

1173-
case NOTIFICATION_TRANSFORM_CHANGED: {
1174-
inv_emission_transform = get_global_transform().affine_inverse();
1175-
1176-
if (!local_coords) {
1177-
int pc = particles.size();
1178-
1179-
float *w = particle_data.ptrw();
1180-
const Particle *r = particles.ptr();
1181-
float *ptr = w;
1182-
1183-
for (int i = 0; i < pc; i++) {
1184-
Transform2D t = inv_emission_transform * r[i].transform;
1185-
1186-
if (r[i].active) {
1187-
ptr[0] = t.columns[0][0];
1188-
ptr[1] = t.columns[1][0];
1189-
ptr[2] = 0;
1190-
ptr[3] = t.columns[2][0];
1191-
ptr[4] = t.columns[0][1];
1192-
ptr[5] = t.columns[1][1];
1193-
ptr[6] = 0;
1194-
ptr[7] = t.columns[2][1];
1195-
1196-
} else {
1197-
memset(ptr, 0, sizeof(float) * 8);
1198-
}
1220+
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
1221+
if (_interpolation_data.interpolated_follow) {
1222+
// Keep the interpolated follow target updated.
1223+
_interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr;
1224+
_interpolation_data.global_xform_curr = get_global_transform();
1225+
}
1226+
} break;
11991227

1200-
ptr += 16;
1228+
case NOTIFICATION_TRANSFORM_CHANGED: {
1229+
if (_interpolation_data.interpolated_follow) {
1230+
// If the transform has been updated AFTER the physics tick, keep data flowing.
1231+
if (Engine::get_singleton()->is_in_physics_frame()) {
1232+
_interpolation_data.global_xform_curr = get_global_transform();
12011233
}
12021234
}
12031235
} break;
1236+
1237+
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
1238+
// Make sure current is up to date with any pending global transform changes.
1239+
_interpolation_data.global_xform_curr = get_global_transform_const();
1240+
_interpolation_data.global_xform_prev = _interpolation_data.global_xform_curr;
1241+
} break;
12041242
}
12051243
}
12061244

@@ -1559,6 +1597,12 @@ CPUParticles2D::CPUParticles2D() {
15591597
set_color(Color(1, 1, 1, 1));
15601598

15611599
_update_mesh_texture();
1600+
1601+
// CPUParticles2D defaults to interpolation off.
1602+
// This is because the result often looks better when the particles are updated every frame.
1603+
// Note that children will need to explicitly turn back on interpolation if they want to use it,
1604+
// rather than relying on inherit mode.
1605+
set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF);
15621606
}
15631607

15641608
CPUParticles2D::~CPUParticles2D() {

scene/2d/cpu_particles_2d.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ class CPUParticles2D : public Node2D {
189189

190190
Mutex update_mutex;
191191

192+
struct InterpolationData {
193+
// Whether this particle is non-interpolated, but following an interpolated parent.
194+
bool interpolated_follow = false;
195+
196+
// If doing interpolated follow, we need to keep these updated per tick.
197+
Transform2D global_xform_curr;
198+
Transform2D global_xform_prev;
199+
} _interpolation_data;
200+
192201
void _update_render_thread();
193202

194203
void _update_mesh_texture();
@@ -197,6 +206,8 @@ class CPUParticles2D : public Node2D {
197206

198207
void _texture_changed();
199208

209+
void _refresh_interpolation_state();
210+
200211
protected:
201212
static void _bind_methods();
202213
void _notification(int p_what);

scene/2d/node_2d.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,9 @@ void Node2D::set_transform(const Transform2D &p_transform) {
374374
transform = p_transform;
375375
_set_xform_dirty(true);
376376

377-
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
377+
if (!_is_using_identity_transform()) {
378+
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
379+
}
378380

379381
_notify_transform();
380382
}

scene/main/canvas_item.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ Transform2D CanvasItem::get_global_transform() const {
188188
return global_transform;
189189
}
190190

191+
// Same as get_global_transform() but no reset for `global_invalid`.
192+
Transform2D CanvasItem::get_global_transform_const() const {
193+
if (_is_global_invalid()) {
194+
const CanvasItem *pi = get_parent_item();
195+
if (pi) {
196+
global_transform = pi->get_global_transform_const() * get_transform();
197+
} else {
198+
global_transform = get_transform();
199+
}
200+
}
201+
202+
return global_transform;
203+
}
204+
191205
void CanvasItem::_set_global_invalid(bool p_invalid) const {
192206
if (is_group_processing()) {
193207
if (p_invalid) {
@@ -1039,6 +1053,24 @@ void CanvasItem::_physics_interpolated_changed() {
10391053
RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated());
10401054
}
10411055

1056+
void CanvasItem::set_canvas_item_use_identity_transform(bool p_enable) {
1057+
// Prevent sending item transforms to RenderingServer when using global coords.
1058+
_set_use_identity_transform(p_enable);
1059+
1060+
// Let RenderingServer know not to concatenate the parent transform during the render.
1061+
RenderingServer::get_singleton()->canvas_item_set_use_identity_transform(get_canvas_item(), p_enable);
1062+
1063+
if (is_inside_tree()) {
1064+
if (p_enable) {
1065+
// Make sure item is using identity transform in server.
1066+
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), Transform2D());
1067+
} else {
1068+
// Make sure item transform is up to date in server if switching identity transform off.
1069+
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), get_transform());
1070+
}
1071+
}
1072+
}
1073+
10421074
Rect2 CanvasItem::get_viewport_rect() const {
10431075
ERR_READ_THREAD_GUARD_V(Rect2());
10441076
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());

scene/main/canvas_item.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class CanvasItem : public Node {
170170

171171
void item_rect_changed(bool p_size_changed = true);
172172

173+
void set_canvas_item_use_identity_transform(bool p_enable);
174+
173175
void _notification(int p_what);
174176
static void _bind_methods();
175177

@@ -339,6 +341,7 @@ class CanvasItem : public Node {
339341
virtual Transform2D get_transform() const = 0;
340342

341343
virtual Transform2D get_global_transform() const;
344+
virtual Transform2D get_global_transform_const() const;
342345
virtual Transform2D get_global_transform_with_canvas() const;
343346
virtual Transform2D get_screen_transform() const;
344347

servers/rendering/renderer_canvas_cull.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ void RendererCanvasCull::_dependency_deleted(const RID &p_dependency, Dependency
7070
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) {
7171
RENDER_TIMESTAMP("Cull CanvasItem Tree");
7272

73+
// This is used to avoid passing the camera transform down the rendering
74+
// function calls, as it won't be used in 99% of cases, because the camera
75+
// transform is normally concatenated with the item global transform.
76+
_current_camera_transform = p_transform;
77+
7378
memset(z_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
7479
memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
7580

@@ -242,14 +247,14 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
242247
}
243248

244249
if (((ci->commands != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(p_global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
245-
//something to draw?
250+
// Something to draw?
246251

247252
if (ci->update_when_visible) {
248253
RenderingServerDefault::redraw_request();
249254
}
250255

251256
if (ci->commands != nullptr || ci->copy_back_buffer) {
252-
ci->final_transform = p_transform;
257+
ci->final_transform = !ci->use_identity_transform ? p_transform : _current_camera_transform;
253258
ci->final_modulate = p_modulate * ci->self_modulate;
254259
ci->global_rect_cache = p_global_rect;
255260
ci->global_rect_cache.position -= p_clip_rect.position;
@@ -322,6 +327,10 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
322327
}
323328
}
324329

330+
// Always calculate final transform as if not using identity xform.
331+
// This is so the expected transform is passed to children.
332+
// However, if use_identity_xform is set,
333+
// we can override the transform for rendering purposes for this item only.
325334
Transform2D self_xform;
326335
Transform2D final_xform;
327336
if (p_is_already_y_sorted) {
@@ -360,7 +369,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
360369
ci->repeat_source_item = repeat_source_item;
361370
}
362371

363-
Rect2 global_rect = final_xform.xform(rect);
372+
Rect2 global_rect;
373+
if (!p_canvas_item->use_identity_transform) {
374+
global_rect = final_xform.xform(rect);
375+
} else {
376+
global_rect = _current_camera_transform.xform(rect);
377+
}
364378
if (repeat_source_item && (repeat_size.x || repeat_size.y)) {
365379
// Top-left repeated rect.
366380
Rect2 corner_rect = global_rect;
@@ -686,6 +700,13 @@ void RendererCanvasCull::canvas_item_set_draw_behind_parent(RID p_item, bool p_e
686700
canvas_item->behind = p_enable;
687701
}
688702

703+
void RendererCanvasCull::canvas_item_set_use_identity_transform(RID p_item, bool p_enable) {
704+
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
705+
ERR_FAIL_NULL(canvas_item);
706+
707+
canvas_item->use_identity_transform = p_enable;
708+
}
709+
689710
void RendererCanvasCull::canvas_item_set_update_when_visible(RID p_item, bool p_update) {
690711
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
691712
ERR_FAIL_NULL(canvas_item);

servers/rendering/renderer_canvas_cull.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ class RendererCanvasCull {
217217
RendererCanvasRender::Item **z_list;
218218
RendererCanvasRender::Item **z_last_list;
219219

220+
Transform2D _current_camera_transform;
221+
220222
public:
221223
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);
222224

@@ -250,6 +252,7 @@ class RendererCanvasCull {
250252
void canvas_item_set_self_modulate(RID p_item, const Color &p_color);
251253

252254
void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable);
255+
void canvas_item_set_use_identity_transform(RID p_item, bool p_enable);
253256

254257
void canvas_item_set_update_when_visible(RID p_item, bool p_update);
255258

servers/rendering/renderer_canvas_render.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ class RendererCanvasRender {
321321
bool update_when_visible : 1;
322322
bool on_interpolate_transform_list : 1;
323323
bool interpolated : 1;
324+
bool use_identity_transform : 1;
324325

325326
struct CanvasGroup {
326327
RS::CanvasGroupMode mode;
@@ -486,6 +487,7 @@ class RendererCanvasRender {
486487
repeat_source = false;
487488
on_interpolate_transform_list = false;
488489
interpolated = true;
490+
use_identity_transform = false;
489491
}
490492
virtual ~Item() {
491493
clear();

0 commit comments

Comments
 (0)