Files
UnrealEngine/Engine/Plugins/Experimental/Water/Source/Runtime/Private/WaterBodyLakeComponent.cpp
2025-05-18 13:04:45 +08:00

292 lines
8.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WaterBodyLakeComponent.h"
#include "Components/StaticMeshComponent.h"
#include "WaterModule.h"
#include "DynamicMesh/InfoTypes.h"
#include "WaterSplineComponent.h"
#include "WaterSubsystem.h"
#include "Engine/StaticMesh.h"
#include "LakeCollisionComponent.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Polygon2.h"
#include "ConstrainedDelaunay2.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "Curve/PolygonOffsetUtils.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(WaterBodyLakeComponent)
// ----------------------------------------------------------------------------------
UWaterBodyLakeComponent::UWaterBodyLakeComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// @todo_water : Remove these checks (Once AWaterBody is no more Blueprintable, these methods should become PURE_VIRTUAL and this class should overload them)
check(IsFlatSurface());
check(IsWaterSplineClosedLoop());
check(!IsHeightOffsetSupported());
}
TArray<UPrimitiveComponent*> UWaterBodyLakeComponent::GetCollisionComponents(bool bInOnlyEnabledComponents) const
{
TArray<UPrimitiveComponent*> Result;
if ((LakeCollision != nullptr) && (!bInOnlyEnabledComponents || (LakeCollision->GetCollisionEnabled() != ECollisionEnabled::NoCollision)))
{
Result.Add(LakeCollision);
}
return Result;
}
TArray<UPrimitiveComponent*> UWaterBodyLakeComponent::GetStandardRenderableComponents() const
{
TArray<UPrimitiveComponent*> Result = Super::GetStandardRenderableComponents();
if (LakeMeshComp != nullptr)
{
Result.Add(LakeMeshComp);
}
return Result;
}
bool UWaterBodyLakeComponent::GenerateWaterBodyMesh(UE::Geometry::FDynamicMesh3& OutMesh, UE::Geometry::FDynamicMesh3* OutDilatedMesh) const
{
using namespace UE::Geometry;
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateLakeMesh);
const UWaterSplineComponent* SplineComp = GetWaterSpline();
if (SplineComp == nullptr || SplineComp->GetNumberOfSplineSegments() < 3)
{
return false;
}
FPolygon2d LakePoly;
{
TArray<FVector> PolyLineVertices;
SplineComp->ConvertSplineToPolyLine(ESplineCoordinateSpace::Local, FMath::Square(10.f), PolyLineVertices);
for (int32 i = 0; i < PolyLineVertices.Num() - 1; ++i) // skip the last vertex since it's the same as the first vertex
{
LakePoly.AppendVertex(FVector2D(PolyLineVertices[i]));
}
}
FConstrainedDelaunay2d Triangulation;
Triangulation.FillRule = FConstrainedDelaunay2d::EFillRule::Positive;
Triangulation.Add(LakePoly);
if (!Triangulation.Triangulate())
{
UE_LOG(LogWater, Warning, TEXT("Failed to triangulate Lake mesh for %s. Ensure that the Lake's spline does not form any loops (%s)"), *GetOwner()->GetActorNameOrLabel(), *GetFullNameSafe(GetOwner()));
}
if (Triangulation.Triangles.Num() == 0)
{
return false;
}
check(OutMesh.Attributes());
FDynamicMeshColorOverlay* ColorOverlay = OutMesh.Attributes()->PrimaryColors();
FDynamicMeshNormalOverlay* NormalOverlay = OutMesh.Attributes()->PrimaryNormals();
for (const FVector2d& Vertex : Triangulation.Vertices)
{
FVertexInfo MeshVertex(FVector3d(Vertex.X, Vertex.Y, 0.f));
OutMesh.AppendVertex(MeshVertex);
ColorOverlay->AppendElement(FVector4f(0.f));
NormalOverlay->AppendElement(FVector3f(0., 0., 1.));
}
for (const FIndex3i& Triangle : Triangulation.Triangles)
{
const int TriangleID = OutMesh.AppendTriangle(Triangle);
ColorOverlay->SetTriangle(TriangleID, Triangle);
NormalOverlay->SetTriangle(TriangleID, Triangle);
}
if (ShapeDilation > 0.f && OutDilatedMesh)
{
TArray<FGeneralPolygon2d> OffsetPolys;
UE::Geometry::PolygonsOffset(
ShapeDilation / 2.f,
{ LakePoly },
OffsetPolys,
false,
2.0);
FConstrainedDelaunay2d DilationTriangulation;
DilationTriangulation.FillRule = FConstrainedDelaunay2d::EFillRule::Positive;
for (const FGeneralPolygon2d& Poly : OffsetPolys)
{
if (Poly.SignedArea() <= 0.)
{
UE_LOG(LogWater, Warning, TEXT("Failed to apply offset for shape dilation (%s"), *GetOwner()->GetActorNameOrLabel());
continue;
}
DilationTriangulation.Add(Poly);
}
if (!DilationTriangulation.Triangulate())
{
UE_LOG(LogWater, Warning, TEXT("Failed to triangulate dilated lake mesh (%s"), *GetOwner()->GetActorNameOrLabel());
return false;
}
if (DilationTriangulation.Triangles.Num() == 0)
{
return false;
}
FDynamicMeshColorOverlay* DilatedColorOverlay = OutDilatedMesh->Attributes()->PrimaryColors();
FDynamicMeshNormalOverlay* DilatedNormalOverlay = OutDilatedMesh->Attributes()->PrimaryNormals();
for (const FVector2d& Vertex : DilationTriangulation.Vertices)
{
FVertexInfo MeshVertex(FVector3d(Vertex.X, Vertex.Y, 0.));
OutDilatedMesh->AppendVertex(MeshVertex);
DilatedColorOverlay->AppendElement(FVector4f(0.f));
DilatedNormalOverlay->AppendElement(FVector3f(0., 0., 1.));
}
for (const FIndex3i& Triangle : DilationTriangulation.Triangles)
{
const int TriangleID = OutDilatedMesh->AppendTriangle(Triangle);
DilatedColorOverlay->SetTriangle(TriangleID, Triangle);
DilatedNormalOverlay->SetTriangle(TriangleID, Triangle);
}
}
return true;
}
FBoxSphereBounds UWaterBodyLakeComponent::CalcBounds(const FTransform& LocalToWorld) const
{
if (UWaterSplineComponent* WaterSpline = GetWaterSpline())
{
FBox BoxExtent = WaterSpline->GetLocalBounds().GetBox();
BoxExtent.Max.Z += MaxWaveHeightOffset;
BoxExtent.Min.Z -= GetChannelDepth();
return FBoxSphereBounds(BoxExtent).TransformBy(LocalToWorld);
}
return FBoxSphereBounds(ForceInit);
}
void UWaterBodyLakeComponent::Reset()
{
AActor* Owner = GetOwner();
check(Owner);
TArray<UStaticMeshComponent*> MeshComponents;
Owner->GetComponents(MeshComponents);
for (UStaticMeshComponent* MeshComponent : MeshComponents)
{
MeshComponent->DestroyComponent();
}
if (LakeCollision)
{
LakeCollision->DestroyComponent();
}
LakeCollision = nullptr;
LakeMeshComp = nullptr;
}
void UWaterBodyLakeComponent::OnUpdateBody(bool bWithExclusionVolumes)
{
AActor* OwnerActor = GetOwner();
check(OwnerActor);
if (!LakeMeshComp)
{
LakeMeshComp = NewObject<UStaticMeshComponent>(OwnerActor, TEXT("LakeMeshComponent"), RF_Transactional);
LakeMeshComp->SetupAttachment(this);
LakeMeshComp->RegisterComponent();
}
if (GetCollisionEnabled() != ECollisionEnabled::NoCollision)
{
if (!LakeCollision)
{
LakeCollision = NewObject<ULakeCollisionComponent>(OwnerActor, TEXT("LakeCollisionComponent"), RF_Transactional);
LakeCollision->SetNetAddressable(); // it's deterministically named so it's addressable over network (needed for collision)
LakeCollision->SetupAttachment(this);
LakeCollision->RegisterComponent();
}
}
else
{
if (LakeCollision)
{
LakeCollision->DestroyComponent();
}
LakeCollision = nullptr;
}
if (UWaterSplineComponent* WaterSpline = GetWaterSpline())
{
UStaticMesh* WaterMesh = GetWaterMeshOverride() ? GetWaterMeshOverride() : ToRawPtr(UWaterSubsystem::StaticClass()->GetDefaultObject<UWaterSubsystem>()->DefaultLakeMesh);
const FVector SplineExtent = WaterSpline->Bounds.BoxExtent;
FVector WorldLoc(WaterSpline->Bounds.Origin);
WorldLoc.Z = GetComponentLocation().Z;
if (WaterMesh)
{
FTransform MeshCompToWorld = WaterSpline->GetComponentToWorld();
// Scale the water mesh so that it is the size of the bounds
FVector MeshExtent = WaterMesh->GetBounds().BoxExtent;
MeshExtent.Z = 1.0f;
const FVector LocalSplineExtent = WaterSpline->Bounds.TransformBy(MeshCompToWorld.Inverse()).BoxExtent;
const FVector ScaleRatio = SplineExtent / MeshExtent;
LakeMeshComp->SetWorldScale3D(FVector(ScaleRatio.X, ScaleRatio.Y, 1));
LakeMeshComp->SetWorldLocation(WorldLoc);
LakeMeshComp->SetWorldRotation(FQuat::Identity);
LakeMeshComp->SetAbsolute(false, false, true);
LakeMeshComp->SetStaticMesh(WaterMesh);
LakeMeshComp->SetMaterial(0, GetWaterMaterialInstance());
LakeMeshComp->SetCastShadow(false);
LakeMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
LakeMeshComp->SetMobility(Mobility);
if (LakeCollision)
{
check(GetCollisionEnabled () != ECollisionEnabled::NoCollision);
LakeCollision->SetMobility(Mobility);
CopySharedCollisionSettingsToComponent(LakeCollision);
CopySharedNavigationSettingsToComponent(LakeCollision);
const float Depth = GetChannelDepth() / 2;
FVector LakeCollisionExtent = FVector(SplineExtent.X, SplineExtent.Y, 0.f) / GetComponentScale();
LakeCollisionExtent.Z = Depth + CollisionHeightOffset / 2;
LakeCollision->SetWorldLocation(WorldLoc + FVector(0, 0, -Depth + CollisionHeightOffset / 2));
LakeCollision->UpdateCollision(LakeCollisionExtent, true);
}
}
}
#if WITH_EDITOR
const TCHAR* UWaterBodyLakeComponent::GetWaterSpriteTextureName() const
{
return TEXT("/Water/Icons/WaterBodyLakeSprite");
}
FVector UWaterBodyLakeComponent::GetWaterSpriteLocation() const
{
return GetWaterSpline()->Bounds.Origin;
}
#endif // WITH_EDITOR