// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "InputCoreTypes.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceConstant.h" #include "EngineUtils.h" #include "EditorViewportClient.h" #include "LandscapeToolInterface.h" #include "LandscapeProxy.h" #include "LandscapeGizmoActor.h" #include "LandscapeEdMode.h" #include "LandscapeEditorObject.h" #include "LandscapeUtils.h" #include "LandscapeRender.h" #include "LevelUtils.h" // // FLandscapeBrush // bool GInLandscapeBrushTransaction = false; void FLandscapeBrush::BeginStroke(float LandscapeX, float LandscapeY, FLandscapeTool* CurrentTool) { if (!GInLandscapeBrushTransaction) { GEditor->BeginTransaction(FText::Format(NSLOCTEXT("UnrealEd", "LandscapeMode_EditTransaction", "Landscape Editing: {0}"), CurrentTool->GetDisplayName())); GInLandscapeBrushTransaction = true; } } void FLandscapeBrush::EndStroke() { if (ensure(GInLandscapeBrushTransaction)) { GEditor->EndTransaction(); GInLandscapeBrushTransaction = false; } } // // FLandscapeBrushCircle // class FLandscapeBrushCircle : public FLandscapeBrush { using Super = FLandscapeBrush; // Components which previously were under the area of the brush. TSet, TWeakObjectPtrSetKeyFuncs>> BrushMaterialComponents; // A Cache of previously created and now unused MIDs we can reuse if required. TArray> BrushMaterialFreeInstances; protected: FVector2f LastMousePosition; TObjectPtr BrushMaterial; TMap, TWeakObjectPtr, FDefaultSetAllocator, TWeakObjectPtrMapKeyFuncs, TWeakObjectPtr>> BrushMaterialInstanceMap; bool bCanPaint; virtual float CalculateFalloff(float Distance, float Radius, float Falloff) = 0; /** Protected so that only subclasses can create instances of this class. */ FLandscapeBrushCircle(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : LastMousePosition(0, 0) , BrushMaterial(UE::Landscape::CreateToolLandscapeMaterialInstanceConstant(InBrushMaterial)) , bCanPaint(false) , EdMode(InEdMode) { } public: FEdModeLandscape* EdMode; // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AddReferencedObject(BrushMaterial); } virtual void LeaveBrush() override { for (auto It = BrushMaterialInstanceMap.CreateIterator(); It; ++It) { if (ULandscapeComponent* Component = It.Key().Get()) { Component->EditToolRenderData.ToolMaterial = nullptr; Component->UpdateEditToolRenderData(); } } TArray> BrushMaterialInstances; ObjectPtrDecay(BrushMaterialInstanceMap).GenerateValueArray(BrushMaterialInstances); BrushMaterialFreeInstances += BrushMaterialInstances; BrushMaterialInstanceMap.Empty(); BrushMaterialComponents.Empty(); bCanPaint = false; } virtual void BeginStroke(float LandscapeX, float LandscapeY, FLandscapeTool* CurrentTool) override { Super::BeginStroke(LandscapeX, LandscapeY, CurrentTool); LastMousePosition = FVector2f(LandscapeX, LandscapeY); } virtual bool CanPaint(const TSet& InAffectedComponents, bool bInHasUnloadedComponents) const { if (bInHasUnloadedComponents) { return false; } if (!EdMode->CanEditTargetLayer()) { return false; } for (const ULandscapeComponent* Component : InAffectedComponents) { const ALandscapeProxy* LandscapeProxy = Component->GetLandscapeProxy(); const ULandscapeLayerInfoObject* LayerInfo = EdMode->CurrentToolTarget.LayerInfo.Get(); if (((EdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Weightmap) || (EdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Visibility)) && (EdMode->UISettings->PaintingRestriction != ELandscapeLayerPaintingRestriction::None)) { if ((EdMode->UISettings->PaintingRestriction == ELandscapeLayerPaintingRestriction::UseComponentAllowList) && !Component->LayerAllowList.Contains(LayerInfo)) { return false; } else { const TArray& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations(true); bool bExisting = ComponentWeightmapLayerAllocations.ContainsByPredicate([LayerInfo](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.LayerInfo == LayerInfo; }); if (!bExisting) { if (((EdMode->UISettings->PaintingRestriction == ELandscapeLayerPaintingRestriction::ExistingOnly) || (EdMode->UISettings->PaintingRestriction == ELandscapeLayerPaintingRestriction::UseMaxLayers)) && (LandscapeProxy->MaxPaintedLayersPerComponent > 0) && (ComponentWeightmapLayerAllocations.Num() >= LandscapeProxy->MaxPaintedLayersPerComponent)) { return false; } } } } if (EdMode->CurrentToolTarget.TargetType == ELandscapeToolTargetType::Visibility && !Component->IsLandscapeHoleMaterialValid()) { return false; } } return true; } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); ALandscapeProxy* Proxy = LandscapeInfo->GetLandscapeProxy(); const float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); const float TotalRadius = EdMode->UISettings->GetCurrentToolBrushRadius() / ScaleXY; const float Radius = (1.0f - EdMode->UISettings->GetCurrentToolBrushFalloff()) * TotalRadius; const float Falloff = EdMode->UISettings->GetCurrentToolBrushFalloff() * TotalRadius; FIntRect Bounds; Bounds.Min.X = FMath::FloorToInt(LastMousePosition.X - TotalRadius); Bounds.Min.Y = FMath::FloorToInt(LastMousePosition.Y - TotalRadius); Bounds.Max.X = FMath::CeilToInt( LastMousePosition.X + TotalRadius); Bounds.Max.Y = FMath::CeilToInt( LastMousePosition.Y + TotalRadius); TSet NewComponents; bool bHasUnloadedComponents = false; // Adjusting the brush may use the same keybind as moving the camera as they can be user-set, so we need this second check. if (!ViewportClient->IsMovingCamera() || EdMode->IsAdjustingBrush(ViewportClient)) { // GetComponentsInRegion expects an inclusive max LandscapeInfo->GetComponentsInRegion(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X - 1, Bounds.Max.Y - 1, NewComponents); bHasUnloadedComponents = LandscapeInfo->HasUnloadedComponentsInRegion(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X - 1, Bounds.Max.Y - 1); } TSet PreviousComponents; for (TWeakObjectPtr BrushMaterialComponent : BrushMaterialComponents) { if (ULandscapeComponent* Component = BrushMaterialComponent.Get()) { PreviousComponents.Add(Component); } } // We're dealing with weak object ptrs. Now is a good time to remove the null entries (weak pointers can be invalidated) from our containers if any : // First, remove all components (the map's keys) that might have become stale (the map's value are also weak object ptrs to materials but they will be handled right after) : for (auto It= BrushMaterialInstanceMap.CreateIterator(); It; ++It) { if (!It.Key().IsValid()) { BrushMaterialFreeInstances.Push(It.Value()); It.RemoveCurrent(); } } // Now remove all material instances that might have become stale : for (auto It = BrushMaterialFreeInstances.CreateIterator(); It; ++It) { if (!It->IsValid()) { It.RemoveCurrent(); } } // Remove the material from any old components that are no longer in the region TSet RemovedComponents = PreviousComponents.Difference(NewComponents); for (ULandscapeComponent* RemovedComponent : RemovedComponents) { TWeakObjectPtr RemovedMaterialInstance; if (ensure(BrushMaterialInstanceMap.RemoveAndCopyValue(RemovedComponent, RemovedMaterialInstance))) { // We should have removed stale material instances already : check(RemovedMaterialInstance.IsValid()); BrushMaterialFreeInstances.Push(RemovedMaterialInstance); } RemovedComponent->EditToolRenderData.ToolMaterial = nullptr; RemovedComponent->UpdateEditToolRenderData(); } // Set brush material for components in new region TSet AddedComponents = NewComponents.Difference(PreviousComponents); for (ULandscapeComponent* AddedComponent : AddedComponents) { UMaterialInstanceDynamic* BrushMaterialInstance = nullptr; while (BrushMaterialFreeInstances.Num() > 0) { if (auto FreeBrushMaterialInstance = BrushMaterialFreeInstances.Pop(); FreeBrushMaterialInstance.IsValid()) { BrushMaterialInstance = FreeBrushMaterialInstance.Get(); break; } } if (!BrushMaterialInstance) { BrushMaterialInstance = UMaterialInstanceDynamic::Create(BrushMaterial, nullptr); } BrushMaterialInstanceMap.Add(AddedComponent, BrushMaterialInstance); AddedComponent->EditToolRenderData.ToolMaterial = BrushMaterialInstance; AddedComponent->UpdateEditToolRenderData(); } BrushMaterialComponents.Empty(); for (ULandscapeComponent* BrushMaterialComponent : NewComponents) { BrushMaterialComponents.Add(TWeakObjectPtr(BrushMaterialComponent)); } // Set params for brush material. FVector WorldLocation = Proxy->LandscapeActorToWorld().TransformPosition(FVector(LastMousePosition.X, LastMousePosition.Y, 0)); // Update whether the brush can actually paint : bCanPaint = CanPaint(NewComponents, bHasUnloadedComponents); for (const auto& BrushMaterialInstancePair : BrushMaterialInstanceMap) { ULandscapeComponent* const Component = BrushMaterialInstancePair.Key.Get(); UMaterialInstanceDynamic* const MaterialInstance = BrushMaterialInstancePair.Value.Get(); if (Component && MaterialInstance) { // Painting can cause the EditToolRenderData to be destructed, so update it if necessary if (!AddedComponents.Contains(Component)) { if (Component->EditToolRenderData.ToolMaterial == nullptr) { Component->EditToolRenderData.ToolMaterial = MaterialInstance; Component->UpdateEditToolRenderData(); } } MaterialInstance->SetScalarParameterValue(FName(TEXT("LocalRadius")), Radius); MaterialInstance->SetScalarParameterValue(FName(TEXT("LocalFalloff")), Falloff); MaterialInstance->SetDoubleVectorParameterValue(FName(TEXT("WorldPosition")), FVector4(WorldLocation.X, WorldLocation.Y, WorldLocation.Z, ScaleXY)); MaterialInstance->SetScalarParameterValue("CanPaint", bCanPaint ? 1.0f : 0.0f); } } } virtual void MouseMove(float LandscapeX, float LandscapeY) override { LastMousePosition = FVector2f(LandscapeX, LandscapeY); } virtual FLandscapeBrushData ApplyBrush(const TArray& InInteractorPositions) override { // bCanPaint is evaluated at an old mouse position, and also does not take interactor positions into account, // so cannot be relied upon to be correct vis-a-vis overlapping unloaded components. We check those again below. if (!bCanPaint) { return FLandscapeBrushData(); } ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); const float TotalRadius = EdMode->UISettings->GetCurrentToolBrushRadius() / ScaleXY; const float Radius = (1.0f - EdMode->UISettings->GetCurrentToolBrushFalloff()) * TotalRadius; const float Falloff = EdMode->UISettings->GetCurrentToolBrushFalloff() * TotalRadius; // Cap number of mouse positions to a sensible number TArray InteractorPositions; if (InInteractorPositions.Num() > 10) { for (int32 i = 0; i < 10; ++i) { // Scale so we include the first and last of the input positions InteractorPositions.Add(InInteractorPositions[(i * (InInteractorPositions.Num() - 1)) / 9]); } } else { InteractorPositions = InInteractorPositions; } FIntRect InclusiveBounds; for (const FLandscapeToolInteractorPosition& InteractorPosition : InteractorPositions) { FIntRect SpotInclusiveBounds; SpotInclusiveBounds.Min.X = FMath::FloorToInt32(InteractorPosition.Position.X - TotalRadius); SpotInclusiveBounds.Min.Y = FMath::FloorToInt32(InteractorPosition.Position.Y - TotalRadius); SpotInclusiveBounds.Max.X = FMath::CeilToInt32(InteractorPosition.Position.X + TotalRadius); SpotInclusiveBounds.Max.Y = FMath::CeilToInt32(InteractorPosition.Position.Y + TotalRadius); if (InclusiveBounds.IsEmpty()) { InclusiveBounds = SpotInclusiveBounds; } else { InclusiveBounds.Min = InclusiveBounds.Min.ComponentMin(SpotInclusiveBounds.Min); InclusiveBounds.Max = InclusiveBounds.Max.ComponentMax(SpotInclusiveBounds.Max); } } // Clamp to landscape bounds int32 MinX, MaxX, MinY, MaxY; if (!ensure(LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))) { // Landscape has no components somehow return FLandscapeBrushData(); } InclusiveBounds.Clip(FIntRect(MinX, MinY, MaxX, MaxY)); // check if region to be modified overlaps with an unloaded component // TODO [chris.tchou] : The +1 shouldn't be necessary, but without it, edges can be modified by the brush. Possibly a bug in the brush apply code. bool bHasUnloadedComponents = LandscapeInfo->HasUnloadedComponentsInRegion(InclusiveBounds.Min.X, InclusiveBounds.Min.Y, InclusiveBounds.Max.X+1, InclusiveBounds.Max.Y+1); if (bHasUnloadedComponents) { return FLandscapeBrushData(); } // FLandscapeBrushData requires exclusive bounds : FLandscapeBrushData BrushData(FIntRect(InclusiveBounds.Min, InclusiveBounds.Max + 1)); for (const FLandscapeToolInteractorPosition& InteractorPosition : InteractorPositions) { FIntRect SpotInclusiveBounds; SpotInclusiveBounds.Min.X = FMath::Max(FMath::FloorToInt32(InteractorPosition.Position.X - TotalRadius), InclusiveBounds.Min.X); SpotInclusiveBounds.Min.Y = FMath::Max(FMath::FloorToInt32(InteractorPosition.Position.Y - TotalRadius), InclusiveBounds.Min.Y); SpotInclusiveBounds.Max.X = FMath::Min(FMath::CeilToInt32(InteractorPosition.Position.X + TotalRadius), InclusiveBounds.Max.X); SpotInclusiveBounds.Max.Y = FMath::Min(FMath::CeilToInt32(InteractorPosition.Position.Y + TotalRadius), InclusiveBounds.Max.Y); for (int32 Y = SpotInclusiveBounds.Min.Y; Y <= SpotInclusiveBounds.Max.Y; Y++) { float* Scanline = BrushData.GetDataPtr(FIntPoint(0, Y)); for (int32 X = SpotInclusiveBounds.Min.X; X <= SpotInclusiveBounds.Max.X; X++) { float PrevAmount = Scanline[X]; if (PrevAmount < 1.0f) { // Distance from mouse float MouseDist = static_cast(FMath::Sqrt(FMath::Square(InteractorPosition.Position.X - X) + FMath::Square(InteractorPosition.Position.Y - Y))); float PaintAmount = CalculateFalloff(MouseDist, Radius, Falloff); if (PaintAmount > 0.0f) { if (EdMode->CurrentTool && EdMode->CurrentTool->GetToolType() != ELandscapeToolType::Mask && EdMode->UISettings->bUseSelectedRegion && LandscapeInfo->SelectedRegion.Num() > 0) { float MaskValue = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y)); if (EdMode->UISettings->bUseNegativeMask) { MaskValue = 1.0f - MaskValue; } PaintAmount *= MaskValue; } if (PaintAmount > PrevAmount) { // Set the brush value for this vertex Scanline[X] = PaintAmount; } } } } } } return BrushData; } }; // // FLandscapeBrushComponent // class FLandscapeBrushComponent : public FLandscapeBrush { using Super = FLandscapeBrush; TSet> BrushMaterialComponents; virtual const TCHAR* GetBrushName() override { return TEXT("Component"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Component", "Component"); }; protected: FVector2D LastMousePosition; TObjectPtr BrushMaterial; FIntRect BrushExtentsInclusive; // True extents of the brush (in landscape coordinates) public: FEdModeLandscape* EdMode; FLandscapeBrushComponent(FEdModeLandscape* InEdMode) : BrushMaterial(nullptr) , EdMode(InEdMode) { UMaterial* BaseBrushMaterial = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/SelectBrushMaterial.SelectBrushMaterial")); BrushMaterial = UE::Landscape::CreateToolLandscapeMaterialInstanceConstant(BaseBrushMaterial); } // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AddReferencedObjects(BrushMaterialComponents); Collector.AddReferencedObject(BrushMaterial); } virtual ELandscapeBrushType GetBrushType() override { return ELandscapeBrushType::Component; } virtual void LeaveBrush() override { for (decltype(BrushMaterialComponents)::TIterator It(BrushMaterialComponents); It; ++It) { if ((*It) != nullptr) { (*It)->EditToolRenderData.ToolMaterial = nullptr; (*It)->UpdateEditToolRenderData(); } } BrushMaterialComponents.Empty(); } virtual void BeginStroke(float LandscapeX, float LandscapeY, FLandscapeTool* CurrentTool) override { Super::BeginStroke(LandscapeX, LandscapeY, CurrentTool); LastMousePosition = FVector2D(LandscapeX, LandscapeY); } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { TSet NewComponents; // Adjusting the brush may use the same keybind as moving the camera as they can be user-set, so we need this second check. if (!ViewportClient->IsMovingCamera() || EdMode->IsAdjustingBrush(ViewportClient)) { ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); if (LandscapeInfo && LandscapeInfo->ComponentSizeQuads > 0) { const int32 BrushSize = FMath::Max(EdMode->UISettings->BrushComponentSize, 0); const float BrushOriginX = static_cast(LastMousePosition.X / LandscapeInfo->ComponentSizeQuads - (BrushSize - 1) / 2.0f); const float BrushOriginY = static_cast(LastMousePosition.Y / LandscapeInfo->ComponentSizeQuads - (BrushSize - 1) / 2.0f); const int32 ComponentIndexX = FMath::FloorToInt(BrushOriginX); const int32 ComponentIndexY = FMath::FloorToInt(BrushOriginY); BrushExtentsInclusive.Min = FIntPoint(ComponentIndexX * LandscapeInfo->ComponentSizeQuads, ComponentIndexY * LandscapeInfo->ComponentSizeQuads); BrushExtentsInclusive.Max = FIntPoint((ComponentIndexX + BrushSize) * LandscapeInfo->ComponentSizeQuads + 1, (ComponentIndexY + BrushSize) * LandscapeInfo->ComponentSizeQuads + 1); for (int32 YIndex = 0; YIndex < BrushSize; ++YIndex) { for (int32 XIndex = 0; XIndex < BrushSize; ++XIndex) { ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX + XIndex), (ComponentIndexY + YIndex))); if (Component && FLevelUtils::IsLevelVisible(Component->GetLandscapeProxy()->GetLevel())) { // For MoveToLevel if (EdMode->CurrentTool->GetToolName() == FName("MoveToLevel")) { if (Component->GetLandscapeProxy() && !Component->GetLandscapeProxy()->GetLevel()->IsCurrentLevel()) { NewComponents.Add(Component); } } else { NewComponents.Add(Component); } } } } // Set brush material for components in new region for (ULandscapeComponent* NewComponent : NewComponents) { NewComponent->EditToolRenderData.ToolMaterial = BrushMaterial; NewComponent->UpdateEditToolRenderData(); } } } // Remove the material from any old components that are no longer in the region TSet RemovedComponents = ObjectPtrDecay(BrushMaterialComponents).Difference(NewComponents); for (ULandscapeComponent* RemovedComponent : RemovedComponents) { if (RemovedComponent != nullptr) { RemovedComponent->EditToolRenderData.ToolMaterial = nullptr; RemovedComponent->UpdateEditToolRenderData(); } } BrushMaterialComponents = ObjectPtrWrap(MoveTemp(NewComponents)); } virtual void MouseMove(float LandscapeX, float LandscapeY) override { LastMousePosition = FVector2D(LandscapeX, LandscapeY); } virtual TOptional GetLastMousePosition() const override { return LastMousePosition; } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { // Selection Brush only works for ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); FIntRect ExclusiveBounds(INT_MAX, INT_MAX, INT_MIN, INT_MIN); // The add component tool needs the raw bounds of the brush rather than the bounds of the actually existing components under the brush if (EdMode->CurrentTool->GetToolName() == FName("AddComponent")) { const int32 BrushSize = FMath::Max(EdMode->UISettings->BrushComponentSize, 0); const float BrushOriginX = static_cast(LastMousePosition.X / LandscapeInfo->ComponentSizeQuads - (BrushSize - 1) / 2.0f); const float BrushOriginY = static_cast(LastMousePosition.Y / LandscapeInfo->ComponentSizeQuads - (BrushSize - 1) / 2.0f); const int32 ComponentIndexX = FMath::FloorToInt(BrushOriginX); const int32 ComponentIndexY = FMath::FloorToInt(BrushOriginY); ExclusiveBounds.Min.X = (ComponentIndexX) * LandscapeInfo->ComponentSizeQuads; ExclusiveBounds.Min.Y = (ComponentIndexY) * LandscapeInfo->ComponentSizeQuads; ExclusiveBounds.Max.X = (ComponentIndexX + BrushSize) * LandscapeInfo->ComponentSizeQuads + 1; ExclusiveBounds.Max.Y = (ComponentIndexY + BrushSize) * LandscapeInfo->ComponentSizeQuads + 1; } else { if (BrushMaterialComponents.Num() == 0) { return FLandscapeBrushData(); } // Get extent for all components for (ULandscapeComponent* Component : BrushMaterialComponents) { if (ensure(Component)) { Component->GetComponentExtent(ExclusiveBounds.Min.X, ExclusiveBounds.Min.Y, ExclusiveBounds.Max.X, ExclusiveBounds.Max.Y); } } // GetComponentExtent returns an inclusive max bound ExclusiveBounds.Max += FIntPoint(1, 1); } FLandscapeBrushData BrushData(ExclusiveBounds); for (int32 Y = ExclusiveBounds.Min.Y; Y < ExclusiveBounds.Max.Y; Y++) { float* Scanline = BrushData.GetDataPtr(FIntPoint(0, Y)); for (int32 X = ExclusiveBounds.Min.X; X < ExclusiveBounds.Max.X; X++) { const int32 ComponentIndexX = FMath::FloorToInt(static_cast(X) / LandscapeInfo->ComponentSizeQuads); const int32 ComponentIndexY = FMath::FloorToInt(static_cast(Y) / LandscapeInfo->ComponentSizeQuads); // Skip the pixels from the border if requested : if (EdMode->UISettings->bBrushComponentIncludeBorder || ((X > BrushExtentsInclusive.Min.X) && (X < (BrushExtentsInclusive.Max.X - 1)) && (Y > BrushExtentsInclusive.Min.Y) && (Y < (BrushExtentsInclusive.Max.Y - 1)))) { float PaintAmount = 1.0f; if (EdMode->CurrentTool && EdMode->CurrentTool->GetToolType() != ELandscapeToolType::Mask && EdMode->UISettings->bUseSelectedRegion && LandscapeInfo->SelectedRegion.Num() > 0) { float MaskValue = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y)); if (EdMode->UISettings->bUseNegativeMask) { MaskValue = 1.0f - MaskValue; } PaintAmount *= MaskValue; } // Set the brush value for this vertex Scanline[X] = PaintAmount; } } } return BrushData; } }; // // FLandscapeBrushGizmo // class FLandscapeBrushGizmo : public FLandscapeBrush { using Super = FLandscapeBrush; TSet> BrushMaterialComponents; const TCHAR* GetBrushName() override { return TEXT("Gizmo"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Gizmo", "Gizmo"); }; protected: TObjectPtr BrushMaterial; public: FEdModeLandscape* EdMode; FLandscapeBrushGizmo(FEdModeLandscape* InEdMode) : BrushMaterial(nullptr) , EdMode(InEdMode) { UMaterialInterface* GizmoMaterial = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/MaskBrushMaterial_Gizmo.MaskBrushMaterial_Gizmo")); BrushMaterial = UMaterialInstanceDynamic::Create(UE::Landscape::CreateToolLandscapeMaterialInstanceConstant(GizmoMaterial), nullptr); } // FGCObject interface virtual void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AddReferencedObjects(BrushMaterialComponents); Collector.AddReferencedObject(BrushMaterial); } virtual ELandscapeBrushType GetBrushType() override { return ELandscapeBrushType::Gizmo; } virtual void EnterBrush() override { // Make sure gizmo actor is selected ALandscapeGizmoActiveActor* Gizmo = EdMode->CurrentGizmoActor.Get(); if (Gizmo) { GEditor->SelectNone(false, true); GEditor->SelectActor(Gizmo, true, false, true); } } virtual void LeaveBrush() override { for (decltype(BrushMaterialComponents)::TIterator It(BrushMaterialComponents); It; ++It) { if ((*It) != nullptr) { (*It)->EditToolRenderData.ToolMaterial = nullptr; (*It)->UpdateEditToolRenderData(); } } BrushMaterialComponents.Empty(); } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { if (GLandscapeEditRenderMode & ELandscapeEditRenderMode::Gizmo || GLandscapeEditRenderMode & ELandscapeEditRenderMode::Select) { ALandscapeGizmoActiveActor* Gizmo = EdMode->CurrentGizmoActor.Get(); if (Gizmo && Gizmo->TargetLandscapeInfo && (Gizmo->TargetLandscapeInfo == EdMode->CurrentToolTarget.LandscapeInfo.Get()) && Gizmo->GizmoTexture && Gizmo->GetRootComponent()) { ULandscapeInfo* LandscapeInfo = Gizmo->TargetLandscapeInfo; if (LandscapeInfo && LandscapeInfo->GetLandscapeProxy()) { float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); FMatrix LToW = LandscapeInfo->GetLandscapeProxy()->LandscapeActorToWorld().ToMatrixWithScale(); FMatrix WToL = LToW.InverseFast(); UTexture2D* DataTexture = Gizmo->GizmoTexture; FIntRect Bounds(MAX_int32, MAX_int32, MIN_int32, MIN_int32); FVector LocalPos[4]; //FMatrix WorldToLocal = Proxy->LocalToWorld().Inverse(); for (int32 i = 0; i < 4; ++i) { //LocalPos[i] = WorldToLocal.TransformPosition(Gizmo->FrustumVerts[i]); LocalPos[i] = WToL.TransformPosition(Gizmo->FrustumVerts[i]); Bounds.Min.X = FMath::Min(Bounds.Min.X, (int32)LocalPos[i].X); Bounds.Min.Y = FMath::Min(Bounds.Min.Y, (int32)LocalPos[i].Y); Bounds.Max.X = FMath::Max(Bounds.Max.X, (int32)LocalPos[i].X); Bounds.Max.Y = FMath::Max(Bounds.Max.Y, (int32)LocalPos[i].Y); } // GetComponentsInRegion expects an inclusive max TSet NewComponents; LandscapeInfo->GetComponentsInRegion(Bounds.Min.X, Bounds.Min.Y, Bounds.Max.X - 1, Bounds.Max.Y - 1, NewComponents); float SquaredScaleXY = FMath::Square(ScaleXY); FLinearColor AlphaScaleBias( SquaredScaleXY / FMath::Max(1.f, Gizmo->GetWidth() * DataTexture->GetSizeX()), SquaredScaleXY / FMath::Max(1.f, Gizmo->GetHeight() * DataTexture->GetSizeY()), static_cast(Gizmo->TextureScale.X), static_cast(Gizmo->TextureScale.Y) ); BrushMaterial->SetVectorParameterValue(FName(TEXT("AlphaScaleBias")), AlphaScaleBias); float Angle = static_cast(-EdMode->CurrentGizmoActor->GetActorRotation().Euler().Z * PI / 180.0f); FVector4 LandscapeLocation(EdMode->CurrentGizmoActor->GetActorLocation().X, EdMode->CurrentGizmoActor->GetActorLocation().Y, EdMode->CurrentGizmoActor->GetActorLocation().Z, Angle); BrushMaterial->SetDoubleVectorParameterValue(FName(TEXT("LandscapeLocation")), LandscapeLocation); BrushMaterial->SetTextureParameterValue(FName(TEXT("AlphaTexture")), DataTexture); // Set brush material for components in new region for (ULandscapeComponent* NewComponent : NewComponents) { NewComponent->EditToolRenderData.GizmoMaterial = ((Gizmo->DataType != LGT_None) && (GLandscapeEditRenderMode & ELandscapeEditRenderMode::Gizmo) ? BrushMaterial : nullptr); NewComponent->UpdateEditToolRenderData(); } // Remove the material from any old components that are no longer in the region TSet RemovedComponents = ObjectPtrDecay(BrushMaterialComponents).Difference(NewComponents); for (ULandscapeComponent* RemovedComponent : RemovedComponents) { if (RemovedComponent != nullptr) { RemovedComponent->EditToolRenderData.GizmoMaterial = nullptr; RemovedComponent->UpdateEditToolRenderData(); } } BrushMaterialComponents = ObjectPtrWrap(MoveTemp(NewComponents)); } } } } virtual void MouseMove(float LandscapeX, float LandscapeY) override { } virtual TOptional InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override { if (InKey == EKeys::LeftMouseButton && InEvent == IE_Pressed) { int32 HitX = InViewport->GetMouseX(); int32 HitY = InViewport->GetMouseY(); HHitProxy* HitProxy = InViewport->GetHitProxy(HitX, HitY); HActor* ActorHitProxy = HitProxyCast(HitProxy); if (ActorHitProxy && ActorHitProxy->Actor->IsA()) { // don't treat clicks on a landscape gizmo as a tool invocation return TOptional(false); } } // default behaviour return TOptional(); } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { // Selection Brush only works for ALandscapeGizmoActiveActor* Gizmo = EdMode->CurrentGizmoActor.Get(); ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); if (!Gizmo || !Gizmo->GetRootComponent()) { return FLandscapeBrushData(); } if (BrushMaterialComponents.Num() == 0) { return FLandscapeBrushData(); } Gizmo->TargetLandscapeInfo = LandscapeInfo; float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); // Get extent for all components FIntRect ExclusiveBounds(INT_MAX, INT_MAX, INT_MIN, INT_MIN); for (ULandscapeComponent* Component : BrushMaterialComponents) { if (ensure(Component)) { Component->GetComponentExtent(ExclusiveBounds.Min.X, ExclusiveBounds.Min.Y, ExclusiveBounds.Max.X, ExclusiveBounds.Max.Y); } } FLandscapeBrushData BrushData(ExclusiveBounds); //FMatrix LandscapeToGizmoLocal = Landscape->LocalToWorld() * Gizmo->WorldToLocal(); const float LW = Gizmo->GetWidth() / (2 * ScaleXY); const float LH = Gizmo->GetHeight() / (2 * ScaleXY); FMatrix WToL = LandscapeInfo->GetLandscapeProxy()->LandscapeActorToWorld().ToMatrixWithScale().InverseFast(); FVector BaseLocation = WToL.TransformPosition(Gizmo->GetActorLocation()); FMatrix LandscapeToGizmoLocal = (FTranslationMatrix(FVector(-LW + 0.5, -LH + 0.5, 0)) * FRotationTranslationMatrix(FRotator(0, Gizmo->GetActorRotation().Yaw, 0), FVector(BaseLocation.X, BaseLocation.Y, 0))).InverseFast(); float W = Gizmo->GetWidth() / ScaleXY; //Gizmo->GetWidth() / (Gizmo->DrawScale * Gizmo->DrawScale3D.X); float H = Gizmo->GetHeight() / ScaleXY; //Gizmo->GetHeight() / (Gizmo->DrawScale * Gizmo->DrawScale3D.Y); for (int32 Y = ExclusiveBounds.Min.Y; Y < ExclusiveBounds.Max.Y; Y++) { float* Scanline = BrushData.GetDataPtr(FIntPoint(0, Y)); for (int32 X = ExclusiveBounds.Min.X; X < ExclusiveBounds.Max.X; X++) { FVector GizmoLocal = LandscapeToGizmoLocal.TransformPosition(FVector(X, Y, 0)); if (GizmoLocal.X < W && GizmoLocal.X > 0 && GizmoLocal.Y < H && GizmoLocal.Y > 0) { float PaintAmount = 1.0f; // Transform in 0,0 origin LW radius if (EdMode->UISettings->bSmoothGizmoBrush) { FVector TransformedLocal(FMath::Abs(GizmoLocal.X - LW), FMath::Abs(GizmoLocal.Y - LH) * (W / H), 0); float FalloffRadius = LW * EdMode->UISettings->GetCurrentToolBrushFalloff(); float SquareRadius = LW - FalloffRadius; float Cos = static_cast(FMath::Abs(TransformedLocal.X) / TransformedLocal.Size2D()); float Sin = static_cast(FMath::Abs(TransformedLocal.Y) / TransformedLocal.Size2D()); float RatioX = FalloffRadius > 0.0f ? 1.0f - FMath::Clamp(static_cast((FMath::Abs(TransformedLocal.X) - Cos*SquareRadius) / FalloffRadius), 0.0f, 1.0f) : 1.0f; float RatioY = FalloffRadius > 0.0f ? 1.0f - FMath::Clamp(static_cast((FMath::Abs(TransformedLocal.Y) - Sin*SquareRadius) / FalloffRadius), 0.0f, 1.0f) : 1.0f; float Ratio = TransformedLocal.Size2D() > SquareRadius ? RatioX * RatioY : 1.0f; //TransformedLocal.X / LW * TransformedLocal.Y / LW; PaintAmount = Ratio*Ratio*(3 - 2 * Ratio); //FMath::Lerp(SquareFalloff, RectFalloff*RectFalloff, Ratio); } if (PaintAmount) { if (EdMode->CurrentTool && EdMode->CurrentTool->GetToolType() != ELandscapeToolType::Mask && EdMode->UISettings->bUseSelectedRegion && LandscapeInfo->SelectedRegion.Num() > 0) { float MaskValue = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y)); if (EdMode->UISettings->bUseNegativeMask) { MaskValue = 1.0f - MaskValue; } PaintAmount *= MaskValue; } // Set the brush value for this vertex Scanline[X] = PaintAmount; } } } } return BrushData; } }; // // FLandscapeBrushSplines // class FLandscapeBrushSplines : public FLandscapeBrush { using Super = FLandscapeBrush; public: const TCHAR* GetBrushName() override { return TEXT("Splines"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Splines", "Splines"); }; FEdModeLandscape* EdMode; FLandscapeBrushSplines(FEdModeLandscape* InEdMode) : EdMode(InEdMode) { } virtual ~FLandscapeBrushSplines() { } virtual ELandscapeBrushType GetBrushType() override { return ELandscapeBrushType::Splines; } virtual void MouseMove(float LandscapeX, float LandscapeY) override { } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { return FLandscapeBrushData(); } }; // // FLandscapeBrushDummy // class FLandscapeBrushDummy : public FLandscapeBrush { using Super = FLandscapeBrush; public: const TCHAR* GetBrushName() override { return TEXT("Circle_Dummy"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_None", "None"); }; FEdModeLandscape* EdMode; FLandscapeBrushDummy(FEdModeLandscape* InEdMode) : EdMode(InEdMode) { } virtual ~FLandscapeBrushDummy() { } virtual ELandscapeBrushType GetBrushType() override { return ELandscapeBrushType::Normal; } virtual void MouseMove(float LandscapeX, float LandscapeY) override { } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { return FLandscapeBrushData(); } }; class FLandscapeBrushCircle_Linear : public FLandscapeBrushCircle { using Super = FLandscapeBrushCircle; protected: /** Protected so only subclasses can create instances. */ FLandscapeBrushCircle_Linear(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushCircle(InEdMode, InBrushMaterial) { } virtual float CalculateFalloff(float Distance, float Radius, float Falloff) override { return Distance < Radius ? 1.0f : Falloff > 0.0f ? FMath::Max(0.0f, 1.0f - (Distance - Radius) / Falloff) : 0.0f; } public: static FLandscapeBrushCircle_Linear* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* CircleBrushMaterial_Linear = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/CircleBrushMaterial_Linear.CircleBrushMaterial_Linear"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushCircle_Linear(InEdMode, CircleBrushMaterial_Linear); } virtual const TCHAR* GetBrushName() override { return TEXT("Circle_Linear"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Falloff_Linear", "Linear falloff"); }; }; class FLandscapeBrushCircle_Smooth : public FLandscapeBrushCircle_Linear { using Super = FLandscapeBrushCircle_Linear; protected: FLandscapeBrushCircle_Smooth(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushCircle_Linear(InEdMode, InBrushMaterial) { } virtual float CalculateFalloff(float Distance, float Radius, float Falloff) override { float Y = Super::CalculateFalloff(Distance, Radius, Falloff); // Smooth-step it return Y * Y * (3 - 2 * Y); } public: static FLandscapeBrushCircle_Smooth* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* CircleBrushMaterial_Smooth = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/CircleBrushMaterial_Smooth.CircleBrushMaterial_Smooth"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushCircle_Smooth(InEdMode, CircleBrushMaterial_Smooth); } virtual const TCHAR* GetBrushName() override { return TEXT("Circle_Smooth"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Falloff_Smooth", "Smooth falloff"); }; }; class FLandscapeBrushCircle_Spherical : public FLandscapeBrushCircle { using Super = FLandscapeBrushCircle; protected: FLandscapeBrushCircle_Spherical(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushCircle(InEdMode, InBrushMaterial) { } virtual float CalculateFalloff(float Distance, float Radius, float Falloff) override { if (Distance <= Radius) { return 1.0f; } if (Distance > Radius + Falloff) { return 0.0f; } // Elliptical falloff return FMath::Sqrt(1.0f - FMath::Square((Distance - Radius) / Falloff)); } public: static FLandscapeBrushCircle_Spherical* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* CircleBrushMaterial_Spherical = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/CircleBrushMaterial_Spherical.CircleBrushMaterial_Spherical"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushCircle_Spherical(InEdMode, CircleBrushMaterial_Spherical); } virtual const TCHAR* GetBrushName() override { return TEXT("Circle_Spherical"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Falloff_Spherical", "Spherical falloff"); }; }; class FLandscapeBrushCircle_Tip : public FLandscapeBrushCircle { using Super = FLandscapeBrushCircle_Tip; protected: FLandscapeBrushCircle_Tip(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushCircle(InEdMode, InBrushMaterial) { } virtual float CalculateFalloff(float Distance, float Radius, float Falloff) override { if (Distance <= Radius) { return 1.0f; } if (Distance > Radius + Falloff) { return 0.0f; } // inverse elliptical falloff return 1.0f - FMath::Sqrt(1.0f - FMath::Square((Falloff + Radius - Distance) / Falloff)); } public: static FLandscapeBrushCircle_Tip* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* CircleBrushMaterial_Tip = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/CircleBrushMaterial_Tip.CircleBrushMaterial_Tip"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushCircle_Tip(InEdMode, CircleBrushMaterial_Tip); } virtual const TCHAR* GetBrushName() override { return TEXT("Circle_Tip"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Falloff_Tip", "Tip falloff"); }; }; // FLandscapeBrushAlphaBase class FLandscapeBrushAlphaBase : public FLandscapeBrushCircle_Smooth { using Super = FLandscapeBrushCircle_Smooth; public: FLandscapeBrushAlphaBase(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushCircle_Smooth(InEdMode, InBrushMaterial) { } float GetAlphaSample(float SampleX, float SampleY) { if (!EdMode->UISettings->HasValidAlphaTextureData()) { return 1.0f; } int32 SizeX = EdMode->UISettings->AlphaTextureSizeX; int32 SizeY = EdMode->UISettings->AlphaTextureSizeY; check((SizeX > 0) && (SizeY > 0)); // Bilinear interpolate the values from the alpha texture int32 SampleX0 = FMath::FloorToInt(SampleX); int32 SampleX1 = (SampleX0 + 1) % SizeX; int32 SampleY0 = FMath::FloorToInt(SampleY); int32 SampleY1 = (SampleY0 + 1) % SizeY; check(!EdMode->UISettings->AlphaTextureData.IsEmpty()); const uint8* AlphaData = EdMode->UISettings->AlphaTextureData.GetData(); float Alpha00 = (float)AlphaData[SampleX0 + SampleY0 * SizeX] / 255.0f; float Alpha01 = (float)AlphaData[SampleX0 + SampleY1 * SizeX] / 255.0f; float Alpha10 = (float)AlphaData[SampleX1 + SampleY0 * SizeX] / 255.0f; float Alpha11 = (float)AlphaData[SampleX1 + SampleY1 * SizeX] / 255.0f; return FMath::Lerp( FMath::Lerp(Alpha00, Alpha01, FMath::Fractional(SampleX)), FMath::Lerp(Alpha10, Alpha11, FMath::Fractional(SampleX)), FMath::Fractional(SampleY) ); } }; // // FLandscapeBrushAlphaPattern // class FLandscapeBrushAlphaPattern : public FLandscapeBrushAlphaBase { using Super = FLandscapeBrushAlphaBase; protected: FLandscapeBrushAlphaPattern(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushAlphaBase(InEdMode, InBrushMaterial) { } public: static FLandscapeBrushAlphaPattern* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* PatternBrushMaterial = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/PatternBrushMaterial_Smooth.PatternBrushMaterial_Smooth"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushAlphaPattern(InEdMode, PatternBrushMaterial); } virtual ELandscapeBrushType GetBrushType() override { return ELandscapeBrushType::Alpha; } virtual bool CanPaint(const TSet& InAffectedComponents, bool bInHasUnloadedComponents) const override { if (!Super::CanPaint(InAffectedComponents, bInHasUnloadedComponents)) { return false; } return EdMode->UISettings->HasValidAlphaTextureData(); } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { if (!bCanPaint) { return FLandscapeBrushData(); } ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); const float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); const float TotalRadius = EdMode->UISettings->GetCurrentToolBrushRadius() / ScaleXY; const float Radius = (1.0f - EdMode->UISettings->GetCurrentToolBrushFalloff()) * TotalRadius; const float Falloff = EdMode->UISettings->GetCurrentToolBrushFalloff() * TotalRadius; int32 SizeX = EdMode->UISettings->AlphaTextureSizeX; int32 SizeY = EdMode->UISettings->AlphaTextureSizeY; check(EdMode->UISettings->HasValidAlphaTextureData() && (SizeX > 0) && (SizeY > 0)); // See CanPaint() above : if we can paint, these must all be valid FIntRect InclusiveBounds; InclusiveBounds.Min.X = FMath::FloorToInt(LastMousePosition.X - TotalRadius); InclusiveBounds.Min.Y = FMath::FloorToInt(LastMousePosition.Y - TotalRadius); InclusiveBounds.Max.X = FMath::CeilToInt(LastMousePosition.X + TotalRadius); InclusiveBounds.Max.Y = FMath::CeilToInt(LastMousePosition.Y + TotalRadius); // Clamp to landscape bounds int32 MinX, MaxX, MinY, MaxY; if (!ensure(LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))) { // Landscape has no components somehow return FLandscapeBrushData(); } InclusiveBounds.Clip(FIntRect(MinX, MinY, MaxX, MaxY)); // FLandscapeBrushData requires exclusive bounds : FLandscapeBrushData BrushData(FIntRect(InclusiveBounds.Min, InclusiveBounds.Max + 1)); for (int32 Y = InclusiveBounds.Min.Y; Y <= InclusiveBounds.Max.Y; Y++) { float* Scanline = BrushData.GetDataPtr(FIntPoint(0, Y)); for (int32 X = InclusiveBounds.Min.X; X <= InclusiveBounds.Max.X; X++) { float Angle; FVector2D Scale; FVector2D Bias; if (EdMode->UISettings->bUseWorldSpacePatternBrush) { FVector2D LocalOrigin = -FVector2D(LandscapeInfo->GetLandscapeProxy()->LandscapeActorToWorld().InverseTransformPosition(FVector(EdMode->UISettings->WorldSpacePatternBrushSettings.Origin, 0.0f))); const FVector2D LocalScale = FVector2D( ScaleXY / (EdMode->UISettings->WorldSpacePatternBrushSettings.RepeatSize * ((float)SizeX / SizeY)), ScaleXY / (EdMode->UISettings->WorldSpacePatternBrushSettings.RepeatSize)); LocalOrigin *= LocalScale; Angle = -EdMode->UISettings->WorldSpacePatternBrushSettings.Rotation; if (EdMode->UISettings->WorldSpacePatternBrushSettings.bCenterTextureOnOrigin) { LocalOrigin += FVector2D(0.5f, 0.5f).GetRotated(-Angle); } Scale = FVector2D(SizeX, SizeY) * LocalScale; Bias = FVector2D(SizeX, SizeY) * LocalOrigin; } else { Scale.X = 1.0f / EdMode->UISettings->AlphaBrushScale; Scale.Y = 1.0f / EdMode->UISettings->AlphaBrushScale; Bias.X = SizeX * EdMode->UISettings->AlphaBrushPanU; Bias.Y = SizeY * EdMode->UISettings->AlphaBrushPanV; Angle = EdMode->UISettings->AlphaBrushRotation; } // Find alphamap sample location FVector2D SamplePos = FVector2D(X, Y) * Scale + Bias; SamplePos = SamplePos.GetRotated(Angle); float ModSampleX = FMath::Fmod(static_cast(SamplePos.X), static_cast(SizeX)); float ModSampleY = FMath::Fmod(static_cast(SamplePos.Y), static_cast(SizeY)); if (ModSampleX < 0.0f) { ModSampleX += (float)SizeX; } if (ModSampleY < 0.0f) { ModSampleY += (float)SizeY; } // Sample the alpha texture float Alpha = GetAlphaSample(ModSampleX, ModSampleY); // Distance from mouse float MouseDist = FMath::Sqrt(FMath::Square(LastMousePosition.X - (float)X) + FMath::Square(LastMousePosition.Y - (float)Y)); float PaintAmount = CalculateFalloff(MouseDist, Radius, Falloff) * Alpha; if (PaintAmount > 0.0f) { if (EdMode->CurrentTool && EdMode->CurrentTool->GetToolType() != ELandscapeToolType::Mask && EdMode->UISettings->bUseSelectedRegion && LandscapeInfo->SelectedRegion.Num() > 0) { float MaskValue = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y)); if (EdMode->UISettings->bUseNegativeMask) { MaskValue = 1.0f - MaskValue; } PaintAmount *= MaskValue; } // Set the brush value for this vertex Scanline[X] = PaintAmount; } } } return BrushData; } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { Super::Tick(ViewportClient, DeltaTime); if (const ALandscapeProxy* Proxy = EdMode->CurrentToolTarget.LandscapeInfo.IsValid() ? EdMode->CurrentToolTarget.LandscapeInfo->GetLandscapeProxy() : nullptr) { const float ScaleXY = static_cast(FMath::Abs(EdMode->CurrentToolTarget.LandscapeInfo->DrawScale.X)); const bool bHasValidAlphaTextureData = EdMode->UISettings->HasValidAlphaTextureData(); int32 SizeX = EdMode->UISettings->AlphaTextureSizeX; int32 SizeY = EdMode->UISettings->AlphaTextureSizeY; check(!bHasValidAlphaTextureData || ((SizeX > 0) && (SizeY > 0))); FLinearColor AlphaScaleBias(ForceInitToZero); float Angle; if (EdMode->UISettings->bUseWorldSpacePatternBrush) { FVector2D LocalOrigin = -FVector2D(Proxy->LandscapeActorToWorld().InverseTransformPosition(FVector(EdMode->UISettings->WorldSpacePatternBrushSettings.Origin, 0.0f))); FVector2D Scale(ForceInitToZero); if (bHasValidAlphaTextureData) { Scale = FVector2D( ScaleXY / (EdMode->UISettings->WorldSpacePatternBrushSettings.RepeatSize * ((float)SizeX / SizeY)), ScaleXY / (EdMode->UISettings->WorldSpacePatternBrushSettings.RepeatSize)); } LocalOrigin *= Scale; Angle = -EdMode->UISettings->WorldSpacePatternBrushSettings.Rotation; if (EdMode->UISettings->WorldSpacePatternBrushSettings.bCenterTextureOnOrigin) { LocalOrigin += FVector2D(0.5f, 0.5f).GetRotated(-Angle); } AlphaScaleBias = FLinearColor( static_cast(Scale.X), static_cast(Scale.Y), static_cast(LocalOrigin.X), static_cast(LocalOrigin.Y)); } else { const FVector2D Scale(EdMode->UISettings->AlphaBrushScale * SizeX, EdMode->UISettings->AlphaBrushScale * SizeY); AlphaScaleBias = FLinearColor( static_cast(FMath::IsNearlyZero(Scale.X) ? 1.0 : 1.0 / (Scale.X)), static_cast(FMath::IsNearlyZero(Scale.Y) ? 1.0 : 1.0 / (Scale.Y)), EdMode->UISettings->AlphaBrushPanU, EdMode->UISettings->AlphaBrushPanV); Angle = EdMode->UISettings->AlphaBrushRotation; } Angle = FMath::DegreesToRadians(Angle); FVector LandscapeLocation = Proxy->LandscapeActorToWorld().GetTranslation(); FVector4 LandscapeLocationParam(LandscapeLocation.X, LandscapeLocation.Y, LandscapeLocation.Z, Angle); FLinearColor AlphaTextureMask( EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Red ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Green ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Blue ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Alpha ? 1.0f : 0.0f); for (const auto& BrushMaterialInstancePair : BrushMaterialInstanceMap) { UMaterialInstanceDynamic* const MaterialInstance = BrushMaterialInstancePair.Value.Get(); if (!MaterialInstance) { continue; } MaterialInstance->SetVectorParameterValue(FName(TEXT("AlphaScaleBias")), AlphaScaleBias); MaterialInstance->SetDoubleVectorParameterValue(FName(TEXT("LandscapeLocation")), LandscapeLocationParam); MaterialInstance->SetVectorParameterValue(FName(TEXT("AlphaTextureMask")), AlphaTextureMask); MaterialInstance->SetTextureParameterValue(FName(TEXT("AlphaTexture")), EdMode->UISettings->AlphaTexture); } } } virtual const TCHAR* GetBrushName() override { return TEXT("Pattern"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_PatternAlpha", "Pattern Alpha"); }; }; // // FLandscapeBrushAlpha // class FLandscapeBrushAlpha : public FLandscapeBrushAlphaBase { using Super = FLandscapeBrushAlphaBase; float LastMouseAngle; FVector2f OldMousePosition; // a previous mouse position, kept until we move a certain distance away, for smoothing deltas double LastMouseSampleTime; protected: FLandscapeBrushAlpha(FEdModeLandscape* InEdMode, UMaterialInterface* InBrushMaterial) : FLandscapeBrushAlphaBase(InEdMode, InBrushMaterial) , LastMouseAngle(0.0f) , OldMousePosition(0.0f, 0.0f) , LastMouseSampleTime(FPlatformTime::Seconds()) { } public: static FLandscapeBrushAlpha* Create(FEdModeLandscape* InEdMode) { UMaterialInstanceConstant* AlphaBrushMaterial = LoadObject(nullptr, TEXT("/Engine/EditorLandscapeResources/AlphaBrushMaterial_Smooth.AlphaBrushMaterial_Smooth"), nullptr, LOAD_None, nullptr); return new FLandscapeBrushAlpha(InEdMode, AlphaBrushMaterial); } bool ComputeAlphaBrushScaleAndRadius(float& OutAlphaBrushScale, float& OutRadius, int32& OutSizeX, int32& OutSizeY) const { ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); int32 SizeX = EdMode->UISettings->AlphaTextureSizeX; int32 SizeY = EdMode->UISettings->AlphaTextureSizeY; bool bIsValidTextureSize = (SizeX > 0) && (SizeY > 0); float ScaleXY = static_cast(FMath::Abs(LandscapeInfo->DrawScale.X)); float Radius = FMath::IsNearlyZero(ScaleXY) ? 0.0f : EdMode->UISettings->GetCurrentToolBrushRadius() / ScaleXY; float MaxSize = 2.0f * FMath::Sqrt(FMath::Square(Radius) / 2.0f); float FinalScale = bIsValidTextureSize ? MaxSize / (float)FMath::Max(SizeX, SizeY) : 0.0f; if (FMath::IsNearlyZero(FinalScale)) { return false; } check(bIsValidTextureSize); OutAlphaBrushScale = FinalScale; OutRadius = Radius; OutSizeX = SizeX; OutSizeY = SizeY; return true; } virtual bool CanPaint(const TSet& InAffectedComponents, bool bInHasUnloadedComponents) const override { if (!Super::CanPaint(InAffectedComponents, bInHasUnloadedComponents)) { return false; } if (!EdMode->UISettings->HasValidAlphaTextureData()) { return false; } float AlphaBrushScale; float Radius; int32 SizeX; int32 SizeY; if (!ComputeAlphaBrushScaleAndRadius(AlphaBrushScale, Radius, SizeX, SizeY)) { return false; } return true; } virtual FLandscapeBrushData ApplyBrush(const TArray& InteractorPositions) override { if (!bCanPaint) { return FLandscapeBrushData(); } ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); if (EdMode->UISettings->bAlphaBrushAutoRotate && OldMousePosition.IsZero()) { OldMousePosition = LastMousePosition; LastMouseAngle = 0.0f; LastMouseSampleTime = FPlatformTime::Seconds(); return FLandscapeBrushData(); } else { float AlphaBrushScale; float Radius; int32 SizeX; int32 SizeY; bool bIsValid = ComputeAlphaBrushScaleAndRadius(AlphaBrushScale, Radius, SizeX, SizeY); check(bIsValid && !FMath::IsNearlyZero(AlphaBrushScale) && !FMath::IsNearlyZero(Radius) && (SizeX > 0) && (SizeY > 0)); // See CanPaint() function above : if bCanPaint, AlphaBrushScale should be non-zero const float BrushAngle = EdMode->UISettings->bAlphaBrushAutoRotate ? LastMouseAngle : FMath::DegreesToRadians(EdMode->UISettings->AlphaBrushRotation); FIntRect InclusiveBounds; InclusiveBounds.Min.X = FMath::FloorToInt(LastMousePosition.X - Radius); InclusiveBounds.Min.Y = FMath::FloorToInt(LastMousePosition.Y - Radius); InclusiveBounds.Max.X = FMath::CeilToInt(LastMousePosition.X + Radius); InclusiveBounds.Max.Y = FMath::CeilToInt(LastMousePosition.Y + Radius); // Clamp to landscape bounds int32 MinX, MaxX, MinY, MaxY; if (!ensure(LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))) { // Landscape has no components somehow return FLandscapeBrushData(); } InclusiveBounds.Clip(FIntRect(MinX, MinY, MaxX, MaxY)); // FLandscapeBrushData requires exclusive bounds : FLandscapeBrushData BrushData(FIntRect(InclusiveBounds.Min, InclusiveBounds.Max + 1)); for (int32 Y = InclusiveBounds.Min.Y; Y <= InclusiveBounds.Max.Y; Y++) { float* Scanline = BrushData.GetDataPtr(FIntPoint(0, Y)); for (int32 X = InclusiveBounds.Min.X; X <= InclusiveBounds.Max.X; X++) { // Find alphamap sample location float ScaleSampleX = ((float)X - LastMousePosition.X) / AlphaBrushScale; float ScaleSampleY = ((float)Y - LastMousePosition.Y) / AlphaBrushScale; // Rotate around center to match angle float SampleX = ScaleSampleX * FMath::Cos(BrushAngle) - ScaleSampleY * FMath::Sin(BrushAngle); float SampleY = ScaleSampleY * FMath::Cos(BrushAngle) + ScaleSampleX * FMath::Sin(BrushAngle); SampleX += (float)SizeX * 0.5f; SampleY += (float)SizeY * 0.5f; if (SampleX >= 0 && SampleX <= (SizeX - 1) && SampleY >= 0 && SampleY <= (SizeY - 1)) { // Sample the alpha texture float Alpha = GetAlphaSample(SampleX, SampleY); if (Alpha > 0.0f) { // Set the brush value for this vertex FIntPoint VertexKey = FIntPoint(X, Y); if (EdMode->CurrentTool && EdMode->CurrentTool->GetToolType() != ELandscapeToolType::Mask && EdMode->UISettings->bUseSelectedRegion && LandscapeInfo->SelectedRegion.Num() > 0) { float MaskValue = LandscapeInfo->SelectedRegion.FindRef(VertexKey); if (EdMode->UISettings->bUseNegativeMask) { MaskValue = 1.0f - MaskValue; } Alpha *= MaskValue; } Scanline[X] = Alpha; } } } } return BrushData; } } virtual void MouseMove(float LandscapeX, float LandscapeY) override { Super::MouseMove(LandscapeX, LandscapeY); if (EdMode->UISettings->bAlphaBrushAutoRotate) { // don't do anything with the angle unless we move at least 0.1 units. FVector2f MouseDelta = LastMousePosition - OldMousePosition; if (MouseDelta.SizeSquared() >= FMath::Square(0.5f)) { double SampleTime = FPlatformTime::Seconds(); float DeltaTime = (float)(SampleTime - LastMouseSampleTime); FVector2f MouseDirection = MouseDelta.GetSafeNormal(); float MouseAngle = FMath::Lerp(LastMouseAngle, FMath::Atan2(-MouseDirection.Y, MouseDirection.X), FMath::Min(10.0f * DeltaTime, 1.0f)); // lerp over 100ms LastMouseAngle = MouseAngle; LastMouseSampleTime = SampleTime; OldMousePosition = LastMousePosition; // UE_LOG(LogLandscape, Log, TEXT("(%f,%f) delta (%f,%f) angle %f"), LandscapeX, LandscapeY, MouseDirection.X, MouseDirection.Y, MouseAngle); } } } virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override { Super::Tick(ViewportClient, DeltaTime); ULandscapeInfo* LandscapeInfo = EdMode->CurrentToolTarget.LandscapeInfo.Get(); if (LandscapeInfo) { FVector2f Scale(1.0f, 1.0f); float AlphaBrushScale; float Radius; int32 SizeX; int32 SizeY; bool bIsValid = ComputeAlphaBrushScaleAndRadius(AlphaBrushScale, Radius, SizeX, SizeY); if (bIsValid) { check(!FMath::IsNearlyZero(AlphaBrushScale) && !FMath::IsNearlyZero(Radius) && (SizeX > 0) && (SizeY > 0)); // See CanPaint() function above : if bCanPaint, AlphaBrushScale should be non-zero Scale = FVector2f(1.0f / (AlphaBrushScale * SizeX), 1.0f / (AlphaBrushScale * SizeY)); } FLinearColor BrushScaleRot( Scale.X, Scale.Y, 0.0f, EdMode->UISettings->bAlphaBrushAutoRotate ? LastMouseAngle : FMath::DegreesToRadians(EdMode->UISettings->AlphaBrushRotation) ); FLinearColor AlphaTextureMask(EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Red ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Green ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Blue ? 1.0f : 0.0f, EdMode->UISettings->AlphaTextureChannel == ELandscapeTextureColorChannel::Alpha ? 1.0f : 0.0f); for (const auto& BrushMaterialInstancePair : BrushMaterialInstanceMap) { UMaterialInstanceDynamic* const MaterialInstance = BrushMaterialInstancePair.Value.Get(); if (!MaterialInstance) { continue; } MaterialInstance->SetVectorParameterValue(FName(TEXT("BrushScaleRot")), BrushScaleRot); MaterialInstance->SetVectorParameterValue(FName(TEXT("AlphaTextureMask")), AlphaTextureMask); MaterialInstance->SetTextureParameterValue(FName(TEXT("AlphaTexture")), EdMode->UISettings->AlphaTexture); } } } virtual const TCHAR* GetBrushName() override { return TEXT("Alpha"); } virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Brush_Alpha", "Alpha"); }; }; void FEdModeLandscape::InitializeBrushes() { FLandscapeBrushSet* BrushSet; BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Circle")); BrushSet->Brushes.Add(FLandscapeBrushCircle_Smooth::Create(this)); BrushSet->Brushes.Add(FLandscapeBrushCircle_Linear::Create(this)); BrushSet->Brushes.Add(FLandscapeBrushCircle_Spherical::Create(this)); BrushSet->Brushes.Add(FLandscapeBrushCircle_Tip::Create(this)); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Alpha")); BrushSet->Brushes.Add(FLandscapeBrushAlpha::Create(this)); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Pattern")); BrushSet->Brushes.Add(FLandscapeBrushAlphaPattern::Create(this)); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Component")); BrushSet->Brushes.Add(new FLandscapeBrushComponent(this)); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Gizmo")); GizmoBrush = new FLandscapeBrushGizmo(this); BrushSet->Brushes.Add(GizmoBrush); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Splines")); BrushSet->Brushes.Add(new FLandscapeBrushSplines(this)); BrushSet = new(LandscapeBrushSets)FLandscapeBrushSet(TEXT("BrushSet_Dummy")); BrushSet->Brushes.Add(new FLandscapeBrushDummy(this)); }