Skip to content
Open
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
2 changes: 1 addition & 1 deletion Source/AGXUnreal/Private/Materials/AGX_ShapeMaterial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ UAGX_ShapeMaterial* UAGX_ShapeMaterial::CreateInstanceFromAsset(
const FString InstanceName = Source->GetName() + "_Instance";

UAGX_ShapeMaterial* NewInstance = NewObject<UAGX_ShapeMaterial>(
GetTransientPackage(), UAGX_ShapeMaterial::StaticClass(), *InstanceName, RF_Transient);
GetTransientPackage(), Source->GetClass(), *InstanceName, RF_Transient);
NewInstance->Asset = Source;
NewInstance->CopyShapeMaterialProperties(Source);
NewInstance->CreateNative(PlayingWorld);
Expand Down
177 changes: 177 additions & 0 deletions Source/AGXUnreal/Private/Model/AGX_DynamicWaterComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2026, Algoryx Simulation AB.


#include "Model/AGX_DynamicWaterComponent.h"

#include "AGX_LogCategory.h"
#include "AGX_Simulation.h"
#include "Model/AGX_WindAndWaterControllerSubsystemBase.h"
#include "Shapes/AGX_ShapeComponent.h"
#include "Utilities/AGX_StringUtilities.h"

namespace
{
class CustomDynamicWaterBarrier : public FDynamicWaterBarrier
{
public:
CustomDynamicWaterBarrier(const UAGX_DynamicWaterComponent& Owner) : Owner_(Owner)
{
}

virtual double FindHeightFromSurface(const FVector& WorldPoint, const FVector& UpVector, const double& Time) const override
{
return Owner_.FindHeightFromSurface(WorldPoint, UpVector, Time);
}

virtual double GetDensity() const override
{
return Owner_.GetDensity();
}

virtual FVector GetVelocity(const FVector& WorldPoint) const override
{
return Owner_.GetVelocity(WorldPoint);
}

const UAGX_DynamicWaterComponent& Owner_;
};
}

// Sets default values for this component's properties
UAGX_DynamicWaterComponent::UAGX_DynamicWaterComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = false;

// ...
}

FDynamicWaterBarrier* UAGX_DynamicWaterComponent::GetOrCreateNative()
{
if (!HasNative())
{
if (GIsReconstructingBlueprintInstances)
{
// We're in a very bad situation. Someone need this Component's native but if we're in
// the middle of a RerunConstructionScripts and this Component haven't been given its
// Native yet then there isn't much we can do. We can't create a new one since we will
// be given the actual Native soon, but we also can't return the actual Native right now
// because it hasn't been restored from the Component Instance Data yet.
//
// For now we simply die in non-shipping (checkNoEntry is active) so unit tests will
// detect this situation, and log error and return nullptr otherwise, so that the
// application can at least keep running. It is unlikely that the simulation will behave
// as intended.
checkNoEntry();
UE_LOG(
LogAGX, Error,
TEXT("A request for the AGX Dynamics instance for water wrapper '%s' in '%s' was made "
"but we are in the middle of a Blueprint Reconstruction and the requested "
"instance has not yet been restored. The instance cannot be returned, which "
"may lead to incorrect scene configuration."),
*GetName(), *GetLabelSafe(GetOwner()));
return nullptr;
}

InitializeNative();
}
check(HasNative()); /// \todo Consider better error handling than 'check'.
return NativeBarrier.Get();
}

FDynamicWaterBarrier* UAGX_DynamicWaterComponent::GetNative()
{
if (!HasNative())
{
return nullptr;
}
return NativeBarrier.Get();
}

const FDynamicWaterBarrier* UAGX_DynamicWaterComponent::GetNative() const
{
if (!HasNative())
{
return nullptr;
}
return NativeBarrier.Get();
}

bool UAGX_DynamicWaterComponent::HasNative() const
{
return NativeBarrier == nullptr ? false : NativeBarrier->HasNative();
}

uint64 UAGX_DynamicWaterComponent::GetNativeAddress() const
{
check(!HasNative());
return static_cast<uint64>(NativeBarrier->GetNativeAddress());
}

void UAGX_DynamicWaterComponent::SetNativeAddress(uint64 NativeAddress)
{
check(!HasNative());
NativeBarrier->SetNativeAddress(static_cast<uintptr_t>(NativeAddress));
}

