Physics Interpolation - add support for CPUParticles2D
Similar to the existing 3D CPUParticles physics interpolation.
This commit is contained in:
parent
b776cf5498
commit
a117a3307a
11 changed files with 371 additions and 179 deletions
|
@ -714,6 +714,24 @@ int CanvasItem::get_light_mask() const {
|
|||
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) {
|
||||
if (p_size_changed) {
|
||||
update();
|
||||
|
|
|
@ -237,6 +237,7 @@ 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();
|
||||
|
|
|
@ -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.");
|
||||
|
||||
particles.resize(p_amount);
|
||||
particles_prev.resize(p_amount);
|
||||
{
|
||||
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));
|
||||
// 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.
|
||||
|
||||
for (int i = 0; i < p_amount; i++) {
|
||||
particles_prev[i].blank();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// being sent to the visual server with set_as_bulk_array,
|
||||
// if this is sent before being regularly updated.
|
||||
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);
|
||||
|
||||
|
@ -95,6 +102,18 @@ void CPUParticles2D::set_lifetime_randomness(float 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 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) {
|
||||
|
@ -519,13 +538,24 @@ static float rand_from_seed(uint32_t &seed) {
|
|||
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()) {
|
||||
_set_redraw(false);
|
||||
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) {
|
||||
inactive_time = 0;
|
||||
} else {
|
||||
|
@ -584,6 +614,117 @@ void CPUParticles2D::_update_internal() {
|
|||
}
|
||||
|
||||
_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) {
|
||||
|
@ -622,6 +763,12 @@ void CPUParticles2D::_particles_process(float p_delta) {
|
|||
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;
|
||||
|
||||
// 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;
|
||||
tv = 1.0;
|
||||
} else {
|
||||
uint32_t alt_seed = p.seed;
|
||||
|
||||
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;
|
||||
_particle_process(p, emission_xform, local_delta, tv);
|
||||
}
|
||||
//apply color
|
||||
//apply hue rotation
|
||||
|
@ -937,6 +987,13 @@ void CPUParticles2D::_particles_process(float p_delta) {
|
|||
p.transform.elements[1] *= base_scale;
|
||||
|
||||
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();
|
||||
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) {
|
||||
ow = particle_order.write();
|
||||
order = ow.ptr();
|
||||
|
@ -967,63 +1033,94 @@ void CPUParticles2D::_update_particle_data_buffer() {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < pc; i++) {
|
||||
int idx = order ? order[i] : i;
|
||||
|
||||
Transform2D t = r[idx].transform;
|
||||
|
||||
if (_interpolated) {
|
||||
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;
|
||||
_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) {
|
||||
t = inv_emission_transform * t;
|
||||
}
|
||||
|
||||
if (r[idx].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];
|
||||
|
||||
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];
|
||||
|
||||
inv_emission_transform = get_global_transform().affine_inverse();
|
||||
for (int i = 0; i < pc; i++) {
|
||||
int idx = order ? order[i] : i;
|
||||
_fill_particle_data<true>(r[idx], ptr, r[idx].active);
|
||||
ptr += 13;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
ptr += 13;
|
||||
#else
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (redraw == p_redraw) {
|
||||
return;
|
||||
}
|
||||
redraw = p_redraw;
|
||||
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) {
|
||||
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()->multimesh_set_visible_instances(multimesh, -1);
|
||||
} 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()->multimesh_set_visible_instances(multimesh, 0);
|
||||
|
@ -1035,7 +1132,11 @@ void CPUParticles2D::_set_redraw(bool p_redraw) {
|
|||
void CPUParticles2D::_update_render_thread() {
|
||||
if (OS::get_singleton()->is_update_pending(true)) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1043,6 +1144,17 @@ void CPUParticles2D::_update_render_thread() {
|
|||
void CPUParticles2D::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
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) {
|
||||
|
@ -1051,8 +1163,8 @@ void CPUParticles2D::_notification(int p_what) {
|
|||
|
||||
if (p_what == NOTIFICATION_DRAW) {
|
||||
// first update before rendering to avoid one frame delay after emitting starts
|
||||
if (emitting && (time == 0)) {
|
||||
_update_internal();
|
||||
if (emitting && (time == 0) && !_interpolated) {
|
||||
_update_internal(false);
|
||||
}
|
||||
|
||||
if (!redraw) {
|
||||
|
@ -1073,39 +1185,10 @@ void CPUParticles2D::_notification(int p_what) {
|
|||
}
|
||||
|
||||
if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
|
||||
_update_internal();
|
||||
_update_internal(false);
|
||||
}
|
||||
|
||||
if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
|
||||
_update_internal(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include "scene/2d/node_2d.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
#define GODOT_CPU_PARTICLES_2D_LEGACY_COMPATIBILITY
|
||||
|
||||
class CPUParticles2D : public Node2D {
|
||||
private:
|
||||
GDCLASS(CPUParticles2D, Node2D);
|
||||
|
@ -81,12 +83,25 @@ public:
|
|||
private:
|
||||
bool emitting;
|
||||
|
||||
// warning - beware of adding non-trivial types
|
||||
// to this structure as it is zeroed to initialize in set_amount()
|
||||
struct Particle {
|
||||
struct ParticleBase {
|
||||
void blank() {
|
||||
for (int n = 0; n < 4; n++) {
|
||||
custom[n] = 0.0f;
|
||||
}
|
||||
}
|
||||
Transform2D transform;
|
||||
Color color;
|
||||
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;
|
||||
Vector2 velocity;
|
||||
bool active;
|
||||
|
@ -112,7 +127,9 @@ private:
|
|||
RID multimesh;
|
||||
|
||||
PoolVector<Particle> particles;
|
||||
LocalVector<ParticleBase> particles_prev;
|
||||
PoolVector<float> particle_data;
|
||||
PoolVector<float> particle_data_prev;
|
||||
PoolVector<int> particle_order;
|
||||
|
||||
struct SortLifetime {
|
||||
|
@ -177,11 +194,13 @@ private:
|
|||
|
||||
Vector2 gravity;
|
||||
|
||||
void _update_internal();
|
||||
void _update_internal(bool p_on_physics_tick);
|
||||
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();
|
||||
|
||||
Mutex update_mutex;
|
||||
bool _interpolated = false;
|
||||
|
||||
void _update_render_thread();
|
||||
|
||||
|
@ -190,6 +209,46 @@ private:
|
|||
void _set_redraw(bool p_redraw);
|
||||
|
||||
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:
|
||||
static void _bind_methods();
|
||||
|
|
|
@ -304,7 +304,9 @@ void Node2D::set_transform(const Transform2D &p_transform) {
|
|||
_mat = p_transform;
|
||||
_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()) {
|
||||
return;
|
||||
|
|
|
@ -995,6 +995,7 @@ public:
|
|||
bool light_masked : 1;
|
||||
bool on_interpolate_transform_list : 1;
|
||||
bool interpolated : 1;
|
||||
bool ignore_parent_xform : 1;
|
||||
mutable bool custom_rect : 1;
|
||||
mutable bool rect_dirty : 1;
|
||||
mutable bool bound_dirty : 1;
|
||||
|
@ -1236,6 +1237,7 @@ public:
|
|||
update_when_visible = false;
|
||||
on_interpolate_transform_list = false;
|
||||
interpolated = true;
|
||||
ignore_parent_xform = false;
|
||||
local_bound_last_update_tick = 0;
|
||||
}
|
||||
virtual ~Item() {
|
||||
|
|
|
@ -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_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
|
||||
|
||||
_current_camera_transform = p_transform;
|
||||
|
||||
if (_canvas_cull_mode == CANVAS_CULL_MODE_NODE) {
|
||||
_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);
|
||||
|
@ -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();
|
||||
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);
|
||||
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();
|
||||
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);
|
||||
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_last_list, 0, z_range * sizeof(RasterizerCanvas::Item *));
|
||||
|
||||
_current_camera_transform = p_transform;
|
||||
|
||||
#ifdef VISUAL_SERVER_CANVAS_TIME_NODE_CULLING
|
||||
bool measure = (Engine::get_singleton()->get_frames_drawn() % 100) == 0;
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
Item *canvas_item = canvas_item_owner.getornull(p_item);
|
||||
ERR_FAIL_COND(!canvas_item);
|
||||
|
|
|
@ -170,6 +170,7 @@ private:
|
|||
|
||||
RasterizerCanvas::Item **z_list;
|
||||
RasterizerCanvas::Item **z_last_list;
|
||||
Transform2D _current_camera_transform;
|
||||
|
||||
// 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);
|
||||
|
@ -226,6 +227,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_ignore_parent_transform(RID p_item, bool p_enable);
|
||||
|
||||
void canvas_item_set_update_when_visible(RID p_item, bool p_update);
|
||||
|
||||
|
|
|
@ -690,6 +690,7 @@ public:
|
|||
BIND2(canvas_item_set_self_modulate, RID, const Color &)
|
||||
|
||||
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)
|
||||
BIND5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)
|
||||
|
|
|
@ -594,6 +594,7 @@ public:
|
|||
FUNC2(canvas_item_set_self_modulate, RID, const Color &)
|
||||
|
||||
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)
|
||||
FUNC5(canvas_item_add_polyline, RID, const Vector<Point2> &, const Vector<Color> &, float, bool)
|
||||
|
|
|
@ -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_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 {
|
||||
NINE_PATCH_STRETCH,
|
||||
|
|
Loading…
Reference in a new issue