Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Hide on Contact" collision mode to ParticlesMaterial #61238

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/classes/GPUParticlesCollision3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
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>
<tutorials>
</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>
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/GPUParticlesCollisionBox3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/GPUParticlesCollisionHeightField3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/GPUParticlesCollisionSDF3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/GPUParticlesCollisionSphere3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
25 changes: 19 additions & 6 deletions doc/classes/ParticlesMaterial.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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>
52 changes: 40 additions & 12 deletions scene/resources/particles_material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down Expand Up @@ -695,16 +695,18 @@ 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 = " ";
}
code += extra_tab + " \n";
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 += " }";
}
}
Expand Down Expand Up @@ -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";
Expand All @@ -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) {
Expand Down Expand Up @@ -1436,6 +1450,14 @@ void ParticlesMaterial::_validate_property(PropertyInfo &property) const {
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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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() :
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 14 additions & 5 deletions scene/resources/particles_material.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ class ParticlesMaterial : public Material {
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.
Expand All @@ -106,7 +114,7 @@ class ParticlesMaterial : public Material {
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;
};
Expand Down Expand Up @@ -153,7 +161,7 @@ class ParticlesMaterial : public Material {
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;
Expand Down Expand Up @@ -300,7 +308,7 @@ class ParticlesMaterial : public Material {
//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;
Expand Down Expand Up @@ -385,8 +393,8 @@ class ParticlesMaterial : public Material {
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;
Expand Down Expand Up @@ -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