Physics Interpolation - add support for CPUParticles2D

Similar to the existing 3D CPUParticles physics interpolation.
This commit is contained in:
lawnjelly 2023-07-11 11:45:23 +01:00
parent b776cf5498
commit a117a3307a
11 changed files with 371 additions and 179 deletions

View file

@ -714,6 +714,24 @@ int CanvasItem::get_light_mask() const {
return light_mask; return light_mask;
} }
void CanvasItem::set_canvas_item_use_identity_transform(bool p_enable) {
// Prevent sending item transforms to VisualServer when using global coords.
_set_use_identity_transform(p_enable);
// Let VisualServer know not to concatenate the parent transform during the render.
VisualServer::get_singleton()->canvas_item_set_ignore_parent_transform(get_canvas_item(), p_enable);
if (is_inside_tree()) {
if (p_enable) {
// Make sure item is using identity transform in server.
VisualServer::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.
VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), get_transform());
}
}
}
void CanvasItem::item_rect_changed(bool p_size_changed) { void CanvasItem::item_rect_changed(bool p_size_changed) {
if (p_size_changed) { if (p_size_changed) {
update(); update();

View file

@ -237,6 +237,7 @@ protected:
} }
void item_rect_changed(bool p_size_changed = true); void item_rect_changed(bool p_size_changed = true);
void set_canvas_item_use_identity_transform(bool p_enable);
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();

View file

@ -51,6 +51,7 @@ void CPUParticles2D::set_amount(int p_amount) {
ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles must be greater than 0."); ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles must be greater than 0.");
particles.resize(p_amount); particles.resize(p_amount);
particles_prev.resize(p_amount);
{ {
PoolVector<Particle>::Write w = particles.write(); PoolVector<Particle>::Write w = particles.write();
@ -59,13 +60,19 @@ void CPUParticles2D::set_amount(int p_amount) {
memset(static_cast<void *>(&w[0]), 0, p_amount * sizeof(Particle)); memset(static_cast<void *>(&w[0]), 0, p_amount * sizeof(Particle));
// cast to prevent compiler warning .. note this relies on Particle not containing any complex types. // cast to prevent compiler warning .. note this relies on Particle not containing any complex types.
// an alternative is to use some zero method per item but the generated code will be far less efficient. // an alternative is to use some zero method per item but the generated code will be far less efficient.
for (int i = 0; i < p_amount; i++) {
particles_prev[i].blank();
}
} }
particle_data.resize((8 + 4 + 1) * p_amount); particle_data.resize((8 + 4 + 1) * p_amount);
particle_data_prev.resize(particle_data.size());
// We must fill immediately to prevent garbage data and Nans // We must fill immediately to prevent garbage data and Nans
// being sent to the visual server with set_as_bulk_array, // being sent to the visual server with set_as_bulk_array,
// if this is sent before being regularly updated. // if this is sent before being regularly updated.
particle_data.fill(0); particle_data.fill(0);
particle_data_prev.fill(0);
VS::get_singleton()->multimesh_allocate(multimesh, p_amount, VS::MULTIMESH_TRANSFORM_2D, VS::MULTIMESH_COLOR_8BIT, VS::MULTIMESH_CUSTOM_DATA_FLOAT); VS::get_singleton()->multimesh_allocate(multimesh, p_amount, VS::MULTIMESH_TRANSFORM_2D, VS::MULTIMESH_COLOR_8BIT, VS::MULTIMESH_CUSTOM_DATA_FLOAT);
@ -95,6 +102,18 @@ void CPUParticles2D::set_lifetime_randomness(float p_random) {
void CPUParticles2D::set_use_local_coordinates(bool p_enable) { void CPUParticles2D::set_use_local_coordinates(bool p_enable) {
local_coords = p_enable; local_coords = p_enable;
set_notify_transform(!p_enable); set_notify_transform(!p_enable);
// Prevent sending item transforms when using global coords,
// and inform VisualServer to use identity mode.
#ifdef GODOT_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY
set_canvas_item_use_identity_transform((_interpolated) && (!p_enable));
// Always reset this, as it is unused when interpolation is on.
// (i.e. We do particles in global space, rather than pseudo globalspace.)
inv_emission_transform = Transform2D();
#else
set_canvas_item_use_identity_transform(!p_enable);
#endif
} }
void CPUParticles2D::set_speed_scale(float p_scale) { void CPUParticles2D::set_speed_scale(float p_scale) {
@ -519,13 +538,24 @@ static float rand_from_seed(uint32_t &seed) {
return float(seed % uint32_t(65536)) / 65535.0; return float(seed % uint32_t(65536)) / 65535.0;
} }
void CPUParticles2D::_update_internal() { void CPUParticles2D::_update_internal(bool p_on_physics_tick) {
if (particles.size() == 0 || !is_visible_in_tree()) { if (particles.size() == 0 || !is_visible_in_tree()) {
_set_redraw(false); _set_redraw(false);
return; return;
} }
float delta = get_process_delta_time(); // Change update mode?
_refresh_interpolation_state();
float delta = 0.0f;
// Is this update occurring on a physics tick (i.e. interpolated), or a frame tick?
if (p_on_physics_tick) {
delta = get_physics_process_delta_time();
} else {
delta = get_process_delta_time();
}
if (emitting) { if (emitting) {
inactive_time = 0; inactive_time = 0;
} else { } else {
@ -584,6 +614,117 @@ void CPUParticles2D::_update_internal() {
} }
_update_particle_data_buffer(); _update_particle_data_buffer();
// If we are interpolating, we send the data to the VisualServer
// right away on a physics tick instead of waiting until a render frame.
if (p_on_physics_tick && redraw) {
_update_render_thread();
}
}
void CPUParticles2D::_particle_process(Particle &r_p, const Transform2D &p_emission_xform, float p_local_delta, float &r_tv) {
uint32_t alt_seed = r_p.seed;
r_p.time += p_local_delta;
r_p.custom[1] = r_p.time / lifetime;
r_tv = r_p.time / r_p.lifetime;
float tex_linear_velocity = 0.0;
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(r_tv);
}
float tex_orbit_velocity = 0.0;
if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(r_tv);
}
float tex_angular_velocity = 0.0;
if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(r_tv);
}
float tex_linear_accel = 0.0;
if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) {
tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(r_tv);
}
float tex_tangential_accel = 0.0;
if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) {
tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(r_tv);
}
float tex_radial_accel = 0.0;
if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) {
tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(r_tv);
}
float tex_damping = 0.0;
if (curve_parameters[PARAM_DAMPING].is_valid()) {
tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(r_tv);
}
float tex_angle = 0.0;
if (curve_parameters[PARAM_ANGLE].is_valid()) {
tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(r_tv);
}
float tex_anim_speed = 0.0;
if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) {
tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(r_tv);
}
float tex_anim_offset = 0.0;
if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) {
tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(r_tv);
}
Vector2 force = gravity;
Vector2 pos = r_p.transform[2];
// Apply linear acceleration.
force += r_p.velocity.length() > 0.0 ? r_p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector2();
// Apply radial acceleration.
Vector2 org = p_emission_xform[2];
Vector2 diff = pos - org;
force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2();
// Apply tangential acceleration.
Vector2 yx = Vector2(diff.y, diff.x);
force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2();
// Apply attractor forces.
r_p.velocity += force * p_local_delta;
// Orbit velocity.
float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]);
if (orbit_amount != 0.0) {
float ang = orbit_amount * p_local_delta * Math_PI * 2.0;
// Not sure why the ParticlesMaterial code uses a clockwise rotation matrix,
// but we use -ang here to reproduce its behavior.
Transform2D rot = Transform2D(-ang, Vector2());
r_p.transform[2] -= diff;
r_p.transform[2] += rot.basis_xform(diff);
}
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
r_p.velocity = r_p.velocity.normalized() * tex_linear_velocity;
}
if (parameters[PARAM_DAMPING] + tex_damping > 0.0) {
float v = r_p.velocity.length();
float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]);
v -= damp * p_local_delta;
if (v < 0.0) {
r_p.velocity = Vector2();
} else {
r_p.velocity = r_p.velocity.normalized() * v;
}
}
float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, r_p.angle_rand, randomness[PARAM_ANGLE]);
base_angle += r_p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]);
r_p.rotation = Math::deg2rad(base_angle); //angle
float animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, r_p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + r_tv * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]);
r_p.custom[2] = animation_phase;
} }
void CPUParticles2D::_particles_process(float p_delta) { void CPUParticles2D::_particles_process(float p_delta) {
@ -622,6 +763,12 @@ void CPUParticles2D::_particles_process(float p_delta) {
continue; continue;
} }
// For interpolation we need to keep a record of previous particles.
if (_interpolated) {
DEV_ASSERT((uint32_t)particles.size() == particles_prev.size());
p.copy_to(particles_prev[i]);
}
float local_delta = p_delta; float local_delta = p_delta;
// The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle.
@ -777,104 +924,7 @@ void CPUParticles2D::_particles_process(float p_delta) {
p.active = false; p.active = false;
tv = 1.0; tv = 1.0;
} else { } else {
uint32_t alt_seed = p.seed; _particle_process(p, emission_xform, local_delta, tv);
p.time += local_delta;
p.custom[1] = p.time / lifetime;
tv = p.time / p.lifetime;
float tex_linear_velocity = 0.0;
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv);
}
float tex_orbit_velocity = 0.0;
if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv);
}
float tex_angular_velocity = 0.0;
if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv);
}
float tex_linear_accel = 0.0;
if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) {
tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv);
}
float tex_tangential_accel = 0.0;
if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) {
tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv);
}
float tex_radial_accel = 0.0;
if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) {
tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv);
}
float tex_damping = 0.0;
if (curve_parameters[PARAM_DAMPING].is_valid()) {
tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv);
}
float tex_angle = 0.0;
if (curve_parameters[PARAM_ANGLE].is_valid()) {
tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv);
}
float tex_anim_speed = 0.0;
if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) {
tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv);
}
float tex_anim_offset = 0.0;
if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) {
tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv);
}
Vector2 force = gravity;
Vector2 pos = p.transform[2];
//apply linear acceleration
force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector2();
//apply radial acceleration
Vector2 org = emission_xform[2];
Vector2 diff = pos - org;
force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2();
//apply tangential acceleration;
Vector2 yx = Vector2(diff.y, diff.x);
force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2();
//apply attractor forces
p.velocity += force * local_delta;
//orbit velocity
float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]);
if (orbit_amount != 0.0) {
float ang = orbit_amount * local_delta * Math_PI * 2.0;
// Not sure why the ParticlesMaterial code uses a clockwise rotation matrix,
// but we use -ang here to reproduce its behavior.
Transform2D rot = Transform2D(-ang, Vector2());
p.transform[2] -= diff;
p.transform[2] += rot.basis_xform(diff);
}
if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
p.velocity = p.velocity.normalized() * tex_linear_velocity;
}
if (parameters[PARAM_DAMPING] + tex_damping > 0.0) {
float v = p.velocity.length();
float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]);
v -= damp * local_delta;
if (v < 0.0) {
p.velocity = Vector2();
} else {
p.velocity = p.velocity.normalized() * v;
}
}
float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]);
base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]);
p.rotation = Math::deg2rad(base_angle); //angle
float animation_phase = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + tv * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]);
p.custom[2] = animation_phase;
} }
//apply color //apply color
//apply hue rotation //apply hue rotation
@ -937,6 +987,13 @@ void CPUParticles2D::_particles_process(float p_delta) {
p.transform.elements[1] *= base_scale; p.transform.elements[1] *= base_scale;
p.transform[2] += p.velocity * local_delta; p.transform[2] += p.velocity * local_delta;
// Teleport if starting a new particle, so
// we don't get a streak from the old position
// to this new start.
if (restart && _interpolated) {
p.copy_to(particles_prev[i]);
}
} }
} }
@ -953,6 +1010,15 @@ void CPUParticles2D::_update_particle_data_buffer() {
PoolVector<Particle>::Read r = particles.read(); PoolVector<Particle>::Read r = particles.read();
float *ptr = w.ptr(); float *ptr = w.ptr();
PoolVector<float>::Write w_prev;
float *ptr_prev = nullptr;
if (_interpolated) {
DEV_ASSERT(particle_data.size() == particle_data_prev.size());
w_prev = particle_data_prev.write();
ptr_prev = w_prev.ptr();
}
if (draw_order != DRAW_ORDER_INDEX) { if (draw_order != DRAW_ORDER_INDEX) {
ow = particle_order.write(); ow = particle_order.write();
order = ow.ptr(); order = ow.ptr();
@ -967,63 +1033,94 @@ void CPUParticles2D::_update_particle_data_buffer() {
} }
} }
for (int i = 0; i < pc; i++) { if (_interpolated) {
int idx = order ? order[i] : i; for (int i = 0; i < pc; i++) {
int idx = order ? order[i] : i;
Transform2D t = r[idx].transform; _fill_particle_data<false>(r[idx], ptr, r[idx].active);
ptr += 13;
_fill_particle_data<false>(particles_prev[idx], ptr_prev, r[idx].active);
ptr_prev += 13;
}
} else {
#ifdef GODOT_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY
if (!local_coords) { if (!local_coords) {
t = inv_emission_transform * t; inv_emission_transform = get_global_transform().affine_inverse();
} for (int i = 0; i < pc; i++) {
int idx = order ? order[i] : i;
if (r[idx].active) { _fill_particle_data<true>(r[idx], ptr, r[idx].active);
ptr[0] = t.elements[0][0]; ptr += 13;
ptr[1] = t.elements[1][0]; }
ptr[2] = 0;
ptr[3] = t.elements[2][0];
ptr[4] = t.elements[0][1];
ptr[5] = t.elements[1][1];
ptr[6] = 0;
ptr[7] = t.elements[2][1];
Color c = r[idx].color;
uint8_t *data8 = (uint8_t *)&ptr[8];
data8[0] = CLAMP(c.r * 255.0, 0, 255);
data8[1] = CLAMP(c.g * 255.0, 0, 255);
data8[2] = CLAMP(c.b * 255.0, 0, 255);
data8[3] = CLAMP(c.a * 255.0, 0, 255);
ptr[9] = r[idx].custom[0];
ptr[10] = r[idx].custom[1];
ptr[11] = r[idx].custom[2];
ptr[12] = r[idx].custom[3];
} else { } else {
memset(ptr, 0, sizeof(float) * 13); for (int i = 0; i < pc; i++) {
int idx = order ? order[i] : i;
_fill_particle_data<false>(r[idx], ptr, r[idx].active);
ptr += 13;
}
} }
#else
ptr += 13; for (int i = 0; i < pc; i++) {
int idx = order ? order[i] : i;
_fill_particle_data<false>(r[idx], ptr, r[idx].active);
ptr += 13;
}
#endif
} }
} }
update_mutex.unlock(); update_mutex.unlock();
} }
void CPUParticles2D::_refresh_interpolation_state() {
if (!is_inside_tree()) {
return;
}
bool interpolated = is_physics_interpolated_and_enabled();
if (_interpolated == interpolated) {
return;
}
bool curr_redraw = redraw;
// Remove all connections.
// This isn't super efficient, but should only happen rarely.
_set_redraw(false);
_interpolated = interpolated;
#ifdef GODOT_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);
// Re-establish all connections.
_set_redraw(curr_redraw);
}
void CPUParticles2D::_set_redraw(bool p_redraw) { void CPUParticles2D::_set_redraw(bool p_redraw) {
if (redraw == p_redraw) { if (redraw == p_redraw) {
return; return;
} }
redraw = p_redraw; redraw = p_redraw;
update_mutex.lock(); update_mutex.lock();
if (!_interpolated) {
if (redraw) {
VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread");
} else {
if (VS::get_singleton()->is_connected("frame_pre_draw", this, "_update_render_thread")) {
VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread");
}
}
}
if (redraw) { if (redraw) {
VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread");
VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true); VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true);
VS::get_singleton()->multimesh_set_visible_instances(multimesh, -1); VS::get_singleton()->multimesh_set_visible_instances(multimesh, -1);
} else { } else {
if (VS::get_singleton()->is_connected("frame_pre_draw", this, "_update_render_thread")) {
VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread");
}
VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false); VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false);
VS::get_singleton()->multimesh_set_visible_instances(multimesh, 0); VS::get_singleton()->multimesh_set_visible_instances(multimesh, 0);
@ -1035,7 +1132,11 @@ void CPUParticles2D::_set_redraw(bool p_redraw) {
void CPUParticles2D::_update_render_thread() { void CPUParticles2D::_update_render_thread() {
if (OS::get_singleton()->is_update_pending(true)) { if (OS::get_singleton()->is_update_pending(true)) {
update_mutex.lock(); update_mutex.lock();
VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); if (_interpolated) {
VS::get_singleton()->multimesh_set_as_bulk_array_interpolated(multimesh, particle_data, particle_data_prev);
} else {
VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data);
}
update_mutex.unlock(); update_mutex.unlock();
} }
} }
@ -1043,6 +1144,17 @@ void CPUParticles2D::_update_render_thread() {
void CPUParticles2D::_notification(int p_what) { void CPUParticles2D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) { if (p_what == NOTIFICATION_ENTER_TREE) {
set_process_internal(emitting); set_process_internal(emitting);
// For interpolated version to update the particles right away,
// we need a sequence of events.
// First ensure we are in _interpolated mode if the Node is set to interpolated.
_refresh_interpolation_state();
// Now, if we are interpolating, we want to force a single tick update.
// If we don't do this, it may be an entire tick before the first update happens.
if (_interpolated) {
_update_internal(true);
}
} }
if (p_what == NOTIFICATION_EXIT_TREE) { if (p_what == NOTIFICATION_EXIT_TREE) {
@ -1051,8 +1163,8 @@ void CPUParticles2D::_notification(int p_what) {
if (p_what == NOTIFICATION_DRAW) { if (p_what == NOTIFICATION_DRAW) {
// first update before rendering to avoid one frame delay after emitting starts // first update before rendering to avoid one frame delay after emitting starts
if (emitting && (time == 0)) { if (emitting && (time == 0) && !_interpolated) {
_update_internal(); _update_internal(false);
} }
if (!redraw) { if (!redraw) {
@ -1073,39 +1185,10 @@ void CPUParticles2D::_notification(int p_what) {
} }
if (p_what == NOTIFICATION_INTERNAL_PROCESS) { if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
_update_internal(); _update_internal(false);
} }
if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { _update_internal(true);
inv_emission_transform = get_global_transform().affine_inverse();
if (!local_coords) {
int pc = particles.size();
PoolVector<float>::Write w = particle_data.write();
PoolVector<Particle>::Read r = particles.read();
float *ptr = w.ptr();
for (int i = 0; i < pc; i++) {
Transform2D t = inv_emission_transform * r[i].transform;
if (r[i].active) {
ptr[0] = t.elements[0][0];
ptr[1] = t.elements[1][0];
ptr[2] = 0;
ptr[3] = t.elements[2][0];
ptr[4] = t.elements[0][1];
ptr[5] = t.elements[1][1];
ptr[6] = 0;
ptr[7] = t.elements[2][1];
} else {
memset(ptr, 0, sizeof(float) * 8);
}
ptr += 13;
}
}
} }
} }

View file

@ -35,6 +35,8 @@
#include "scene/2d/node_2d.h" #include "scene/2d/node_2d.h"
#include "scene/resources/texture.h" #include "scene/resources/texture.h"
#define GODOT_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY
class CPUParticles2D : public Node2D { class CPUParticles2D : public Node2D {
private: private:
GDCLASS(CPUParticles2D, Node2D); GDCLASS(CPUParticles2D, Node2D);
@ -81,12 +83,25 @@ public:
private: private:
bool emitting; bool emitting;
// warning - beware of adding non-trivial types struct ParticleBase {
// to this structure as it is zeroed to initialize in set_amount() void blank() {
struct Particle { for (int n = 0; n < 4; n++) {
custom[n] = 0.0f;
}
}
Transform2D transform; Transform2D transform;
Color color; Color color;
float custom[4]; float custom[4];
};
// Warning - beware of adding non-trivial types
// to this structure as it is zeroed to initialize in set_amount().
struct Particle : public ParticleBase {
void copy_to(ParticleBase &r_o) {
r_o.transform = transform;
r_o.color = color;
memcpy(r_o.custom, custom, sizeof(custom));
}
float rotation; float rotation;
Vector2 velocity; Vector2 velocity;
bool active; bool active;
@ -112,7 +127,9 @@ private:
RID multimesh; RID multimesh;
PoolVector<Particle> particles; PoolVector<Particle> particles;
LocalVector<ParticleBase> particles_prev;
PoolVector<float> particle_data; PoolVector<float> particle_data;
PoolVector<float> particle_data_prev;
PoolVector<int> particle_order; PoolVector<int> particle_order;
struct SortLifetime { struct SortLifetime {
@ -177,11 +194,13 @@ private:
Vector2 gravity; Vector2 gravity;
void _update_internal(); void _update_internal(bool p_on_physics_tick);
void _particles_process(float p_delta); void _particles_process(float p_delta);
void _particle_process(Particle &r_p, const Transform2D &p_emission_xform, float p_local_delta, float &r_tv);
void _update_particle_data_buffer(); void _update_particle_data_buffer();
Mutex update_mutex; Mutex update_mutex;
bool _interpolated = false;
void _update_render_thread(); void _update_render_thread();
@ -190,6 +209,46 @@ private:
void _set_redraw(bool p_redraw); void _set_redraw(bool p_redraw);
void _texture_changed(); void _texture_changed();
void _refresh_interpolation_state();
template <bool TRANSFORM_PARTICLE>
void _fill_particle_data(const ParticleBase &p_source, float *r_dest, bool p_active) const {
if (p_active) {
#ifdef GODOT_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY
Transform2D t = p_source.transform;
if (TRANSFORM_PARTICLE) {
t = inv_emission_transform * t;
}
#else
const Transform2D &t = p_source.transform;
#endif
r_dest[0] = t.elements[0][0];
r_dest[1] = t.elements[1][0];
r_dest[2] = 0;
r_dest[3] = t.elements[2][0];
r_dest[4] = t.elements[0][1];
r_dest[5] = t.elements[1][1];
r_dest[6] = 0;
r_dest[7] = t.elements[2][1];
Color c = p_source.color;
uint8_t *data8 = (uint8_t *)&r_dest[8];
data8[0] = CLAMP(c.r * 255.0, 0, 255);
data8[1] = CLAMP(c.g * 255.0, 0, 255);
data8[2] = CLAMP(c.b * 255.0, 0, 255);
data8[3] = CLAMP(c.a * 255.0, 0, 255);
r_dest[9] = p_source.custom[0];
r_dest[10] = p_source.custom[1];
r_dest[11] = p_source.custom[2];
r_dest[12] = p_source.custom[3];
} else {
memset(r_dest, 0, sizeof(float) * 13);
}
}
protected: protected:
static void _bind_methods(); static void _bind_methods();

View file

@ -304,7 +304,9 @@ void Node2D::set_transform(const Transform2D &p_transform) {
_mat = p_transform; _mat = p_transform;
_xform_dirty = true; _xform_dirty = true;
VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat); if (!_is_using_identity_transform()) {
VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat);
}
if (!is_inside_tree()) { if (!is_inside_tree()) {
return; return;

View file

@ -995,6 +995,7 @@ public:
bool light_masked : 1; bool light_masked : 1;
bool on_interpolate_transform_list : 1; bool on_interpolate_transform_list : 1;
bool interpolated : 1; bool interpolated : 1;
bool ignore_parent_xform : 1;
mutable bool custom_rect : 1; mutable bool custom_rect : 1;
mutable bool rect_dirty : 1; mutable bool rect_dirty : 1;
mutable bool bound_dirty : 1; mutable bool bound_dirty : 1;
@ -1236,6 +1237,7 @@ public:
update_when_visible = false; update_when_visible = false;
on_interpolate_transform_list = false; on_interpolate_transform_list = false;
interpolated = true; interpolated = true;
ignore_parent_xform = false;
local_bound_last_update_tick = 0; local_bound_last_update_tick = 0;
} }
virtual ~Item() { virtual ~Item() {

View file

@ -40,6 +40,8 @@ void VisualServerCanvas::_render_canvas_item_tree(Item *p_canvas_item, const Tra
memset(z_list, 0, z_range * sizeof(RasterizerCanvas::Item *)); memset(z_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
memset(z_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *)); memset(z_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
_current_camera_transform = p_transform;
if (_canvas_cull_mode == CANVAS_CULL_MODE_NODE) { if (_canvas_cull_mode == CANVAS_CULL_MODE_NODE) {
_prepare_tree_bounds(p_canvas_item); _prepare_tree_bounds(p_canvas_item);
_render_canvas_item_cull_by_node(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false); _render_canvas_item_cull_by_node(p_canvas_item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false);
@ -337,7 +339,12 @@ void VisualServerCanvas::_render_canvas_item_cull_by_item(Item *p_canvas_item, c
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
} }
final_xform = p_transform * final_xform;
if (!p_canvas_item->ignore_parent_xform) {
final_xform = p_transform * final_xform;
} else {
final_xform = _current_camera_transform * final_xform;
}
Rect2 global_rect = final_xform.xform(rect); Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position; global_rect.position += p_clip_rect.position;
@ -473,7 +480,12 @@ void VisualServerCanvas::_render_canvas_item_cull_by_node(Item *p_canvas_item, c
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
} }
final_xform = p_transform * final_xform;
if (!p_canvas_item->ignore_parent_xform) {
final_xform = p_transform * final_xform;
} else {
final_xform = _current_camera_transform * final_xform;
}
Rect2 global_rect = final_xform.xform(rect); Rect2 global_rect = final_xform.xform(rect);
ci->global_rect_cache = global_rect; ci->global_rect_cache = global_rect;
@ -667,6 +679,8 @@ void VisualServerCanvas::render_canvas(Canvas *p_canvas, const Transform2D &p_tr
memset(z_list, 0, z_range * sizeof(RasterizerCanvas::Item *)); memset(z_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
memset(z_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *)); memset(z_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
_current_camera_transform = p_transform;
#ifdef VISUAL_SERVER_CANVAS_TIME_NODE_CULLING #ifdef VISUAL_SERVER_CANVAS_TIME_NODE_CULLING
bool measure = (Engine::get_singleton()->get_frames_drawn() % 100) == 0; bool measure = (Engine::get_singleton()->get_frames_drawn() % 100) == 0;
measure &= !Engine::get_singleton()->is_editor_hint(); measure &= !Engine::get_singleton()->is_editor_hint();
@ -941,6 +955,14 @@ void VisualServerCanvas::canvas_item_set_draw_behind_parent(RID p_item, bool p_e
_check_bound_integrity(canvas_item); _check_bound_integrity(canvas_item);
} }
void VisualServerCanvas::canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) {
Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item);
canvas_item->ignore_parent_xform = p_enable;
_make_bound_dirty(canvas_item);
}
void VisualServerCanvas::canvas_item_set_update_when_visible(RID p_item, bool p_update) { void VisualServerCanvas::canvas_item_set_update_when_visible(RID p_item, bool p_update) {
Item *canvas_item = canvas_item_owner.getornull(p_item); Item *canvas_item = canvas_item_owner.getornull(p_item);
ERR_FAIL_COND(!canvas_item); ERR_FAIL_COND(!canvas_item);

View file

@ -170,6 +170,7 @@ private:
RasterizerCanvas::Item **z_list; RasterizerCanvas::Item **z_list;
RasterizerCanvas::Item **z_last_list; RasterizerCanvas::Item **z_last_list;
Transform2D _current_camera_transform;
// 3.5 and earlier had no hierarchical culling. // 3.5 and earlier had no hierarchical culling.
void _render_canvas_item_cull_by_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RasterizerCanvas::Item **z_list, RasterizerCanvas::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner); void _render_canvas_item_cull_by_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RasterizerCanvas::Item **z_list, RasterizerCanvas::Item **z_last_list, Item *p_canvas_clip, Item *p_material_owner);
@ -226,6 +227,7 @@ public:
void canvas_item_set_self_modulate(RID p_item, const Color &p_color); 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_draw_behind_parent(RID p_item, bool p_enable);
void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable);
void canvas_item_set_update_when_visible(RID p_item, bool p_update); void canvas_item_set_update_when_visible(RID p_item, bool p_update);

View file

@ -690,6 +690,7 @@ public:
BIND2(canvas_item_set_self_modulate, RID, const Color &) BIND2(canvas_item_set_self_modulate, RID, const Color &)
BIND2(canvas_item_set_draw_behind_parent, RID, bool) BIND2(canvas_item_set_draw_behind_parent, RID, bool)
BIND2(canvas_item_set_ignore_parent_transform, RID, bool)
BIND6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool) BIND6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, bool)
BIND5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool) BIND5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)

View file

@ -594,6 +594,7 @@ public:
FUNC2(canvas_item_set_self_modulate, RID, const Color &) FUNC2(canvas_item_set_self_modulate, RID, const Color &)
FUNC2(canvas_item_set_draw_behind_parent, RID, bool) FUNC2(canvas_item_set_draw_behind_parent, RID, bool)
FUNC2(canvas_item_set_ignore_parent_transform, RID, bool)
FUNC6(canvas_item_add_line, RID, const Point2 &, const Point2 &, const Color &, float, 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) FUNC5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)

View file

@ -1024,6 +1024,7 @@ public:
virtual void canvas_item_set_self_modulate(RID p_item, const Color &p_color) = 0; virtual void canvas_item_set_self_modulate(RID p_item, const Color &p_color) = 0;
virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0; virtual void canvas_item_set_draw_behind_parent(RID p_item, bool p_enable) = 0;
virtual void canvas_item_set_ignore_parent_transform(RID p_item, bool p_enable) = 0;
enum NinePatchAxisMode { enum NinePatchAxisMode {
NINE_PATCH_STRETCH, NINE_PATCH_STRETCH,