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

Cell Fluid Dynamics trail effect #5996

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open

Conversation

AverageNerdz
Copy link
Contributor

Brief Description of What This PR Does
Adds a transparent water ripple effect that creates dynamic fluid disturbances as cells move through the environment.

Related Issues
Closes #5702

Progress Checklist
Note: before starting this checklist the PR should be marked as non-draft.

  • PR author has checked that this PR works as intended and doesn't
    break existing features:
    https://wiki.revolutionarygamesstudio.com/wiki/Testing_Checklist
    (this is important as to not waste the time of Thrive team
    members reviewing this PR)
  • Initial code review passed (this and further items should not be checked by the PR author)
  • Functionality is confirmed working by another person (see above checklist link)
  • Final code review is passed and code conforms to the
    styleguide.
    Before merging all CI jobs should finish on this PR without errors, if
    there are automatically detected style issues they should be fixed by
    the PR author. Merging must follow our
    styleguide.

@Patryk26g
Copy link
Contributor

Looks good, but the effect is barely visible for large cells with nucleus. Is it possible to somehow scale the range of the ripples with the cell size?

@AverageNerdz
Copy link
Contributor Author

AverageNerdz commented Mar 9, 2025

Looks good, but the effect is barely visible for large cells with nucleus. Is it possible to somehow scale the range of the ripples with the cell size?

yes pal, you can play around with the parameters in the editor just look it up on the Membrane.tscn file ;)

@hhyyrylainen hhyyrylainen added this to the Release 0.8.2 milestone Mar 10, 2025
@hhyyrylainen hhyyrylainen requested a review from a team March 10, 2025 08:41
@hhyyrylainen hhyyrylainen requested a review from a team March 10, 2025 08:41
shader_type spatial;
render_mode blend_mix, depth_prepass_alpha, cull_back, diffuse_lambert;

uniform vec4 water_color : source_color = vec4(0.0, 0.0, 0.0, 0.02); // Almost completely transparent background
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our shaders should also use our C# naming convention so this should be converted to use pascalCase naming convention rather than snake_case. Also the trailing comments should be changed to be comments on the line before what they are commenting on to match Thrive style.

vec2 current_pos = vec2(0.0, 0.0);

