// 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 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 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(&UniquePointer); } const FLinearColor SplineColor; const UTexture2D* ControlPointSprite; const bool bDrawControlPointSprite; const bool bDrawFalloff; struct FSegmentProxy { ULandscapeSplineSegment* Owner; TRefCountPtr HitProxy; TArray Points; uint32 bSelected : 1; }; TArray Segments; struct FControlPointProxy { ULandscapeSplineControlPoint* Owner; TRefCountPtr HitProxy; FVector Location; TArray 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 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(ControlPoint->Width != 0 ? ControlPoint->Width / 8 : ControlPoint->SideFalloff / 4, 10, 1000); ControlPointProxy.bSelected = ControlPoint->IsSplineSelected(); ControlPoints.Add(ControlPointProxy); } } virtual HHitProxy* CreateHitProxies(UPrimitiveComponent* Component, TArray >& 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& 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(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(); 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(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(ControlPointSpriteScale, 10, ControlPoint.SpriteScale); ControlPointSpriteLocation = ControlPointLocation + FVector(0, 0, ControlPointSpriteScale * 0.75f); } PDI->DrawSprite( ControlPointSpriteLocation, static_cast(ControlPointSpriteScale), static_cast(ControlPointSpriteScale), ControlPointSprite->GetResource(), ControlPointSpriteColor, DepthPriority, 0, static_cast(ControlPointSprite->GetResource()->GetSizeX()), 0, static_cast(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(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 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 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 ULandscapeSplinesComponent::GetSplineMeshComponents() { TArray SplineMeshComponents; #if WITH_EDITOR for (ULandscapeSplineSegment* SplineSegment : Segments) { // local SplineMeshComponents.Append(SplineSegment->GetLocalMeshComponents()); // foreign TMap> ForeignMeshComponentsMap = SplineSegment->GetForeignMeshComponents(); for (const auto& ForeignMeshComponentsPair : ForeignMeshComponentsMap) { for (USplineMeshComponent* ForeignMeshComponent : ForeignMeshComponentsPair.Value) { SplineMeshComponents.Add(ForeignMeshComponent); } } } #endif return SplineMeshComponents; } ILandscapeSplineInterface* ULandscapeSplinesComponent::GetSplineOwner() { return Cast(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(); TSoftObjectPtr 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 Func) { // Copy in case iteration modifies the list TArray 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 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 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(); check(IsRunningCommandlet() || ThisOuterWorld->WorldType == EWorldType::Editor); TSet OutdatedWorlds; TMap 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(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(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::GetAllStreamingSplinesComponents() { ALandscapeProxy* OuterLandscape = Cast(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 SplinesComponents; LandscapeInfo->ForAllSplineActors([&SplinesComponents](TScriptInterface SplineOwner) { if (ULandscapeSplinesComponent* SplineComponent = SplineOwner->GetSplinesComponent()) { SplinesComponents.Add(SplineComponent); } }); return SplinesComponents; } } return {}; } void ULandscapeSplinesComponent::UpdateModificationKey(ULandscapeSplineSegment* Owner) { UWorld* OwnerWorld = Owner->GetTypedOuter(); checkSlow(OwnerWorld != GetTypedOuter()); 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(); checkSlow(OwnerWorld != GetTypedOuter()); 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(); #if DO_GUARD_SLOW UWorld* ThisOuterWorld = GetTypedOuter(); UWorld* ComponentOuterWorld = Component->GetTypedOuter(); 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(); #if DO_GUARD_SLOW UWorld* ThisOuterWorld = GetTypedOuter(); UWorld* ComponentOuterWorld = Component->GetTypedOuter(); 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(); checkSlow(OwnerWorld != GetTypedOuter()); 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(); #if DO_GUARD_SLOW UWorld* ThisOuterWorld = GetTypedOuter(); UWorld* ComponentOuterWorld = Component->GetTypedOuter(); 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(); #if DO_GUARD_SLOW UWorld* ThisOuterWorld = GetTypedOuter(); UWorld* ComponentOuterWorld = Component->GetTypedOuter(); 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 Func) { TArray ObjectsWithOuter; GetObjectsWithOuter(GetOwner(), ObjectsWithOuter); for (UObject* Obj : ObjectsWithOuter) { if (USplineMeshComponent* SplineMeshComponent = Cast(Obj)) { // Find Mesh in the Spline Component local map if (TLazyObjectPtr* ForeignOwner = MeshComponentForeignOwnersMap.Find(SplineMeshComponent)) { if (ULandscapeSplineSegment* OwnerForMesh = Cast(ForeignOwner->Get())) { // Try to find the mesh through the global map from the owner we found in local map TArray 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(Obj)) { if (TLazyObjectPtr* ForeignOwner = MeshComponentForeignOwnersMap.Find(SplineMeshComponent)) { if (ULandscapeSplineControlPoint* OwnerForMesh = Cast(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 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() != 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() != 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(); checkSlow(OwnerWorld != GetTypedOuter()); FForeignWorldSplineData* ForeignWorldSplineData = ForeignWorldSplineDataMap.Find(OwnerWorld); if (ForeignWorldSplineData) { FForeignControlPointData* ForeignControlPointData = ForeignWorldSplineData->FindControlPoint(Owner); if (ForeignControlPointData) { return ForeignControlPointData->MeshComponent; } } return nullptr; } TArray ULandscapeSplinesComponent::GetForeignMeshComponents(ULandscapeSplineSegment* Owner) { UWorld* OwnerWorld = Owner->GetTypedOuter(); checkSlow(OwnerWorld != GetTypedOuter()); 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* 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()->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(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 ULandscapeSplineControlPoint::GetForeignMeshComponents() { TMap ForeignMeshComponentsMap; ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent(); TArray 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(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* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner(); ILandscapeSplineInterface* SplineOwner = Cast(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(); } 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(VirtualTextureLodBias); MeshComponent->MarkRenderStateDirty(); } if (MeshComponent->VirtualTextureCullMips != VirtualTextureCullMips) { MeshComponent->Modify(); MeshComponent->VirtualTextureCullMips = static_cast(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(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(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(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(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((NewPos - OldPos).Size()); OldPos = NewPos; } return static_cast(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> ULandscapeSplineSegment::GetForeignMeshComponents() { TMap> ForeignMeshComponentsMap; ULandscapeSplinesComponent* OuterSplines = GetOuterULandscapeSplinesComponent(); TArray 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 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(StartRotation.Roll * (Connections[0].TangentLen > 0 ? 1 : -1)); const float EndRollDegrees = static_cast(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 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 MeshComponents; TArray> OldLocalMeshComponents; Swap(LocalMeshComponents, OldLocalMeshComponents); LocalMeshComponents.Reserve(20); // Gather all ForeignMesh associated with this Segment TMap> ForeignMeshComponentsMap = GetForeignMeshComponents(); // Unregister components, Remove Foreign/Local Associations for (TObjectPtr& 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 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(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* 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(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* MeshComponentOuterActor = MeshComponentOuterSplines->GetOwner(); ILandscapeSplineInterface* SplineOwner = Cast(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()); } 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(VirtualTextureLodBias); MeshComponent->VirtualTextureCullMips = static_cast(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& 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(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(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(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 From, TScriptInterface 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 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 From, TScriptInterface To) { AActor* ToActor = CastChecked(To.GetObject()); ToActor->Modify(); AActor* FromActor = CastChecked(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> 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(); 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 InNewOwner) { bool bUpdateBounds = false; ULandscapeSplinesComponent* FromSplineComponent = InControlPoint->GetOuterULandscapeSplinesComponent(); TScriptInterface 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(); 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 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