599 lines
21 KiB
C++
599 lines
21 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "WaterBodyRiverComponent.h"
|
|
#include "Chaos/CollisionConvexMesh.h"
|
|
#include "WaterSplineComponent.h"
|
|
#include "DynamicMesh/InfoTypes.h"
|
|
#include "WaterSubsystem.h"
|
|
#include "WaterUtils.h"
|
|
#include "Algo/ForEach.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Components/SplineMeshComponent.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/World.h"
|
|
#include "DynamicMesh/DynamicMesh3.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
#include "WaterBodyActor.h"
|
|
|
|
// for working around Chaos issue
|
|
#include "Chaos/Convex.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(WaterBodyRiverComponent)
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
UWaterBodyRiverComponent::UWaterBodyRiverComponent(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*> UWaterBodyRiverComponent::GetCollisionComponents(bool bInOnlyEnabledComponents) const
|
|
{
|
|
TArray<UPrimitiveComponent*> Result;
|
|
Result.Reserve(SplineMeshComponents.Num());
|
|
|
|
Algo::TransformIf(SplineMeshComponents, Result,
|
|
[bInOnlyEnabledComponents](USplineMeshComponent* SplineComp) { return ((SplineComp != nullptr) && (!bInOnlyEnabledComponents || (SplineComp->GetCollisionEnabled() != ECollisionEnabled::NoCollision))); },
|
|
[](USplineMeshComponent* SplineComp) { return SplineComp; });
|
|
|
|
return Result;
|
|
}
|
|
|
|
TArray<UPrimitiveComponent*> UWaterBodyRiverComponent::GetStandardRenderableComponents() const
|
|
{
|
|
TArray<UPrimitiveComponent*> Result = Super::GetStandardRenderableComponents();
|
|
Result.Reserve(Result.Num() + SplineMeshComponents.Num());
|
|
|
|
Algo::CopyIf(SplineMeshComponents, Result,
|
|
[](USplineMeshComponent* SplineComp) { return (SplineComp != nullptr); });
|
|
|
|
return Result;
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::OnUpdateBody(bool bWithExclusionVolumes)
|
|
{
|
|
if (const USplineComponent* WaterSpline = GetWaterSpline())
|
|
{
|
|
const int32 NumSplinePoints = WaterSpline->GetNumberOfSplinePoints();
|
|
const int32 NumberOfMeshComponentsNeeded = WaterSpline->IsClosedLoop() ? NumSplinePoints : NumSplinePoints - 1;
|
|
if (SplineMeshComponents.Num() != NumberOfMeshComponentsNeeded)
|
|
{
|
|
GenerateMeshes();
|
|
}
|
|
else
|
|
{
|
|
for (int32 SplinePtIndex = 0; SplinePtIndex < NumberOfMeshComponentsNeeded; ++SplinePtIndex)
|
|
{
|
|
USplineMeshComponent* MeshComp = SplineMeshComponents[SplinePtIndex];
|
|
|
|
// Validate all mesh components. Blueprints can null these out somehow
|
|
if (MeshComp)
|
|
{
|
|
UpdateSplineMesh(MeshComp, SplinePtIndex);
|
|
MeshComp->MarkRenderStateDirty();
|
|
}
|
|
else
|
|
{
|
|
GenerateMeshes();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
static void AddVerticesForRiverSplineStep(
|
|
float DistanceAlongSpline,
|
|
const UWaterBodyRiverComponent* Component,
|
|
const UWaterSplineComponent* SplineComp,
|
|
const UWaterSplineMetadata* WaterSplineMetadata,
|
|
UE::Geometry::FDynamicMesh3& OutMesh,
|
|
UE::Geometry::FDynamicMesh3* OutDilatedMesh)
|
|
{
|
|
using namespace UE::Geometry;
|
|
check((Component != nullptr) && (SplineComp != nullptr) && (WaterSplineMetadata != nullptr));
|
|
|
|
const FVector Tangent = SplineComp->GetTangentAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local).GetSafeNormal();
|
|
const FVector Up = SplineComp->GetUpVectorAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local).GetSafeNormal();
|
|
|
|
const FVector Normal = FVector::CrossProduct(Tangent, Up).GetSafeNormal();
|
|
const FVector Pos = SplineComp->GetLocationAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local);
|
|
|
|
const float Key = SplineComp->GetInputKeyValueAtDistanceAlongSpline(DistanceAlongSpline);
|
|
const float HalfWidth = WaterSplineMetadata->RiverWidth.Eval(Key) / 2;
|
|
float Velocity = WaterSplineMetadata->WaterVelocityScalar.Eval(Key);
|
|
|
|
// Distance from the center of the spline to place our first vertices
|
|
FVector OutwardDistance = Normal * HalfWidth;
|
|
// Prevent there being a relative height difference between the two vertices even when the spline has a slight roll to it
|
|
OutwardDistance.Z = 0.f;
|
|
|
|
float FlowDirection = Tangent.HeadingAngle() + FMath::DegreesToRadians(Component->GetRelativeRotation().Yaw);
|
|
|
|
// Restrict all angles between [0, 2 PI]. UnwindRadians returns a value between [-Pi, Pi] so we must remap again:
|
|
FlowDirection = FMath::UnwindRadians(FlowDirection);
|
|
if (FlowDirection < 0.f)
|
|
{
|
|
FlowDirection += TWO_PI;
|
|
}
|
|
|
|
// If negative velocity, inverse the direction and change the velocity back to positive.
|
|
if (Velocity < 0.f)
|
|
{
|
|
Velocity *= -1.f;
|
|
FlowDirection = FMath::Fmod(PI + FlowDirection, TWO_PI);
|
|
}
|
|
|
|
FVertexInfo Left(FVector3d(Pos - OutwardDistance));
|
|
FVertexInfo Right(FVector3d(Pos + OutwardDistance));
|
|
|
|
const FVector4f FlowData = FWaterUtils::PackFlowData(Velocity, FlowDirection);
|
|
Left.Color = FlowData;
|
|
Right.Color = FlowData;
|
|
|
|
check(OutMesh.Attributes());
|
|
FDynamicMeshColorOverlay* Colors = OutMesh.Attributes()->PrimaryColors();
|
|
FDynamicMeshNormalOverlay* Normals = OutMesh.Attributes()->PrimaryNormals();
|
|
// Append regular geometry to mesh:
|
|
{
|
|
/* Non - dilated river segment geometry:
|
|
0 --- 1
|
|
| / |
|
|
-2 --- -1
|
|
*/
|
|
const int32 BaseIndex = OutMesh.GetVerticesBuffer().Num();
|
|
|
|
OutMesh.AppendVertex(Left);
|
|
Colors->AppendElement(FlowData);
|
|
Normals->AppendElement(FVector3f(0., 0., 1.));
|
|
|
|
OutMesh.AppendVertex(Right);
|
|
Colors->AppendElement(FlowData);
|
|
Normals->AppendElement(FVector3f(0., 0., 1.));
|
|
|
|
if (BaseIndex != 0)
|
|
{
|
|
int TriangleID1 = OutMesh.AppendTriangle(BaseIndex - 2, BaseIndex + 1, BaseIndex - 1);
|
|
int TriangleID2 = OutMesh.AppendTriangle(BaseIndex - 2, BaseIndex, BaseIndex + 1);
|
|
|
|
Colors->SetTriangle(TriangleID1, FIndex3i(BaseIndex - 2, BaseIndex + 1, BaseIndex - 1));
|
|
Colors->SetTriangle(TriangleID2, FIndex3i(BaseIndex - 2, BaseIndex, BaseIndex + 1));
|
|
}
|
|
}
|
|
|
|
const float DilationAmount = Component->ShapeDilation;
|
|
// If dilation is required, append dilated geometry:
|
|
if (DilationAmount > 0.f && OutDilatedMesh)
|
|
{
|
|
const FVector DilationOffset = Normal * DilationAmount;
|
|
|
|
const FVertexInfo DilatedFarLeft(FVector3d(Pos - OutwardDistance - DilationOffset));
|
|
const FVertexInfo DilatedLeft(FVector3d(Pos - OutwardDistance));
|
|
const FVertexInfo DilatedRight(FVector3d(Pos + OutwardDistance));
|
|
const FVertexInfo DilatedFarRight(FVector3d(Pos + OutwardDistance + DilationOffset));
|
|
|
|
/* Dilated River segment geometry:
|
|
1 --- 2 3 --- 4
|
|
| / | | / |
|
|
-4 --- -3 -2 --- -1
|
|
*/
|
|
const int32 BaseIndex = OutDilatedMesh->GetVerticesBuffer().Num();
|
|
|
|
OutDilatedMesh->AppendVertex(DilatedFarLeft);
|
|
OutDilatedMesh->AppendVertex(DilatedLeft);
|
|
OutDilatedMesh->AppendVertex(DilatedRight);
|
|
OutDilatedMesh->AppendVertex(DilatedFarRight);
|
|
|
|
// If this is the first point we need to add the triangles for the starting dilated center quad since that references vertices which were only just appended
|
|
if (DistanceAlongSpline == 0.f)
|
|
{
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 3, BaseIndex + 2, BaseIndex - 2);
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 3, BaseIndex + 1, BaseIndex + 2);
|
|
}
|
|
|
|
if (BaseIndex != 0)
|
|
{
|
|
// Append left dilation quad
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 4, BaseIndex + 1, BaseIndex - 3);
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 4, BaseIndex, BaseIndex + 1);
|
|
// Append right dilation quad
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 2, BaseIndex + 2, BaseIndex + 3);
|
|
OutDilatedMesh->AppendTriangle(BaseIndex - 2, BaseIndex + 3, BaseIndex - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum class ERiverBoundaryEdge {
|
|
Start,
|
|
End,
|
|
};
|
|
|
|
static void AddTerminalVerticesForRiverSpline(
|
|
ERiverBoundaryEdge Edge,
|
|
const UWaterBodyRiverComponent* Component,
|
|
const UWaterSplineComponent* SplineComp,
|
|
const UWaterSplineMetadata* WaterSplineMetadata,
|
|
UE::Geometry::FDynamicMesh3& OutDilatedMesh)
|
|
{
|
|
using namespace UE::Geometry;
|
|
|
|
check((Component != nullptr) && (SplineComp != nullptr) && (WaterSplineMetadata != nullptr));
|
|
|
|
const float DistanceAlongSpline = (Edge == ERiverBoundaryEdge::Start) ? 0.f : SplineComp->GetSplineLength();
|
|
|
|
const FVector Tangent = SplineComp->GetTangentAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local).GetSafeNormal();
|
|
const FVector Up = SplineComp->GetUpVectorAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local).GetSafeNormal();
|
|
|
|
const FVector Normal = FVector::CrossProduct(Tangent, Up).GetSafeNormal();
|
|
const FVector Pos = SplineComp->GetLocationAtDistanceAlongSpline(DistanceAlongSpline, ESplineCoordinateSpace::Local);
|
|
|
|
const float Key = SplineComp->GetInputKeyValueAtDistanceAlongSpline(DistanceAlongSpline);
|
|
const float HalfWidth = WaterSplineMetadata->RiverWidth.Eval(Key) / 2;
|
|
|
|
const float DilationAmount = Component->ShapeDilation;
|
|
const FVector DilationOffset = Normal * DilationAmount;
|
|
FVector OutwardDistance = Normal * HalfWidth;
|
|
OutwardDistance.Z = 0.f;
|
|
|
|
FVector TangentialOffset = Tangent * DilationAmount;
|
|
TangentialOffset.Z = 0.f;
|
|
|
|
// For the starting edge the tangential offset is negative to push it backwards
|
|
if (Edge == ERiverBoundaryEdge::Start)
|
|
{
|
|
TangentialOffset *= -1;
|
|
}
|
|
|
|
const FVertexInfo BackLeft(FVector3d(Pos - OutwardDistance + TangentialOffset - DilationOffset));
|
|
const FVertexInfo Left(FVector3d(Pos - OutwardDistance + TangentialOffset));
|
|
const FVertexInfo Right(FVector3d(Pos + OutwardDistance + TangentialOffset));
|
|
const FVertexInfo BackRight(FVector3d(Pos + OutwardDistance + TangentialOffset + DilationOffset));
|
|
|
|
const int32 BaseIndex = OutDilatedMesh.GetVerticesBuffer().Num();
|
|
|
|
OutDilatedMesh.AppendVertex(BackLeft);
|
|
OutDilatedMesh.AppendVertex(Left);
|
|
OutDilatedMesh.AppendVertex(Right);
|
|
OutDilatedMesh.AppendVertex(BackRight);
|
|
|
|
|
|
if (Edge == ERiverBoundaryEdge::End)
|
|
{
|
|
/* Dilated edge segment geometry:
|
|
0 --- 1 ------- 2 --- 3
|
|
| / | / | / |
|
|
-4 --- -3 ----- -2 --- -1
|
|
*/
|
|
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 4, BaseIndex + 0, BaseIndex + 1);
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 4, BaseIndex + 1, BaseIndex - 3);
|
|
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 3, BaseIndex + 1, BaseIndex + 2);
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 3, BaseIndex + 2, BaseIndex - 2);
|
|
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 2, BaseIndex + 2, BaseIndex + 3);
|
|
OutDilatedMesh.AppendTriangle(BaseIndex - 2, BaseIndex + 3, BaseIndex - 1);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------
|
|
|
|
bool UWaterBodyRiverComponent::GenerateWaterBodyMesh(UE::Geometry::FDynamicMesh3& OutMesh, UE::Geometry::FDynamicMesh3* OutDilatedMesh) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateRiverMesh);
|
|
|
|
const UWaterSplineComponent* SplineComp = GetWaterSpline();
|
|
|
|
check(GetWaterBodyActor());
|
|
|
|
const UWaterSplineMetadata* SplineMetadata = GetWaterBodyActor()->GetWaterSplineMetadata();
|
|
if ((SplineComp == nullptr) || (SplineMetadata == nullptr) || (SplineComp->GetNumberOfSplinePoints() < 2))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<double> Distances;
|
|
TArray<FVector> Points;
|
|
SplineComp->ConvertSplineToPolyLineWithDistances(ESplineCoordinateSpace::Local, FMath::Square(5.f), Points, Distances);
|
|
if (Distances.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ShapeDilation > 0.f && OutDilatedMesh)
|
|
{
|
|
AddTerminalVerticesForRiverSpline(ERiverBoundaryEdge::Start, this, SplineComp, SplineMetadata, *OutDilatedMesh);
|
|
}
|
|
|
|
for (double DistanceAlongSpline : Distances)
|
|
{
|
|
AddVerticesForRiverSplineStep(DistanceAlongSpline, this, SplineComp, SplineMetadata, OutMesh, OutDilatedMesh);
|
|
}
|
|
|
|
if (ShapeDilation > 0.f && OutDilatedMesh)
|
|
{
|
|
AddTerminalVerticesForRiverSpline(ERiverBoundaryEdge::End, this, SplineComp, SplineMetadata, *OutDilatedMesh);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FBoxSphereBounds UWaterBodyRiverComponent::CalcBounds(const FTransform& LocalToWorld) const
|
|
{
|
|
FBox BoxExtent(ForceInit);
|
|
for (const USplineMeshComponent* SplineMeshComponent : SplineMeshComponents)
|
|
{
|
|
if (SplineMeshComponent != nullptr)
|
|
{
|
|
const FBox SplineMeshComponentBounds = SplineMeshComponent->CalcBounds(SplineMeshComponent->GetRelativeTransform()).GetBox();
|
|
BoxExtent += SplineMeshComponentBounds;
|
|
}
|
|
}
|
|
// Spline mesh components aren't storing our vertical bounds so account for that with the ChannelDepth parameter.
|
|
BoxExtent.Max.Z += MaxWaveHeightOffset;
|
|
BoxExtent.Min.Z -= GetChannelDepth();
|
|
return FBoxSphereBounds(BoxExtent).TransformBy(LocalToWorld);
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::UpdateMaterialInstances()
|
|
{
|
|
CreateOrUpdateLakeTransitionMID();
|
|
CreateOrUpdateOceanTransitionMID();
|
|
|
|
// Must be called after the transition MIDs are created. Super::UpdateMaterialInstances will rebuild the water mesh if necessary to push new MIDs.
|
|
Super::UpdateMaterialInstances();
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::SetLakeTransitionMaterial(UMaterialInterface* InMaterial)
|
|
{
|
|
LakeTransitionMaterial = InMaterial;
|
|
UpdateMaterialInstances();
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::SetOceanTransitionMaterial(UMaterialInterface* InMaterial)
|
|
{
|
|
OceanTransitionMaterial = InMaterial;
|
|
UpdateMaterialInstances();
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::SetLakeAndOceanTransitionMaterials(UMaterialInterface* InLakeTransition, UMaterialInterface* InOceanTransition)
|
|
{
|
|
const bool bUpdateInstances = LakeTransitionMaterial != InLakeTransition || OceanTransitionMaterial != InOceanTransition;
|
|
|
|
LakeTransitionMaterial = InLakeTransition;
|
|
OceanTransitionMaterial = InOceanTransition;
|
|
|
|
if (bUpdateInstances)
|
|
{
|
|
UpdateMaterialInstances();
|
|
}
|
|
}
|
|
|
|
float UWaterBodyRiverComponent::GetRiverWidthAtSplineInputKey(float InKey) const
|
|
{
|
|
return WaterSplineMetadata ? WaterSplineMetadata->RiverWidth.Eval(InKey, 0.f) : 0.0f;
|
|
}
|
|
|
|
float UWaterBodyRiverComponent::GetRiverDepthAtSplineInputKey(float InKey) const
|
|
{
|
|
return WaterSplineMetadata ? WaterSplineMetadata->Depth.Eval(InKey, 0.f) : 0.0f;
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::SetRiverWidthAtSplineInputKey(float InKey, float InWidth)
|
|
{
|
|
if (WaterSplineMetadata)
|
|
{
|
|
const int32 PointIndexForKey = WaterSplineMetadata->RiverWidth.GetPointIndexForInputValue(InKey);
|
|
WaterSplineMetadata->RiverWidth.Points[PointIndexForKey].OutVal = InWidth;
|
|
}
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::SetRiverDepthAtSplineInputKey(float InKey, float InDepth)
|
|
{
|
|
if (WaterSplineMetadata)
|
|
{
|
|
const int32 PointIndexForKey = WaterSplineMetadata->Depth.GetPointIndexForInputValue(InKey);
|
|
WaterSplineMetadata->Depth.Points[PointIndexForKey].OutVal = InDepth;
|
|
}
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::Reset()
|
|
{
|
|
for (USplineMeshComponent* Comp : SplineMeshComponents)
|
|
{
|
|
if (Comp)
|
|
{
|
|
Comp->DestroyComponent();
|
|
}
|
|
}
|
|
SplineMeshComponents.Empty();
|
|
}
|
|
|
|
UMaterialInstanceDynamic* UWaterBodyRiverComponent::GetRiverToLakeTransitionMaterialInstance()
|
|
{
|
|
CreateOrUpdateLakeTransitionMID();
|
|
return LakeTransitionMID;
|
|
}
|
|
|
|
UMaterialInstanceDynamic* UWaterBodyRiverComponent::GetRiverToOceanTransitionMaterialInstance()
|
|
{
|
|
CreateOrUpdateOceanTransitionMID();
|
|
return OceanTransitionMID;
|
|
}
|
|
|
|
UMaterialInterface* UWaterBodyRiverComponent::GetRiverToLakeTransitionMaterial() const
|
|
{
|
|
return LakeTransitionMaterial;
|
|
}
|
|
|
|
UMaterialInterface* UWaterBodyRiverComponent::GetRiverToOceanTransitionMaterial() const
|
|
{
|
|
return OceanTransitionMaterial;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
TArray<UPrimitiveComponent*> UWaterBodyRiverComponent::GetBrushRenderableComponents() const
|
|
{
|
|
TArray<UPrimitiveComponent*> BrushRenderableComponents;
|
|
|
|
BrushRenderableComponents.Reserve(SplineMeshComponents.Num());
|
|
Algo::ForEach(SplineMeshComponents, [&BrushRenderableComponents](USplineMeshComponent* Comp)
|
|
{
|
|
if (Comp != nullptr)
|
|
{
|
|
BrushRenderableComponents.Add(StaticCast<UPrimitiveComponent*>(Comp));
|
|
}
|
|
});
|
|
|
|
return BrushRenderableComponents;
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
void UWaterBodyRiverComponent::CreateOrUpdateLakeTransitionMID()
|
|
{
|
|
if (GetWorld())
|
|
{
|
|
LakeTransitionMID = FWaterUtils::GetOrCreateTransientMID(LakeTransitionMID, TEXT("LakeTransitionMID"), LakeTransitionMaterial, GetTransientMIDFlags());
|
|
|
|
SetDynamicParametersOnMID(LakeTransitionMID);
|
|
}
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::CreateOrUpdateOceanTransitionMID()
|
|
{
|
|
if (GetWorld())
|
|
{
|
|
OceanTransitionMID = FWaterUtils::GetOrCreateTransientMID(OceanTransitionMID, TEXT("OceanTransitionMID"), OceanTransitionMaterial, GetTransientMIDFlags());
|
|
|
|
SetDynamicParametersOnMID(OceanTransitionMID);
|
|
}
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::GenerateMeshes()
|
|
{
|
|
Reset();
|
|
|
|
AActor* Owner = GetOwner();
|
|
check(Owner);
|
|
|
|
if (const USplineComponent* WaterSpline = GetWaterSpline())
|
|
{
|
|
const int32 NumSplinePoints = WaterSpline->GetNumberOfSplinePoints();
|
|
|
|
const int32 NumberOfMeshComponentsNeeded = WaterSpline->IsClosedLoop() ? NumSplinePoints : NumSplinePoints - 1;
|
|
for (int32 SplinePtIndex = 0; SplinePtIndex < NumberOfMeshComponentsNeeded; ++SplinePtIndex)
|
|
{
|
|
FString Name = FString::Printf(TEXT("SplineMeshComponent_%d"), SplinePtIndex);
|
|
USplineMeshComponent* MeshComp = NewObject<USplineMeshComponent>(Owner, *Name, RF_Transactional);
|
|
MeshComp->SetNetAddressable(); // it's deterministically named so it's addressable over network (needed for collision)
|
|
SplineMeshComponents.Add(MeshComp);
|
|
MeshComp->SetMobility(Owner->GetRootComponent()->Mobility);
|
|
MeshComp->SetupAttachment(this);
|
|
if (GetWorld() && GetWorld()->bIsWorldInitialized)
|
|
{
|
|
MeshComp->RegisterComponent();
|
|
}
|
|
|
|
// Call UpdateSplineMesh after RegisterComponent so that physics state creation can happen (needs the component to be registered)
|
|
UpdateSplineMesh(MeshComp, SplinePtIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UWaterBodyRiverComponent::UpdateSplineMesh(USplineMeshComponent* MeshComp, int32 SplinePointIndex)
|
|
{
|
|
if (const USplineComponent* WaterSpline = GetWaterSpline())
|
|
{
|
|
const int32 NumSplinePoints = WaterSpline->GetNumberOfSplinePoints();
|
|
|
|
const int32 StartSplinePointIndex = SplinePointIndex;
|
|
const int32 StopSplinePointIndex = WaterSpline->IsClosedLoop() && StartSplinePointIndex == NumSplinePoints - 1 ? 0 : StartSplinePointIndex + 1;
|
|
|
|
UStaticMesh* StaticMesh = GetWaterMeshOverride();
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
StaticMesh = UWaterSubsystem::StaticClass()->GetDefaultObject<UWaterSubsystem>()->DefaultRiverMesh;
|
|
}
|
|
check(StaticMesh != nullptr);
|
|
MeshComp->SetStaticMesh(StaticMesh);
|
|
MeshComp->SetMaterial(0, GetWaterMaterialInstance());
|
|
|
|
CopySharedCollisionSettingsToComponent(MeshComp);
|
|
CopySharedNavigationSettingsToComponent(MeshComp);
|
|
MeshComp->SetCastShadow(false);
|
|
|
|
const bool bUpdateMesh = false;
|
|
const FVector StartScale = WaterSpline->GetScaleAtSplinePoint(StartSplinePointIndex);
|
|
const FVector EndScale = WaterSpline->GetScaleAtSplinePoint(StopSplinePointIndex);
|
|
|
|
// Scale the water mesh so that it is the size of the bounds
|
|
FVector StaticMeshExtent = 2.0f * StaticMesh->GetBounds().BoxExtent;
|
|
StaticMeshExtent.X = FMath::Max(StaticMeshExtent.X, 0.0001f);
|
|
StaticMeshExtent.Y = FMath::Max(StaticMeshExtent.Y, 0.0001f);
|
|
StaticMeshExtent.Z = 1.0f;
|
|
|
|
MeshComp->SetStartScale(FVector2D(StartScale / StaticMeshExtent), bUpdateMesh);
|
|
MeshComp->SetEndScale(FVector2D(EndScale / StaticMeshExtent), bUpdateMesh);
|
|
|
|
FVector StartPos, StartTangent;
|
|
WaterSpline->GetLocationAndTangentAtSplinePoint(StartSplinePointIndex, StartPos, StartTangent, ESplineCoordinateSpace::Local);
|
|
|
|
FVector EndPos, EndTangent;
|
|
WaterSpline->GetLocationAndTangentAtSplinePoint(StopSplinePointIndex, EndPos, EndTangent, ESplineCoordinateSpace::Local);
|
|
|
|
MeshComp->SetStartAndEnd(StartPos, StartTangent, EndPos, EndTangent, bUpdateMesh);
|
|
|
|
MeshComp->UpdateMesh();
|
|
|
|
//
|
|
// GROSS HACK to work around the issue above that CreatePhysicsMeshes() doesn't currently work at Runtime.
|
|
// The "cook" for a FKConvexElem just creates and caches a Chaos::FConvex instance, and the restore from cooked
|
|
// data just restores that and passes it to the FKConvexElem. So we're just going to do that ourselves.
|
|
// Code below is taken from FChaosDerivedDataCooker::BuildConvexMeshes()
|
|
//
|
|
if (MeshComp->GetBodySetup())
|
|
{
|
|
for (FKConvexElem& Elem : MeshComp->GetBodySetup()->AggGeom.ConvexElems)
|
|
{
|
|
const int32 NumHullVerts = Elem.VertexData.Num();
|
|
TArray<Chaos::FConvex::FVec3Type> ConvexVertices;
|
|
ConvexVertices.SetNum(NumHullVerts);
|
|
for (int32 VertIndex = 0; VertIndex < NumHullVerts; ++VertIndex)
|
|
{
|
|
ConvexVertices[VertIndex] = Elem.VertexData[VertIndex];
|
|
}
|
|
|
|
Chaos::FConvexPtr ChaosConvex( new Chaos::FConvex(ConvexVertices, 0.0f));
|
|
|
|
Elem.SetConvexMeshObject(MoveTemp(ChaosConvex));
|
|
}
|
|
|
|
MeshComp->RecreatePhysicsState();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UWaterBodyRiverComponent::OnPostEditChangeProperty(FOnWaterBodyChangedParams& InOutOnWaterBodyChangedParams)
|
|
{
|
|
Super::OnPostEditChangeProperty(InOutOnWaterBodyChangedParams);
|
|
|
|
if (InOutOnWaterBodyChangedParams.PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UWaterBodyRiverComponent, LakeTransitionMaterial) ||
|
|
InOutOnWaterBodyChangedParams.PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UWaterBodyRiverComponent, OceanTransitionMaterial))
|
|
{
|
|
UpdateMaterialInstances();
|
|
}
|
|
}
|
|
const TCHAR* UWaterBodyRiverComponent::GetWaterSpriteTextureName() const
|
|
{
|
|
return TEXT("/Water/Icons/WaterBodyRiverSprite");
|
|
}
|
|
#endif
|