// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "LandscapeEditorPrivatePCH.h" #include "ObjectTools.h" #include "LandscapeEdMode.h" #include "ScopedTransaction.h" #include "Landscape/LandscapeEdit.h" #include "Landscape/LandscapeRender.h" #include "Landscape/LandscapeDataAccess.h" #include "Landscape/LandscapeSplineProxies.h" #include "LandscapeEditorModule.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "LandscapeEdModeTools.h" #include "Landscape/Landscape.h" namespace { FORCEINLINE FVector4 GetWorldPos(const FMatrix& LocalToWorld, FVector2D LocalXY, uint16 Height, FVector2D XYOffset) { return LocalToWorld.TransformPosition( FVector( LocalXY.X + XYOffset.X, LocalXY.Y + XYOffset.Y, ((float)Height - 32768.f) * LANDSCAPE_ZSCALE ) ); } FORCEINLINE FVector4 GetWorldPos(const FMatrix& LocalToWorld, FVector2D LocalXY, FVector XYOffsetVector) { return LocalToWorld.TransformPosition( FVector( LocalXY.X + XYOffsetVector.X, LocalXY.Y + XYOffsetVector.Y, XYOffsetVector.Z ) ); } const int32 XOffsets[4] = { 0, 1, 0, 1 }; const int32 YOffsets[4] = { 0, 0, 1, 1 }; float GetHeight(int32 X, int32 Y, int32 MinX, int32 MinY, int32 MaxX, int32 MaxY, const FVector& XYOffset, const TArray& XYOffsetVectorData ) { float Height[4]; for (int32 Idx = 0; Idx < 4; ++Idx) { int32 XX = FMath::Clamp(FMath::FloorToInt(X+XYOffset.X + XOffsets[Idx]), MinX, MaxX); int32 YY = FMath::Clamp(FMath::FloorToInt(Y+XYOffset.Y + YOffsets[Idx]), MinY, MaxY); Height[Idx] = XYOffsetVectorData[ XX - MinX + (YY - MinY) * (MaxX-MinX+1) ].Z; } float FracX = FMath::Fractional(X+XYOffset.X); float FracY = FMath::Fractional(Y+XYOffset.Y); return FMath::Lerp( FMath::Lerp(Height[0], Height[1], FracX), FMath::Lerp(Height[2], Height[3], FracX), FracY); } }; // // FLandscapeToolRetopologize // class FLandscapeToolStrokeRetopologize { public: FLandscapeToolStrokeRetopologize(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget) : LandscapeInfo(InTarget.LandscapeInfo.Get()) , Cache(InTarget) {} virtual void Apply(FLevelEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray& MousePositions) { if (!LandscapeInfo) { return; } // Get list of verts to update TMap BrushInfo; int32 X1, Y1, X2, Y2; if (!Brush->ApplyBrush(MousePositions, BrushInfo, X1, Y1, X2, Y2)) { return; } //LandscapeInfo->Modify(); //LandscapeInfo->LandscapeProxy->Modify(); FVector DrawScale3D = LandscapeInfo->DrawScale; // Tablet pressure float Pressure = ViewportClient->Viewport->IsPenActive() ? ViewportClient->Viewport->GetTabletPressure() : 1.f; // expand the area by one vertex in each direction to ensure normals are calculated correctly /* X1 -= 1; Y1 -= 1; X2 += 1; Y2 += 1; */ { int32 ValidX1, ValidX2, ValidY1, ValidY2; ValidX1 = ValidY1 = INT_MAX; ValidX2 = ValidY2 = INT_MIN; int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2; int32 ComponentSizeQuads = LandscapeInfo->ComponentSizeQuads; ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2); for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ ) { for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ ) { ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY)); if (Component) { // Update valid region ValidX1 = FMath::Min(Component->GetSectionBase().X, ValidX1); ValidX2 = FMath::Max(Component->GetSectionBase().X+ComponentSizeQuads, ValidX2); ValidY1 = FMath::Min(Component->GetSectionBase().Y, ValidY1); ValidY2 = FMath::Max(Component->GetSectionBase().Y+ComponentSizeQuads, ValidY2); } } } X1 = FMath::Max(X1, ValidX1); X2 = FMath::Min(X2, ValidX2); Y1 = FMath::Max(Y1, ValidY1); Y2 = FMath::Min(Y2, ValidY2); } if (X1 > X2 || Y1 > Y2) // No valid region... { return; } const float AreaResolution = LANDSCAPE_XYOFFSET_SCALE; //1.f/256.f; Cache.CacheData(X1,Y1,X2,Y2); TArray XYOffsetVectorData; Cache.GetCachedData(X1,Y1,X2,Y2,XYOffsetVectorData); TArray NewXYOffset; NewXYOffset = XYOffsetVectorData; // Retopologize algorithm... { // Calculate surface world space area without missing area... float TotalArea = 0.f; int32 QuadNum = 0; const int32 MaxIterNum = 300; TArray QuadX, QuadY, MinX, MaxX, MinY, MaxY; QuadX.AddZeroed( X2 - X1 ); QuadY.AddZeroed( Y2 - Y1 ); MinX.Empty( Y2 - Y1 + 1 ); MaxX.Empty( Y2 - Y1 + 1 ); MinY.Empty( X2 - X1 + 1 ); MaxY.Empty( X2 - X1 + 1 ); for (int32 X = X1; X <= X2; ++X) { MinY.Add(INT_MAX); MaxY.Add(INT_MIN); } for (int32 Y = Y1; Y <= Y2; ++Y) { MinX.Add(INT_MAX); MaxX.Add(INT_MIN); } // Calculate Average... TArray ComponentArray; // Ptr to component ComponentArray.AddZeroed( (X2-X1+1)*(Y2-Y1+1) ); int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2; int32 ComponentSizeQuads = LandscapeInfo->ComponentSizeQuads; ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2); for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ ) { for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ ) { ULandscapeComponent* Comp = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY)); if (Comp) { const FMatrix LocalToWorld = Comp->GetRenderMatrix(); // Find coordinates of box that lies inside component int32 ComponentX1 = FMath::Clamp(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads); int32 ComponentY1 = FMath::Clamp(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads); int32 ComponentX2 = FMath::Clamp(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads); int32 ComponentY2 = FMath::Clamp(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads); // World space area calculation for (int32 Y = ComponentY1; Y <= ComponentY2; ++Y ) { for (int32 X = ComponentX1; X <= ComponentX2; ++X ) { if ( X < ComponentX2 && Y < ComponentY2) { // Need to read XY Offset value from XYOffsetTexture before this FVector P[4]; for (int32 Idx = 0; Idx < 4; ++Idx) { int32 XX = X + XOffsets[Idx]; int32 YY = Y + YOffsets[Idx]; P[Idx] = FVector(GetWorldPos(LocalToWorld, FVector2D(XX, YY), XYOffsetVectorData[ (ComponentIndexX*ComponentSizeQuads + XX - X1) + (ComponentIndexY*ComponentSizeQuads + YY - Y1)*(X2-X1+1) ] )); } TotalArea += (((P[3] - P[0]) ^ (P[1] - P[0])).Size() + ((P[3] - P[0]) ^ (P[2] - P[0])).Size()) * 0.5f; QuadNum++; QuadX[ ComponentIndexX*ComponentSizeQuads + X - X1 ]++; QuadY[ ComponentIndexY*ComponentSizeQuads + Y - Y1 ]++; // Mark valid quad position ComponentArray[ (ComponentIndexX*ComponentSizeQuads) + X - X1 + (ComponentIndexY*ComponentSizeQuads + Y - Y1)*(X2-X1+1) ] = Comp; } MinX[ComponentIndexY*ComponentSizeQuads+Y-Y1] = FMath::Min(MinX[ComponentIndexY*ComponentSizeQuads+Y-Y1], ComponentIndexX*ComponentSizeQuads + X); MaxX[ComponentIndexY*ComponentSizeQuads+Y-Y1] = FMath::Max(MaxX[ComponentIndexY*ComponentSizeQuads+Y-Y1], ComponentIndexX*ComponentSizeQuads + X); MinY[ComponentIndexX*ComponentSizeQuads+X-X1] = FMath::Min(MinY[ComponentIndexX*ComponentSizeQuads+X-X1], ComponentIndexY*ComponentSizeQuads + Y); MaxY[ComponentIndexX*ComponentSizeQuads+X-X1] = FMath::Max(MaxY[ComponentIndexX*ComponentSizeQuads+X-X1], ComponentIndexY*ComponentSizeQuads + Y); } } } } } const float HeightErrorThreshold = DrawScale3D.X * 0.5f; // Made XYOffset and new Z value allocation... float AreaErrorThreshold = FMath::Square(AreaResolution); float RemainArea = TotalArea; int32 RemainQuads = QuadNum; for (int32 Y = Y1; Y < Y2-1; ++Y) { // Like rasterization style // Search for Y offset if (MinX[Y-Y1] > MaxX[Y-Y1]) { continue; } float AverageArea = RemainArea / RemainQuads; float AreaBaseError = AverageArea * 0.5f; float TotalLineArea = 0.f; float TargetLineArea = AverageArea * QuadY[Y - Y1]; float YOffset = Y+1, PreYOffset = Y+1; // Need to be bigger than previous Y float StepSize = FPlatformMath::Sqrt(2) * 0.25f; float LineAreaDiff = FLT_MAX; //Abs(TotalLineArea - TargetLineArea); int32 IterNum = 0; float TotalHeightError = 0.f; while( NewXYOffset[ (Y - Y1) * (X2-X1+1) ].Y + Y > XYOffsetVectorData[(FPlatformMath::FloorToInt(YOffset) - Y1) * (X2-X1+1) ].Y + FPlatformMath::FloorToInt(YOffset) ) { YOffset = YOffset+1.f; if (YOffset >= Y2) { YOffset = Y2; break; } } PreYOffset = YOffset; while (FMath::Abs(TotalLineArea - TargetLineArea) > AreaErrorThreshold) { IterNum++; TotalLineArea = 0.f; TotalHeightError = 0.f; //for (int32 X = X1; X < X2; ++X) for (int32 X = MinX[Y-Y1]; X < MaxX[Y-Y1]; ++X) { ULandscapeComponent* Comp = ComponentArray[X - X1 + (Y - Y1)*(X2-X1+1) ]; if (Comp != NULL) // valid { const FMatrix LocalToWorld = Comp->GetRenderMatrix(); FVector P[4]; for (int32 Idx = 0; Idx < 2; ++Idx) { int32 XX = FMath::Clamp(X + XOffsets[Idx], X1, X2); P[Idx] = FVector( GetWorldPos( LocalToWorld, FVector2D(XX - Comp->GetSectionBase().X, Y - Comp->GetSectionBase().Y), NewXYOffset[ XX - X1 + (Y - Y1) * (X2-X1+1) ] ) ); } int32 YY0 = FMath::Clamp(FMath::FloorToInt(YOffset-1), Y1, Y2); int32 YY1 = FMath::Clamp(FMath::FloorToInt(YOffset), Y1, Y2); int32 YY2 = FMath::Clamp(FMath::FloorToInt(1+YOffset), Y1, Y2); // Search for valid YOffset... for (int32 Idx = 2; Idx < 4; ++Idx) { int32 XX = FMath::Clamp(X + XOffsets[Idx], X1, X2); FVector P1( GetWorldPos( LocalToWorld, FVector2D(XX - Comp->GetSectionBase().X, YY1 - Comp->GetSectionBase().Y), XYOffsetVectorData[ XX - X1 + (YY1 - Y1) * (X2-X1+1) ] ) ); FVector P2( GetWorldPos( LocalToWorld, FVector2D(XX - Comp->GetSectionBase().X, YY2 - Comp->GetSectionBase().Y), XYOffsetVectorData[ XX - X1 + (YY2 - Y1) * (X2-X1+1) ] ) ); P[Idx] = FMath::Lerp(P1, P2, FMath::Fractional(YOffset)); if (Idx == 2) { FVector P0( GetWorldPos( LocalToWorld, FVector2D(XX - Comp->GetSectionBase().X, YY0 - Comp->GetSectionBase().Y), XYOffsetVectorData[ XX - X1 + (YY0 - Y1) * (X2-X1+1) ] ) ); TotalHeightError += FMath::Abs( ((P[2] - P0) ^ (P2 - P[2])).Size() - ((P1 - P0) ^ (P2 - P1)).Size() ); } } //TotalHeightError += Abs( XYOffsetVectorData( X - X1 + (YY2 - Y1) * (X2-X1+1) ).Z - XYOffsetVectorData( X - X1 + (YY1 - Y1) * (X2-X1+1) ).Z * appFractional(YOffset) ); TotalLineArea += (((P[3] - P[0]) ^ (P[1] - P[0])).Size() + ((P[3] - P[0]) ^ (P[2] - P[0])).Size()) * 0.5f; } } if (TotalLineArea < AreaErrorThreshold || IterNum > MaxIterNum) { break; } if (MaxX[Y-Y1] - MinX[Y-Y1] > 0) { TotalHeightError /= (MaxX[Y-Y1] - MinX[Y-Y1]); } float NewLineAreaDiff = FMath::Abs(TotalLineArea - TargetLineArea); if (NewLineAreaDiff > LineAreaDiff || TotalHeightError > HeightErrorThreshold) { // backtracking YOffset = PreYOffset; StepSize *= 0.5f; } else { PreYOffset = YOffset; LineAreaDiff = FMath::Abs(TotalLineArea - TargetLineArea); if (TotalLineArea - TargetLineArea > 0) { YOffset -= StepSize; } else { YOffset += StepSize; } // clamp if ( YOffset < Y1 ) { YOffset = Y1; break; } if ( YOffset >= Y2 ) { YOffset = Y2; break; } } if (StepSize < AreaResolution) { break; } } // Set Y Offset if (TotalLineArea >= AreaErrorThreshold) { RemainArea -= TotalLineArea; RemainQuads -= QuadY[Y - Y1]; for (int32 X = MinX[Y-Y1]; X < MaxX[Y-Y1]; ++X) { int32 YY1 = FMath::Clamp(FMath::FloorToInt(YOffset), Y1, Y2); int32 YY2 = FMath::Clamp(FMath::FloorToInt(1+YOffset), Y1, Y2); FVector P1 = XYOffsetVectorData[ X - X1 + (YY1 - Y1) * (X2-X1+1) ]; //P1.X = X+P1.X; P1.Y = YY1+P1.Y; FVector P2 = XYOffsetVectorData[ X - X1 + (YY2 - Y1) * (X2-X1+1) ]; //P2.X = X+P2.X; P2.Y = YY2+P2.Y; FVector& XYOffset = NewXYOffset[ X - X1 + (Y+1 - Y1) * (X2-X1+1) ]; XYOffset = FMath::Lerp(P1, P2, FMath::Fractional(YOffset)); //XYOffset.X -= X; XYOffset.Y -= Y+1; } } } // X ... TArray NewYOffsets = NewXYOffset; RemainArea = TotalArea; RemainQuads = QuadNum; for (int32 X = X1; X < X2-1; ++X) { // Like rasterization style // Search for X offset if (MinY[X-X1] > MaxY[X-X1]) { continue; } float AverageArea = RemainArea / RemainQuads; float AreaBaseError = AverageArea * 0.5f; float TotalLineArea = 0.f; float TargetLineArea = AverageArea * QuadX[X - X1]; float XOffset = X+1, PreXOffset = X+1; // Need to be bigger than previous Y float StepSize = FMath::Sqrt(2) * 0.25f; float LineAreaDiff = FLT_MAX; // Abs(TotalLineArea - TargetLineArea); int32 IterNum = 0; float TotalHeightError = 0.f; while( NewXYOffset[X - X1].X + X > NewYOffsets[FMath::FloorToInt(XOffset) - X1].X + FMath::FloorToFloat(XOffset) ) { XOffset = XOffset+1.f; if (XOffset >= X2) { XOffset = X2; break; } } PreXOffset = XOffset; while (FMath::Abs(TotalLineArea - TargetLineArea) > AreaErrorThreshold) { TotalLineArea = 0.f; IterNum++; for (int32 Y = MinY[X-X1]; Y < MaxY[X-X1]; ++Y) { ULandscapeComponent* Comp = ComponentArray[X - X1 + (Y - Y1)*(X2-X1+1) ]; if (Comp != NULL) // valid { const FMatrix LocalToWorld = Comp->GetRenderMatrix(); FVector P[4]; for (int32 Idx = 0; Idx < 4; Idx+=2) { int32 YY = FMath::Clamp(Y + YOffsets[Idx], Y1, Y2); P[Idx] = FVector( GetWorldPos( LocalToWorld, FVector2D(X - Comp->GetSectionBase().X, YY - Comp->GetSectionBase().Y), NewXYOffset[ X - X1 + (YY - Y1) * (X2-X1+1) ] ) ); } int32 XX0 = FMath::Clamp(FMath::FloorToInt(XOffset-1), X1, X2); int32 XX1 = FMath::Clamp(FMath::FloorToInt(XOffset), X1, X2); int32 XX2 = FMath::Clamp(FMath::FloorToInt(1+XOffset), X1, X2); // Search for valid YOffset... for (int32 Idx = 1; Idx < 4; Idx+=2) { int32 YY = FMath::Clamp(Y + YOffsets[Idx], Y1, Y2); FVector P1( GetWorldPos( LocalToWorld, FVector2D(XX1 - Comp->GetSectionBase().X, YY - Comp->GetSectionBase().Y), NewYOffsets[ XX1 - X1 + (YY - Y1) * (X2-X1+1) ] ) ); FVector P2( GetWorldPos( LocalToWorld, FVector2D(XX2 - Comp->GetSectionBase().X, YY - Comp->GetSectionBase().Y), NewYOffsets[ XX2 - X1 + (YY - Y1) * (X2-X1+1) ] ) ); P[Idx] = FMath::Lerp(P1, P2, FMath::Fractional(XOffset)); if (Idx == 1) { FVector P0( GetWorldPos( LocalToWorld, FVector2D(XX0 - Comp->GetSectionBase().X, YY - Comp->GetSectionBase().Y), NewYOffsets[ XX0 - X1 + (YY - Y1) * (X2-X1+1) ] ) ); TotalHeightError += FMath::Abs( ((P[1] - P0) ^ (P2 - P[1])).Size() - ((P1 - P0) ^ (P2 - P1)).Size() ); } } //TotalHeightError += Abs( NewYOffsets( XX1 - X1 + (Y - Y1) * (X2-X1+1) ).Z - NewYOffsets( XX2 - X1 + (Y - Y1) * (X2-X1+1) ).Z * appFractional(XOffset) ); TotalLineArea += (((P[3] - P[0]) ^ (P[1] - P[0])).Size() + ((P[3] - P[0]) ^ (P[2] - P[0])).Size()) * 0.5f; } } if (TotalLineArea < AreaErrorThreshold || IterNum > MaxIterNum) { break; } if (MaxY[X-X1] - MinY[X-X1] > 0) { TotalHeightError /= (MaxY[X-X1] - MinY[X-X1]); } float NewLineAreaDiff = FMath::Abs(TotalLineArea - TargetLineArea); if (NewLineAreaDiff > LineAreaDiff || TotalHeightError > HeightErrorThreshold) { // backtracking XOffset = PreXOffset; StepSize *= 0.5f; } else { PreXOffset = XOffset; LineAreaDiff = FMath::Abs(TotalLineArea - TargetLineArea); if (TotalLineArea - TargetLineArea > 0) { XOffset -= StepSize; } else { XOffset += StepSize; } // clamp if ( XOffset <= X1 ) { XOffset = X1; break; } else if ( XOffset >= X2 ) { XOffset = X2; break; } } if (StepSize < AreaResolution) { break; } } // Set X Offset if (TotalLineArea >= AreaErrorThreshold) { RemainArea -= TotalLineArea; RemainQuads -= QuadX[X - X1]; for (int32 Y = MinY[X-X1]; Y < MaxY[X-X1]; ++Y) { int32 XX1 = FMath::Clamp(FMath::FloorToInt(XOffset), X1, X2); int32 XX2 = FMath::Clamp(FMath::FloorToInt(1+XOffset), X1, X2); FVector P1 = NewYOffsets[ XX1 - X1 + (Y - Y1) * (X2-X1+1) ]; P1.X = XX1+P1.X; FVector P2 = NewYOffsets[ XX2 - X1 + (Y - Y1) * (X2-X1+1) ]; P2.X = XX2+P2.X; FVector& XYOffset = NewXYOffset[ X+1 - X1 + (Y - Y1) * (X2-X1+1) ]; XYOffset = FMath::Lerp(P1, P2, FMath::Fractional(XOffset)); XYOffset.X -= X+1; } } } } // Same as Gizmo fall off... float W = X2 - X1 + 1; float H = Y2 - Y1 + 1; float FalloffRadius = W * 0.5f * UISettings->BrushFalloff; float SquareRadius = W * 0.5f - FalloffRadius; for (int32 Y = 0; Y <= Y2-Y1; ++Y) { for (int32 X = 0; X <= X2-X1; ++X) { int32 Index = X + Y * (X2-X1+1); FVector2D TransformedLocal(FMath::Abs(X - W * 0.5f), FMath::Abs(Y - H * 0.5f) * (W / H)); float Cos = FMath::Abs(TransformedLocal.X) / TransformedLocal.Size(); float Sin = FMath::Abs(TransformedLocal.Y) / TransformedLocal.Size(); float RatioX = FalloffRadius > 0.f ? 1.f - FMath::Clamp((FMath::Abs(TransformedLocal.X) - Cos*SquareRadius) / FalloffRadius, 0.f, 1.f) : 1.f; float RatioY = FalloffRadius > 0.f ? 1.f - FMath::Clamp((FMath::Abs(TransformedLocal.Y) - Sin*SquareRadius) / FalloffRadius, 0.f, 1.f) : 1.f; float Ratio = TransformedLocal.Size() > SquareRadius ? RatioX * RatioY : 1.f; //TransformedLocal.X / LW * TransformedLocal.Y / LW; float PaintAmount = Ratio*Ratio*(3-2*Ratio); XYOffsetVectorData[Index] = FMath::Lerp( XYOffsetVectorData[Index], NewXYOffset[Index], PaintAmount ); //XYOffsetVectorData(Index) = NewXYOffset(Index); } } // Apply to XYOffset Texture map and Height map Cache.SetCachedData(X1, Y1, X2, Y2, XYOffsetVectorData); Cache.Flush(); } protected: class ULandscapeInfo* LandscapeInfo; FLandscapeXYOffsetCache Cache; }; class FLandscapeToolRetopologize : public FLandscapeToolBase { public: FLandscapeToolRetopologize(class FEdModeLandscape* InEdMode) : FLandscapeToolBase(InEdMode) {} virtual const TCHAR* GetToolName() OVERRIDE { return TEXT("Retopologize"); } virtual FText GetDisplayName() OVERRIDE { return NSLOCTEXT("UnrealEd", "LandscapeMode_Retopologize", "Retopologize"); } virtual bool IsValidForTarget(const FLandscapeToolTarget& Target) { return true; // erosion applied to all... } }; void FEdModeLandscape::IntializeToolSet_Retopologize() { FLandscapeToolSet* ToolSet_Retopologize = new(LandscapeToolSets) FLandscapeToolSet(TEXT("ToolSet_Retopologize")); ToolSet_Retopologize->AddTool(new FLandscapeToolRetopologize(this)); ToolSet_Retopologize->ValidBrushes.Add("BrushSet_Circle"); ToolSet_Retopologize->ValidBrushes.Add("BrushSet_Alpha"); ToolSet_Retopologize->ValidBrushes.Add("BrushSet_Pattern"); }