Files
UnrealEngine/Engine/Source/Runtime/Landscape/Private/LandscapeSplines.cpp
2025-05-18 13:04:45 +08:00

3981 lines
138 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
LandscapeSpline.cpp
=============================================================================*/
#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformStackWalk.h"
#include "Misc/Guid.h"
#include "Math/RandomStream.h"
#include "SceneView.h"
#include "UObject/ObjectMacros.h"
#include "Templates/Casts.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/EngineTypes.h"
#include "HitProxies.h"
#include "PrimitiveViewRelevance.h"
#include "PrimitiveSceneProxy.h"
#include "LandscapeProxy.h"
#include "Engine/Texture2D.h"
#include "PrimitiveDrawingUtils.h"
#include "LandscapeComponent.h"
#include "LandscapeVersion.h"
#include "Components/SplineMeshComponent.h"
#include "Components/BillboardComponent.h"
#include "Engine/StaticMesh.h"
#include "LandscapeSplineProxies.h"
#include "LandscapeSplinesComponent.h"
#include "LandscapeSplineSegment.h"
#include "LandscapeSplineRaster.h"
#include "LandscapeSplineControlPoint.h"
#include "LandscapePrivate.h"
#include "MeshElementCollector.h"
#include "ILandscapeSplineInterface.h"
#include "ControlPointMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "Engine/Engine.h"
#include "PhysicsEngine/BodySetup.h"
#include "Engine/StaticMeshSocket.h"
#include "EngineGlobals.h"
#include "TextureResource.h"
#include "UObject/FortniteReleaseBranchCustomObjectVersion.h"
#include "UObject/UObjectIterator.h"
#if WITH_EDITOR
#include "Logging/TokenizedMessage.h"
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
#include "Landscape.h"
#include "LandscapeLayerInfoObject.h"
#include "LandscapeInfoMap.h"
#include "LandscapeSplineActor.h"
#include "LandscapeSettings.h"
#endif
IMPLEMENT_HIT_PROXY(HLandscapeSplineProxy, HHitProxy);
IMPLEMENT_HIT_PROXY(HLandscapeSplineProxy_Segment, HLandscapeSplineProxy);
IMPLEMENT_HIT_PROXY(HLandscapeSplineProxy_ControlPoint, HLandscapeSplineProxy);
IMPLEMENT_HIT_PROXY(HLandscapeSplineProxy_Tangent, HLandscapeSplineProxy);
#define LOCTEXT_NAMESPACE "Landscape.Splines"
int32 SplinesAlwaysUseBlockAll = 0;
static FAutoConsoleVariableRef CVarSplinesAlwaysUseBlockAll(
TEXT("splines.blockall"),
SplinesAlwaysUseBlockAll,
TEXT("Force splines to always use the BlockAll collision profile instead of whatever is stored in the CollisionProfileName property")
);
int32 LandscapeSplineToSplineComponentMaxIterations = 200;
static FAutoConsoleVariableRef CVarLandscapeSplineToSplineComponentMaxIterations(
TEXT("Landscape.Splines.ApplyToSplineComponentMaxIterations"),
LandscapeSplineToSplineComponentMaxIterations,
TEXT("Max possible iterations when converting a landscape spline into a spline component")
);
#if WITH_EDITOR
static FAutoConsoleVariable CVarLandscapeSplineEnableFixedSpriteIcons(
TEXT("landscape.Splines.EnableFixedSpriteIcons"),
false,
TEXT("Enables fixed scaling and Z-offset for spline icons. If disabled, configure dynamic scale and offset in project settings."));
#endif // WITH_EDITOR
//////////////////////////////////////////////////////////////////////////
// LANDSCAPE SPLINES SCENE PROXY
/** Represents a ULandscapeSplinesComponent to the scene manager. */
#if WITH_EDITOR
struct FLandscapeFixSplines
{
FLandscapeFixSplines()
: FixSplinesConsoleCommand(
TEXT("Landscape.FixSplines"),
TEXT("One off fix for bad layer width"),
FConsoleCommandDelegate::CreateRaw(this, &FLandscapeFixSplines::FixSplines))
{
}
FAutoConsoleCommand FixSplinesConsoleCommand;
void FixSplines()
{
bool bFixed = false;
for (TObjectIterator<UWorld> It(/*AdditionalExclusionFlags = */RF_ClassDefaultObject, /*bIncludeDerivedClasses = */true, /*InInternalExclusionFlags = */EInternalObjectFlags::Garbage); It; ++It)
{
UWorld* CurrentWorld = *It;
if (!CurrentWorld->IsGameWorld())
{
auto& LandscapeInfoMap = ULandscapeInfoMap::GetLandscapeInfoMap(CurrentWorld);
for (auto& Pair : LandscapeInfoMap.Map)
{
if (Pair.Value && Pair.Value->SupportsLandscapeEditing())
{
Pair.Value->ForAllSplineActors([&bFixed](TScriptInterface<ILandscapeSplineInterface> SplineOwner)
{
if (SplineOwner && SplineOwner->GetSplinesComponent())
{
SplineOwner->GetSplinesComponent()->RebuildAllSplines();
bFixed = true;
}
});
if (Pair.Value->LandscapeActor.IsValid() && Pair.Value->LandscapeActor->HasLayersContent())
{
Pair.Value->LandscapeActor->RequestSplineLayerUpdate();
}
}
}
}
}
UE_LOG(LogLandscape, Display, TEXT("Landscape.FixSplines: %s"), bFixed ? TEXT("Splines fixed") : TEXT("Nothing to fix"));
}
};
FLandscapeFixSplines GLandscapeFixSplines;
class FLandscapeSplinesSceneProxy final : public FPrimitiveSceneProxy
{
private:
SIZE_T GetTypeHash() const override
{
static size_t UniquePointer;
return reinterpret_cast<size_t>(&UniquePointer);
}
const FLinearColor SplineColor;
const UTexture2D* ControlPointSprite;
const bool bDrawControlPointSprite;
const bool bDrawFalloff;
struct FSegmentProxy
{
ULandscapeSplineSegment* Owner;
TRefCountPtr<HHitProxy> HitProxy;
TArray<FLandscapeSplineInterpPoint> Points;
uint32 bSelected : 1;
};
TArray<FSegmentProxy> Segments;
struct FControlPointProxy
{
ULandscapeSplineControlPoint* Owner;
TRefCountPtr<HHitProxy> HitProxy;
FVector Location;
TArray<FLandscapeSplineInterpPoint> Points;
// Controls sprite scaling based on segment width, may be removed in favor of project settings as sole scale factor
// Scale usage toggled by CVar command landscape.Splines.EnableFixedSpriteIcons
float SpriteScale;
uint32 bSelected : 1;
};
TArray<FControlPointProxy> ControlPoints;
public:
~FLandscapeSplinesSceneProxy()
{
}
FLandscapeSplinesSceneProxy(ULandscapeSplinesComponent* Component):
FPrimitiveSceneProxy(Component),
SplineColor(Component->SplineColor),
ControlPointSprite(Component->ControlPointSprite),
bDrawControlPointSprite(Component->bShowSplineEditorMesh),
bDrawFalloff(Component->bShowSplineEditorMesh)
{
Segments.Reserve(Component->Segments.Num());
for (ULandscapeSplineSegment* Segment : Component->Segments)
{
FSegmentProxy SegmentProxy;
SegmentProxy.Owner = Segment;
SegmentProxy.HitProxy = nullptr;
SegmentProxy.Points = Segment->GetPoints();
SegmentProxy.bSelected = Segment->IsSplineSelected();
Segments.Add(SegmentProxy);
}
ControlPoints.Reserve(Component->ControlPoints.Num());
for (ULandscapeSplineControlPoint* ControlPoint : Component->ControlPoints)
{
FControlPointProxy ControlPointProxy;
ControlPointProxy.Owner = ControlPoint;
ControlPointProxy.HitProxy = nullptr;
ControlPointProxy.Location = ControlPoint->Location;
ControlPointProxy.Points = ControlPoint->GetPoints();
ControlPointProxy.SpriteScale = FMath::Clamp<float>(ControlPoint->Width != 0 ? ControlPoint->Width / 8 : ControlPoint->SideFalloff / 4, 10, 1000);
ControlPointProxy.bSelected = ControlPoint->IsSplineSelected();
ControlPoints.Add(ControlPointProxy);
}
}
virtual HHitProxy* CreateHitProxies(UPrimitiveComponent* Component, TArray<TRefCountPtr<HHitProxy> >& OutHitProxies) override
{
OutHitProxies.Reserve(OutHitProxies.Num() + Segments.Num() + ControlPoints.Num());
for (FSegmentProxy& Segment : Segments)
{
Segment.HitProxy = new HLandscapeSplineProxy_Segment(Segment.Owner);
OutHitProxies.Add(Segment.HitProxy);
}
for (FControlPointProxy& ControlPoint : ControlPoints)
{
ControlPoint.HitProxy = new HLandscapeSplineProxy_ControlPoint(ControlPoint.Owner);
OutHitProxies.Add(ControlPoint.HitProxy);
}
return nullptr;
}
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{
// Slight Depth Bias so that the splines show up when they exactly match the target surface
// e.g. someone playing with splines on a newly-created perfectly-flat landscape
static const float DepthBias = 0.0001;
const FMatrix& MyLocalToWorld = GetLocalToWorld();
const FLinearColor SelectedSplineColor = GEngine->GetSelectedMaterialColor();
const FLinearColor SelectedControlPointSpriteColor = FLinearColor::White + (GEngine->GetSelectedMaterialColor() * GEngine->SelectionHighlightIntensityBillboards * 20);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{
const FSceneView* const View = Views[ViewIndex];
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
const uint8 DepthPriority = static_cast<uint8>(GetDepthPriorityGroup(View));
for (const FSegmentProxy& Segment : Segments)
{
const FLinearColor SegmentColor = Segment.bSelected ? SelectedSplineColor : SplineColor;
if (Segment.Points.Num() == 0 || !Segment.Points.IsValidIndex(0)) // for some reason the segment do not have valid points, prevent possible crash, by simply not rendering this segment
continue;
FLandscapeSplineInterpPoint OldPoint = Segment.Points[0];
OldPoint.Center = MyLocalToWorld.TransformPosition(OldPoint.Center);
OldPoint.Left = MyLocalToWorld.TransformPosition(OldPoint.Left);
OldPoint.Right = MyLocalToWorld.TransformPosition(OldPoint.Right);
OldPoint.FalloffLeft = MyLocalToWorld.TransformPosition(OldPoint.FalloffLeft);
OldPoint.FalloffRight = MyLocalToWorld.TransformPosition(OldPoint.FalloffRight);
for (int32 i = 1; i < Segment.Points.Num(); i++)
{
FLandscapeSplineInterpPoint NewPoint = Segment.Points[i];
NewPoint.Center = MyLocalToWorld.TransformPosition(NewPoint.Center);
NewPoint.Left = MyLocalToWorld.TransformPosition(NewPoint.Left);
NewPoint.Right = MyLocalToWorld.TransformPosition(NewPoint.Right);
NewPoint.FalloffLeft = MyLocalToWorld.TransformPosition(NewPoint.FalloffLeft);
NewPoint.FalloffRight = MyLocalToWorld.TransformPosition(NewPoint.FalloffRight);
// Draw lines from the last keypoint.
PDI->SetHitProxy(Segment.HitProxy);
// center line
PDI->DrawLine(OldPoint.Center, NewPoint.Center, SegmentColor, DepthPriority, 0.0f, DepthBias);
// draw sides
PDI->DrawLine(OldPoint.Left, NewPoint.Left, SegmentColor, DepthPriority, 0.0f, DepthBias);
PDI->DrawLine(OldPoint.Right, NewPoint.Right, SegmentColor, DepthPriority, 0.0f, DepthBias);
PDI->SetHitProxy(nullptr);
// draw falloff sides
if (bDrawFalloff)
{
DrawDashedLine(PDI, OldPoint.FalloffLeft, NewPoint.FalloffLeft, SegmentColor, 100, DepthPriority, DepthBias);
DrawDashedLine(PDI, OldPoint.FalloffRight, NewPoint.FalloffRight, SegmentColor, 100, DepthPriority, DepthBias);
}
OldPoint = NewPoint;
}
}
for (const FControlPointProxy& ControlPoint : ControlPoints)
{
const FVector ControlPointLocation = MyLocalToWorld.TransformPosition(ControlPoint.Location);
// Draw Sprite
if (bDrawControlPointSprite)
{
// By default use values from Landscape Settings for scale and z-offset
const ULandscapeSettings* Settings = GetDefault<ULandscapeSettings>();
bool bEnableFixedSpriteIcons = CVarLandscapeSplineEnableFixedSpriteIcons->GetBool();
float ControlPointSpriteZOffset = Settings->GetSplineIconWorldZOffset();
float ControlPointSpriteScale = bEnableFixedSpriteIcons ? ControlPoint.SpriteScale : Settings->GetSplineIconScale();
FVector ControlPointSpriteLocation = ControlPointLocation + FVector(0, 0, ControlPointSpriteZOffset);
if (bEnableFixedSpriteIcons)
{
ControlPointSpriteScale *= MyLocalToWorld.GetScaleVector().X;
const FMatrix& ProjectionMatrix = View->ViewMatrices.GetProjectionMatrix();
const double ZoomFactor = FMath::Min<double>(ProjectionMatrix.M[0][0], ProjectionMatrix.M[1][1]);
const double Scale = View->WorldToScreen(ControlPointLocation).W * (4.0f / View->UnscaledViewRect.Width() / ZoomFactor);
ControlPointSpriteScale *= Scale;
}
const FLinearColor ControlPointSpriteColor = ControlPoint.bSelected ? SelectedControlPointSpriteColor : FLinearColor::White;
PDI->SetHitProxy(ControlPoint.HitProxy);
#if WITH_EDITORONLY_DATA
ControlPointSpriteScale *= UBillboardComponent::EditorScale;
#endif
if (bEnableFixedSpriteIcons)
{
// Clamping the scale between 10 and the ControlPoint initial scale
ControlPointSpriteScale = FMath::Clamp<double>(ControlPointSpriteScale, 10, ControlPoint.SpriteScale);
ControlPointSpriteLocation = ControlPointLocation + FVector(0, 0, ControlPointSpriteScale * 0.75f);
}
PDI->DrawSprite(
ControlPointSpriteLocation,
static_cast<float>(ControlPointSpriteScale),
static_cast<float>(ControlPointSpriteScale),
ControlPointSprite->GetResource(),
ControlPointSpriteColor,
DepthPriority,
0, static_cast<float>(ControlPointSprite->GetResource()->GetSizeX()),
0, static_cast<float>(ControlPointSprite->GetResource()->GetSizeY()),
SE_BLEND_Masked);
}
// Draw Lines
const FLinearColor ControlPointColor = ControlPoint.bSelected ? SelectedSplineColor : SplineColor;
if (ControlPoint.Points.Num() == 1)
{
FLandscapeSplineInterpPoint NewPoint = ControlPoint.Points[0];
NewPoint.Center = MyLocalToWorld.TransformPosition(NewPoint.Center);
NewPoint.Left = MyLocalToWorld.TransformPosition(NewPoint.Left);
NewPoint.Right = MyLocalToWorld.TransformPosition(NewPoint.Right);
NewPoint.FalloffLeft = MyLocalToWorld.TransformPosition(NewPoint.FalloffLeft);
NewPoint.FalloffRight = MyLocalToWorld.TransformPosition(NewPoint.FalloffRight);
// draw end for spline connection
PDI->DrawPoint(NewPoint.Center, ControlPointColor, 6.0f, DepthPriority);
PDI->DrawLine(NewPoint.Left, NewPoint.Center, ControlPointColor, DepthPriority, 0.0f, DepthBias);
PDI->DrawLine(NewPoint.Right, NewPoint.Center, ControlPointColor, DepthPriority, 0.0f, DepthBias);
if (bDrawFalloff)
{
DrawDashedLine(PDI, NewPoint.FalloffLeft, NewPoint.Left, ControlPointColor, 100, DepthPriority, DepthBias);
DrawDashedLine(PDI, NewPoint.FalloffRight, NewPoint.Right, ControlPointColor, 100, DepthPriority, DepthBias);
}
}
else if (ControlPoint.Points.Num() >= 2)
{
FLandscapeSplineInterpPoint OldPoint = ControlPoint.Points.Last();
//OldPoint.Left = MyLocalToWorld.TransformPosition(OldPoint.Left);
OldPoint.Right = MyLocalToWorld.TransformPosition(OldPoint.Right);
//OldPoint.FalloffLeft = MyLocalToWorld.TransformPosition(OldPoint.FalloffLeft);
OldPoint.FalloffRight = MyLocalToWorld.TransformPosition(OldPoint.FalloffRight);
for (const FLandscapeSplineInterpPoint& Point : ControlPoint.Points)
{
FLandscapeSplineInterpPoint NewPoint = Point;
NewPoint.Center = MyLocalToWorld.TransformPosition(NewPoint.Center);
NewPoint.Left = MyLocalToWorld.TransformPosition(NewPoint.Left);
NewPoint.Right = MyLocalToWorld.TransformPosition(NewPoint.Right);
NewPoint.FalloffLeft = MyLocalToWorld.TransformPosition(NewPoint.FalloffLeft);
NewPoint.FalloffRight = MyLocalToWorld.TransformPosition(NewPoint.FalloffRight);
PDI->SetHitProxy(ControlPoint.HitProxy);
// center line
PDI->DrawLine(ControlPointLocation, NewPoint.Center, ControlPointColor, DepthPriority, 0.0f, DepthBias);
// draw sides
PDI->DrawLine(OldPoint.Right, NewPoint.Left, ControlPointColor, DepthPriority, 0.0f, DepthBias);
PDI->SetHitProxy(nullptr);
// draw falloff sides
if (bDrawFalloff)
{
DrawDashedLine(PDI, OldPoint.FalloffRight, NewPoint.FalloffLeft, ControlPointColor, 100, DepthPriority, DepthBias);
}
// draw end for spline connection
PDI->DrawPoint(NewPoint.Center, ControlPointColor, 6.0f, DepthPriority);
PDI->DrawLine(NewPoint.Left, NewPoint.Center, ControlPointColor, DepthPriority, 0.0f, DepthBias);
PDI->DrawLine(NewPoint.Right, NewPoint.Center, ControlPointColor, DepthPriority, 0.0f, DepthBias);
if (bDrawFalloff)
{
DrawDashedLine(PDI, NewPoint.FalloffLeft, NewPoint.Left, ControlPointColor, 100, DepthPriority, DepthBias);
DrawDashedLine(PDI, NewPoint.FalloffRight, NewPoint.Right, ControlPointColor, 100, DepthPriority, DepthBias);
}
//OldPoint = NewPoint;
OldPoint.Right = NewPoint.Right;
OldPoint.FalloffRight = NewPoint.FalloffRight;
}
}
}
PDI->SetHitProxy(nullptr);
}
}
}
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View) && View->Family->EngineShowFlags.Splines;
Result.bDynamicRelevance = true;
return Result;
}
virtual uint32 GetMemoryFootprint() const override
{
return sizeof(*this) + GetAllocatedSize();
}
uint32 GetAllocatedSize() const
{
SIZE_T AllocatedSize = FPrimitiveSceneProxy::GetAllocatedSize() + Segments.GetAllocatedSize() + ControlPoints.GetAllocatedSize();
for (const FSegmentProxy& Segment : Segments)
{
AllocatedSize += Segment.Points.GetAllocatedSize();
}
for (const FControlPointProxy& ControlPoint : ControlPoints)
{
AllocatedSize += ControlPoint.Points.GetAllocatedSize();
}
return IntCastChecked<uint32>(AllocatedSize);
}
};
#endif
//////////////////////////////////////////////////////////////////////////
// LANDSCAPE SPLINE INTERFACE
ULandscapeSplineInterface::ULandscapeSplineInterface(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
//////////////////////////////////////////////////////////////////////////
// SPLINE COMPONENT
ULandscapeSplinesComponent::ULandscapeSplinesComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Mobility = EComponentMobility::Static;
#if WITH_EDITORONLY_DATA
SplineResolution = 512;
SplineColor = FColor(0, 192, 48);
if (!IsRunningCommandlet())
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UTexture2D> SpriteTexture;
FConstructorStatics()
: SpriteTexture(TEXT("/Engine/EditorResources/S_Terrain.S_Terrain"))
{
}
};
static FConstructorStatics ConstructorStatics;
ControlPointSprite = ConstructorStatics.SpriteTexture.Object;
}
// Another one-time initialization (non-conditional to whether we're in the editor, this time): unlike the sprite, which is purely cosmetic, SplineEditorMesh is needed
// at all times because we often check if a spline segment/control point is using the SplineEditorMesh (on PostLoad, for example) :
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UStaticMesh> SplineEditorMesh;
FConstructorStatics()
: SplineEditorMesh(TEXT("/Engine/EditorLandscapeResources/SplineEditorMesh"))
{
}
};
static FConstructorStatics ConstructorStatics;
SplineEditorMesh = ConstructorStatics.SplineEditorMesh.Object;
}
#endif
//RelativeScale3D = FVector(1/100.0f, 1/100.0f, 1/100.0f); // cancel out landscape scale. The scale is set up when component is created, but for a default landscape it's this
}
TArray<USplineMeshComponent*> ULandscapeSplinesComponent::GetSplineMeshComponents()
{
TArray<USplineMeshComponent*> SplineMeshComponents;
#if WITH_EDITOR
for (ULandscapeSplineSegment* SplineSegment : Segments)
{
// local
SplineMeshComponents.Append(SplineSegment->GetLocalMeshComponents());
// foreign
TMap<ULandscapeSplinesComponent*, TArray<USplineMeshComponent*>> ForeignMeshComponentsMap = SplineSegment->GetForeignMeshComponents();
for (const auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
for (USplineMeshComponent* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
SplineMeshComponents.Add(ForeignMeshComponent);
}
}
}
#endif
return SplineMeshComponents;
}
ILandscapeSplineInterface* ULandscapeSplinesComponent::GetSplineOwner()
{
return Cast<ILandscapeSplineInterface>(GetOwner());
}
void ULandscapeSplinesComponent::CheckSplinesValid()
{
#if DO_CHECK
// Remove all null control points/segments
// This shouldn't happen, but it has somehow (TTP #334549) so we have to fix it
ensure(!ControlPoints.Remove(nullptr));
ensure(!Segments.Remove(nullptr));
// Check for cross-spline connections, as this is a potential source of nulls
// this may be allowed in future, but is not currently
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
ensure(ControlPoint->GetOuterULandscapeSplinesComponent() == this);
for (const FLandscapeSplineConnection& Connection : ControlPoint->ConnectedSegments)
{
if (Connection.Segment)
{
ensure(Connection.Segment->GetOuterULandscapeSplinesComponent() == this);
}
}
}
for (ULandscapeSplineSegment* Segment : Segments)
{
ensure(Segment->GetOuterULandscapeSplinesComponent() == this);
for (const FLandscapeSplineSegmentConnection& Connection : Segment->Connections)
{
if (Connection.ControlPoint)
{
ensure(Connection.ControlPoint->GetOuterULandscapeSplinesComponent() == this);
}
}
}
#endif
}
void ULandscapeSplinesComponent::OnRegister()
{
CheckSplinesValid();
Super::OnRegister();
}
#if WITH_EDITOR
FPrimitiveSceneProxy* ULandscapeSplinesComponent::CreateSceneProxy()
{
CheckSplinesValid();
return new FLandscapeSplinesSceneProxy(this);
}
#endif
FBoxSphereBounds ULandscapeSplinesComponent::CalcBounds(const FTransform& LocalToWorld) const
{
FBox NewBoundsCalc(ForceInit);
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
// TTP #334549: Somehow we're getting nulls in the ControlPoints array
if (ControlPoint)
{
NewBoundsCalc += ControlPoint->GetBounds();
}
}
for (ULandscapeSplineSegment* Segment : Segments)
{
if (Segment)
{
NewBoundsCalc += Segment->GetBounds();
}
}
FBoxSphereBounds NewBounds;
if (NewBoundsCalc.IsValid)
{
NewBoundsCalc = NewBoundsCalc.TransformBy(LocalToWorld);
NewBounds = FBoxSphereBounds(NewBoundsCalc);
}
else
{
// There's no such thing as an "invalid" FBoxSphereBounds (unlike FBox)
// try to return something that won't modify the parent bounds
if (GetAttachParent())
{
NewBounds = FBoxSphereBounds(GetAttachParent()->Bounds.Origin, FVector::ZeroVector, 0.0f);
}
else
{
NewBounds = FBoxSphereBounds(LocalToWorld.GetTranslation(), FVector::ZeroVector, 0.0f);
}
}
return NewBounds;
}
bool ULandscapeSplinesComponent::ModifySplines(bool bAlwaysMarkDirty /*= true*/)
{
bool bSavedToTransactionBuffer = Modify(bAlwaysMarkDirty);
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
bSavedToTransactionBuffer = ControlPoint->Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
}
for (ULandscapeSplineSegment* Segment : Segments)
{
bSavedToTransactionBuffer = Segment->Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
}
return bSavedToTransactionBuffer;
}
#if WITH_EDITORONLY_DATA
// legacy ForeignWorldSplineDataMap serialization
FArchive& operator<<(FArchive& Ar, FForeignSplineSegmentData& Value)
{
if (!Ar.IsFilterEditorOnly())
{
Ar << Value.ModificationKey << Value.MeshComponents;
}
return Ar;
}
FArchive& operator<<(FArchive& Ar, FForeignWorldSplineData& Value)
{
if (!Ar.IsFilterEditorOnly())
{
// note: ForeignControlPointDataMap is missing in legacy serialization
Ar << Value.ForeignSplineSegmentDataMap_DEPRECATED;
}
return Ar;
}
#endif
void ULandscapeSplinesComponent::Serialize(FArchive& Ar)
{
#if WITH_EDITORONLY_DATA
// Cooking is a save-time operation, so has to be done before Super::Serialize
if (Ar.IsCooking())
{
CookedForeignMeshComponents.Reset();
for (const auto& ForeignWorldSplineDataPair : ForeignWorldSplineDataMap)
{
const auto& ForeignWorldSplineData = ForeignWorldSplineDataPair.Value;
for (const auto& ForeignControlPointData : ForeignWorldSplineData.ForeignControlPointData)
{
CookedForeignMeshComponents.Add(ForeignControlPointData.MeshComponent);
}
for (const auto& ForeignSplineSegmentData : ForeignWorldSplineData.ForeignSplineSegmentData)
{
CookedForeignMeshComponents.Append(ForeignSplineSegmentData.MeshComponents);
}
}
}
#endif
Super::Serialize(Ar);
#if WITH_EDITORONLY_DATA
if (Ar.UEVer() >= VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES &&
!Ar.IsFilterEditorOnly())
{
Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID);
if (Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::NewSplineCrossLevelMeshSerialization)
{
Ar << ForeignWorldSplineDataMap;
}
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::SplineForeignDataLazyObjectPtrFix)
{
for (auto& SplineData : ForeignWorldSplineDataMap)
{
for (auto& ControlPoint : SplineData.Value.ForeignControlPointDataMap_DEPRECATED)
{
ControlPoint.Value.Identifier = ControlPoint.Key;
SplineData.Value.ForeignControlPointData.Add(ControlPoint.Value);
}
SplineData.Value.ForeignControlPointDataMap_DEPRECATED.Empty();
for (auto& SegmentData : SplineData.Value.ForeignSplineSegmentDataMap_DEPRECATED)
{
SegmentData.Value.Identifier = SegmentData.Key;
SplineData.Value.ForeignSplineSegmentData.Add(SegmentData.Value);
}
SplineData.Value.ForeignSplineSegmentDataMap_DEPRECATED.Empty();
}
}
}
if (!Ar.IsPersistent())
{
Ar << MeshComponentLocalOwnersMap;
Ar << MeshComponentForeignOwnersMap;
}
#endif
}
#if WITH_EDITOR
void ULandscapeSplinesComponent::AutoFixMeshComponentErrors(UWorld* OtherWorld)
{
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
TSoftObjectPtr<UWorld> OtherWorldSoftPtr = OtherWorld;
ULandscapeSplinesComponent* StreamingSplinesComponent = GetStreamingSplinesComponentForLevel(OtherWorld->PersistentLevel);
auto* ForeignWorldSplineData = StreamingSplinesComponent ? StreamingSplinesComponent->ForeignWorldSplineDataMap.Find(ThisOuterWorld) : nullptr;
// Fix control point meshes
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
if (ControlPoint->GetForeignWorld() == OtherWorld)
{
auto* ForeignControlPointData = ForeignWorldSplineData ? ForeignWorldSplineData->FindControlPoint(ControlPoint) : nullptr;
if (!ForeignControlPointData || ForeignControlPointData->ModificationKey != ControlPoint->GetModificationKey())
{
// We don't pass true for update segments to avoid them being updated multiple times
ControlPoint->UpdateSplinePoints(true, false);
}
}
}
// Fix spline segment meshes
for (ULandscapeSplineSegment* Segment : Segments)
{
if (Segment->GetForeignWorlds().Contains(OtherWorld))
{
auto* ForeignSplineSegmentData = ForeignWorldSplineData ? ForeignWorldSplineData->FindSegmentData(Segment) : nullptr;
if (!ForeignSplineSegmentData || ForeignSplineSegmentData->ModificationKey != Segment->GetModificationKey())
{
Segment->UpdateSplinePoints(true);
}
}
}
if (StreamingSplinesComponent)
{
StreamingSplinesComponent->DestroyOrphanedForeignSplineMeshComponents(ThisOuterWorld);
StreamingSplinesComponent->DestroyOrphanedForeignControlPointMeshComponents(ThisOuterWorld);
}
}
bool ULandscapeSplinesComponent::IsUsingEditorMesh(const USplineMeshComponent* SplineMeshComponent) const
{
return SplineMeshComponent->GetStaticMesh() == SplineEditorMesh && SplineMeshComponent->bHiddenInGame;
}
void ULandscapeSplinesComponent::ForEachControlPoint(TFunctionRef<void(ULandscapeSplineControlPoint*)> Func)
{
// Copy in case iteration modifies the list
TArray<ULandscapeSplineControlPoint*> CopyControlPoints(ControlPoints);
for (ULandscapeSplineControlPoint* ControlPoint : CopyControlPoints)
{
Func(ControlPoint);
}
}
void ULandscapeSplinesComponent::CopyToSplineComponent(USplineComponent* SplineComponent)
{
if (SplineComponent == nullptr)
{
return;
}
if (Segments.IsEmpty())
{
return;
}
SplineComponent->ClearSplinePoints(false);
// Incremented index for spline component points
int32 CurrentPointIndex = 0;
// Traverse up to this many segments
// For safety. Ensures we don't get caught in an infinite loop.
const int32 MAX_ITERATIONS = LandscapeSplineToSplineComponentMaxIterations;
int32 NumIterations = 0;
// List of segments we've visited. Used to ensure we don't visit the same segment twice.
TSet<ULandscapeSplineSegment*> VisitedSegments;
// The last control point we used
// 2 control points per segment
// 2 segments can share the same control point - one being the arrive control point and one being the exit
ULandscapeSplineControlPoint* LastControlPoint = nullptr;
// Arrive Tangent to apply to the next spline point. Populated as the previous spline mesh's End Tangent
FVector ArriveTangent(0,0,0);
ULandscapeSplineSegment* FirstSegment = Segments[0];
ULandscapeSplineSegment* CurrentSegment = FirstSegment;
do
{
if (!ensure(NumIterations < MAX_ITERATIONS))
{
UE_LOG(LogLandscape, Error, TEXT("%s Reached Max Iterations. Exiting."), ANSI_TO_TCHAR(__FUNCTION__));
break;
}
if (!ensure(CurrentSegment != nullptr))
{
UE_LOG(LogLandscape, Error, TEXT("%s Current Segment is nullptr. Exiting."), ANSI_TO_TCHAR(__FUNCTION__));
break;
}
if (!ensure(!VisitedSegments.Contains(CurrentSegment)))
{
UE_LOG(LogLandscape, Error, TEXT("%s Current Segment was already visited. Exiting."), ANSI_TO_TCHAR(__FUNCTION__));
break;
}
VisitedSegments.Add(CurrentSegment);
// No guarantee if the control point we're looking at is the segment's start or end control point
// If it's the end control point, we must reverse the direction we traverse over this segment
// Connections[0] == Start; Connections[1] == End;
const bool bReverseTraversal = CurrentSegment->Connections[1].ControlPoint == LastControlPoint;
const FLandscapeSplineSegmentConnection& CurrentConnection = bReverseTraversal ? CurrentSegment->Connections[1] : CurrentSegment->Connections[0];
const FLandscapeSplineSegmentConnection& NextConnection = bReverseTraversal ? CurrentSegment->Connections[0] : CurrentSegment->Connections[1];
TArray<USplineMeshComponent*> SegmentSplineMeshComponents = CurrentSegment->GetLocalMeshComponents();
// No guarantee on the direction of individual spline mesh component directions
// Test the first one and determine if the start or end location is closer
bool bReverseSplineMeshes = false;
const FVector ControlPointWorldLocation = GetComponentTransform().TransformPosition(CurrentConnection.ControlPoint->Location);
if (const USplineMeshComponent* FirstSplineMesh = SegmentSplineMeshComponents[bReverseTraversal ? SegmentSplineMeshComponents.Num() - 1 : 0])
{
const float StartDistanceSqr = FVector::DistSquared(FirstSplineMesh->GetStartPosition() + FirstSplineMesh->GetComponentLocation(), ControlPointWorldLocation);
const float EndDistanceSqr = FVector::DistSquared(FirstSplineMesh->GetEndPosition() + FirstSplineMesh->GetComponentLocation(), ControlPointWorldLocation);
bReverseSplineMeshes = EndDistanceSqr < StartDistanceSqr;
}
// Walk down the spline meshes
for (int32 Index = 0; Index < SegmentSplineMeshComponents.Num(); ++Index)
{
const int32 ActualIndex = bReverseTraversal ? SegmentSplineMeshComponents.Num() - (1 + Index) : Index;
USplineMeshComponent* SplineMesh = SegmentSplineMeshComponents[ActualIndex];
if (SplineMesh == nullptr)
{
continue;
}
FVector Position = bReverseSplineMeshes ? SplineMesh->GetEndPosition() : SplineMesh->GetStartPosition();
Position += SplineMesh->GetComponentLocation();
FVector LeaveTangent = bReverseSplineMeshes ? -SplineMesh->GetEndTangent() : SplineMesh->GetStartTangent();
// Create a spline component point from each spline mesh in the segment
SplineComponent->AddSplinePoint(Position, ESplineCoordinateSpace::World, false);
SplineComponent->SetTangentsAtSplinePoint(CurrentPointIndex, ArriveTangent, LeaveTangent, ESplineCoordinateSpace::World, false);
ArriveTangent = bReverseSplineMeshes ? SplineMesh->GetStartPosition() : SplineMesh->GetEndTangent();
++CurrentPointIndex;
}
// Search for the next segment
bool bFoundNextSegment = false;
bool bLoopingSpline = false;
if (NextConnection.ControlPoint)
{
for (FLandscapeSplineConnection& ControlPointConnection : NextConnection.ControlPoint->ConnectedSegments)
{
if (ControlPointConnection.Segment == nullptr || VisitedSegments.Contains(ControlPointConnection.Segment))
{
// Check for loop
if (ControlPointConnection.Segment == FirstSegment)
{
bLoopingSpline = true;
}
continue;
}
CurrentSegment = ControlPointConnection.Segment;
LastControlPoint = NextConnection.ControlPoint;
bFoundNextSegment = true;
break;
}
}
if (!bFoundNextSegment)
{
SplineComponent->SetClosedLoop(bLoopingSpline);
if (bLoopingSpline)
{
// Populate first point's arrive tangent
if (SplineComponent->GetNumberOfSplinePoints() > 0)
{
const FVector LeaveTangent = SplineComponent->GetLeaveTangentAtSplinePoint(0, ESplineCoordinateSpace::Local);
SplineComponent->SetTangentsAtSplinePoint(0, ArriveTangent, LeaveTangent, ESplineCoordinateSpace::World, false);
}
}
UE_LOG(LogLandscape, Verbose, TEXT("%s Could not find next segment or all segments have been traversed."), ANSI_TO_TCHAR(__FUNCTION__));
break;
}
++NumIterations;
}
while (NumIterations < MAX_ITERATIONS);
// Finish by updating the spline with all of our work
SplineComponent->UpdateSpline();
}
bool ULandscapeSplinesComponent::IsUsingLayerInfo(const ULandscapeLayerInfoObject* LayerInfo) const
{
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
if (ControlPoint->LayerName == LayerInfo->LayerName)
{
return true;
}
}
for (ULandscapeSplineSegment* Segment : Segments)
{
if (Segment->LayerName == LayerInfo->LayerName)
{
return true;
}
}
return false;
}
void ULandscapeSplinesComponent::CheckForErrors()
{
Super::CheckForErrors();
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
check(IsRunningCommandlet() || ThisOuterWorld->WorldType == EWorldType::Editor);
TSet<UWorld*> OutdatedWorlds;
TMap<UWorld*, FForeignWorldSplineData*> ForeignWorldSplineDataMapCache;
// Check control point meshes
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
UWorld* ForeignWorld = ControlPoint->GetForeignWorld().Get();
if (ForeignWorld && !OutdatedWorlds.Contains(ForeignWorld))
{
auto** ForeignWorldSplineDataCachedPtr = ForeignWorldSplineDataMapCache.Find(ForeignWorld);
auto* ForeignWorldSplineData = ForeignWorldSplineDataCachedPtr ? *ForeignWorldSplineDataCachedPtr : nullptr;
if (!ForeignWorldSplineDataCachedPtr)
{
ULandscapeSplinesComponent* StreamingSplinesComponent = GetStreamingSplinesComponentForLevel(ForeignWorld->PersistentLevel);
ForeignWorldSplineData = StreamingSplinesComponent ? StreamingSplinesComponent->ForeignWorldSplineDataMap.Find(ThisOuterWorld) : nullptr;
ForeignWorldSplineDataMapCache.Add(ForeignWorld, ForeignWorldSplineData);
}
auto* ForeignControlPointData = ForeignWorldSplineData ? ForeignWorldSplineData->FindControlPoint(ControlPoint) : nullptr;
if (!ForeignControlPointData || ForeignControlPointData->ModificationKey != ControlPoint->GetModificationKey())
{
OutdatedWorlds.Add(ForeignWorld);
}
}
}
// Check spline segment meshes
for (ULandscapeSplineSegment* Segment : Segments)
{
for (auto& ForeignWorldSoftPtr : Segment->GetForeignWorlds())
{
UWorld* ForeignWorld = ForeignWorldSoftPtr.Get();
if (ForeignWorld && !OutdatedWorlds.Contains(ForeignWorld))
{
auto** ForeignWorldSplineDataCachedPtr = ForeignWorldSplineDataMapCache.Find(ForeignWorld);
auto* ForeignWorldSplineData = ForeignWorldSplineDataCachedPtr ? *ForeignWorldSplineDataCachedPtr : nullptr;
if (!ForeignWorldSplineDataCachedPtr)
{
ULandscapeSplinesComponent* StreamingSplinesComponent = GetStreamingSplinesComponentForLevel(ForeignWorld->PersistentLevel);
ForeignWorldSplineData = StreamingSplinesComponent ? StreamingSplinesComponent->ForeignWorldSplineDataMap.Find(ThisOuterWorld) : nullptr;
ForeignWorldSplineDataMapCache.Add(ForeignWorld, ForeignWorldSplineData);
}
auto* ForeignSplineSegmentData = ForeignWorldSplineData ? ForeignWorldSplineData->FindSegmentData(Segment) : nullptr;
if (!ForeignSplineSegmentData || ForeignSplineSegmentData->ModificationKey != Segment->GetModificationKey())
{
OutdatedWorlds.Add(ForeignWorld);
}
}
}
}
ForeignWorldSplineDataMapCache.Empty();
for (UWorld* OutdatedWorld : OutdatedWorlds)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("MeshMap"), FText::FromName(OutdatedWorld->GetFName()));
Arguments.Add(TEXT("SplineMap"), FText::FromName(ThisOuterWorld->GetFName()));
FMessageLog("MapCheck").Error()
->AddToken(FUObjectToken::Create(GetOwner()))
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_MeshesOutDated", "Meshes in {MeshMap} out of date compared to landscape spline in {SplineMap}"), Arguments)))
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_ActionName_MeshesOutDated", "Rebuild landscape splines"), FText(),
FOnActionTokenExecuted::CreateUObject(this, &ULandscapeSplinesComponent::AutoFixMeshComponentErrors, OutdatedWorld), true));
}
// check for orphaned components
for (auto& ForeignWorldSplineDataPair : ForeignWorldSplineDataMap)
{
auto& ForeignWorldSoftPtr = ForeignWorldSplineDataPair.Key;
auto& ForeignWorldSplineData = ForeignWorldSplineDataPair.Value;
// World is not loaded
if (ForeignWorldSoftPtr.IsPending())
{
continue;
}
UWorld* ForeignWorld = ForeignWorldSoftPtr.Get();
for (auto& ForeignSplineSegmentData : ForeignWorldSplineData.ForeignSplineSegmentData)
{
const ULandscapeSplineSegment* ForeignSplineSegment = ForeignSplineSegmentData.Identifier.Get();
// No such segment or segment doesn't match our meshes
if (!ForeignSplineSegment)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("MeshMap"), FText::FromName(ThisOuterWorld->GetFName()));
Arguments.Add(TEXT("SplineMap"), FText::FromName(ForeignWorld->GetFName()));
FMessageLog("MapCheck").Error()
->AddToken(FUObjectToken::Create(GetOwner()))
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_OrphanedSplineMeshes", "{MeshMap} contains orphaned spline meshes due to mismatch with landscape splines in {SplineMap}"), Arguments)))
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_ActionName_OrphanedSplineMeshes", "Clean up orphaned spline meshes"), FText(),
FOnActionTokenExecuted::CreateUObject(this, &ULandscapeSplinesComponent::DestroyOrphanedForeignSplineMeshComponents, ForeignWorld), true));
break;
}
}
for (auto& ForeignControlPointData : ForeignWorldSplineData.ForeignControlPointData)
{
const ULandscapeSplineControlPoint* ForeignControlPoint = ForeignControlPointData.Identifier.Get();
// No such control point
if (!ForeignControlPoint)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("MeshMap"), FText::FromName(ThisOuterWorld->GetFName()));
Arguments.Add(TEXT("SplineMap"), FText::FromName(ForeignWorld->GetFName()));
FMessageLog("MapCheck").Error()
->AddToken(FUObjectToken::Create(GetOwner()))
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_OrphanedControlPointMeshes", "{MeshMap} contains orphaned control point meshes due to mismatch with landscape control points in {SplineMap}"), Arguments)))
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_ActionName_OrphanedControlPointMeshes", "Clean up orphaned control point meshes"), FText(),
FOnActionTokenExecuted::CreateUObject(this, &ULandscapeSplinesComponent::DestroyOrphanedForeignControlPointMeshComponents, ForeignWorld), true));
break;
}
}
}
// Find Unreferenced Foreign Mesh Components
ForEachUnreferencedForeignMeshComponent([this, ThisOuterWorld](ULandscapeSplineSegment*, USplineMeshComponent*, ULandscapeSplineControlPoint*, UControlPointMeshComponent*)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("MeshMap"), FText::FromName(ThisOuterWorld->GetFName()));
FMessageLog("MapCheck").Error()
->AddToken(FUObjectToken::Create(GetOwner()))
->AddToken(FTextToken::Create(FText::Format(LOCTEXT("MapCheck_Message_UnreferencedMeshes", "{MeshMap} contains meshes with no owners"), Arguments)))
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_ActionName_UnreferencedMeshes", "Clean up meshes that are not referenced by their owner segments/control points"), FText(),
FOnActionTokenExecuted::CreateUObject(this, &ULandscapeSplinesComponent::DestroyUnreferencedForeignMeshComponents), true));
return true;
});
}
#endif
void ULandscapeSplinesComponent::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
if (GIsEditor && GetWorld() && GetWorld()->WorldType == EWorldType::Editor)
{
// Build MeshComponentForeignOwnersMap (Component->Spline) from ForeignWorldSplineDataMap (World->Spline->Component)
for (auto& ForeignWorldSplineDataPair : ForeignWorldSplineDataMap)
{
auto& ForeignWorld = ForeignWorldSplineDataPair.Key;
auto& ForeignWorldSplineData = ForeignWorldSplineDataPair.Value;
for (auto& ForeignControlPointData : ForeignWorldSplineData.ForeignControlPointData)
{
MeshComponentForeignOwnersMap.Add(ForeignControlPointData.MeshComponent, ForeignControlPointData.Identifier);
}
for (auto& ForeignSplineSegmentData : ForeignWorldSplineData.ForeignSplineSegmentData)
{
for (auto& MeshComponent : ForeignSplineSegmentData.MeshComponents)
{
MeshComponentForeignOwnersMap.Add(MeshComponent, ForeignSplineSegmentData.Identifier);
}
}
}
}
#endif
CheckSplinesValid();
}
#if WITH_EDITOR
static bool bHackIsUndoingSplines = false;
void ULandscapeSplinesComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
// Don't update splines when undoing, not only is it unnecessary and expensive,
// it also causes failed asserts in debug builds when trying to register components
// (because the actor hasn't reset its OwnedComponents array yet)
if (!bHackIsUndoingSplines)
{
const bool bUpdateCollision = PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive;
RebuildAllSplines(bUpdateCollision);
}
}
void ULandscapeSplinesComponent::PostEditUndo()
{
bHackIsUndoingSplines = true;
Super::PostEditUndo();
bHackIsUndoingSplines = false;
MarkRenderStateDirty();
}
void ULandscapeSplinesComponent::RebuildAllSplines(bool bUpdateCollision)
{
for (ULandscapeSplineControlPoint* ControlPoint : ControlPoints)
{
ControlPoint->UpdateSplinePoints(true, false);
}
for (ULandscapeSplineSegment* Segment : Segments)
{
Segment->UpdateSplinePoints(true);
}
}
void ULandscapeSplinesComponent::RequestSplineLayerUpdate()
{
if (IsValidChecked(this))
{
ILandscapeSplineInterface* SplineOwner = GetSplineOwner();
SplineOwner->GetLandscapeInfo()->RequestSplineLayerUpdate();
}
}
void ULandscapeSplinesComponent::ShowSplineEditorMesh(bool bShow)
{
bShowSplineEditorMesh = bShow;
for (ULandscapeSplineSegment* Segment : Segments)
{
Segment->UpdateSplineEditorMesh();
}
MarkRenderStateDirty();
}
bool FForeignWorldSplineData::IsEmpty()
{
return ForeignControlPointData.Num() == 0 && ForeignSplineSegmentData.Num() == 0;
}
FForeignControlPointData* FForeignWorldSplineData::FindControlPoint(ULandscapeSplineControlPoint* InIdentifer)
{
for (auto& ControlPoint : ForeignControlPointData)
{
if (ControlPoint.Identifier == InIdentifer)
{
return &ControlPoint;
}
}
return nullptr;
}
FForeignSplineSegmentData* FForeignWorldSplineData::FindSegmentData(ULandscapeSplineSegment* InIdentifer)
{
for (auto& SegmentData : ForeignSplineSegmentData)
{
if (SegmentData.Identifier == InIdentifer)
{
return &SegmentData;
}
}
return nullptr;
}
ULandscapeSplinesComponent* ULandscapeSplinesComponent::GetStreamingSplinesComponentByLocation(const FVector& LocalLocation, bool bCreate /* = true*/)
{
ALandscapeProxy* OuterLandscape = Cast<ALandscapeProxy>(GetOwner());
if (OuterLandscape &&
// when copy/pasting this can get called with a null guid on the parent landscape
// this is fine, we won't have any cross-level meshes in this case anyway
OuterLandscape->GetLandscapeGuid().IsValid() &&
OuterLandscape->GetLandscapeInfo())
{
FVector LandscapeLocalLocation = GetComponentTransform().GetRelativeTransform(OuterLandscape->LandscapeActorToWorld()).TransformPosition(LocalLocation);
const int32 ComponentIndexX = (LandscapeLocalLocation.X >= 0.0f) ? FMath::FloorToInt32(LandscapeLocalLocation.X / OuterLandscape->ComponentSizeQuads) : FMath::CeilToInt32(LandscapeLocalLocation.X / OuterLandscape->ComponentSizeQuads);
const int32 ComponentIndexY = (LandscapeLocalLocation.Y >= 0.0f) ? FMath::FloorToInt32(LandscapeLocalLocation.Y / OuterLandscape->ComponentSizeQuads) : FMath::CeilToInt32(LandscapeLocalLocation.Y / OuterLandscape->ComponentSizeQuads);
ULandscapeComponent* LandscapeComponent = OuterLandscape->GetLandscapeInfo()->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
if (LandscapeComponent)
{
ALandscapeProxy* ComponentLandscapeProxy = LandscapeComponent->GetLandscapeProxy();
if (!ComponentLandscapeProxy->GetSplinesComponent() && bCreate)
{
ComponentLandscapeProxy->CreateSplineComponent(GetRelativeScale3D());
}
if (ULandscapeSplinesComponent* SplineComponent = ComponentLandscapeProxy->GetSplinesComponent())
{
return SplineComponent;
}
}
}
return this;
}
ULandscapeSplinesComponent* ULandscapeSplinesComponent::GetStreamingSplinesComponentForLevel(ULevel* Level, bool bCreate /* = true*/)
{
ALandscapeProxy* OuterLandscape = Cast<ALandscapeProxy>(GetOwner());
if (OuterLandscape)
{
ULandscapeInfo* LandscapeInfo = OuterLandscape->GetLandscapeInfo();
check(LandscapeInfo);
ALandscapeProxy* Proxy = LandscapeInfo->GetLandscapeProxyForLevel(Level);
if (Proxy)
{
if (!Proxy->GetSplinesComponent() && bCreate)
{
Proxy->CreateSplineComponent(GetRelativeScale3D());
}
return Proxy->GetSplinesComponent();
}
}
return nullptr;
}
TArray<ULandscapeSplinesComponent*> ULandscapeSplinesComponent::GetAllStreamingSplinesComponents()
{
ALandscapeProxy* OuterLandscape = Cast<ALandscapeProxy>(GetOwner());
if (OuterLandscape &&
// when copy/pasting this can get called with a null guid on the parent landscape
// this is fine, we won't have any cross-level meshes in this case anyway
OuterLandscape->GetLandscapeGuid().IsValid())
{
ULandscapeInfo* LandscapeInfo = OuterLandscape->GetLandscapeInfo();
if (LandscapeInfo)
{
TArray<ULandscapeSplinesComponent*> SplinesComponents;
LandscapeInfo->ForAllSplineActors([&SplinesComponents](TScriptInterface<ILandscapeSplineInterface> SplineOwner)
{
if (ULandscapeSplinesComponent* SplineComponent = SplineOwner->GetSplinesComponent())
{
SplinesComponents.Add(SplineComponent);
}
});
return SplinesComponents;
}
}
return {};
}
void ULandscapeSplinesComponent::UpdateModificationKey(ULandscapeSplineSegment* Owner)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
checkSlow(ForeignWorldSplineData);
if (ForeignWorldSplineData)
{
auto* ForeignSplineSegmentData = ForeignWorldSplineData->FindSegmentData(Owner);
if (ForeignSplineSegmentData != nullptr)
{
ForeignSplineSegmentData->ModificationKey = Owner->GetModificationKey();
}
}
}
void ULandscapeSplinesComponent::UpdateModificationKey(ULandscapeSplineControlPoint* Owner)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
checkSlow(ForeignWorldSplineData);
if (ForeignWorldSplineData)
{
auto* ForeignControlPointData = ForeignWorldSplineData->FindControlPoint(Owner);
if (ForeignControlPointData != nullptr)
{
ForeignControlPointData->ModificationKey = Owner->GetModificationKey();
}
}
}
void ULandscapeSplinesComponent::AddForeignMeshComponent(ULandscapeSplineSegment* Owner, USplineMeshComponent* Component)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
#if DO_GUARD_SLOW
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
checkSlow(ComponentOuterWorld == ThisOuterWorld);
checkSlow(OwnerWorld != ThisOuterWorld);
#endif
auto& ForeignWorldSplineData = ForeignWorldSplineDataMap.FindOrAdd(OwnerWorld);
FForeignSplineSegmentData* ForeignSplineSegmentData = ForeignWorldSplineData.FindSegmentData(Owner);
if (ForeignSplineSegmentData == nullptr)
{
int32 AddedIndex = ForeignWorldSplineData.ForeignSplineSegmentData.Add(FForeignSplineSegmentData());
ForeignSplineSegmentData = &ForeignWorldSplineData.ForeignSplineSegmentData[AddedIndex];
}
ForeignSplineSegmentData->MeshComponents.Add(Component);
ForeignSplineSegmentData->ModificationKey = Owner->GetModificationKey();
ForeignSplineSegmentData->Identifier = Owner;
MeshComponentForeignOwnersMap.Add(Component, Owner);
}
void ULandscapeSplinesComponent::RemoveForeignMeshComponent(ULandscapeSplineSegment* Owner, USplineMeshComponent* Component)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
#if DO_GUARD_SLOW
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
checkSlow(ComponentOuterWorld == ThisOuterWorld);
checkSlow(OwnerWorld != ThisOuterWorld);
#endif
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
checkSlow(ForeignWorldSplineData);
checkSlow(MeshComponentForeignOwnersMap.FindRef(Component) == Owner);
verifySlow(MeshComponentForeignOwnersMap.Remove(Component) == 1);
if (ForeignWorldSplineData)
{
FForeignSplineSegmentData* SegmentData = ForeignWorldSplineData->FindSegmentData(Owner);
verifySlow(SegmentData->MeshComponents.RemoveSingle(Component) == 1);
if (SegmentData->MeshComponents.Num() == 0)
{
verifySlow(ForeignWorldSplineData->ForeignSplineSegmentData.RemoveSingle(*SegmentData) == 1);
if (ForeignWorldSplineData->IsEmpty())
{
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
}
}
else
{
SegmentData->ModificationKey = Owner->GetModificationKey();
}
}
}
void ULandscapeSplinesComponent::RemoveAllForeignMeshComponents(ULandscapeSplineSegment* Owner)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
checkSlow(ForeignWorldSplineData);
if (ForeignWorldSplineData)
{
auto* ForeignSplineSegmentData = ForeignWorldSplineData->FindSegmentData(Owner);
if (ForeignSplineSegmentData)
{
for (auto& MeshComponent : ForeignSplineSegmentData->MeshComponents)
{
checkSlow(MeshComponentForeignOwnersMap.FindRef(MeshComponent) == Owner);
verifySlow(MeshComponentForeignOwnersMap.Remove(MeshComponent) == 1);
}
ForeignSplineSegmentData->MeshComponents.Empty();
verifySlow(ForeignWorldSplineData->ForeignSplineSegmentData.RemoveSingle(*ForeignSplineSegmentData) == 1);
}
if (ForeignWorldSplineData->IsEmpty())
{
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
}
}
}
void ULandscapeSplinesComponent::AddForeignMeshComponent(ULandscapeSplineControlPoint* Owner, UControlPointMeshComponent* Component)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
#if DO_GUARD_SLOW
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
checkSlow(ComponentOuterWorld == ThisOuterWorld);
checkSlow(OwnerWorld != ThisOuterWorld);
#endif
auto& ForeignWorldSplineData = ForeignWorldSplineDataMap.FindOrAdd(OwnerWorld);
checkSlow(ForeignWorldSplineData.FindControlPoint(Owner) == nullptr);
int32 AddedIndex = ForeignWorldSplineData.ForeignControlPointData.Add(FForeignControlPointData());
auto& ForeignControlPointData = ForeignWorldSplineData.ForeignControlPointData[AddedIndex];
ForeignControlPointData.MeshComponent = Component;
ForeignControlPointData.ModificationKey = Owner->GetModificationKey();
ForeignControlPointData.Identifier = Owner;
MeshComponentForeignOwnersMap.Add(Component, Owner);
}
void ULandscapeSplinesComponent::RemoveForeignMeshComponent(ULandscapeSplineControlPoint* Owner, UControlPointMeshComponent* Component)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
#if DO_GUARD_SLOW
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
checkSlow(ComponentOuterWorld == ThisOuterWorld);
checkSlow(OwnerWorld != ThisOuterWorld);
#endif
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
checkSlow(ForeignWorldSplineData);
checkSlow(MeshComponentForeignOwnersMap.FindRef(Component) == Owner);
verifySlow(MeshComponentForeignOwnersMap.Remove(Component) == 1);
if (ForeignWorldSplineData)
{
auto* ForeignControlPointData = ForeignWorldSplineData->FindControlPoint(Owner);
checkSlow(ForeignControlPointData);
checkSlow(ForeignControlPointData->MeshComponent == Component);
verifySlow(ForeignWorldSplineData->ForeignControlPointData.RemoveSingle(*ForeignControlPointData) == 1);
if (ForeignWorldSplineData->IsEmpty())
{
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
}
}
}
void ULandscapeSplinesComponent::ForEachUnreferencedForeignMeshComponent(TFunctionRef<bool(ULandscapeSplineSegment*, USplineMeshComponent*, ULandscapeSplineControlPoint*, UControlPointMeshComponent*)> Func)
{
TArray<UObject*> ObjectsWithOuter;
GetObjectsWithOuter(GetOwner(), ObjectsWithOuter);
for (UObject* Obj : ObjectsWithOuter)
{
if (USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(Obj))
{
// Find Mesh in the Spline Component local map
if (TLazyObjectPtr<UObject>* ForeignOwner = MeshComponentForeignOwnersMap.Find(SplineMeshComponent))
{
if (ULandscapeSplineSegment* OwnerForMesh = Cast<ULandscapeSplineSegment>(ForeignOwner->Get()))
{
// Try to find the mesh through the global map from the owner we found in local map
TArray<USplineMeshComponent*> SplineMeshComponents = GetForeignMeshComponents(OwnerForMesh);
if (!SplineMeshComponents.Contains(SplineMeshComponent))
{
// It wasn't found. Link is broken, cleanup is needed.
if (Func(OwnerForMesh, SplineMeshComponent, nullptr, nullptr))
{
break;
}
}
}
}
}
else if (UControlPointMeshComponent* ControlPointMeshComponent = Cast<UControlPointMeshComponent>(Obj))
{
if (TLazyObjectPtr<UObject>* ForeignOwner = MeshComponentForeignOwnersMap.Find(SplineMeshComponent))
{
if (ULandscapeSplineControlPoint* OwnerForMesh = Cast<ULandscapeSplineControlPoint>(ForeignOwner->Get()))
{
UControlPointMeshComponent* ForeignControlPointMeshComponent = GetForeignMeshComponent(OwnerForMesh);
if (ControlPointMeshComponent != ForeignControlPointMeshComponent)
{
if (Func(nullptr, nullptr, OwnerForMesh, ControlPointMeshComponent))
{
break;
}
}
}
}
}
}
}
void ULandscapeSplinesComponent::DestroyUnreferencedForeignMeshComponents()
{
ForEachUnreferencedForeignMeshComponent([this](ULandscapeSplineSegment* Segment, USplineMeshComponent* SplineMeshComponent, ULandscapeSplineControlPoint* ControlPoint, UControlPointMeshComponent* ControlPointMeshComponent)
{
TArray<UWorld*> EmptyWorlds;
if (SplineMeshComponent != nullptr)
{
SplineMeshComponent->DestroyComponent();
// We should be removing something here
verify(MeshComponentForeignOwnersMap.Remove(SplineMeshComponent));
for (auto Pair : ForeignWorldSplineDataMap)
{
for (int32 i = Pair.Value.ForeignSplineSegmentData.Num() - 1; i >= 0; --i)
{
FForeignSplineSegmentData& SegmentData = Pair.Value.ForeignSplineSegmentData[i];
if (SegmentData.Identifier == Segment)
{
int32 RemoveCount = SegmentData.MeshComponents.Remove(SplineMeshComponent);
// if remove count is not 0, then we are expecting worlds not to match.
check(RemoveCount == 0 || Segment->GetTypedOuter<UWorld>() != Pair.Key.Get());
}
if (SegmentData.MeshComponents.Num() == 0)
{
Pair.Value.ForeignSplineSegmentData.RemoveSingle(SegmentData);
}
if (Pair.Value.IsEmpty())
{
EmptyWorlds.Add(Pair.Key.Get());
}
}
}
}
if (ControlPointMeshComponent != nullptr)
{
ControlPointMeshComponent->DestroyComponent();
// We should be removing something here
verify(MeshComponentForeignOwnersMap.Remove(ControlPointMeshComponent));
for (auto Pair : ForeignWorldSplineDataMap)
{
for (int32 i = Pair.Value.ForeignControlPointData.Num() - 1; i >= 0; --i)
{
FForeignControlPointData& ControlPointData = Pair.Value.ForeignControlPointData[i];
if (ControlPointData.Identifier == ControlPoint && ControlPointData.MeshComponent == ControlPointMeshComponent)
{
check(ControlPoint->GetTypedOuter<UWorld>() != Pair.Key.Get());
Pair.Value.ForeignControlPointData.RemoveSingle(ControlPointData);
}
if (Pair.Value.IsEmpty())
{
EmptyWorlds.Add(Pair.Key.Get());
}
}
}
}
for (UWorld* EmptyWorld : EmptyWorlds)
{
ForeignWorldSplineDataMap.Remove(EmptyWorld);
}
return false;
});
}
void ULandscapeSplinesComponent::DestroyOrphanedForeignSplineMeshComponents(UWorld* OwnerWorld)
{
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
if (ForeignWorldSplineData)
{
for (int32 i = ForeignWorldSplineData->ForeignSplineSegmentData.Num() - 1; i >= 0; --i)
{
FForeignSplineSegmentData& SegmentData = ForeignWorldSplineData->ForeignSplineSegmentData[i];
const auto& ForeignSplineSegment = SegmentData.Identifier;
if (!ForeignSplineSegment)
{
for (auto& MeshComponent : SegmentData.MeshComponents)
{
if (MeshComponent)
{
checkSlow(!MeshComponentForeignOwnersMap.FindRef(MeshComponent).IsValid());
verifySlow(MeshComponentForeignOwnersMap.Remove(MeshComponent) == 1);
MeshComponent->DestroyComponent();
}
}
SegmentData.MeshComponents.Empty();
ForeignWorldSplineData->ForeignSplineSegmentData.RemoveSingle(SegmentData);
}
}
if (ForeignWorldSplineData->IsEmpty())
{
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
}
}
}
void ULandscapeSplinesComponent::DestroyOrphanedForeignControlPointMeshComponents(UWorld* OwnerWorld)
{
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
if (ForeignWorldSplineData)
{
for (int32 i = ForeignWorldSplineData->ForeignControlPointData.Num() - 1; i >= 0; --i)
{
FForeignControlPointData& ControlPointData = ForeignWorldSplineData->ForeignControlPointData[i];
const auto& ForeignControlPoint = ControlPointData.Identifier;
if (!ForeignControlPoint && ControlPointData.MeshComponent)
{
ControlPointData.MeshComponent->DestroyComponent();
ForeignWorldSplineData->ForeignControlPointData.RemoveSingle(ControlPointData);
MeshComponentForeignOwnersMap.Remove(ControlPointData.MeshComponent);
}
}
if (ForeignWorldSplineData->IsEmpty())
{
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
}
}
}
UControlPointMeshComponent* ULandscapeSplinesComponent::GetForeignMeshComponent(ULandscapeSplineControlPoint* Owner)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
FForeignWorldSplineData* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
if (ForeignWorldSplineData)
{
FForeignControlPointData* ForeignControlPointData = ForeignWorldSplineData->FindControlPoint(Owner);
if (ForeignControlPointData)
{
return ForeignControlPointData->MeshComponent;
}
}
return nullptr;
}
TArray<USplineMeshComponent*> ULandscapeSplinesComponent::GetForeignMeshComponents(ULandscapeSplineSegment* Owner)
{
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
if (ForeignWorldSplineData)
{
auto* ForeignSplineSegmentData = ForeignWorldSplineData->FindSegmentData(Owner);
if (ForeignSplineSegmentData)
{
return ForeignSplineSegmentData->MeshComponents;
}
}
return {};
}
UObject* ULandscapeSplinesComponent::GetOwnerForMeshComponent(const UMeshComponent* SplineMeshComponent)
{
UObject* LocalOwner = MeshComponentLocalOwnersMap.FindRef(SplineMeshComponent);
if (LocalOwner)
{
return LocalOwner;
}
TLazyObjectPtr<UObject>* ForeignOwner = MeshComponentForeignOwnersMap.Find(SplineMeshComponent);
if (ForeignOwner)
{
// this will be null if ForeignOwner isn't currently loaded
return ForeignOwner->Get();
}
return nullptr;
}
#endif // WITH_EDITOR
//////////////////////////////////////////////////////////////////////////
// CONTROL POINT MESH COMPONENT
UControlPointMeshComponent::UControlPointMeshComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
Mobility = EComponentMobility::Static;
#if WITH_EDITORONLY_DATA
bSelected = false;
#endif
}
#if WITH_EDITOR
bool UControlPointMeshComponent::IsRelevantForSplinePartitioning() const
{
// Ignore editor only splines
// Avoid using USplineMeshComponent::IsEditorOnly() as that would exclude all spline mesh components that are partitioned already
if (Super::IsEditorOnly())
{
return false;
}
if (GetStaticMesh() == nullptr)
{
return false;
}
// Ignore editor splines
if (GetStaticMesh() == GetDefault<ULandscapeSplinesComponent>()->SplineEditorMesh)
{
return false;
}
return true;
}
bool UControlPointMeshComponent::IsEditorOnly() const
{
if (Super::IsEditorOnly())
{
return true;
}
// If landscape uses generated LandscapeSplineMeshesActors, ControlPointMeshComponents is removed from cooked build
ALandscapeSplineActor* SplineActor = Cast<ALandscapeSplineActor>(GetOwner());
if (SplineActor && SplineActor->HasGeneratedLandscapeSplineMeshesActors())
{
// Treat as editor only if the component was included in the spline partitioning
return IsRelevantForSplinePartitioning();
}
return false;
}
#endif
//////////////////////////////////////////////////////////////////////////
// SPLINE CONTROL POINT
ULandscapeSplineControlPoint::ULandscapeSplineControlPoint(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Width = 1000;
SideFalloff = 1000;
EndFalloff = 2000;
#if WITH_EDITORONLY_DATA
Mesh = nullptr;
MeshScale = FVector(1);
bHiddenInGame = false;
LDMaxDrawDistance = 0;
TranslucencySortPriority = 0;
bEnableCollision_DEPRECATED = true;
BodyInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
LayerName = NAME_None;
bRaiseTerrain = true;
bLowerTerrain = true;
LocalMeshComponent = nullptr;
bPlaceSplineMeshesInStreamingLevels = true;
bCastShadow = true;
bRenderCustomDepth = false;
CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default;
CustomDepthStencilValue = 0;
// transients
bSelected = false;
#endif
}
void ULandscapeSplineControlPoint::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID);
Ar.UsingCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID);
#if WITH_EDITOR
if (Ar.UEVer() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
{
bPlaceSplineMeshesInStreamingLevels = false;
}
#endif
#if WITH_EDITORONLY_DATA
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddingBodyInstanceToSplinesElements)
{
BodyInstance.SetCollisionProfileName(bEnableCollision_DEPRECATED ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName);
}
if(Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddSplineLayerFalloff)
{
LeftSideLayerFalloffFactor = LeftSideFalloffFactor;
RightSideLayerFalloffFactor = RightSideFalloffFactor;
for (FLandscapeSplineInterpPoint& Point : Points)
{
Point.LayerFalloffLeft = Point.FalloffLeft;
Point.LayerFalloffRight = Point.FalloffRight;
}
}
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddSplineLayerWidth)
{
for (FLandscapeSplineInterpPoint& Point : Points)
{
Point.LayerLeft = Point.Left;
Point.LayerRight = Point.Right;
}
}
#endif
}
void ULandscapeSplineControlPoint::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
if (GIsEditor)
{
if (LocalMeshComponent != nullptr)
{
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
OuterSplines->MeshComponentLocalOwnersMap.Add(LocalMeshComponent, this);
}
}
if (GetLinkerUEVersion() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
{
// Fix collision profile
if (LocalMeshComponent != nullptr) // ForeignMeshComponents didn't exist yet
{
#if WITH_EDITORONLY_DATA
const FName DesiredCollisionProfileName = SplinesAlwaysUseBlockAll ? UCollisionProfile::BlockAll_ProfileName : CollisionProfileName_DEPRECATED;
const FName CollisionProfile = bEnableCollision_DEPRECATED ? DesiredCollisionProfileName : UCollisionProfile::NoCollision_ProfileName;
#else
const FName CollisionProfile = UCollisionProfile::BlockAll_ProfileName;
#endif
if (LocalMeshComponent->GetCollisionProfileName() != CollisionProfile)
{
LocalMeshComponent->SetCollisionProfileName(CollisionProfile);
}
LocalMeshComponent->SetFlags(RF_TextExportTransient);
}
}
if (GIsEditor
&& ((GetLinkerCustomVersion(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddingBodyInstanceToSplinesElements)
|| (GetLinkerCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID) < FFortniteReleaseBranchCustomObjectVersion::RemoveUselessLandscapeMeshesCookedCollisionData)))
{
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
const bool bUsingEditorMesh = ForeignMeshComponentsPair.Value->bHiddenInGame;
if (!bUsingEditorMesh)
{
ForeignMeshComponentsPair.Value->BodyInstance = BodyInstance;
}
else
{
ForeignMeshComponentsPair.Value->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
// We won't ever enable collisions when using the editor mesh, ensure we don't even cook or load any collision data on this mesh :
if (UBodySetup* BodySetup = ForeignMeshComponentsPair.Value->GetBodySetup())
{
BodySetup->bNeverNeedsCookedCollisionData = bUsingEditorMesh;
}
}
if (LocalMeshComponent != nullptr)
{
const bool bUsingEditorMesh = LocalMeshComponent->bHiddenInGame;
if (!bUsingEditorMesh)
{
LocalMeshComponent->BodyInstance = BodyInstance;
}
else
{
LocalMeshComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
// We won't ever enable collisions when using the editor mesh, ensure we don't even cook or load any collision data on this mesh :
if (UBodySetup* BodySetup = LocalMeshComponent->GetBodySetup())
{
BodySetup->bNeverNeedsCookedCollisionData = bUsingEditorMesh;
}
}
}
#endif
}
FLandscapeSplineSegmentConnection& FLandscapeSplineConnection::GetNearConnection() const
{
return Segment->Connections[End];
}
FLandscapeSplineSegmentConnection& FLandscapeSplineConnection::GetFarConnection() const
{
return Segment->Connections[1 - End];
}
#if WITH_EDITOR
bool ULandscapeSplineControlPoint::SupportsForeignSplineMesh() const
{
return bPlaceSplineMeshesInStreamingLevels && GetOuterULandscapeSplinesComponent()->GetSplineOwner()->SupportsForeignSplineMesh();
}
FName ULandscapeSplineControlPoint::GetBestConnectionTo(FVector Destination) const
{
FName BestSocket = NAME_None;
double BestScore = -DBL_MAX;
if (Mesh != nullptr)
{
for (const UStaticMeshSocket* Socket : Mesh->Sockets)
{
FTransform SocketTransform = FTransform(Socket->RelativeRotation, Socket->RelativeLocation) * FTransform(Rotation, Location, MeshScale);
FVector SocketLocation = SocketTransform.GetTranslation();
FRotator SocketRotation = SocketTransform.GetRotation().Rotator();
const double DistanceToCP = (Destination - Location).Size();
// score closer locations higher
const FVector SocketDelta = Destination - SocketLocation;
const double DistanceToSocket = SocketDelta.Size();
// score closer rotations higher
const double DirectionWeight = FMath::Abs(FVector::DotProduct(SocketDelta, SocketRotation.Vector()));
const double Score = (DistanceToCP - DistanceToSocket) * DirectionWeight;
if (Score > BestScore)
{
BestSocket = Socket->SocketName;
BestScore = Score;
}
}
}
return BestSocket;
}
void ULandscapeSplineControlPoint::GetConnectionLocalLocationAndRotation(FName SocketName, OUT FVector& OutLocation, OUT FRotator& OutRotation) const
{
OutLocation = FVector::ZeroVector;
OutRotation = FRotator::ZeroRotator;
if (Mesh != nullptr)
{
const UStaticMeshSocket* Socket = Mesh->FindSocket(SocketName);
if (Socket != nullptr)
{
OutLocation = Socket->RelativeLocation;
OutRotation = Socket->RelativeRotation;
}
}
}
void ULandscapeSplineControlPoint::GetConnectionLocationAndRotation(FName SocketName, OUT FVector& OutLocation, OUT FRotator& OutRotation) const
{
OutLocation = Location;
OutRotation = Rotation;
if (Mesh != nullptr)
{
const UStaticMeshSocket* Socket = Mesh->FindSocket(SocketName);
if (Socket != nullptr)
{
FTransform SocketTransform = FTransform(Socket->RelativeRotation, Socket->RelativeLocation) * FTransform(Rotation, Location, MeshScale);
OutLocation = SocketTransform.GetTranslation();
OutRotation = SocketTransform.GetRotation().Rotator().GetNormalized();
}
}
}
void ULandscapeSplineControlPoint::SetSplineSelected(bool bInSelected)
{
if (bSelected == bInSelected)
{
return;
}
bSelected = bInSelected;
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
UpdateSplineMeshSelectionState();
}
void ULandscapeSplineControlPoint::AutoCalcRotation(bool bAlwaysRotateForward)
{
Modify();
// always rotate forward only applies when there is exactly 2 segments connected
if (bAlwaysRotateForward && ConnectedSegments.Num() == 2)
{
const FLandscapeSplineSegmentConnection& Near0 = ConnectedSegments[0].GetNearConnection();
const FLandscapeSplineSegmentConnection& Far0 = ConnectedSegments[0].GetFarConnection();
// we modify the rotation so it is halfway between the two connected segments
// Get the start and end location/rotation of this connection
FVector StartLocation0; FRotator StartRotation0;
this->GetConnectionLocationAndRotation(Near0.SocketName, StartLocation0, StartRotation0);
FVector EndLocation0; FRotator EndRotation0;
Far0.ControlPoint->GetConnectionLocationAndRotation(Far0.SocketName, EndLocation0, EndRotation0);
const FVector DesiredDirection0 = (EndLocation0 - StartLocation0);
const FLandscapeSplineSegmentConnection& Near1 = ConnectedSegments[1].GetNearConnection();
const FLandscapeSplineSegmentConnection& Far1 = ConnectedSegments[1].GetFarConnection();
FVector StartLocation1; FRotator StartRotation1;
this->GetConnectionLocationAndRotation(Near1.SocketName, StartLocation1, StartRotation1);
FVector EndLocation1; FRotator EndRotation1;
Far1.ControlPoint->GetConnectionLocationAndRotation(Far1.SocketName, EndLocation1, EndRotation1);
const FVector DesiredDirection1 = (EndLocation1 - StartLocation1);
// compute orientations for incoming and outgoing segments from their direction
FRotator Rot0 = DesiredDirection0.Rotation();
FRotator Rot1 = (-DesiredDirection1).Rotation();
// determine the midpoint orientation via slerp, then convert back to rotator representation
FRotator DesiredRot = FQuat::Slerp(Rot0.Quaternion(), Rot1.Quaternion(), 0.5).Rotator();
// enforce pitch as the average of the incoming and outgoing orientations
DesiredRot.Pitch = (Rot0.Pitch + Rot1.Pitch) * 0.5;
// Remove socket local rotation to calculate the Rotation setting
FVector StartLocalLocation; FRotator StartLocalRotation;
this->GetConnectionLocalLocationAndRotation(Near0.SocketName, StartLocalLocation, StartLocalRotation);
FQuat SocketLocalRotation = StartLocalRotation.Quaternion();
if (FMath::Sign(Near0.TangentLen) < 0) // flip 180 if the tangent is inverted
{
SocketLocalRotation = SocketLocalRotation * FRotator(0, 180, 0).Quaternion();
}
FQuat LocalQuat = DesiredRot.Quaternion() * SocketLocalRotation.Inverse();
Rotation = LocalQuat.Rotator().GetNormalized();
AutoFlipTangents();
return;
}
FRotator Delta = FRotator::ZeroRotator;
// check all connections to this control point
for (const FLandscapeSplineConnection& Connection : ConnectedSegments)
{
// Get the start and end location/rotation of this connection
FVector StartLocation; FRotator StartRotation;
this->GetConnectionLocationAndRotation(Connection.GetNearConnection().SocketName, StartLocation, StartRotation);
FVector StartLocalLocation; FRotator StartLocalRotation;
this->GetConnectionLocalLocationAndRotation(Connection.GetNearConnection().SocketName, StartLocalLocation, StartLocalRotation);
FVector EndLocation; FRotator EndRotation;
Connection.GetFarConnection().ControlPoint->GetConnectionLocationAndRotation(Connection.GetFarConnection().SocketName, EndLocation, EndRotation);
// Find the delta between the direction of the tangent at the connection point and
// the direction to the other end's control point
FQuat SocketLocalRotation = StartLocalRotation.Quaternion();
if (FMath::Sign(Connection.GetNearConnection().TangentLen) < 0)
{
SocketLocalRotation = SocketLocalRotation * FRotator(0, 180, 0).Quaternion();
}
const FVector DesiredDirection = (EndLocation - StartLocation);
const FQuat DesiredSocketRotation = DesiredDirection.Rotation().Quaternion();
const FRotator DesiredRotation = (DesiredSocketRotation * SocketLocalRotation.Inverse()).Rotator().GetNormalized();
const FRotator DesiredRotationDelta = (DesiredRotation - Rotation).GetNormalized();
Delta += DesiredRotationDelta;
}
// Average delta of all connections
Delta *= 1.0f / ConnectedSegments.Num();
// Apply Delta and normalize
Rotation = (Rotation + Delta).GetNormalized();
}
void ULandscapeSplineControlPoint::AutoFlipTangents()
{
for (const FLandscapeSplineConnection& Connection : ConnectedSegments)
{
Connection.Segment->AutoFlipTangents();
}
}
void ULandscapeSplineControlPoint::AutoSetConnections(bool bIncludingValid)
{
for (const FLandscapeSplineConnection& Connection : ConnectedSegments)
{
FLandscapeSplineSegmentConnection& NearConnection = Connection.GetNearConnection();
if (bIncludingValid ||
(Mesh != nullptr && Mesh->FindSocket(NearConnection.SocketName) == nullptr) ||
(Mesh == nullptr && NearConnection.SocketName != NAME_None))
{
FLandscapeSplineSegmentConnection& FarConnection = Connection.GetFarConnection();
FVector EndLocation; FRotator EndRotation;
FarConnection.ControlPoint->GetConnectionLocationAndRotation(FarConnection.SocketName, EndLocation, EndRotation);
NearConnection.SocketName = GetBestConnectionTo(EndLocation);
NearConnection.TangentLen = FMath::Abs(NearConnection.TangentLen);
// Allow flipping tangent on the null connection
if (NearConnection.SocketName == NAME_None)
{
FVector StartLocation; FRotator StartRotation;
NearConnection.ControlPoint->GetConnectionLocationAndRotation(NearConnection.SocketName, StartLocation, StartRotation);
if (FVector::DotProduct((EndLocation - StartLocation).GetSafeNormal(), StartRotation.Vector()) < 0)
{
NearConnection.TangentLen = -NearConnection.TangentLen;
}
}
}
}
}
#endif
#if WITH_EDITOR
TMap<ULandscapeSplinesComponent*, UControlPointMeshComponent*> ULandscapeSplineControlPoint::GetForeignMeshComponents()
{
TMap<ULandscapeSplinesComponent*, UControlPointMeshComponent*> ForeignMeshComponentsMap;
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
TArray<ULandscapeSplinesComponent*> SplineComponents = OuterSplines->GetAllStreamingSplinesComponents();
for (ULandscapeSplinesComponent* SplineComponent : SplineComponents)
{
if (SplineComponent != OuterSplines)
{
auto* ForeignMeshComponent = SplineComponent->GetForeignMeshComponent(this);
if (ForeignMeshComponent)
{
ForeignMeshComponent->Modify(false);
ForeignMeshComponentsMap.Add(SplineComponent, ForeignMeshComponent);
}
}
}
return ForeignMeshComponentsMap;
}
void ULandscapeSplineControlPoint::UpdateSplinePoints(bool bUpdateCollision, bool bUpdateAttachedSegments, bool bUpdateMeshLevel)
{
Modify();
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
ModificationKey = FGuid::NewGuid();
// Clear Foreign world (will get updated if MeshComponent lives outside of Spline world)
ForeignWorld = nullptr;
UControlPointMeshComponent* MeshComponent = LocalMeshComponent;
ULandscapeSplinesComponent* MeshComponentOuterSplines = OuterSplines;
if (Mesh != nullptr)
{
// Attempt to place mesh components into the appropriate landscape streaming levels based on the components under the spline
if (SupportsForeignSplineMesh())
{
MeshComponentOuterSplines = OuterSplines->GetStreamingSplinesComponentByLocation(Location);
if (MeshComponentOuterSplines != OuterSplines)
{
MeshComponent = MeshComponentOuterSplines->GetForeignMeshComponent(this);
if (MeshComponent)
{
MeshComponentOuterSplines->Modify();
MeshComponentOuterSplines->UpdateModificationKey(this);
}
}
}
// Create mesh component if needed
bool bComponentNeedsRegistering = false;
bool bUpdateLocalForeign = false;
if (MeshComponent == nullptr)
{
AActor* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner();
MeshComponentOuterSplines->Modify();
MeshComponentOuterActor->Modify();
MeshComponent = NewObject<UControlPointMeshComponent>(MeshComponentOuterActor, NAME_None, RF_Transactional | RF_TextExportTransient);
MeshComponent->bSelected = bSelected;
MeshComponent->AttachToComponent(MeshComponentOuterSplines, FAttachmentTransformRules::KeepRelativeTransform);
bComponentNeedsRegistering = true;
bUpdateLocalForeign = true;
}
else if(bUpdateMeshLevel)// Update Foreign/Local if necessary
{
AActor* CurrentMeshComponentOuterActor = MeshComponent->GetTypedOuter<AActor>();
AActor* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner();
ILandscapeSplineInterface* SplineOwner = Cast<ILandscapeSplineInterface>(MeshComponentOuterActor);
// Needs updating
if (MeshComponentOuterActor != CurrentMeshComponentOuterActor)
{
MeshComponentOuterActor->Modify();
CurrentMeshComponentOuterActor->Modify();
MeshComponent->Modify();
MeshComponent->UnregisterComponent();
MeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
MeshComponent->InvalidateLightingCache();
MeshComponent->Rename(nullptr, MeshComponentOuterActor);
MeshComponent->AttachToComponent(SplineOwner->GetSplinesComponent(), FAttachmentTransformRules::KeepWorldTransform);
bComponentNeedsRegistering = true;
bUpdateLocalForeign = true;
}
}
// If something changed add MeshComponent to proper map
if (bUpdateLocalForeign)
{
if (MeshComponentOuterSplines == OuterSplines)
{
UObject*& ValueRef = MeshComponentOuterSplines->MeshComponentLocalOwnersMap.FindOrAdd(MeshComponent);
ValueRef = this;
LocalMeshComponent = MeshComponent;
}
else
{
MeshComponentOuterSplines->AddForeignMeshComponent(this, MeshComponent);
}
}
// Update Foreign World
if (MeshComponent && MeshComponentOuterSplines != OuterSplines)
{
ForeignWorld = MeshComponentOuterSplines->GetTypedOuter<UWorld>();
}
FVector MeshLocation = Location;
FRotator MeshRotation = Rotation;
if (MeshComponentOuterSplines != OuterSplines)
{
const FTransform RelativeTransform = OuterSplines->GetComponentTransform().GetRelativeTransform(MeshComponentOuterSplines->GetComponentTransform());
MeshLocation = RelativeTransform.TransformPosition(MeshLocation);
}
const float LocationErrorTolerance = 0.01f;
if (!MeshComponent->GetRelativeLocation().Equals(MeshLocation, LocationErrorTolerance) ||
!MeshComponent->GetRelativeRotation().Equals(MeshRotation) ||
!MeshComponent->GetRelativeScale3D().Equals(MeshScale))
{
MeshComponent->Modify();
MeshComponent->SetRelativeTransform(FTransform(MeshRotation, MeshLocation, MeshScale));
MeshComponent->InvalidateLightingCache();
}
if (MeshComponent->GetStaticMesh() != Mesh)
{
MeshComponent->Modify();
MeshComponent->UnregisterComponent();
bComponentNeedsRegistering = true;
MeshComponent->SetStaticMesh(Mesh);
AutoSetConnections(false);
}
if (MeshComponent->OverrideMaterials != MaterialOverrides)
{
MeshComponent->Modify();
MeshComponent->OverrideMaterials = MaterialOverrides;
MeshComponent->MarkRenderStateDirty();
if (MeshComponent->BodyInstance.IsValidBodyInstance())
{
MeshComponent->BodyInstance.UpdatePhysicalMaterials();
}
}
if (MeshComponent->TranslucencySortPriority != TranslucencySortPriority)
{
MeshComponent->Modify();
MeshComponent->TranslucencySortPriority = TranslucencySortPriority;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->bHiddenInGame != bHiddenInGame)
{
MeshComponent->Modify();
MeshComponent->SetHiddenInGame(bHiddenInGame);
}
if (UBodySetup* BodySetup = MeshComponent->GetBodySetup())
{
BodySetup->bNeverNeedsCookedCollisionData = bHiddenInGame;
}
if (MeshComponent->LDMaxDrawDistance != LDMaxDrawDistance)
{
MeshComponent->Modify();
MeshComponent->LDMaxDrawDistance = LDMaxDrawDistance;
MeshComponent->CachedMaxDrawDistance = 0;
MeshComponent->MarkRenderStateDirty();
}
#if WITH_EDITORONLY_DATA
if (MeshComponent->BodyInstance.GetCollisionProfileName() != BodyInstance.GetCollisionProfileName())
{
MeshComponent->Modify();
MeshComponent->BodyInstance = BodyInstance;
MeshComponent->RecreatePhysicsState();
MeshComponent->MarkRenderStateDirty();
}
#endif
if (MeshComponent->CastShadow != bCastShadow)
{
MeshComponent->Modify();
MeshComponent->SetCastShadow(bCastShadow);
}
if (MeshComponent->RuntimeVirtualTextures != RuntimeVirtualTextures)
{
MeshComponent->Modify();
MeshComponent->RuntimeVirtualTextures = RuntimeVirtualTextures;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->VirtualTextureLodBias != VirtualTextureLodBias)
{
MeshComponent->Modify();
MeshComponent->VirtualTextureLodBias = static_cast<int8>(VirtualTextureLodBias);
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->VirtualTextureCullMips != VirtualTextureCullMips)
{
MeshComponent->Modify();
MeshComponent->VirtualTextureCullMips = static_cast<int8>(VirtualTextureCullMips);
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->VirtualTextureMainPassMaxDrawDistance != VirtualTextureMainPassMaxDrawDistance)
{
MeshComponent->Modify();
MeshComponent->VirtualTextureMainPassMaxDrawDistance = VirtualTextureMainPassMaxDrawDistance;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->VirtualTextureRenderPassType != VirtualTextureRenderPassType)
{
MeshComponent->Modify();
MeshComponent->VirtualTextureRenderPassType = VirtualTextureRenderPassType;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->bRenderCustomDepth != bRenderCustomDepth)
{
MeshComponent->Modify();
MeshComponent->bRenderCustomDepth = bRenderCustomDepth;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->CustomDepthStencilWriteMask != CustomDepthStencilWriteMask)
{
MeshComponent->Modify();
MeshComponent->CustomDepthStencilWriteMask = CustomDepthStencilWriteMask;
MeshComponent->MarkRenderStateDirty();
}
if (MeshComponent->CustomDepthStencilValue != CustomDepthStencilValue)
{
MeshComponent->Modify();
MeshComponent->CustomDepthStencilValue = CustomDepthStencilValue;
MeshComponent->MarkRenderStateDirty();
}
if (bComponentNeedsRegistering)
{
MeshComponent->RegisterComponent();
}
}
else
{
MeshComponent = nullptr;
ForeignWorld = nullptr;
AutoSetConnections(false);
}
// Destroy any unused components
if (LocalMeshComponent && LocalMeshComponent != MeshComponent)
{
OuterSplines->Modify();
LocalMeshComponent->Modify();
checkSlow(OuterSplines->MeshComponentLocalOwnersMap.FindRef(LocalMeshComponent) == this);
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(LocalMeshComponent) == 1);
LocalMeshComponent->DestroyComponent();
LocalMeshComponent = nullptr;
}
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ULandscapeSplinesComponent* ForeignMeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
auto* ForeignMeshComponent = ForeignMeshComponentsPair.Value;
if (ForeignMeshComponent != MeshComponent)
{
ForeignMeshComponentOuterSplines->Modify();
ForeignMeshComponent->Modify();
ForeignMeshComponentOuterSplines->RemoveForeignMeshComponent(this, ForeignMeshComponent);
ForeignMeshComponent->DestroyComponent();
}
}
ForeignMeshComponentsMap.Empty();
const float LeftSideFalloff = LeftSideFalloffFactor * SideFalloff;
const float RightSideFalloff = RightSideFalloffFactor * SideFalloff;
const float LeftSideLayerFalloff = LeftSideLayerFalloffFactor * SideFalloff;
const float RightSideLayerFalloff = RightSideLayerFalloffFactor * SideFalloff;
const float LayerWidth = Width * LayerWidthRatio;
// Update "Points" array
if (Mesh != nullptr)
{
Points.Reset(ConnectedSegments.Num());
for (const FLandscapeSplineConnection& Connection : ConnectedSegments)
{
FVector StartLocation; FRotator StartRotation;
GetConnectionLocationAndRotation(Connection.GetNearConnection().SocketName, StartLocation, StartRotation);
const double Roll = FMath::DegreesToRadians(StartRotation.Roll);
const FVector Tangent = StartRotation.Vector();
const FVector BiNormal = FQuat(Tangent, -Roll).RotateVector((Tangent ^ FVector(0, 0, -1)).GetSafeNormal());
const FVector LeftPos = StartLocation - BiNormal * Width;
const FVector RightPos = StartLocation + BiNormal * Width;
const FVector FalloffLeftPos = StartLocation - BiNormal * (Width + LeftSideFalloff);
const FVector FalloffRightPos = StartLocation + BiNormal * (Width + RightSideFalloff);
const FVector LayerLeftPos = StartLocation - BiNormal * LayerWidth;
const FVector LayerRightPos = StartLocation + BiNormal * LayerWidth;
const FVector LayerFalloffLeftPos = StartLocation - BiNormal * (LayerWidth + LeftSideLayerFalloff);
const FVector LayerFalloffRightPos = StartLocation + BiNormal * (LayerWidth + RightSideLayerFalloff);
Points.Emplace(StartLocation, LeftPos, RightPos, FalloffLeftPos, FalloffRightPos, LayerLeftPos, LayerRightPos, LayerFalloffLeftPos, LayerFalloffRightPos, 1.0f);
}
const FVector CPLocation = Location;
Points.Sort([&CPLocation](const FLandscapeSplineInterpPoint& x, const FLandscapeSplineInterpPoint& y){return (x.Center - CPLocation).Rotation().Yaw < (y.Center - CPLocation).Rotation().Yaw;});
}
else
{
Points.Reset(1);
FVector StartLocation; FRotator StartRotation;
GetConnectionLocationAndRotation(NAME_None, StartLocation, StartRotation);
const double Roll = FMath::DegreesToRadians(StartRotation.Roll);
const FVector Tangent = StartRotation.Vector();
const FVector BiNormal = FQuat(Tangent, -Roll).RotateVector((Tangent ^ FVector(0, 0, -1)).GetSafeNormal());
const FVector LeftPos = StartLocation - BiNormal * Width;
const FVector RightPos = StartLocation + BiNormal * Width;
const FVector FalloffLeftPos = StartLocation - BiNormal * (Width + LeftSideFalloff);
const FVector FalloffRightPos = StartLocation + BiNormal * (Width + RightSideFalloff);
const FVector LayerLeftPos = StartLocation - BiNormal * LayerWidth;
const FVector LayerRightPos = StartLocation + BiNormal * LayerWidth;
const FVector LayerFalloffLeftPos = StartLocation - BiNormal * (LayerWidth + LeftSideLayerFalloff);
const FVector LayerFalloffRightPos = StartLocation + BiNormal * (LayerWidth + RightSideLayerFalloff);
Points.Emplace(StartLocation, LeftPos, RightPos, FalloffLeftPos, FalloffRightPos, LayerLeftPos, LayerRightPos, LayerFalloffLeftPos, LayerFalloffRightPos, 1.0f);
}
// Update bounds
Bounds = FBox(ForceInit);
// Sprite bounds
float SpriteScale = FMath::Clamp<float>(Width != 0 ? Width / 2 : SideFalloff / 4, 10, 1000);
Bounds += Location + FVector(0, 0, 0.75f * SpriteScale);
Bounds = Bounds.ExpandBy(SpriteScale);
// Points bounds
for (const FLandscapeSplineInterpPoint& Point : Points)
{
Bounds += Point.FalloffLeft;
Bounds += Point.FalloffRight;
}
OuterSplines->MarkRenderStateDirty();
if (bUpdateAttachedSegments)
{
for (const FLandscapeSplineConnection& Connection : ConnectedSegments)
{
Connection.Segment->UpdateSplinePoints(bUpdateCollision);
}
}
}
void ULandscapeSplineControlPoint::UpdateSplineMeshSelectionState()
{
if (LocalMeshComponent != nullptr)
{
LocalMeshComponent->bSelected = bSelected;
LocalMeshComponent->PushSelectionToProxy();
}
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
auto* MeshComponent = ForeignMeshComponentsPair.Value;
MeshComponent->bSelected = bSelected;
MeshComponent->PushSelectionToProxy();
}
}
void ULandscapeSplineControlPoint::DeleteSplinePoints()
{
Modify();
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
Points.Reset();
Bounds = FBox(ForceInit);
OuterSplines->MarkRenderStateDirty();
if (LocalMeshComponent != nullptr)
{
OuterSplines->Modify();
LocalMeshComponent->Modify();
checkSlow(OuterSplines->MeshComponentLocalOwnersMap.FindRef(LocalMeshComponent) == this);
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(LocalMeshComponent) == 1);
LocalMeshComponent->DestroyComponent();
LocalMeshComponent = nullptr;
}
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
MeshComponentOuterSplines->Modify();
auto* MeshComponent = ForeignMeshComponentsPair.Value;
MeshComponent->Modify();
MeshComponentOuterSplines->RemoveForeignMeshComponent(this, MeshComponent);
MeshComponent->DestroyComponent();
}
}
FName ULandscapeSplineControlPoint::GetCollisionProfileName() const
{
#if WITH_EDITORONLY_DATA
return BodyInstance.GetCollisionProfileName();
#else
return UCollisionProfile::BlockAll_ProfileName;
#endif
}
void ULandscapeSplineControlPoint::PostEditUndo()
{
bHackIsUndoingSplines = true;
Super::PostEditUndo();
bHackIsUndoingSplines = false;
// Update mesh components to make sure the selection state stays in sync
UpdateSplineMeshSelectionState();
ULandscapeSplinesComponent* SplineComponent = GetOuterULandscapeSplinesComponent();
SplineComponent->MarkRenderStateDirty();
SplineComponent->RequestSplineLayerUpdate();
}
void ULandscapeSplineControlPoint::PostDuplicate(bool bDuplicateForPIE)
{
if (!bDuplicateForPIE)
{
// if we get duplicated but our local mesh doesn't, then clear our reference to the mesh - it's not ours
if (LocalMeshComponent != nullptr)
{
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
if (LocalMeshComponent->GetOuter() != OuterSplines->GetOwner())
{
LocalMeshComponent = nullptr;
}
else
{
// If LocalMeshComponent is still valid make sure its added to the transient map that normally gets populated on PostLoad
OuterSplines->MeshComponentLocalOwnersMap.Add(LocalMeshComponent, this);
}
}
UpdateSplinePoints();
}
Super::PostDuplicate(bDuplicateForPIE);
}
void ULandscapeSplineControlPoint::PostEditImport()
{
Super::PostEditImport();
GetOuterULandscapeSplinesComponent()->ControlPoints.AddUnique(this);
}
void ULandscapeSplineControlPoint::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
Width = FMath::Max(Width, 0.001f);
LayerWidthRatio = FMath::Max(LayerWidthRatio, 0.01f);
SideFalloff = FMath::Max(SideFalloff, 0.0f);
LeftSideFalloffFactor = FMath::Clamp(LeftSideFalloffFactor, 0.0f, 1.0f);
RightSideFalloffFactor = FMath::Clamp(RightSideFalloffFactor, 0.0f, 1.0f);
LeftSideLayerFalloffFactor = FMath::Clamp(LeftSideLayerFalloffFactor, 0.0f, 1.0f);
RightSideLayerFalloffFactor = FMath::Clamp(RightSideLayerFalloffFactor, 0.0f, 1.0f);
EndFalloff = FMath::Max(EndFalloff, 0.0f);
// Don't update splines when undoing, not only is it unnecessary and expensive,
// it also causes failed asserts in debug builds when trying to register components
// (because the actor hasn't reset its OwnedComponents array yet)
if (!bHackIsUndoingSplines)
{
const bool bUpdateCollision = PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive;
UpdateSplinePoints(bUpdateCollision);
}
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet)
{
GetOuterULandscapeSplinesComponent()->RequestSplineLayerUpdate();
}
}
#endif // WITH_EDITOR
//////////////////////////////////////////////////////////////////////////
// SPLINE SEGMENT
ULandscapeSplineSegment::ULandscapeSplineSegment(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
Connections[0].ControlPoint = nullptr;
Connections[0].TangentLen = 0;
Connections[1].ControlPoint = nullptr;
Connections[1].TangentLen = 0;
#if WITH_EDITORONLY_DATA
LayerName = NAME_None;
bRaiseTerrain = true;
bLowerTerrain = true;
// SplineMesh properties
SplineMeshes.Empty();
LDMaxDrawDistance = 0;
bHiddenInGame = false;
TranslucencySortPriority = 0;
bPlaceSplineMeshesInStreamingLevels = true;
bCastShadow = true;
bRenderCustomDepth = false;
CustomDepthStencilWriteMask = ERendererStencilMask::ERSM_Default;
CustomDepthStencilValue = 0;
bEnableCollision_DEPRECATED = true;
BodyInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
// transients
bSelected = false;
#endif
}
void ULandscapeSplineSegment::PostInitProperties()
{
Super::PostInitProperties();
#if WITH_EDITORONLY_DATA
if (!HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad) &&
!HasAnyInternalFlags(EInternalObjectFlags_AsyncLoading))
{
// create a new random seed for all new objects
RandomSeed = FMath::Rand();
}
#endif
}
void ULandscapeSplineSegment::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FLandscapeCustomVersion::GUID);
Ar.UsingCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID);
#if WITH_EDITOR
if (Ar.UEVer() < VER_UE4_SPLINE_MESH_ORIENTATION)
{
for (FLandscapeSplineMeshEntry& MeshEntry : SplineMeshes)
{
switch (MeshEntry.Orientation_DEPRECATED)
{
case LSMO_XUp:
MeshEntry.ForwardAxis = ESplineMeshAxis::Z;
MeshEntry.UpAxis = ESplineMeshAxis::X;
break;
case LSMO_YUp:
MeshEntry.ForwardAxis = ESplineMeshAxis::Z;
MeshEntry.UpAxis = ESplineMeshAxis::Y;
break;
}
}
}
if (Ar.UEVer() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
{
bPlaceSplineMeshesInStreamingLevels = false;
}
#endif
#if WITH_EDITORONLY_DATA
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddingBodyInstanceToSplinesElements)
{
BodyInstance.SetCollisionProfileName(bEnableCollision_DEPRECATED ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName);
}
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddSplineLayerFalloff)
{
for (FLandscapeSplineInterpPoint& Point : Points)
{
Point.LayerFalloffLeft = Point.FalloffLeft;
Point.LayerFalloffRight = Point.FalloffRight;
}
}
if (Ar.IsLoading() && Ar.CustomVer(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddSplineLayerWidth)
{
for (FLandscapeSplineInterpPoint& Point : Points)
{
Point.LayerLeft = Point.Left;
Point.LayerRight = Point.Right;
}
}
#endif
}
void ULandscapeSplineSegment::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
if (GIsEditor)
{
if (GetLinkerUEVersion() < VER_UE4_ADDED_LANDSCAPE_SPLINE_EDITOR_MESH &&
LocalMeshComponents.Num() == 0) // ForeignMeshComponents didn't exist yet
{
UpdateSplinePoints();
}
// Replace null meshes with the editor mesh
// Also make sure that we update their visibility and collision profile if they are editor mesh
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
if (OuterSplines->SplineEditorMesh != nullptr)
{
for (auto& LocalMeshComponent : LocalMeshComponents)
{
if (LocalMeshComponent->GetStaticMesh() == nullptr || LocalMeshComponent->GetStaticMesh() == OuterSplines->SplineEditorMesh)
{
LocalMeshComponent->ConditionalPostLoad();
if (LocalMeshComponent->GetStaticMesh() == nullptr)
{
LocalMeshComponent->SetStaticMesh(OuterSplines->SplineEditorMesh);
}
LocalMeshComponent->SetHiddenInGame(true);
LocalMeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
LocalMeshComponent->BodyInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
}
}
for (auto& LocalMeshComponent : LocalMeshComponents)
{
OuterSplines->MeshComponentLocalOwnersMap.Add(LocalMeshComponent, this);
}
}
if (GetLinkerUEVersion() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
{
// Fix collision profile
for (auto& LocalMeshComponent : LocalMeshComponents) // ForeignMeshComponents didn't exist yet
{
UpdateMeshCollisionProfile(LocalMeshComponent);
LocalMeshComponent->SetFlags(RF_TextExportTransient);
}
}
if (GIsEditor
&& ((GetLinkerCustomVersion(FLandscapeCustomVersion::GUID) < FLandscapeCustomVersion::AddingBodyInstanceToSplinesElements)
|| (GetLinkerCustomVersion(FFortniteReleaseBranchCustomObjectVersion::GUID) < FFortniteReleaseBranchCustomObjectVersion::RemoveUselessLandscapeMeshesCookedCollisionData)))
{
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
for (auto* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
const bool bUsingEditorMesh = OuterSplines->IsUsingEditorMesh(ForeignMeshComponent);
if (!bUsingEditorMesh)
{
ForeignMeshComponent->BodyInstance = BodyInstance;
}
else
{
ForeignMeshComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
// We won't ever enable collisions when using the editor mesh, ensure we don't even cook or load any collision data on this mesh :
ForeignMeshComponent->SetbNeverNeedsCookedCollisionData(bUsingEditorMesh);
}
}
for (auto& LocalMeshComponent : LocalMeshComponents)
{
if (LocalMeshComponent != nullptr)
{
const bool bUsingEditorMesh = OuterSplines->IsUsingEditorMesh(LocalMeshComponent);
if (!bUsingEditorMesh)
{
LocalMeshComponent->BodyInstance = BodyInstance;
}
else
{
LocalMeshComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
// We won't ever enable collisions when using the editor mesh, ensure we don't even cook or load any collision data on this mesh :
LocalMeshComponent->SetbNeverNeedsCookedCollisionData(bUsingEditorMesh);
}
}
}
#endif
// If the "spline.blockall" cvar is on we might need to rebuild collision
// for our spline meshes.
if (SplinesAlwaysUseBlockAll)
{
for (auto& LocalMeshComponent : LocalMeshComponents)
{
UpdateMeshCollisionProfile(LocalMeshComponent);
LocalMeshComponent->Modify();
}
}
}
void ULandscapeSplineSegment::UpdateMeshCollisionProfile(USplineMeshComponent* MeshComponent)
{
#if WITH_EDITORONLY_DATA
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
const bool bUsingEditorMesh = OuterSplines->IsUsingEditorMesh(MeshComponent);
const FName DesiredCollisionProfileName = SplinesAlwaysUseBlockAll ? UCollisionProfile::BlockAll_ProfileName : GetCollisionProfileName();
const FName CollisionProfile = (bEnableCollision_DEPRECATED && !bUsingEditorMesh) ? DesiredCollisionProfileName : UCollisionProfile::NoCollision_ProfileName;
// We won't ever enable collisions when using the editor mesh, ensure we don't even cook or load any collision data on this mesh :
MeshComponent->SetbNeverNeedsCookedCollisionData(bUsingEditorMesh);
#else
const FName CollisionProfile = UCollisionProfile::BlockAll_ProfileName;
#endif
if (MeshComponent->GetCollisionProfileName() != CollisionProfile)
{
MeshComponent->SetCollisionProfileName(CollisionProfile);
MeshComponent->Modify();
}
}
/** */
#if WITH_EDITOR
void ULandscapeSplineSegment::SetSplineSelected(bool bInSelected)
{
if (bInSelected == bSelected)
{
return;
}
bSelected = bInSelected;
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
UpdateSplineMeshSelectionState();
}
void ULandscapeSplineSegment::AutoFlipTangents()
{
FVector StartLocation; FRotator StartRotation;
Connections[0].ControlPoint->GetConnectionLocationAndRotation(Connections[0].SocketName, StartLocation, StartRotation);
FVector EndLocation; FRotator EndRotation;
Connections[1].ControlPoint->GetConnectionLocationAndRotation(Connections[1].SocketName, EndLocation, EndRotation);
// Flipping the tangent is only allowed if not using a socket
if (Connections[0].SocketName == NAME_None && FVector::DotProduct((EndLocation - StartLocation).GetSafeNormal() * Connections[0].TangentLen, StartRotation.Vector()) < 0)
{
Connections[0].TangentLen = -Connections[0].TangentLen;
}
if (Connections[1].SocketName == NAME_None && FVector::DotProduct((StartLocation - EndLocation).GetSafeNormal() * Connections[1].TangentLen, EndRotation.Vector()) < 0)
{
Connections[1].TangentLen = -Connections[1].TangentLen;
}
}
#endif
static float ApproxLength(const FInterpCurveVector& SplineInfo, const float Start = 0.0f, const float End = 1.0f, const int32 ApproxSections = 4)
{
double SplineLength = 0;
FVector OldPos = SplineInfo.Eval(Start, FVector::ZeroVector);
for (int32 i = 1; i <= ApproxSections; i++)
{
FVector NewPos = SplineInfo.Eval(FMath::Lerp(Start, End, (float)i / (float)ApproxSections), FVector::ZeroVector);
SplineLength += static_cast<float>((NewPos - OldPos).Size());
OldPos = NewPos;
}
return static_cast<float>(SplineLength);
}
static ESplineMeshAxis::Type CrossAxis(ESplineMeshAxis::Type InForwardAxis, ESplineMeshAxis::Type InUpAxis)
{
check(InForwardAxis != InUpAxis);
return (ESplineMeshAxis::Type)(3 ^ InForwardAxis ^ InUpAxis);
}
bool FLandscapeSplineMeshEntry::IsValid() const
{
return Mesh != nullptr && ForwardAxis != UpAxis && Scale.GetAbsMin() > KINDA_SMALL_NUMBER;
}
#if WITH_EDITOR
TMap<ULandscapeSplinesComponent*, TArray<USplineMeshComponent*>> ULandscapeSplineSegment::GetForeignMeshComponents()
{
TMap<ULandscapeSplinesComponent*, TArray<USplineMeshComponent*>> ForeignMeshComponentsMap;
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
TArray<ULandscapeSplinesComponent*> SplineComponents = OuterSplines->GetAllStreamingSplinesComponents();
for (ULandscapeSplinesComponent* SplineComponent : SplineComponents)
{
if (SplineComponent != OuterSplines)
{
auto ForeignMeshComponents = SplineComponent->GetForeignMeshComponents(this);
if (ForeignMeshComponents.Num() > 0)
{
for (auto* ForeignMeshComponent : ForeignMeshComponents)
{
ForeignMeshComponent->Modify(false);
}
ForeignMeshComponentsMap.Add(SplineComponent, MoveTemp(ForeignMeshComponents));
}
}
}
return ForeignMeshComponentsMap;
}
TArray<USplineMeshComponent*> ULandscapeSplineSegment::GetLocalMeshComponents() const
{
return LocalMeshComponents;
}
bool ULandscapeSplineSegment::SupportsForeignSplineMesh() const
{
return bPlaceSplineMeshesInStreamingLevels&& GetOuterULandscapeSplinesComponent()->GetSplineOwner()->SupportsForeignSplineMesh();
}
void ULandscapeSplineSegment::UpdateSplinePoints(bool bUpdateCollision, bool bUpdateMeshLevel)
{
Modify();
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
SplineInfo.Points.Empty(2);
Points.Reset();
if (Connections[0].ControlPoint == nullptr
|| Connections[1].ControlPoint == nullptr)
{
return;
}
// Set up BSpline
FVector StartLocation; FRotator StartRotation;
Connections[0].ControlPoint->GetConnectionLocationAndRotation(Connections[0].SocketName, StartLocation, StartRotation);
SplineInfo.Points.Emplace(0.0f, StartLocation, StartRotation.Vector() * Connections[0].TangentLen, StartRotation.Vector() * Connections[0].TangentLen, CIM_CurveUser);
FVector EndLocation; FRotator EndRotation;
Connections[1].ControlPoint->GetConnectionLocationAndRotation(Connections[1].SocketName, EndLocation, EndRotation);
SplineInfo.Points.Emplace(1.0f, EndLocation, EndRotation.Vector() * -Connections[1].TangentLen, EndRotation.Vector() * -Connections[1].TangentLen, CIM_CurveUser);
// Pointify
// Calculate spline length
const float SplineLength = ApproxLength(SplineInfo, 0.0f, 1.0f, 4);
const float StartFalloffFraction = ((Connections[0].ControlPoint->ConnectedSegments.Num() > 1) ? 0 : (Connections[0].ControlPoint->EndFalloff / SplineLength));
const float EndFalloffFraction = ((Connections[1].ControlPoint->ConnectedSegments.Num() > 1) ? 0 : (Connections[1].ControlPoint->EndFalloff / SplineLength));
const float StartWidth = Connections[0].ControlPoint->Width;
const float EndWidth = Connections[1].ControlPoint->Width;
const float StartLayerWidth = StartWidth * Connections[0].ControlPoint->LayerWidthRatio;
const float EndLayerWidth = EndWidth * Connections[1].ControlPoint->LayerWidthRatio;
LandscapeSplineRaster::FPointifyFalloffs Falloffs;
Falloffs.StartLeftSide = Connections[0].ControlPoint->LeftSideFalloffFactor * Connections[0].ControlPoint->SideFalloff;
Falloffs.EndLeftSide = Connections[1].ControlPoint->LeftSideFalloffFactor * Connections[1].ControlPoint->SideFalloff;
Falloffs.StartRightSide = Connections[0].ControlPoint->RightSideFalloffFactor * Connections[0].ControlPoint->SideFalloff;
Falloffs.EndRightSide = Connections[1].ControlPoint->RightSideFalloffFactor * Connections[1].ControlPoint->SideFalloff;
Falloffs.StartLeftSideLayer = Connections[0].ControlPoint->LeftSideLayerFalloffFactor * Connections[0].ControlPoint->SideFalloff;
Falloffs.EndLeftSideLayer = Connections[1].ControlPoint->LeftSideLayerFalloffFactor * Connections[1].ControlPoint->SideFalloff;
Falloffs.StartRightSideLayer = Connections[0].ControlPoint->RightSideLayerFalloffFactor * Connections[0].ControlPoint->SideFalloff;
Falloffs.EndRightSideLayer = Connections[1].ControlPoint->RightSideLayerFalloffFactor * Connections[1].ControlPoint->SideFalloff;
const float StartRollDegrees = static_cast<float>(StartRotation.Roll * (Connections[0].TangentLen > 0 ? 1 : -1));
const float EndRollDegrees = static_cast<float>(EndRotation.Roll * (Connections[1].TangentLen > 0 ? -1 : 1));
const float StartRoll = FMath::DegreesToRadians(StartRollDegrees);
const float EndRoll = FMath::DegreesToRadians(EndRollDegrees);
const float StartMeshOffset = Connections[0].ControlPoint->SegmentMeshOffset;
const float EndMeshOffset = Connections[1].ControlPoint->SegmentMeshOffset;
int32 NumPoints = FMath::CeilToInt(SplineLength / OuterSplines->SplineResolution);
NumPoints = FMath::Clamp(NumPoints, 1, 1000);
LandscapeSplineRaster::Pointify(SplineInfo, Points, NumPoints, StartFalloffFraction, EndFalloffFraction, StartWidth, EndWidth, StartLayerWidth, EndLayerWidth, Falloffs, StartRollDegrees, EndRollDegrees);
// Update Bounds
Bounds = FBox(ForceInit);
for (const FLandscapeSplineInterpPoint& Point : Points)
{
Bounds += Point.FalloffLeft;
Bounds += Point.FalloffRight;
}
OuterSplines->MarkRenderStateDirty();
// Spline mesh components
TArray<const FLandscapeSplineMeshEntry*> UsableMeshes;
UsableMeshes.Reserve(SplineMeshes.Num());
for (const FLandscapeSplineMeshEntry& MeshEntry : SplineMeshes)
{
if (MeshEntry.IsValid())
{
UsableMeshes.Add(&MeshEntry);
}
}
// Editor mesh
bool bUsingEditorMesh = false;
FLandscapeSplineMeshEntry SplineEditorMeshEntry;
if (UsableMeshes.Num() == 0 && OuterSplines->SplineEditorMesh != nullptr)
{
SplineEditorMeshEntry.Mesh = OuterSplines->SplineEditorMesh;
SplineEditorMeshEntry.MaterialOverrides = {};
SplineEditorMeshEntry.bCenterH = true;
SplineEditorMeshEntry.CenterAdjust = {0.0f, 0.5f};
SplineEditorMeshEntry.bScaleToWidth = true;
SplineEditorMeshEntry.Scale = {3, 1, 1};
SplineEditorMeshEntry.ForwardAxis = ESplineMeshAxis::X;
SplineEditorMeshEntry.UpAxis = ESplineMeshAxis::Z;
UsableMeshes.Add(&SplineEditorMeshEntry);
bUsingEditorMesh = true;
}
OuterSplines->Modify();
TArray<USplineMeshComponent*> MeshComponents;
TArray<TObjectPtr<USplineMeshComponent>> OldLocalMeshComponents;
Swap(LocalMeshComponents, OldLocalMeshComponents);
LocalMeshComponents.Reserve(20);
// Gather all ForeignMesh associated with this Segment
TMap<ULandscapeSplinesComponent*, TArray<USplineMeshComponent*>> ForeignMeshComponentsMap = GetForeignMeshComponents();
// Unregister components, Remove Foreign/Local Associations
for (TObjectPtr<USplineMeshComponent>& LocalMeshComponent : OldLocalMeshComponents)
{
USplineMeshComponent* Comp = LocalMeshComponent.Get();
checkSlow(OuterSplines->MeshComponentLocalOwnersMap.FindRef(Comp) == this);
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(Comp) == 1);
Comp->Modify();
Comp->UnregisterComponent();
}
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ForeignMeshComponentsPair.Key->Modify();
ForeignMeshComponentsPair.Key->GetOwner()->Modify();
ForeignMeshComponentsPair.Key->RemoveAllForeignMeshComponents(this);
for (auto* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
ForeignMeshComponent->Modify();
ForeignMeshComponent->UnregisterComponent();
}
}
ModificationKey = FGuid::NewGuid();
ForeignWorlds.Reset();
if (SplineLength > 0 && (StartWidth > 0 || EndWidth > 0) && UsableMeshes.Num() > 0)
{
float T = 0;
int32 iMesh = 0;
struct FMeshSettings
{
const float T;
const FLandscapeSplineMeshEntry* const MeshEntry;
FMeshSettings(float InT, const FLandscapeSplineMeshEntry* const InMeshEntry) :
T(InT), MeshEntry(InMeshEntry) { }
};
TArray<FMeshSettings> MeshSettings;
MeshSettings.Reserve(21);
FRandomStream Random(RandomSeed);
// First pass:
// Choose meshes, create components, calculate lengths
while (T < 1.0f && iMesh < 20) // Max 20 meshes per spline segment
{
const float CosInterp = 0.5f - 0.5f * FMath::Cos(T * PI);
const float Width = FMath::Lerp(StartWidth, EndWidth, CosInterp);
const FLandscapeSplineMeshEntry* MeshEntry = UsableMeshes[Random.RandHelper(UsableMeshes.Num())];
UStaticMesh* Mesh = MeshEntry->Mesh;
const FBoxSphereBounds MeshBounds = Mesh->GetBounds();
FVector Scale = MeshEntry->Scale;
if (MeshEntry->bScaleToWidth)
{
float ScaleFactor = Width / USplineMeshComponent::GetAxisValueRef(MeshBounds.BoxExtent, CrossAxis(MeshEntry->ForwardAxis, MeshEntry->UpAxis));
if (MeshEntry->bNoZScaling)
{
Scale.X *= ScaleFactor;
Scale.Y *= ScaleFactor;
}
else
{
Scale *= ScaleFactor;
}
}
const float MeshLength = static_cast<float>(FMath::Abs(USplineMeshComponent::GetAxisValueRef(MeshBounds.BoxExtent, MeshEntry->ForwardAxis) * 2.0 *
USplineMeshComponent::GetAxisValueRef(Scale, MeshEntry->ForwardAxis)));
float MeshT = (MeshLength / SplineLength);
// Improve our approximation if we're not going off the end of the spline
if (T + MeshT <= 1.0f)
{
MeshT *= (MeshLength / ApproxLength(SplineInfo, T, T + MeshT, 4));
MeshT *= (MeshLength / ApproxLength(SplineInfo, T, T + MeshT, 4));
}
// If it's smaller to round up than down, don't add another component
if (iMesh != 0 && (1.0f - T) < (T + MeshT - 1.0f))
{
break;
}
ULandscapeSplinesComponent* MeshComponentOuterSplines = OuterSplines;
// Attempt to place mesh components into the appropriate landscape streaming levels based on the components under the spline
if (SupportsForeignSplineMesh() && !bUsingEditorMesh)
{
// Only "approx" because we rescale T for the 2nd pass based on how well our chosen meshes fit, but it should be good enough
FVector ApproxMeshLocation = SplineInfo.Eval(T + MeshT / 2, FVector::ZeroVector);
MeshComponentOuterSplines = OuterSplines->GetStreamingSplinesComponentByLocation(ApproxMeshLocation);
MeshComponentOuterSplines->Modify();
}
USplineMeshComponent* MeshComponent = nullptr;
if (MeshComponentOuterSplines == OuterSplines)
{
if (OldLocalMeshComponents.Num() > 0)
{
MeshComponent = OldLocalMeshComponents.Pop(EAllowShrinking::No).Get();
}
}
else
{
TArray<USplineMeshComponent*>* ForeignMeshComponents = ForeignMeshComponentsMap.Find(MeshComponentOuterSplines);
if (ForeignMeshComponents && ForeignMeshComponents->Num() > 0)
{
MeshComponentOuterSplines->UpdateModificationKey(this);
MeshComponent = ForeignMeshComponents->Pop(EAllowShrinking::No);
}
}
if (MeshComponent == nullptr)
{
AActor* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner();
MeshComponentOuterActor->Modify();
MeshComponent = NewObject<USplineMeshComponent>(MeshComponentOuterActor, NAME_None, RF_Transactional | RF_TextExportTransient);
MeshComponent->bSelected = bSelected;
MeshComponent->AttachToComponent(MeshComponentOuterSplines, FAttachmentTransformRules::KeepRelativeTransform);
}
else if (bUpdateMeshLevel)// Update Foreign/Local if necessary
{
AActor* CurrentMeshComponentOuterActor = MeshComponent->GetTypedOuter<AActor>();
AActor* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner();
ILandscapeSplineInterface* SplineOwner = Cast<ILandscapeSplineInterface>(MeshComponentOuterActor);
// Needs updating
if (MeshComponentOuterActor != CurrentMeshComponentOuterActor)
{
MeshComponentOuterActor->Modify();
CurrentMeshComponentOuterActor->Modify();
MeshComponent->Modify();
MeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
MeshComponent->InvalidateLightingCache();
MeshComponent->Rename(nullptr, MeshComponentOuterActor);
MeshComponent->AttachToComponent(SplineOwner->GetSplinesComponent(), FAttachmentTransformRules::KeepWorldTransform);
}
}
// New Foreign/Local Mapping
if (MeshComponentOuterSplines == OuterSplines)
{
LocalMeshComponents.Add(MeshComponent);
MeshComponentOuterSplines->MeshComponentLocalOwnersMap.Add(MeshComponent, this);
}
else
{
MeshComponentOuterSplines->AddForeignMeshComponent(this, MeshComponent);
ForeignWorlds.AddUnique(MeshComponentOuterSplines->GetTypedOuter<UWorld>());
}
MeshComponents.Add(MeshComponent);
MeshComponent->SetStaticMesh(Mesh);
MeshComponent->OverrideMaterials = MeshEntry->MaterialOverrides;
MeshComponent->MarkRenderStateDirty();
if (MeshComponent->BodyInstance.IsValidBodyInstance())
{
MeshComponent->BodyInstance.UpdatePhysicalMaterials();
}
MeshComponent->SetHiddenInGame(bUsingEditorMesh || bHiddenInGame);
MeshComponent->SetVisibility(!bUsingEditorMesh || OuterSplines->bShowSplineEditorMesh);
MeshSettings.Add(FMeshSettings(T, MeshEntry));
iMesh++;
T += MeshT;
}
// Add terminating key
MeshSettings.Add(FMeshSettings(T, nullptr));
// Second pass:
// Rescale components to fit a whole number to the spline, set up final parameters
const float Rescale = 1.0f / T;
for (int32 i = 0; i < MeshComponents.Num(); i++)
{
USplineMeshComponent* const MeshComponent = MeshComponents[i];
const UStaticMesh* const Mesh = MeshComponent->GetStaticMesh();
const FBoxSphereBounds MeshBounds = Mesh->GetBounds();
const float RescaledT = MeshSettings[i].T * Rescale;
const FLandscapeSplineMeshEntry* MeshEntry = MeshSettings[i].MeshEntry;
const ESplineMeshAxis::Type SideAxis = CrossAxis(MeshEntry->ForwardAxis, MeshEntry->UpAxis);
const float TEnd = MeshSettings[i + 1].T * Rescale;
const float CosInterp = 0.5f - 0.5f * FMath::Cos(RescaledT * PI);
const float Width = FMath::Lerp(StartWidth, EndWidth, CosInterp);
const bool bDoOrientationRoll = (MeshEntry->ForwardAxis == ESplineMeshAxis::X && MeshEntry->UpAxis == ESplineMeshAxis::Y) ||
(MeshEntry->ForwardAxis == ESplineMeshAxis::Y && MeshEntry->UpAxis == ESplineMeshAxis::Z) ||
(MeshEntry->ForwardAxis == ESplineMeshAxis::Z && MeshEntry->UpAxis == ESplineMeshAxis::X);
const float Roll = FMath::Lerp(StartRoll, EndRoll, CosInterp) + (bDoOrientationRoll ? -HALF_PI : 0);
const float MeshOffset = FMath::Lerp(StartMeshOffset, EndMeshOffset, CosInterp);
FVector Scale = MeshEntry->Scale;
if (MeshEntry->bScaleToWidth)
{
float ScaleFactor = Width / USplineMeshComponent::GetAxisValueRef(MeshBounds.BoxExtent, SideAxis);
if (MeshEntry->bNoZScaling)
{
Scale.X *= ScaleFactor;
Scale.Y *= ScaleFactor;
}
else
{
Scale *= ScaleFactor;
}
}
FVector2D Offset = MeshEntry->CenterAdjust;
if (MeshEntry->bCenterH)
{
if (bDoOrientationRoll)
{
Offset.Y -= USplineMeshComponent::GetAxisValueRef(MeshBounds.Origin, SideAxis);
}
else
{
Offset.X -= USplineMeshComponent::GetAxisValueRef(MeshBounds.Origin, SideAxis);
}
}
FVector2D Scale2D;
switch (MeshEntry->ForwardAxis)
{
case ESplineMeshAxis::X:
Scale2D = FVector2D(Scale.Y, Scale.Z);
break;
case ESplineMeshAxis::Y:
Scale2D = FVector2D(Scale.Z, Scale.X);
break;
case ESplineMeshAxis::Z:
Scale2D = FVector2D(Scale.X, Scale.Y);
break;
default:
check(0);
break;
}
Offset *= Scale2D;
Offset.Y += MeshOffset;
Offset = Offset.GetRotated(-Roll);
MeshComponent->SplineParams.StartPos = SplineInfo.Eval(RescaledT, FVector::ZeroVector);
MeshComponent->SplineParams.StartTangent = SplineInfo.EvalDerivative(RescaledT, FVector::ZeroVector) * (TEnd - RescaledT);
MeshComponent->SplineParams.StartScale = Scale2D;
MeshComponent->SplineParams.StartRoll = Roll;
MeshComponent->SplineParams.StartOffset = Offset;
const float CosInterpEnd = 0.5f - 0.5f * FMath::Cos(TEnd * PI);
const float WidthEnd = FMath::Lerp(StartWidth, EndWidth, CosInterpEnd);
const float RollEnd = FMath::Lerp(StartRoll, EndRoll, CosInterpEnd) + (bDoOrientationRoll ? -HALF_PI : 0);
const float MeshOffsetEnd = FMath::Lerp(StartMeshOffset, EndMeshOffset, CosInterpEnd);
FVector ScaleEnd = MeshEntry->Scale;
if (MeshEntry->bScaleToWidth)
{
float ScaleFactor = WidthEnd / USplineMeshComponent::GetAxisValueRef(MeshBounds.BoxExtent, SideAxis);
if (MeshEntry->bNoZScaling)
{
ScaleEnd.X *= ScaleFactor;
ScaleEnd.Y *= ScaleFactor;
}
else
{
ScaleEnd *= ScaleFactor;
}
}
FVector2D OffsetEnd = MeshEntry->CenterAdjust;
if (MeshEntry->bCenterH)
{
if (bDoOrientationRoll)
{
OffsetEnd.Y -= USplineMeshComponent::GetAxisValueRef(MeshBounds.Origin, SideAxis);
}
else
{
OffsetEnd.X -= USplineMeshComponent::GetAxisValueRef(MeshBounds.Origin, SideAxis);
}
}
FVector2D Scale2DEnd;
switch (MeshEntry->ForwardAxis)
{
case ESplineMeshAxis::X:
Scale2DEnd = FVector2D(ScaleEnd.Y, ScaleEnd.Z);
break;
case ESplineMeshAxis::Y:
Scale2DEnd = FVector2D(ScaleEnd.Z, ScaleEnd.X);
break;
case ESplineMeshAxis::Z:
Scale2DEnd = FVector2D(ScaleEnd.X, ScaleEnd.Y);
break;
default:
check(0);
break;
}
OffsetEnd *= Scale2DEnd;
OffsetEnd.Y += MeshOffsetEnd;
OffsetEnd = OffsetEnd.GetRotated(-RollEnd);
MeshComponent->SplineParams.EndPos = SplineInfo.Eval(TEnd, FVector::ZeroVector);
MeshComponent->SplineParams.EndTangent = SplineInfo.EvalDerivative(TEnd, FVector::ZeroVector) * (TEnd - RescaledT);
MeshComponent->SplineParams.EndScale = Scale2DEnd;
MeshComponent->SplineParams.EndRoll = RollEnd;
MeshComponent->SplineParams.EndOffset = OffsetEnd;
MeshComponent->SplineUpDir = FVector(0,0,1); // Up, to be consistent between joined meshes. We rotate it to horizontal using roll if using Z Forward X Up or X Forward Y Up
MeshComponent->ForwardAxis = MeshEntry->ForwardAxis;
auto* const MeshComponentOuterSplines = MeshComponent->GetAttachParent();
if (MeshComponentOuterSplines != nullptr && MeshComponentOuterSplines != OuterSplines)
{
const FTransform RelativeTransform = OuterSplines->GetComponentTransform().GetRelativeTransform(MeshComponentOuterSplines->GetComponentTransform());
MeshComponent->SplineParams.StartPos = RelativeTransform.TransformPosition(MeshComponent->SplineParams.StartPos);
MeshComponent->SplineParams.EndPos = RelativeTransform.TransformPosition(MeshComponent->SplineParams.EndPos);
}
if (USplineMeshComponent::GetAxisValueRef(MeshEntry->Scale, MeshEntry->ForwardAxis) < 0)
{
Swap(MeshComponent->SplineParams.StartPos, MeshComponent->SplineParams.EndPos);
Swap(MeshComponent->SplineParams.StartTangent, MeshComponent->SplineParams.EndTangent);
Swap(MeshComponent->SplineParams.StartScale, MeshComponent->SplineParams.EndScale);
Swap(MeshComponent->SplineParams.StartRoll, MeshComponent->SplineParams.EndRoll);
Swap(MeshComponent->SplineParams.StartOffset, MeshComponent->SplineParams.EndOffset);
MeshComponent->SplineParams.StartTangent = -MeshComponent->SplineParams.StartTangent;
MeshComponent->SplineParams.EndTangent = -MeshComponent->SplineParams.EndTangent;
MeshComponent->SplineParams.StartScale.X = -MeshComponent->SplineParams.StartScale.X;
MeshComponent->SplineParams.EndScale.X = -MeshComponent->SplineParams.EndScale.X;
MeshComponent->SplineParams.StartRoll = -MeshComponent->SplineParams.StartRoll;
MeshComponent->SplineParams.EndRoll = -MeshComponent->SplineParams.EndRoll;
MeshComponent->SplineParams.StartOffset.X = -MeshComponent->SplineParams.StartOffset.X;
MeshComponent->SplineParams.EndOffset.X = -MeshComponent->SplineParams.EndOffset.X;
}
// Set Mesh component's location to half way between the start and end points. Improves the bounds and allows LDMaxDrawDistance to work
MeshComponent->SetRelativeLocation_Direct((MeshComponent->SplineParams.StartPos + MeshComponent->SplineParams.EndPos) / 2);
MeshComponent->SplineParams.StartPos -= MeshComponent->GetRelativeLocation();
MeshComponent->SplineParams.EndPos -= MeshComponent->GetRelativeLocation();
if (MeshComponent->LDMaxDrawDistance != LDMaxDrawDistance)
{
MeshComponent->LDMaxDrawDistance = LDMaxDrawDistance;
MeshComponent->CachedMaxDrawDistance = 0;
}
MeshComponent->TranslucencySortPriority = TranslucencySortPriority;
MeshComponent->RuntimeVirtualTextures = RuntimeVirtualTextures;
MeshComponent->VirtualTextureLodBias = static_cast<int8>(VirtualTextureLodBias);
MeshComponent->VirtualTextureCullMips = static_cast<int8>(VirtualTextureCullMips);
MeshComponent->VirtualTextureMainPassMaxDrawDistance = VirtualTextureMainPassMaxDrawDistance;
MeshComponent->VirtualTextureRenderPassType = VirtualTextureRenderPassType;
MeshComponent->SetRenderCustomDepth(bRenderCustomDepth);
MeshComponent->SetCustomDepthStencilWriteMask(CustomDepthStencilWriteMask);
MeshComponent->SetCustomDepthStencilValue(CustomDepthStencilValue);
MeshComponent->SetCastShadow(bCastShadow);
MeshComponent->InvalidateLightingCache();
#if WITH_EDITOR
if (!bUsingEditorMesh)
{
MeshComponent->BodyInstance = BodyInstance;
}
else
{
MeshComponent->BodyInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
}
// If we won't ever enable collisions, ensure we don't even cook or load any collision data on this mesh :
MeshComponent->SetbNeverNeedsCookedCollisionData(bUsingEditorMesh);
if (bUpdateCollision)
{
MeshComponent->RecreateCollision();
}
else
{
if (MeshComponent->BodySetup)
{
MeshComponent->BodySetup->InvalidatePhysicsData();
MeshComponent->BodySetup->AggGeom.EmptyElements();
}
}
#endif
}
// Finally, register components if the world is initialized
UWorld* World = GetWorld();
if (World && World->bIsWorldInitialized)
{
for (USplineMeshComponent* MeshComponent : MeshComponents)
{
MeshComponent->RegisterComponent();
}
}
}
// Clean up unused components
for (TObjectPtr<USplineMeshComponent>& LocalMeshComponent : OldLocalMeshComponents)
{
LocalMeshComponent->DestroyComponent();
}
OldLocalMeshComponents.Empty();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
for (USplineMeshComponent* MeshComponent : ForeignMeshComponentsPair.Value)
{
MeshComponent->DestroyComponent();
}
}
ForeignMeshComponentsMap.Empty();
}
void ULandscapeSplineSegment::UpdateSplineMeshSelectionState()
{
for (auto& LocalMeshComponent : LocalMeshComponents)
{
LocalMeshComponent->bSelected = bSelected;
LocalMeshComponent->PushSelectionToProxy();
}
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
for (auto* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
ForeignMeshComponent->bSelected = bSelected;
ForeignMeshComponent->PushSelectionToProxy();
}
}
}
void ULandscapeSplineSegment::UpdateSplineEditorMesh()
{
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
for (auto& LocalMeshComponent : LocalMeshComponents)
{
if (OuterSplines->IsUsingEditorMesh(LocalMeshComponent))
{
LocalMeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
}
}
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
for (auto* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
if (OuterSplines->IsUsingEditorMesh(ForeignMeshComponent))
{
ForeignMeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
}
}
}
}
void ULandscapeSplineSegment::DeleteSplinePoints()
{
Modify();
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
SplineInfo.Reset();
Points.Reset();
Bounds = FBox(ForceInit);
OuterSplines->MarkRenderStateDirty();
// Destroy mesh components
if (LocalMeshComponents.Num() > 0)
{
OuterSplines->Modify();
for (auto& LocalMeshComponent : LocalMeshComponents)
{
checkSlow(OuterSplines->MeshComponentLocalOwnersMap.FindRef(LocalMeshComponent) == this);
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(LocalMeshComponent) == 1);
LocalMeshComponent->Modify();
LocalMeshComponent->DestroyComponent();
}
LocalMeshComponents.Empty();
}
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
{
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
MeshComponentOuterSplines->Modify();
MeshComponentOuterSplines->GetOwner()->Modify();
for (auto* ForeignMeshComponent : ForeignMeshComponentsPair.Value)
{
ForeignMeshComponent->Modify();
MeshComponentOuterSplines->RemoveForeignMeshComponent(this, ForeignMeshComponent);
ForeignMeshComponent->DestroyComponent();
}
}
ModificationKey.Invalidate();
ForeignWorlds.Empty();
}
FName ULandscapeSplineSegment::GetCollisionProfileName() const
{
#if WITH_EDITORONLY_DATA
return BodyInstance.GetCollisionProfileName();
#else
return UCollisionProfile::BlockAll_ProfileName;
#endif
}
#endif
void ULandscapeSplineSegment::FindNearest( const FVector& InLocation, float& t, FVector& OutLocation, FVector& OutTangent )
{
float TempOutDistanceSq;
t = SplineInfo.FindNearest(InLocation, TempOutDistanceSq);
OutLocation = SplineInfo.Eval(t, FVector::ZeroVector);
OutTangent = SplineInfo.EvalDerivative(t, FVector::ZeroVector);
}
FFloatInterval ULandscapeSplineSegment::GetKeyInterval() const
{
return SplineInfo.GetKeyInterval();
}
FVector ULandscapeSplineSegment::Evaluate(const float Key, const FVector& Default) const
{
return SplineInfo.Eval(Key, Default);
}
#if WITH_EDITOR
void ULandscapeSplineSegment::PostEditUndo()
{
bHackIsUndoingSplines = true;
Super::PostEditUndo();
bHackIsUndoingSplines = false;
// Update mesh components to make sure the selection state stays in sync
UpdateSplineMeshSelectionState();
ULandscapeSplinesComponent* SplineComponent = GetOuterULandscapeSplinesComponent();
SplineComponent->MarkRenderStateDirty();
SplineComponent->RequestSplineLayerUpdate();
}
void ULandscapeSplineSegment::PostDuplicate(bool bDuplicateForPIE)
{
if (!bDuplicateForPIE)
{
// if we get duplicated but our local meshes don't, then clear our reference to the meshes - they're not ours
if (LocalMeshComponents.Num() > 0)
{
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
// we assume all meshes are duplicated or none are, to avoid testing every one
if (LocalMeshComponents[0]->GetOuter() != OuterSplines->GetOwner())
{
LocalMeshComponents.Empty();
}
// If LocalMeshComponents are still valid make sure they are added to the transient map that normally gets populated PostLoad
for (auto& LocalMeshComponent : LocalMeshComponents)
{
OuterSplines->MeshComponentLocalOwnersMap.Add(LocalMeshComponent, this);
}
}
UpdateSplinePoints();
}
Super::PostDuplicate(bDuplicateForPIE);
}
void ULandscapeSplineSegment::PostEditImport()
{
Super::PostEditImport();
GetOuterULandscapeSplinesComponent()->Segments.AddUnique(this);
if (Connections[0].ControlPoint != nullptr)
{
Connections[0].ControlPoint->ConnectedSegments.AddUnique(FLandscapeSplineConnection(this, 0));
Connections[1].ControlPoint->ConnectedSegments.AddUnique(FLandscapeSplineConnection(this, 1));
}
}
void ULandscapeSplineSegment::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
// Flipping the tangent is only allowed if not using a socket
if (Connections[0].SocketName != NAME_None)
{
Connections[0].TangentLen = FMath::Abs(Connections[0].TangentLen);
}
if (Connections[1].SocketName != NAME_None)
{
Connections[1].TangentLen = FMath::Abs(Connections[1].TangentLen);
}
// Don't update splines when undoing, not only is it unnecessary and expensive,
// it also causes failed asserts in debug builds when trying to register components
// (because the actor hasn't reset its OwnedComponents array yet)
if (!bHackIsUndoingSplines)
{
const bool bUpdateCollision = PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive;
UpdateSplinePoints(bUpdateCollision);
}
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet)
{
GetOuterULandscapeSplinesComponent()->RequestSplineLayerUpdate();
}
}
void ALandscapeProxy::CreateSplineComponent()
{
CreateSplineComponent(FVector(1.0f) / GetRootComponent()->GetRelativeScale3D());
}
void ALandscapeProxy::CreateSplineComponent(const FVector& Scale3D)
{
check(SplineComponent == nullptr);
Modify();
SplineComponent = NewObject<ULandscapeSplinesComponent>(this, NAME_None, RF_Transactional);
SplineComponent->SetRelativeScale3D_Direct(Scale3D);
SplineComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
SplineComponent->ShowSplineEditorMesh(true);
if (RootComponent && RootComponent->IsRegistered())
{
SplineComponent->RegisterComponent();
}
}
void ULandscapeInfo::MoveControlPoint(ULandscapeSplineControlPoint* InControlPoint, TScriptInterface<ILandscapeSplineInterface> From, TScriptInterface<ILandscapeSplineInterface> To)
{
ULandscapeSplinesComponent* ToSplineComponent = To->GetSplinesComponent();
ULandscapeSplinesComponent* FromSplineComponent = From->GetSplinesComponent();
if (ToSplineComponent == nullptr)
{
To->CreateSplineComponent(FromSplineComponent->GetRelativeScale3D());
ToSplineComponent = To->GetSplinesComponent();
check(ToSplineComponent);
}
if (InControlPoint->GetOuterULandscapeSplinesComponent() == ToSplineComponent)
{
return;
}
check(InControlPoint->GetOuterULandscapeSplinesComponent() == FromSplineComponent);
ToSplineComponent->Modify();
const FTransform FromComponentTransform = FromSplineComponent->GetComponentTransform(); // (From-->World)
const FTransform ToComponentTransform = ToSplineComponent->GetComponentTransform(); // (To-->World)
const FTransform OldToNewTransform = // (From-->To) == (From-->World) * (World-->To) == From * Inverse(To)
FromComponentTransform.GetRelativeTransform(ToComponentTransform);
// Delete all Mesh Components associated with the ControlPoint. (Will get recreated in UpdateSplinePoints)
if (InControlPoint->LocalMeshComponent)
{
InControlPoint->LocalMeshComponent->Modify();
InControlPoint->LocalMeshComponent->UnregisterComponent();
InControlPoint->LocalMeshComponent->DestroyComponent();
FromSplineComponent->Modify();
FromSplineComponent->MeshComponentLocalOwnersMap.Remove(InControlPoint->LocalMeshComponent);
InControlPoint->LocalMeshComponent = nullptr;
}
TMap<ULandscapeSplinesComponent*, UControlPointMeshComponent*> ForeignMeshComponents = InControlPoint->GetForeignMeshComponents();
for (auto Pair : ForeignMeshComponents)
{
Pair.Key->RemoveForeignMeshComponent(InControlPoint, Pair.Value);
Pair.Value->Modify();
Pair.Value->UnregisterComponent();
Pair.Value->DestroyComponent();
}
// Move control point to new level
FromSplineComponent->ControlPoints.Remove(InControlPoint);
InControlPoint->Rename(nullptr, ToSplineComponent);
ToSplineComponent->ControlPoints.Add(InControlPoint);
InControlPoint->Location = OldToNewTransform.TransformPosition(InControlPoint->Location);
InControlPoint->Rotation = FRotator(OldToNewTransform.TransformRotation(InControlPoint->Rotation.Quaternion()));
const bool bUpdateCollision = true; // default value
const bool bUpdateSegments = false; // done in next loop
const bool bUpdateMeshLevel = false; // no need because mesh have been deleted
InControlPoint->UpdateSplinePoints(bUpdateCollision, bUpdateSegments, bUpdateMeshLevel);
// Continue
for (const FLandscapeSplineConnection& Connection : InControlPoint->ConnectedSegments)
{
// Continue both directions anyways it will early out for ControlPoints that already have moved
MoveSegment(Connection.Segment, From, To);
}
}
void ULandscapeInfo::MoveSegment(ULandscapeSplineSegment* InSegment, TScriptInterface<ILandscapeSplineInterface> From, TScriptInterface<ILandscapeSplineInterface> To)
{
AActor* ToActor = CastChecked<AActor>(To.GetObject());
ToActor->Modify();
AActor* FromActor = CastChecked<AActor>(From.GetObject());
ULandscapeSplinesComponent* ToSplineComponent = To->GetSplinesComponent();
ULandscapeSplinesComponent* FromSplineComponent = From->GetSplinesComponent();
if (ToSplineComponent == nullptr)
{
To->CreateSplineComponent(FromSplineComponent->GetRelativeScale3D());
ToSplineComponent = To->GetSplinesComponent();
check(ToSplineComponent);
}
if (InSegment->GetOuterULandscapeSplinesComponent() == ToSplineComponent)
{
return;
}
check(InSegment->GetOuterULandscapeSplinesComponent() == FromSplineComponent);
ToSplineComponent->Modify();
// Delete all Mesh Components associated with the Segment. (Will get recreated in UpdateSplinePoints)
for (auto& MeshComponent : InSegment->LocalMeshComponents)
{
MeshComponent->Modify();
MeshComponent->UnregisterComponent();
MeshComponent->DestroyComponent();
FromActor->Modify();
FromSplineComponent->MeshComponentLocalOwnersMap.Remove(MeshComponent);
}
InSegment->LocalMeshComponents.Empty();
TMap<ULandscapeSplinesComponent*, TArray<USplineMeshComponent*>> ForeignMeshComponents = InSegment->GetForeignMeshComponents();
for (auto Pair : ForeignMeshComponents)
{
Pair.Key->RemoveAllForeignMeshComponents(InSegment);
for (auto MeshComponent : Pair.Value)
{
MeshComponent->Modify();
MeshComponent->UnregisterComponent();
MeshComponent->DestroyComponent();
}
}
// Move segment to new level
FromSplineComponent->Segments.Remove(InSegment);
InSegment->Rename(nullptr, ToSplineComponent);
ToSplineComponent->Segments.Add(InSegment);
// Continue both directions anyways it will early out for ControlPoints that already have moved
MoveControlPoint(InSegment->Connections[0].ControlPoint, From, To);
MoveControlPoint(InSegment->Connections[1].ControlPoint, From, To);
const bool bUpdateCollision = true; // default value
const bool bUpdateMeshLevel = false; // no need because mesh have been deleted
InSegment->UpdateSplinePoints(bUpdateCollision, bUpdateMeshLevel);
}
void ULandscapeInfo::MoveSplineToLevel(ULandscapeSplineControlPoint* InControlPoint, ULevel* TargetLevel)
{
ALandscapeProxy* FromProxy = InControlPoint->GetTypedOuter<ALandscapeProxy>();
if (FromProxy->GetLevel() == TargetLevel)
{
return;
}
ALandscapeProxy* ToProxy = GetLandscapeProxyForLevel(TargetLevel);
if (!ToProxy)
{
return;
}
MoveSplineToProxy(InControlPoint, ToProxy);
}
void ULandscapeInfo::MoveSplineToProxy(ULandscapeSplineControlPoint* InControlPoint, ALandscapeProxy* InLandscapeProxy)
{
MoveSpline(InControlPoint, InLandscapeProxy);
}
void ULandscapeInfo::MoveSpline(ULandscapeSplineControlPoint* InControlPoint, TScriptInterface<ILandscapeSplineInterface> InNewOwner)
{
bool bUpdateBounds = false;
ULandscapeSplinesComponent* FromSplineComponent = InControlPoint->GetOuterULandscapeSplinesComponent();
TScriptInterface<ILandscapeSplineInterface> From(FromSplineComponent->GetOwner());
if (From == InNewOwner)
{
return;
}
bUpdateBounds = true;
From.GetObject()->Modify();
FromSplineComponent->Modify();
FromSplineComponent->MarkRenderStateDirty();
MoveControlPoint(InControlPoint, From, InNewOwner);
FromSplineComponent->UpdateBounds();
if (bUpdateBounds)
{
InNewOwner->GetSplinesComponent()->UpdateBounds();
}
}
void ULandscapeInfo::MoveSplinesToLevel(ULandscapeSplinesComponent* InSplineComponent, ULevel * TargetLevel)
{
ALandscapeProxy* FromProxy = InSplineComponent->GetTypedOuter<ALandscapeProxy>();
if (FromProxy->GetLevel() == TargetLevel)
{
return;
}
ALandscapeProxy* ToProxy = GetLandscapeProxyForLevel(TargetLevel);
if (!ToProxy)
{
return;
}
MoveSplines(InSplineComponent, ToProxy);
}
/** Moves all Splines to target Proxy. Creates ULandscapeSplineComponent if needed */
void ULandscapeInfo::MoveSplinesToProxy(ULandscapeSplinesComponent* InSplineComponent, ALandscapeProxy* InLandscapeProxy)
{
MoveSplines(InSplineComponent, InLandscapeProxy);
check(InSplineComponent->ControlPoints.Num() == 0 && InSplineComponent->Segments.Num() == 0);
}
/** Moves all Splines to target Spline owner */
void ULandscapeInfo::MoveSplines(ULandscapeSplinesComponent* InSplineComponent, TScriptInterface<ILandscapeSplineInterface> InNewOwner)
{
check(InSplineComponent != InNewOwner->GetSplinesComponent());
InSplineComponent->ForEachControlPoint([this, InNewOwner](ULandscapeSplineControlPoint* ControlPoint)
{
// Even if ControlPoints are part of same connected spline they will be skipped if already moved
MoveSpline(ControlPoint, InNewOwner);
});
}
#endif // WITH_EDITOR
#undef LOCTEXT_NAMESPACE