// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "InputCoreTypes.h" #include "Materials/MaterialInterface.h" #include "AI/NavigationSystemBase.h" #include "Materials/MaterialInstanceDynamic.h" #include "UnrealWidgetFwd.h" #include "EditorModeManager.h" #include "EditorViewportClient.h" #include "LandscapeToolInterface.h" #include "LandscapeProxy.h" #include "LandscapeEdMode.h" #include "Containers/ArrayView.h" #include "LandscapeEditorObject.h" #include "ScopedTransaction.h" #include "LandscapeEdit.h" #include "LandscapeDataAccess.h" #include "LandscapeRender.h" #include "LandscapeHeightfieldCollisionComponent.h" #include "Landscape.h" #include "Misc/MessageDialog.h" #define LOCTEXT_NAMESPACE "Landscape" class FLandscapeToolMirror : public FLandscapeTool { protected: FEdModeLandscape* EdMode; TObjectPtr MirrorPlaneMaterial; ECoordSystem SavedCoordSystem; public: FLandscapeToolMirror(FEdModeLandscape* InEdMode) : EdMode(InEdMode) { UMaterialInterface* BaseMirrorPlaneMaterial = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/MirrorPlaneMaterial.MirrorPlaneMaterial")); MirrorPlaneMaterial = UMaterialInstanceDynamic::Create(BaseMirrorPlaneMaterial, GetTransientPackage()); MirrorPlaneMaterial->SetScalarParameterValue(FName("LineThickness"), 2.0f); } virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AddReferencedObject(MirrorPlaneMaterial); } virtual const TCHAR* GetToolName() const override { return TEXT("Mirror"); } virtual FText GetDisplayName() const override { return NSLOCTEXT("UnrealEd", "LandscapeTool_Mirror", "Mirror Landscape"); }; virtual FText GetDisplayMessage() const override { return NSLOCTEXT("UnrealEd", "LandscapeTool_Mirror_Message", "Copy one side of a landscape to the other side so that you can easily mirror or rotate the landscape geometry along the X or Y axis."); }; virtual void SetEditRenderType() override { GLandscapeEditRenderMode = ELandscapeEditRenderMode::None | (GLandscapeEditRenderMode & ELandscapeEditRenderMode::BitMaskForMask); } virtual bool SupportsMask() override { return false; } virtual ELandscapeToolTargetTypeMask::Type GetSupportedTargetTypes() override { return ELandscapeToolTargetTypeMask::Heightmap; } virtual void EnterTool() override { if (EdMode->UISettings->MirrorPoint == FVector2D::ZeroVector) { CenterMirrorPoint(); } GLevelEditorModeTools().SetWidgetMode(UE::Widget::WM_Translate); SavedCoordSystem = GLevelEditorModeTools().GetCoordSystem(); GLevelEditorModeTools().SetCoordSystem(COORD_Local); } virtual void ExitTool() override { GLevelEditorModeTools().SetCoordSystem(SavedCoordSystem); } virtual bool BeginTool(FEditorViewportClient* ViewportClient, const FLandscapeToolTarget& Target, const FVector& InHitLocation) override { return true; } virtual void EndTool(FEditorViewportClient* ViewportClient) override { } virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override { return false; } virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override { if (InKey == EKeys::Enter && InEvent == IE_Pressed) { ApplyMirror(); } return false; } virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override { const ALandscapeProxy* LandscapeProxy = EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr; if ((LandscapeProxy != nullptr) && (InViewportClient->GetCurrentWidgetAxis() != EAxisList::None)) { const FTransform LandscapeToWorld = LandscapeProxy->LandscapeActorToWorld(); EdMode->UISettings->MirrorPoint += FVector2D(LandscapeToWorld.InverseTransformVector(InDrag)); return true; } return false; } virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override { // The editor can try to render the tool before the UpdateLandscapeEditorData command runs and the landscape editor realizes that the landscape has been hidden/deleted const ULandscapeInfo* const LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const ALandscapeProxy* const LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); if (LandscapeProxy) { const FTransform LandscapeToWorld = LandscapeProxy->LandscapeActorToWorld(); int32 MinX, MinY, MaxX, MaxY; if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) { FVector MirrorPoint3D = FVector((MaxX + MinX) / 2.0f, (MaxY + MinY) / 2.0f, 0); FVector MirrorPlaneScale = FVector(0, 1, 100); if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::MinusXToPlusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::PlusXToMinusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusXToPlusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusXToMinusX) { MirrorPoint3D.X = EdMode->UISettings->MirrorPoint.X; MirrorPlaneScale.Y = (MaxY - MinY) / 2.0f; } else { MirrorPoint3D.Y = EdMode->UISettings->MirrorPoint.Y; MirrorPlaneScale.Y = (MaxX - MinX) / 2.0f; } MirrorPoint3D.Z = GetLocalZAtPoint(LandscapeInfo, FMath::RoundToInt32(MirrorPoint3D.X), FMath::RoundToInt32(MirrorPoint3D.Y)); MirrorPoint3D = LandscapeToWorld.TransformPosition(MirrorPoint3D); FMatrix Matrix; if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::MinusYToPlusY || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::PlusYToMinusY || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusYToPlusY || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusYToMinusY) { Matrix = FScaleRotationTranslationMatrix(MirrorPlaneScale, FRotator(0, 90, 0), FVector::ZeroVector); } else { Matrix = FScaleMatrix(MirrorPlaneScale); } Matrix *= LandscapeToWorld.ToMatrixWithScale(); Matrix.SetOrigin(MirrorPoint3D); // Convert plane from horizontal to vertical Matrix = FMatrix(FVector(0, 1, 0), FVector(0, 0, 1), FVector(1, 0, 0), FVector(0, 0, 0)) * Matrix; const FBox Box = FBox(FVector(-1, -1, 0), FVector(+1, +1, 0)); DrawWireBox(PDI, Matrix, Box, FLinearColor::Green, SDPG_World); const float LandscapeScaleRatio = static_cast(LandscapeToWorld.GetScale3D().Z / LandscapeToWorld.GetScale3D().X); FVector2D UVScale = FVector2D(FMath::RoundToFloat(MirrorPlaneScale.Y / 10), FMath::RoundToFloat(MirrorPlaneScale.Z * LandscapeScaleRatio / 10 / 2) * 2); MirrorPlaneMaterial->SetVectorParameterValue(FName("GridSize"), FVector(UVScale, 0)); DrawPlane10x10(PDI, Matrix, 1, FVector2D(0, 0), FVector2D(1, 1), MirrorPlaneMaterial->GetRenderProxy(), SDPG_World); } } } virtual bool OverrideSelection() const override { return true; } virtual bool IsSelectionAllowed(AActor* InActor, bool bInSelection) const override { // Only filter selection not deselection if (bInSelection) { return false; } return true; } virtual bool UsesTransformWidget() const override { // The editor can try to render the transform widget before the landscape editor ticks and realises that the landscape has been hidden/deleted const ULandscapeInfo* const LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const ALandscapeProxy* const LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); if (LandscapeProxy) { return true; } return false; } virtual EAxisList::Type GetWidgetAxisToDraw(UE::Widget::EWidgetMode CheckMode) const override { if (CheckMode == UE::Widget::WM_Translate) { switch (EdMode->UISettings->MirrorOp) { case ELandscapeMirrorOperation::MinusXToPlusX: case ELandscapeMirrorOperation::PlusXToMinusX: case ELandscapeMirrorOperation::RotateMinusXToPlusX: case ELandscapeMirrorOperation::RotatePlusXToMinusX: return EAxisList::X; case ELandscapeMirrorOperation::MinusYToPlusY: case ELandscapeMirrorOperation::PlusYToMinusY: case ELandscapeMirrorOperation::RotateMinusYToPlusY: case ELandscapeMirrorOperation::RotatePlusYToMinusY: return EAxisList::Y; default: check(0); return EAxisList::None; } } return EAxisList::None; } virtual FVector GetWidgetLocation() const override { const ULandscapeInfo* const LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const ALandscapeProxy* const LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); if (LandscapeProxy) { const FTransform LandscapeToWorld = LandscapeProxy->LandscapeActorToWorld(); int32 MinX, MinY, MaxX, MaxY; if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) { MinX = MinY = 0; MaxX = MaxY = 0; } FVector MirrorPoint3D = FVector((MaxX + MinX) / 2.0f, (MaxY + MinY) / 2.0f, 0); if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::MinusXToPlusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::PlusXToMinusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusXToPlusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusXToMinusX) { MirrorPoint3D.X = EdMode->UISettings->MirrorPoint.X; } else { MirrorPoint3D.Y = EdMode->UISettings->MirrorPoint.Y; } MirrorPoint3D.Z = GetLocalZAtPoint(LandscapeInfo, FMath::RoundToInt32(MirrorPoint3D.X), FMath::RoundToInt32(MirrorPoint3D.Y)); MirrorPoint3D = LandscapeToWorld.TransformPosition(MirrorPoint3D); MirrorPoint3D.Z += 1000.f; // place the widget a little off the ground for better visibility return MirrorPoint3D; } return FVector::ZeroVector; } virtual FMatrix GetWidgetRotation() const override { if (const ALandscapeProxy* LandscapeProxy = EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr) { const FTransform LandscapeToWorld = LandscapeProxy->LandscapeActorToWorld(); FMatrix Result = FQuatRotationTranslationMatrix(LandscapeToWorld.GetRotation(), FVector::ZeroVector); if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::PlusXToMinusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::PlusYToMinusY || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusXToMinusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusYToMinusY) { Result = FRotationMatrix(FRotator(0, 180, 0)) * Result; } return Result; } return FMatrix::Identity; } protected: float GetLocalZAtPoint(const ULandscapeInfo* LandscapeInfo, int32 x, int32 y) const { // try to find Z location TSet Components; LandscapeInfo->GetComponentsInRegion(x, y, x, y, Components); if (auto It = Components.CreateConstIterator(); It) { ULandscapeComponent* Component = *It; FLandscapeComponentDataInterface DataInterface(Component); return LandscapeDataAccess::GetLocalHeight(DataInterface.GetHeight(x - Component->SectionBaseX, y - Component->SectionBaseY)); } return 0.0f; } /** * @param SourceData Data from the "source" side of the mirror op, including blend region * @param DestData Result of the mirror op, including blend region * @param SourceSizeX Width of SourceData * @param SourceSizeY Height of SourceData * @param DestSizeX Width of DestData * @param DestSizeY Height of DestData * @param MirrorPos Position of the mirror point in the source data (X or Y pos depending on MirrorOp) * @param BlendWidth Width of the blend region (in X or Y axis depending on MirrorOp) */ template void ApplyMirrorInternal(TArrayView SourceData, TArrayView DestData, int32 SourceSizeX, int32 SourceSizeY, int32 DestSizeX, int32 DestSizeY, int32 MirrorPos, int32 BlendWidth) { checkSlow(SourceData.Num() == SourceSizeX * SourceSizeY); checkSlow(DestData.Num() == DestSizeX * DestSizeY); switch (EdMode->UISettings->MirrorOp) { case ELandscapeMirrorOperation::MinusXToPlusX: case ELandscapeMirrorOperation::RotateMinusXToPlusX: { checkSlow(SourceSizeY == DestSizeY); checkSlow(MirrorPos + BlendWidth + 1 == SourceSizeX); const int32 BlendStart = (DestSizeX - MirrorPos - 1) - BlendWidth; const int32 BlendEnd = BlendStart + 2 * BlendWidth + 1; const int32 Offset = 2 * MirrorPos - DestSizeX + 1; const bool bFlipY = (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusXToPlusX); for (int32 Y = 0; Y < DestSizeY; ++Y) { TArrayView SourceLine1 = SourceData.Slice(Y * SourceSizeX, SourceSizeX); TArrayView SourceLine2 = bFlipY ? SourceData.Slice((SourceSizeY - Y - 1) * SourceSizeX, SourceSizeX) : SourceLine1; TArrayView DestLine = DestData.Slice(Y * DestSizeX, DestSizeX); // Pre-blend int32 DestX = 0; for (int32 SourceX = Offset; DestX < BlendStart; ++DestX, ++SourceX) { DestLine.GetData()[DestX] = SourceLine1.GetData()[SourceX]; } // Blend for (int32 SourceX1 = BlendStart + Offset, SourceX2 = BlendEnd + Offset - 1; DestX < BlendEnd; ++DestX, ++SourceX1, --SourceX2) { const float Frac = (float)(DestX - BlendStart + 1) / (BlendEnd - BlendStart + 1); const float Alpha = FMath::Cos(Frac * PI) * -0.5f + 0.5f; DestLine.GetData()[DestX] = FMath::Lerp(SourceLine1.GetData()[SourceX1], SourceLine2.GetData()[SourceX2], Alpha); } // Post-Blend for (int32 SourceX = BlendStart + Offset - 1; DestX < DestSizeX; ++DestX, --SourceX) { DestLine.GetData()[DestX] = SourceLine2.GetData()[SourceX]; } } } break; case ELandscapeMirrorOperation::PlusXToMinusX: case ELandscapeMirrorOperation::RotatePlusXToMinusX: { checkSlow(SourceSizeY == DestSizeY); const int32 BlendStart = (SourceSizeX - MirrorPos - 1) - BlendWidth; const int32 BlendEnd = BlendStart + 2 * BlendWidth + 1; const int32 Offset = 2 * MirrorPos - SourceSizeX + 1; const bool bFlipY = (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusXToMinusX); for (int32 Y = 0; Y < DestSizeY; ++Y) { TArrayView SourceLine1 = SourceData.Slice(Y * SourceSizeX, SourceSizeX); TArrayView SourceLine2 = bFlipY ? SourceData.Slice((SourceSizeY - Y - 1) * SourceSizeX, SourceSizeX) : SourceLine1; TArrayView DestLine = DestData.Slice(Y * DestSizeX, DestSizeX); // Pre-blend int32 DestX = 0; for (int32 SourceX = SourceSizeX - 1; DestX < BlendStart; ++DestX, --SourceX) { DestLine.GetData()[DestX] = SourceLine2.GetData()[SourceX]; } // Blend for (int32 SourceX1 = BlendStart + Offset, SourceX2 = BlendEnd + Offset - 1; DestX < BlendEnd; ++DestX, ++SourceX1, --SourceX2) { const float Frac = (float)(DestX - BlendStart + 1) / (BlendEnd - BlendStart + 1); const float Alpha = FMath::Cos(Frac * PI) * -0.5f + 0.5f; DestLine.GetData()[DestX] = FMath::Lerp(SourceLine2.GetData()[SourceX2], SourceLine1.GetData()[SourceX1], Alpha); } // Post-Blend for (int32 SourceX = BlendEnd + Offset; DestX < DestSizeX; ++DestX, ++SourceX) { DestLine.GetData()[DestX] = SourceLine1.GetData()[SourceX]; } } } break; case ELandscapeMirrorOperation::MinusYToPlusY: case ELandscapeMirrorOperation::RotateMinusYToPlusY: { checkSlow(SourceSizeX == DestSizeX); checkSlow(MirrorPos + BlendWidth + 1 == SourceSizeY); const int32 BlendStart = (DestSizeY - MirrorPos - 1) - BlendWidth; const int32 BlendEnd = BlendStart + 2 * BlendWidth + 1; const int32 Offset = 2 * MirrorPos - DestSizeY + 1; const bool bFlipX = (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusYToPlusY); // Pre-blend int32 DestY = 0; for (int32 SourceY = Offset; DestY < BlendStart; ++DestY, ++SourceY) { TArrayView SourceLine = SourceData.Slice(SourceY * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); FMemory::Memcpy(DestLine.GetData(), SourceLine.GetData(), DestSizeX * sizeof(T)); } // Blend for (int32 SourceY1 = BlendStart + Offset, SourceY2 = BlendEnd + Offset - 1; DestY < BlendEnd; ++DestY, ++SourceY1, --SourceY2) { const float Frac = (float)(DestY - BlendStart + 1) / (BlendEnd - BlendStart + 1); const float Alpha = FMath::Cos(Frac * PI) * -0.5f + 0.5f; TArrayView SourceLine1 = SourceData.Slice(SourceY1 * SourceSizeX, SourceSizeX); TArrayView SourceLine2 = SourceData.Slice(SourceY2 * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); for (int32 DestX = 0; DestX < DestSizeX; ++DestX) { const int32 SourceX2 = bFlipX ? (SourceSizeX - DestX - 1) : DestX; DestLine.GetData()[DestX] = FMath::Lerp(SourceLine1.GetData()[DestX], SourceLine2.GetData()[SourceX2], Alpha); } } // Post-Blend for (int32 SourceY = BlendStart + Offset - 1; DestY < DestSizeY; ++DestY, --SourceY) { TArrayView SourceLine = SourceData.Slice(SourceY * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); FMemory::Memcpy(DestLine.GetData(), SourceLine.GetData(), DestSizeX * sizeof(T)); if (bFlipX) { Algo::Reverse(DestLine); } } } break; case ELandscapeMirrorOperation::PlusYToMinusY: case ELandscapeMirrorOperation::RotatePlusYToMinusY: { checkSlow(SourceSizeX == DestSizeX); const int32 BlendStart = (SourceSizeY - MirrorPos - 1) - BlendWidth; const int32 BlendEnd = BlendStart + 2 * BlendWidth + 1; const int32 Offset = 2 * MirrorPos - SourceSizeY + 1; const bool bFlipX = (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotatePlusYToMinusY); // Pre-blend int32 DestY = 0; for (int32 SourceY = SourceSizeY - 1; DestY < BlendStart; ++DestY, --SourceY) { TArrayView SourceLine = SourceData.Slice(SourceY * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); FMemory::Memcpy(DestLine.GetData(), SourceLine.GetData(), DestSizeX * sizeof(T)); if (bFlipX) { Algo::Reverse(DestLine); } } // Blend for (int32 SourceY1 = BlendStart + Offset, SourceY2 = BlendEnd + Offset - 1; DestY < BlendEnd; ++DestY, ++SourceY1, --SourceY2) { const float Frac = (float)(DestY - BlendStart + 1) / (BlendEnd - BlendStart + 1); const float Alpha = FMath::Cos(Frac * PI) * -0.5f + 0.5f; TArrayView SourceLine1 = SourceData.Slice(SourceY1 * SourceSizeX, SourceSizeX); TArrayView SourceLine2 = SourceData.Slice(SourceY2 * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); for (int32 DestX = 0; DestX < DestSizeX; ++DestX) { const int32 SourceX2 = bFlipX ? (SourceSizeX - DestX - 1) : DestX; DestLine.GetData()[DestX] = FMath::Lerp(SourceLine2.GetData()[SourceX2], SourceLine1.GetData()[DestX], Alpha); } } // Post-Blend for (int32 SourceY = BlendEnd + Offset; DestY < DestSizeY; ++DestY, ++SourceY) { TArrayView SourceLine = SourceData.Slice(SourceY * SourceSizeX, SourceSizeX); TArrayView DestLine = DestData.Slice(DestY * DestSizeX, DestSizeX); FMemory::Memcpy(DestLine.GetData(), SourceLine.GetData(), DestSizeX * sizeof(T)); } } break; default: check(0); return; } } public: virtual void ApplyMirror() { FText Reason; if (!EdMode->CanEditLayer(&Reason)) { FMessageDialog::Open(EAppMsgType::Ok, Reason); return; } FScopedTransaction Transaction(LOCTEXT("Mirror_Apply", "Landscape Editing: Mirror Landscape")); FScopedSetLandscapeEditingLayer Scope(EdMode->GetLandscape(), EdMode->GetCurrentLayerGuid(), [&] { EdMode->RequestLayersContentUpdateForceAll(); }); const ULandscapeInfo* const LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const ALandscapeProxy* const LandscapeProxy = LandscapeInfo->GetLandscapeProxy(); const FTransform LandscapeToWorld = LandscapeProxy->LandscapeActorToWorld(); const FVector2D MirrorPoint = EdMode->UISettings->MirrorPoint; int32 BlendWidth = FMath::Clamp(EdMode->UISettings->MirrorSmoothingWidth, 0, 32768); FLandscapeEditDataInterface LandscapeEdit(EdMode->CurrentToolTarget.LandscapeInfo.Get()); int32 MinX, MinY, MaxX, MaxY; if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) { return; } const int32 SizeX = (1 + MaxX - MinX); const int32 SizeY = (1 + MaxY - MinY); int32 SourceMinX, SourceMinY; int32 SourceMaxX, SourceMaxY; int32 DestMinX, DestMinY; int32 DestMaxX, DestMaxY; int32 MirrorPos; switch (EdMode->UISettings->MirrorOp) { case ELandscapeMirrorOperation::MinusXToPlusX: case ELandscapeMirrorOperation::RotateMinusXToPlusX: case ELandscapeMirrorOperation::PlusXToMinusX: case ELandscapeMirrorOperation::RotatePlusXToMinusX: { MirrorPos = FMath::RoundToInt32(MirrorPoint.X); if (MirrorPos <= MinX || MirrorPos >= MaxX) { return; } const int32 MirrorSize = FMath::Max(MaxX - MirrorPos, MirrorPos - MinX); // not including the mirror column itself BlendWidth = FMath::Min(BlendWidth, MirrorSize); if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::MinusXToPlusX || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusXToPlusX) { SourceMinX = MirrorPos - MirrorSize; SourceMaxX = MirrorPos + BlendWidth; DestMinX = MirrorPos - BlendWidth - 1; // extra column to calc normals for mirror column DestMaxX = MirrorPos + MirrorSize; } else { SourceMinX = MirrorPos - BlendWidth; SourceMaxX = MirrorPos + MirrorSize; DestMinX = MirrorPos - MirrorSize; DestMaxX = MirrorPos + BlendWidth + 1; // extra column to calc normals for mirror column } SourceMinY = MinY; SourceMaxY = MaxY; DestMinY = MinY; DestMaxY = MaxY; MirrorPos -= SourceMinX; } break; case ELandscapeMirrorOperation::MinusYToPlusY: case ELandscapeMirrorOperation::RotateMinusYToPlusY: case ELandscapeMirrorOperation::PlusYToMinusY: case ELandscapeMirrorOperation::RotatePlusYToMinusY: { MirrorPos = FMath::RoundToInt32(MirrorPoint.Y); if (MirrorPos <= MinY || MirrorPos >= MaxY) { return; } const int32 MirrorSize = FMath::Max(MaxY - MirrorPos, MirrorPos - MinY); // not including the mirror row itself SourceMinX = MinX; SourceMaxX = MaxX; DestMinX = MinX; DestMaxX = MaxX; if (EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::MinusYToPlusY || EdMode->UISettings->MirrorOp == ELandscapeMirrorOperation::RotateMinusYToPlusY) { SourceMinY = MirrorPos - MirrorSize; SourceMaxY = MirrorPos + BlendWidth; DestMinY = MirrorPos - BlendWidth - 1; // extra column to calc normals for mirror column DestMaxY = MirrorPos + MirrorSize; } else { SourceMinY = MirrorPos - BlendWidth; SourceMaxY = MirrorPos + MirrorSize; DestMinY = MirrorPos - MirrorSize; DestMaxY = MirrorPos + BlendWidth + 1; // extra column to calc normals for mirror column } MirrorPos -= SourceMinY; } break; default: check(0); return; } const int32 SourceSizeX = SourceMaxX - SourceMinX + 1; const int32 SourceSizeY = SourceMaxY - SourceMinY + 1; const int32 DestSizeX = DestMaxX - DestMinX + 1; const int32 DestSizeY = DestMaxY - DestMinY + 1; TArray SourceHeightData, DestHeightData; SourceHeightData.AddUninitialized(SourceSizeX * SourceSizeY); DestHeightData.AddUninitialized(DestSizeX * DestSizeY); int32 TempMinX = SourceMinX; // GetHeightData overwrites its input min/max x/y int32 TempMaxX = SourceMaxX; int32 TempMinY = SourceMinY; int32 TempMaxY = SourceMaxY; LandscapeEdit.GetHeightData(TempMinX, TempMinY, TempMaxX, TempMaxY, &SourceHeightData[0], SourceSizeX); ApplyMirrorInternal(SourceHeightData, DestHeightData, SourceSizeX, SourceSizeY, DestSizeX, DestSizeY, MirrorPos, BlendWidth); LandscapeEdit.SetHeightData(DestMinX, DestMinY, DestMaxX, DestMaxY, &DestHeightData[0], DestSizeX, true); TArray SourceWeightData, DestWeightData; SourceWeightData.AddUninitialized(SourceSizeX * SourceSizeY); DestWeightData.AddUninitialized(DestSizeX * DestSizeY); for (const auto& LayerSettings : LandscapeInfo->Layers) { ULandscapeLayerInfoObject* LayerInfo = LayerSettings.LayerInfoObj; if (LayerInfo) { TempMinX = SourceMinX; // GetWeightData overwrites its input min/max x/y TempMaxX = SourceMaxX; TempMinY = SourceMinY; TempMaxY = SourceMaxY; LandscapeEdit.GetWeightData(LayerInfo, TempMinX, TempMinY, TempMaxX, TempMaxY, &SourceWeightData[0], SourceSizeX); ApplyMirrorInternal(SourceWeightData, DestWeightData, SourceSizeX, SourceSizeY, DestSizeX, DestSizeY, MirrorPos, BlendWidth); LandscapeEdit.SetAlphaData(LayerInfo, DestMinX, DestMinY, DestMaxX, DestMaxY, &DestWeightData[0], DestSizeX, ELandscapeLayerPaintingRestriction::None, false, false); //LayerInfo->IsReferencedFromLoadedData = true; } } LandscapeEdit.Flush(); TSet Components; if (LandscapeEdit.GetComponentsInRegion(DestMinX, DestMinY, DestMaxX, DestMaxY, &Components) && Components.Num() > 0) { if (!EdMode->HasLandscapeLayersContent()) { ALandscapeProxy::InvalidateGeneratedComponentData(Components); for (ULandscapeComponent* Component : Components) { // Recreate collision for modified components and update the navmesh ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->GetCollisionComponent(); if (CollisionComponent) { CollisionComponent->RecreateCollision(); FNavigationSystem::UpdateComponentData(*CollisionComponent); } } } EdMode->UpdateLayerUsageInformation(); } } void CenterMirrorPoint() { ULandscapeInfo* const LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); int32 MinX, MinY, MaxX, MaxY; if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) { EdMode->UISettings->MirrorPoint = FVector2D((float)(MinX + MaxX) / 2.0f, (float)(MinY + MaxY) / 2.0f); } else { EdMode->UISettings->MirrorPoint = FVector2D(0, 0); } } }; void FEdModeLandscape::ApplyMirrorTool() { if (CurrentTool->GetToolName() == FName("Mirror")) { FLandscapeToolMirror* MirrorTool = (FLandscapeToolMirror*)CurrentTool; MirrorTool->ApplyMirror(); GEditor->RedrawLevelEditingViewports(); } } void FEdModeLandscape::CenterMirrorTool() { if (CurrentTool->GetToolName() == FName("Mirror")) { FLandscapeToolMirror* MirrorTool = (FLandscapeToolMirror*)CurrentTool; MirrorTool->CenterMirrorPoint(); GEditor->RedrawLevelEditingViewports(); } } // // Toolset initialization // void FEdModeLandscape::InitializeTool_Mirror() { auto Tool_Mirror = MakeUnique(this); Tool_Mirror->ValidBrushes.Add("BrushSet_Dummy"); LandscapeTools.Add(MoveTemp(Tool_Mirror)); } #undef LOCTEXT_NAMESPACE