// Called when the game starts
void UAGX_DynamicWaterComponent::BeginPlay()
{
Super::BeginPlay();
if (GIsReconstructingBlueprintInstances) return;

GetOrCreateNative();
const UAGX_Simulation* Simulation = UAGX_Simulation::GetFrom(this);
if (Simulation == nullptr)
{
UE_LOG(
LogAGX, Error,
TEXT("Shape '%s' in '%s' tried to get Simulation, but UAGX_Simulation::GetFrom "
"returned nullptr."),
*GetName(), *GetLabelSafe(GetOwner()));
return;
}

UAGX_ShapeComponent* ParentShape = Cast<UAGX_ShapeComponent>(GetAttachParent());
if (ParentShape == nullptr) return;

if (const auto WindAndWaterControllerSubsystem = UAGX_WindAndWaterControllerSubsystemBase::GetFrom(this))
{
WindAndWaterControllerSubsystem->SetWaterWrapper(ParentShape, this);
WindAndWaterControllerSubsystem->SetWaterFlowGenerator(ParentShape, this);
}
}

void UAGX_DynamicWaterComponent::EndPlay(EEndPlayReason::Type Reason)
{
Super::EndPlay(Reason);
if (GIsReconstructingBlueprintInstances) return;
if (HasNative())
{
NativeBarrier->ReleaseNative();
}
}

double UAGX_DynamicWaterComponent::FindHeightFromSurface(const FVector& WorldPoint, const FVector& UpVector, const double& Time) const
{
return WorldPoint.Z;
}

double UAGX_DynamicWaterComponent::GetDensity() const
{
return 1000.0;
}

FVector UAGX_DynamicWaterComponent::GetVelocity(const FVector& WorldPoint) const
{
return FVector::Zero();
}

