You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2497 lines
84 KiB
C++
2497 lines
84 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
LandscapeSpline.cpp
|
|
=============================================================================*/
|
|
|
|
#include "Landscape.h"
|
|
#include "Components/SplineMeshComponent.h"
|
|
#include "ShaderParameterUtils.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "LandscapeSplineProxies.h"
|
|
#include "LandscapeSplinesComponent.h"
|
|
#include "LandscapeSplineControlPoint.h"
|
|
#include "LandscapeSplineSegment.h"
|
|
#include "ControlPointMeshComponent.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "Engine/Engine.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/StaticMeshSocket.h"
|
|
#if WITH_EDITOR
|
|
#include "LandscapeSplineRaster.h"
|
|
#include "MessageLog.h"
|
|
#include "UObjectToken.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"
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// LANDSCAPE SPLINES SCENE PROXY
|
|
|
|
/** Represents a ULandscapeSplinesComponent to the scene manager. */
|
|
#if WITH_EDITOR
|
|
class FLandscapeSplinesSceneProxy : public FPrimitiveSceneProxy
|
|
{
|
|
private:
|
|
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;
|
|
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 / 2 : 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& LocalToWorld = GetLocalToWorld();
|
|
|
|
const FLinearColor SelectedSplineColor = GEngine->GetSelectedMaterialColor();
|
|
const FLinearColor SelectedControlPointSpriteColor = FLinearColor::White + (GEngine->GetSelectedMaterialColor() * GEngine->SelectionHighlightIntensityBillboards * 10);
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if (VisibilityMap & (1 << ViewIndex))
|
|
{
|
|
const FSceneView* View = Views[ViewIndex];
|
|
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
|
|
|
|
for (const FSegmentProxy& Segment : Segments)
|
|
{
|
|
const FLinearColor SegmentColor = Segment.bSelected ? SelectedSplineColor : SplineColor;
|
|
|
|
FLandscapeSplineInterpPoint OldPoint = Segment.Points[0];
|
|
OldPoint.Center = LocalToWorld.TransformPosition(OldPoint.Center);
|
|
OldPoint.Left = LocalToWorld.TransformPosition(OldPoint.Left);
|
|
OldPoint.Right = LocalToWorld.TransformPosition(OldPoint.Right);
|
|
OldPoint.FalloffLeft = LocalToWorld.TransformPosition(OldPoint.FalloffLeft);
|
|
OldPoint.FalloffRight = LocalToWorld.TransformPosition(OldPoint.FalloffRight);
|
|
for (int32 i = 1; i < Segment.Points.Num(); i++)
|
|
{
|
|
FLandscapeSplineInterpPoint NewPoint = Segment.Points[i];
|
|
NewPoint.Center = LocalToWorld.TransformPosition(NewPoint.Center);
|
|
NewPoint.Left = LocalToWorld.TransformPosition(NewPoint.Left);
|
|
NewPoint.Right = LocalToWorld.TransformPosition(NewPoint.Right);
|
|
NewPoint.FalloffLeft = LocalToWorld.TransformPosition(NewPoint.FalloffLeft);
|
|
NewPoint.FalloffRight = LocalToWorld.TransformPosition(NewPoint.FalloffRight);
|
|
|
|
// Draw lines from the last keypoint.
|
|
PDI->SetHitProxy(Segment.HitProxy);
|
|
|
|
// center line
|
|
PDI->DrawLine(OldPoint.Center, NewPoint.Center, SegmentColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
|
|
// draw sides
|
|
PDI->DrawLine(OldPoint.Left, NewPoint.Left, SegmentColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
PDI->DrawLine(OldPoint.Right, NewPoint.Right, SegmentColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
// draw falloff sides
|
|
if (bDrawFalloff)
|
|
{
|
|
DrawDashedLine(PDI, OldPoint.FalloffLeft, NewPoint.FalloffLeft, SegmentColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
DrawDashedLine(PDI, OldPoint.FalloffRight, NewPoint.FalloffRight, SegmentColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
}
|
|
|
|
OldPoint = NewPoint;
|
|
}
|
|
}
|
|
|
|
for (const FControlPointProxy& ControlPoint : ControlPoints)
|
|
{
|
|
const FVector ControlPointLocation = LocalToWorld.TransformPosition(ControlPoint.Location);
|
|
|
|
// Draw Sprite
|
|
if (bDrawControlPointSprite)
|
|
{
|
|
const float ControlPointSpriteScale = LocalToWorld.GetScaleVector().X * ControlPoint.SpriteScale;
|
|
const FVector ControlPointSpriteLocation = ControlPointLocation + FVector(0, 0, ControlPointSpriteScale * 0.75f);
|
|
const FLinearColor ControlPointSpriteColor = ControlPoint.bSelected ? SelectedControlPointSpriteColor : FLinearColor::White;
|
|
|
|
PDI->SetHitProxy(ControlPoint.HitProxy);
|
|
|
|
PDI->DrawSprite(
|
|
ControlPointSpriteLocation,
|
|
ControlPointSpriteScale,
|
|
ControlPointSpriteScale,
|
|
ControlPointSprite->Resource,
|
|
ControlPointSpriteColor,
|
|
GetDepthPriorityGroup(View),
|
|
0, ControlPointSprite->Resource->GetSizeX(),
|
|
0, ControlPointSprite->Resource->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 = LocalToWorld.TransformPosition(NewPoint.Center);
|
|
NewPoint.Left = LocalToWorld.TransformPosition(NewPoint.Left);
|
|
NewPoint.Right = LocalToWorld.TransformPosition(NewPoint.Right);
|
|
NewPoint.FalloffLeft = LocalToWorld.TransformPosition(NewPoint.FalloffLeft);
|
|
NewPoint.FalloffRight = LocalToWorld.TransformPosition(NewPoint.FalloffRight);
|
|
|
|
// draw end for spline connection
|
|
PDI->DrawPoint(NewPoint.Center, ControlPointColor, 6.0f, GetDepthPriorityGroup(View));
|
|
PDI->DrawLine(NewPoint.Left, NewPoint.Center, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
PDI->DrawLine(NewPoint.Right, NewPoint.Center, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
if (bDrawFalloff)
|
|
{
|
|
DrawDashedLine(PDI, NewPoint.FalloffLeft, NewPoint.Left, ControlPointColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
DrawDashedLine(PDI, NewPoint.FalloffRight, NewPoint.Right, ControlPointColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
}
|
|
}
|
|
else if (ControlPoint.Points.Num() >= 2)
|
|
{
|
|
FLandscapeSplineInterpPoint OldPoint = ControlPoint.Points.Last();
|
|
//OldPoint.Left = LocalToWorld.TransformPosition(OldPoint.Left);
|
|
OldPoint.Right = LocalToWorld.TransformPosition(OldPoint.Right);
|
|
//OldPoint.FalloffLeft = LocalToWorld.TransformPosition(OldPoint.FalloffLeft);
|
|
OldPoint.FalloffRight = LocalToWorld.TransformPosition(OldPoint.FalloffRight);
|
|
|
|
for (const FLandscapeSplineInterpPoint& Point : ControlPoint.Points)
|
|
{
|
|
FLandscapeSplineInterpPoint NewPoint = Point;
|
|
NewPoint.Center = LocalToWorld.TransformPosition(NewPoint.Center);
|
|
NewPoint.Left = LocalToWorld.TransformPosition(NewPoint.Left);
|
|
NewPoint.Right = LocalToWorld.TransformPosition(NewPoint.Right);
|
|
NewPoint.FalloffLeft = LocalToWorld.TransformPosition(NewPoint.FalloffLeft);
|
|
NewPoint.FalloffRight = LocalToWorld.TransformPosition(NewPoint.FalloffRight);
|
|
|
|
PDI->SetHitProxy(ControlPoint.HitProxy);
|
|
|
|
// center line
|
|
PDI->DrawLine(ControlPointLocation, NewPoint.Center, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
|
|
// draw sides
|
|
PDI->DrawLine(OldPoint.Right, NewPoint.Left, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
// draw falloff sides
|
|
if (bDrawFalloff)
|
|
{
|
|
DrawDashedLine(PDI, OldPoint.FalloffRight, NewPoint.FalloffLeft, ControlPointColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
}
|
|
|
|
// draw end for spline connection
|
|
PDI->DrawPoint(NewPoint.Center, ControlPointColor, 6.0f, GetDepthPriorityGroup(View));
|
|
PDI->DrawLine(NewPoint.Left, NewPoint.Center, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
PDI->DrawLine(NewPoint.Right, NewPoint.Center, ControlPointColor, GetDepthPriorityGroup(View), 0.0f, DepthBias);
|
|
if (bDrawFalloff)
|
|
{
|
|
DrawDashedLine(PDI, NewPoint.FalloffLeft, NewPoint.Left, ControlPointColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
DrawDashedLine(PDI, NewPoint.FalloffRight, NewPoint.Right, ControlPointColor, 100, GetDepthPriorityGroup(View), DepthBias);
|
|
}
|
|
|
|
//OldPoint = NewPoint;
|
|
OldPoint.Right = NewPoint.Right;
|
|
OldPoint.FalloffRight = NewPoint.FalloffRight;
|
|
}
|
|
}
|
|
}
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) 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
|
|
{
|
|
uint32 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 AllocatedSize;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// 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;
|
|
ConstructorHelpers::FObjectFinder<UStaticMesh> SplineEditorMesh;
|
|
FConstructorStatics()
|
|
: SpriteTexture(TEXT("/Engine/EditorResources/S_Terrain.S_Terrain"))
|
|
, SplineEditorMesh(TEXT("/Engine/EditorLandscapeResources/SplineEditorMesh"))
|
|
{
|
|
}
|
|
};
|
|
static FConstructorStatics ConstructorStatics;
|
|
|
|
ControlPointSprite = ConstructorStatics.SpriteTexture.Object;
|
|
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
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::CheckSplinesValid()
|
|
{
|
|
#if DO_CHECK
|
|
// This shouldn't happen, but it has somehow (TTP #334549) so we have to fix it
|
|
ensure(!ControlPoints.Contains(nullptr));
|
|
ensure(!Segments.Contains(nullptr));
|
|
|
|
// Remove all null control points/segments
|
|
ControlPoints.Remove(nullptr);
|
|
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)
|
|
{
|
|
ensure(Connection.Segment->GetOuterULandscapeSplinesComponent() == this);
|
|
}
|
|
}
|
|
for (ULandscapeSplineSegment* Segment : Segments)
|
|
{
|
|
ensure(Segment->GetOuterULandscapeSplinesComponent() == this);
|
|
for (const FLandscapeSplineSegmentConnection& Connection : Segment->Connections)
|
|
{
|
|
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(0);
|
|
|
|
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 (AttachParent)
|
|
{
|
|
NewBounds = FBoxSphereBounds(AttachParent->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;
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar, FForeignControlPointData& Value)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!Ar.IsFilterEditorOnly())
|
|
{
|
|
Ar << Value.ModificationKey << Value.MeshComponent;
|
|
}
|
|
#endif
|
|
return Ar;
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar, FForeignSplineSegmentData& Value)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!Ar.IsFilterEditorOnly())
|
|
{
|
|
Ar << Value.ModificationKey << Value.MeshComponents;
|
|
}
|
|
#endif
|
|
return Ar;
|
|
}
|
|
|
|
FArchive& operator<<(FArchive& Ar, FForeignWorldSplineData& Value)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!Ar.IsFilterEditorOnly())
|
|
{
|
|
Ar << Value.ForeignSplineSegmentDataMap;
|
|
}
|
|
#endif
|
|
return Ar;
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (Ar.UE4Ver() >= VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES &&
|
|
!Ar.IsFilterEditorOnly())
|
|
{
|
|
Ar << ForeignWorldSplineDataMap;
|
|
}
|
|
|
|
if (!Ar.IsPersistent())
|
|
{
|
|
Ar << MeshComponentLocalOwnersMap;
|
|
Ar << MeshComponentForeignOwnersMap;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeSplinesComponent::AutoFixMeshComponentErrors(UWorld* OtherWorld)
|
|
{
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
|
|
TAssetPtr<UWorld> OtherWorldAssetPtr = 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->ForeignControlPointDataMap.Find(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->ForeignSplineSegmentDataMap.Find(Segment) : nullptr;
|
|
if (!ForeignSplineSegmentData || ForeignSplineSegmentData->ModificationKey != Segment->GetModificationKey())
|
|
{
|
|
Segment->UpdateSplinePoints(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (StreamingSplinesComponent)
|
|
{
|
|
StreamingSplinesComponent->DestroyOrphanedForeignMeshComponents(ThisOuterWorld);
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::CheckForErrors()
|
|
{
|
|
Super::CheckForErrors();
|
|
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
|
|
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->ForeignControlPointDataMap.Find(ControlPoint) : nullptr;
|
|
if (!ForeignControlPointData || ForeignControlPointData->ModificationKey != ControlPoint->GetModificationKey())
|
|
{
|
|
OutdatedWorlds.Add(ForeignWorld);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check spline segment meshes
|
|
for (ULandscapeSplineSegment* Segment : Segments)
|
|
{
|
|
for (auto& ForeignWorldAssetPtr : Segment->GetForeignWorlds())
|
|
{
|
|
UWorld* ForeignWorld = ForeignWorldAssetPtr.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->ForeignSplineSegmentDataMap.Find(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& ForeignWorldAssetPtr = ForeignWorldSplineDataPair.Key;
|
|
auto& ForeignWorldSplineData = ForeignWorldSplineDataPair.Value;
|
|
|
|
// World is not loaded
|
|
if (ForeignWorldAssetPtr.IsPending())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UWorld* ForeignWorld = ForeignWorldAssetPtr.Get();
|
|
for (auto& ForeignSplineSegmentDataPair : ForeignWorldSplineData.ForeignSplineSegmentDataMap)
|
|
{
|
|
const ULandscapeSplineSegment* ForeignSplineSegment = ForeignSplineSegmentDataPair.Key.Get();
|
|
auto& ForeignSplineSegmentData = ForeignSplineSegmentDataPair.Value;
|
|
|
|
// 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_OrphanedMeshes", "{MeshMap} contains orphaned meshes due to mismatch with landscape splines in {SplineMap}"), Arguments)))
|
|
->AddToken(FActionToken::Create(LOCTEXT("MapCheck_ActionName_OrphanedMeshes", "Clean up orphaned meshes"), FText(),
|
|
FOnActionTokenExecuted::CreateUObject(this, &ULandscapeSplinesComponent::DestroyOrphanedForeignMeshComponents, ForeignWorld), true));
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ULandscapeSplinesComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
// Build MeshComponentForeignOwnersMap (Component->Spline) from ForeignWorldSplineDataMap (World->Spline->Component)
|
|
for (auto& ForeignWorldSplineDataPair : ForeignWorldSplineDataMap)
|
|
{
|
|
auto& ForeignWorld = ForeignWorldSplineDataPair.Key;
|
|
auto& ForeignWorldSplineData = ForeignWorldSplineDataPair.Value;
|
|
|
|
for (auto& ForeignControlPointDataPair : ForeignWorldSplineData.ForeignControlPointDataMap)
|
|
{
|
|
TLazyObjectPtr<ULandscapeSplineControlPoint> ForeignControlPoint = ForeignControlPointDataPair.Key;
|
|
auto& ForeignControlPointData = ForeignControlPointDataPair.Value;
|
|
MeshComponentForeignOwnersMap.Add(ForeignControlPointData.MeshComponent, ForeignControlPoint);
|
|
}
|
|
|
|
for (auto& ForeignSplineSegmentDataPair : ForeignWorldSplineData.ForeignSplineSegmentDataMap)
|
|
{
|
|
TLazyObjectPtr<ULandscapeSplineSegment> ForeignSplineSegment = ForeignSplineSegmentDataPair.Key;
|
|
auto& ForeignSplineSegmentData = ForeignSplineSegmentDataPair.Value;
|
|
|
|
for (auto* MeshComponent : ForeignSplineSegmentData.MeshComponents)
|
|
{
|
|
MeshComponentForeignOwnersMap.Add(MeshComponent, ForeignSplineSegment);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CheckSplinesValid();
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
CheckForErrors();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#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::ShowSplineEditorMesh(bool bShow)
|
|
{
|
|
bShowSplineEditorMesh = bShow;
|
|
|
|
for (ULandscapeSplineSegment* Segment : Segments)
|
|
{
|
|
Segment->UpdateSplineEditorMesh();
|
|
}
|
|
|
|
MarkRenderStateDirty();
|
|
}
|
|
|
|
bool FForeignWorldSplineData::IsEmpty()
|
|
{
|
|
return ForeignControlPointDataMap.Num() == 0 && ForeignSplineSegmentDataMap.Num() == 0;
|
|
}
|
|
|
|
ULandscapeSplinesComponent* ULandscapeSplinesComponent::GetStreamingSplinesComponentByLocation(const FVector& LocalLocation, bool bCreate /* = true*/)
|
|
{
|
|
ALandscapeProxy* OuterLandscape = Cast<ALandscapeProxy>(GetOwner());
|
|
if (OuterLandscape)
|
|
{
|
|
FVector LandscapeLocalLocation = ComponentToWorld.GetRelativeTransform(OuterLandscape->LandscapeActorToWorld()).TransformPosition(LocalLocation);
|
|
const int32 ComponentIndexX = (LandscapeLocalLocation.X >= 0.0f) ? FMath::FloorToInt(LandscapeLocalLocation.X / OuterLandscape->ComponentSizeQuads) : FMath::CeilToInt(LandscapeLocalLocation.X / OuterLandscape->ComponentSizeQuads);
|
|
const int32 ComponentIndexY = (LandscapeLocalLocation.Y >= 0.0f) ? FMath::FloorToInt(LandscapeLocalLocation.Y / OuterLandscape->ComponentSizeQuads) : FMath::CeilToInt(LandscapeLocalLocation.Y / OuterLandscape->ComponentSizeQuads);
|
|
ULandscapeComponent* LandscapeComponent = OuterLandscape->GetLandscapeInfo()->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
|
|
if (LandscapeComponent)
|
|
{
|
|
ALandscapeProxy* ComponentLandscapeProxy = LandscapeComponent->GetLandscapeProxy();
|
|
if (!ComponentLandscapeProxy->SplineComponent && bCreate)
|
|
{
|
|
ComponentLandscapeProxy->Modify();
|
|
ComponentLandscapeProxy->SplineComponent = NewObject<ULandscapeSplinesComponent>(ComponentLandscapeProxy, NAME_None, RF_Transactional);
|
|
ComponentLandscapeProxy->SplineComponent->RelativeScale3D = RelativeScale3D;
|
|
ComponentLandscapeProxy->SplineComponent->AttachTo(ComponentLandscapeProxy->GetRootComponent());
|
|
}
|
|
if (ComponentLandscapeProxy->SplineComponent)
|
|
{
|
|
return ComponentLandscapeProxy->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->SplineComponent && bCreate)
|
|
{
|
|
Proxy->Modify();
|
|
Proxy->SplineComponent = NewObject<ULandscapeSplinesComponent>(Proxy, NAME_None, RF_Transactional);
|
|
Proxy->SplineComponent->RelativeScale3D = RelativeScale3D;
|
|
Proxy->SplineComponent->AttachTo(Proxy->GetRootComponent());
|
|
}
|
|
return Proxy->SplineComponent;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TArray<ULandscapeSplinesComponent*> ULandscapeSplinesComponent::GetAllStreamingSplinesComponents()
|
|
{
|
|
ALandscapeProxy* OuterLandscape = Cast<ALandscapeProxy>(GetOwner());
|
|
if (OuterLandscape)
|
|
{
|
|
ULandscapeInfo* LandscapeInfo = OuterLandscape->GetLandscapeInfo();
|
|
check(LandscapeInfo);
|
|
|
|
TArray<ULandscapeSplinesComponent*> SplinesComponents;
|
|
SplinesComponents.Reserve(LandscapeInfo->Proxies.Num() + 1);
|
|
|
|
ALandscape* RootLandscape = LandscapeInfo->LandscapeActor.Get();
|
|
if (RootLandscape && RootLandscape->SplineComponent)
|
|
{
|
|
SplinesComponents.Add(RootLandscape->SplineComponent);
|
|
}
|
|
for (ALandscapeProxy* LandscapeProxy : LandscapeInfo->Proxies)
|
|
{
|
|
if (LandscapeProxy && LandscapeProxy->SplineComponent)
|
|
{
|
|
SplinesComponents.Add(LandscapeProxy->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->ForeignSplineSegmentDataMap.Find(Owner);
|
|
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->ForeignControlPointDataMap.Find(Owner);
|
|
ForeignControlPointData->ModificationKey = Owner->GetModificationKey();
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::AddForeignMeshComponent(ULandscapeSplineSegment* Owner, USplineMeshComponent* Component)
|
|
{
|
|
#if DO_GUARD_SLOW
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
|
|
checkSlow(ComponentOuterWorld == ThisOuterWorld);
|
|
#endif
|
|
|
|
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
|
|
checkSlow(OwnerWorld != ThisOuterWorld);
|
|
|
|
auto& ForeignWorldSplineData = ForeignWorldSplineDataMap.FindOrAdd(OwnerWorld);
|
|
auto& ForeignSplineSegmentData = ForeignWorldSplineData.ForeignSplineSegmentDataMap.FindOrAdd(Owner);
|
|
ForeignSplineSegmentData.MeshComponents.Add(Component);
|
|
ForeignSplineSegmentData.ModificationKey = Owner->GetModificationKey();
|
|
|
|
MeshComponentForeignOwnersMap.Add(Component, Owner);
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::RemoveForeignMeshComponent(ULandscapeSplineSegment* Owner, USplineMeshComponent* Component)
|
|
{
|
|
#if DO_GUARD_SLOW
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
|
|
checkSlow(ComponentOuterWorld == ThisOuterWorld);
|
|
#endif
|
|
|
|
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
|
|
checkSlow(OwnerWorld != ThisOuterWorld);
|
|
|
|
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
|
|
checkSlow(ForeignWorldSplineData);
|
|
checkSlow(MeshComponentForeignOwnersMap.FindRef(Component) == Owner);
|
|
verifySlow(MeshComponentForeignOwnersMap.Remove(Component) == 1);
|
|
|
|
if (ForeignWorldSplineData)
|
|
{
|
|
auto* ForeignSplineSegmentData = ForeignWorldSplineData->ForeignSplineSegmentDataMap.Find(Owner);
|
|
verifySlow(ForeignSplineSegmentData->MeshComponents.RemoveSingle(Component) == 1);
|
|
if (ForeignSplineSegmentData->MeshComponents.Num() == 0)
|
|
{
|
|
verifySlow(ForeignWorldSplineData->ForeignSplineSegmentDataMap.Remove(Owner) == 1);
|
|
if (ForeignWorldSplineData->IsEmpty())
|
|
{
|
|
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ForeignSplineSegmentData->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->ForeignSplineSegmentDataMap.Find(Owner);
|
|
|
|
for (auto* MeshComponent : ForeignSplineSegmentData->MeshComponents)
|
|
{
|
|
checkSlow(MeshComponentForeignOwnersMap.FindRef(MeshComponent) == Owner);
|
|
verifySlow(MeshComponentForeignOwnersMap.Remove(MeshComponent) == 1);
|
|
}
|
|
ForeignSplineSegmentData->MeshComponents.Empty();
|
|
verifySlow(ForeignWorldSplineData->ForeignSplineSegmentDataMap.Remove(Owner) == 1);
|
|
if (ForeignWorldSplineData->IsEmpty())
|
|
{
|
|
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::AddForeignMeshComponent(ULandscapeSplineControlPoint* Owner, UControlPointMeshComponent* Component)
|
|
{
|
|
#if DO_GUARD_SLOW
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
|
|
checkSlow(ComponentOuterWorld == ThisOuterWorld);
|
|
#endif
|
|
|
|
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
|
|
checkSlow(OwnerWorld != ThisOuterWorld);
|
|
|
|
auto& ForeignWorldSplineData = ForeignWorldSplineDataMap.FindOrAdd(OwnerWorld);
|
|
checkSlow(!ForeignWorldSplineData.ForeignControlPointDataMap.Find(Owner));
|
|
auto& ForeignControlPointData = ForeignWorldSplineData.ForeignControlPointDataMap.Add(Owner);
|
|
|
|
ForeignControlPointData.MeshComponent = Component;
|
|
ForeignControlPointData.ModificationKey = Owner->GetModificationKey();
|
|
|
|
MeshComponentForeignOwnersMap.Add(Component, Owner);
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::RemoveForeignMeshComponent(ULandscapeSplineControlPoint* Owner, UControlPointMeshComponent* Component)
|
|
{
|
|
#if DO_GUARD_SLOW
|
|
UWorld* ThisOuterWorld = GetTypedOuter<UWorld>();
|
|
UWorld* ComponentOuterWorld = Component->GetTypedOuter<UWorld>();
|
|
checkSlow(ComponentOuterWorld == ThisOuterWorld);
|
|
#endif
|
|
|
|
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
|
|
checkSlow(OwnerWorld != ThisOuterWorld);
|
|
|
|
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
|
|
checkSlow(ForeignWorldSplineData);
|
|
checkSlow(MeshComponentForeignOwnersMap.FindRef(Component) == Owner);
|
|
verifySlow(MeshComponentForeignOwnersMap.Remove(Component) == 1);
|
|
|
|
if (ForeignWorldSplineData)
|
|
{
|
|
auto* ForeignControlPointData = ForeignWorldSplineData->ForeignControlPointDataMap.Find(Owner);
|
|
checkSlow(ForeignControlPointData);
|
|
checkSlow(ForeignControlPointData->MeshComponent == Component);
|
|
|
|
verifySlow(ForeignWorldSplineData->ForeignControlPointDataMap.Remove(Owner) == 1);
|
|
if (ForeignWorldSplineData->IsEmpty())
|
|
{
|
|
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplinesComponent::DestroyOrphanedForeignMeshComponents(UWorld* OwnerWorld)
|
|
{
|
|
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
|
|
|
|
if (ForeignWorldSplineData)
|
|
{
|
|
for (auto ForeignSplineSegmentDataIt = ForeignWorldSplineData->ForeignSplineSegmentDataMap.CreateIterator(); ForeignSplineSegmentDataIt; ++ForeignSplineSegmentDataIt)
|
|
{
|
|
const auto& ForeignSplineSegment = ForeignSplineSegmentDataIt->Key;
|
|
auto& ForeignSplineSegmentData = ForeignSplineSegmentDataIt->Value;
|
|
|
|
if (!ForeignSplineSegment)
|
|
{
|
|
for (auto* MeshComponent : ForeignSplineSegmentData.MeshComponents)
|
|
{
|
|
checkSlow(!MeshComponentForeignOwnersMap.FindRef(MeshComponent).IsValid());
|
|
verifySlow(MeshComponentForeignOwnersMap.Remove(MeshComponent) == 1);
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
ForeignSplineSegmentData.MeshComponents.Empty();
|
|
ForeignSplineSegmentDataIt.RemoveCurrent();
|
|
}
|
|
}
|
|
|
|
if (ForeignWorldSplineData->IsEmpty())
|
|
{
|
|
verifySlow(ForeignWorldSplineDataMap.Remove(OwnerWorld) == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
UControlPointMeshComponent* ULandscapeSplinesComponent::GetForeignMeshComponent(ULandscapeSplineControlPoint* Owner)
|
|
{
|
|
UWorld* OwnerWorld = Owner->GetTypedOuter<UWorld>();
|
|
checkSlow(OwnerWorld != GetTypedOuter<UWorld>());
|
|
|
|
auto* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld);
|
|
if (ForeignWorldSplineData)
|
|
{
|
|
auto* ForeignControlPointData = ForeignWorldSplineData->ForeignControlPointDataMap.Find(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->ForeignSplineSegmentDataMap.Find(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
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// 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);
|
|
|
|
LDMaxDrawDistance = 0;
|
|
TranslucencySortPriority = 0;
|
|
|
|
LayerName = NAME_None;
|
|
bRaiseTerrain = true;
|
|
bLowerTerrain = true;
|
|
|
|
LocalMeshComponent = nullptr;
|
|
bPlaceSplineMeshesInStreamingLevels = true;
|
|
bEnableCollision = true;
|
|
bCastShadow = true;
|
|
|
|
// transients
|
|
bSelected = false;
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeSplineControlPoint::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
#if WITH_EDITOR
|
|
if (Ar.UE4Ver() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
|
|
{
|
|
bPlaceSplineMeshesInStreamingLevels = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeSplineControlPoint::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
if (LocalMeshComponent != nullptr)
|
|
{
|
|
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
|
|
OuterSplines->MeshComponentLocalOwnersMap.Add(LocalMeshComponent, this);
|
|
}
|
|
}
|
|
|
|
if (GetLinkerUE4Version() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
|
|
{
|
|
// Fix collision profile
|
|
if (LocalMeshComponent != nullptr) // ForeignMeshComponents didn't exist yet
|
|
{
|
|
const FName CollisionProfile = bEnableCollision ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName;
|
|
if (LocalMeshComponent->GetCollisionProfileName() != CollisionProfile)
|
|
{
|
|
LocalMeshComponent->SetCollisionProfileName(CollisionProfile);
|
|
}
|
|
|
|
LocalMeshComponent->SetFlags(RF_TextExportTransient);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FLandscapeSplineSegmentConnection& FLandscapeSplineConnection::GetNearConnection() const
|
|
{
|
|
return Segment->Connections[End];
|
|
}
|
|
|
|
FLandscapeSplineSegmentConnection& FLandscapeSplineConnection::GetFarConnection() const
|
|
{
|
|
return Segment->Connections[1 - End];
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FName ULandscapeSplineControlPoint::GetBestConnectionTo(FVector Destination) const
|
|
{
|
|
FName BestSocket = NAME_None;
|
|
float BestScore = -FLT_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();
|
|
|
|
float Score = (Destination - Location).Size() - (Destination - SocketLocation).Size(); // Score closer sockets higher
|
|
Score *= FMath::Abs(FVector::DotProduct((Destination - SocketLocation), SocketRotation.Vector())); // score closer rotation higher
|
|
|
|
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)
|
|
{
|
|
bSelected = bInSelected;
|
|
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
|
|
|
|
if (LocalMeshComponent != nullptr)
|
|
{
|
|
LocalMeshComponent->bSelected = bInSelected;
|
|
LocalMeshComponent->PushSelectionToProxy();
|
|
}
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
|
|
auto* MeshComponent = ForeignMeshComponentsPair.Value;
|
|
MeshComponent->bSelected = bInSelected;
|
|
MeshComponent->PushSelectionToProxy();
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplineControlPoint::AutoCalcRotation()
|
|
{
|
|
Modify();
|
|
|
|
FRotator Delta = FRotator::ZeroRotator;
|
|
|
|
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();
|
|
ForeignMeshComponentsMap.Add(SplineComponent, ForeignMeshComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ForeignMeshComponentsMap;
|
|
}
|
|
|
|
void ULandscapeSplineControlPoint::UpdateSplinePoints(bool bUpdateCollision, bool bUpdateAttachedSegments)
|
|
{
|
|
Modify();
|
|
|
|
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
|
|
ModificationKey = FGuid::NewGuid();
|
|
|
|
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 (bPlaceSplineMeshesInStreamingLevels)
|
|
{
|
|
MeshComponentOuterSplines = OuterSplines->GetStreamingSplinesComponentByLocation(Location);
|
|
|
|
if (MeshComponentOuterSplines != OuterSplines)
|
|
{
|
|
MeshComponent = MeshComponentOuterSplines->GetForeignMeshComponent(this);
|
|
MeshComponentOuterSplines->Modify();
|
|
MeshComponentOuterSplines->UpdateModificationKey(this);
|
|
}
|
|
}
|
|
|
|
// Create mesh component if needed
|
|
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->AttachTo(MeshComponentOuterSplines);
|
|
if (MeshComponentOuterSplines == OuterSplines)
|
|
{
|
|
MeshComponentOuterSplines->MeshComponentLocalOwnersMap.Add(MeshComponent, this);
|
|
}
|
|
else
|
|
{
|
|
MeshComponentOuterSplines->AddForeignMeshComponent(this, MeshComponent);
|
|
ForeignWorld = MeshComponentOuterSplines->GetTypedOuter<UWorld>();
|
|
}
|
|
}
|
|
|
|
if (MeshComponent->RelativeLocation != Location ||
|
|
MeshComponent->RelativeRotation != Rotation ||
|
|
MeshComponent->RelativeScale3D != MeshScale)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->SetRelativeTransform(FTransform(Rotation, Location, MeshScale));
|
|
MeshComponent->InvalidateLightingCache();
|
|
}
|
|
|
|
if (MeshComponent->StaticMesh != Mesh)
|
|
{
|
|
MeshComponent->Modify();
|
|
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->LDMaxDrawDistance != LDMaxDrawDistance)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->LDMaxDrawDistance = LDMaxDrawDistance;
|
|
MeshComponent->CachedMaxDrawDistance = 0;
|
|
MeshComponent->MarkRenderStateDirty();
|
|
}
|
|
|
|
if (MeshComponent->CastShadow != bCastShadow)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->SetCastShadow(bCastShadow);
|
|
}
|
|
|
|
const FName CollisionProfile = bEnableCollision ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName;
|
|
if (MeshComponent->BodyInstance.GetCollisionProfileName() != CollisionProfile)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->BodyInstance.SetCollisionProfileName(CollisionProfile);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MeshComponent = nullptr;
|
|
ForeignWorld = NULL;
|
|
}
|
|
|
|
// Destroy any unused components
|
|
bool bDestroyedAnyComponents = false;
|
|
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();
|
|
if (bDestroyedAnyComponents)
|
|
{
|
|
AutoSetConnections(false);
|
|
}
|
|
|
|
// 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 float 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 + SideFalloff);
|
|
const FVector FalloffRightPos = StartLocation + BiNormal * (Width + SideFalloff);
|
|
|
|
Points.Emplace(StartLocation, LeftPos, RightPos, FalloffLeftPos, FalloffRightPos, 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 float 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 + SideFalloff);
|
|
const FVector FalloffRightPos = StartLocation + BiNormal * (Width + SideFalloff);
|
|
|
|
Points.Emplace(StartLocation, LeftPos, RightPos, FalloffLeftPos, FalloffRightPos, 1.0f);
|
|
}
|
|
|
|
// Update Bounds
|
|
Bounds = FBox(0);
|
|
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::DeleteSplinePoints()
|
|
{
|
|
Modify();
|
|
|
|
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
|
|
|
|
Points.Reset();
|
|
Bounds = FBox(0);
|
|
|
|
OuterSplines->MarkRenderStateDirty();
|
|
|
|
if (LocalMeshComponent != nullptr)
|
|
{
|
|
OuterSplines->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();
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplineControlPoint::PostEditUndo()
|
|
{
|
|
bHackIsUndoingSplines = true;
|
|
Super::PostEditUndo();
|
|
bHackIsUndoingSplines = false;
|
|
|
|
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
SideFalloff = FMath::Max(SideFalloff, 0.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);
|
|
}
|
|
}
|
|
#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;
|
|
TranslucencySortPriority = 0;
|
|
bPlaceSplineMeshesInStreamingLevels = true;
|
|
bEnableCollision = true;
|
|
bCastShadow = true;
|
|
|
|
// transients
|
|
bSelected = false;
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeSplineSegment::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (!HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad | RF_AsyncLoading))
|
|
{
|
|
// create a new random seed for all new objects
|
|
RandomSeed = FMath::Rand();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeSplineSegment::Serialize(FArchive& Ar)
|
|
{
|
|
Super::Serialize(Ar);
|
|
|
|
#if WITH_EDITOR
|
|
if (Ar.UE4Ver() < 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.UE4Ver() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
|
|
{
|
|
bPlaceSplineMeshesInStreamingLevels = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ULandscapeSplineSegment::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
if (GetLinkerUE4Version() < VER_UE4_ADDED_LANDSCAPE_SPLINE_EDITOR_MESH &&
|
|
LocalMeshComponents.Num() == 0) // ForeignMeshComponents didn't exist yet
|
|
{
|
|
UpdateSplinePoints();
|
|
}
|
|
|
|
// Replace null meshes with the editor mesh
|
|
// Otherwise the spline will have no mesh and won't be easily selectable
|
|
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
|
|
if (OuterSplines->SplineEditorMesh != nullptr)
|
|
{
|
|
for (auto* MeshComponent : LocalMeshComponents)
|
|
{
|
|
if (MeshComponent->StaticMesh == nullptr)
|
|
{
|
|
MeshComponent->ConditionalPostLoad();
|
|
MeshComponent->SetStaticMesh(OuterSplines->SplineEditorMesh);
|
|
MeshComponent->SetHiddenInGame(true);
|
|
MeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
|
|
MeshComponent->BodyInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto* MeshComponent : LocalMeshComponents)
|
|
{
|
|
OuterSplines->MeshComponentLocalOwnersMap.Add(MeshComponent, this);
|
|
}
|
|
}
|
|
|
|
if (GetLinkerUE4Version() < VER_UE4_LANDSCAPE_SPLINE_CROSS_LEVEL_MESHES)
|
|
{
|
|
// Fix collision profile
|
|
for (auto* MeshComponent : LocalMeshComponents) // ForeignMeshComponents didn't exist yet
|
|
{
|
|
const bool bUsingEditorMesh = MeshComponent->bHiddenInGame;
|
|
const FName CollisionProfile = (bEnableCollision && !bUsingEditorMesh) ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName;
|
|
if (MeshComponent->GetCollisionProfileName() != CollisionProfile)
|
|
{
|
|
MeshComponent->SetCollisionProfileName(CollisionProfile);
|
|
}
|
|
|
|
MeshComponent->SetFlags(RF_TextExportTransient);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** */
|
|
#if WITH_EDITOR
|
|
void ULandscapeSplineSegment::SetSplineSelected(bool bInSelected)
|
|
{
|
|
bSelected = bInSelected;
|
|
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
|
|
|
|
for (auto* MeshComponent : LocalMeshComponents)
|
|
{
|
|
MeshComponent->bSelected = bInSelected;
|
|
MeshComponent->PushSelectionToProxy();
|
|
}
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
|
|
for (auto* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
MeshComponent->bSelected = bInSelected;
|
|
MeshComponent->PushSelectionToProxy();
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
float 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 += (NewPos - OldPos).Size();
|
|
OldPos = NewPos;
|
|
}
|
|
|
|
return 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();
|
|
}
|
|
ForeignMeshComponentsMap.Add(SplineComponent, MoveTemp(ForeignMeshComponents));
|
|
}
|
|
}
|
|
}
|
|
|
|
return ForeignMeshComponentsMap;
|
|
}
|
|
|
|
void ULandscapeSplineSegment::UpdateSplinePoints(bool bUpdateCollision)
|
|
{
|
|
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 StartSideFalloff = Connections[0].ControlPoint->SideFalloff;
|
|
const float EndSideFalloff = Connections[1].ControlPoint->SideFalloff;
|
|
const float StartRollDegrees = StartRotation.Roll * (Connections[0].TangentLen > 0 ? 1 : -1);
|
|
const float EndRollDegrees = EndRotation.Roll * (Connections[1].TangentLen > 0 ? -1 : 1);
|
|
const float StartRoll = FMath::DegreesToRadians(StartRollDegrees);
|
|
const float EndRoll = FMath::DegreesToRadians(EndRollDegrees);
|
|
|
|
int32 NumPoints = FMath::CeilToInt(SplineLength / OuterSplines->SplineResolution);
|
|
NumPoints = FMath::Clamp(NumPoints, 1, 1000);
|
|
|
|
LandscapeSplineRaster::Pointify(SplineInfo, Points, NumPoints, StartFalloffFraction, EndFalloffFraction, StartWidth, EndWidth, StartSideFalloff, EndSideFalloff, StartRollDegrees, EndRollDegrees);
|
|
|
|
// Update Bounds
|
|
Bounds = FBox(0);
|
|
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.Offset = {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<USplineMeshComponent*> OldLocalMeshComponents = MoveTemp(LocalMeshComponents);
|
|
LocalMeshComponents.Reserve(20);
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
|
|
// Unregister components
|
|
for (auto* MeshComponent : OldLocalMeshComponents)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->UnregisterComponent();
|
|
}
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
ForeignMeshComponentsPair.Key->Modify();
|
|
ForeignMeshComponentsPair.Key->GetOwner()->Modify();
|
|
for (auto* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->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)
|
|
{
|
|
Scale *= Width / USplineMeshComponent::GetAxisValue(MeshBounds.BoxExtent, CrossAxis(MeshEntry->ForwardAxis, MeshEntry->UpAxis));
|
|
}
|
|
|
|
const float MeshLength = FMath::Abs(USplineMeshComponent::GetAxisValue(MeshBounds.BoxExtent, MeshEntry->ForwardAxis) * 2 * USplineMeshComponent::GetAxisValue(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 (bPlaceSplineMeshesInStreamingLevels && !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(false);
|
|
LocalMeshComponents.Add(MeshComponent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<USplineMeshComponent*>* ForeignMeshComponents = ForeignMeshComponentsMap.Find(MeshComponentOuterSplines);
|
|
if (ForeignMeshComponents && ForeignMeshComponents->Num() > 0)
|
|
{
|
|
MeshComponentOuterSplines->UpdateModificationKey(this);
|
|
MeshComponent = ForeignMeshComponents->Pop(false);
|
|
ForeignWorlds.AddUnique(MeshComponentOuterSplines->GetTypedOuter<UWorld>());
|
|
}
|
|
}
|
|
|
|
if (!MeshComponent)
|
|
{
|
|
AActor* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner();
|
|
MeshComponentOuterActor->Modify();
|
|
MeshComponent = NewObject<USplineMeshComponent>(MeshComponentOuterActor, NAME_None, RF_Transactional | RF_TextExportTransient);
|
|
MeshComponent->bSelected = bSelected;
|
|
MeshComponent->AttachTo(MeshComponentOuterSplines);
|
|
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);
|
|
MeshComponent->SetVisibility(!bUsingEditorMesh || OuterSplines->bShowSplineEditorMesh);
|
|
|
|
MeshSettings.Add(FMeshSettings(T, MeshEntry));
|
|
iMesh++;
|
|
T += MeshT;
|
|
}
|
|
// Add terminating key
|
|
MeshSettings.Add(FMeshSettings(T, nullptr));
|
|
|
|
// Destroy old unwanted components now
|
|
for (UMeshComponent* MeshComponent : OldLocalMeshComponents)
|
|
{
|
|
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(MeshComponent) == 1);
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
OldLocalMeshComponents.Empty();
|
|
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
|
|
for (auto* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
MeshComponentOuterSplines->RemoveForeignMeshComponent(this, MeshComponent);
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
}
|
|
ForeignMeshComponentsMap.Empty();
|
|
|
|
// 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->StaticMesh;
|
|
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);
|
|
|
|
FVector Scale = MeshEntry->Scale;
|
|
if (MeshEntry->bScaleToWidth)
|
|
{
|
|
Scale *= Width / USplineMeshComponent::GetAxisValue(MeshBounds.BoxExtent, SideAxis);
|
|
}
|
|
|
|
FVector2D Offset = MeshEntry->Offset;
|
|
if (MeshEntry->bCenterH)
|
|
{
|
|
if (bDoOrientationRoll)
|
|
{
|
|
Offset.Y -= USplineMeshComponent::GetAxisValue(MeshBounds.Origin, SideAxis);
|
|
}
|
|
else
|
|
{
|
|
Offset.X -= USplineMeshComponent::GetAxisValue(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 = 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);
|
|
|
|
FVector ScaleEnd = MeshEntry->Scale;
|
|
if (MeshEntry->bScaleToWidth)
|
|
{
|
|
ScaleEnd *= WidthEnd / USplineMeshComponent::GetAxisValue(MeshBounds.BoxExtent, SideAxis);
|
|
}
|
|
|
|
FVector2D OffsetEnd = MeshEntry->Offset;
|
|
if (MeshEntry->bCenterH)
|
|
{
|
|
if (bDoOrientationRoll)
|
|
{
|
|
OffsetEnd.Y -= USplineMeshComponent::GetAxisValue(MeshBounds.Origin, SideAxis);
|
|
}
|
|
else
|
|
{
|
|
OffsetEnd.X -= USplineMeshComponent::GetAxisValue(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 = 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;
|
|
|
|
if (MeshComponent->AttachParent != OuterSplines)
|
|
{
|
|
FTransform RelativeTransform = OuterSplines->ComponentToWorld.GetRelativeTransform(MeshComponent->AttachParent->ComponentToWorld);
|
|
MeshComponent->SplineParams.StartPos = RelativeTransform.TransformPosition(MeshComponent->SplineParams.StartPos);
|
|
MeshComponent->SplineParams.EndPos = RelativeTransform.TransformPosition(MeshComponent->SplineParams.EndPos);
|
|
}
|
|
|
|
if (USplineMeshComponent::GetAxisValue(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->RelativeLocation = (MeshComponent->SplineParams.StartPos + MeshComponent->SplineParams.EndPos) / 2;
|
|
MeshComponent->SplineParams.StartPos -= MeshComponent->RelativeLocation;
|
|
MeshComponent->SplineParams.EndPos -= MeshComponent->RelativeLocation;
|
|
|
|
if (MeshComponent->LDMaxDrawDistance != LDMaxDrawDistance)
|
|
{
|
|
MeshComponent->LDMaxDrawDistance = LDMaxDrawDistance;
|
|
MeshComponent->CachedMaxDrawDistance = 0;
|
|
}
|
|
MeshComponent->TranslucencySortPriority = TranslucencySortPriority;
|
|
|
|
MeshComponent->SetCastShadow(bCastShadow);
|
|
MeshComponent->InvalidateLightingCache();
|
|
|
|
MeshComponent->BodyInstance.SetCollisionProfileName((bEnableCollision && !bUsingEditorMesh) ? UCollisionProfile::BlockAll_ProfileName : UCollisionProfile::NoCollision_ProfileName);
|
|
|
|
#if WITH_EDITOR
|
|
if (bUpdateCollision)
|
|
{
|
|
MeshComponent->RecreateCollision();
|
|
}
|
|
else
|
|
{
|
|
if (MeshComponent->BodySetup)
|
|
{
|
|
MeshComponent->BodySetup->InvalidatePhysicsData();
|
|
MeshComponent->BodySetup->AggGeom.EmptyElements();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Finally, register components
|
|
for (auto* MeshComponent : MeshComponents)
|
|
{
|
|
MeshComponent->RegisterComponent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Spline needs no mesh components (0 length or no meshes to use) so destroy any we have
|
|
for (auto* MeshComponent : OldLocalMeshComponents)
|
|
{
|
|
verifySlow(OuterSplines->MeshComponentLocalOwnersMap.Remove(MeshComponent) == 1);
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
OldLocalMeshComponents.Empty();
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
for (USplineMeshComponent* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
ULandscapeSplinesComponent* MeshComponentOuterSplines = dynamic_cast<ULandscapeSplinesComponent*>(MeshComponent->GetAttachParent());
|
|
if (MeshComponentOuterSplines)
|
|
{
|
|
MeshComponentOuterSplines->RemoveForeignMeshComponent(this, MeshComponent);
|
|
}
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
}
|
|
ForeignMeshComponentsMap.Empty();
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplineSegment::UpdateSplineEditorMesh()
|
|
{
|
|
ULandscapeSplinesComponent* OuterSplines = CastChecked<ULandscapeSplinesComponent>(GetOuter());
|
|
|
|
for (auto* MeshComponent : LocalMeshComponents)
|
|
{
|
|
if (MeshComponent->bHiddenInGame)
|
|
{
|
|
MeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
|
|
}
|
|
}
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
for (auto* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
if (MeshComponent->bHiddenInGame)
|
|
{
|
|
MeshComponent->SetVisibility(OuterSplines->bShowSplineEditorMesh);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ULandscapeSplineSegment::DeleteSplinePoints()
|
|
{
|
|
Modify();
|
|
|
|
ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent();
|
|
|
|
SplineInfo.Reset();
|
|
Points.Reset();
|
|
Bounds = FBox(0);
|
|
|
|
OuterSplines->MarkRenderStateDirty();
|
|
|
|
// Destroy mesh components
|
|
OuterSplines->GetOwner()->Modify();
|
|
for (auto* MeshComponent : LocalMeshComponents)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
LocalMeshComponents.Empty();
|
|
|
|
auto ForeignMeshComponentsMap = GetForeignMeshComponents();
|
|
for (auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap)
|
|
{
|
|
ULandscapeSplinesComponent* MeshComponentOuterSplines = ForeignMeshComponentsPair.Key;
|
|
MeshComponentOuterSplines->Modify();
|
|
MeshComponentOuterSplines->GetOwner()->Modify();
|
|
for (auto* MeshComponent : ForeignMeshComponentsPair.Value)
|
|
{
|
|
MeshComponent->Modify();
|
|
MeshComponentOuterSplines->RemoveForeignMeshComponent(this, MeshComponent);
|
|
MeshComponent->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
ModificationKey.Invalidate();
|
|
ForeignWorlds.Empty();
|
|
}
|
|
#endif
|
|
|
|
void ULandscapeSplineSegment::FindNearest( const FVector& InLocation, float& t, FVector& OutLocation, FVector& OutTangent )
|
|
{
|
|
float TempOutDistanceSq;
|
|
t = SplineInfo.InaccurateFindNearest(InLocation, TempOutDistanceSq);
|
|
OutLocation = SplineInfo.Eval(t, FVector::ZeroVector);
|
|
OutTangent = SplineInfo.EvalDerivative(t, FVector::ZeroVector);
|
|
}
|
|
|
|
bool ULandscapeSplineSegment::Modify(bool bAlwaysMarkDirty /*= true*/)
|
|
{
|
|
bool bSavedToTransactionBuffer = Super::Modify(bAlwaysMarkDirty);
|
|
|
|
//for (auto MeshComponent : MeshComponents)
|
|
//{
|
|
// if (MeshComponent)
|
|
// {
|
|
// bSavedToTransactionBuffer = MeshComponent->Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
|
|
// }
|
|
//}
|
|
|
|
return bSavedToTransactionBuffer;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeSplineSegment::PostEditUndo()
|
|
{
|
|
bHackIsUndoingSplines = true;
|
|
Super::PostEditUndo();
|
|
bHackIsUndoingSplines = false;
|
|
|
|
GetOuterULandscapeSplinesComponent()->MarkRenderStateDirty();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|