1150 lines
48 KiB
C++
1150 lines
48 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
LandscapeSplineRaster.cpp:
|
|
Functions to rasterize a spline into landscape heights/weights
|
|
=============================================================================*/
|
|
|
|
#include "LandscapeSplineRaster.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "AI/NavigationSystemBase.h"
|
|
#include "LandscapeComponent.h"
|
|
#include "LandscapeLayerInfoObject.h"
|
|
#include "LandscapeHeightfieldCollisionComponent.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeEditLayer.h"
|
|
#include "LandscapeSplinesComponent.h"
|
|
#include "LandscapeSplineControlPoint.h"
|
|
#include "LandscapePrivate.h"
|
|
#if WITH_EDITOR
|
|
#include "ScopedTransaction.h"
|
|
#include "Raster.h"
|
|
#include "Landscape.h"
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "Landscape"
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Apply splines
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#if WITH_EDITOR
|
|
TAutoConsoleVariable<int32> CVarLandscapeSplineFalloffModulation(
|
|
TEXT("landscape.SplineFalloffModulation"),
|
|
1,
|
|
TEXT("Enable Texture Modulation fo Spline Layer Falloff."));
|
|
|
|
using FModulateAlphaFunc = TFunction<float(float InValue, int32 X, int32 Y)>;
|
|
|
|
class FLandscapeSplineHeightsRasterPolicy
|
|
{
|
|
public:
|
|
// X = Side Alpha, Y = End Alpha, Z = Height
|
|
typedef FVector InterpolantType;
|
|
|
|
/** Initialization constructor. */
|
|
FLandscapeSplineHeightsRasterPolicy(TArray<uint16>& InHeightData, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, bool InbRaiseTerrain, bool InbLowerTerrain, TArray<uint16>* InHeightAlphaBlendData = nullptr, TArray<uint8>* InHeightFlagsData = nullptr) :
|
|
HeightData(InHeightData),
|
|
HeightAlphaBlendData(InHeightAlphaBlendData),
|
|
HeightFlagsData(InHeightFlagsData),
|
|
MinX(InMinX),
|
|
MinY(InMinY),
|
|
MaxX(InMaxX),
|
|
MaxY(InMaxY),
|
|
bRaiseTerrain(InbRaiseTerrain),
|
|
bLowerTerrain(InbLowerTerrain)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
|
|
// FTriangleRasterizer policy interface.
|
|
int32 GetMinX() const { return MinX; }
|
|
int32 GetMaxX() const { return MaxX; }
|
|
int32 GetMinY() const { return MinY; }
|
|
int32 GetMaxY() const { return MaxY; }
|
|
|
|
inline void ProcessPixel(int32 X, int32 Y, const InterpolantType& Interpolant, bool BackFacing)
|
|
{
|
|
if (!bRaiseTerrain && !bLowerTerrain)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float CosInterpX = static_cast<float>(Interpolant.X >= 1 ? 1 : 0.5f - 0.5f * FMath::Cos(Interpolant.X * PI));
|
|
const float CosInterpY = static_cast<float>(Interpolant.Y >= 1 ? 1 : 0.5f - 0.5f * FMath::Cos(Interpolant.Y * PI));
|
|
const float Alpha = FMath::Clamp<float>(CosInterpX * CosInterpY, 0.f, 1.f);
|
|
|
|
int32 DataIndex = (Y - MinY)*(1 + MaxX - MinX) + X - MinX;
|
|
uint16& Dest = HeightData[DataIndex];
|
|
|
|
if (HeightAlphaBlendData)
|
|
{
|
|
uint16 NewHeight = static_cast<uint16>(FMath::Clamp(static_cast<float>(Interpolant.Z), 0.0f, static_cast<float>(LandscapeDataAccess::MaxValue)));
|
|
float InterpValue = (NewHeight * Alpha) + (Dest * (1.f - Alpha));
|
|
Dest = (uint16)FMath::Clamp<float>(InterpValue, 0, (float)LandscapeDataAccess::MaxValue);
|
|
|
|
uint16& DestAlphaValue = (*HeightAlphaBlendData)[DataIndex];
|
|
float InterpAlphaValue = DestAlphaValue * (1.f - Alpha);
|
|
DestAlphaValue = (uint16)FMath::Clamp<float>(InterpAlphaValue, 0.f, static_cast<float>(LandscapeDataAccess::MaxValue));
|
|
|
|
if (HeightFlagsData)
|
|
{
|
|
uint8& DestHeightFlagsValue = (*HeightFlagsData)[DataIndex];
|
|
uint8 Flags = DestHeightFlagsValue;
|
|
if (bLowerTerrain)
|
|
Flags |= 1;
|
|
if (bRaiseTerrain)
|
|
Flags |= 2;
|
|
DestHeightFlagsValue = Flags;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float Value = FMath::Lerp<float>(Dest, static_cast<float>(Interpolant.Z), Alpha);
|
|
uint16 DValue = (uint16)FMath::Clamp<float>(Value, 0, (float)LandscapeDataAccess::MaxValue);
|
|
if ((bRaiseTerrain && DValue > Dest) ||
|
|
(bLowerTerrain && DValue < Dest))
|
|
{
|
|
Dest = DValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
TArray<uint16>& HeightData;
|
|
TArray<uint16>* HeightAlphaBlendData;
|
|
TArray<uint8>* HeightFlagsData;
|
|
int32 MinX, MinY, MaxX, MaxY;
|
|
uint32 bRaiseTerrain : 1, bLowerTerrain : 1;
|
|
};
|
|
|
|
extern const size_t ChannelOffsets[4];
|
|
|
|
class FModulateAlpha
|
|
{
|
|
public:
|
|
static TSharedPtr<FModulateAlpha> CreateFromLayerInfo(ULandscapeLayerInfoObject* InLayerInfo, int32 InLandscapeMinX, int32 InLandscapeMinY)
|
|
{
|
|
if (CVarLandscapeSplineFalloffModulation.GetValueOnAnyThread() == 0
|
|
|| (InLayerInfo == nullptr)
|
|
|| (InLayerInfo->SplineFalloffModulationTexture == nullptr))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (!InLayerInfo->SplineFalloffModulationTexture->Source.IsValid())
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("Invalid source data for spline falloff modulation texture (%s). Alpha modulation will be disabled."), *InLayerInfo->SplineFalloffModulationTexture->GetPathName());
|
|
return nullptr;
|
|
}
|
|
|
|
return MakeShareable(new FModulateAlpha(InLayerInfo, InLandscapeMinX, InLandscapeMinY));
|
|
}
|
|
|
|
float Modulate(float InValue, int32 InX, int32 InY) const
|
|
{
|
|
float X = FMath::Frac((float)(InX - LandscapeMinX) / (TextureWidth * Tiling)) * TextureWidth;
|
|
float Y = FMath::Frac((float)(InY - LandscapeMinY) / (TextureHeight * Tiling)) * TextureHeight;
|
|
|
|
int32 X0 = FMath::FloorToInt(X);
|
|
int32 X1 = FMath::CeilToInt(X);
|
|
int32 Y0 = FMath::FloorToInt(Y);
|
|
int32 Y1 = FMath::CeilToInt(Y);
|
|
|
|
const uint8* Data = MipData.GetData() + ChannelOffset;
|
|
uint8 SampleX0Y0 = *(Data + ((Y0*TextureWidth + X0) * sizeof(FColor)));
|
|
uint8 SampleX1Y0 = *(Data + ((Y0*TextureWidth + X1) * sizeof(FColor)));
|
|
uint8 SampleX0Y1 = *(Data + ((Y1*TextureWidth + X0) * sizeof(FColor)));
|
|
uint8 SampleX1Y1 = *(Data + ((Y1*TextureWidth + X1) * sizeof(FColor)));
|
|
|
|
uint8 LerpY0 = FMath::Lerp(SampleX0Y0, SampleX1Y0, X - X0);
|
|
uint8 LerpY1 = FMath::Lerp(SampleX0Y1, SampleX1Y1, X - X0);
|
|
uint8 FinalLerp = FMath::Lerp(LerpY0, LerpY1, Y - Y0);
|
|
|
|
InValue = InValue + (((FinalLerp / 255.0f) - Bias) * Scale);
|
|
|
|
return FMath::Clamp(InValue, 0.0f, 1.0f);
|
|
}
|
|
|
|
private:
|
|
FModulateAlpha(ULandscapeLayerInfoObject* InLayerInfo, int32 InLandscapeMinX, int32 InLandscapeMinY)
|
|
{
|
|
check(InLayerInfo && InLayerInfo->SplineFalloffModulationTexture);
|
|
|
|
verify(InLayerInfo->SplineFalloffModulationTexture->Source.GetMipData(MipData, 0));
|
|
TextureWidth = InLayerInfo->SplineFalloffModulationTexture->Source.GetSizeX();
|
|
TextureHeight = InLayerInfo->SplineFalloffModulationTexture->Source.GetSizeY();
|
|
|
|
Tiling = 1.0f / InLayerInfo->SplineFalloffModulationTiling;
|
|
Bias = InLayerInfo->SplineFalloffModulationBias;
|
|
Scale = InLayerInfo->SplineFalloffModulationScale;
|
|
|
|
LandscapeMinX = InLandscapeMinX;
|
|
LandscapeMinY = InLandscapeMinY;
|
|
|
|
ChannelOffset = ChannelOffsets[(int32)InLayerInfo->SplineFalloffModulationColorMask];
|
|
}
|
|
|
|
private:
|
|
float Tiling;
|
|
float Bias;
|
|
float Scale;
|
|
TArray64<uint8> MipData;
|
|
int32 TextureWidth;
|
|
int32 TextureHeight;
|
|
int32 LandscapeMinX;
|
|
int32 LandscapeMinY;
|
|
int32 ChannelOffset;
|
|
};
|
|
|
|
class FLandscapeSplineBlendmaskRasterPolicy
|
|
{
|
|
public:
|
|
// X = Side Alpha, Y = End Alpha, Z = Blend Value
|
|
typedef FVector InterpolantType;
|
|
|
|
/** Initialization constructor. */
|
|
FLandscapeSplineBlendmaskRasterPolicy(TArray<uint8>& InData, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, const TSharedPtr<FModulateAlpha>& InModulateAlpha = nullptr) :
|
|
Data(InData),
|
|
MinX(InMinX),
|
|
MinY(InMinY),
|
|
MaxX(InMaxX),
|
|
MaxY(InMaxY),
|
|
ModulateAlpha(InModulateAlpha)
|
|
{
|
|
}
|
|
protected:
|
|
|
|
// FTriangleRasterizer policy interface.
|
|
|
|
int32 GetMinX() const { return MinX; }
|
|
int32 GetMaxX() const { return MaxX; }
|
|
int32 GetMinY() const { return MinY; }
|
|
int32 GetMaxY() const { return MaxY; }
|
|
|
|
inline void ProcessPixel(int32 X, int32 Y, const InterpolantType& Interpolant, bool BackFacing)
|
|
{
|
|
float Alpha = 0.0f;
|
|
|
|
if (ModulateAlpha == nullptr)
|
|
{
|
|
const float CosInterpX = static_cast<float>(Interpolant.X >= 1 ? 1 : 0.5f - 0.5f * FMath::Cos(Interpolant.X * PI));
|
|
const float CosInterpY = static_cast<float>(Interpolant.Y >= 1 ? 1 : 0.5f - 0.5f * FMath::Cos(Interpolant.Y * PI));
|
|
Alpha = CosInterpX * CosInterpY;
|
|
}
|
|
else
|
|
{
|
|
const float InterpX = FMath::Clamp<float>(static_cast<float>(Interpolant.X), 0.0f, 1.0f);
|
|
const float InterpY = FMath::Clamp<float>(static_cast<float>(Interpolant.Y), 0.0f, 1.0f);
|
|
Alpha = ModulateAlpha->Modulate(InterpX * InterpY, X, Y);
|
|
}
|
|
|
|
uint8& Dest = Data[(Y - MinY)*(1 + MaxX - MinX) + X - MinX];
|
|
float Value = FMath::Lerp<float>(Dest, static_cast<float>(Interpolant.Z), Alpha);
|
|
Dest = FMath::Clamp<uint8>(static_cast<uint8>(Value), 0, 255);
|
|
}
|
|
|
|
private:
|
|
TArray<uint8>& Data;
|
|
int32 MinX, MinY, MaxX, MaxY;
|
|
const TSharedPtr<FModulateAlpha>& ModulateAlpha;
|
|
};
|
|
|
|
void RasterizeHeight(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY, FLandscapeEditDataInterface& LandscapeEdit, bool bRaiseTerrain, bool bLowerTerrain, TSet<ULandscapeComponent*>& ModifiedComponents, TFunctionRef<void(FTriangleRasterizer<FLandscapeSplineHeightsRasterPolicy>&)> RasterizerFunction)
|
|
{
|
|
if (!(bRaiseTerrain || bLowerTerrain))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<uint16> HeightData;
|
|
HeightData.AddZeroed((1 + MaxY - MinY) * (1 + MaxX - MinX));
|
|
|
|
int32 ValidMinX = MinX;
|
|
int32 ValidMinY = MinY;
|
|
int32 ValidMaxX = MaxX;
|
|
int32 ValidMaxY = MaxY;
|
|
LandscapeEdit.GetHeightData(ValidMinX, ValidMinY, ValidMaxX, ValidMaxY, HeightData.GetData(), 0);
|
|
|
|
if (ValidMinX > ValidMaxX || ValidMinY > ValidMaxY)
|
|
{
|
|
// The bounds don't intersect any data, so skip it. Return the invalid bounds.
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
return;
|
|
}
|
|
|
|
// shrink our data to the valid range returned by GetHeightData
|
|
FLandscapeEditDataInterface::ShrinkData(HeightData, MinX, MinY, MaxX, MaxY, ValidMinX, ValidMinY, ValidMaxX, ValidMaxY);
|
|
|
|
const ALandscape* Landscape = LandscapeEdit.GetTargetLandscape();
|
|
check(Landscape != nullptr);
|
|
|
|
bool bIsEditingLayerReservedForSplines = false;
|
|
if (Landscape->CanHaveLayersContent())
|
|
{
|
|
const ULandscapeEditLayerBase* EditingLayer = Landscape->GetEditLayerConst(Landscape->GetEditingLayer());
|
|
check(EditingLayer != nullptr);
|
|
const ULandscapeEditLayerBase* SplinesLayer = Landscape->FindEditLayerOfTypeConst(ULandscapeEditLayerSplines::StaticClass());
|
|
bIsEditingLayerReservedForSplines = (SplinesLayer == EditingLayer);
|
|
check(!bIsEditingLayerReservedForSplines || (SplinesLayer->GetBlendMode() == LSBM_AlphaBlend));
|
|
}
|
|
|
|
TArray<uint16> HeightAlphaBlendData;
|
|
TArray<uint8> HeightFlagsData;
|
|
TArray<uint16>* HeightAlphaBlendDataPtr = nullptr;
|
|
TArray<uint8>* HeightFlagsDataPtr = nullptr;
|
|
|
|
bool bCalculateNormals = true;
|
|
|
|
// when using spline edit layer there is additional alpha blending data
|
|
if (bIsEditingLayerReservedForSplines)
|
|
{
|
|
bCalculateNormals = false;
|
|
|
|
HeightAlphaBlendData.AddZeroed((1 + MaxY - MinY) * (1 + MaxX - MinX));
|
|
int32 AlphaValidMinX = MinX;
|
|
int32 AlphaValidMinY = MinY;
|
|
int32 AlphaValidMaxX = MaxX;
|
|
int32 AlphaValidMaxY = MaxY;
|
|
LandscapeEdit.GetHeightAlphaBlendData(AlphaValidMinX, AlphaValidMinY, AlphaValidMaxX, AlphaValidMaxY, HeightAlphaBlendData.GetData(), 0);
|
|
check(AlphaValidMinX == ValidMinX && AlphaValidMinY == ValidMinY && AlphaValidMaxX == ValidMaxX && AlphaValidMaxY == ValidMaxY);
|
|
FLandscapeEditDataInterface::ShrinkData(HeightAlphaBlendData, MinX, MinY, MaxX, MaxY, AlphaValidMinX, AlphaValidMinY, AlphaValidMaxX, AlphaValidMaxY);
|
|
HeightAlphaBlendDataPtr = &HeightAlphaBlendData;
|
|
|
|
HeightFlagsData.AddZeroed((1 + MaxY - MinY) * (1 + MaxX - MinX));
|
|
int32 FlagsValidMinX = MinX;
|
|
int32 FlagsValidMinY = MinY;
|
|
int32 FlagsValidMaxX = MaxX;
|
|
int32 FlagsValidMaxY = MaxY;
|
|
LandscapeEdit.GetHeightFlagsData(FlagsValidMinX, FlagsValidMinY, FlagsValidMaxX, FlagsValidMaxY, HeightFlagsData.GetData(), 0);
|
|
check(FlagsValidMinX == ValidMinX && FlagsValidMinY == ValidMinY && FlagsValidMaxX == ValidMaxX && FlagsValidMaxY == ValidMaxY);
|
|
FLandscapeEditDataInterface::ShrinkData(HeightFlagsData, MinX, MinY, MaxX, MaxY, FlagsValidMinX, FlagsValidMinY, FlagsValidMaxX, FlagsValidMaxY);
|
|
HeightFlagsDataPtr = &HeightFlagsData;
|
|
}
|
|
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
FTriangleRasterizer<FLandscapeSplineHeightsRasterPolicy> Rasterizer(FLandscapeSplineHeightsRasterPolicy(HeightData, MinX, MinY, MaxX, MaxY, bRaiseTerrain, bLowerTerrain, HeightAlphaBlendDataPtr, HeightFlagsDataPtr));
|
|
|
|
RasterizerFunction(Rasterizer);
|
|
|
|
LandscapeEdit.SetHeightData(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0, bCalculateNormals, nullptr, HeightAlphaBlendDataPtr ? HeightAlphaBlendDataPtr->GetData() : nullptr, HeightFlagsDataPtr ? HeightFlagsDataPtr->GetData() : nullptr, false, nullptr, nullptr, true, !bIsEditingLayerReservedForSplines);
|
|
LandscapeEdit.GetComponentsInRegion(MinX, MinY, MaxX, MaxY, &ModifiedComponents);
|
|
}
|
|
|
|
void RasterizeControlPointHeights(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY, FLandscapeEditDataInterface& LandscapeEdit, FVector ControlPointLocation, const TArray<FLandscapeSplineInterpPoint>& Points, bool bRaiseTerrain, bool bLowerTerrain, TSet<ULandscapeComponent*>& ModifiedComponents)
|
|
{
|
|
RasterizeHeight(MinX, MinY, MaxX, MaxY, LandscapeEdit, bRaiseTerrain, bLowerTerrain, ModifiedComponents, [&](FTriangleRasterizer<FLandscapeSplineHeightsRasterPolicy>& Rasterizer)
|
|
{
|
|
const FVector2D CenterPos = FVector2D(ControlPointLocation);
|
|
const FVector Center = FVector(1.0f, Points[0].StartEndFalloff, ControlPointLocation.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue);
|
|
|
|
for (int32 i = Points.Num() - 1, j = 0; j < Points.Num(); i = j++)
|
|
{
|
|
// Solid center
|
|
const FVector2D Right0Pos = FVector2D(Points[i].Right);
|
|
const FVector2D Left1Pos = FVector2D(Points[j].Left);
|
|
const FVector2D Right1Pos = FVector2D(Points[j].Right);
|
|
const FVector Right0 = FVector(1.0f, Points[i].StartEndFalloff, Points[i].Right.Z);
|
|
const FVector Left1 = FVector(1.0f, Points[j].StartEndFalloff, Points[j].Left.Z);
|
|
const FVector Right1 = FVector(1.0f, Points[j].StartEndFalloff, Points[j].Right.Z);
|
|
|
|
Rasterizer.DrawTriangle(Center, Right0, Left1, CenterPos, Right0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(Center, Left1, Right1, CenterPos, Left1Pos, Right1Pos, false);
|
|
|
|
// Falloff
|
|
FVector2D FalloffRight0Pos = FVector2D(Points[i].FalloffRight);
|
|
FVector2D FalloffLeft1Pos = FVector2D(Points[j].FalloffLeft);
|
|
FVector FalloffRight0 = FVector(0.0f, Points[i].StartEndFalloff, Points[i].FalloffRight.Z);
|
|
FVector FalloffLeft1 = FVector(0.0f, Points[j].StartEndFalloff, Points[j].FalloffLeft.Z);
|
|
Rasterizer.DrawTriangle(Right0, FalloffRight0, Left1, Right0Pos, FalloffRight0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(FalloffRight0, Left1, FalloffLeft1, FalloffRight0Pos, Left1Pos, FalloffLeft1Pos, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
void RasterizeControlPointAlpha(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY, FLandscapeEditDataInterface& LandscapeEdit, FVector ControlPointLocation, const TArray<FLandscapeSplineInterpPoint>& Points, ULandscapeLayerInfoObject* LayerInfo, TSet<ULandscapeComponent*>& ModifiedComponents, const TSharedPtr<FModulateAlpha>& ModulateAlpha = nullptr)
|
|
{
|
|
if (LayerInfo == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<uint8> Data;
|
|
Data.AddZeroed((1 + MaxY - MinY) * (1 + MaxX - MinX));
|
|
|
|
int32 ValidMinX = MinX;
|
|
int32 ValidMinY = MinY;
|
|
int32 ValidMaxX = MaxX;
|
|
int32 ValidMaxY = MaxY;
|
|
LandscapeEdit.GetWeightData(LayerInfo, ValidMinX, ValidMinY, ValidMaxX, ValidMaxY, Data.GetData(), 0);
|
|
|
|
if (ValidMinX > ValidMaxX || ValidMinY > ValidMaxY)
|
|
{
|
|
// The control point's bounds don't intersect any data, so skip it. Return the invalid bounds.
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
return;
|
|
}
|
|
|
|
// shrink our data to the valid range returned by GetWeightData
|
|
FLandscapeEditDataInterface::ShrinkData(Data, MinX, MinY, MaxX, MaxY, ValidMinX, ValidMinY, ValidMaxX, ValidMaxY);
|
|
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
FTriangleRasterizer<FLandscapeSplineBlendmaskRasterPolicy> Rasterizer(
|
|
FLandscapeSplineBlendmaskRasterPolicy(Data, MinX, MinY, MaxX, MaxY, ModulateAlpha));
|
|
|
|
const float BlendValue = 255;
|
|
|
|
const FVector2D CenterPos = FVector2D(ControlPointLocation);
|
|
const FVector Center = FVector(1.0f, Points[0].StartEndFalloff, BlendValue);
|
|
|
|
for (int32 i = Points.Num() - 1, j = 0; j < Points.Num(); i = j++)
|
|
{
|
|
// Solid center
|
|
const FVector2D Right0Pos = FVector2D(Points[i].LayerRight);
|
|
const FVector2D Left1Pos = FVector2D(Points[j].LayerLeft);
|
|
const FVector2D Right1Pos = FVector2D(Points[j].LayerRight);
|
|
const FVector Right0 = FVector(1.0f, Points[i].StartEndFalloff, BlendValue);
|
|
const FVector Left1 = FVector(1.0f, Points[j].StartEndFalloff, BlendValue);
|
|
const FVector Right1 = FVector(1.0f, Points[j].StartEndFalloff, BlendValue);
|
|
|
|
Rasterizer.DrawTriangle(Center, Right0, Left1, CenterPos, Right0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(Center, Left1, Right1, CenterPos, Left1Pos, Right1Pos, false);
|
|
|
|
// Falloff
|
|
FVector2D FalloffRight0Pos = FVector2D(Points[i].LayerFalloffRight);
|
|
FVector2D FalloffLeft1Pos = FVector2D(Points[j].LayerFalloffLeft);
|
|
FVector FalloffRight0 = FVector(0.0f, Points[i].StartEndFalloff, BlendValue);
|
|
FVector FalloffLeft1 = FVector(0.0f, Points[j].StartEndFalloff, BlendValue);
|
|
Rasterizer.DrawTriangle(Right0, FalloffRight0, Left1, Right0Pos, FalloffRight0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(FalloffRight0, Left1, FalloffLeft1, FalloffRight0Pos, Left1Pos, FalloffLeft1Pos, false);
|
|
}
|
|
|
|
LandscapeEdit.SetAlphaData(LayerInfo, MinX, MinY, MaxX, MaxY, Data.GetData(), 0, ELandscapeLayerPaintingRestriction::None, !LayerInfo->bNoWeightBlend, false);
|
|
|
|
LandscapeEdit.GetComponentsInRegion(MinX, MinY, MaxX, MaxY, &ModifiedComponents);
|
|
}
|
|
|
|
void RasterizeSegmentHeight(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY, FLandscapeEditDataInterface& LandscapeEdit, const TArray<FLandscapeSplineInterpPoint>& Points, bool bRaiseTerrain, bool bLowerTerrain, TSet<ULandscapeComponent*>& ModifiedComponents)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(LandscapeSpline_RasterizeSegmentHeight);
|
|
|
|
RasterizeHeight(MinX, MinY, MaxX, MaxY, LandscapeEdit, bRaiseTerrain, bLowerTerrain, ModifiedComponents, [&](FTriangleRasterizer<FLandscapeSplineHeightsRasterPolicy>& Rasterizer)
|
|
{
|
|
for (int32 j = 1; j < Points.Num(); j++)
|
|
{
|
|
// Middle
|
|
FVector2D Left0Pos = FVector2D(Points[j - 1].Left);
|
|
FVector2D Right0Pos = FVector2D(Points[j - 1].Right);
|
|
FVector2D Left1Pos = FVector2D(Points[j].Left);
|
|
FVector2D Right1Pos = FVector2D(Points[j].Right);
|
|
FVector Left0 = FVector(1.0f, Points[j - 1].StartEndFalloff, Points[j - 1].Left.Z);
|
|
FVector Right0 = FVector(1.0f, Points[j - 1].StartEndFalloff, Points[j - 1].Right.Z);
|
|
FVector Left1 = FVector(1.0f, Points[j].StartEndFalloff, Points[j].Left.Z);
|
|
FVector Right1 = FVector(1.0f, Points[j].StartEndFalloff, Points[j].Right.Z);
|
|
Rasterizer.DrawTriangle(Left0, Right0, Left1, Left0Pos, Right0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(Right0, Left1, Right1, Right0Pos, Left1Pos, Right1Pos, false);
|
|
|
|
// Left Falloff
|
|
FVector2D FalloffLeft0Pos = FVector2D(Points[j - 1].FalloffLeft);
|
|
FVector2D FalloffLeft1Pos = FVector2D(Points[j].FalloffLeft);
|
|
FVector FalloffLeft0 = FVector(0.0f, Points[j - 1].StartEndFalloff, Points[j - 1].FalloffLeft.Z);
|
|
FVector FalloffLeft1 = FVector(0.0f, Points[j].StartEndFalloff, Points[j].FalloffLeft.Z);
|
|
Rasterizer.DrawTriangle(FalloffLeft0, Left0, FalloffLeft1, FalloffLeft0Pos, Left0Pos, FalloffLeft1Pos, false);
|
|
Rasterizer.DrawTriangle(Left0, FalloffLeft1, Left1, Left0Pos, FalloffLeft1Pos, Left1Pos, false);
|
|
|
|
// Right Falloff
|
|
FVector2D FalloffRight0Pos = FVector2D(Points[j - 1].FalloffRight);
|
|
FVector2D FalloffRight1Pos = FVector2D(Points[j].FalloffRight);
|
|
FVector FalloffRight0 = FVector(0.0f, Points[j - 1].StartEndFalloff, Points[j - 1].FalloffRight.Z);
|
|
FVector FalloffRight1 = FVector(0.0f, Points[j].StartEndFalloff, Points[j].FalloffRight.Z);
|
|
Rasterizer.DrawTriangle(Right0, FalloffRight0, Right1, Right0Pos, FalloffRight0Pos, Right1Pos, false);
|
|
Rasterizer.DrawTriangle(FalloffRight0, Right1, FalloffRight1, FalloffRight0Pos, Right1Pos, FalloffRight1Pos, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void RasterizeSegmentAlpha(int32& MinX, int32& MinY, int32& MaxX, int32& MaxY, FLandscapeEditDataInterface& LandscapeEdit, const TArray<FLandscapeSplineInterpPoint>& Points, ULandscapeLayerInfoObject* LayerInfo, TSet<ULandscapeComponent*>& ModifiedComponents, const TSharedPtr<FModulateAlpha>& ModulateAlpha = nullptr)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(LandscapeSpline_RasterizeSegmentAlpha);
|
|
|
|
if (LayerInfo == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<uint8> Data;
|
|
Data.AddZeroed((1 + MaxY - MinY) * (1 + MaxX - MinX));
|
|
|
|
int32 ValidMinX = MinX;
|
|
int32 ValidMinY = MinY;
|
|
int32 ValidMaxX = MaxX;
|
|
int32 ValidMaxY = MaxY;
|
|
LandscapeEdit.GetWeightData(LayerInfo, ValidMinX, ValidMinY, ValidMaxX, ValidMaxY, Data.GetData(), 0);
|
|
|
|
if (ValidMinX > ValidMaxX || ValidMinY > ValidMaxY)
|
|
{
|
|
// The segment's bounds don't intersect any data, so skip it. Return the invalid bounds.
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
return;
|
|
}
|
|
|
|
// shrink our data to the valid range returned by GetWeightData
|
|
FLandscapeEditDataInterface::ShrinkData(Data, MinX, MinY, MaxX, MaxY, ValidMinX, ValidMinY, ValidMaxX, ValidMaxY);
|
|
|
|
MinX = ValidMinX;
|
|
MinY = ValidMinY;
|
|
MaxX = ValidMaxX;
|
|
MaxY = ValidMaxY;
|
|
|
|
FTriangleRasterizer<FLandscapeSplineBlendmaskRasterPolicy> Rasterizer(
|
|
FLandscapeSplineBlendmaskRasterPolicy(Data, MinX, MinY, MaxX, MaxY, ModulateAlpha));
|
|
|
|
const float BlendValue = 255;
|
|
|
|
for (int32 j = 1; j < Points.Num(); j++)
|
|
{
|
|
// Middle
|
|
FVector2D Left0Pos = FVector2D(Points[j - 1].LayerLeft);
|
|
FVector2D Right0Pos = FVector2D(Points[j - 1].LayerRight);
|
|
FVector2D Left1Pos = FVector2D(Points[j].LayerLeft);
|
|
FVector2D Right1Pos = FVector2D(Points[j].LayerRight);
|
|
FVector Left0 = FVector(1.0f, Points[j - 1].StartEndFalloff, BlendValue);
|
|
FVector Right0 = FVector(1.0f, Points[j - 1].StartEndFalloff, BlendValue);
|
|
FVector Left1 = FVector(1.0f, Points[j].StartEndFalloff, BlendValue);
|
|
FVector Right1 = FVector(1.0f, Points[j].StartEndFalloff, BlendValue);
|
|
Rasterizer.DrawTriangle(Left0, Right0, Left1, Left0Pos, Right0Pos, Left1Pos, false);
|
|
Rasterizer.DrawTriangle(Right0, Left1, Right1, Right0Pos, Left1Pos, Right1Pos, false);
|
|
|
|
// Left Falloff
|
|
FVector2D FalloffLeft0Pos = FVector2D(Points[j - 1].LayerFalloffLeft);
|
|
FVector2D FalloffLeft1Pos = FVector2D(Points[j].LayerFalloffLeft);
|
|
FVector FalloffLeft0 = FVector(0.0f, Points[j - 1].StartEndFalloff, BlendValue);
|
|
FVector FalloffLeft1 = FVector(0.0f, Points[j].StartEndFalloff, BlendValue);
|
|
Rasterizer.DrawTriangle(FalloffLeft0, Left0, FalloffLeft1, FalloffLeft0Pos, Left0Pos, FalloffLeft1Pos, false);
|
|
Rasterizer.DrawTriangle(Left0, FalloffLeft1, Left1, Left0Pos, FalloffLeft1Pos, Left1Pos, false);
|
|
|
|
// Right Falloff
|
|
FVector2D FalloffRight0Pos = FVector2D(Points[j - 1].LayerFalloffRight);
|
|
FVector2D FalloffRight1Pos = FVector2D(Points[j].LayerFalloffRight);
|
|
FVector FalloffRight0 = FVector(0.0f, Points[j - 1].StartEndFalloff, BlendValue);
|
|
FVector FalloffRight1 = FVector(0.0f, Points[j].StartEndFalloff, BlendValue);
|
|
Rasterizer.DrawTriangle(Right0, FalloffRight0, Right1, Right0Pos, FalloffRight0Pos, Right1Pos, false);
|
|
Rasterizer.DrawTriangle(FalloffRight0, Right1, FalloffRight1, FalloffRight0Pos, Right1Pos, FalloffRight1Pos, false);
|
|
}
|
|
|
|
LandscapeEdit.SetAlphaData(LayerInfo, MinX, MinY, MaxX, MaxY, Data.GetData(), 0, ELandscapeLayerPaintingRestriction::None, !LayerInfo->bNoWeightBlend, false);
|
|
|
|
LandscapeEdit.GetComponentsInRegion(MinX, MinY, MaxX, MaxY, &ModifiedComponents);
|
|
}
|
|
|
|
bool ULandscapeInfo::ApplySplines(bool bOnlySelected, TSet<TObjectPtr<ULandscapeComponent>>* OutModifiedComponents, bool bMarkPackageDirty)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(LandscapeInfo_ApplySplines);
|
|
|
|
bool bResult = false;
|
|
|
|
ALandscape* Landscape = LandscapeActor.Get();
|
|
|
|
const ULandscapeEditLayerBase* Layer = Landscape ? Landscape->FindEditLayerOfTypeConst(ULandscapeEditLayerSplines::StaticClass()) : nullptr;
|
|
FGuid SplinesTargetLayerGuid = Layer ? Layer->GetGuid() : Landscape ? Landscape->GetEditingLayer() : FGuid();
|
|
FScopedSetLandscapeEditingLayer Scope(Landscape, SplinesTargetLayerGuid, [=] { Landscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); });
|
|
|
|
TMap<ULandscapeLayerInfoObject*, TSharedPtr<FModulateAlpha>> ModulatePerLayerInfo;
|
|
int32 LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY;
|
|
if (!GetLandscapeExtent(LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto GetOrCreateModulate = [&](ULandscapeLayerInfoObject* LayerInfo) -> TSharedPtr<FModulateAlpha>
|
|
{
|
|
if (const TSharedPtr<FModulateAlpha>* SharedPtr = ModulatePerLayerInfo.Find(LayerInfo))
|
|
{
|
|
return *SharedPtr;
|
|
}
|
|
|
|
TSharedPtr<FModulateAlpha> SharedPtr = FModulateAlpha::CreateFromLayerInfo(LayerInfo, LandscapeMinX, LandscapeMinY);
|
|
ModulatePerLayerInfo.Add(LayerInfo, SharedPtr);
|
|
|
|
return SharedPtr;
|
|
};
|
|
|
|
ForAllSplineActors([&](TScriptInterface<ILandscapeSplineInterface> SplineOwner)
|
|
{
|
|
bResult |= ApplySplinesInternal(bOnlySelected, SplineOwner, OutModifiedComponents, bMarkPackageDirty, LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY, GetOrCreateModulate);
|
|
});
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool ULandscapeInfo::ApplySplinesInternal(bool bOnlySelected, TScriptInterface<ILandscapeSplineInterface> SplineOwner, TSet<TObjectPtr<ULandscapeComponent>>* OutModifiedComponents, bool bMarkPackageDirty, int32 LandscapeMinX, int32 LandscapeMinY, int32 LandscapeMaxX, int32 LandscapeMaxY, TFunctionRef<TSharedPtr<FModulateAlpha>(ULandscapeLayerInfoObject*)> GetOrCreateModulate)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(LandscapeInfo_ApplySplinesInternal);
|
|
|
|
if (!SplineOwner || !SplineOwner->IsSplineOwnerValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ULandscapeSplinesComponent* SplineComponent = SplineOwner->GetSplinesComponent();
|
|
|
|
if (!SplineComponent || !SplineComponent->IsRegistered() || SplineComponent->ControlPoints.Num() == 0 || SplineComponent->Segments.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
const FTransform SplineToLandscape = SplineComponent->GetComponentTransform().GetRelativeTransform(SplineOwner->LandscapeActorToWorld());
|
|
|
|
FLandscapeEditDataInterface LandscapeEdit(this);
|
|
FLandscapeDoNotDirtyScope DoNotDirtyScope(LandscapeEdit, !bMarkPackageDirty);
|
|
TSet<ULandscapeComponent*> ModifiedComponents;
|
|
|
|
|
|
for (const ULandscapeSplineControlPoint* ControlPoint : SplineComponent->ControlPoints)
|
|
{
|
|
if (bOnlySelected && !ControlPoint->IsSplineSelected())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ControlPoint->GetPoints().Num() < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FBox ControlPointBounds = ControlPoint->GetBounds();
|
|
ControlPointBounds = ControlPointBounds.TransformBy(SplineToLandscape.ToMatrixWithScale());
|
|
|
|
int32 MinX = FMath::CeilToInt32(ControlPointBounds.Min.X);
|
|
int32 MinY = FMath::CeilToInt32(ControlPointBounds.Min.Y);
|
|
int32 MaxX = FMath::FloorToInt32(ControlPointBounds.Max.X);
|
|
int32 MaxY = FMath::FloorToInt32(ControlPointBounds.Max.Y);
|
|
|
|
MinX = FMath::Max(MinX, LandscapeMinX);
|
|
MinY = FMath::Max(MinY, LandscapeMinY);
|
|
MaxX = FMath::Min(MaxX, LandscapeMaxX);
|
|
MaxY = FMath::Min(MaxY, LandscapeMaxY);
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
// The control point's bounds don't intersect the landscape, so skip it entirely
|
|
continue;
|
|
}
|
|
|
|
TArray<FLandscapeSplineInterpPoint> Points = ControlPoint->GetPoints();
|
|
for (int32 j = 0; j < Points.Num(); j++)
|
|
{
|
|
Points[j].Center = SplineToLandscape.TransformPosition(Points[j].Center);
|
|
Points[j].Left = SplineToLandscape.TransformPosition(Points[j].Left);
|
|
Points[j].Right = SplineToLandscape.TransformPosition(Points[j].Right);
|
|
Points[j].FalloffLeft = SplineToLandscape.TransformPosition(Points[j].FalloffLeft);
|
|
Points[j].FalloffRight = SplineToLandscape.TransformPosition(Points[j].FalloffRight);
|
|
|
|
Points[j].LayerLeft = SplineToLandscape.TransformPosition(Points[j].LayerLeft);
|
|
Points[j].LayerRight = SplineToLandscape.TransformPosition(Points[j].LayerRight);
|
|
Points[j].LayerFalloffLeft = SplineToLandscape.TransformPosition(Points[j].LayerFalloffLeft);
|
|
Points[j].LayerFalloffRight = SplineToLandscape.TransformPosition(Points[j].LayerFalloffRight);
|
|
|
|
// local-heights to texture value heights
|
|
Points[j].Left.Z = Points[j].Left.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].Right.Z = Points[j].Right.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffLeft.Z = Points[j].FalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffRight.Z = Points[j].FalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
|
|
Points[j].LayerLeft.Z = Points[j].LayerLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerRight.Z = Points[j].LayerRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffLeft.Z = Points[j].LayerFalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffRight.Z = Points[j].LayerFalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
}
|
|
|
|
// Heights raster
|
|
if (ControlPoint->bRaiseTerrain || ControlPoint->bLowerTerrain)
|
|
{
|
|
const FVector Center3D = SplineToLandscape.TransformPosition(ControlPoint->Location);
|
|
|
|
RasterizeControlPointHeights(MinX, MinY, MaxX, MaxY, LandscapeEdit, Center3D, Points, ControlPoint->bRaiseTerrain, ControlPoint->bLowerTerrain, ModifiedComponents);
|
|
}
|
|
|
|
// Blend layer raster
|
|
ULandscapeLayerInfoObject* LayerInfo = GetLayerInfoByName(ControlPoint->LayerName);
|
|
if (ControlPoint->LayerName != NAME_None && LayerInfo != NULL)
|
|
{
|
|
const FVector Center3D = SplineToLandscape.TransformPosition(ControlPoint->Location);
|
|
|
|
TSharedPtr<FModulateAlpha> ModulateAlpha = GetOrCreateModulate(LayerInfo);
|
|
RasterizeControlPointAlpha(MinX, MinY, MaxX, MaxY, LandscapeEdit, Center3D, Points, LayerInfo, ModifiedComponents, ModulateAlpha);
|
|
}
|
|
}
|
|
|
|
for (const ULandscapeSplineSegment* Segment : SplineComponent->Segments)
|
|
{
|
|
if (bOnlySelected && !Segment->IsSplineSelected())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FBox SegmentBounds = Segment->GetBounds();
|
|
SegmentBounds = SegmentBounds.TransformBy(SplineToLandscape.ToMatrixWithScale());
|
|
|
|
int32 MinX = FMath::CeilToInt32(SegmentBounds.Min.X);
|
|
int32 MinY = FMath::CeilToInt32(SegmentBounds.Min.Y);
|
|
int32 MaxX = FMath::FloorToInt32(SegmentBounds.Max.X);
|
|
int32 MaxY = FMath::FloorToInt32(SegmentBounds.Max.Y);
|
|
|
|
MinX = FMath::Max(MinX, LandscapeMinX);
|
|
MinY = FMath::Max(MinY, LandscapeMinY);
|
|
MaxX = FMath::Min(MaxX, LandscapeMaxX);
|
|
MaxY = FMath::Min(MaxY, LandscapeMaxY);
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
// The segment's bounds don't intersect the landscape, so skip it entirely
|
|
continue;
|
|
}
|
|
|
|
TArray<FLandscapeSplineInterpPoint> Points = Segment->GetPoints();
|
|
for (int32 j = 0; j < Points.Num(); j++)
|
|
{
|
|
Points[j].Center = SplineToLandscape.TransformPosition(Points[j].Center);
|
|
Points[j].Left = SplineToLandscape.TransformPosition(Points[j].Left);
|
|
Points[j].Right = SplineToLandscape.TransformPosition(Points[j].Right);
|
|
Points[j].FalloffLeft = SplineToLandscape.TransformPosition(Points[j].FalloffLeft);
|
|
Points[j].FalloffRight = SplineToLandscape.TransformPosition(Points[j].FalloffRight);
|
|
|
|
Points[j].LayerLeft = SplineToLandscape.TransformPosition(Points[j].LayerLeft);
|
|
Points[j].LayerRight = SplineToLandscape.TransformPosition(Points[j].LayerRight);
|
|
Points[j].LayerFalloffLeft = SplineToLandscape.TransformPosition(Points[j].LayerFalloffLeft);
|
|
Points[j].LayerFalloffRight = SplineToLandscape.TransformPosition(Points[j].LayerFalloffRight);
|
|
|
|
// local-heights to texture value heights
|
|
Points[j].Left.Z = Points[j].Left.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].Right.Z = Points[j].Right.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffLeft.Z = Points[j].FalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffRight.Z = Points[j].FalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
|
|
Points[j].LayerLeft.Z = Points[j].LayerLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerRight.Z = Points[j].LayerRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffLeft.Z = Points[j].LayerFalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffRight.Z = Points[j].LayerFalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
}
|
|
|
|
// Heights raster
|
|
if (Segment->bRaiseTerrain || Segment->bLowerTerrain)
|
|
{
|
|
RasterizeSegmentHeight(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, Segment->bRaiseTerrain, Segment->bLowerTerrain, ModifiedComponents);
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
// The segment's bounds don't intersect any height data.
|
|
// This means it wouldn't intersect any weightmap data either so we can skip the alpha rasterize below
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Blend layer raster
|
|
ULandscapeLayerInfoObject* LayerInfo = GetLayerInfoByName(Segment->LayerName);
|
|
if (Segment->LayerName != NAME_None && LayerInfo != NULL)
|
|
{
|
|
TSharedPtr<FModulateAlpha> ModulateAlpha = GetOrCreateModulate(LayerInfo);
|
|
RasterizeSegmentAlpha(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, LayerInfo, ModifiedComponents, ModulateAlpha);
|
|
}
|
|
}
|
|
|
|
LandscapeEdit.Flush();
|
|
|
|
if (!CanHaveLayersContent())
|
|
{
|
|
ALandscapeProxy::InvalidateGeneratedComponentData(ModifiedComponents);
|
|
}
|
|
|
|
for (ULandscapeComponent* Component : ModifiedComponents)
|
|
{
|
|
if (Component->GetLandscapeProxy()->HasLayersContent())
|
|
{
|
|
Component->RequestHeightmapUpdate();
|
|
Component->RequestWeightmapUpdate();
|
|
if (OutModifiedComponents)
|
|
{
|
|
OutModifiedComponents->Add(Component);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Recreate collision for modified components and update the navmesh
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->GetCollisionComponent();
|
|
if (CollisionComponent)
|
|
{
|
|
CollisionComponent->RecreateCollision();
|
|
FNavigationSystem::UpdateComponentData(*CollisionComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace LandscapeSplineRaster
|
|
{
|
|
void RasterizeSegmentPoints(ULandscapeInfo* LandscapeInfo, TArray<FLandscapeSplineInterpPoint> Points, const FTransform& SplineToWorld, bool bRaiseTerrain, bool bLowerTerrain, ULandscapeLayerInfoObject* LayerInfo)
|
|
{
|
|
ALandscapeProxy* LandscapeProxy = LandscapeInfo->GetLandscapeProxy();
|
|
const FTransform SplineToLandscape = SplineToWorld.GetRelativeTransform(LandscapeProxy->LandscapeActorToWorld());
|
|
|
|
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo);
|
|
TSet<ULandscapeComponent*> ModifiedComponents;
|
|
|
|
// I'd dearly love to use FIntRect in this code, but Landscape works with "Inclusive Max" and FIntRect is "Exclusive Max"
|
|
int32 LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY;
|
|
if (!LandscapeInfo->GetLandscapeExtent(LandscapeMinX, LandscapeMinY, LandscapeMaxX, LandscapeMaxY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FBox SegmentBounds = FBox(ForceInit);
|
|
for (const FLandscapeSplineInterpPoint& Point : Points)
|
|
{
|
|
SegmentBounds += Point.FalloffLeft;
|
|
SegmentBounds += Point.FalloffRight;
|
|
}
|
|
|
|
SegmentBounds = SegmentBounds.TransformBy(SplineToLandscape.ToMatrixWithScale());
|
|
|
|
int32 MinX = FMath::CeilToInt32(SegmentBounds.Min.X);
|
|
int32 MinY = FMath::CeilToInt32(SegmentBounds.Min.Y);
|
|
int32 MaxX = FMath::FloorToInt32(SegmentBounds.Max.X);
|
|
int32 MaxY = FMath::FloorToInt32(SegmentBounds.Max.Y);
|
|
|
|
MinX = FMath::Max(MinX, LandscapeMinX);
|
|
MinY = FMath::Max(MinY, LandscapeMinY);
|
|
MaxX = FMath::Min(MaxX, LandscapeMaxX);
|
|
MaxY = FMath::Min(MaxY, LandscapeMaxY);
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
// The segment's bounds don't intersect the landscape, so skip it entirely
|
|
return;
|
|
}
|
|
|
|
for (int32 j = 0; j < Points.Num(); j++)
|
|
{
|
|
Points[j].Center = SplineToLandscape.TransformPosition(Points[j].Center);
|
|
Points[j].Left = SplineToLandscape.TransformPosition(Points[j].Left);
|
|
Points[j].Right = SplineToLandscape.TransformPosition(Points[j].Right);
|
|
Points[j].FalloffLeft = SplineToLandscape.TransformPosition(Points[j].FalloffLeft);
|
|
Points[j].FalloffRight = SplineToLandscape.TransformPosition(Points[j].FalloffRight);
|
|
|
|
Points[j].LayerLeft = SplineToLandscape.TransformPosition(Points[j].LayerLeft);
|
|
Points[j].LayerRight = SplineToLandscape.TransformPosition(Points[j].LayerRight);
|
|
Points[j].LayerFalloffLeft = SplineToLandscape.TransformPosition(Points[j].LayerFalloffLeft);
|
|
Points[j].LayerFalloffRight = SplineToLandscape.TransformPosition(Points[j].LayerFalloffRight);
|
|
|
|
// local-heights to texture value heights
|
|
Points[j].Left.Z = Points[j].Left.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].Right.Z = Points[j].Right.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffLeft.Z = Points[j].FalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].FalloffRight.Z = Points[j].FalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
|
|
Points[j].LayerLeft.Z = Points[j].LayerLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerRight.Z = Points[j].LayerRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffLeft.Z = Points[j].LayerFalloffLeft.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
Points[j].LayerFalloffRight.Z = Points[j].LayerFalloffRight.Z * LANDSCAPE_INV_ZSCALE + LandscapeDataAccess::MidValue;
|
|
}
|
|
|
|
// Heights raster
|
|
if (bRaiseTerrain || bLowerTerrain)
|
|
{
|
|
RasterizeSegmentHeight(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, bRaiseTerrain, bLowerTerrain, ModifiedComponents);
|
|
|
|
if (MinX > MaxX || MinY > MaxY)
|
|
{
|
|
// The segment's bounds don't intersect any data, so we skip it entirely
|
|
// it wouldn't intersect any weightmap data either so we don't even bother trying
|
|
}
|
|
}
|
|
|
|
// Blend layer raster
|
|
if (LayerInfo != NULL)
|
|
{
|
|
RasterizeSegmentAlpha(MinX, MinY, MaxX, MaxY, LandscapeEdit, Points, LayerInfo, ModifiedComponents);
|
|
}
|
|
|
|
LandscapeEdit.Flush();
|
|
|
|
if (!LandscapeProxy->HasLayersContent())
|
|
{
|
|
for (ULandscapeComponent* Component : ModifiedComponents)
|
|
{
|
|
// Recreate collision for modified components and update the navmesh
|
|
ULandscapeHeightfieldCollisionComponent* CollisionComponent = Component->GetCollisionComponent();
|
|
if (CollisionComponent)
|
|
{
|
|
CollisionComponent->RecreateCollision();
|
|
FNavigationSystem::UpdateComponentData(*CollisionComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool LineIntersect(const FVector2D& L1Start, const FVector2D& L1End, const FVector2D& L2Start, const FVector2D& L2End, FVector2D& Intersect, float Tolerance = KINDA_SMALL_NUMBER)
|
|
{
|
|
float tA = static_cast<float>((L2End - L2Start) ^ (L2Start - L1Start));
|
|
float tB = static_cast<float>((L1End - L1Start) ^ (L2Start - L1Start));
|
|
float Denom = static_cast<float>((L2End - L2Start) ^ (L1End - L1Start));
|
|
|
|
if (FMath::IsNearlyZero(tA) && FMath::IsNearlyZero(tB))
|
|
{
|
|
// Lines are the same
|
|
Intersect = (L2Start + L2End) / 2;
|
|
return true;
|
|
}
|
|
|
|
if (FMath::IsNearlyZero(Denom))
|
|
{
|
|
// Lines are parallel
|
|
Intersect = (L2Start + L2End) / 2;
|
|
return false;
|
|
}
|
|
|
|
tA /= Denom;
|
|
tB /= Denom;
|
|
|
|
Intersect = L1Start + tA * (L1End - L1Start);
|
|
|
|
if (tA >= -Tolerance && tA <= (1.0f + Tolerance) && tB >= -Tolerance && tB <= (1.0f + Tolerance))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FixSelfIntersection(TArray<FLandscapeSplineInterpPoint>& Points, FVector FLandscapeSplineInterpPoint::* Side)
|
|
{
|
|
int32 StartSide = INDEX_NONE;
|
|
for (int32 i = 0; i < Points.Num(); i++)
|
|
{
|
|
bool bReversed = false;
|
|
|
|
if (i < Points.Num() - 1)
|
|
{
|
|
const FLandscapeSplineInterpPoint& CurrentPoint = Points[i];
|
|
const FLandscapeSplineInterpPoint& NextPoint = Points[i + 1];
|
|
const FVector Direction = (NextPoint.Center - CurrentPoint.Center).GetSafeNormal();
|
|
const FVector SideDirection = (NextPoint.*Side - CurrentPoint.*Side).GetSafeNormal();
|
|
bReversed = (SideDirection | Direction) < 0;
|
|
}
|
|
|
|
if (bReversed)
|
|
{
|
|
if (StartSide == INDEX_NONE)
|
|
{
|
|
StartSide = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (StartSide != INDEX_NONE)
|
|
{
|
|
int32 EndSide = i;
|
|
|
|
// step startSide back until before the endSide point
|
|
while (StartSide > 0)
|
|
{
|
|
const float Projection = static_cast<float>((Points[StartSide].*Side - Points[StartSide - 1].*Side) | (Points[EndSide].*Side - Points[StartSide - 1].*Side));
|
|
if (Projection >= 0)
|
|
{
|
|
break;
|
|
}
|
|
StartSide--;
|
|
}
|
|
// step endSide forwards until after the startSide point
|
|
while (EndSide < Points.Num() - 1)
|
|
{
|
|
const float Projection = static_cast<float>((Points[EndSide].*Side - Points[EndSide + 1].*Side) | (Points[StartSide].*Side - Points[EndSide + 1].*Side));
|
|
if (Projection >= 0)
|
|
{
|
|
break;
|
|
}
|
|
EndSide++;
|
|
}
|
|
|
|
// Can't do anything if the start and end intersect, as they're both unalterable
|
|
if (StartSide == 0 && EndSide == Points.Num() - 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FVector2D Collapse;
|
|
if (StartSide == 0)
|
|
{
|
|
Collapse = FVector2D(Points[StartSide].*Side);
|
|
StartSide++;
|
|
}
|
|
else if (EndSide == Points.Num() - 1)
|
|
{
|
|
Collapse = FVector2D(Points[EndSide].*Side);
|
|
EndSide--;
|
|
}
|
|
else
|
|
{
|
|
LineIntersect(FVector2D(Points[StartSide - 1].*Side), FVector2D(Points[StartSide].*Side),
|
|
FVector2D(Points[EndSide + 1].*Side), FVector2D(Points[EndSide].*Side), Collapse);
|
|
}
|
|
|
|
for (int32 j = StartSide; j <= EndSide; j++)
|
|
{
|
|
(Points[j].*Side).X = Collapse.X;
|
|
(Points[j].*Side).Y = Collapse.Y;
|
|
}
|
|
|
|
StartSide = INDEX_NONE;
|
|
i = EndSide;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Pointify(const FInterpCurveVector& SplineInfo, TArray<FLandscapeSplineInterpPoint>& Points, int32 NumSubdivisions,
|
|
float StartFalloffFraction, float EndFalloffFraction,
|
|
const float StartWidth, const float EndWidth,
|
|
const float StartLayerWidth, const float EndLayerWidth,
|
|
const FPointifyFalloffs& Falloffs,
|
|
const float StartRollDegrees, const float EndRollDegrees)
|
|
{
|
|
// Stop the start and end fall-off overlapping
|
|
const float TotalFalloff = StartFalloffFraction + EndFalloffFraction;
|
|
if (TotalFalloff > 1.0f)
|
|
{
|
|
StartFalloffFraction /= TotalFalloff;
|
|
EndFalloffFraction /= TotalFalloff;
|
|
}
|
|
|
|
const float StartRoll = FMath::DegreesToRadians(StartRollDegrees);
|
|
const float EndRoll = FMath::DegreesToRadians(EndRollDegrees);
|
|
|
|
float OldKeyTime = 0;
|
|
for (int32 i = 0; i < SplineInfo.Points.Num(); i++)
|
|
{
|
|
const float NewKeyTime = SplineInfo.Points[i].InVal;
|
|
const float NewKeyCosInterp = 0.5f - 0.5f * FMath::Cos(NewKeyTime * PI);
|
|
const float NewKeyWidth = FMath::Lerp(StartWidth, EndWidth, NewKeyCosInterp);
|
|
const float NewKeyLayerWidth = FMath::Lerp(StartLayerWidth, EndLayerWidth, NewKeyCosInterp);
|
|
const float NewKeyLeftFalloff = FMath::Lerp(Falloffs.StartLeftSide, Falloffs.EndLeftSide, NewKeyCosInterp);
|
|
const float NewKeyRightFalloff = FMath::Lerp(Falloffs.StartRightSide, Falloffs.EndRightSide, NewKeyCosInterp);
|
|
const float NewKeyLeftLayerFalloff = FMath::Lerp(Falloffs.StartLeftSideLayer, Falloffs.EndLeftSideLayer, NewKeyCosInterp);
|
|
const float NewKeyRightLayerFalloff = FMath::Lerp(Falloffs.StartRightSideLayer, Falloffs.EndRightSideLayer, NewKeyCosInterp);
|
|
const float NewKeyRoll = FMath::Lerp(StartRoll, EndRoll, NewKeyCosInterp);
|
|
const FVector NewKeyPos = SplineInfo.Eval(NewKeyTime, FVector::ZeroVector);
|
|
const FVector NewKeyTangent = SplineInfo.EvalDerivative(NewKeyTime, FVector::ZeroVector).GetSafeNormal();
|
|
const FVector NewKeyBiNormal = FQuat(NewKeyTangent, -NewKeyRoll).RotateVector((NewKeyTangent ^ FVector(0, 0, -1)).GetSafeNormal());
|
|
const FVector NewKeyLeftPos = NewKeyPos - NewKeyBiNormal * NewKeyWidth;
|
|
const FVector NewKeyRightPos = NewKeyPos + NewKeyBiNormal * NewKeyWidth;
|
|
const FVector NewKeyFalloffLeftPos = NewKeyPos - NewKeyBiNormal * (NewKeyWidth + NewKeyLeftFalloff);
|
|
const FVector NewKeyFalloffRightPos = NewKeyPos + NewKeyBiNormal * (NewKeyWidth + NewKeyRightFalloff);
|
|
|
|
const FVector NewKeyLayerLeftPos = NewKeyPos - NewKeyBiNormal * NewKeyLayerWidth;
|
|
const FVector NewKeyLayerRightPos = NewKeyPos + NewKeyBiNormal * NewKeyLayerWidth;
|
|
const FVector NewKeyLayerFalloffLeftPos = NewKeyPos - NewKeyBiNormal * (NewKeyLayerWidth + NewKeyLeftLayerFalloff);
|
|
const FVector NewKeyLayerFalloffRightPos = NewKeyPos + NewKeyBiNormal * (NewKeyLayerWidth + NewKeyRightLayerFalloff);
|
|
const float NewKeyStartEndFalloff = FMath::Min((StartFalloffFraction > 0 ? NewKeyTime / StartFalloffFraction : 1.0f), (EndFalloffFraction > 0 ? (1 - NewKeyTime) / EndFalloffFraction : 1.0f));
|
|
|
|
// If not the first keypoint, interp from the last keypoint.
|
|
if (i > 0)
|
|
{
|
|
const int32 NumSteps = FMath::CeilToInt((NewKeyTime - OldKeyTime) * NumSubdivisions);
|
|
const float DrawSubstep = (NewKeyTime - OldKeyTime) / NumSteps;
|
|
|
|
// Add a point for each substep, except the ends because that's the point added outside the interp'ing.
|
|
for (int32 j = 1; j < NumSteps; j++)
|
|
{
|
|
const float NewTime = OldKeyTime + j*DrawSubstep;
|
|
const float NewCosInterp = 0.5f - 0.5f * FMath::Cos(NewTime * PI);
|
|
const float NewWidth = FMath::Lerp(StartWidth, EndWidth, NewCosInterp);
|
|
const float NewLayerWidth = FMath::Lerp(StartLayerWidth, EndLayerWidth, NewCosInterp);
|
|
const float NewLeftFalloff = FMath::Lerp(Falloffs.StartLeftSide, Falloffs.EndLeftSide, NewCosInterp);
|
|
const float NewRightFalloff = FMath::Lerp(Falloffs.StartRightSide, Falloffs.EndRightSide, NewCosInterp);
|
|
const float NewLeftLayerFalloff = FMath::Lerp(Falloffs.StartLeftSideLayer, Falloffs.EndLeftSideLayer, NewCosInterp);
|
|
const float NewRightLayerFalloff = FMath::Lerp(Falloffs.StartRightSideLayer, Falloffs.EndRightSideLayer, NewCosInterp);
|
|
const float NewRoll = FMath::Lerp(StartRoll, EndRoll, NewCosInterp);
|
|
const FVector NewPos = SplineInfo.Eval(NewTime, FVector::ZeroVector);
|
|
const FVector NewTangent = SplineInfo.EvalDerivative(NewTime, FVector::ZeroVector).GetSafeNormal();
|
|
const FVector NewBiNormal = FQuat(NewTangent, -NewRoll).RotateVector((NewTangent ^ FVector(0, 0, -1)).GetSafeNormal());
|
|
const FVector NewLeftPos = NewPos - NewBiNormal * NewWidth;
|
|
const FVector NewRightPos = NewPos + NewBiNormal * NewWidth;
|
|
const FVector NewFalloffLeftPos = NewPos - NewBiNormal * (NewWidth + NewLeftFalloff);
|
|
const FVector NewFalloffRightPos = NewPos + NewBiNormal * (NewWidth + NewRightFalloff);
|
|
|
|
const FVector NewLayerLeftPos = NewPos - NewBiNormal * NewLayerWidth;
|
|
const FVector NewLayerRightPos = NewPos + NewBiNormal * NewLayerWidth;
|
|
const FVector NewLayerFalloffLeftPos = NewPos - NewBiNormal * (NewLayerWidth + NewLeftLayerFalloff);
|
|
const FVector NewLayerFalloffRightPos = NewPos + NewBiNormal * (NewLayerWidth + NewRightLayerFalloff);
|
|
const float NewStartEndFalloff = FMath::Min((StartFalloffFraction > 0 ? NewTime / StartFalloffFraction : 1.0f), (EndFalloffFraction > 0 ? (1 - NewTime) / EndFalloffFraction : 1.0f));
|
|
|
|
Points.Emplace(NewPos, NewLeftPos, NewRightPos, NewFalloffLeftPos, NewFalloffRightPos, NewLayerLeftPos, NewLayerRightPos, NewLayerFalloffLeftPos, NewLayerFalloffRightPos, NewStartEndFalloff);
|
|
}
|
|
}
|
|
|
|
Points.Emplace(NewKeyPos, NewKeyLeftPos, NewKeyRightPos, NewKeyFalloffLeftPos, NewKeyFalloffRightPos, NewKeyLayerLeftPos, NewKeyLayerRightPos, NewKeyLayerFalloffLeftPos, NewKeyLayerFalloffRightPos, NewKeyStartEndFalloff);
|
|
|
|
OldKeyTime = NewKeyTime;
|
|
}
|
|
|
|
// Handle self-intersection errors due to tight turns
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::Left);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::Right);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::FalloffLeft);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::FalloffRight);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::LayerLeft);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::LayerRight);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::LayerFalloffLeft);
|
|
FixSelfIntersection(Points, &FLandscapeSplineInterpPoint::LayerFalloffRight);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#undef LOCTEXT_NAMESPACE
|