From 1217c049c2149233a56b3250836962c4de5d0b22 Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Thu, 3 Aug 2017 22:16:03 -0700 Subject: [PATCH 1/6] Fixed heightmap generation. --- .../Classes/Maps/Heightmap/HeightmapPointTask.h | 3 +++ .../Private/IO/MapTextureRenderer.cpp | 2 +- .../Maps/Heightmap/HeightmapPointTask.cpp | 17 ++++++++++------- .../Maps/Heightmap/PolygonalMapHeightmap.cpp | 4 ++-- .../Private/Maps/IslandMapGenerator.cpp | 4 ++-- .../Private/Maps/MapDebugVisualizer.cpp | 6 ++++-- .../Private/Maps/Moisture/River.cpp | 4 ++-- .../Public/Maps/IslandMapGenerator.h | 2 +- .../Public/Maps/MapDebugVisualizer.h | 2 +- 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h index c69ec27..e22d246 100644 --- a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h +++ b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h @@ -80,6 +80,9 @@ public: static UPolygonalMapHeightmap* MapHeightmap; static UPolygonMap* MapGraph; static UBiomeManager* BiomeManager; + // The scale between heightmap units and graph units + // 1 heightmap unit is this many graph units + static float MapScale; // Results of the threads static TArray HeightmapData; diff --git a/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp b/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp index 968a624..2ee025d 100644 --- a/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp +++ b/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp @@ -16,7 +16,7 @@ bool UMapTextureRenderer::SaveTextureFromHeightmap(UPolygonalMapHeightmap* MapHe // Didn't generate heightmap yet return false; } - UE_LOG(LogTemp, Warning, TEXT("Attempting to generate map heightmap!")); + UE_LOG(LogWorldGen, Warning, TEXT("Attempting to generate map heightmap texture!")); uint32 dtWidth = MapHeightmap->HeightmapSize; uint32 dtHeight = MapHeightmap->HeightmapSize; diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp index 7c86381..9735bd0 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp @@ -9,6 +9,7 @@ UPolygonalMapHeightmap* FHeightmapPointGenerator::MapHeightmap = NULL; UPolygonMap* FHeightmapPointGenerator::MapGraph = NULL; UBiomeManager* FHeightmapPointGenerator::BiomeManager = NULL; +float FHeightmapPointGenerator::MapScale = 1.0f; // Results of the threads TArray FHeightmapPointGenerator::HeightmapData = TArray(); @@ -29,20 +30,22 @@ bool FHeightmapPointGenerator::TasksAreComplete() void FHeightmapPointGenerator::GenerateHeightmapPoints(const int32 HeightmapSize, int32 NumberOfPointsToAverage, UPolygonalMapHeightmap* HeightmapGenerator, UPolygonMap* Graph, UBiomeManager* BiomeMgr, const FIslandGeneratorDelegate OnComplete) { + check(HeightmapSize > 0); MapHeightmap = HeightmapGenerator; MapGraph = Graph; BiomeManager = BiomeMgr; OnAllPointsComplete = OnComplete; + MapScale = (float)MapGraph->GetGraphSize() / (float)HeightmapSize; TotalNumberOfThreads = 0; CompletedThreads = 0; HeightmapData.Empty(); - StartingMapDataArray = FHeightmapPointGenerator::MapGraph->GetAllMapData(); + //StartingMapDataArray = FHeightmapPointGenerator::MapGraph->GetAllMapData(); EPointSelectionMode pointSelectionMode = EPointSelectionMode::InterpolatedWithPolygonBiome; - if (pointSelectionMode == EPointSelectionMode::Interpolated || pointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) + /*if (pointSelectionMode == EPointSelectionMode::Interpolated || pointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) { int32 graphSize = FHeightmapPointGenerator::MapGraph->GetGraphSize(); // First, insert a border around the map @@ -70,9 +73,9 @@ void FHeightmapPointGenerator::GenerateHeightmapPoints(const int32 HeightmapSize borderPoint.Point = FVector2D(graphSize - 1, y); StartingMapDataArray.Add(borderPoint); } - } + }*/ - // Add a task for each prime number + // Add a task for each heightmap pixel for (int32 x = 0; x < HeightmapSize; x++) { for(int32 y = 0; y < HeightmapSize; y++) @@ -272,10 +275,10 @@ FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* }*/ FMapData pixelData = FMapData(); - pixelData.Point = PixelPosition; + pixelData.Point = PixelPosition * FHeightmapPointGenerator::MapScale; FMapCorner triangleCenter; - float pointZPostion = MapGraph->CalculateZPosition(PixelPosition, triangleCenter); + float pointZPostion = MapGraph->CalculateZPosition(pixelData.Point, triangleCenter); if (triangleCenter.Index >= 0) { // The point is valid, populate from the triangle @@ -324,7 +327,7 @@ void FHeightmapPointTask::DoTask(ENamedThreads::Type CurrentThread, const FGraph FHeightmapPointGenerator::CompletedThreads++; float percentComplete = (float)FHeightmapPointGenerator::CompletedThreads / (float)FHeightmapPointGenerator::TotalNumberOfThreads; - UE_LOG(LogWorldGen, Log, TEXT("Heightmap completion percent: %f percent."), percentComplete); + UE_LOG(LogWorldGen, Log, TEXT("Created pixel at (%d, %d), completing thread %d of %d. Heightmap completion percent: %f percent."), X, Y, FHeightmapPointGenerator::CompletedThreads, FHeightmapPointGenerator::TotalNumberOfThreads, percentComplete); if (FHeightmapPointGenerator::CompletedThreads == FHeightmapPointGenerator::TotalNumberOfThreads) { // If we're all done, check in with the on completion delegate diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp index 4f8e887..594c986 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp @@ -55,7 +55,7 @@ FMapData UPolygonalMapHeightmap::GetMapPoint(int32 x, int32 y) int32 index = x + (y * HeightmapSize); if (index < 0 || HeightmapData.Num() <= index) { - UE_LOG(LogWorldGen, Warning, TEXT("Tried to fetch a pixel at %d, %d, but no pixel was found."), x, y); + UE_LOG(LogWorldGen, Warning, TEXT("Tried to get a pixel at %d, %d, but no pixel was found."), x, y); return FMapData(); } else @@ -69,7 +69,7 @@ void UPolygonalMapHeightmap::SetMapPoint(int32 X, int32 Y, FMapData MapData) int32 index = X + (Y * HeightmapSize); if (index < 0 || HeightmapData.Num() <= index) { - UE_LOG(LogWorldGen, Warning, TEXT("Tried to fetch a pixel at %d, %d, but no pixel was found."), X, Y); + UE_LOG(LogWorldGen, Warning, TEXT("Tried to set a pixel at %d, %d, but no pixel was found."), X, Y); return; } else diff --git a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp index 3b5082c..ddd12ae 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp @@ -468,11 +468,11 @@ void AIslandMapGenerator::DrawDelaunayGraph() UMapDebugVisualizer::DrawDebugDelaunayGrid(this, IslandData.PolygonMapSettings, MapGraph); } -void AIslandMapGenerator::DrawHeightmap(float PixelSize) +void AIslandMapGenerator::DrawHeightmap(float PixelSize, float PixelHeightMultiplier) { if (MapHeightmap == NULL || !bHasGeneratedHeightmap) { return; } - UMapDebugVisualizer::DrawDebugPixelGrid(this, IslandData.PolygonMapSettings, MapHeightmap->GetMapData(), IslandData.Size, PixelSize); + UMapDebugVisualizer::DrawDebugPixelGrid(this, IslandData.PolygonMapSettings, MapHeightmap->GetMapData(), MapHeightmap->HeightmapSize, PixelSize, PixelHeightMultiplier); } diff --git a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp index e50c85e..6b89917 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp @@ -6,7 +6,7 @@ #include "PolygonMap.h" #include "MapDebugVisualizer.h" -void UMapDebugVisualizer::DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMapData& MapData, const TArray& HeightmapData, int32 HeightmapSize, float PixelSize) +void UMapDebugVisualizer::DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMapData& MapData, const TArray& HeightmapData, int32 HeightmapSize, float PixelSize, float PixelHeightMultiplier) { UWorld* world = Actor->GetWorld(); if (world == NULL) @@ -40,7 +40,9 @@ void UMapDebugVisualizer::DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMap color = FColor(147, 198, 255); } - FVector v0 = offset + FVector(mapData.Point.X * PixelSize, mapData.Point.Y * PixelSize, FMath::FloorToInt(mapData.Elevation * (MapData.ElevationScale / 100.0f)) * PixelSize + 1.0f); + float pixelHeight = FMath::FloorToInt(mapData.Elevation * ((MapData.ElevationScale / 100.0f) * PixelHeightMultiplier)); + + FVector v0 = offset + FVector(x * PixelSize, y * PixelSize, pixelHeight * PixelSize); FVector v1 = FVector(v0.X, v0.Y + PixelSize, v0.Z); FVector v2 = FVector(v0.X + PixelSize, v0.Y, v0.Z); FVector v3 = FVector(v2.X, v1.Y, v0.Z); diff --git a/Source/PolygonalMapGenerator/Private/Maps/Moisture/River.cpp b/Source/PolygonalMapGenerator/Private/Maps/Moisture/River.cpp index 028486d..44584fd 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Moisture/River.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Moisture/River.cpp @@ -267,7 +267,7 @@ FVector2D URiver::CalculateBezierPoint(float t, FVector2D p0, FVector2D p1, FVec void URiver::DrawLineOnHeightmap(UPolygonalMapHeightmap* MapHeightmap, const FVector2D& point1, const FVector2D& point2) { - float deltaX = point2.X - point1.X; + /*float deltaX = point2.X - point1.X; if (deltaX == 0) { // Line is vertical @@ -318,5 +318,5 @@ void URiver::DrawLineOnHeightmap(UPolygonalMapHeightmap* MapHeightmap, const FVe y++; error -= 1.0f; } - } + }*/ } \ No newline at end of file diff --git a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h index 1787a18..8507acd 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h +++ b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h @@ -213,7 +213,7 @@ public: // Be sure to call CreateHeightmap() and wait for the delegate to complete before calling // this function, otherwise there may not be any data in the heightmap array. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Debug") - void DrawHeightmap(float PixelSize = 100.0f); + void DrawHeightmap(float PixelSize = 100.0f, float PixelHeightMultiplier = 0.01f); // Different settings that make up our island. // Changing these settings will produce different-looking islands. diff --git a/Source/PolygonalMapGenerator/Public/Maps/MapDebugVisualizer.h b/Source/PolygonalMapGenerator/Public/Maps/MapDebugVisualizer.h index 2cf9554..6e51d5a 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/MapDebugVisualizer.h +++ b/Source/PolygonalMapGenerator/Public/Maps/MapDebugVisualizer.h @@ -20,7 +20,7 @@ public: // Keep in mind that larger values of HeightmapSize will have a SEVERE performance impact. // Try to only use this with a limited number of pixels in the heightmap. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Debug") - static void DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMapData& MapData, const TArray& HeightmapData, int32 HeightmapSize, float PixelSize); + static void DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMapData& MapData, const TArray& HeightmapData, int32 HeightmapSize, float PixelSize, float PixelHeightMultiplier); // Same as DrawDebugPixelGrid(), but only draws the parts of the debug grid which contain a river. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Debug") -- 2.51.2 From 2cdc409329a4a39079e89bf743c4736eddbe4c92 Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Fri, 4 Aug 2017 01:41:18 -0700 Subject: [PATCH 2/6] Made small optimizations to the heightmap code. Overall, there's less code duplication, although my benchmarks have it performing slightly slower than it was before. The bottleneck has been found and (somewhat) optimized, although further optimizations can always be made. Additionally, one can now toggle between single-threaded and multithreaded mode. Single-threaded is usually about twice as fast, but it locks up the game thread entirely. Multithreaded mode happens in the background and doesn't lock up the game thread, but it's a bit slower. --- .../Maps/Heightmap/HeightmapPointTask.h | 2 +- .../Maps/Heightmap/HeightmapPointTask.cpp | 194 ++--------------- .../Maps/Heightmap/PolygonalMapHeightmap.cpp | 29 ++- .../Private/Maps/IslandMapGenerator.cpp | 4 +- .../Private/Maps/MapDebugVisualizer.cpp | 2 +- .../Private/Maps/PolygonMap.cpp | 195 ++++++++---------- .../Maps/Elevations/PolygonalMapHeightmap.h | 4 +- .../Public/Maps/IslandMapGenerator.h | 2 +- .../Public/Maps/PolygonMap.h | 48 ++++- 9 files changed, 173 insertions(+), 307 deletions(-) diff --git a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h index e22d246..36b97ec 100644 --- a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h +++ b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h @@ -67,7 +67,7 @@ public: } // Creates a map point at the given pixel position and adds it to the end of the HeightmapData array. - FMapData MakeMapPoint(FVector2D PixelPosition, UPolygonMap* MapGraph, UBiomeManager* BiomeManager); + static FMapData MakeMapPoint(FVector2D PixelPosition, UPolygonMap* MapGraph, UBiomeManager* BiomeManager, EPointSelectionMode SelectionMode); // Do the task void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent); diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp index 9735bd0..73966b1 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp @@ -103,201 +103,31 @@ void FHeightmapPointGenerator::CheckComplete() } } -FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* MapGraph, UBiomeManager* BiomeManager) +FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* MapGraph, UBiomeManager* BiomeManager, EPointSelectionMode PointSelectionMode) { - /*if (PointSelectionMode == Interpolated || PointSelectionMode == InterpolatedWithPolygonBiome) - { - TArray closestPoints; - // Iterate over the entire mapData array to find how many points we need to average - for (int i = 0; i < MapData.Num(); i++) - { - if (closestPoints.Num() == 0) - { - closestPoints.Add(MapData[i]); - continue; - } - float distance = FVector2D::DistSquared(PixelPosition, MapData[i].Point); - if (distance <= 0.001f) - { - // Close enough - pixelData = MapData[i]; - return pixelData; - } - - // This will hold the index of first point we find that's further away than our point - int addPointIndex = -1; - for (int j = 0; j < closestPoints.Num(); j++) - { - // Get the distance of this point - float pointDist = FVector2D::DistSquared(PixelPosition, closestPoints[j].Point); - if (distance < pointDist) - { - addPointIndex = j; - break; - } - } - - // If we found a point that's further away than our point, place it in the array and move everything else down - if (addPointIndex >= 0) - { - FMapData last = MapData[i]; - for (int j = addPointIndex; j < closestPoints.Num(); j++) - { - FMapData temp = closestPoints[j]; - closestPoints[j] = last; - last = temp; - } - // If we are below the number of points we need to add, then add the furthest point to the end - if (closestPoints.Num() < NumberOfPointsToAverage) - { - closestPoints.Add(last); - } - } - else if (closestPoints.Num() < NumberOfPointsToAverage) - { - closestPoints.Add(MapData[i]); - } - } - - // Cache the distances - TArray closestPointDistances; - float totalDistance = 0.0f; - for (int i = 0; i < closestPoints.Num(); i++) - { - float distance = FVector2D::DistSquared(PixelPosition, closestPoints[i].Point); - totalDistance += distance; - closestPointDistances.Add(distance); - } - - float inversePercentageTotal = 0.0f; - - for (int i = 0; i < closestPoints.Num(); i++) - { - // Get the total percentage that this point contributed to the overall distance - float percentageOfDistance = closestPointDistances[i] / totalDistance; - - // Take the inverse of the distance percentage -- points which are closer get a larger weight - float inversePercentage = 1.0f - percentageOfDistance; - - // We re-add the inverse percentage to the array so we can make sure it all totals up to 1 - closestPointDistances[i] = inversePercentage; - inversePercentageTotal += inversePercentage; - } - - // Now gather the weighted distance for each point - TArray> pointWeights; - for (int i = 0; i < closestPoints.Num(); i++) - { - TPair weight; - weight.Key = closestPoints[i]; - weight.Value = closestPointDistances[i] / inversePercentageTotal; - pointWeights.Add(weight); - } - - float elevation = 0.0f; - float moisture = 0.0f; - - TMap tagWeights; - - for (int i = 0; i < pointWeights.Num(); i++) - { - FMapData curPoint = pointWeights[i].Key; - float weight = pointWeights[i].Value; - - elevation += (curPoint.Elevation * weight); - moisture += (curPoint.Moisture * weight); - - if(PointSelectionMode == EPointSelectionMode::Interpolated) - { - for (int j = 0; j < curPoint.Tags.Num(); j++) - { - FGameplayTag tag = curPoint.Tags.GetByIndex(i); - if (tag.MatchesTag(FGameplayTag::RequestGameplayTag(TEXT("MapData.MetaData.Water.River")))) - { - // Rivers are handled later - continue; - } - float currentTagWeight = tagWeights.FindOrAdd(tag); - currentTagWeight += weight; - tagWeights[tag] = currentTagWeight; - } - } - } - pixelData.Elevation = elevation; - pixelData.Moisture = moisture; - pixelData.Point = PixelPosition; - if (PointSelectionMode == EPointSelectionMode::Interpolated) - { - pixelData.Tags.Reset(); - for (auto& elem : tagWeights) - { - if (elem.Value >= 0.5f) - { - pixelData.Tags.AddTagFast(elem.Key); - } - } - // Right now, this sometimes causes a crash - // TODO: Find out why it crashes (maybe due to multithreading?) - // In the meantime, use EPointSelectionMode::InterpolatedWithPolygonBiome instead - pixelData.Biome = BiomeManager->DetermineBiome(pixelData); - } - else if(PointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) - { - FMapCenter center = FHeightmapPointGenerator::MapGraph->FindMapCenterForCoordinate(PixelPosition); - if (center.Index < 0) - { - //UE_LOG(LogWorldGen, Warning, TEXT("Could not find polygon! Returning what we have.")); - return pixelData; - } - pixelData.Tags = center.CenterData.Tags; - pixelData.Biome = center.CenterData.Biome; - } - return pixelData; - } - else if(PointSelectionMode == EPointSelectionMode::UsePolygon) - { - FMapCenter center = FHeightmapPointGenerator::MapGraph->FindMapCenterForCoordinate(PixelPosition); - if (center.Index < 0) - { - //UE_LOG(LogWorldGen, Warning, TEXT("Could not find polygon! Returning blank FMapData!")); - return FMapData(); - } - - pixelData = center.CenterData; - pixelData.Point = PixelPosition; - return pixelData; - } - else - { - // Should never get to this point - unimplemented(); - return FMapData(); - }*/ - FMapData pixelData = FMapData(); pixelData.Point = PixelPosition * FHeightmapPointGenerator::MapScale; - FMapCorner triangleCenter; - float pointZPostion = MapGraph->CalculateZPosition(pixelData.Point, triangleCenter); - if (triangleCenter.Index >= 0) + FPointInterpolationData pointData = MapGraph->FindInterpolatedDataForPoint(pixelData.Point); + if (pointData.bTriangleIsValid) { // The point is valid, populate from the triangle - pixelData.Elevation = pointZPostion; + pixelData.Elevation = pointData.InterpolatedElevation; if (PointSelectionMode == EPointSelectionMode::UsePolygon) { - pixelData.Moisture = triangleCenter.CornerData.Moisture; - pixelData.Tags = triangleCenter.CornerData.Tags; - pixelData.Biome = triangleCenter.CornerData.Biome; + pixelData.Moisture = pointData.SourceTriangle.CornerData.Moisture; + pixelData.Tags = pointData.SourceTriangle.CornerData.Tags; + pixelData.Biome = pointData.SourceTriangle.CornerData.Biome; } else { - pixelData.Moisture = MapGraph->InterpolateMapDataMoisture(MapGraph->GetCenter(triangleCenter.Touches[0]).CenterData, MapGraph->GetCenter(triangleCenter.Touches[1]).CenterData, MapGraph->GetCenter(triangleCenter.Touches[2]).CenterData, PixelPosition); + pixelData.Moisture = pointData.InterpolatedMoisture; // TODO: Interpolate tags - pixelData.Tags = triangleCenter.CornerData.Tags; + pixelData.Tags = pointData.SourceTriangle.CornerData.Tags; if (PointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) { // Grab the biome directly from the CornerData - pixelData.Biome = triangleCenter.CornerData.Biome; + pixelData.Biome = pointData.SourceTriangle.CornerData.Biome; } else { @@ -322,12 +152,12 @@ void FHeightmapPointTask::DoTask(ENamedThreads::Type CurrentThread, const FGraph FVector2D point = FVector2D(X, Y); // Now make the actual map point - FMapData mapData = MakeMapPoint(point, FHeightmapPointGenerator::MapGraph, FHeightmapPointGenerator::BiomeManager); + FMapData mapData = MakeMapPoint(point, FHeightmapPointGenerator::MapGraph, FHeightmapPointGenerator::BiomeManager, PointSelectionMode); FHeightmapPointGenerator::HeightmapData.Add(mapData); FHeightmapPointGenerator::CompletedThreads++; float percentComplete = (float)FHeightmapPointGenerator::CompletedThreads / (float)FHeightmapPointGenerator::TotalNumberOfThreads; - UE_LOG(LogWorldGen, Log, TEXT("Created pixel at (%d, %d), completing thread %d of %d. Heightmap completion percent: %f percent."), X, Y, FHeightmapPointGenerator::CompletedThreads, FHeightmapPointGenerator::TotalNumberOfThreads, percentComplete); + //UE_LOG(LogWorldGen, Log, TEXT("Created pixel at (%d, %d), completing thread %d of %d. Heightmap completion percent: %f percent."), X, Y, FHeightmapPointGenerator::CompletedThreads, FHeightmapPointGenerator::TotalNumberOfThreads, percentComplete); if (FHeightmapPointGenerator::CompletedThreads == FHeightmapPointGenerator::TotalNumberOfThreads) { // If we're all done, check in with the on completion delegate diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp index 594c986..bbe9b57 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp @@ -7,7 +7,7 @@ #include "Maps/Heightmap/HeightmapPointTask.h" #include "PolygonalMapHeightmap.h" -void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const FIslandGeneratorDelegate OnComplete) +void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete) { if (PolygonMap == NULL) { @@ -15,19 +15,40 @@ void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeMana } MoistureDistributor = MoistureDist; HeightmapSize = Size; + HeightmapData.Empty(); OnGenerationComplete = OnComplete; // Interpolate between the actual points CreateHeightmapTimer = FPlatformTime::Seconds(); - FIslandGeneratorDelegate generatePoints; - generatePoints.BindDynamic(this, &UPolygonalMapHeightmap::CheckMapPointsDone); - FHeightmapPointGenerator::GenerateHeightmapPoints(HeightmapSize, NumberOfPointsToAverage, this, PolygonMap, BiomeManager, generatePoints); + if (HeightmapGenerationOptions == EHeightmapGenerationType::ForceMultithreaded) + { + FIslandGeneratorDelegate generatePoints; + generatePoints.BindDynamic(this, &UPolygonalMapHeightmap::CheckMapPointsDone); + FHeightmapPointGenerator::GenerateHeightmapPoints(HeightmapSize, NumberOfPointsToAverage, this, PolygonMap, BiomeManager, generatePoints); + } + else + { + FHeightmapPointGenerator::MapScale = (float)PolygonMap->GetGraphSize() / (float)HeightmapSize; + for (int32 x = 0; x < HeightmapSize; x++) + { + for (int32 y = 0; y < HeightmapSize; y++) + { + HeightmapData.Add(FHeightmapPointTask::MakeMapPoint(FVector2D(x, y), PolygonMap, BiomeManager, EPointSelectionMode::InterpolatedWithPolygonBiome)); + } + } + DoHeightmapPostProcess(); + } } void UPolygonalMapHeightmap::CheckMapPointsDone() { HeightmapData = FHeightmapPointGenerator::HeightmapData; + DoHeightmapPostProcess(); +} + +void UPolygonalMapHeightmap::DoHeightmapPostProcess() +{ UE_LOG(LogWorldGen, Log, TEXT("%d map points created in %f seconds."), HeightmapSize * HeightmapSize, FPlatformTime::Seconds() - CreateHeightmapTimer); // Add the rivers diff --git a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp index ddd12ae..058dbf5 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp @@ -420,7 +420,7 @@ void AIslandMapGenerator::DetermineBiomes() UE_LOG(LogWorldGen, Log, TEXT("Biomes determined in %f seconds."), FPlatformTime::Seconds() - CurrentGenerationTime); } -void AIslandMapGenerator::CreateHeightmap(const int32 HeightmapSize, const FIslandGeneratorDelegate OnHeightmapGenerationFinished) +void AIslandMapGenerator::CreateHeightmap(const int32 HeightmapSize, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnHeightmapGenerationFinished) { if (MapGraph == NULL) { @@ -436,7 +436,7 @@ void AIslandMapGenerator::CreateHeightmap(const int32 HeightmapSize, const FIsla FIslandGeneratorDelegate finalizationFinished; finalizationFinished.BindDynamic(this, &AIslandMapGenerator::OnHeightmapFinished); - MapHeightmap->CreateHeightmap(MapGraph, BiomeManager, MoistureDistributor, HeightmapSize, finalizationFinished); + MapHeightmap->CreateHeightmap(MapGraph, BiomeManager, MoistureDistributor, HeightmapSize, HeightmapGenerationOptions, finalizationFinished); } void AIslandMapGenerator::OnHeightmapFinished() diff --git a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp index 6b89917..51a74b4 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp @@ -42,7 +42,7 @@ void UMapDebugVisualizer::DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMap float pixelHeight = FMath::FloorToInt(mapData.Elevation * ((MapData.ElevationScale / 100.0f) * PixelHeightMultiplier)); - FVector v0 = offset + FVector(x * PixelSize, y * PixelSize, pixelHeight * PixelSize); + FVector v0 = offset + FVector(x * PixelSize, y * PixelSize, pixelHeight); FVector v1 = FVector(v0.X, v0.Y + PixelSize, v0.Z); FVector v2 = FVector(v0.X + PixelSize, v0.Y, v0.Z); FVector v3 = FVector(v2.X, v1.Y, v0.Z); diff --git a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp index b576442..67d9dd6 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp @@ -472,7 +472,7 @@ FMapCorner UPolygonMap::FindMapCornerForCoordinate(const FVector2D& Point) FMapCorner corner = FMapCorner(); for (int i = 0; i < Corners.Num(); i++) { - if (CornerContainsPoint(Point, Corners[i])) + if (CornerContainsPoint(Point, Corners[i]).bTriangleIsValid) { corner = Corners[i]; if (corner.Touches.Num() == 0) @@ -543,144 +543,123 @@ bool UPolygonMap::CenterContainsPoint(const FVector2D& Point, const FMapCenter& return (intersections & 1) == 1; // True if point is odd (inside of polygon) } -bool UPolygonMap::CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const +FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const { + FPointInterpolationData output = FPointInterpolationData(); if (Corner.Touches.Num() != 3) { - return false; + return output; } - FVector2D p1 = GetCenter(Corner.Touches[0]).CenterData.Point; - FVector2D p2 = GetCenter(Corner.Touches[1]).CenterData.Point; - FVector2D p3 = GetCenter(Corner.Touches[2]).CenterData.Point; - - float y1 = p1.Y; - float y2 = p2.Y; - float y3 = p3.Y; - float x1 = p1.X; - float x2 = p2.X; - float x3 = p3.X; + FMapData center1 = GetCenter(Corner.Touches[0]).CenterData; + FMapData center2 = GetCenter(Corner.Touches[1]).CenterData; + FMapData center3 = GetCenter(Corner.Touches[2]).CenterData; + FVector2D p1 = center1.Point; + FVector2D p2 = center2.Point; + FVector2D p3 = center3.Point; // Calculate determinant - float det = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); + float det = (p2.Y - p3.Y) * (p1.X - p3.X) + (p3.X - p2.X) * (p1.Y - p3.Y); if (det == 0.0f) { // Shouldn't happen, but okay - return false; + return output; } - float x = Point.X; - float y = Point.Y; - // https://stackoverflow.com/questions/36090269/finding-height-of-point-on-height-map-triangles - float a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det; - float b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det; - float c = 1 - a - b; - // p lies in T if and only if 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1 - return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1; -} - -float UPolygonMap::CalculateZPosition(FVector2D MapLocation, FMapCorner& OutMapCorner) -{ - OutMapCorner = FindMapCornerForCoordinate(MapLocation); - if (OutMapCorner.Index < 0) + float lambda1 = ((p2.Y - p3.Y) * (Point.X - p3.X) + (p3.X - p2.X) * (Point.Y - p3.Y)) / det; + if (0 > lambda1 || lambda1 > 1) { - // Not a valid corner - return 0.0f; + return output; } - - return CalculateZPositionBetweenCenters(GetCenter(OutMapCorner.Touches[0]), GetCenter(OutMapCorner.Touches[1]), GetCenter(OutMapCorner.Touches[2]), MapLocation); -} - - -float UPolygonMap::CalculateZPositionBetweenCenters(FMapCenter CenterA, FMapCenter CenterB, FMapCenter CenterC, FVector2D MapLocation) const -{ - FVector p1 = FVector(CenterA.CenterData.Point.X, CenterA.CenterData.Point.Y, CenterA.CenterData.Elevation); - FVector p2 = FVector(CenterB.CenterData.Point.X, CenterB.CenterData.Point.Y, CenterB.CenterData.Elevation); - FVector p3 = FVector(CenterC.CenterData.Point.X, CenterC.CenterData.Point.Y, CenterC.CenterData.Elevation); - - float y1 = p1.Y; - float y2 = p2.Y; - float y3 = p3.Y; - - float x1 = p1.X; - float x2 = p2.X; - float x3 = p3.X; - - // Calculate determinant - float det = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); - if (det == 0.0f) + float lambda2 = ((p3.Y - p1.Y) * (Point.X - p3.X) + (p1.X - p3.X) * (Point.Y - p3.Y)) / det; + if (0 > lambda2 || lambda2 > 1) { - // Shouldn't happen, but okay - return 0.0f; + return output; } - - float x = MapLocation.X; - float y = MapLocation.Y; - - // https://stackoverflow.com/questions/36090269/finding-height-of-point-on-height-map-triangles - float lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det; - float lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det; float lambda3 = 1 - lambda1 - lambda2; - - float z1 = p1.Z; - float z2 = p2.Z; - float z3 = p3.Z; - - // Calculate Z coordinate - return ((lambda1 * z1 + lambda2 * z2 + lambda3 * z3) * WorldData.ElevationScale) + WorldData.ElevationOffset; -} - -float UPolygonMap::CalculateMoistureAtPoint(FVector2D MapLocation, FMapCorner& OutMapCorner) -{ - OutMapCorner = FindMapCornerForCoordinate(MapLocation); - if (OutMapCorner.Index < 0) + if (0 > lambda3 || lambda3 > 1) { - // Not a valid corner - return 0.0f; + return output; } - return InterpolateMapDataMoisture(GetCenter(OutMapCorner.Touches[0]).CenterData, GetCenter(OutMapCorner.Touches[1]).CenterData, GetCenter(OutMapCorner.Touches[2]).CenterData, MapLocation); + output.bTriangleIsValid = true; + output.SourceTriangle = Corner; + output.InterpolatedElevation = ((lambda1 * center1.Elevation + lambda2 * center2.Elevation + lambda3 * center3.Elevation) * WorldData.ElevationScale) + WorldData.ElevationOffset; + output.InterpolatedMoisture = lambda1 * center1.Moisture + lambda2 * center2.Moisture + lambda3 * center3.Moisture; + + return output; } - -float UPolygonMap::InterpolateMapDataMoisture(FMapData PointA, FMapData PointB, FMapData PointC, FVector2D MapLocation) const +FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2D& Point) { - FVector p1 = FVector(PointA.Point.X, PointA.Point.Y, PointA.Moisture); - FVector p2 = FVector(PointB.Point.X, PointB.Point.Y, PointB.Moisture); - FVector p3 = FVector(PointC.Point.X, PointC.Point.Y, PointC.Moisture); - - float y1 = p1.Y; - float y2 = p2.Y; - float y3 = p3.Y; - - float x1 = p1.X; - float x2 = p2.X; - float x3 = p3.X; - - // Calculate determinant - float det = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3); - if (det == 0.0f) + if (Point.X > MaxPointLocation || Point.Y > MaxPointLocation || Point.X < MinPointLocation || Point.Y < MinPointLocation) { - // Shouldn't happen, but okay - return 0.0f; + // Point out of bounds; don't even bother + return FPointInterpolationData(); } - float x = MapLocation.X; - float y = MapLocation.Y; + FVector2D intMapCoordinates = Point; + intMapCoordinates.X = FMath::RoundToInt(Point.X); + intMapCoordinates.Y = FMath::RoundToInt(Point.Y); + if (CornerLookup.Contains(intMapCoordinates)) + { + UE_LOG(LogWorldGen, Log, TEXT("Cache hit! (%f, %f)"), Point.X, Point.Y); + return CornerContainsPoint(Point, GetCorner(CornerLookup[intMapCoordinates])); + } - // https://stackoverflow.com/questions/36090269/finding-height-of-point-on-height-map-triangles - float lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det; - float lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det; - float lambda3 = 1 - lambda1 - lambda2; + FPointInterpolationData data = FPointInterpolationData(); + if (LastFoundCorner.Index >= 0) + { + // Optimization: Check to see if we share a triangle with the last point we found. + // If we're running on a single thread, this is very helpful. + // It doesn't work so well if we're multithreaded. + data = CornerContainsPoint(Point, LastFoundCorner); + if (data.bTriangleIsValid) + { + CornerLookup.Add(intMapCoordinates, LastFoundCorner.Index); + } + else + { + // Check the neighboring triangles + for (int i = 0; i < LastFoundCorner.Adjacent.Num(); i++) + { + FMapCorner adjacent = GetCorner(LastFoundCorner.Adjacent[i]); + data = CornerContainsPoint(Point, adjacent); + if (data.bTriangleIsValid) + { + LastFoundCorner = adjacent; + CornerLookup.Add(intMapCoordinates, LastFoundCorner.Index); + break; + } + } + } + } - float z1 = p1.Z; - float z2 = p2.Z; - float z3 = p3.Z; + if (!data.bTriangleIsValid) + { + // Wasn't in the previous triangle we tried; search the whole array + // This is VERY SLOW. It's the biggest bottleneck in the whole process. + // Try to avoid getting here as much as physically possible. + for (int i = 0; i < Corners.Num(); i++) + { + data = CornerContainsPoint(Point, Corners[i]); + if (data.bTriangleIsValid) + { + LastFoundCorner = Corners[i]; + CornerLookup.Add(intMapCoordinates, i); + break; + } + } + } - // Calculate moisture - return lambda1 * z1 + lambda2 * z2 + lambda3 * z3; + if (!data.bTriangleIsValid) + { + // Couldn't find this corner + CornerLookup.Add(intMapCoordinates, -1); + } + return data; } // The main function that returns true if line segment 'p1q1' diff --git a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h index 5b53553..698528e 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h @@ -37,6 +37,8 @@ private: UFUNCTION() void CheckMapPointsDone(); + UFUNCTION() + void DoHeightmapPostProcess(); public: // The number of "pixels" in the heightmap. // Larger values create a higher-resolution heightmap, but also mean increased processing time. @@ -55,7 +57,7 @@ public: // The number of nearest points to consider is governed by the value of the NumberOfPointsToAverage in the UPolygonalMapHeightmap class. // Each "pixel" in the heightmap will be supplied with its own biome, which is determined by the UBiomeManager passed to this function. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Heightmap") - void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const FIslandGeneratorDelegate onComplete); + void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate onComplete); // Returns a COPY of this object's raw heightmap. // This can be iterated over easily, but keep in mind that any changes you make won't be made to the actual heightmap object (i.e., this). diff --git a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h index 8507acd..3b90c01 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h +++ b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h @@ -196,7 +196,7 @@ public: // When the function is done, it will call the OnComplete delegate. // The heightmap can be accessed through the GetHeightmap() function. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") - void CreateHeightmap(const int32 HeightmapSize, const FIslandGeneratorDelegate OnComplete); + void CreateHeightmap(const int32 HeightmapSize, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete); // This draws a debug voronoi representation of the island, using the values specified in // the IslandData's PolygonMapSettings struct. diff --git a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h index e041689..8c47d7d 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h @@ -246,6 +246,34 @@ struct POLYGONALMAPGENERATOR_API FWorldSpaceMapData } }; +USTRUCT(BlueprintType) +struct POLYGONALMAPGENERATOR_API FPointInterpolationData +{ + GENERATED_BODY() + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + bool bTriangleIsValid; + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + FMapCorner SourceTriangle; + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + float InterpolatedElevation; + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + float InterpolatedMoisture; + + FPointInterpolationData() + { + bTriangleIsValid = false; + InterpolatedElevation = 0.0f; + InterpolatedMoisture = 0.0f; + } +}; + +UENUM(BlueprintType) +enum class EHeightmapGenerationType : uint8 +{ + ForceMultithreaded, + ForceSingleThread +}; + /** * The PolygonMap is a class which uses a Voronoi diagram to collect data about a graph of * points on the XY plane. @@ -441,23 +469,24 @@ public: // If it does, this function returns true. Otherwise, it returns false. UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") bool CenterContainsPoint(const FVector2D& Point, const FMapCenter& Center) const; + // Returns the interpolated data from a 2D map coordinate. + // The MapCorner reference will be populated with data from the triangle that the point is in. + // If the triangle is invalid, bTriangleIsValid will be set to false. + // If the triangle is valid, the moisture and elevation will be interpolated. UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") - bool CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const; + FPointInterpolationData CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const; - // Returns the Z position of a 2D map coordinate. - // The MapCorner reference will be populated with data from the triangle that the point is in. - // If the MapCorner's index is below 0, the point lies outside of the generated map. UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") - float CalculateZPosition(FVector2D MapLocation, FMapCorner& OutMapCorner); + FPointInterpolationData FindInterpolatedDataForPoint(const FVector2D& Point); // Calculates the interpolated Z position of a 2D point between 3 MapCenters. - UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") + /*UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") float CalculateZPositionBetweenCenters(FMapCenter CenterA, FMapCenter CenterB, FMapCenter CenterC, FVector2D MapLocation) const; UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") float CalculateMoistureAtPoint(FVector2D MapLocation, FMapCorner& OutMapCorner); UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") - float InterpolateMapDataMoisture(FMapData PointA, FMapData PointB, FMapData PointC, FVector2D MapLocation) const; + float InterpolateMapDataMoisture(FMapData PointA, FMapData PointB, FMapData PointC, FVector2D MapLocation) const;*/ private: /// Graph Data // The points in our graph @@ -484,6 +513,11 @@ private: UPROPERTY() int32 MapSize; + // The last corner we found + // Used to optimize searching for which triangle a point belongs in + UPROPERTY() + FMapCorner LastFoundCorner; + // ALL MapData from both MapCenters and MapCorners. // Must be compiled first. UPROPERTY() -- 2.51.2 From 05f1ff523411e7c336fa33320cfae20e2da6cc5c Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Sat, 5 Aug 2017 14:04:21 -0700 Subject: [PATCH 3/6] Made further optimizations to heightmap generation. --- .../Maps/Heightmap/HeightmapPointTask.h | 2 + .../Maps/Heightmap/HeightmapPointTask.cpp | 46 +++++----------- .../Maps/Heightmap/PolygonalMapHeightmap.cpp | 18 ++++++- .../Private/Maps/PolygonMap.cpp | 52 +++++++++++++++++-- .../Maps/Elevations/PolygonalMapHeightmap.h | 5 ++ .../Public/Maps/PolygonMap.h | 4 +- 6 files changed, 86 insertions(+), 41 deletions(-) diff --git a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h index 36b97ec..3aff880 100644 --- a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h +++ b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h @@ -90,6 +90,8 @@ public: // This is the array of thread completions, used to determine if all threads are done static FGraphEventArray CompletionEvents; + static bool bShouldLogOnCompletion; + static float CompletionPercent; // How many threads have completed so far. static int32 CompletedThreads; diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp index 73966b1..84a48a6 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp @@ -20,6 +20,9 @@ FGraphEventArray FHeightmapPointGenerator::CompletionEvents = FGraphEventArray() int32 FHeightmapPointGenerator::CompletedThreads = 0; int32 FHeightmapPointGenerator::TotalNumberOfThreads = 0; +bool FHeightmapPointGenerator::bShouldLogOnCompletion = true; +float FHeightmapPointGenerator::CompletionPercent = 0.0f; + FIslandGeneratorDelegate FHeightmapPointGenerator::OnAllPointsComplete; bool FHeightmapPointGenerator::TasksAreComplete() @@ -43,37 +46,13 @@ void FHeightmapPointGenerator::GenerateHeightmapPoints(const int32 HeightmapSize //StartingMapDataArray = FHeightmapPointGenerator::MapGraph->GetAllMapData(); - EPointSelectionMode pointSelectionMode = EPointSelectionMode::InterpolatedWithPolygonBiome; - - /*if (pointSelectionMode == EPointSelectionMode::Interpolated || pointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) + bShouldLogOnCompletion = HeightmapSize <= 150; + if (!bShouldLogOnCompletion) { - int32 graphSize = FHeightmapPointGenerator::MapGraph->GetGraphSize(); - // First, insert a border around the map - for (int x = 0; x < graphSize; x++) - { - FMapData borderPoint = FMapData(); - borderPoint.Elevation = 0.0f; - borderPoint.Moisture = 0.0f; - borderPoint = UMapDataHelper::SetOcean(borderPoint); - borderPoint = UMapDataHelper::SetBorder(borderPoint); - borderPoint.Point = FVector2D(x, 0); - StartingMapDataArray.Add(borderPoint); - borderPoint.Point = FVector2D(x, graphSize - 1); - StartingMapDataArray.Add(borderPoint); - } - for (int y = 0; y < graphSize; y++) - { - FMapData borderPoint = FMapData(); - borderPoint.Elevation = 0.0f; - borderPoint.Moisture = 0.0f; - borderPoint = UMapDataHelper::SetOcean(borderPoint); - borderPoint = UMapDataHelper::SetBorder(borderPoint); - borderPoint.Point = FVector2D(0, y); - StartingMapDataArray.Add(borderPoint); - borderPoint.Point = FVector2D(graphSize - 1, y); - StartingMapDataArray.Add(borderPoint); - } - }*/ + UE_LOG(LogWorldGen, Warning, TEXT("You have a large heightmap size (%d)! Your heightmap may take a while (> 15 seconds) to complete. Logging individual completion events will be disabled. You can check the current completion percentage in FHeightmapPointGenerator::CompletionPercent or by calling GetCompletionPercent() on your PolygonalMapHeightmap object (accessible by calling GetHeightmap() on the IslandMapGenerator)"), HeightmapSize); + } + + EPointSelectionMode pointSelectionMode = EPointSelectionMode::InterpolatedWithPolygonBiome; // Add a task for each heightmap pixel for (int32 x = 0; x < HeightmapSize; x++) @@ -156,8 +135,11 @@ void FHeightmapPointTask::DoTask(ENamedThreads::Type CurrentThread, const FGraph FHeightmapPointGenerator::HeightmapData.Add(mapData); FHeightmapPointGenerator::CompletedThreads++; - float percentComplete = (float)FHeightmapPointGenerator::CompletedThreads / (float)FHeightmapPointGenerator::TotalNumberOfThreads; - //UE_LOG(LogWorldGen, Log, TEXT("Created pixel at (%d, %d), completing thread %d of %d. Heightmap completion percent: %f percent."), X, Y, FHeightmapPointGenerator::CompletedThreads, FHeightmapPointGenerator::TotalNumberOfThreads, percentComplete); + float percentComplete = (float)FHeightmapPointGenerator::CompletedThreads / (float)FHeightmapPointGenerator::TotalNumberOfThreads; + if (FHeightmapPointGenerator::bShouldLogOnCompletion) + { + UE_LOG(LogWorldGen, Log, TEXT("Created pixel at (%d, %d), completing thread %d of %d. Heightmap completion percent: %f percent."), X, Y, FHeightmapPointGenerator::CompletedThreads, FHeightmapPointGenerator::TotalNumberOfThreads, percentComplete); + } if (FHeightmapPointGenerator::CompletedThreads == FHeightmapPointGenerator::TotalNumberOfThreads) { // If we're all done, check in with the on completion delegate diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp index bbe9b57..0afe16d 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp @@ -21,24 +21,32 @@ void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeMana // Interpolate between the actual points CreateHeightmapTimer = FPlatformTime::Seconds(); - if (HeightmapGenerationOptions == EHeightmapGenerationType::ForceMultithreaded) + if (HeightmapGenerationOptions == EHeightmapGenerationType::Background) { FIslandGeneratorDelegate generatePoints; generatePoints.BindDynamic(this, &UPolygonalMapHeightmap::CheckMapPointsDone); FHeightmapPointGenerator::GenerateHeightmapPoints(HeightmapSize, NumberOfPointsToAverage, this, PolygonMap, BiomeManager, generatePoints); } - else + else if (HeightmapGenerationOptions == EHeightmapGenerationType::Foreground) { FHeightmapPointGenerator::MapScale = (float)PolygonMap->GetGraphSize() / (float)HeightmapSize; + float squaredHeightmap = (float)HeightmapSize * (float)HeightmapSize; + float current = 0.0f; for (int32 x = 0; x < HeightmapSize; x++) { for (int32 y = 0; y < HeightmapSize; y++) { HeightmapData.Add(FHeightmapPointTask::MakeMapPoint(FVector2D(x, y), PolygonMap, BiomeManager, EPointSelectionMode::InterpolatedWithPolygonBiome)); + current++; + FHeightmapPointGenerator::CompletionPercent = current / squaredHeightmap; } } DoHeightmapPostProcess(); } + else + { + unimplemented(); + } } void UPolygonalMapHeightmap::CheckMapPointsDone() @@ -66,6 +74,12 @@ void UPolygonalMapHeightmap::DoHeightmapPostProcess() } } +float UPolygonalMapHeightmap::GetCompletionPercent() const +{ + return FHeightmapPointGenerator::CompletionPercent; +} + + TArray UPolygonalMapHeightmap::GetMapData() { return HeightmapData; diff --git a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp index 67d9dd6..3c6e363 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp @@ -551,18 +551,50 @@ FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, return output; } - FMapData center1 = GetCenter(Corner.Touches[0]).CenterData; - FMapData center2 = GetCenter(Corner.Touches[1]).CenterData; - FMapData center3 = GetCenter(Corner.Touches[2]).CenterData; + FMapData center1 = Centers[Corner.Touches[0]].CenterData; + FMapData center2 = Centers[Corner.Touches[1]].CenterData; + FMapData center3 = Centers[Corner.Touches[2]].CenterData; FVector2D p1 = center1.Point; FVector2D p2 = center2.Point; FVector2D p3 = center3.Point; + // Check bounding box + //float maxX = FMath::Max3(p1.X, p2.X, p3.X); + //if (Point.X > maxX) + if (Point.X > p1.X && Point.X > p2.X && Point.X > p3.X) + { + // To the right of maximum triangle bounds + return output; + } + //float maxY = FMath::Max3(p1.Y, p2.Y, p3.Y); + //if (Point.Y > maxY) + if (Point.Y > p1.Y && Point.Y > p2.Y && Point.Y > p3.Y) + { + // Above maximum triangle bounds + return output; + } + //float minX = FMath::Min3(p1.X, p2.X, p3.X); + //if (Point.X < minX) + if (Point.X < p1.X && Point.X < p2.X && Point.X < p3.X) + { + // To the left of maximum triangle bounds + return output; + } + //float minY = FMath::Min3(p1.Y, p2.Y, p3.Y); + //if (Point.Y < minY) + if (Point.Y < p1.Y && Point.Y < p2.Y && Point.Y < p3.Y) + { + // Underneath maximum triangle bounds + return output; + } + + // Point is inside of bounding box + // Calculate determinant float det = (p2.Y - p3.Y) * (p1.X - p3.X) + (p3.X - p2.X) * (p1.Y - p3.Y); if (det == 0.0f) { - // Shouldn't happen, but okay + // Shouldn't happen, but just in case return output; } @@ -610,6 +642,7 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 } FPointInterpolationData data = FPointInterpolationData(); + TArray tried = TArray(); if (LastFoundCorner.Index >= 0) { // Optimization: Check to see if we share a triangle with the last point we found. @@ -622,10 +655,11 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 } else { + tried.Add(LastFoundCorner.Index); // Check the neighboring triangles for (int i = 0; i < LastFoundCorner.Adjacent.Num(); i++) { - FMapCorner adjacent = GetCorner(LastFoundCorner.Adjacent[i]); + FMapCorner adjacent = Corners[LastFoundCorner.Adjacent[i]]; data = CornerContainsPoint(Point, adjacent); if (data.bTriangleIsValid) { @@ -633,6 +667,10 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 CornerLookup.Add(intMapCoordinates, LastFoundCorner.Index); break; } + else + { + tried.Add(LastFoundCorner.Adjacent[i]); + } } } } @@ -644,6 +682,10 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 // Try to avoid getting here as much as physically possible. for (int i = 0; i < Corners.Num(); i++) { + if (tried.Contains(i)) + { + continue; + } data = CornerContainsPoint(Point, Corners[i]); if (data.bTriangleIsValid) { diff --git a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h index 698528e..57f6a4d 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h @@ -59,6 +59,11 @@ public: UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Heightmap") void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate onComplete); + // How much of the heightmap we have complete so far. + // This is most useful if you are calculating the heightmap in the background, so it can be used as a loading bar or such. + UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Heightmap") + float GetCompletionPercent() const; + // Returns a COPY of this object's raw heightmap. // This can be iterated over easily, but keep in mind that any changes you make won't be made to the actual heightmap object (i.e., this). // Also keep in mind that the array is actually a 1D representation of a 2D array of HeightmapSize x HeightmapSize. diff --git a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h index 8c47d7d..76ba33c 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h @@ -270,8 +270,8 @@ struct POLYGONALMAPGENERATOR_API FPointInterpolationData UENUM(BlueprintType) enum class EHeightmapGenerationType : uint8 { - ForceMultithreaded, - ForceSingleThread + Foreground, + Background }; /** -- 2.51.2 From 677901c9cb12a359a678f758ef9860a86b6981ec Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Tue, 8 Aug 2017 00:36:09 -0700 Subject: [PATCH 4/6] Moved Biome processing out of the task graph and fixed a bug with heightmap ranges. The change to biome processing should close issue #12, since GameplayTags don't play nice on multiple threads. This takes the GameplayTag system off of the multithreaded approach and instead runs biome determination as a post-processing step on the game thread. This will have a slight decrease in performance, but hopefully it should no longer crash randomly on some systems. Additionally, the Elevation property on each heightmap pixel (which was assumed to be between 0 and 1) was actually being set to values between 0 and ~25000. This was creating an integer overflow when writing a heightmap to disk, as the absurdly high Elevation values were being multiplied by 255 and cast to an 8-bit int. The 100+ value difference between neighboring points were creating a psychadelic effect (as seen in issue #16) and should now be fixed. --- .../Maps/Heightmap/HeightmapPointTask.cpp | 8 ++--- .../Maps/Heightmap/PolygonalMapHeightmap.cpp | 32 +++++++++++++++++-- .../Private/Maps/MapDebugVisualizer.cpp | 2 +- .../Private/Maps/PolygonMap.cpp | 2 +- .../Maps/Elevations/PolygonalMapHeightmap.h | 4 ++- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp index 84a48a6..b33dcad 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp @@ -96,14 +96,14 @@ FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* { pixelData.Moisture = pointData.SourceTriangle.CornerData.Moisture; pixelData.Tags = pointData.SourceTriangle.CornerData.Tags; - pixelData.Biome = pointData.SourceTriangle.CornerData.Biome; + //pixelData.Biome = pointData.SourceTriangle.CornerData.Biome; } else { pixelData.Moisture = pointData.InterpolatedMoisture; // TODO: Interpolate tags pixelData.Tags = pointData.SourceTriangle.CornerData.Tags; - if (PointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome) + /*if (PointSelectionMode == EPointSelectionMode::InterpolatedWithPolygonBiome && pointData.SourceTriangle.CornerData.Biome.IsValid()) { // Grab the biome directly from the CornerData pixelData.Biome = pointData.SourceTriangle.CornerData.Biome; @@ -114,14 +114,14 @@ FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* // TODO: Find out why it crashes (maybe due to multithreading?) // In the meantime, use EPointSelectionMode::InterpolatedWithPolygonBiome instead pixelData.Biome = BiomeManager->DetermineBiome(pixelData); - } + }*/ } } else { // If the point is invalid, the default constructor for the MapData struct is // sufficient for making an ocean pixel. We just need to set the biome. - pixelData.Biome = FGameplayTag::RequestGameplayTag(TEXT("MapData.Biome.Water.Ocean")); + //pixelData.Biome = FGameplayTag::RequestGameplayTag(TEXT("MapData.Biome.Water.Ocean")); } return pixelData; } diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp index 0afe16d..d06a741 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp @@ -7,12 +7,13 @@ #include "Maps/Heightmap/HeightmapPointTask.h" #include "PolygonalMapHeightmap.h" -void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete) +void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete) { if (PolygonMap == NULL) { return; } + BiomeManager = BiomeMngr; MoistureDistributor = MoistureDist; HeightmapSize = Size; HeightmapData.Empty(); @@ -59,13 +60,38 @@ void UPolygonalMapHeightmap::DoHeightmapPostProcess() { UE_LOG(LogWorldGen, Log, TEXT("%d map points created in %f seconds."), HeightmapSize * HeightmapSize, FPlatformTime::Seconds() - CreateHeightmapTimer); - // Add the rivers + /*// Normalize between 0 and 1 + // I had assumed these values were already normalized, but apparently not + float maxHeightmapSize = -1.0f; CreateHeightmapTimer = FPlatformTime::Seconds(); + for (int i = 0; i < HeightmapData.Num(); i++) + { + if (HeightmapData[i].Elevation > maxHeightmapSize) + { + maxHeightmapSize = HeightmapData[i].Elevation; + } + } + for (int i = 0; i < HeightmapData.Num(); i++) + { + HeightmapData[i].Elevation /= maxHeightmapSize; + } + UE_LOG(LogWorldGen, Log, TEXT("Points normalized in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer);*/ + + // Create the biomes + CreateHeightmapTimer = FPlatformTime::Seconds(); + for (int i = 0; i < HeightmapData.Num(); i++) + { + HeightmapData[i].Biome = BiomeManager->DetermineBiome(HeightmapData[i]); + } + UE_LOG(LogWorldGen, Log, TEXT("Biomes determined in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer); + + // Add the rivers + /*CreateHeightmapTimer = FPlatformTime::Seconds(); for (int i = 0; i < MoistureDistributor->Rivers.Num(); i++) { MoistureDistributor->Rivers[i]->MoveRiverToHeightmap(this); } - UE_LOG(LogWorldGen, Log, TEXT("Rivers placed in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer); + UE_LOG(LogWorldGen, Log, TEXT("Rivers placed in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer);*/ if (OnGenerationComplete.IsBound()) { diff --git a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp index 51a74b4..ed2aa3b 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/MapDebugVisualizer.cpp @@ -40,7 +40,7 @@ void UMapDebugVisualizer::DrawDebugPixelGrid(AActor* Actor, const FWorldSpaceMap color = FColor(147, 198, 255); } - float pixelHeight = FMath::FloorToInt(mapData.Elevation * ((MapData.ElevationScale / 100.0f) * PixelHeightMultiplier)); + float pixelHeight = FMath::FloorToInt(mapData.Elevation * ((MapData.ElevationScale / 100.0f) * PixelHeightMultiplier) + MapData.ElevationOffset); FVector v0 = offset + FVector(x * PixelSize, y * PixelSize, pixelHeight); FVector v1 = FVector(v0.X, v0.Y + PixelSize, v0.Z); diff --git a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp index 3c6e363..2c9491d 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp @@ -618,7 +618,7 @@ FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, output.bTriangleIsValid = true; output.SourceTriangle = Corner; - output.InterpolatedElevation = ((lambda1 * center1.Elevation + lambda2 * center2.Elevation + lambda3 * center3.Elevation) * WorldData.ElevationScale) + WorldData.ElevationOffset; + output.InterpolatedElevation = lambda1 * center1.Elevation + lambda2 * center2.Elevation + lambda3 * center3.Elevation; output.InterpolatedMoisture = lambda1 * center1.Moisture + lambda2 * center2.Moisture + lambda3 * center3.Moisture; return output; diff --git a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h index 57f6a4d..93fa008 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h @@ -29,6 +29,8 @@ private: TArray HeightmapData; UPROPERTY() UMoistureDistributor* MoistureDistributor; + UPROPERTY() + UBiomeManager* BiomeManager; UPROPERTY() float CreateHeightmapTimer; @@ -57,7 +59,7 @@ public: // The number of nearest points to consider is governed by the value of the NumberOfPointsToAverage in the UPolygonalMapHeightmap class. // Each "pixel" in the heightmap will be supplied with its own biome, which is determined by the UBiomeManager passed to this function. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Heightmap") - void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeManager, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate onComplete); + void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate onComplete); // How much of the heightmap we have complete so far. // This is most useful if you are calculating the heightmap in the background, so it can be used as a loading bar or such. -- 2.51.2 From addb527dcb0fba3b89b1b5700c2800d9d689b296 Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Tue, 8 Aug 2017 01:14:20 -0700 Subject: [PATCH 5/6] Updated texture renderer to decouple some functionality from MapHeightmap. --- .../Private/IO/MapTextureRenderer.cpp | 230 ++---------------- .../Public/IO/MapTextureRenderer.h | 22 +- 2 files changed, 39 insertions(+), 213 deletions(-) diff --git a/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp b/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp index 2ee025d..9024f53 100644 --- a/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp +++ b/Source/PolygonalMapGenerator/Private/IO/MapTextureRenderer.cpp @@ -16,234 +16,56 @@ bool UMapTextureRenderer::SaveTextureFromHeightmap(UPolygonalMapHeightmap* MapHe // Didn't generate heightmap yet return false; } - UE_LOG(LogWorldGen, Warning, TEXT("Attempting to generate map heightmap texture!")); - - uint32 dtWidth = MapHeightmap->HeightmapSize; - uint32 dtHeight = MapHeightmap->HeightmapSize; - uint32 pixelCount = dtWidth * dtHeight; - uint32 dtBytesPerPixel = 4; - uint32 dtBufferSize = dtWidth * dtHeight * dtBytesPerPixel; - uint32 dtBufferSizeSqrt = dtWidth * dtBytesPerPixel; - - TArray colors; - colors.SetNumZeroed(pixelCount); - - for (uint32 x = 0; x < dtWidth; x++) - { - for (uint32 y = 0; y < dtHeight; y++) - { - FMapData pointData = MapHeightmap->GetMapPoint(x, y); - colors[dtWidth * y + x] = FColor(uint8(pointData.Elevation * 255), uint8(pointData.Elevation * 255), uint8(pointData.Elevation * 255), 255); - } - } - - FIntPoint destSize(dtWidth, dtHeight); - FString resultPath; - FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); - FString filePath = FPaths::Combine(FPaths::GameSavedDir(), Filename); - - return HighResScreenshotConfig.SaveImage(filePath, colors, destSize, &resultPath); -} - -/*void UMapTextureRenderer::CreateTextureFromHeightmap(UPolygonalMapHeightmap* MapHeightmap, UTextureRenderTarget2D* IslandRenderTarget, UMaterialInstanceDynamic* IslandMaterialInstanceDynamic) -{ - if (MapHeightmap->GetMapData().Num() == 0) - { - UE_LOG(LogTemp, Warning, TEXT("Did not create heightmap yet!")); - // Didn't generate heightmap yet - return; - } - UE_LOG(LogTemp, Warning, TEXT("Attempting to generate map heightmap!")); - uint32 dtWidth = MapHeightmap->HeightmapSize; uint32 dtHeight = MapHeightmap->HeightmapSize; + uint32 pixelCount = dtWidth * dtHeight; - //IslandRenderTarget->bHDR = 0; - //IslandRenderTarget->InitAutoFormat(dtWidth, dtHeight); - //IslandRenderTarget->UpdateResource(); - UE_LOG(LogTemp, Log, TEXT("Set map render target!")); - - UTexture2D* dtTexture = UTexture2D::CreateTransient(dtWidth, dtHeight); - dtTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; - dtTexture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; - dtTexture->SRGB = 0; - dtTexture->AddToRoot(); // Guarantee no garbage collection by adding it as a root reference - dtTexture->UpdateResource(); // Update the texture with new variable values. - - int32 pixelCount = dtWidth * dtHeight; - - TArray colors; - colors.SetNumZeroed(pixelCount); + TArray heights; + heights.SetNumZeroed(pixelCount); for (uint32 x = 0; x < dtWidth; x++) { for (uint32 y = 0; y < dtHeight; y++) { FMapData pointData = MapHeightmap->GetMapPoint(x, y); - colors[dtWidth * y + x] = FColor(uint8(pointData.Elevation * 255), uint8(pointData.Elevation * 255), uint8(pointData.Elevation * 255), 255); - //colors[dtWidth * y + x] = FColor::Red; + heights[x + (y * dtHeight)] = pointData.Elevation; } } - UE_LOG(LogTemp, Log, TEXT("Created colors!")); - - if (IslandMaterialInstanceDynamic) - { - UE_LOG(LogTemp, Warning, TEXT("Time to generate texture!")); - CreateTexture(dtTexture, colors); - UE_LOG(LogTemp, Log, TEXT("All done, setting the material instance.")); - IslandMaterialInstanceDynamic->SetTextureParameterValue(FName("DynamicTextureParam"), dtTexture); - - //UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, IslandRenderTarget, dtMaterialInstanceDynamic); - } - else - { - UE_LOG(LogTemp, Error, TEXT("Could not initialize material instance!")); - } + return SaveTextureFromFloatArray(heights, MapHeightmap->HeightmapSize, Filename); } -void UMapTextureRenderer::CreateTexture(UTexture2D* TargetTexture, TArray Colors) +bool UMapTextureRenderer::SaveTextureFromFloatArray(TArray HeightArray, int32 HeightmapSideLength, FString Filename) { - if (TargetTexture) + if (HeightArray.Num() == 0) { - UE_LOG(LogTemp, Warning, TEXT("Target texture is valid.")); - - uint32 dtWidth = TargetTexture->GetSizeX(); - uint32 dtHeight = TargetTexture->GetSizeY(); - UE_LOG(LogTemp, Warning, TEXT("Got size!")); - - uint32 dtBytesPerPixel = 4; - - uint32 dtBufferSize = dtWidth * dtHeight * dtBytesPerPixel; - - UE_LOG(LogTemp, Log, TEXT("Color length: %d; Size of buffer: %d, Texture dimensions: %d x %d."), Colors.Num(), dtBufferSize, dtWidth, dtHeight); - if ((uint32)Colors.Num() > dtBufferSize) - { - UE_LOG(LogTemp, Error, TEXT("Buffer size too small!")); - return; - } - else if ((uint32)Colors.Num() * dtBytesPerPixel < dtBufferSize) - { - UE_LOG(LogTemp, Warning, TEXT("Not enough colors in buffer!")); - } - - uint32 dtBufferSizeSqrt = dtWidth * dtBytesPerPixel; - // This is the data that we Memcpy into the dynamic texture - uint8* dtBuffer = new uint8[dtBufferSize]; - FUpdateTextureRegion2D* updateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, dtWidth, dtHeight); - - for (int i = 0; i < Colors.Num(); i++) - { - int iBlue = i * 4 + 0; - int iGreen = i * 4 + 1; - int iRed = i * 4 + 2; - int iAlpha = i * 4 + 3; - - dtBuffer[iBlue] = Colors[i].B; - dtBuffer[iGreen] = Colors[i].G; - dtBuffer[iRed] = Colors[i].R; - dtBuffer[iAlpha] = Colors[i].A; - } - UE_LOG(LogTemp, Log, TEXT("Done parsing colors, moving on to updating region.")); - - UpdateTextureRegions(TargetTexture, 0, 1, updateTextureRegion, dtBufferSizeSqrt, dtBytesPerPixel, dtBuffer, false); + UE_LOG(LogTemp, Error, TEXT("No floats to make an image out of!")); + return false; } - else + else if (HeightmapSideLength < 0) { - UE_LOG(LogTemp, Error, TEXT("Invalid texture!")); + UE_LOG(LogTemp, Error, TEXT("Side length must be positive!")); + return false; } -} - -void UMapTextureRenderer::UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData) -{ - if (Texture && Texture->Resource && Regions) - { - struct FUpdateTextureRegionsData - { - FTexture2DResource* Texture2DResource; - int32 MipIndex; - uint32 NumRegions; - FUpdateTextureRegion2D* Regions; - uint32 SrcPitch; - uint32 SrcBpp; - uint8* SrcData; - }; - UE_LOG(LogTemp, Log, TEXT("Creating region data!")); - - FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData; - RegionData->Texture2DResource = (FTexture2DResource*)Texture->Resource; - RegionData->MipIndex = MipIndex; - RegionData->NumRegions = NumRegions; - RegionData->Regions = Regions; - RegionData->SrcPitch = SrcPitch; - RegionData->SrcBpp = SrcBpp; - RegionData->SrcData = SrcData; - - UE_LOG(LogTemp, Log, TEXT("Handing off to the GPU!")); + uint32 dtWidth = (uint32)HeightmapSideLength; + uint32 dtHeight = (uint32)HeightmapSideLength;; + uint32 pixelCount = dtWidth * dtHeight; - ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( - UpdateTextureRegionsData, - FUpdateTextureRegionsData*, RegionData, RegionData, - bool, bFreeData, bFreeData, - { - for (uint32 RegionIndex = 0; RegionIndex < RegionData->NumRegions; ++RegionIndex) - { - int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip(); - if (RegionData->MipIndex >= CurrentFirstMip) - { - RHIUpdateTexture2D( - RegionData->Texture2DResource->GetTexture2DRHI(), - RegionData->MipIndex - CurrentFirstMip, - RegionData->Regions[RegionIndex], - RegionData->SrcPitch, - RegionData->SrcData - + RegionData->Regions[RegionIndex].SrcY * RegionData->SrcPitch - + RegionData->Regions[RegionIndex].SrcX * RegionData->SrcBpp - ); - } - } - if (bFreeData) - { - FMemory::Free(RegionData->Regions); - FMemory::Free(RegionData->SrcData); - } - delete RegionData; - }); - UE_LOG(LogTemp, Warning, TEXT("GPU is done creating texture!")); - } - else + TArray colors; + colors.SetNumZeroed(pixelCount); + for(uint32 i = 0; i < pixelCount; i++) { - UE_LOG(LogTemp, Error, TEXT("Attempted to access an invalid texture!")); + colors[i] = FColor(uint8(HeightArray[i] * 255), uint8(HeightArray[i] * 255), uint8(HeightArray[i] * 255), 255); } -} - -bool UMapTextureRenderer::SaveTextureToDisk(UTexture2D* Texture, FString Filename) -{ - Texture->UpdateResource(); - FTexture2DMipMap* MM = &Texture->PlatformData->Mips[0]; - - TArray OutBMP; - int w = MM->SizeX; - int h = MM->SizeY; - OutBMP.InsertZeroed(0, w*h); - - FByteBulkData* RawImageData = &MM->BulkData; - - FColor* FormatedImageData = static_cast(RawImageData->Lock(LOCK_READ_ONLY)); - - for (int i = 0; i < (w*h); ++i) - { - OutBMP[i] = FormatedImageData[i]; - OutBMP[i].A = 255; - } + FIntPoint destSize(dtWidth, dtHeight); + FString resultPath; + FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); - RawImageData->Unlock(); - FIntPoint DestSize(w, h); + FString filenameWithExtension = Filename + ".png"; + FString filePath = FPaths::Combine(FPaths::GameSavedDir(), filenameWithExtension); - FString ResultPath; - FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); - return HighResScreenshotConfig.SaveImage(Filename, OutBMP, DestSize, &ResultPath); -}*/ \ No newline at end of file + return HighResScreenshotConfig.SaveImage(filePath, colors, destSize, &resultPath); +} \ No newline at end of file diff --git a/Source/PolygonalMapGenerator/Public/IO/MapTextureRenderer.h b/Source/PolygonalMapGenerator/Public/IO/MapTextureRenderer.h index fe5500b..13d851f 100644 --- a/Source/PolygonalMapGenerator/Public/IO/MapTextureRenderer.h +++ b/Source/PolygonalMapGenerator/Public/IO/MapTextureRenderer.h @@ -16,17 +16,21 @@ class POLYGONALMAPGENERATOR_API UMapTextureRenderer : public UBlueprintFunctionL { GENERATED_BODY() + // Creates a heightmap texture on disk. + // The data is pulled from the supplied Map Heightmap, and the file is saved as a .png in your GameSavedDir. + // On Windows, this will be //InstallDir/WindowsNoEditor/GameName/Saved. + // In the Editor, this will be //ProjectDirectory/Saved. + // Specify the filename you want to save the heightmap as, without any extension. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") static bool SaveTextureFromHeightmap(UPolygonalMapHeightmap* MapHeightmap, FString Filename); - - /*UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") - static void CreateTextureFromHeightmap(UPolygonalMapHeightmap* MapHeightmap, UTextureRenderTarget2D* IslandRenderTarget, UMaterialInstanceDynamic* IslandMaterialInstanceDynamic); + // Creates a heightmap texture on disk. + // The data is pulled from the supplied array of floats, and the file is saved as a .png in your GameSavedDir. + // The floats are assumed to be in the range 0-1, where 0 is black and 1 is white. The heightmap will use the RGB channels, + // with alpha set to 255. + // On Windows, the image will be saved under //InstallDir/WindowsNoEditor/GameName/Saved. + // In the Editor, the image will be saved under //ProjectDirectory/Saved. + // Specify the filename you want to save the heightmap as, without any extension. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") - static void CreateTexture(UTexture2D* TargetTexture, TArray Colors); - - UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") - static bool SaveTextureToDisk(UTexture2D* Texture, FString Filename); - - static void UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);*/ + static bool SaveTextureFromFloatArray(TArray HeightArray, int32 HeightmapSideLength, FString Filename); }; -- 2.51.2 From 1623532336b52bf6aa906cb5dd92aca7f7a79928 Mon Sep 17 00:00:00 2001 From: Jay Stevens Date: Tue, 8 Aug 2017 02:31:05 -0700 Subject: [PATCH 6/6] Heightmap can now interpolate using data from the center of a Delaunay triangle, rather than just the edges. --- .../Maps/Heightmap/HeightmapPointTask.h | 8 +- .../Maps/Heightmap/HeightmapPointTask.cpp | 10 ++- .../Maps/Heightmap/PolygonalMapHeightmap.cpp | 63 +++++++++++----- .../Private/Maps/IslandMapGenerator.cpp | 4 +- .../Private/Maps/PolygonMap.cpp | 74 ++++++++++++++++--- .../Maps/Elevations/PolygonalMapHeightmap.h | 4 +- .../Public/Maps/IslandMapGenerator.h | 2 +- .../Public/Maps/PolygonMap.h | 43 ++++++++++- 8 files changed, 167 insertions(+), 41 deletions(-) diff --git a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h index 3aff880..a0529a9 100644 --- a/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h +++ b/Source/PolygonalMapGenerator/Classes/Maps/Heightmap/HeightmapPointTask.h @@ -31,18 +31,15 @@ class FHeightmapPointTask { public: - FHeightmapPointTask(int32 XCoord, int32 YCoord, int32 NumberOfPoints, EPointSelectionMode SelectionMode) + FHeightmapPointTask(int32 XCoord, int32 YCoord, EPointSelectionMode SelectionMode) { X = XCoord; Y = YCoord; - NumberOfPointsToAverage = NumberOfPoints; PointSelectionMode = SelectionMode; } int32 X; int32 Y; - // How many points to take into account when generating an interpolated heightmap - int32 NumberOfPointsToAverage; // What mode to be in for generating the heightmap. EPointSelectionMode PointSelectionMode; @@ -83,6 +80,7 @@ public: // The scale between heightmap units and graph units // 1 heightmap unit is this many graph units static float MapScale; + static bool bInterpolateUsingTriangleCenters; // Results of the threads static TArray HeightmapData; @@ -104,7 +102,7 @@ public: static bool TasksAreComplete(); // Initiation point to start the heightmap generation process. - static void GenerateHeightmapPoints(const int32 HeightmapSize, int32 NumberOfPointsToAverage, UPolygonalMapHeightmap* HeightmapGenerator, UPolygonMap* Graph, UBiomeManager* BiomeMgr, const FIslandGeneratorDelegate OnComplete); + static void GenerateHeightmapPoints(UPolygonalMapHeightmap* HeightmapGenerator, UPolygonMap* Graph, UBiomeManager* BiomeMgr, const FHeightmapCreationData HeightmapProperties, const FIslandGeneratorDelegate OnComplete); // Check to see if all threads are complete. // If so, call the OnAllPointsComplete delegate. diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp index b33dcad..8327c38 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/HeightmapPointTask.cpp @@ -10,6 +10,7 @@ UPolygonalMapHeightmap* FHeightmapPointGenerator::MapHeightmap = NULL; UPolygonMap* FHeightmapPointGenerator::MapGraph = NULL; UBiomeManager* FHeightmapPointGenerator::BiomeManager = NULL; float FHeightmapPointGenerator::MapScale = 1.0f; +bool FHeightmapPointGenerator::bInterpolateUsingTriangleCenters = true; // Results of the threads TArray FHeightmapPointGenerator::HeightmapData = TArray(); @@ -31,14 +32,17 @@ bool FHeightmapPointGenerator::TasksAreComplete() return CompletedThreads == TotalNumberOfThreads; } -void FHeightmapPointGenerator::GenerateHeightmapPoints(const int32 HeightmapSize, int32 NumberOfPointsToAverage, UPolygonalMapHeightmap* HeightmapGenerator, UPolygonMap* Graph, UBiomeManager* BiomeMgr, const FIslandGeneratorDelegate OnComplete) +void FHeightmapPointGenerator::GenerateHeightmapPoints(UPolygonalMapHeightmap* HeightmapGenerator, UPolygonMap* Graph, UBiomeManager* BiomeMgr, const FHeightmapCreationData HeightmapProperties, const FIslandGeneratorDelegate OnComplete) { + int32 HeightmapSize = HeightmapProperties.Size; + check(HeightmapSize > 0); MapHeightmap = HeightmapGenerator; MapGraph = Graph; BiomeManager = BiomeMgr; OnAllPointsComplete = OnComplete; MapScale = (float)MapGraph->GetGraphSize() / (float)HeightmapSize; + bInterpolateUsingTriangleCenters = HeightmapProperties.bUseTriangleCentersForInterpolation; TotalNumberOfThreads = 0; CompletedThreads = 0; @@ -59,7 +63,7 @@ void FHeightmapPointGenerator::GenerateHeightmapPoints(const int32 HeightmapSize { for(int32 y = 0; y < HeightmapSize; y++) { - CompletionEvents.Add(TGraphTask::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(x, y, NumberOfPointsToAverage, pointSelectionMode)); + CompletionEvents.Add(TGraphTask::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(x, y, pointSelectionMode)); TotalNumberOfThreads++; } } @@ -87,7 +91,7 @@ FMapData FHeightmapPointTask::MakeMapPoint(FVector2D PixelPosition, UPolygonMap* FMapData pixelData = FMapData(); pixelData.Point = PixelPosition * FHeightmapPointGenerator::MapScale; - FPointInterpolationData pointData = MapGraph->FindInterpolatedDataForPoint(pixelData.Point); + FPointInterpolationData pointData = MapGraph->FindInterpolatedDataForPoint(pixelData.Point, FHeightmapPointGenerator::bInterpolateUsingTriangleCenters); if (pointData.bTriangleIsValid) { // The point is valid, populate from the triangle diff --git a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp index d06a741..677cd16 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/Heightmap/PolygonalMapHeightmap.cpp @@ -7,7 +7,7 @@ #include "Maps/Heightmap/HeightmapPointTask.h" #include "PolygonalMapHeightmap.h" -void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete) +void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const FHeightmapCreationData HeightmapCreationOptions, const FIslandGeneratorDelegate OnComplete) { if (PolygonMap == NULL) { @@ -15,20 +15,23 @@ void UPolygonalMapHeightmap::CreateHeightmap(UPolygonMap* PolygonMap, UBiomeMana } BiomeManager = BiomeMngr; MoistureDistributor = MoistureDist; - HeightmapSize = Size; + + HeightmapProperties = HeightmapCreationOptions; + HeightmapSize = HeightmapProperties.Size; + HeightmapData.Empty(); + OnGenerationComplete = OnComplete; // Interpolate between the actual points CreateHeightmapTimer = FPlatformTime::Seconds(); - - if (HeightmapGenerationOptions == EHeightmapGenerationType::Background) + if (HeightmapCreationOptions.HeightmapGenerationPriority == EHeightmapGenerationType::Background) { FIslandGeneratorDelegate generatePoints; generatePoints.BindDynamic(this, &UPolygonalMapHeightmap::CheckMapPointsDone); - FHeightmapPointGenerator::GenerateHeightmapPoints(HeightmapSize, NumberOfPointsToAverage, this, PolygonMap, BiomeManager, generatePoints); + FHeightmapPointGenerator::GenerateHeightmapPoints(this, PolygonMap, BiomeManager, HeightmapProperties, generatePoints); } - else if (HeightmapGenerationOptions == EHeightmapGenerationType::Foreground) + else if (HeightmapCreationOptions.HeightmapGenerationPriority == EHeightmapGenerationType::Foreground) { FHeightmapPointGenerator::MapScale = (float)PolygonMap->GetGraphSize() / (float)HeightmapSize; float squaredHeightmap = (float)HeightmapSize * (float)HeightmapSize; @@ -60,22 +63,46 @@ void UPolygonalMapHeightmap::DoHeightmapPostProcess() { UE_LOG(LogWorldGen, Log, TEXT("%d map points created in %f seconds."), HeightmapSize * HeightmapSize, FPlatformTime::Seconds() - CreateHeightmapTimer); - /*// Normalize between 0 and 1 - // I had assumed these values were already normalized, but apparently not - float maxHeightmapSize = -1.0f; - CreateHeightmapTimer = FPlatformTime::Seconds(); - for (int i = 0; i < HeightmapData.Num(); i++) + // Blur polygon edges + int blurSteps = HeightmapProperties.PostProcessBlurSteps; + if (blurSteps > 0) { - if (HeightmapData[i].Elevation > maxHeightmapSize) + CreateHeightmapTimer = FPlatformTime::Seconds(); + TArray blurredData; + blurredData.SetNumZeroed(HeightmapData.Num()); + for (int x = 0; x < HeightmapSize; x++) { - maxHeightmapSize = HeightmapData[i].Elevation; + for (int y = 0; y < HeightmapSize; y++) + { + float averageElevation = 0.0f; + float averageMoisture = 0.0f; + int iterations = 0; + for (int xOffset = x - blurSteps; xOffset <= x + blurSteps; xOffset++) + { + if (xOffset < 0 || xOffset >= HeightmapSize) + { + continue; + } + for (int yOffset = y - blurSteps; yOffset <= y + blurSteps; yOffset++) + { + if (yOffset < 0 || yOffset >= HeightmapSize) + { + continue; + } + int32 offsetIndex = xOffset + (yOffset * HeightmapSize); + averageElevation += HeightmapData[offsetIndex].Elevation; + averageMoisture += HeightmapData[offsetIndex].Moisture; + iterations++; + } + } + int32 index = x + (y * HeightmapSize); + blurredData[index].Elevation = averageElevation / (float)iterations; + blurredData[index].Moisture = averageMoisture / (float)iterations; + } } + HeightmapData = blurredData; + UE_LOG(LogWorldGen, Log, TEXT("Points blurred in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer); } - for (int i = 0; i < HeightmapData.Num(); i++) - { - HeightmapData[i].Elevation /= maxHeightmapSize; - } - UE_LOG(LogWorldGen, Log, TEXT("Points normalized in %f seconds."), FPlatformTime::Seconds() - CreateHeightmapTimer);*/ // Create the biomes CreateHeightmapTimer = FPlatformTime::Seconds(); diff --git a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp index 058dbf5..5f3cce5 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/IslandMapGenerator.cpp @@ -420,7 +420,7 @@ void AIslandMapGenerator::DetermineBiomes() UE_LOG(LogWorldGen, Log, TEXT("Biomes determined in %f seconds."), FPlatformTime::Seconds() - CurrentGenerationTime); } -void AIslandMapGenerator::CreateHeightmap(const int32 HeightmapSize, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnHeightmapGenerationFinished) +void AIslandMapGenerator::CreateHeightmap(const FHeightmapCreationData HeightmapGenerationOptions, const FIslandGeneratorDelegate OnHeightmapGenerationFinished) { if (MapGraph == NULL) { @@ -436,7 +436,7 @@ void AIslandMapGenerator::CreateHeightmap(const int32 HeightmapSize, const EHeig FIslandGeneratorDelegate finalizationFinished; finalizationFinished.BindDynamic(this, &AIslandMapGenerator::OnHeightmapFinished); - MapHeightmap->CreateHeightmap(MapGraph, BiomeManager, MoistureDistributor, HeightmapSize, HeightmapGenerationOptions, finalizationFinished); + MapHeightmap->CreateHeightmap(MapGraph, BiomeManager, MoistureDistributor, HeightmapGenerationOptions, finalizationFinished); } void AIslandMapGenerator::OnHeightmapFinished() diff --git a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp index 2c9491d..1d21bf8 100644 --- a/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp +++ b/Source/PolygonalMapGenerator/Private/Maps/PolygonMap.cpp @@ -472,7 +472,7 @@ FMapCorner UPolygonMap::FindMapCornerForCoordinate(const FVector2D& Point) FMapCorner corner = FMapCorner(); for (int i = 0; i < Corners.Num(); i++) { - if (CornerContainsPoint(Point, Corners[i]).bTriangleIsValid) + if (CornerContainsPoint(Point, Corners[i], false).bTriangleIsValid) { corner = Corners[i]; if (corner.Touches.Num() == 0) @@ -543,7 +543,7 @@ bool UPolygonMap::CenterContainsPoint(const FVector2D& Point, const FMapCenter& return (intersections & 1) == 1; // True if point is odd (inside of polygon) } -FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const +FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner, bool bInterpolateUsingTriangleCenter) const { FPointInterpolationData output = FPointInterpolationData(); if (Corner.Touches.Num() != 3) @@ -618,13 +618,70 @@ FPointInterpolationData UPolygonMap::CornerContainsPoint(const FVector2D& Point, output.bTriangleIsValid = true; output.SourceTriangle = Corner; + + if (bInterpolateUsingTriangleCenter) + { + // At this point, we know we are inside the triangle. + // However, our triangle is actually 3 triangles put together, with the SourceTriangle being the point at the center. + // We know we are in triangle p1-p2-p3. + // However, we want to find out if we are in triangle p1-Center-p2 (Triangle A), triangle p1-Center-p3 (Triangle B), or triangle p2-Center-p3 (Triangle C). + // To determine this, we do the barycentric test one more time + FVector2D triangleCenter = Corner.CornerData.Point; + + // Triangle A (p1-Center-p2) + float triangleADet = (triangleCenter.Y - p2.Y) * (p1.X - p2.X) + (p2.X - triangleCenter.X) * (p1.Y - p2.Y); + if (triangleADet != 0.0f) + { + float triangleALambda1 = ((triangleCenter.Y - p2.Y) * (Point.X - p2.X) + (p2.X - triangleCenter.X) * (Point.Y - p2.Y)) / triangleADet; + float triangleALambda2 = ((p2.Y - p1.Y) * (Point.X - p2.X) + (p1.X - p2.X) * (Point.Y - p2.Y)) / triangleADet; + float triangleALambda3 = 1 - triangleALambda1 - triangleALambda2; + if (0 <= triangleALambda1 && triangleALambda1 <= 1 && 0 <= triangleALambda2 && triangleALambda2 <= 1 && 0 <= triangleALambda3 && triangleALambda3 <= 1) + { + output.InterpolatedElevation = triangleALambda1 * center1.Elevation + triangleALambda2 * Corner.CornerData.Elevation + triangleALambda3 * center2.Elevation; + output.InterpolatedMoisture = triangleALambda1 * center1.Moisture + triangleALambda2 * Corner.CornerData.Moisture + triangleALambda3 * center2.Moisture; + return output; + } + } + + // Triangle A didn't work out, try Triangle B (p1-Center-p3) + float triangleBDet = (triangleCenter.Y - p3.Y) * (p1.X - p3.X) + (p3.X - triangleCenter.X) * (p1.Y - p3.Y); + if (triangleBDet != 0.0f) + { + float triangleBLambda1 = ((triangleCenter.Y - p3.Y) * (Point.X - p3.X) + (p3.X - triangleCenter.X) * (Point.Y - p3.Y)) / triangleBDet; + float triangleBLambda2 = ((p3.Y - p1.Y) * (Point.X - p3.X) + (p1.X - p3.X) * (Point.Y - p3.Y)) / triangleBDet; + float triangleBLambda3 = 1 - triangleBLambda1 - triangleBLambda2; + if (0 <= triangleBLambda1 && triangleBLambda1 <= 1 && 0 <= triangleBLambda2 && triangleBLambda2 <= 1 && 0 <= triangleBLambda3 && triangleBLambda3 <= 1) + { + output.InterpolatedElevation = triangleBLambda1 * center1.Elevation + triangleBLambda2 * Corner.CornerData.Elevation + triangleBLambda3 * center2.Elevation; + output.InterpolatedMoisture = triangleBLambda1 * center1.Moisture + triangleBLambda2 * Corner.CornerData.Moisture + triangleBLambda3 * center2.Moisture; + return output; + } + } + + // Triangle B didn't work out, try Triangle C (p2-Center-p3) + float triangleCDet = (triangleCenter.Y - p3.Y) * (p2.X - p3.X) + (p3.X - triangleCenter.X) * (p2.Y - p3.Y); + if (triangleCDet != 0.0f) + { + float triangleCLambda1 = ((triangleCenter.Y - p3.Y) * (Point.X - p3.X) + (p3.X - triangleCenter.X) * (Point.Y - p3.Y)) / triangleCDet; + float triangleCLambda2 = ((p3.Y - p2.Y) * (Point.X - p3.X) + (p2.X - p3.X) * (Point.Y - p3.Y)) / triangleCDet; + float triangleCLambda3 = 1 - triangleCLambda1 - triangleCLambda2; + if (0 <= triangleCLambda1 && triangleCLambda1 <= 1 && 0 <= triangleCLambda2 && triangleCLambda2 <= 1 && 0 <= triangleCLambda3 && triangleCLambda3 <= 1) + { + output.InterpolatedElevation = triangleCLambda1 * center1.Elevation + triangleCLambda2 * Corner.CornerData.Elevation + triangleCLambda3 * center2.Elevation; + output.InterpolatedMoisture = triangleCLambda1 * center1.Moisture + triangleCLambda2 * Corner.CornerData.Moisture + triangleCLambda3 * center2.Moisture; + return output; + } + } + unimplemented(); + } + + // We don't care about interpolating relative to the center, we just care that we're in the triangle output.InterpolatedElevation = lambda1 * center1.Elevation + lambda2 * center2.Elevation + lambda3 * center3.Elevation; output.InterpolatedMoisture = lambda1 * center1.Moisture + lambda2 * center2.Moisture + lambda3 * center3.Moisture; - return output; } -FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2D& Point) +FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2D& Point, bool bInterpolateUsingTriangleCenters) { if (Point.X > MaxPointLocation || Point.Y > MaxPointLocation || Point.X < MinPointLocation || Point.Y < MinPointLocation) { @@ -637,8 +694,7 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 intMapCoordinates.Y = FMath::RoundToInt(Point.Y); if (CornerLookup.Contains(intMapCoordinates)) { - UE_LOG(LogWorldGen, Log, TEXT("Cache hit! (%f, %f)"), Point.X, Point.Y); - return CornerContainsPoint(Point, GetCorner(CornerLookup[intMapCoordinates])); + return CornerContainsPoint(Point, GetCorner(CornerLookup[intMapCoordinates]), bInterpolateUsingTriangleCenters); } FPointInterpolationData data = FPointInterpolationData(); @@ -648,7 +704,7 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 // Optimization: Check to see if we share a triangle with the last point we found. // If we're running on a single thread, this is very helpful. // It doesn't work so well if we're multithreaded. - data = CornerContainsPoint(Point, LastFoundCorner); + data = CornerContainsPoint(Point, LastFoundCorner, bInterpolateUsingTriangleCenters); if (data.bTriangleIsValid) { CornerLookup.Add(intMapCoordinates, LastFoundCorner.Index); @@ -660,7 +716,7 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 for (int i = 0; i < LastFoundCorner.Adjacent.Num(); i++) { FMapCorner adjacent = Corners[LastFoundCorner.Adjacent[i]]; - data = CornerContainsPoint(Point, adjacent); + data = CornerContainsPoint(Point, adjacent, bInterpolateUsingTriangleCenters); if (data.bTriangleIsValid) { LastFoundCorner = adjacent; @@ -686,7 +742,7 @@ FPointInterpolationData UPolygonMap::FindInterpolatedDataForPoint(const FVector2 { continue; } - data = CornerContainsPoint(Point, Corners[i]); + data = CornerContainsPoint(Point, Corners[i], bInterpolateUsingTriangleCenters); if (data.bTriangleIsValid) { LastFoundCorner = Corners[i]; diff --git a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h index 93fa008..0ce4d23 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/Elevations/PolygonalMapHeightmap.h @@ -31,6 +31,8 @@ private: UMoistureDistributor* MoistureDistributor; UPROPERTY() UBiomeManager* BiomeManager; + UPROPERTY() + FHeightmapCreationData HeightmapProperties; UPROPERTY() float CreateHeightmapTimer; @@ -59,7 +61,7 @@ public: // The number of nearest points to consider is governed by the value of the NumberOfPointsToAverage in the UPolygonalMapHeightmap class. // Each "pixel" in the heightmap will be supplied with its own biome, which is determined by the UBiomeManager passed to this function. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Heightmap") - void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const int32 Size, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate onComplete); + void CreateHeightmap(UPolygonMap* PolygonMap, UBiomeManager* BiomeMngr, UMoistureDistributor* MoistureDist, const FHeightmapCreationData HeightmapCreationOptions, const FIslandGeneratorDelegate onComplete); // How much of the heightmap we have complete so far. // This is most useful if you are calculating the heightmap in the background, so it can be used as a loading bar or such. diff --git a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h index 3b90c01..9e28641 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h +++ b/Source/PolygonalMapGenerator/Public/Maps/IslandMapGenerator.h @@ -196,7 +196,7 @@ public: // When the function is done, it will call the OnComplete delegate. // The heightmap can be accessed through the GetHeightmap() function. UFUNCTION(BlueprintCallable, Category = "World Generation|Island Generation|Map") - void CreateHeightmap(const int32 HeightmapSize, const EHeightmapGenerationType HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete); + void CreateHeightmap(const FHeightmapCreationData HeightmapGenerationOptions, const FIslandGeneratorDelegate OnComplete); // This draws a debug voronoi representation of the island, using the values specified in // the IslandData's PolygonMapSettings struct. diff --git a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h index 76ba33c..47858f4 100644 --- a/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h +++ b/Source/PolygonalMapGenerator/Public/Maps/PolygonMap.h @@ -274,6 +274,45 @@ enum class EHeightmapGenerationType : uint8 Background }; +USTRUCT(BlueprintType) +struct POLYGONALMAPGENERATOR_API FHeightmapCreationData +{ + GENERATED_BODY() + + // Running heightmap generation in the foreground is a bit faster, but locks up the game thread. + // Running heightmap generation in the background takes longer, but the game thread remains free for + // you to show a loading bar, have the player run around a different level, or whatever you may need. + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + EHeightmapGenerationType HeightmapGenerationPriority; + + // This is the size of the heightmap, in pixels. + // Increasing this increases loading times exponentially, but gives more detail on the heightmap. + // Remember: Setting this to 1024 means you need to generate 1024 * 1024 pixels. It may take a while (1-2 minutes). + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + int32 Size; + + // Whether the centers of a triangle should be used for interpolation. + // This essentially splits every Delaunay triangle into the map into 3 separate triangles, with their shared vertex being the voronoi center. + // This will marginally slow down heightmap generation, but the provided interpolation data is more accurate. + // If you don't care about interpolation data and just want to know if a point is in a triangle or not, set this to false for better performance. + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + bool bUseTriangleCentersForInterpolation; + + // How many blurring steps we should use for post-processing. + // A value of 0 will disable blurring entirely, giving the heightmap a very "polygonal" appearance. + // A value of around 5 usually gives fairly good results, as the polygon edges get blurred away. + UPROPERTY(Category = "Map", BlueprintReadWrite, EditAnywhere) + uint8 PostProcessBlurSteps; + + FHeightmapCreationData() + { + HeightmapGenerationPriority = EHeightmapGenerationType::Background; + Size = 256; + bUseTriangleCentersForInterpolation = true; + PostProcessBlurSteps = 5; + } +}; + /** * The PolygonMap is a class which uses a Voronoi diagram to collect data about a graph of * points on the XY plane. @@ -474,10 +513,10 @@ public: // If the triangle is invalid, bTriangleIsValid will be set to false. // If the triangle is valid, the moisture and elevation will be interpolated. UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") - FPointInterpolationData CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner) const; + FPointInterpolationData CornerContainsPoint(const FVector2D& Point, const FMapCorner& Corner, bool bInterpolateUsingTriangleCenter) const; UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") - FPointInterpolationData FindInterpolatedDataForPoint(const FVector2D& Point); + FPointInterpolationData FindInterpolatedDataForPoint(const FVector2D& Point, bool bInterpolateUsingTriangleCenters); // Calculates the interpolated Z position of a 2D point between 3 MapCenters. /*UFUNCTION(BlueprintPure, Category = "World Generation|Island Generation|Graph") -- 2.51.2