|
32 | 32 | #include "cpu_particles_2d.compat.inc" |
33 | 33 |
|
34 | 34 | #include "core/math/random_number_generator.h" |
| 35 | +#include "core/math/transform_interpolator.h" |
35 | 36 | #include "scene/2d/gpu_particles_2d.h" |
36 | 37 | #include "scene/resources/atlas_texture.h" |
37 | 38 | #include "scene/resources/canvas_item_material.h" |
@@ -96,7 +97,14 @@ void CPUParticles2D::set_lifetime_randomness(double p_random) { |
96 | 97 |
|
97 | 98 | void CPUParticles2D::set_use_local_coordinates(bool p_enable) { |
98 | 99 | 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); |
100 | 108 | } |
101 | 109 |
|
102 | 110 | void CPUParticles2D::set_speed_scale(double p_scale) { |
@@ -228,6 +236,27 @@ void CPUParticles2D::_texture_changed() { |
228 | 236 | } |
229 | 237 | } |
230 | 238 |
|
| 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 | + |
231 | 260 | Ref<Texture2D> CPUParticles2D::get_texture() const { |
232 | 261 | return texture; |
233 | 262 | } |
@@ -600,6 +629,9 @@ void CPUParticles2D::_update_internal() { |
600 | 629 | return; |
601 | 630 | } |
602 | 631 |
|
| 632 | + // Change update mode? |
| 633 | + _refresh_interpolation_state(); |
| 634 | + |
603 | 635 | double delta = get_process_delta_time(); |
604 | 636 | if (!active && !emitting) { |
605 | 637 | set_process_internal(false); |
@@ -679,7 +711,11 @@ void CPUParticles2D::_particles_process(double p_delta) { |
679 | 711 | Transform2D emission_xform; |
680 | 712 | Transform2D velocity_xform; |
681 | 713 | 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 | + } |
683 | 719 | velocity_xform = emission_xform; |
684 | 720 | velocity_xform[2] = Vector2(); |
685 | 721 | } |
@@ -1142,6 +1178,17 @@ void CPUParticles2D::_notification(int p_what) { |
1142 | 1178 | switch (p_what) { |
1143 | 1179 | case NOTIFICATION_ENTER_TREE: { |
1144 | 1180 | 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 | + } |
1145 | 1192 | } break; |
1146 | 1193 |
|
1147 | 1194 | case NOTIFICATION_EXIT_TREE: { |
@@ -1170,37 +1217,28 @@ void CPUParticles2D::_notification(int p_what) { |
1170 | 1217 | _update_internal(); |
1171 | 1218 | } break; |
1172 | 1219 |
|
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; |
1199 | 1227 |
|
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(); |
1201 | 1233 | } |
1202 | 1234 | } |
1203 | 1235 | } 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; |
1204 | 1242 | } |
1205 | 1243 | } |
1206 | 1244 |
|
@@ -1559,6 +1597,12 @@ CPUParticles2D::CPUParticles2D() { |
1559 | 1597 | set_color(Color(1, 1, 1, 1)); |
1560 | 1598 |
|
1561 | 1599 | _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); |
1562 | 1606 | } |
1563 | 1607 |
|
1564 | 1608 | CPUParticles2D::~CPUParticles2D() { |
|
0 commit comments