Skip to content

Commit

Permalink
Optimized terrain generation
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewScheidecker committed Apr 22, 2014
1 parent 873c357 commit 4275a1f
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 42 deletions.
Binary file modified Content/Blueprints/CavernProbabilityByHeight.uasset
Binary file not shown.
Binary file modified Content/Blueprints/DirtProbabilityByHeight.uasset
Binary file not shown.
Binary file modified Content/Maps/BrickMap.umap
Binary file not shown.
11 changes: 7 additions & 4 deletions Plugins/BrickGrid/Source/BrickGrid/Classes/BrickGridComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
#pragma once
#include "BrickGridComponent.generated.h"

namespace BrickGridConstants
{
enum { MaxBricksPerRegionAxisLog2 = 7 };
enum { MaxBricksPerRegionAxis = 1 << MaxBricksPerRegionAxisLog2 };
};

/** Shifts a number right with sign extension. */
inline int32 SignedShiftRight(int32 A,int32 B)
{
Expand Down Expand Up @@ -237,6 +243,7 @@ class UBrickGridComponent : public USceneComponent
BRICKGRID_API FBrick GetBrick(const FInt3& BrickCoordinates) const;

BRICKGRID_API void GetBrickMaterialArray(const FInt3& MinBrickCoordinates,const FInt3& MaxBrickCoordinates,TArray<uint8>& OutBrickMaterials) const;
BRICKGRID_API void SetBrickMaterialArray(const FInt3& MinBrickCoordinates,const FInt3& MaxBrickCoordinates,const TArray<uint8>& BrickMaterials);

// Returns a height-map containing the non-empty brick with greatest Z for each XY in the rectangle bounded by MinBrickCoordinates.XY-MaxBrickCoordinates.XY.
// The returned heights are relative to MinBrickCoordinates.Z, but MaxBrickCoordinates.Z is ignored.
Expand All @@ -247,10 +254,6 @@ class UBrickGridComponent : public USceneComponent
UFUNCTION(BlueprintCallable,Category = "Brick Grid")
BRICKGRID_API bool SetBrick(const FInt3& BrickCoordinates,int32 MaterialIndex);

// Writes the brick at the given coordinates without invalidating the chunk components.
UFUNCTION(BlueprintCallable,Category = "Brick Grid")
BRICKGRID_API bool SetBrickWithoutInvalidatingComponents(const FInt3& BrickCoordinates,int32 MaterialIndex);

// Invalidates the chunk components for a range of brick coordinates.
UFUNCTION(BlueprintCallable,Category = "Brick Grid")
BRICKGRID_API void InvalidateChunkComponents(const FInt3& MinBrickCoordinates,const FInt3& MaxBrickCoordinates);
Expand Down
44 changes: 37 additions & 7 deletions Plugins/BrickGrid/Source/BrickGrid/Private/BrickGridComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ void UBrickGridComponent::Init(const FBrickGridParameters& InParameters)
Parameters.EmptyMaterialIndex = FMath::Clamp<int32>(Parameters.EmptyMaterialIndex, 0, Parameters.Materials.Num() - 1);

// Limit each region to 128x128x128 bricks, which is the largest power of 2 size that can be rendered using 8-bit relative vertex positions.
Parameters.BricksPerRegionLog2 = FInt3::Clamp(Parameters.BricksPerRegionLog2, FInt3::Scalar(0), FInt3::Scalar(7));
Parameters.BricksPerRegionLog2 = FInt3::Clamp(Parameters.BricksPerRegionLog2, FInt3::Scalar(0), FInt3::Scalar(BrickGridConstants::MaxBricksPerRegionAxisLog2));

// Don't allow fractional chunks/region, or chunks smaller than one brick.
Parameters.RenderChunksPerRegionLog2 = FInt3::Clamp(Parameters.RenderChunksPerRegionLog2, FInt3::Scalar(0), Parameters.BricksPerRegionLog2);
Expand Down Expand Up @@ -135,17 +135,46 @@ void UBrickGridComponent::GetBrickMaterialArray(const FInt3& MinBrickCoordinates
}
}

