Merge pull request #61238 from Calinou/particlesmaterial-add-fade-on-contact-collision-mode

Add "Hide on Contact" collision mode to ParticlesMaterial
This commit is contained in:
Rémi Verschelde 2022-08-25 00:01:47 +02:00 committed by GitHub
commit 56752e32a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 29 deletions

View file

@ -7,7 +7,7 @@
Particle collision shapes can be used to make particles stop or bounce against them.
Particle collision shapes in real-time and can be moved, rotated and scaled during gameplay. Unlike attractors, non-uniform scaling of collision shapes is [i]not[/i] supported.
Particle collision shapes can be temporarily disabled by hiding them.
[b]Note:[/b] [member ParticlesMaterial.collision_enabled] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] [member ParticlesMaterial.collision_mode] must be [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D].
[b]Note:[/b] Particles pushed by a collider that is being moved will not be interpolated, which can result in visible stuttering. This can be alleviated by setting [member GPUParticles3D.fixed_fps] to [code]0[/code] or a value that matches or exceeds the target framerate.
</description>
@ -15,7 +15,7 @@
</tutorials>
<members>
<member name="cull_mask" type="int" setter="set_cull_mask" getter="get_cull_mask" default="4294967295">
The particle rendering layers ([member VisualInstance3D.layers]) that will be affected by the collision shape. By default, all particles that have [member ParticlesMaterial.collision_enabled] set to [code]true[/code] will be affected by a collision shape.
The particle rendering layers ([member VisualInstance3D.layers]) that will be affected by the collision shape. By default, all particles that have [member ParticlesMaterial.collision_mode] set to [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] will be affected by a collision shape.
After configuring particle nodes accordingly, specific layers can be unchecked to prevent certain particles from being affected by attractors. For example, this can be used if you're using an attractor as part of a spell effect but don't want the attractor to affect unrelated weather particles at the same position.
Particle attraction can also be disabled on a per-process material basis by setting [member ParticlesMaterial.attractor_interaction_enabled] on the [GPUParticles3D] node.
</member>

View file

@ -5,7 +5,7 @@
</brief_description>
<description>
Box-shaped 3D particle collision shape affecting [GPUParticles3D] nodes.
[b]Note:[/b] [member ParticlesMaterial.collision_enabled] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] [member ParticlesMaterial.collision_mode] must be [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D].
</description>
<tutorials>

View file

@ -7,7 +7,7 @@
Real-time heightmap-shaped 3D particle attractor affecting [GPUParticles3D] nodes.
Heightmap shapes allow for efficiently representing collisions for convex and concave objects with a single "floor" (such as terrain). This is less flexible than [GPUParticlesCollisionSDF3D], but it doesn't require a baking step.
[GPUParticlesCollisionHeightField3D] can also be regenerated in real-time when it is moved, when the camera moves, or even continuously. This makes [GPUParticlesCollisionHeightField3D] a good choice for weather effects such as rain and snow and games with highly dynamic geometry. However, since heightmaps cannot represent overhangs, [GPUParticlesCollisionHeightField3D] is not suited for indoor particle collision.
[b]Note:[/b] [member ParticlesMaterial.collision_enabled] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] [member ParticlesMaterial.collision_mode] must be [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D].
</description>
<tutorials>

View file

@ -8,7 +8,7 @@
Signed distance fields (SDF) allow for efficiently representing approximate collision shapes for convex and concave objects of any shape. This is more flexible than [GPUParticlesCollisionHeightField3D], but it requires a baking step.
[b]Baking:[/b] The signed distance field texture can be baked by selecting the [GPUParticlesCollisionSDF3D] node in the editor, then clicking [b]Bake SDF[/b] at the top of the 3D viewport. Any [i]visible[/i] [MeshInstance3D]s touching the [member extents] will be taken into account for baking, regardless of their [member GeometryInstance3D.gi_mode].
[b]Note:[/b] Baking a [GPUParticlesCollisionSDF3D]'s [member texture] is only possible within the editor, as there is no bake method exposed for use in exported projects. However, it's still possible to load pre-baked [Texture3D]s into its [member texture] property in an exported project.
[b]Note:[/b] [member ParticlesMaterial.collision_enabled] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] [member ParticlesMaterial.collision_mode] must be [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D].
</description>
<tutorials>