// Simulate multiple ripple sources from object's recent past positions (longer trail)
for (int i = 0; i < 14; i++) { // VISCOSITY: More iterations = longer trail, less viscous appearance
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing quite a lot of loops and ifs so I would expect this shader to be pretty expensive, so we probably need to have a graphics option to turn this off.

Comment on lines +25 to +26
/*Controls how quickly ripples dissipate
higher values = less viscous appearance*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be formatted as an XML summary comment?

public float RadiusMultiplier = 2.5f;

[Export]
public int MaxMeshSubdivision = 100; // Reduced for better performane can be increased for a more detailed effect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing comments shouldn't be used

[Export]
public float VerticalOffset = -0.15f; // How far below the membrane the water plane should be

// Default radius if we can't determine it from the membrane
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely needs to be an XML summary comment.

Comment on lines +70 to +72
// Initialize the effect
SetProcess(true);
SetPhysicsProcess(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be automatic, so not required to be explicitly written like this.

Comment on lines +75 to +81
parentMembrane = GetParent<Membrane>();
if (parentMembrane == null)
{
GD.PrintErr("MembraneWaterRipple must be a child of a Membrane");
QueueFree();
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done the other way around, i.e. the membrane initializing the component inside it and not calling up to the parent. That's basically always a sign of bad Godot scene tree usage.

// Initialize position tracking for movement calculation
lastPosition = parentMembrane.GlobalPosition;
previousPosition = lastPosition;
lastMovementTime = Time.GetTicksMsec() / 1000.0f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time shouldn't be done like this, only rely on the delta values given in _Process or _PhysicsProcess.

UpdatePosition();
}

// Called every frame for visual updates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is like a default comment that should be deleted. Or if you really want to keep this, this must be reformatted as an XML comment.

Comment on lines +111 to +114
catch (Exception e)
{
GD.PrintErr("Error in water ripple process: " + e.Message);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be a catch like this in a component.

// Update shader time with exact original timing
if (waterMaterial != null)
{
float currentTime = Time.GetTicksMsec() / 1000.0f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time passing should be taken from delta and not like this.

}

// Called on physics process for movement and simulation updates
public override void _PhysicsProcess(double delta)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an actual performance improvement from splitting the processing into two parts? I would guess that as you already check the time elapsed here, it would be more performance friendly to put all of the content from here into _Process and using a double variable to keep track when the extra checks need to run.

Copy link
Member

@hhyyrylainen hhyyrylainen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't read all of the shader but other than that this is a full code review. Sorry if the tone of the comments gets worse by the end of the file but by that point I was actually surprised about how the implementation was done.

// Dispose managed resources
if (parentMembrane is IDisposable disposableMembrane)
{
disposableMembrane.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is absolutely incorrect. Godot nodes that are attached to the scene tree may never be disposed.

You need to suppress the compiler warning suggesting to do this like we do in basically every one of our Node-derived classes.

Comment on lines +186 to +187
waterPlane = new MeshInstance3D();
AddChild(waterPlane);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be directly setup in the scene as this looks like static node data that doesn't need to be actually dynamically created.

float currentRadius = GetMembraneRadius();

// Only update if size changed significantly
// NOLINT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there these NOLINT lines all over the place?

Comment on lines +197 to +198
PlaneMesh planeMesh = new PlaneMesh();
planeMesh.Size = new Vector2(meshSize, meshSize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could also be directly setup in the tscn file but marked scene local so that modifying the plane mesh would be okay.

Comment on lines +219 to +221
catch (Exception e)
{
GD.PrintErr("Error creating water plane: " + e.Message);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exception catching everywhere should just be deleted from this entire file. It's fine if you were developing with this on but from the finished version these need to be deleted. Godot 4 already has a top level catch and will prevent the game from crashing if a node _Process throws an exception.

AddChild(waterPlane);

// Set the water plane's position to match the membrane's position
waterPlane.GlobalPosition = parentMembrane.GlobalPosition;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this position immediately overridden a few lines below? Meaning that this is a useless memory copy that goes all the way to Godot (C# -> C++ is an expensive call).


// Get current position
Vector3 currentPos = parentMembrane.GlobalPosition;
float currentTime = Time.GetTicksMsec() / 1000.0f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect time handling here as well.

Comment on lines +379 to +380
waterMaterial.SetShaderParameter("movement_direction", currentDirection);
waterMaterial.SetShaderParameter("movement_speed", currentSpeed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here should definitely need to use StringNames as these are called often.

return DEFAULT_MEMBRANE_RADIUS;

// Tries to access radius directly if available
foreach (var propertyName in new[] { "Radius", "radius", "_radius" })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??? This is the most questionable code.

Why is this using reflection? This is super bad for performance and very, very weird.

If this is about getting parent properties, it would be 100% better if the Membrane class handled giving its radius to this component that is a child of the membrane.


return DEFAULT_MEMBRANE_RADIUS;
}
catch (Exception)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this silently ignores errors in reading the data??

@AverageNerdz
Copy link
Contributor Author

I didn't read all of the shader but other than that this is a full code review. Sorry if the tone of the comments gets worse by the end of the file but by that point I was actually surprised about how the implementation was done.

Pal you shouldn’t hold back you’re just giving me suggestions to improve the code and you as always are very polite so there’s nothing to worry about, originally the dispose part wasn’t in the original code I tried to experiment with it after I had to change the implementation the shader is really expensive as you said so the solution you proposed is probably the right way to do it

@hhyyrylainen
Copy link
Member

Thanks for the encouragement. I do worry about coming across as too demanding in reviews, but that of course also depends on how the recipient feels about the review.

originally the dispose part wasn’t in the original code I tried to experiment with it after I had to change the implementation

Yeah, I can see how that happened. Unless I'm misremembering the style guide should actually have the advice on how to deal with the Dispose stuff.

@hhyyrylainen
Copy link
Member

One more thing I just realized, the new .uid files should be committed to git otherwise the game causes unknown uid warnings on everyone else's computer.

image

Should be committed to this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

3 participants