bool UBrickGridComponent::SetBrick(const FInt3& BrickCoordinates, int32 MaterialIndex)
void UBrickGridComponent::SetBrickMaterialArray(const FInt3& MinBrickCoordinates,const FInt3& MaxBrickCoordinates,const TArray<uint8>& BrickMaterials)
{
if(SetBrickWithoutInvalidatingComponents(BrickCoordinates,MaterialIndex))
const FInt3 InputSize = MaxBrickCoordinates - MinBrickCoordinates + FInt3::Scalar(1);
const FInt3 MinRegionCoordinates = BrickToRegionCoordinates(MinBrickCoordinates);
const FInt3 MaxRegionCoordinates = BrickToRegionCoordinates(MaxBrickCoordinates);
for(int32 RegionY = MinRegionCoordinates.Y;RegionY <= MaxRegionCoordinates.Y;++RegionY)
{
InvalidateChunkComponents(BrickCoordinates,BrickCoordinates);
return true;
for(int32 RegionX = MinRegionCoordinates.X;RegionX <= MaxRegionCoordinates.X;++RegionX)
{
for(int32 RegionZ = MinRegionCoordinates.Z;RegionZ <= MaxRegionCoordinates.Z;++RegionZ)
{
const FInt3 RegionCoordinates(RegionX,RegionY,RegionZ);
const int32* const RegionIndex = RegionCoordinatesToIndex.Find(RegionCoordinates);
const FInt3 MinRegionBrickCoordinates = FInt3(RegionX,RegionY,RegionZ) * BricksPerRegion;
const FInt3 MinInputRegionBrickCoordinates = FInt3::Max(FInt3::Scalar(0),MinBrickCoordinates - MinRegionBrickCoordinates);
const FInt3 MaxInputRegionBrickCoordinates = FInt3::Min(BricksPerRegion - FInt3::Scalar(1),MaxBrickCoordinates - MinRegionBrickCoordinates);
for(int32 RegionBrickY = MinInputRegionBrickCoordinates.Y;RegionBrickY <= MaxInputRegionBrickCoordinates.Y;++RegionBrickY)
{
for(int32 RegionBrickX = MinInputRegionBrickCoordinates.X;RegionBrickX <= MaxInputRegionBrickCoordinates.X;++RegionBrickX)
{
const int32 InputX = MinRegionBrickCoordinates.X + RegionBrickX - MinBrickCoordinates.X;
const int32 InputY = MinRegionBrickCoordinates.Y + RegionBrickY - MinBrickCoordinates.Y;
const int32 InputMinZ = MinRegionBrickCoordinates.Z + MinInputRegionBrickCoordinates.Z - MinBrickCoordinates.Z;
const int32 InputSizeZ = MaxInputRegionBrickCoordinates.Z - MinInputRegionBrickCoordinates.Z + 1;
const uint32 InputBaseBrickIndex = (InputY * InputSize.X + InputX) * InputSize.Z + InputMinZ;
const uint32 RegionBaseBrickIndex = (((RegionBrickY << Parameters.BricksPerRegionLog2.X) + RegionBrickX) << Parameters.BricksPerRegionLog2.Z) + MinInputRegionBrickCoordinates.Z;
if(RegionIndex)
{
FMemory::Memcpy(&Regions[*RegionIndex].BrickContents[RegionBaseBrickIndex],&BrickMaterials[InputBaseBrickIndex],InputSizeZ * sizeof(uint8));
}
}
}
}
}
}
return false;

InvalidateChunkComponents(MinBrickCoordinates,MaxBrickCoordinates);
}