View file

@ -5,7 +5,7 @@
</brief_description>
<description>
Sphere-shaped 3D particle collision shape affecting [GPUParticles3D] nodes.
[b]Note:[/b] [member ParticlesMaterial.collision_enabled] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] [member ParticlesMaterial.collision_mode] must be [constant ParticlesMaterial.COLLISION_RIGID] or [constant ParticlesMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work.
[b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D].
</description>
<tutorials>

View file

@ -115,14 +115,15 @@
<member name="attractor_interaction_enabled" type="bool" setter="set_attractor_interaction_enabled" getter="is_attractor_interaction_enabled" default="true">
True if the interaction with particle attractors is enabled.
</member>
<member name="collision_bounce" type="float" setter="set_collision_bounce" getter="get_collision_bounce" default="0.0">
Collision bounciness.
<member name="collision_bounce" type="float" setter="set_collision_bounce" getter="get_collision_bounce">
The particles' bounciness. Values range from [code]0[/code] (no bounce) to [code]1[/code] (full bounciness). Only effective if [member collision_mode] is [constant COLLISION_RIGID].
</member>
<member name="collision_enabled" type="bool" setter="set_collision_enabled" getter="is_collision_enabled" default="false">
True if collisions are enabled for this particle system.
<member name="collision_friction" type="float" setter="set_collision_friction" getter="get_collision_friction">
The particles' friction. Values range from [code]0[/code] (frictionless) to [code]1[/code] (maximum friction). Only effective if [member collision_mode] is [constant COLLISION_RIGID].
</member>
<member name="collision_friction" type="float" setter="set_collision_friction" getter="get_collision_friction" default="0.0">
Collision friction.
<member name="collision_mode" type="int" setter="set_collision_mode" getter="get_collision_mode" enum="ParticlesMaterial.CollisionMode" default="0">
The particles' collision mode.
[b]Note:[/b] Particles can only collide with [GPUParticlesCollision3D] nodes, not [PhysicsBody3D] nodes. To make particles collide with various objects, you can add [GPUParticlesCollision3D] nodes as children of [PhysicsBody3D] nodes.
</member>
<member name="collision_use_scale" type="bool" setter="set_collision_use_scale" getter="is_collision_using_scale" default="false">
Should collision take scale into account.
@ -404,5 +405,17 @@
<constant name="SUB_EMITTER_MAX" value="4" enum="SubEmitterMode">
Represents the size of the [enum SubEmitterMode] enum.
</constant>
<constant name="COLLISION_DISABLED" value="0" enum="CollisionMode">
No collision for particles. Particles will go through [GPUParticlesCollision3D] nodes.
</constant>
<constant name="COLLISION_RIGID" value="1" enum="CollisionMode">
[RigidDynamicBody3D]-style collision for particles using [GPUParticlesCollision3D] nodes.
</constant>
<constant name="COLLISION_HIDE_ON_CONTACT" value="2" enum="CollisionMode">
Hide particles instantly when colliding with a [GPUParticlesCollision3D] node. This can be combined with a subemitter that uses the [constant COLLISION_RIGID] collision mode to "replace" the parent particle with the subemitter on impact.
</constant>
<constant name="COLLISION_MAX" value="3" enum="CollisionMode">
Represents the size of the [enum CollisionMode] enum.
</constant>
</constants>
</class>

View file

@ -287,7 +287,7 @@ void ParticlesMaterial::_update_shader() {
code += "uniform sampler2D anim_offset_texture : repeat_disable;\n";
}
if (collision_enabled) {
if (collision_mode == COLLISION_RIGID) {
code += "uniform float collision_friction;\n";
code += "uniform float collision_bounce;\n";
}
@ -695,8 +695,10 @@ void ParticlesMaterial::_update_shader() {
}
code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz, EMISSION_TRANSFORM[3].xyz, time_noise);\n";
// If collision happened, turbulence is no longer applied.
// We don't need this check when the collision mode is "hide on contact",
// as the particle will be hidden anyway.
String extra_tab = "";
if (collision_enabled) {
if (collision_mode != COLLISION_RIGID) {
code += " if (!COLLIDED) {\n";
extra_tab = " ";
}
@ -704,7 +706,7 @@ void ParticlesMaterial::_update_shader() {
code += extra_tab + " float vel_mag = length(VELOCITY);\n";
code += extra_tab + " float vel_infl = clamp(mix(turbulence_influence_min, turbulence_influence_max, rand_from_seed(alt_seed)) * turbulence_influence, 0.0, 1.0);\n";
code += extra_tab + " VELOCITY = mix(VELOCITY, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n";
if (collision_enabled) {
if (collision_mode != COLLISION_RIGID) {
code += " }";
}
}
@ -828,7 +830,7 @@ void ParticlesMaterial::_update_shader() {
code += " TRANSFORM[3].z = 0.0;\n";
}
if (collision_enabled) {
if (collision_mode == COLLISION_RIGID) {
code += " if (COLLIDED) {\n";
code += " if (length(VELOCITY) > 3.0) {\n";
code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
@ -851,6 +853,18 @@ void ParticlesMaterial::_update_shader() {
code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n";
code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n";
if (collision_mode == COLLISION_RIGID) {
code += " if (COLLIDED) {\n";
code += " TRANSFORM[3].xyz+=COLLISION_NORMAL * COLLISION_DEPTH;\n";
code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
code += " VELOCITY = mix(VELOCITY,vec3(0.0),collision_friction * DELTA * 100.0);\n";
code += " }\n";
} else if (collision_mode == COLLISION_HIDE_ON_CONTACT) {
code += " if (COLLIDED) {\n";
code += " ACTIVE = false;\n";
code += " }\n";
}
if (sub_emitter_mode != SUB_EMITTER_DISABLED) {
code += " int emit_count = 0;\n";
switch (sub_emitter_mode) {
@ -1436,6 +1450,14 @@ void ParticlesMaterial::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
if (property.name == "collision_friction" && collision_mode != COLLISION_RIGID) {
property.usage = PROPERTY_USAGE_NONE;
}
if (property.name == "collision_bounce" && collision_mode != COLLISION_RIGID) {
property.usage = PROPERTY_USAGE_NONE;
}
}
void ParticlesMaterial::set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode) {
@ -1483,13 +1505,14 @@ bool ParticlesMaterial::is_attractor_interaction_enabled() const {
return attractor_interaction_enabled;
}
void ParticlesMaterial::set_collision_enabled(bool p_enabled) {
collision_enabled = p_enabled;
void ParticlesMaterial::set_collision_mode(CollisionMode p_collision_mode) {
collision_mode = p_collision_mode;
_queue_shader_change();
notify_property_list_changed();
}
bool ParticlesMaterial::is_collision_enabled() const {
return collision_enabled;
ParticlesMaterial::CollisionMode ParticlesMaterial::get_collision_mode() const {
return collision_mode;
}
void ParticlesMaterial::set_collision_use_scale(bool p_scale) {
@ -1623,8 +1646,8 @@ void ParticlesMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_attractor_interaction_enabled", "enabled"), &ParticlesMaterial::set_attractor_interaction_enabled);
ClassDB::bind_method(D_METHOD("is_attractor_interaction_enabled"), &ParticlesMaterial::is_attractor_interaction_enabled);
ClassDB::bind_method(D_METHOD("set_collision_enabled", "enabled"), &ParticlesMaterial::set_collision_enabled);
ClassDB::bind_method(D_METHOD("is_collision_enabled"), &ParticlesMaterial::is_collision_enabled);
ClassDB::bind_method(D_METHOD("set_collision_mode", "mode"), &ParticlesMaterial::set_collision_mode);
ClassDB::bind_method(D_METHOD("get_collision_mode"), &ParticlesMaterial::get_collision_mode);
ClassDB::bind_method(D_METHOD("set_collision_use_scale", "radius"), &ParticlesMaterial::set_collision_use_scale);
ClassDB::bind_method(D_METHOD("is_collision_using_scale"), &ParticlesMaterial::is_collision_using_scale);
@ -1734,7 +1757,7 @@ void ParticlesMaterial::_bind_methods() {
ADD_GROUP("Attractor Interaction", "attractor_interaction_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "attractor_interaction_enabled"), "set_attractor_interaction_enabled", "is_attractor_interaction_enabled");
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Rigid,Hide On Contact"), "set_collision_mode", "get_collision_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale");
@ -1776,6 +1799,11 @@ void ParticlesMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(SUB_EMITTER_AT_END);
BIND_ENUM_CONSTANT(SUB_EMITTER_AT_COLLISION);
BIND_ENUM_CONSTANT(SUB_EMITTER_MAX);
BIND_ENUM_CONSTANT(COLLISION_DISABLED);
BIND_ENUM_CONSTANT(COLLISION_RIGID);
BIND_ENUM_CONSTANT(COLLISION_HIDE_ON_CONTACT);
BIND_ENUM_CONSTANT(COLLISION_MAX);
}
ParticlesMaterial::ParticlesMaterial() :
@ -1834,7 +1862,7 @@ ParticlesMaterial::ParticlesMaterial() :
set_sub_emitter_keep_velocity(false);
set_attractor_interaction_enabled(true);
set_collision_enabled(false);
set_collision_mode(COLLISION_DISABLED);
set_collision_bounce(0.0);
set_collision_friction(0.0);
set_collision_use_scale(false);

View file

@ -93,6 +93,14 @@ public:
SUB_EMITTER_MAX
};
// When extending, make sure not to overflow the size of the MaterialKey below.
enum CollisionMode {
COLLISION_DISABLED,
COLLISION_RIGID,
COLLISION_HIDE_ON_CONTACT,
COLLISION_MAX
};
private:
union MaterialKey {
// The bit size of the struct must be kept below or equal to 32 bits.
@ -106,7 +114,7 @@ private:
uint32_t has_emission_color : 1;
uint32_t sub_emitter : 2;
uint32_t attractor_enabled : 1;
uint32_t collision_enabled : 1;
uint32_t collision_mode : 2;
uint32_t collision_scale : 1;
uint32_t turbulence_enabled : 1;
};
@ -153,7 +161,7 @@ private:
mk.emission_shape = emission_shape;
mk.has_emission_color = emission_shape >= EMISSION_SHAPE_POINTS && emission_color_texture.is_valid();
mk.sub_emitter = sub_emitter_mode;
mk.collision_enabled = collision_enabled;
mk.collision_mode = collision_mode;
mk.attractor_enabled = attractor_interaction_enabled;
mk.collision_scale = collision_scale;
mk.turbulence_enabled = turbulence_enabled;
@ -300,7 +308,7 @@ private:
//do not save emission points here
bool attractor_interaction_enabled = false;
bool collision_enabled = false;
CollisionMode collision_mode;
bool collision_scale = false;
float collision_friction = 0.0f;
float collision_bounce = 0.0f;
@ -385,8 +393,8 @@ public:
void set_attractor_interaction_enabled(bool p_enable);
bool is_attractor_interaction_enabled() const;
void set_collision_enabled(bool p_enabled);
bool is_collision_enabled() const;
void set_collision_mode(CollisionMode p_collision_mode);
CollisionMode get_collision_mode() const;
void set_collision_use_scale(bool p_scale);
bool is_collision_using_scale() const;
@ -425,5 +433,6 @@ VARIANT_ENUM_CAST(ParticlesMaterial::Parameter)
VARIANT_ENUM_CAST(ParticlesMaterial::ParticleFlags)
VARIANT_ENUM_CAST(ParticlesMaterial::EmissionShape)
VARIANT_ENUM_CAST(ParticlesMaterial::SubEmitterMode)
VARIANT_ENUM_CAST(ParticlesMaterial::CollisionMode)
#endif // PARTICLES_MATERIAL_H