diff --git a/doc/classes/Particles.xml b/doc/classes/Particles.xml index 90bdf5a2ab46..704f1fc1967a 100644 --- a/doc/classes/Particles.xml +++ b/doc/classes/Particles.xml @@ -79,7 +79,11 @@ If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect. - The amount of time each particle will exist (in seconds). + The amount of time each particle will exist (in seconds). See also [member lifetime_infinite]. + + + If [code]true[/code], particles will be emitted once and will never despawn. Particles can still be manually restarted, either by changing [member amount] or calling [method restart]. + [member lifetime_infinite] is mainly useful for custom particle shaders (such as grass rendering). [member lifetime] is ignored when [member lifetime_infinite] is [code]true[/code]. If [code]true[/code], particles use the parent node's coordinate space. If [code]false[/code], they use global coordinates. diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml index cd0460b974ae..a5316dcd0198 100644 --- a/doc/classes/VisualServer.xml +++ b/doc/classes/VisualServer.xml @@ -2395,6 +2395,13 @@ Sets the lifetime of each particle in the system. Equivalent to [member Particles.lifetime]. + + + + + + + diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 33be5c5fe457..5e374afecfb4 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -676,6 +676,7 @@ class RasterizerStorageDummy : public RasterizerStorage { void particles_set_emitting(RID p_particles, bool p_emitting) {} void particles_set_amount(RID p_particles, int p_amount) {} void particles_set_lifetime(RID p_particles, float p_lifetime) {} + void particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite) {} void particles_set_one_shot(RID p_particles, bool p_one_shot) {} void particles_set_pre_process_time(RID p_particles, float p_time) {} void particles_set_explosiveness_ratio(RID p_particles, float p_ratio) {} diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 144621fb184e..43a9567339f1 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -4840,6 +4840,9 @@ void RasterizerStorageGLES2::particles_set_amount(RID p_particles, int p_amount) void RasterizerStorageGLES2::particles_set_lifetime(RID p_particles, float p_lifetime) { } +void RasterizerStorageGLES2::particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite) { +} + void RasterizerStorageGLES2::particles_set_one_shot(RID p_particles, bool p_one_shot) { } diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index f532c2eacea0..5b223174d2c9 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -1141,6 +1141,7 @@ class RasterizerStorageGLES2 : public RasterizerStorage { virtual void particles_set_amount(RID p_particles, int p_amount); virtual void particles_set_lifetime(RID p_particles, float p_lifetime); + virtual void particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite); virtual void particles_set_one_shot(RID p_particles, bool p_one_shot); virtual void particles_set_pre_process_time(RID p_particles, float p_time); virtual void particles_set_explosiveness_ratio(RID p_particles, float p_ratio); diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 54a288302ee4..20baf6bc0cd2 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -6261,6 +6261,12 @@ void RasterizerStorageGLES3::particles_set_lifetime(RID p_particles, float p_lif particles->lifetime = p_lifetime; } +void RasterizerStorageGLES3::particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite) { + Particles *particles = particles_owner.getornull(p_particles); + ERR_FAIL_COND(!particles); + particles->lifetime_infinite = p_lifetime_infinite; +} + void RasterizerStorageGLES3::particles_set_one_shot(RID p_particles, bool p_one_shot) { Particles *particles = particles_owner.getornull(p_particles); ERR_FAIL_COND(!particles); @@ -6488,12 +6494,17 @@ RID RasterizerStorageGLES3::particles_get_draw_pass_mesh(RID p_particles, int p_ } void RasterizerStorageGLES3::_particles_process(Particles *p_particles, float p_delta) { - float new_phase = Math::fmod((float)p_particles->phase + (p_delta / p_particles->lifetime) * p_particles->speed_scale, (float)1.0); + float new_phase; + if (p_particles->lifetime_infinite) { + new_phase = 0.5; + } else { + new_phase = Math::fmod((float)p_particles->phase + (p_delta / p_particles->lifetime) * p_particles->speed_scale, (float)1.0); + } if (p_particles->clear) { p_particles->cycle_number = 0; p_particles->random_seed = Math::rand(); - } else if (new_phase < p_particles->phase) { + } else if (!p_particles->lifetime_infinite && new_phase < p_particles->phase) { if (p_particles->one_shot) { p_particles->emitting = false; shaders.particles.set_uniform(ParticlesShaderGLES3::EMITTING, false); @@ -6505,7 +6516,11 @@ void RasterizerStorageGLES3::_particles_process(Particles *p_particles, float p_ shaders.particles.set_uniform(ParticlesShaderGLES3::PREV_SYSTEM_PHASE, p_particles->phase); p_particles->phase = new_phase; - shaders.particles.set_uniform(ParticlesShaderGLES3::DELTA, p_delta * p_particles->speed_scale); + if (p_particles->lifetime_infinite) { + shaders.particles.set_uniform(ParticlesShaderGLES3::DELTA, 0.0); + } else { + shaders.particles.set_uniform(ParticlesShaderGLES3::DELTA, p_delta * p_particles->speed_scale); + } shaders.particles.set_uniform(ParticlesShaderGLES3::CLEAR, p_particles->clear); glUniform1ui(shaders.particles.get_uniform_location(ParticlesShaderGLES3::RANDOM_SEED), p_particles->random_seed); @@ -6594,7 +6609,7 @@ void RasterizerStorageGLES3::update_particles() { particles->inactive_time = 0; } else { particles->inactive_time += particles->speed_scale * frame.delta; - if (particles->inactive_time > particles->lifetime * 1.2) { + if (!particles->lifetime_infinite && particles->inactive_time > particles->lifetime * 1.2) { particles->inactive = true; particle_update_list.remove(particle_update_list.first()); continue; @@ -6663,6 +6678,7 @@ void RasterizerStorageGLES3::update_particles() { shaders.particles.set_uniform(ParticlesShaderGLES3::TIME, frame.time[0]); shaders.particles.set_uniform(ParticlesShaderGLES3::EXPLOSIVENESS, particles->explosiveness); shaders.particles.set_uniform(ParticlesShaderGLES3::LIFETIME, particles->lifetime); + shaders.particles.set_uniform(ParticlesShaderGLES3::LIFETIME_INFINITE, particles->lifetime_infinite); shaders.particles.set_uniform(ParticlesShaderGLES3::ATTRACTOR_COUNT, 0); shaders.particles.set_uniform(ParticlesShaderGLES3::EMITTING, particles->emitting); shaders.particles.set_uniform(ParticlesShaderGLES3::RANDOMNESS, particles->randomness); diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index aecc70396a65..a3c697535e60 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -1188,6 +1188,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage { bool one_shot; int amount; float lifetime; + bool lifetime_infinite; float pre_process_time; float explosiveness; float randomness; @@ -1234,6 +1235,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage { one_shot(false), amount(0), lifetime(1.0), + lifetime_infinite(false), pre_process_time(0.0), explosiveness(0.0), randomness(0.0), @@ -1279,6 +1281,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage { virtual bool particles_get_emitting(RID p_particles); virtual void particles_set_amount(RID p_particles, int p_amount); virtual void particles_set_lifetime(RID p_particles, float p_lifetime); + virtual void particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite); virtual void particles_set_one_shot(RID p_particles, bool p_one_shot); virtual void particles_set_pre_process_time(RID p_particles, float p_time); virtual void particles_set_explosiveness_ratio(RID p_particles, float p_ratio); diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl index b09e7faaaebd..ded45bd007d1 100644 --- a/drivers/gles3/shaders/particles.glsl +++ b/drivers/gles3/shaders/particles.glsl @@ -38,6 +38,7 @@ uniform Attractor attractors[MAX_ATTRACTORS]; uniform bool clear; uniform uint cycle; uniform float lifetime; +uniform bool lifetime_infinite; uniform mat4 emission_transform; uniform uint random_seed; @@ -103,7 +104,9 @@ void main() { if (restart_phase >= prev_system_phase && restart_phase < system_phase) { restart = true; #ifdef USE_FRACTIONAL_DELTA //ubershader-runtime - local_delta = (system_phase - restart_phase) * lifetime; + if (!lifetime_infinite) { + local_delta = (system_phase - restart_phase) * lifetime; + } #endif //ubershader-runtime } @@ -111,12 +114,16 @@ void main() { if (restart_phase >= prev_system_phase) { restart = true; #ifdef USE_FRACTIONAL_DELTA //ubershader-runtime - local_delta = (1.0 - restart_phase + system_phase) * lifetime; + if (!lifetime_infinite) { + local_delta = (1.0 - restart_phase + system_phase) * lifetime; + } #endif //ubershader-runtime } else if (restart_phase < system_phase) { restart = true; #ifdef USE_FRACTIONAL_DELTA //ubershader-runtime - local_delta = (system_phase - restart_phase) * lifetime; + if (!lifetime_infinite) { + local_delta = (system_phase - restart_phase) * lifetime; + } #endif //ubershader-runtime } } @@ -201,9 +208,7 @@ VERTEX_SHADER_CODE #if !defined(DISABLE_VELOCITY) - if (true) { - xform[3].xyz += out_velocity_active.xyz * local_delta; - } + xform[3].xyz += out_velocity_active.xyz * local_delta; #endif } else { xform = mat4(0.0); diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index ef816da6555c..4015152411ee 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -62,6 +62,11 @@ void Particles::set_lifetime(float p_lifetime) { lifetime = p_lifetime; VS::get_singleton()->particles_set_lifetime(particles, lifetime); } +void Particles::set_lifetime_infinite(bool p_lifetime_infinite) { + lifetime_infinite = p_lifetime_infinite; + VS::get_singleton()->particles_set_lifetime_infinite(particles, p_lifetime_infinite); + _change_notify(); +} void Particles::set_one_shot(bool p_one_shot) { one_shot = p_one_shot; @@ -126,6 +131,9 @@ int Particles::get_amount() const { float Particles::get_lifetime() const { return lifetime; } +bool Particles::is_lifetime_infinite() const { + return lifetime_infinite; +} bool Particles::get_one_shot() const { return one_shot; } @@ -288,6 +296,12 @@ AABB Particles::capture_aabb() const { } void Particles::_validate_property(PropertyInfo &property) const { + if (property.name == "lifetime" && lifetime_infinite) { + // The lifetime property is ignored when lifetime is infinite, so hide it. + property.usage = 0; + return; + } + if (property.name.begins_with("draw_pass_")) { int index = property.name.get_slicec('_', 2).to_int() - 1; if (index >= draw_passes.size()) { @@ -327,6 +341,7 @@ void Particles::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &Particles::set_emitting); ClassDB::bind_method(D_METHOD("set_amount", "amount"), &Particles::set_amount); ClassDB::bind_method(D_METHOD("set_lifetime", "secs"), &Particles::set_lifetime); + ClassDB::bind_method(D_METHOD("set_lifetime_infinite", "infinite"), &Particles::set_lifetime_infinite); ClassDB::bind_method(D_METHOD("set_one_shot", "enable"), &Particles::set_one_shot); ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &Particles::set_pre_process_time); ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &Particles::set_explosiveness_ratio); @@ -341,6 +356,7 @@ void Particles::_bind_methods() { ClassDB::bind_method(D_METHOD("is_emitting"), &Particles::is_emitting); ClassDB::bind_method(D_METHOD("get_amount"), &Particles::get_amount); ClassDB::bind_method(D_METHOD("get_lifetime"), &Particles::get_lifetime); + ClassDB::bind_method(D_METHOD("is_lifetime_infinite"), &Particles::is_lifetime_infinite); ClassDB::bind_method(D_METHOD("get_one_shot"), &Particles::get_one_shot); ClassDB::bind_method(D_METHOD("get_pre_process_time"), &Particles::get_pre_process_time); ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &Particles::get_explosiveness_ratio); @@ -369,6 +385,7 @@ void Particles::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lifetime_infinite"), "set_lifetime_infinite", "is_lifetime_infinite"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); @@ -403,6 +420,7 @@ Particles::Particles() { set_one_shot(false); set_amount(8); set_lifetime(1); + set_lifetime_infinite(false); set_fixed_fps(0); set_fractional_delta(true); set_pre_process_time(0); diff --git a/scene/3d/particles.h b/scene/3d/particles.h index ed1e2d97a2eb..9d20867cfbce 100644 --- a/scene/3d/particles.h +++ b/scene/3d/particles.h @@ -56,6 +56,7 @@ class Particles : public GeometryInstance { bool one_shot; int amount; float lifetime; + bool lifetime_infinite; float pre_process_time; float explosiveness_ratio; float randomness_ratio; @@ -83,6 +84,7 @@ class Particles : public GeometryInstance { void set_emitting(bool p_emitting); void set_amount(int p_amount); void set_lifetime(float p_lifetime); + void set_lifetime_infinite(bool p_lifetime_infinite); void set_one_shot(bool p_one_shot); void set_pre_process_time(float p_time); void set_explosiveness_ratio(float p_ratio); @@ -95,6 +97,7 @@ class Particles : public GeometryInstance { bool is_emitting() const; int get_amount() const; float get_lifetime() const; + bool is_lifetime_infinite() const; bool get_one_shot() const; float get_pre_process_time() const; float get_explosiveness_ratio() const; diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 4260a40af14b..ff8acc5a5b3c 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -608,6 +608,7 @@ class RasterizerStorage { virtual void particles_set_amount(RID p_particles, int p_amount) = 0; virtual void particles_set_lifetime(RID p_particles, float p_lifetime) = 0; + virtual void particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite) = 0; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0; virtual void particles_set_pre_process_time(RID p_particles, float p_time) = 0; virtual void particles_set_explosiveness_ratio(RID p_particles, float p_ratio) = 0; diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index a0a1b1db49ae..f24383680af1 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -426,6 +426,7 @@ class VisualServerRaster : public VisualServer { BIND1R(bool, particles_get_emitting, RID) BIND2(particles_set_amount, RID, int) BIND2(particles_set_lifetime, RID, float) + BIND2(particles_set_lifetime_infinite, RID, bool) BIND2(particles_set_one_shot, RID, bool) BIND2(particles_set_pre_process_time, RID, float) BIND2(particles_set_explosiveness_ratio, RID, float) diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 5230a26ee10e..b161f48bf683 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -346,6 +346,7 @@ class VisualServerWrapMT : public VisualServer { FUNC1R(bool, particles_get_emitting, RID) FUNC2(particles_set_amount, RID, int) FUNC2(particles_set_lifetime, RID, float) + FUNC2(particles_set_lifetime_infinite, RID, bool) FUNC2(particles_set_one_shot, RID, bool) FUNC2(particles_set_pre_process_time, RID, float) FUNC2(particles_set_explosiveness_ratio, RID, float) diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 7a2e00cb9643..4751ab1623c0 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2040,6 +2040,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("particles_get_emitting", "particles"), &VisualServer::particles_get_emitting); ClassDB::bind_method(D_METHOD("particles_set_amount", "particles", "amount"), &VisualServer::particles_set_amount); ClassDB::bind_method(D_METHOD("particles_set_lifetime", "particles", "lifetime"), &VisualServer::particles_set_lifetime); + ClassDB::bind_method(D_METHOD("particles_set_lifetime_infinite", "particles", "lifetime"), &VisualServer::particles_set_lifetime_infinite); ClassDB::bind_method(D_METHOD("particles_set_one_shot", "particles", "one_shot"), &VisualServer::particles_set_one_shot); ClassDB::bind_method(D_METHOD("particles_set_pre_process_time", "particles", "time"), &VisualServer::particles_set_pre_process_time); ClassDB::bind_method(D_METHOD("particles_set_explosiveness_ratio", "particles", "ratio"), &VisualServer::particles_set_explosiveness_ratio); diff --git a/servers/visual_server.h b/servers/visual_server.h index 81df90ae7f09..404e4f9940db 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -592,6 +592,7 @@ class VisualServer : public Object { virtual bool particles_get_emitting(RID p_particles) = 0; virtual void particles_set_amount(RID p_particles, int p_amount) = 0; virtual void particles_set_lifetime(RID p_particles, float p_lifetime) = 0; + virtual void particles_set_lifetime_infinite(RID p_particles, bool p_lifetime_infinite) = 0; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0; virtual void particles_set_pre_process_time(RID p_particles, float p_time) = 0; virtual void particles_set_explosiveness_ratio(RID p_particles, float p_ratio) = 0;