void UAGX_DynamicWaterComponent::InitializeNative()
{
check(!HasNative());

NativeBarrier = MakeUnique<CustomDynamicWaterBarrier>(*this);
NativeBarrier->AllocateNative();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2026, Algoryx Simulation AB.

#include "Model/AGX_WindAndWaterAwareShapeMaterial.h"

void UAGX_WindAndWaterAwareShapeMaterial::CopyShapeMaterialProperties(const UAGX_ShapeMaterial* Source)
{
Super::CopyShapeMaterialProperties(Source);
if (auto SubSource = Cast<UAGX_WindAndWaterAwareShapeMaterial>(Source))
{
bIsWaterGeometry = SubSource->bIsWaterGeometry;
HydroParameters = SubSource->HydroParameters;
AeroParameters = SubSource->AeroParameters;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2026, Algoryx Simulation AB.


#include "Model/AGX_WindAndWaterControllerSubsystemBase.h"

#include "AGX_Simulation.h"
#include "Model/AGX_DynamicWaterComponent.h"
#include "Model/AGX_WindAndWaterAwareShapeMaterial.h"
#include "Model/WindAndWaterControllerBarrier.h"
#include "Model/WindAndWaterParametersEnums.h"
#include "Shapes/AGX_BoxShapeComponent.h"
#include "Shapes/AGX_HeightFieldShapeComponent.h"
#include "Shapes/AGX_ShapeComponent.h"
#include "Shapes/AGX_SphereShapeComponent.h"
#include "Wire/AGX_WireComponent.h"

bool UAGX_WindAndWaterControllerSubsystemBase::HasNative() const
{
return NativeWindAndWaterControllerBarrier.HasNative();
}

uint64 UAGX_WindAndWaterControllerSubsystemBase::GetNativeAddress() const
{
return static_cast<uint64>(NativeWindAndWaterControllerBarrier.GetNativeAddress());
}

void UAGX_WindAndWaterControllerSubsystemBase::SetNativeAddress(uint64 NativeAddress)
{
check(!HasNative());
NativeWindAndWaterControllerBarrier.SetNativeAddress(static_cast<uintptr_t>(NativeAddress));
}

UAGX_WindAndWaterControllerSubsystemBase* UAGX_WindAndWaterControllerSubsystemBase::GetFrom(const UActorComponent* Component)
{
return Component ? GetFrom(Component->GetOwner()) : nullptr;
}

UAGX_WindAndWaterControllerSubsystemBase* UAGX_WindAndWaterControllerSubsystemBase::GetFrom(const AActor* Actor)
{
return Actor ? Actor->GetGameInstance()->GetSubsystem<UAGX_WindAndWaterControllerSubsystemBase>() : nullptr;
}

void UAGX_WindAndWaterControllerSubsystemBase::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
Collection.InitializeDependency(UAGX_Simulation::StaticClass());
if (!HasNative())
{
NativeWindAndWaterControllerBarrier.AllocateNative();
}

if (UAGX_Simulation* Sim = GetGameInstance()->GetSubsystem<UAGX_Simulation>())
{
if (Sim->HasNative())
{
Sim->GetNative()->Add(NativeWindAndWaterControllerBarrier);
}
}
}

void UAGX_WindAndWaterControllerSubsystemBase::Deinitialize()
{
Super::Deinitialize();

NativeWindAndWaterControllerBarrier.ReleaseNative();
}

FWindAndWaterControllerBarrier* UAGX_WindAndWaterControllerSubsystemBase::GetNative()
{
return HasNative() ? &NativeWindAndWaterControllerBarrier : nullptr;
}

bool UAGX_WindAndWaterControllerSubsystemBase::AddWater(UAGX_ShapeComponent* Shape)
{
return HasNative() ? NativeWindAndWaterControllerBarrier.AddWater(Shape->GetOrCreateNative()) : false;
}

bool UAGX_WindAndWaterControllerSubsystemBase::SetEnableAerodynamics(UAGX_ShapeComponent* Shape, bool bEnabled)
{
return HasNative() ? NativeWindAndWaterControllerBarrier.SetEnableAerodynamics(Shape->GetOrCreateNative(), bEnabled) : false;
}

bool UAGX_WindAndWaterControllerSubsystemBase::SetEnableAerodynamics(UAGX_WireComponent* Wire, bool bEnabled)
{
return HasNative() ? NativeWindAndWaterControllerBarrier.SetEnableAerodynamics(Wire->GetOrCreateNative(), bEnabled) : false;
}

void UAGX_WindAndWaterControllerSubsystemBase::SetHydrodynamicParameters(UAGX_ShapeComponent* Shape, EWindAndWaterParametersCoefficient Coefficient, double Value)
{
if (HasNative())
{
NativeWindAndWaterControllerBarrier.GetOrCreateHydrodynamicsParameters(Shape->GetOrCreateNative()).SetCoefficient(Coefficient, Value);
}
}

void UAGX_WindAndWaterControllerSubsystemBase::SetHydrodynamicParameters(UAGX_ShapeComponent* Shape, EWindAndWaterShapeTessellation ShapeTessellation)
{
if (HasNative())
{
NativeWindAndWaterControllerBarrier.GetOrCreateHydrodynamicsParameters(Shape->GetOrCreateNative()).SetShapeTessellation(ShapeTessellation);
}
}

void UAGX_WindAndWaterControllerSubsystemBase::SetAerodynamicParameters(UAGX_ShapeComponent* Shape, EWindAndWaterParametersCoefficient Coefficient, double Value)
{
if (HasNative())
{
NativeWindAndWaterControllerBarrier.GetOrCreateAerodynamicsParameters(Shape->GetOrCreateNative()).SetCoefficient(Coefficient, Value);
}
}

void UAGX_WindAndWaterControllerSubsystemBase::SetAerodynamicParameters(UAGX_ShapeComponent* Shape, EWindAndWaterShapeTessellation ShapeTessellation)
{
if (HasNative())
{
NativeWindAndWaterControllerBarrier.GetOrCreateAerodynamicsParameters(Shape->GetOrCreateNative()).SetShapeTessellation(ShapeTessellation);
}
}

bool UAGX_WindAndWaterControllerSubsystemBase::UpdateNativeWindAndWaterParameters(UAGX_ShapeComponent* Shape)
{
if (!Shape) return false;

const UAGX_WindAndWaterAwareShapeMaterial* ShapeMaterial = Cast<UAGX_WindAndWaterAwareShapeMaterial>(Shape->ShapeMaterial);
if (!ShapeMaterial) return false;

if (ShapeMaterial->bIsWaterGeometry)
{
if (Shape->IsA(UAGX_BoxShapeComponent::StaticClass()) ||
Shape->IsA(UAGX_HeightFieldShapeComponent::StaticClass()) ||
Shape->IsA(UAGX_SphereShapeComponent::StaticClass()))
{
Shape->SetCanCollide(false);
return AddWater(Shape);
}
}

SetHydrodynamicParameters(Shape, EWindAndWaterParametersCoefficient::PRESSURE_DRAG, ShapeMaterial->HydroParameters.PressureDrag_);
SetHydrodynamicParameters(Shape, EWindAndWaterParametersCoefficient::VISCOUS_DRAG, ShapeMaterial->HydroParameters.ViscousDrag_);
SetHydrodynamicParameters(Shape, EWindAndWaterParametersCoefficient::LIFT, ShapeMaterial->HydroParameters.Lift_);
SetHydrodynamicParameters(Shape, EWindAndWaterParametersCoefficient::BUOYANCY, ShapeMaterial->HydroParameters.Buoyancy_);
SetHydrodynamicParameters(Shape, ShapeMaterial->HydroParameters.ShapeTessellation_);

SetAerodynamicParameters(Shape, EWindAndWaterParametersCoefficient::PRESSURE_DRAG, ShapeMaterial->AeroParameters.PressureDrag_);
SetAerodynamicParameters(Shape, EWindAndWaterParametersCoefficient::VISCOUS_DRAG, ShapeMaterial->AeroParameters.ViscousDrag_);
SetAerodynamicParameters(Shape, EWindAndWaterParametersCoefficient::LIFT, ShapeMaterial->AeroParameters.Lift_);
SetAerodynamicParameters(Shape, ShapeMaterial->AeroParameters.ShapeTessellation_);
return true;
}

void UAGX_WindAndWaterControllerSubsystemBase::SetWaterWrapper(UAGX_ShapeComponent* Shape, UAGX_DynamicWaterComponent* WaterWrapper)
{
if (NativeWindAndWaterControllerBarrier.HasNative())
{
NativeWindAndWaterControllerBarrier.SetWaterWrapper(Shape->GetOrCreateNative(), WaterWrapper->GetOrCreateNative());
}
}

void UAGX_WindAndWaterControllerSubsystemBase::SetWaterFlowGenerator(UAGX_ShapeComponent* ParentShape, UAGX_DynamicWaterComponent* DynamicWater)
{
if (NativeWindAndWaterControllerBarrier.HasNative())
{
NativeWindAndWaterControllerBarrier.SetWaterFlowGenerator(ParentShape->GetOrCreateNative(), DynamicWater->GetOrCreateNative());
}
}
7 changes: 7 additions & 0 deletions Source/AGXUnreal/Private/Shapes/AGX_ShapeComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Import/AGX_ImportContext.h"
#include "Materials/AGX_ShapeMaterial.h"
#include "Materials/ShapeMaterialBarrier.h"
#include "Model/AGX_WindAndWaterControllerSubsystemBase.h"
#include "Shapes/RenderDataBarrier.h"
#include "Shapes/RenderMaterial.h"
#include "Utilities/AGX_ImportRuntimeUtilities.h"
Expand Down Expand Up @@ -138,6 +139,12 @@ bool UAGX_ShapeComponent::UpdateNativeMaterial()
FShapeMaterialBarrier* MaterialBarrier = Instance->GetOrCreateShapeMaterialNative(World);
check(MaterialBarrier);
GetNative()->SetMaterial(*MaterialBarrier);

if (const auto WindAndWaterSubsystem = UAGX_WindAndWaterControllerSubsystemBase::GetFrom(this))
{
WindAndWaterSubsystem->UpdateNativeWindAndWaterParameters(this);
}

return true;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/AGXUnreal/Public/Materials/AGX_ShapeMaterial.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class AGXUNREAL_API UAGX_ShapeMaterial : public UObject

bool IsInstance() const;

void CopyShapeMaterialProperties(const UAGX_ShapeMaterial* Source);
virtual void CopyShapeMaterialProperties(const UAGX_ShapeMaterial* Source);

private:
void CreateNative(UWorld* PlayingWorld);
Expand Down
Loading