You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Refactored landscape tool strokes somewhat, removing unnecessary virtual functions (most of the stroke classes were single-level hierarchies, making virtuals pointless), empty functions and unnecessary dynamic allocation (using refactored TOptional) [CL 2293878 by Gareth Martin in Main branch]
576 lines
20 KiB
C++
576 lines
20 KiB
C++
// 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<FVector>& 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 FLandscapeToolStrokeBase
|
|
{
|
|
public:
|
|
FLandscapeToolStrokeRetopologize(FEdModeLandscape* InEdMode, const FLandscapeToolTarget& InTarget)
|
|
: LandscapeInfo(InTarget.LandscapeInfo.Get())
|
|
, Cache(InTarget)
|
|
{
|
|
}
|
|
|
|
void Apply(FEditorViewportClient* ViewportClient, FLandscapeBrush* Brush, const ULandscapeEditorObject* UISettings, const TArray<FLandscapeToolMousePosition>& MousePositions)
|
|
{
|
|
if (!LandscapeInfo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get list of verts to update
|
|
TMap<FIntPoint, float> 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<int32>(Component->GetSectionBase().X, ValidX1);
|
|
ValidX2 = FMath::Max<int32>(Component->GetSectionBase().X + ComponentSizeQuads, ValidX2);
|
|
ValidY1 = FMath::Min<int32>(Component->GetSectionBase().Y, ValidY1);
|
|
ValidY2 = FMath::Max<int32>(Component->GetSectionBase().Y + ComponentSizeQuads, ValidY2);
|
|
}
|
|
}
|
|
}
|
|
|
|
X1 = FMath::Max<int32>(X1, ValidX1);
|
|
X2 = FMath::Min<int32>(X2, ValidX2);
|
|
Y1 = FMath::Max<int32>(Y1, ValidY1);
|
|
Y2 = FMath::Min<int32>(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<FVector> XYOffsetVectorData;
|
|
Cache.GetCachedData(X1, Y1, X2, Y2, XYOffsetVectorData);
|
|
TArray<FVector> 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<int32> 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<ULandscapeComponent*> 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<int32>(X1 - ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
|
|
int32 ComponentY1 = FMath::Clamp<int32>(Y1 - ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
|
|
int32 ComponentX2 = FMath::Clamp<int32>(X2 - ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
|
|
int32 ComponentY2 = FMath::Clamp<int32>(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<int32>(MinX[ComponentIndexY*ComponentSizeQuads + Y - Y1], ComponentIndexX*ComponentSizeQuads + X);
|
|
MaxX[ComponentIndexY*ComponentSizeQuads + Y - Y1] = FMath::Max<int32>(MaxX[ComponentIndexY*ComponentSizeQuads + Y - Y1], ComponentIndexX*ComponentSizeQuads + X);
|
|
MinY[ComponentIndexX*ComponentSizeQuads + X - X1] = FMath::Min<int32>(MinY[ComponentIndexX*ComponentSizeQuads + X - X1], ComponentIndexY*ComponentSizeQuads + Y);
|
|
MaxY[ComponentIndexX*ComponentSizeQuads + X - X1] = FMath::Max<int32>(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<int32>(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<int32>(FMath::FloorToInt(YOffset - 1), Y1, Y2);
|
|
int32 YY1 = FMath::Clamp<int32>(FMath::FloorToInt(YOffset), Y1, Y2);
|
|
int32 YY2 = FMath::Clamp<int32>(FMath::FloorToInt(1 + YOffset), Y1, Y2);
|
|
// Search for valid YOffset...
|
|
for (int32 Idx = 2; Idx < 4; ++Idx)
|
|
{
|
|
int32 XX = FMath::Clamp<int32>(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<int32>(FMath::FloorToInt(YOffset), Y1, Y2);
|
|
int32 YY2 = FMath::Clamp<int32>(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<FVector> 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<int32>(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<int32>(FMath::FloorToInt(XOffset - 1), X1, X2);
|
|
int32 XX1 = FMath::Clamp<int32>(FMath::FloorToInt(XOffset), X1, X2);
|
|
int32 XX2 = FMath::Clamp<int32>(FMath::FloorToInt(1 + XOffset), X1, X2);
|
|
|
|
// Search for valid YOffset...
|
|
for (int32 Idx = 1; Idx < 4; Idx += 2)
|
|
{
|
|
int32 YY = FMath::Clamp<int32>(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<int32>(FMath::FloorToInt(XOffset), X1, X2);
|
|
int32 XX2 = FMath::Clamp<int32>(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:
|
|
ULandscapeInfo* LandscapeInfo;
|
|
FLandscapeXYOffsetCache<false> Cache;
|
|
};
|
|
|
|
class FLandscapeToolRetopologize : public FLandscapeToolBase<FLandscapeToolStrokeRetopologize>
|
|
{
|
|
public:
|
|
FLandscapeToolRetopologize(FEdModeLandscape* InEdMode)
|
|
: FLandscapeToolBase<FLandscapeToolStrokeRetopologize>(InEdMode)
|
|
{
|
|
}
|
|
|
|
virtual const TCHAR* GetToolName() override { return TEXT("Retopologize"); }
|
|
virtual FText GetDisplayName() override { return NSLOCTEXT("UnrealEd", "LandscapeMode_Retopologize", "Retopologize"); }
|
|
|
|
virtual ELandscapeToolTargetTypeMask::Type GetSupportedTargetTypes() override
|
|
{
|
|
// technically not entirely accurate, also modifies the XYOffset map
|
|
return ELandscapeToolTargetTypeMask::Heightmap;
|
|
}
|
|
};
|
|
|
|
void FEdModeLandscape::InitializeTool_Retopologize()
|
|
{
|
|
auto Tool_Retopologize = MakeUnique<FLandscapeToolRetopologize>(this);
|
|
Tool_Retopologize->ValidBrushes.Add("BrushSet_Circle");
|
|
Tool_Retopologize->ValidBrushes.Add("BrushSet_Alpha");
|
|
Tool_Retopologize->ValidBrushes.Add("BrushSet_Pattern");
|
|
LandscapeTools.Add(MoveTemp(Tool_Retopologize));
|
|
}
|