Buoyancy simulation

I’m trying to simulate an ocean construction process which needs to simulate real water and buoyancy system. I’ve looked into water plugin and buoyancy component but the buoyancy seems to be set by pontoon manually. Is it possible to calculate buoyancy by volume and density by physics engine?

I’ve been working to setup neutral buoyancy with this builtin system, so I dove into the BuoyancyComponent.cpp to better understand what happens there. So here’s a synthesis of my work.

Verifying the Archimedes’ principle

To achieve neutral buoyancy, the Archimedes’ principle must be applicable with this builtin buoyancy system. So I needed to verify how they compute the buoyant force, hopefully it is proportional to the pontoons volume.

Each pontons stores a local force vector that is updated each simulation tick and applied to the simulated body of the actor. It is an absolute up vector (buoyant force) that is computed as follow.

Relation to the volume

They calculate the submersed volume, for each pontoon, as a Spherical Cap (using the pontoons radius and height under water).
This volume is transformed three times :

  1. It is scaled by the component Buoyancy Coefficient, which can ramp up/down between its current value and the Buoyancy Ramp Max when the body’s forward speed is between min and max velocity.

  2. A damping factor is applied to it when the body moves upwards only, basically it reduces the resulting volume using the first and second order damping factors (respectively multiplied by the Z velocity and the squared Z velocity).

  3. It is finally scaled by a coefficient stored for each ponton, which has been previously computed by distributing a mass of 1 over all active pontoons, based on their relative location to the center of mass.
    I’ve not checked but I’m pretty sure it is XY relative, so the closer the CoM the pontoon is, the higher the coefficient will be. If there’ only 1 pontoon, its coefficient will be 1, if there’s 2 equidistant pontoons, each one will have 0.5 of mass coefficient.

This resulting volume is directly scaling a unit up vector and stored as the pontoon local force. Then this force is applied each component tick.
Notice that there’s no water density taken in account, the force applied is equivalent to the displaced volume. So if we want to implement a liquid density sensitive system, we would need to add a computation layer on the Buoyancy Coefficient.
Considering my needs aren’t to have density implemented, and the local force is proportional to the submersed volume, I can go further with a simplified Archimedes’ principle. :white_check_mark:

Resolving the maths

So we have :

  1. The buoyant force, expressed as the volume of the pontoon (consider it fully submersed, and standing still) :

  1. The body is pulled down by the gravity, so there’s always a downward force equal to :

Knowing all that, there is multiple ways to setup the buoyancy component for a neutral buoyancy, the one I’ve chosen is to setup all pontoons radius so they displace a volume equal to the Gravity Force (not considering water density).
As the mass of the body is distributed among all pontoons for the buoyant force calculation, I will compute a unique radius and apply it to all pontoons, they will all work together to repel the gravity force.
As we want the buoyant force to cancel the gravity force, we get the equation :


So for a given Mass and GravityAccel, we can compute the radius as :

Implementation

To implement this, I decided to create a subclass of UBuoyancyComponent and extend it, here’s the code :

BuoyancyComponentExtended.h

// BuoyancyComponentExtended.h
#pragma once

#include "CoreMinimal.h"
#include "BuoyancyComponent.h"
#include "BuoyancyComponentExtended.generated.h"

/**
 * An extension of the class UBuoyancyComponent
 * By default it creates one default pontoon
 * At BeginPlay, all pontoons radius are calculated for neutral buoyancy
 * See bInitWithNeutralBuoyancy 
 */
UCLASS(Blueprintable, Config = Game, meta = (BlueprintSpawnableComponent))
class MYPROJECT_API UBuoyancyComponentExtended : public UBuoyancyComponent
{
	GENERATED_BODY()

public :
	UBuoyancyComponentExtended(const FObjectInitializer& ObjectInitializer);
	virtual void BeginPlay() override;

	UFUNCTION(BlueprintCallable)
	FORCEINLINE void SetBuoyancyCoefficient(float NewCoefficient) { BuoyancyData.BuoyancyCoefficient = NewCoefficient; }

	UFUNCTION(BlueprintPure)
	FORCEINLINE float GetBuoyancyCoefficient() { return BuoyancyData.BuoyancyCoefficient; }

protected:
	// Calculate the pontoons radius to have neutral buoyancy at BuoyancyCoefficient = 1.f
	UFUNCTION(BlueprintCallable)
	void ComputePontoonsRadiusForNeutralBuoyancy();

	// Will override pontoons radius at BeginPlay when enabled,
	// it allows to set buoyancy easily :
	// BuoyancyCoefficient >  1 => buoyant
	// BuoyancyCoefficient == 1 => static
	// BuoyancyCoefficient <  1 => sinking
	UPROPERTY(EditDefaultsOnly, category = "Buoyancy Extended")
	bool bInitWithNeutralBuoyancy = true;
};

BuoyancyComponentExtended.cpp

// UBuoyancyComponentExtended.cpp
#include "Components/BuoyancyComponentExtended.h"


UBuoyancyComponentExtended::UBuoyancyComponentExtended(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{	
	BuoyancyData.bCenterPontoonsOnCOM = false;
	BuoyancyData.bApplyDragForcesInWater = true;
	BuoyancyData.MaxBuoyantForce = TNumericLimits<float>::Max();
	BuoyancyData.BuoyancyCoefficient = 1.0f;

	// Add one default pontoon
	AddCustomPontoon(100.0f, FVector(0.0f, 0.0f, 0.0f));
}


void UBuoyancyComponentExtended::BeginPlay()
{
	Super::BeginPlay();

	if (SimulatingComponent)
	{
		if (bInitWithNeutralBuoyancy)
		{
			ComputePontoonsRadiusForNeutralBuoyancy();
		}
	}
}


void UBuoyancyComponentExtended::ComputePontoonsRadiusForNeutralBuoyancy()
{
	float GravityZ = 0.0f;
	if (UWorld* World = GetWorld())
	{
		GravityZ = World->GetGravityZ();
	}

	float BodyMass = SimulatingComponent->GetMass();
	// Compute one radius for all pontoons because the body mass is distributed
	float PontoonRadius = FMath::Pow(6 * BodyMass * (-GravityZ), 1.f/3.f) / (2.f * FMath::Pow(PI, 1.f/3.f));
	for (FSphericalPontoon& Pontoon : BuoyancyData.Pontoons)
	{
		Pontoon.Radius = PontoonRadius;
	}
}

The result

This is a showcase with 3 different buoyancy coefficients (displayed).

Conclusion

With this method, you can add pontoons to your body and simulate buoyant, static, or sinking body. The Buoyancy Coefficient, after pontoons radius computed, is then inversely related to the body’s density over water density.
Feel free to bring improvements and remarks about my work, learning never ends.

1 Like

@Skyrow_8
Thanks a lot for sharing your experiment. I’m trying to simulate a floating ship in open seas and I’m having big trouble figuring out how many pontoons should I add and where should I place them to be able to use your formula. Can you help me to understand more towards my simulation?