bool UBrickGridComponent::SetBrickWithoutInvalidatingComponents(const FInt3& BrickCoordinates, int32 MaterialIndex)
bool UBrickGridComponent::SetBrick(const FInt3& BrickCoordinates, int32 MaterialIndex)
{
if(FInt3::All(BrickCoordinates >= MinBrickCoordinates) && FInt3::All(BrickCoordinates <= MaxBrickCoordinates) && MaterialIndex < Parameters.Materials.Num())
{
Expand All @@ -156,6 +185,7 @@ bool UBrickGridComponent::SetBrickWithoutInvalidatingComponents(const FInt3& Bri
const uint32 BrickIndex = BrickCoordinatesToRegionBrickIndex(RegionCoordinates,BrickCoordinates);
FBrickRegion& Region = Regions[*RegionIndex];
Region.BrickContents[BrickIndex] = MaterialIndex;
InvalidateChunkComponents(BrickCoordinates,BrickCoordinates);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ struct FBrickTerrainGenerationParameters
FNoiseFunction ErosionFunction;

UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Terrain Generation")
FNoiseFunction DirtProbabilityFunction;
FNoiseFunction DirtThicknessFunction;

UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Terrain Generation")
UCurveFloat* DirtThresholdByHeight;
UCurveFloat* DirtThicknessFactorByHeight;

UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Terrain Generation")
FNoiseFunction CavernProbabilityFunction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,52 @@ void UBrickTerrainGenerationLibrary::InitRegion(const FBrickTerrainGenerationPar
const FLocalNoiseFunction LocalErodedHeightFunction(Parameters.ErodedHeightFunction,LocalToWorldScale / Parameters.Scale);
const FLocalNoiseFunction LocalErosionFunction(Parameters.ErosionFunction,LocalToWorldScale / Parameters.Scale);
const FLocalNoiseFunction LocalMoistureFunction(Parameters.MoistureFunction,LocalToWorldScale / Parameters.Scale);
const FLocalNoiseFunction LocalDirtProbabilityFunction(Parameters.DirtProbabilityFunction,LocalToWorldScale / Parameters.Scale);
const FLocalNoiseFunction LocalDirtThicknessFunction(Parameters.DirtThicknessFunction,LocalToWorldScale / Parameters.Scale);
const FLocalNoiseFunction LocalCavernProbabilityFunction(Parameters.CavernProbabilityFunction,LocalToWorldScale / Parameters.Scale);
const float NoiseToLocalScale = Parameters.Scale / LocalToWorldScale;

// Allocate a local array for the generated bricks.
const FInt3 BricksPerRegion = Grid->BricksPerRegion;
TArray<uint8> LocalBrickMaterials;
LocalBrickMaterials.Init(BricksPerRegion.X * BricksPerRegion.Y * BricksPerRegion.Z);

const FInt3 MinRegionBrickCoordinates = RegionCoordinates * BricksPerRegion;
const FInt3 MaxRegionBrickCoordinates = MinRegionBrickCoordinates + BricksPerRegion - FInt3::Scalar(1);
for(int32 Y = MinRegionBrickCoordinates.Y;Y <= MaxRegionBrickCoordinates.Y;++Y)
for(int32 LocalY = 0;LocalY < BricksPerRegion.Y;++LocalY)
{
for(int32 X = MinRegionBrickCoordinates.X;X <= MaxRegionBrickCoordinates.X;++X)
for(int32 LocalX = 0;LocalX < BricksPerRegion.X;++LocalX)
{
const int32 X = MinRegionBrickCoordinates.X + LocalX;
const int32 Y = MinRegionBrickCoordinates.Y + LocalY;
const float Erosion = LocalErosionFunction.Sample2D(BiasedSeed * 59 + X,Y);
const float UnerodedGroundHeight = LocalUnerodedHeightFunction.Sample2D(BiasedSeed * 67 + X,Y) * NoiseToLocalScale;
const float ErodedGroundHeight = LocalErodedHeightFunction.Sample2D(BiasedSeed * 71 + X,Y) * NoiseToLocalScale;
const float GroundHeight = FMath::Lerp(UnerodedGroundHeight,ErodedGroundHeight,Erosion);
const float RockHeight = FMath::Lerp(UnerodedGroundHeight,ErodedGroundHeight,Erosion);
const float BaseDirtThickness = LocalDirtThicknessFunction.Sample2D(BiasedSeed * 79 + X,Y) * NoiseToLocalScale;
const float DirtThicknessFactor = Parameters.DirtThicknessFactorByHeight->FloatCurve.Eval(RockHeight * LocalToWorldScale);
const float DirtThickness = FMath::Max(0.0f,BaseDirtThickness * DirtThicknessFactor);
const float GroundHeight = RockHeight + DirtThickness;

const int32 BrickRockHeight = FMath::Min(MaxRegionBrickCoordinates.Z,FMath::Ceil(RockHeight));
const int32 BrickGroundHeight = FMath::Min(MaxRegionBrickCoordinates.Z,FMath::Ceil(GroundHeight));

for(int32 Z = MinRegionBrickCoordinates.Z;Z <= MaxRegionBrickCoordinates.Z;++Z)
float CavernProbabilitySamples[BrickGridConstants::MaxBricksPerRegionAxis];
const uint32 NumCavernProbabilitySamples = FMath::Min(BrickGridConstants::MaxBricksPerRegionAxis >> 2,(BrickGroundHeight + 3) >> 2);
float PreviousCavernProbabilitySample = LocalCavernProbabilityFunction.Sample3D(BiasedSeed * 73 + X,Y,MinRegionBrickCoordinates.Z - 1);
for(uint32 CavernSampleIndex = 0;CavernSampleIndex < NumCavernProbabilitySamples;++CavernSampleIndex)
{
const uint32 LocalZ = CavernSampleIndex << 2;
const float NextCavernProbabilitySample = LocalCavernProbabilityFunction.Sample3D(BiasedSeed * 73 + X,Y,MinRegionBrickCoordinates.Z + LocalZ + 3);
CavernProbabilitySamples[LocalZ + 0] = FMath::Lerp(PreviousCavernProbabilitySample,NextCavernProbabilitySample,1.0f / 4.0f);
CavernProbabilitySamples[LocalZ + 1] = FMath::Lerp(PreviousCavernProbabilitySample,NextCavernProbabilitySample,2.0f / 4.0f);
CavernProbabilitySamples[LocalZ + 2] = FMath::Lerp(PreviousCavernProbabilitySample,NextCavernProbabilitySample,3.0f / 4.0f);
CavernProbabilitySamples[LocalZ + 3] = NextCavernProbabilitySample;
PreviousCavernProbabilitySample = NextCavernProbabilitySample;
}

for(int32 LocalZ = 0;LocalZ < BricksPerRegion.Z;++LocalZ)
{
const int32 Z = MinRegionBrickCoordinates.Z + LocalZ;
const FInt3 BrickCoordinates(X,Y,Z);
int32 MaterialIndex = 0;
if(Z == Grid->MinBrickCoordinates.Z)
Expand All @@ -108,34 +134,31 @@ void UBrickTerrainGenerationLibrary::InitRegion(const FBrickTerrainGenerationPar
}
else if(Z <= BrickGroundHeight)
{
const float DirtProbability = LocalDirtProbabilityFunction.Sample3D(BiasedSeed * 79 + X,Y,Z);
const float CavernProbability = LocalCavernProbabilityFunction.Sample3D(BiasedSeed * 73 + X,Y,Z);
const float CavernProbability = CavernProbabilitySamples[LocalZ];
const float RockCavernThreshold = Parameters.CavernThresholdByHeight->FloatCurve.Eval(Z * LocalToWorldScale);
const float DirtThreshold = Parameters.DirtThresholdByHeight->FloatCurve.Eval(Z * LocalToWorldScale);
if(DirtProbability < DirtThreshold)
if(Z <= BrickRockHeight)
{
if(CavernProbability > RockCavernThreshold + Parameters.DirtCavernThresholdBias)
if(CavernProbability > RockCavernThreshold)
{
MaterialIndex = Z == BrickGroundHeight && LocalMoistureFunction.Sample2D(BiasedSeed * 61 + X,Y) > Parameters.GrassMoistureThreshold
? Parameters.GrassMaterialIndex
: Parameters.DirtMaterialIndex;
MaterialIndex = Parameters.RockMaterialIndex;
}
}
else
{
if(CavernProbability > RockCavernThreshold)
if(CavernProbability > RockCavernThreshold + Parameters.DirtCavernThresholdBias)
{
MaterialIndex = Parameters.RockMaterialIndex;
MaterialIndex = Z == BrickGroundHeight && LocalMoistureFunction.Sample2D(BiasedSeed * 61 + X,Y) > Parameters.GrassMoistureThreshold
? Parameters.GrassMaterialIndex
: Parameters.DirtMaterialIndex;
}
}
}
Grid->SetBrickWithoutInvalidatingComponents(BrickCoordinates,MaterialIndex);
LocalBrickMaterials[((LocalY * BricksPerRegion.X) + LocalX) * BricksPerRegion.Z + LocalZ] = MaterialIndex;
}
}
}

// Invalidate the chunk components for this region.
Grid->InvalidateChunkComponents(MinRegionBrickCoordinates,MaxRegionBrickCoordinates);
Grid->SetBrickMaterialArray(MinRegionBrickCoordinates,MaxRegionBrickCoordinates,LocalBrickMaterials);

UE_LOG(LogStats,Log,TEXT("UBrickTerrainGenerationLibrary::InitRegion took %fms"),1000.0f * float(FPlatformTime::Seconds() - StartTime));
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ public:
float y2 = y0 - 1.0f + 2.0f * G2;

// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
int32 ii = i % 256;
int32 jj = j % 256;
int32 ii = i & 255;
int32 jj = j & 255;

// Calculate the contribution from the three corners
float t0 = 0.5f - x0*x0-y0*y0;
Expand Down Expand Up @@ -189,9 +189,9 @@ public:
float z3 = z0 - 1.0f + 3.0f*G3;

// Wrap the integer indices at 256, to avoid indexing perm[] out of bounds
int32 ii = Mod(i, 256);
int32 jj = Mod(j, 256);
int32 kk = Mod(k, 256);
int32 ii = i & 255;
int32 jj = j & 255;
int32 kk = k & 255;

// Calculate the contribution from the four corners
float t0 = 0.6f - x0*x0 - y0*y0 - z0*z0;
Expand Down Expand Up @@ -261,12 +261,6 @@ public:

private:

int32 Mod(int32 x, int32 m)
{
int32 a = x % m;
return a < 0 ? a + m : a;
}

float grad( int32 hash, float x )
{
int32 h = hash & 15;
Expand Down

0 comments on commit 4275a1f

Please sign in to comment.