├── .gitignore ├── FWorldGenerator.uplugin ├── LICENSE ├── README.md ├── Resources └── Icon128.png ├── Source └── FWorldGenerator │ ├── FWorldGenerator.Build.cs │ ├── FWorldGenerator.cpp │ ├── FWorldGenerator.h │ ├── Private │ ├── FWGChunk.cpp │ ├── FWGChunk.h │ ├── FWGen.cpp │ └── PerlinNoise.hpp │ └── Public │ └── FWGen.h └── pics ├── Example BindFunctionToSpawn 1.png ├── Example BindFunctionToSpawn 2.jpg ├── Example Ground Material.jpg └── FWorldGenerator.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | Intermediate -------------------------------------------------------------------------------- /FWorldGenerator.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0.0", 5 | "FriendlyName": "FWorldGenerator", 6 | "Description": "Procedural World Generator", 7 | "Category": "Procedural Generation", 8 | "CreatedBy": "Flone", 9 | "CreatedByURL": "https://github.com/Flone-dnb", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "FWorldGenerator", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "ProceduralMeshComponent", 27 | "Enabled": true 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (c) 2019-2021 Aleksandr "Flone" Tretyakov (github.com/Flone-dnb) 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | 22 | -------------------------------------------------------------------------- 23 | 24 | 25 | FWorldGenerator is using Reputeless's Perlin noise generator 26 | (https://github.com/Reputeless/PerlinNoise) 27 | its license is shown below: 28 | 29 | MIT License 30 | 31 | Copyright (c) 2013-2018 Ryo Suzuki 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FWorldGenerator 2 | 3 | No longer supported, reason: the heightmap approach that this plugin uses has quite a few limitations, but it was a valuable experience. For proper procedural generation I recommend looking at the Marching Cubes algorithm. 4 | 5 |

6 | 7 |

8 | Example of the infinite world generated with the default settings and custom user objects by the FWorldGenerator (note that the world is loading by chunks). 9 |
10 | (far away objects were culled for performance reasons) 11 |
12 |
13 | If you would like to use the FWorldGenerator, please, read this fully to avoid any mistakes. 14 | 15 | # Parameters 16 | 17 | You can configure the generator's parameters in the details tab (or through the "set" functions of the generator - see below). 18 |
19 | 20 |

Preview

21 | 25 | 26 |

Chunks

27 | 35 | 36 |

Generation

37 | 44 | 45 |

World

46 | 49 | 50 |

Ground

51 | 58 | 59 |

Water

60 | 67 | 68 |

Additional Steps

69 | 73 | 74 |

Ground Material Blend

75 | 84 | 85 |

Slope Dependent Blend

86 | 89 | 90 |

Spawning Objects

91 | 99 | 100 | 101 | # Terrain Material 102 | 103 | To set up Ground Material correctly you can just add your material but it's just gonna cover all the terrain. If you want your materials to change depending on the terrain height, for example, if you want to have grass on low terrain height and snow on the high terrain height then you should create the material which is similar to the one below: 104 |

105 | 106 |

107 | Let's guess you have a material of grass, you have base color, metallic, specular, etc. You can just create simple material with the Lerp_3Color node and add the Vertex Color node. Then copy-paste your material into this new simple material and connect your base color output to one of then Lerp_3Color inputs. Then you can copy-paste the Lerp and Vertex Color nodes and connect them to the Normal input of the material, for example.
108 |
109 | If you want your terrain to have only one material then you can just connect your textures to every input of the Lerp node. 110 | 111 | # How to use it 112 |
    113 |
  1. Download the plugin from the releases tab or just clone the master branch.
  2. 114 |
  3. Unzip the downloaded archive and move the unzipped folder to your project's "Plugins" folder (create one if it doesn't exist). So your project's root directory will have the "Plugins" folder and the "FWorldGenerator" folder inside it, containing the .uplugin in there.
  4. 115 |
  5. If you have a Blueprint project you need to convert it to the C++ project by just doing "Add New C++ Class" inside the engine which will set up everything needed.
  6. 116 |
  7. If you have code in your project: Right-click your .uproject file and click "Generate Visual Studio project files".
  8. 117 |
  9. Run the project (it should build the plugin if necessary).
  10. 118 |
  11. To access the plugin in the C++ code: In your project's .Build.cs file you must add "FWorldGenerator" to PublicDependencyModuleNames. Recompile. And now you can include "FWGen.h".
  12. 119 |
120 | 121 | To use the generator just place an object of the FWGen class (search in the Modes tab in UE) in your level and configure its details. Don't forget to enable Complex Preview if you want to see how it looks.
122 |
123 | To generate the world in the runtime you need to call the GenerateWorld() function from this object. Passed Actor (character) will be teleported to the central chunk (Z-coord will be the same).
124 |
125 | You need to be sure that you are creating your character in the center (central chunk) of the world. You can check chunk bounds by enabling the Draw Chunk Bounds option.
126 |
127 | BE AWARE that generating a world (probably with your custom actors (see below)) can take a while! Don't think that it stuck somewhere or froze, just take your time. For example, generating a world with the default parameters without spawning objects may take up to a minute (PIE).
128 |
129 | Do not worry, you will not fall from the generated terrain, there are blocking volumes at the borders. 130 | 131 | # Load/unload chunks logic 132 | 133 | Chunks of the world will be loaded and unloaded depending on where the player is going. But before calling GenerateWorld() you need to call AddOverlapToActorClass() function and add a class that will be considered as a player. You may add more than one class. Use RemoveOverlapToActorClass() to remove classes from considered as a player. 134 | 135 | # How to spawn custom objects in world randomly 136 | 137 | If you want FWorldGenerator to spawn your custom objects in the world such as trees, then you need to call BindFunctionToSpawn function and pass a name of the function which will be called on GenerateWorld() and the parameter of this function will be position in which you will need to spawn an object by yourself. 138 |
BindFunctionToSpawn takes 3 arguments: 139 | 146 | "Function Name" should be a function that accepts Transform, Integer64 and Integer64 (which determine the coordinates of the chunk).
147 | Here is the example of how you want to spawn many grass meshes on the terrain:
148 | (click to see in full size) 149 |

150 | 151 |

152 | And Spawn Grass function: 153 |
154 |

155 | 156 |

157 |
158 | And don't forget to set the culling in the Hierarchical Instanced Static Mesh Component if you don't want to melt your PC. 159 |
160 | If you want to spawn something which is not like grass or trees (i.e. not through Hierarchical Instanced Static Mesh) then be sure to set the "Always Spawn, Ignore Collisions" option in the Collision Handling Override param in the SpawnActor node OR spawn your actors a little higher than the given transform.
161 |
162 | And if you want to get the location of the free cell, then you can call the GetFreeCellLocation function. The SetBlocking param, if set to true, will make this cell block, so on the next GetFreeCellLocation call, you will not get this cell. 163 |
164 | Please note that if you want to call GenerateWorld() again you need to delete all actors that you've spawned.
165 |
166 | If your character moves then the generator will load new chunks and unload far ones. So, you will need to despawn all actors on those far chunks. 167 | Using the BindFunctionToDespawnActors() function you can bind a function to despawn actors. 168 | The function should accept 2 Integer64 values as an input (x and y of chunk that gets unloaded). 169 | It's up to you to decide on how to despawn them. You also can you GetChunkXByLocation() and GetChunkYByLocation() functions to see if any of your non-static objects are on the chunk that is despawning.
170 |
171 | Also, you need to control the probabilities of each layer manually so that the probabilities of one layer add up to 1.0. And so you will have a probability of 4.0 in total (for 4 layers).
172 |
173 | Note, if you are using Hierarchical Instanced Static Mesh Component (like shown in the picture above) and your actors don't have a material, then you should check the Used with Instanced Static Meshes option in the Usage category inside your material. 174 | 175 | # Save/Load world params to/from file 176 | 177 | Inside the Editor in the "Save / Load" section of the FWGen Details tab, you can find a field for the path of the save file: "Path to Save File". Type in a path, for example: "D:/Home/save" and then click on the "Save Params to File Right Now" to make this value "true". After that, your file will be saved with the .fwgs extension. If you want to read params from this file you just need to add the ".fwgs" extension to your "D:/Home/save" to make it "D:/Home/save.fwgs" and then click on the "Read Params from File Right Now". If the path is correct and this save file has a supported save version, then the "Last Save Load Operation Status" value will be set to "true" automatically. For now, save/load operations is done like this, I didn't spend too much time on this, but in the future updates maybe this operation will have their own windows.
178 |
179 | You can visit our site fwg-save-share.xyz where you can share your world params save file and download others'. 180 | 181 | # Save/Load world 182 | 183 | Although there are still a lot of things to do, FWorldGenerator has functions to save/load the world. GenerateWorld() function has 3 params which are X, Y and a pointer to a character. You can remember your X and Y by calling the GetCentralChunkX() GetCentralChunkY() functions and then continue playing from this chunk by entering those coordinates to the GenerateWorld() function and passing the pointer to a character which will be teleported to the center of this world. 184 | 185 | # Functions 186 | 187 | You can set the parameters of the generator in the Editor but you also can do this in the Blueprints (or C++ code) by calling functions from the "FWorldGenerator" category. Functions usually called like this: "Set(parameter name)". Functions that return bool will return true if your passed value was incorrect or out of valid range. See Parameters (above) to see their valid values range. 188 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flone-dnb/FWorldGenerator/77c712d1001ec178c9b336767bd740c42b76a003/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/FWorldGenerator/FWorldGenerator.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class FWorldGenerator : ModuleRules 6 | { 7 | public FWorldGenerator(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | } 14 | ); 15 | 16 | 17 | PrivateIncludePaths.AddRange( 18 | new string[] { 19 | } 20 | ); 21 | 22 | 23 | PublicDependencyModuleNames.AddRange( 24 | new string[] 25 | { 26 | "Core", 27 | "ProceduralMeshComponent", 28 | // ... add other public dependencies that you statically link with here ... 29 | } 30 | ); 31 | 32 | 33 | PrivateDependencyModuleNames.AddRange( 34 | new string[] 35 | { 36 | "CoreUObject", 37 | "Engine", 38 | "Slate", 39 | "SlateCore", 40 | // ... add private dependencies that you statically link with here ... 41 | } 42 | ); 43 | 44 | 45 | DynamicallyLoadedModuleNames.AddRange( 46 | new string[] 47 | { 48 | // ... add any modules that your module loads dynamically here ... 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/FWorldGenerator/FWorldGenerator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "FWorldGenerator.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FFWorldGeneratorModule" 6 | 7 | void FFWorldGeneratorModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FFWorldGeneratorModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FFWorldGeneratorModule, FWorldGenerator) -------------------------------------------------------------------------------- /Source/FWorldGenerator/FWorldGenerator.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FFWorldGeneratorModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; -------------------------------------------------------------------------------- /Source/FWorldGenerator/Private/FWGChunk.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the FWorldGenenerator. 2 | // Copyright Aleksandr "Flone" Tretyakov (github.com/Flone-dnb). 3 | // Licensed under the ZLib license. 4 | // Refer to the LICENSE file included. 5 | 6 | 7 | #include "FWGChunk.h" 8 | 9 | #include "FWGen.h" 10 | 11 | AFWGChunk::AFWGChunk() 12 | { 13 | pTriggerBox = CreateDefaultSubobject(MakeUniqueObjectName(this, UBoxComponent::StaticClass(), "Trigger")); 14 | pTriggerBox->SetupAttachment(RootComponent); 15 | pTriggerBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); 16 | pTriggerBox->BodyInstance.SetCollisionProfileName(TEXT("OverlapAll")); 17 | 18 | // need to have this on BeginPlay because here they can cause some problems when working with blueprints 19 | //pTriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AFWGChunk::OnBeginOverlap); 20 | //pTriggerBox->OnComponentEndOverlap.AddDynamic(this, &AFWGChunk::OnEndOverlap); 21 | } 22 | 23 | void AFWGChunk::BeginPlay() 24 | { 25 | Super::BeginPlay(); 26 | 27 | pTriggerBox->OnComponentBeginOverlap.AddDynamic(this, &AFWGChunk::OnBeginOverlap); 28 | //pTriggerBox->OnComponentEndOverlap.AddDynamic(this, &AFWGChunk::OnEndOverlap); 29 | } 30 | 31 | void AFWGChunk::setInit(long long ix, long long iy, int32 iSectionindex, bool bAroundcenter) 32 | { 33 | pMeshSection = nullptr; 34 | 35 | this->iX = ix; 36 | this->iY = iy; 37 | 38 | this->iSectionIndex = iSectionindex; 39 | 40 | this->bAroundCenter = bAroundcenter; 41 | } 42 | 43 | void AFWGChunk::setUpdate(long long ix, long long iy, bool bAroundcenter) 44 | { 45 | clearChunk(); 46 | 47 | this->iX = ix; 48 | this->iY = iy; 49 | 50 | this->bAroundCenter = bAroundcenter; 51 | } 52 | 53 | void AFWGChunk::setChunkSize(int32 iXCount, int32 iYCount) 54 | { 55 | vChunkCells.resize(iYCount); 56 | 57 | for (size_t i = 0; i < vChunkCells.size(); i++) 58 | { 59 | vChunkCells[i].resize(iXCount); 60 | } 61 | } 62 | 63 | void AFWGChunk::setChunkMap(FWGenChunkMap* pChunkmap) 64 | { 65 | this->pChunkMap = pChunkmap; 66 | } 67 | 68 | void AFWGChunk::setOverlapToActors(std::vector vClasses) 69 | { 70 | vClassesToOverlap = vClasses; 71 | } 72 | 73 | void AFWGChunk::clearChunk() 74 | { 75 | vVertices .Empty(); 76 | vTriangles .Empty(); 77 | vNormals .Empty(); 78 | vUV0 .Empty(); 79 | vVertexColors .Empty(); 80 | vTangents .Empty(); 81 | 82 | vLayerIndex.clear(); 83 | vChunkCells.clear(); 84 | } 85 | 86 | void AFWGChunk::setMeshSection(FProcMeshSection* pMeshsection) 87 | { 88 | this->pMeshSection = pMeshsection; 89 | } 90 | 91 | AFWGChunk::~AFWGChunk() 92 | { 93 | if (!pTriggerBox->IsValidLowLevel()) 94 | { 95 | return; 96 | } 97 | 98 | if (pTriggerBox->IsPendingKill()) 99 | { 100 | return; 101 | } 102 | 103 | pTriggerBox->DestroyComponent(); 104 | pTriggerBox = nullptr; 105 | } 106 | 107 | void AFWGChunk::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) 108 | { 109 | if ( (OtherActor != nullptr ) && (OtherActor != this) && ( OtherComp != nullptr ) ) 110 | { 111 | for (size_t i = 0; i < vClassesToOverlap.size(); i++) 112 | { 113 | if (OtherActor->GetClass()->GetName() == vClassesToOverlap[i]) 114 | { 115 | pChunkMap->setCurrentChunk(this); 116 | 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | 123 | //void AFWGChunk::OnEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) 124 | //{ 125 | // if ( (OtherActor != nullptr ) && (OtherActor != this) && ( OtherComp != nullptr ) ) 126 | // { 127 | // for (size_t i = 0; i < vClassesToOverlap.size(); i++) 128 | // { 129 | // if (OtherActor->GetClass()->GetName() == vClassesToOverlap[i]) 130 | // { 131 | // break; 132 | // } 133 | // } 134 | // } 135 | //} -------------------------------------------------------------------------------- /Source/FWorldGenerator/Private/FWGChunk.h: -------------------------------------------------------------------------------- 1 | // This file is part of the FWorldGenenerator. 2 | // Copyright Aleksandr "Flone" Tretyakov (github.com/Flone-dnb). 3 | // Licensed under the ZLib license. 4 | // Refer to the LICENSE file included. 5 | 6 | #pragma once 7 | 8 | #include "CoreMinimal.h" 9 | #include "GameFramework/Actor.h" 10 | 11 | // UE 12 | #include "ProceduralMeshComponent.h" 13 | #include "Components/BoxComponent.h" 14 | 15 | // STL 16 | #include 17 | 18 | #include "FWGChunk.generated.h" 19 | 20 | class FWGenChunkMap; 21 | 22 | UCLASS() 23 | class AFWGChunk : public AActor 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | 29 | AFWGChunk(); 30 | 31 | void setInit (long long ix, long long iy, int32 iSectionindex, bool bAroundcenter); 32 | void setUpdate (long long ix, long long iy, bool bAroundcenter); 33 | void setChunkSize (int32 iXCount, int32 iYCount); 34 | void setChunkMap (FWGenChunkMap* pChunkmap); 35 | void setOverlapToActors (std::vector vClasses); 36 | void setMeshSection (FProcMeshSection* pMeshsection); 37 | 38 | ~AFWGChunk(); 39 | 40 | 41 | UFUNCTION() 42 | void OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); 43 | 44 | /*UFUNCTION() 45 | void OnEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);*/ 46 | 47 | 48 | FProcMeshSection* pMeshSection; 49 | 50 | UPROPERTY() 51 | UBoxComponent* pTriggerBox; 52 | 53 | 54 | TArray vTangents; 55 | TArray vVertexColors; 56 | TArray vVertices; 57 | TArray vTriangles; 58 | TArray vNormals; 59 | TArray vUV0; 60 | 61 | 62 | std::vector vLayerIndex; 63 | std::vector> vChunkCells; 64 | 65 | 66 | size_t iMaxZVertexIndex; 67 | 68 | 69 | long long iX; 70 | long long iY; 71 | 72 | 73 | int32 iSectionIndex; 74 | 75 | protected: 76 | 77 | virtual void BeginPlay() override; 78 | 79 | void clearChunk (); 80 | 81 | 82 | std::vector vClassesToOverlap; 83 | 84 | FWGenChunkMap* pChunkMap; 85 | 86 | bool bAroundCenter; 87 | }; 88 | -------------------------------------------------------------------------------- /Source/FWorldGenerator/Private/FWGen.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of the FWorldGenenerator. 2 | // Copyright Aleksandr "Flone" Tretyakov (github.com/Flone-dnb). 3 | // Licensed under the ZLib license. 4 | // Refer to the LICENSE file included. 5 | 6 | #include "FWGen.h" 7 | 8 | // UE 9 | #include "Components/StaticMeshComponent.h" 10 | #include 11 | 12 | // STL 13 | #include 14 | #include 15 | 16 | // Custom 17 | #include "FWGChunk.h" 18 | 19 | // External 20 | #include "PerlinNoise.hpp" 21 | 22 | #if !UE_BUILD_SHIPPING 23 | #include "DrawDebugHelpers.h" 24 | #include 25 | #endif // WITH_EDITOR 26 | 27 | // -------------------------------------------------------------------------------------------------------- 28 | // -------------------------------------------------------------------------------------------------------- 29 | // -------------------------------------------------------------------------------------------------------- 30 | 31 | 32 | 33 | AFWGen::AFWGen() 34 | { 35 | PrimaryActorTick.bCanEverTick = false; 36 | bWorldCreated = false; 37 | 38 | 39 | iGeneratedSeed = 0; 40 | 41 | 42 | 43 | // Mesh structure 44 | 45 | pRootNode = CreateDefaultSubobject("Root"); 46 | RootComponent = pRootNode; 47 | 48 | pProcMeshComponent = CreateDefaultSubobject("ProcMeshComp"); 49 | //pProcMeshComponent->bUseAsyncCooking = true; // commented because the world should be generated after GenerateWorld() returned, and not calculate something after the function returned. 50 | //pProcMeshComponent->RegisterComponent(); 51 | pProcMeshComponent->SetupAttachment(RootComponent); 52 | 53 | // Enable collision 54 | pProcMeshComponent->ContainsPhysicsTriMeshData(true); 55 | 56 | 57 | 58 | pChunkMap = new FWGenChunkMap(this); 59 | pCallbackToDespawn = nullptr; 60 | 61 | 62 | 63 | 64 | // Water Plane 65 | 66 | WaterPlane = CreateDefaultSubobject("WaterPlane"); 67 | WaterPlane->SetupAttachment(RootComponent); 68 | WaterPlane->SetCollisionEnabled(ECollisionEnabled::NoCollision); 69 | static ConstructorHelpers::FObjectFinder PlaneMeshAsset(TEXT("StaticMesh'/Engine/BasicShapes/Plane.Plane'")); 70 | WaterPlane->SetStaticMesh(PlaneMeshAsset.Object); 71 | 72 | if (CreateWater == false) WaterPlane->SetVisibility(false); 73 | 74 | 75 | 76 | // Blocking volumes. 77 | pBlockingVolumeX1 = CreateDefaultSubobject("Blocking Volume X1"); 78 | pBlockingVolumeX1->SetupAttachment(RootComponent); 79 | pBlockingVolumeX1->BodyInstance.SetCollisionProfileName(TEXT("BlockAll")); 80 | 81 | pBlockingVolumeX2 = CreateDefaultSubobject("Blocking Volume X2"); 82 | pBlockingVolumeX2->SetupAttachment(RootComponent); 83 | pBlockingVolumeX2->BodyInstance.SetCollisionProfileName(TEXT("BlockAll")); 84 | 85 | pBlockingVolumeY1 = CreateDefaultSubobject("Blocking Volume Y1"); 86 | pBlockingVolumeY1->SetupAttachment(RootComponent); 87 | pBlockingVolumeY1->BodyInstance.SetCollisionProfileName(TEXT("BlockAll")); 88 | 89 | pBlockingVolumeY2 = CreateDefaultSubobject("Blocking Volume Y2"); 90 | pBlockingVolumeY2->SetupAttachment(RootComponent); 91 | pBlockingVolumeY2->BodyInstance.SetCollisionProfileName(TEXT("BlockAll")); 92 | 93 | 94 | 95 | // Materials 96 | 97 | WaterMaterial = nullptr; 98 | GroundMaterial = nullptr; 99 | 100 | 101 | 102 | // Preview Plane 103 | 104 | #if !UE_BUILD_SHIPPING 105 | PreviewPlane = CreateDefaultSubobject("PreviewPlane"); 106 | PreviewPlane->SetupAttachment(RootComponent); 107 | PreviewPlane->SetCollisionEnabled(ECollisionEnabled::NoCollision); 108 | 109 | refreshPreview(); 110 | 111 | if (ComplexPreview) GenerateWorld(); 112 | #endif // WITH_EDITOR 113 | } 114 | 115 | AFWGen::~AFWGen() 116 | { 117 | if (pChunkMap) 118 | { 119 | delete pChunkMap; 120 | } 121 | 122 | 123 | 124 | 125 | if (pProcMeshComponent->IsValidLowLevel()) 126 | { 127 | if (pProcMeshComponent->IsPendingKill() == false) 128 | { 129 | pProcMeshComponent->DestroyComponent(); 130 | } 131 | } 132 | 133 | 134 | 135 | if (pBlockingVolumeX1->IsValidLowLevel()) 136 | { 137 | if (pBlockingVolumeX1->IsPendingKill() == false) 138 | { 139 | pBlockingVolumeX1->DestroyComponent(); 140 | } 141 | } 142 | 143 | 144 | 145 | if (pBlockingVolumeX2->IsValidLowLevel()) 146 | { 147 | if (pBlockingVolumeX2->IsPendingKill() == false) 148 | { 149 | pBlockingVolumeX2->DestroyComponent(); 150 | } 151 | } 152 | 153 | 154 | 155 | if (pBlockingVolumeY1->IsValidLowLevel()) 156 | { 157 | if (pBlockingVolumeY1->IsPendingKill() == false) 158 | { 159 | pBlockingVolumeY1->DestroyComponent(); 160 | } 161 | } 162 | 163 | 164 | 165 | if (pBlockingVolumeY2->IsValidLowLevel()) 166 | { 167 | if (pBlockingVolumeY2->IsPendingKill() == false) 168 | { 169 | pBlockingVolumeY2->DestroyComponent(); 170 | } 171 | } 172 | 173 | 174 | if (pCallbackToDespawn) 175 | { 176 | delete pCallbackToDespawn; 177 | } 178 | } 179 | 180 | bool AFWGen::BindFunctionToSpawn(UObject* FunctionOwner, FString FunctionName, float Layer, float ProbabilityToSpawn, bool IsBlocking) 181 | { 182 | FWGCallback callback; 183 | 184 | callback.pOwner = FunctionOwner; 185 | callback.fProbabilityToSpawn = ProbabilityToSpawn; 186 | callback.bIsBlocking = IsBlocking; 187 | callback.fLayer = Layer; 188 | 189 | for ( TFieldIterator FIT ( FunctionOwner->GetClass(), EFieldIteratorFlags::IncludeSuper ); FIT; ++FIT) 190 | { 191 | UFunction* Function = *FIT; 192 | if (Function->GetName() == FunctionName) 193 | { 194 | callback.pFunction = Function; 195 | 196 | vObjectsToSpawn.push_back(callback); 197 | 198 | return false; 199 | } 200 | } 201 | 202 | return true; 203 | } 204 | 205 | bool AFWGen::BindFunctionToDespawnActors(UObject* FunctionOwner, FString FunctionName) 206 | { 207 | for ( TFieldIterator FIT ( FunctionOwner->GetClass(), EFieldIteratorFlags::IncludeSuper ); FIT; ++FIT) 208 | { 209 | UFunction* Function = *FIT; 210 | if (Function->GetName() == FunctionName) 211 | { 212 | if (pCallbackToDespawn == nullptr) 213 | { 214 | pCallbackToDespawn = new FWGCallback(); 215 | } 216 | 217 | pCallbackToDespawn->pOwner = FunctionOwner; 218 | pCallbackToDespawn->sFunctionName = FunctionName; 219 | pCallbackToDespawn->pFunction = Function; 220 | 221 | return false; 222 | } 223 | } 224 | 225 | return true; 226 | } 227 | 228 | void AFWGen::UnBindFunctionToSpawn(FString FunctionName) 229 | { 230 | for (size_t i = 0; i < vObjectsToSpawn.size(); i++) 231 | { 232 | if (vObjectsToSpawn[i].sFunctionName == FunctionName) 233 | { 234 | vObjectsToSpawn.erase( vObjectsToSpawn.begin() + i); 235 | break; 236 | } 237 | } 238 | } 239 | 240 | void AFWGen::AddOverlapToActorClass(UClass* OverlapToClass) 241 | { 242 | vOverlapToClasses.push_back(OverlapToClass->GetName()); 243 | } 244 | 245 | void AFWGen::RemoveOverlapToActorClass(UClass* OverlapToClass) 246 | { 247 | for (size_t i = 0; i < vOverlapToClasses.size(); i++) 248 | { 249 | if (vOverlapToClasses[i] == OverlapToClass->GetName()) 250 | { 251 | vOverlapToClasses.erase( vOverlapToClasses.begin() + i); 252 | break; 253 | } 254 | } 255 | } 256 | 257 | FVector AFWGen::GetFreeCellLocation(float Layer, bool SetBlocking) 258 | { 259 | FVector location; 260 | 261 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 262 | { 263 | float fChunkX = GetActorLocation().X; 264 | float fChunkY = GetActorLocation().Y; 265 | 266 | if (pChunkMap->vChunks[i]->iX != 0) 267 | { 268 | fChunkX += (pChunkMap->vChunks[i]->iX * ChunkPieceColumnCount * ChunkPieceSizeX); 269 | } 270 | 271 | if (pChunkMap->vChunks[i]->iY != 0) 272 | { 273 | fChunkY += (pChunkMap->vChunks[i]->iY * ChunkPieceRowCount * ChunkPieceSizeY); 274 | } 275 | 276 | float fStartX = fChunkX - (ChunkPieceColumnCount * ChunkPieceSizeX) / 2; 277 | float fStartY = fChunkY - (ChunkPieceRowCount * ChunkPieceSizeY) / 2; 278 | 279 | float fXCellSize = ChunkPieceColumnCount * ChunkPieceSizeX / DivideChunkXCount; 280 | float fYCellSize = ChunkPieceRowCount * ChunkPieceSizeY / DivideChunkYCount; 281 | 282 | 283 | for (size_t y = 0; y < pChunkMap->vChunks[i]->vChunkCells.size(); y++) 284 | { 285 | for (size_t x = 0; x < pChunkMap->vChunks[i]->vChunkCells[y].size(); x++) 286 | { 287 | if (pChunkMap->vChunks[i]->vChunkCells[y][x]) 288 | { 289 | continue; 290 | } 291 | 292 | location.X = fStartX + x * fXCellSize + fXCellSize / 2; 293 | location.Y = fStartY + y * fYCellSize + fYCellSize / 2; 294 | location.Z = GetActorLocation().Z; 295 | 296 | FHitResult OutHit; 297 | FVector TraceStart(location.X, location.Y, GetActorLocation().Z + GenerationMaxZFromActorZ + 5.0f); 298 | FVector TraceEnd(location.X, location.Y, GetActorLocation().Z - 5.0f); 299 | FCollisionQueryParams CollisionParams; 300 | 301 | // Get Z. 302 | if (GetWorld()->LineTraceSingleByChannel(OutHit, TraceStart, TraceEnd, ECC_Visibility, CollisionParams)) 303 | { 304 | if (OutHit.bBlockingHit) 305 | { 306 | location.Z = OutHit.ImpactPoint.Z; 307 | } 308 | } 309 | 310 | if ( CreateWater && (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * ZWaterLevelInWorld 311 | + GenerationMaxZFromActorZ * 0.01f)) // error 312 | { 313 | // Water layer. 314 | 315 | if (areEqual(Layer, -0.5f, 0.1f)) 316 | { 317 | if (SetBlocking) 318 | { 319 | pChunkMap->vChunks[i]->vChunkCells[y][x] = true; 320 | } 321 | 322 | return location; 323 | } 324 | } 325 | else if (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * FirstMaterialMaxRelativeHeight) 326 | { 327 | // First layer. 328 | 329 | if (areEqual(Layer, 0.0f, 0.1f)) 330 | { 331 | if (SetBlocking) 332 | { 333 | pChunkMap->vChunks[i]->vChunkCells[y][x] = true; 334 | } 335 | 336 | return location; 337 | } 338 | } 339 | else if (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * SecondMaterialMaxRelativeHeight) 340 | { 341 | // Second layer. 342 | 343 | if (areEqual(Layer, 0.5f, 0.1f)) 344 | { 345 | if (SetBlocking) 346 | { 347 | pChunkMap->vChunks[i]->vChunkCells[y][x] = true; 348 | } 349 | 350 | return location; 351 | } 352 | } 353 | else 354 | { 355 | // Third layer. 356 | 357 | if (areEqual(Layer, 1.0f, 0.1f)) 358 | { 359 | if (SetBlocking) 360 | { 361 | pChunkMap->vChunks[i]->vChunkCells[y][x] = true; 362 | } 363 | 364 | return location; 365 | } 366 | } 367 | } 368 | } 369 | } 370 | 371 | location = FVector(0, 0, 0); 372 | return location; 373 | } 374 | 375 | void AFWGen::SaveWorldParamsToFile(FString PathToFile) 376 | { 377 | PathToFile.Append(L".fwgs"); 378 | 379 | std::ofstream saveFile(TCHAR_TO_UTF16(*PathToFile)); 380 | 381 | // Version. 382 | std::string sVersion = FWGEN_VERSION; 383 | char vVersionBuffer[VERSION_SIZE]; 384 | memset(vVersionBuffer, 0, VERSION_SIZE); 385 | std::memcpy(vVersionBuffer, sVersion.c_str(), sVersion.size()); 386 | 387 | saveFile.write(vVersionBuffer, VERSION_SIZE); 388 | 389 | // Chunks. 390 | saveFile.write(reinterpret_cast(&ChunkPieceRowCount), sizeof(ChunkPieceRowCount)); 391 | saveFile.write(reinterpret_cast(&ChunkPieceColumnCount), sizeof(ChunkPieceColumnCount)); 392 | saveFile.write(reinterpret_cast(&ChunkPieceSizeX), sizeof(ChunkPieceSizeX)); 393 | saveFile.write(reinterpret_cast(&ChunkPieceSizeY), sizeof(ChunkPieceSizeY)); 394 | saveFile.write(reinterpret_cast(&ViewDistance), sizeof(ViewDistance)); 395 | saveFile.write(reinterpret_cast(&LoadUnloadChunkMaxZ), sizeof(LoadUnloadChunkMaxZ)); 396 | 397 | // Generation. 398 | saveFile.write(reinterpret_cast(&GenerationFrequency), sizeof(GenerationFrequency)); 399 | saveFile.write(reinterpret_cast(&GenerationOctaves), sizeof(GenerationOctaves)); 400 | saveFile.write(reinterpret_cast(&GenerationSeed), sizeof(GenerationSeed)); 401 | saveFile.write(reinterpret_cast(&GenerationMaxZFromActorZ), sizeof(GenerationMaxZFromActorZ)); 402 | saveFile.write(reinterpret_cast(&InvertWorld), sizeof(InvertWorld)); 403 | 404 | // World. 405 | saveFile.write(reinterpret_cast(&WorldSize), sizeof(WorldSize)); 406 | 407 | // Ground. 408 | saveFile.write(reinterpret_cast(&FirstMaterialMaxRelativeHeight), sizeof(FirstMaterialMaxRelativeHeight)); 409 | saveFile.write(reinterpret_cast(&SecondMaterialMaxRelativeHeight), sizeof(SecondMaterialMaxRelativeHeight)); 410 | saveFile.write(reinterpret_cast(&MaterialHeightMaxDeviation), sizeof(MaterialHeightMaxDeviation)); 411 | saveFile.write(reinterpret_cast(&TerrainCutHeightFromActorZ), sizeof(TerrainCutHeightFromActorZ)); 412 | 413 | // Water. 414 | saveFile.write(reinterpret_cast(&CreateWater), sizeof(CreateWater)); 415 | saveFile.write(reinterpret_cast(&SecondMaterialUnderWater), sizeof(SecondMaterialUnderWater)); 416 | saveFile.write(reinterpret_cast(&ZWaterLevelInWorld), sizeof(ZWaterLevelInWorld)); 417 | saveFile.write(reinterpret_cast(&WaterSize), sizeof(WaterSize)); 418 | 419 | // Additional Steps. 420 | saveFile.write(reinterpret_cast(&ApplyGroundMaterialBlend), sizeof(ApplyGroundMaterialBlend)); 421 | saveFile.write(reinterpret_cast(&ApplySlopeDependentBlend), sizeof(ApplySlopeDependentBlend)); 422 | 423 | // Ground Material Blend. 424 | saveFile.write(reinterpret_cast(&FirstMaterialOnSecondProbability), sizeof(FirstMaterialOnSecondProbability)); 425 | saveFile.write(reinterpret_cast(&FirstMaterialOnThirdProbability), sizeof(FirstMaterialOnThirdProbability)); 426 | saveFile.write(reinterpret_cast(&SecondMaterialOnFirstProbability), sizeof(SecondMaterialOnFirstProbability)); 427 | saveFile.write(reinterpret_cast(&SecondMaterialOnThirdProbability), sizeof(SecondMaterialOnThirdProbability)); 428 | saveFile.write(reinterpret_cast(&ThirdMaterialOnFirstProbability), sizeof(ThirdMaterialOnFirstProbability)); 429 | saveFile.write(reinterpret_cast(&ThirdMaterialOnSecondProbability), sizeof(ThirdMaterialOnSecondProbability)); 430 | saveFile.write(reinterpret_cast(&IncreasedMaterialBlendProbability), sizeof(IncreasedMaterialBlendProbability)); 431 | 432 | // Slope Dependent Blend. 433 | saveFile.write(reinterpret_cast(&MinSlopeHeightMultiplier), sizeof(MinSlopeHeightMultiplier)); 434 | 435 | // Spawning Objects. 436 | saveFile.write(reinterpret_cast(&DivideChunkXCount), sizeof(DivideChunkXCount)); 437 | saveFile.write(reinterpret_cast(&DivideChunkYCount), sizeof(DivideChunkYCount)); 438 | saveFile.write(reinterpret_cast(&MaxOffsetByX), sizeof(MaxOffsetByX)); 439 | saveFile.write(reinterpret_cast(&MaxOffsetByY), sizeof(MaxOffsetByY)); 440 | saveFile.write(reinterpret_cast(&MaxRotation), sizeof(MaxRotation)); 441 | saveFile.write(reinterpret_cast(&MaxZDiffInCell), sizeof(MaxZDiffInCell)); 442 | 443 | saveFile.close(); 444 | 445 | #if !UE_BUILD_SHIPPING 446 | LastSaveLoadOperationStatus = true; 447 | #endif 448 | } 449 | 450 | void AFWGen::LoadWorldParamsFromFile(FString PathToFile) 451 | { 452 | std::ifstream readFile(TCHAR_TO_UTF16(*PathToFile)); 453 | 454 | if (readFile.is_open() == false) 455 | { 456 | #if !UE_BUILD_SHIPPING 457 | LastSaveLoadOperationStatus = false; 458 | #endif 459 | return; 460 | } 461 | 462 | // Version. 463 | char vVersionBuffer[VERSION_SIZE]; 464 | memset(vVersionBuffer, 0, VERSION_SIZE); 465 | 466 | readFile.read(vVersionBuffer, VERSION_SIZE); 467 | 468 | 469 | std::string sVersion(vVersionBuffer); 470 | 471 | if (sVersion.substr(0, 3) != "FWG") 472 | { 473 | #if !UE_BUILD_SHIPPING 474 | LastSaveLoadOperationStatus = false; 475 | #endif 476 | return; 477 | } 478 | 479 | 480 | // Chunks. 481 | readFile.read(reinterpret_cast(&ChunkPieceRowCount), sizeof(ChunkPieceRowCount)); 482 | readFile.read(reinterpret_cast(&ChunkPieceColumnCount), sizeof(ChunkPieceColumnCount)); 483 | readFile.read(reinterpret_cast(&ChunkPieceSizeX), sizeof(ChunkPieceSizeX)); 484 | readFile.read(reinterpret_cast(&ChunkPieceSizeY), sizeof(ChunkPieceSizeY)); 485 | readFile.read(reinterpret_cast(&ViewDistance), sizeof(ViewDistance)); 486 | readFile.read(reinterpret_cast(&LoadUnloadChunkMaxZ), sizeof(LoadUnloadChunkMaxZ)); 487 | 488 | // Generation. 489 | readFile.read(reinterpret_cast(&GenerationFrequency), sizeof(GenerationFrequency)); 490 | readFile.read(reinterpret_cast(&GenerationOctaves), sizeof(GenerationOctaves)); 491 | readFile.read(reinterpret_cast(&GenerationSeed), sizeof(GenerationSeed)); 492 | readFile.read(reinterpret_cast(&GenerationMaxZFromActorZ), sizeof(GenerationMaxZFromActorZ)); 493 | readFile.read(reinterpret_cast(&InvertWorld), sizeof(InvertWorld)); 494 | 495 | // World. 496 | readFile.read(reinterpret_cast(&WorldSize), sizeof(WorldSize)); 497 | 498 | // Ground. 499 | readFile.read(reinterpret_cast(&FirstMaterialMaxRelativeHeight), sizeof(FirstMaterialMaxRelativeHeight)); 500 | readFile.read(reinterpret_cast(&SecondMaterialMaxRelativeHeight), sizeof(SecondMaterialMaxRelativeHeight)); 501 | readFile.read(reinterpret_cast(&MaterialHeightMaxDeviation), sizeof(MaterialHeightMaxDeviation)); 502 | readFile.read(reinterpret_cast(&TerrainCutHeightFromActorZ), sizeof(TerrainCutHeightFromActorZ)); 503 | 504 | // Water. 505 | readFile.read(reinterpret_cast(&CreateWater), sizeof(CreateWater)); 506 | readFile.read(reinterpret_cast(&SecondMaterialUnderWater), sizeof(SecondMaterialUnderWater)); 507 | readFile.read(reinterpret_cast(&ZWaterLevelInWorld), sizeof(ZWaterLevelInWorld)); 508 | readFile.read(reinterpret_cast(&WaterSize), sizeof(WaterSize)); 509 | 510 | // Additional Steps. 511 | readFile.read(reinterpret_cast(&ApplyGroundMaterialBlend), sizeof(ApplyGroundMaterialBlend)); 512 | readFile.read(reinterpret_cast(&ApplySlopeDependentBlend), sizeof(ApplySlopeDependentBlend)); 513 | 514 | // Ground Material Blend. 515 | readFile.read(reinterpret_cast(&FirstMaterialOnSecondProbability), sizeof(FirstMaterialOnSecondProbability)); 516 | readFile.read(reinterpret_cast(&FirstMaterialOnThirdProbability), sizeof(FirstMaterialOnThirdProbability)); 517 | readFile.read(reinterpret_cast(&SecondMaterialOnFirstProbability), sizeof(SecondMaterialOnFirstProbability)); 518 | readFile.read(reinterpret_cast(&SecondMaterialOnThirdProbability), sizeof(SecondMaterialOnThirdProbability)); 519 | readFile.read(reinterpret_cast(&ThirdMaterialOnFirstProbability), sizeof(ThirdMaterialOnFirstProbability)); 520 | readFile.read(reinterpret_cast(&ThirdMaterialOnSecondProbability), sizeof(ThirdMaterialOnSecondProbability)); 521 | readFile.read(reinterpret_cast(&IncreasedMaterialBlendProbability), sizeof(IncreasedMaterialBlendProbability)); 522 | 523 | // Slope Dependent Blend. 524 | readFile.read(reinterpret_cast(&MinSlopeHeightMultiplier), sizeof(MinSlopeHeightMultiplier)); 525 | 526 | // Spawning Objects. 527 | readFile.read(reinterpret_cast(&DivideChunkXCount), sizeof(DivideChunkXCount)); 528 | readFile.read(reinterpret_cast(&DivideChunkYCount), sizeof(DivideChunkYCount)); 529 | readFile.read(reinterpret_cast(&MaxOffsetByX), sizeof(MaxOffsetByX)); 530 | readFile.read(reinterpret_cast(&MaxOffsetByY), sizeof(MaxOffsetByY)); 531 | readFile.read(reinterpret_cast(&MaxRotation), sizeof(MaxRotation)); 532 | readFile.read(reinterpret_cast(&MaxZDiffInCell), sizeof(MaxZDiffInCell)); 533 | 534 | readFile.close(); 535 | 536 | #if !UE_BUILD_SHIPPING 537 | LastSaveLoadOperationStatus = true; 538 | #endif 539 | } 540 | 541 | void AFWGen::GenerateWorld(int64 iCentralChunkX, int64 iCentralChunkY, AActor* pCharacter) 542 | { 543 | if (pChunkMap) 544 | { 545 | pChunkMap->clearWorld(pProcMeshComponent); 546 | } 547 | 548 | 549 | generateSeed(); 550 | 551 | if (WorldSize != -1) 552 | { 553 | size_t iChunkCount = (ViewDistance * 2 + 1) * (ViewDistance * 2 + 1); 554 | 555 | 556 | // Generate the chunks. 557 | 558 | int32 iSectionIndex = 0; 559 | 560 | 561 | for (long long y = iCentralChunkY + ViewDistance; y > iCentralChunkY - ViewDistance - 1; y--) 562 | { 563 | for (long long x = iCentralChunkX - ViewDistance; x < iCentralChunkX + ViewDistance + 1; x++) 564 | { 565 | bool bAroundCenter = false; 566 | 567 | if ((x <= iCentralChunkX + 1) && (x >= iCentralChunkX - 1) && (y <= iCentralChunkY + 1) && (y >= iCentralChunkY - 1)) 568 | { 569 | bAroundCenter = true; 570 | } 571 | 572 | AFWGChunk* pNewChunk = generateChunk(x, y, iSectionIndex, bAroundCenter); 573 | 574 | pChunkMap->addChunk(pNewChunk); 575 | 576 | if (x == iCentralChunkX && y == iCentralChunkY) 577 | { 578 | pChunkMap->setCurrentChunk(pNewChunk); 579 | } 580 | 581 | iSectionIndex++; 582 | } 583 | } 584 | 585 | iCurrentSectionIndex = iSectionIndex; 586 | } 587 | else 588 | { 589 | AFWGChunk* pNewChunk = generateChunk(iCentralChunkX, iCentralChunkY, 0, false); 590 | 591 | pChunkMap->addChunk(pNewChunk); 592 | 593 | iCurrentSectionIndex = 1; 594 | } 595 | 596 | if (ApplyGroundMaterialBlend) 597 | { 598 | blendWorldMaterialsMore(); 599 | } 600 | 601 | if (ApplySlopeDependentBlend) 602 | { 603 | applySlopeDependentBlend(); 604 | } 605 | 606 | // Update mesh. 607 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 608 | { 609 | pProcMeshComponent->UpdateMeshSection_LinearColor(i, pChunkMap->vChunks[i]->vVertices, pChunkMap->vChunks[i]->vNormals, 610 | pChunkMap->vChunks[i]->vUV0, pChunkMap->vChunks[i]->vVertexColors, pChunkMap->vChunks[i]->vTangents); 611 | } 612 | 613 | 614 | 615 | // Water Plane 616 | 617 | WaterPlane->SetWorldLocation(FVector( 618 | GetActorLocation().X, 619 | GetActorLocation().Y, 620 | GetActorLocation().Z + (GenerationMaxZFromActorZ * ZWaterLevelInWorld) 621 | )); 622 | 623 | WaterPlane->SetWorldScale3D(FVector( 624 | (ChunkPieceColumnCount*ChunkPieceSizeX) * (WaterSize / 100.0f), 625 | (ChunkPieceRowCount*ChunkPieceSizeY) * (WaterSize / 100.0f), 626 | 0.1f 627 | )); 628 | 629 | if (WaterMaterial) 630 | { 631 | WaterPlane->SetMaterial(0, WaterMaterial); 632 | } 633 | 634 | if (CreateWater) 635 | { 636 | WaterPlane->SetVisibility(true); 637 | } 638 | else 639 | { 640 | WaterPlane->SetVisibility(false); 641 | } 642 | 643 | 644 | spawnObjects(); 645 | 646 | if (iCentralChunkX != 0 || iCentralChunkY != 0) 647 | { 648 | float fChunkX = GetActorLocation().X; 649 | float fChunkY = GetActorLocation().Y; 650 | 651 | if (GetCentralChunkX() != 0) 652 | { 653 | // Left or right chunk 654 | 655 | fChunkX += (GetCentralChunkX() * ChunkPieceColumnCount * ChunkPieceSizeX); 656 | } 657 | 658 | if (GetCentralChunkY() != 0) 659 | { 660 | // Top or bottom chunk 661 | 662 | fChunkY += (GetCentralChunkY() * ChunkPieceRowCount * ChunkPieceSizeY); 663 | } 664 | 665 | fChunkX += ChunkPieceColumnCount * ChunkPieceSizeX / 2; 666 | fChunkY += ChunkPieceRowCount * ChunkPieceSizeY / 2; 667 | 668 | if (pCharacter) 669 | { 670 | pCharacter->SetActorLocation(FVector(fChunkX, fChunkY, pCharacter->GetActorLocation().Z)); 671 | } 672 | } 673 | else if (iCentralChunkX == 0 && iCentralChunkY == 0) 674 | { 675 | if (pCharacter) 676 | { 677 | pCharacter->SetActorLocation(FVector(GetCentralChunkX(), GetCentralChunkY(), pCharacter->GetActorLocation().Z)); 678 | } 679 | } 680 | 681 | if (WorldSize != -1) 682 | { 683 | // Create triggers. 684 | 685 | #if !UE_BUILD_SHIPPING 686 | FlushPersistentDebugLines(GetWorld()); 687 | #endif // WITH_EDITOR 688 | 689 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 690 | { 691 | float fTriggerX = GetActorLocation().X; 692 | float fTriggerY = GetActorLocation().Y; 693 | float fTriggerZ = GetActorLocation().Z + LoadUnloadChunkMaxZ / 2; 694 | 695 | if (pChunkMap->vChunks[i]->iX != 0) 696 | { 697 | // Left or right chunk 698 | 699 | fTriggerX += (pChunkMap->vChunks[i]->iX * ChunkPieceColumnCount * ChunkPieceSizeX); 700 | } 701 | 702 | if (pChunkMap->vChunks[i]->iY != 0) 703 | { 704 | // Top or bottom chunk 705 | 706 | fTriggerY += (pChunkMap->vChunks[i]->iY * ChunkPieceRowCount * ChunkPieceSizeY); 707 | } 708 | 709 | createTriggerBoxForChunk(pChunkMap->vChunks[i]); 710 | 711 | #if !UE_BUILD_SHIPPING 712 | if (DrawChunkBounds) 713 | { 714 | DrawDebugBox(GetWorld(), FVector(fTriggerX, fTriggerY, fTriggerZ), FVector ( 715 | (ChunkPieceColumnCount * ChunkPieceSizeX / 2), 716 | (ChunkPieceRowCount * ChunkPieceSizeY / 2), 717 | LoadUnloadChunkMaxZ / 2), 718 | FColor::Red, true); 719 | } 720 | #endif // WITH_EDITOR 721 | } 722 | 723 | float fChunkXSize = ChunkPieceColumnCount * ChunkPieceSizeX; 724 | float fChunkYSize = ChunkPieceRowCount * ChunkPieceSizeY; 725 | 726 | pBlockingVolumeX1->SetWorldLocation(FVector(GetActorLocation().X - (ViewDistance + 1) * fChunkXSize, 727 | GetActorLocation().Y, 728 | GetActorLocation().Z + LoadUnloadChunkMaxZ / 2)); 729 | 730 | pBlockingVolumeX1->SetBoxExtent(FVector(ChunkPieceColumnCount * ChunkPieceSizeX / 2, 731 | (ChunkPieceRowCount * ChunkPieceSizeY / 2) * (ViewDistance * 3), 732 | LoadUnloadChunkMaxZ / 2)); 733 | 734 | pBlockingVolumeX1->SetGenerateOverlapEvents(true); 735 | 736 | 737 | 738 | pBlockingVolumeX2->SetWorldLocation(FVector(GetActorLocation().X + (ViewDistance + 1) * fChunkXSize, 739 | GetActorLocation().Y, 740 | GetActorLocation().Z + LoadUnloadChunkMaxZ / 2)); 741 | 742 | pBlockingVolumeX2->SetBoxExtent(FVector(ChunkPieceColumnCount * ChunkPieceSizeX / 2, 743 | (ChunkPieceRowCount * ChunkPieceSizeY / 2) * (ViewDistance * 3), 744 | LoadUnloadChunkMaxZ / 2)); 745 | 746 | pBlockingVolumeX2->SetGenerateOverlapEvents(true); 747 | 748 | 749 | 750 | pBlockingVolumeY1->SetWorldLocation(FVector(GetActorLocation().X, 751 | GetActorLocation().Y - (ViewDistance + 1) * fChunkYSize, 752 | GetActorLocation().Z + LoadUnloadChunkMaxZ / 2)); 753 | 754 | pBlockingVolumeY1->SetBoxExtent(FVector((ChunkPieceColumnCount * ChunkPieceSizeX / 2) * (ViewDistance * 3), 755 | ChunkPieceRowCount * ChunkPieceSizeY / 2, 756 | LoadUnloadChunkMaxZ / 2)); 757 | 758 | pBlockingVolumeY1->SetGenerateOverlapEvents(true); 759 | 760 | 761 | 762 | pBlockingVolumeY2->SetWorldLocation(FVector(GetActorLocation().X, 763 | GetActorLocation().Y + (ViewDistance + 1) * fChunkYSize, 764 | GetActorLocation().Z + LoadUnloadChunkMaxZ / 2)); 765 | 766 | pBlockingVolumeY2->SetBoxExtent(FVector((ChunkPieceColumnCount * ChunkPieceSizeX / 2) * (ViewDistance * 3), 767 | ChunkPieceRowCount * ChunkPieceSizeY / 2, 768 | LoadUnloadChunkMaxZ / 2)); 769 | 770 | pBlockingVolumeY2->SetGenerateOverlapEvents(true); 771 | } 772 | } 773 | 774 | long long AFWGen::GetCentralChunkX() 775 | { 776 | return pChunkMap->getCentralChunkX(); 777 | } 778 | 779 | long long AFWGen::GetCentralChunkY() 780 | { 781 | return pChunkMap->getCentralChunkY(); 782 | } 783 | 784 | int64 AFWGen::GetChunkXByLocation(FVector Location) 785 | { 786 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 787 | { 788 | float fChunkX = GetActorLocation().X; 789 | 790 | if (pChunkMap->vChunks[i]->iX != 0) 791 | { 792 | fChunkX += (pChunkMap->vChunks[i]->iX * ChunkPieceColumnCount * ChunkPieceSizeX); 793 | } 794 | 795 | if (Location.X >= fChunkX && Location.X <= fChunkX + ChunkPieceColumnCount * ChunkPieceSizeX) 796 | { 797 | return pChunkMap->vChunks[i]->iX; 798 | } 799 | } 800 | 801 | return 0; 802 | } 803 | 804 | int64 AFWGen::GetChunkYByLocation(FVector Location) 805 | { 806 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 807 | { 808 | float fChunkY = GetActorLocation().Y; 809 | 810 | if (pChunkMap->vChunks[i]->iY != 0) 811 | { 812 | fChunkY += (pChunkMap->vChunks[i]->iY * ChunkPieceRowCount * ChunkPieceSizeY); 813 | } 814 | 815 | if (Location.Y >= fChunkY && Location.Y >= fChunkY + ChunkPieceRowCount * ChunkPieceSizeY) 816 | { 817 | return pChunkMap->vChunks[i]->iY; 818 | } 819 | } 820 | 821 | return 0; 822 | } 823 | 824 | void AFWGen::blendWorldMaterialsMore(AFWGChunk* pOnlyForThisChunk) 825 | { 826 | std::mt19937_64 gen(std::random_device{}()); 827 | std::uniform_real_distribution urd(0.0f, 1.0f); 828 | 829 | std::vector vChunkToProcess; 830 | 831 | if (pOnlyForThisChunk) 832 | { 833 | vChunkToProcess.push_back(pOnlyForThisChunk); 834 | } 835 | else 836 | { 837 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 838 | { 839 | vChunkToProcess.push_back(pChunkMap->vChunks[i]); 840 | } 841 | } 842 | 843 | 844 | for (size_t i = 0; i < vChunkToProcess.size(); i++) 845 | { 846 | size_t iVertexIndex = 0; 847 | 848 | for (size_t iRow = 0; iRow < ChunkPieceRowCount + 1; iRow++) 849 | { 850 | iVertexIndex = (ChunkPieceColumnCount + 1) * iRow; 851 | 852 | size_t iSkipPoints = 0; 853 | 854 | for (size_t iColumn = 0; iColumn < ChunkPieceColumnCount + 1; iColumn++) 855 | { 856 | if (iSkipPoints > 0) 857 | { 858 | iSkipPoints--; 859 | iVertexIndex++; 860 | 861 | continue; 862 | } 863 | 864 | if ((iRow >= 3) && ((iRow + 3) < (ChunkPieceRowCount + 1)) 865 | && (iColumn >= 3) && ((iColumn + 3) < (ChunkPieceColumnCount + 1))) 866 | { 867 | if (SecondMaterialUnderWater && 868 | (vChunkToProcess[i]->vVertices[iVertexIndex].Z <= (GetActorLocation().Z + (GenerationMaxZFromActorZ * (ZWaterLevelInWorld + 0.008f))))) 869 | { 870 | // Under water material - don't touch. 871 | } 872 | else 873 | { 874 | bool bFirstLayerWithOtherColor = false; 875 | bool bSecondLayerWithOtherColor = false; 876 | bool bThirdLayerWithOtherColor = false; 877 | 878 | if ((vChunkToProcess[i]->vLayerIndex[iVertexIndex] == 1) 879 | && (areEqual(vChunkToProcess[i]->vVertexColors[iVertexIndex].A, 0.0f, 0.1f) == false)) 880 | { 881 | bFirstLayerWithOtherColor = true; 882 | } 883 | else if ((vChunkToProcess[i]->vLayerIndex[iVertexIndex] == 2) 884 | && (areEqual(vChunkToProcess[i]->vVertexColors[iVertexIndex].A, 0.5f, 0.1f) == false)) 885 | { 886 | bSecondLayerWithOtherColor = true; 887 | } 888 | else if ((vChunkToProcess[i]->vLayerIndex[iVertexIndex] == 3) 889 | && (areEqual(vChunkToProcess[i]->vVertexColors[iVertexIndex].A, 1.0f, 0.1f) == false)) 890 | { 891 | bThirdLayerWithOtherColor = true; 892 | } 893 | 894 | 895 | if (bFirstLayerWithOtherColor || bSecondLayerWithOtherColor || bThirdLayerWithOtherColor) 896 | { 897 | // Add a little more of other color. 898 | 899 | // . . . . . 900 | // . . . . . 901 | // . . + . . <- we are in the center. 902 | // . . . . . 903 | // . . . . . 904 | 905 | // Close square: 906 | 907 | for (int32 iIndexY = -(ChunkPieceColumnCount + 1); iIndexY <= (ChunkPieceColumnCount + 1); iIndexY += (ChunkPieceColumnCount + 1)) 908 | { 909 | for (int32 iIndexX = -1; iIndexX <= 1; iIndexX++) 910 | { 911 | if (urd(gen) <= IncreasedMaterialBlendProbability) 912 | { 913 | vChunkToProcess[i]->vVertexColors[iVertexIndex + iIndexY + iIndexX] = vChunkToProcess[i]->vVertexColors[iVertexIndex]; 914 | } 915 | } 916 | } 917 | 918 | // Far square: 919 | 920 | for (int32 iIndexY = -((ChunkPieceColumnCount + 1) * 2 - 1); iIndexY <= ((ChunkPieceColumnCount + 1) * 2 - 1); iIndexY += (ChunkPieceColumnCount + 1)) 921 | { 922 | for (int32 iIndexX = -2; iIndexX <= 2; iIndexX++) 923 | { 924 | if (urd(gen) <= (IncreasedMaterialBlendProbability / 2)) 925 | { 926 | vChunkToProcess[i]->vVertexColors[iVertexIndex + iIndexY + iIndexX] = vChunkToProcess[i]->vVertexColors[iVertexIndex]; 927 | } 928 | } 929 | } 930 | 931 | iSkipPoints = 2; 932 | } 933 | } 934 | } 935 | 936 | iVertexIndex++; 937 | } 938 | } 939 | } 940 | } 941 | 942 | void AFWGen::applySlopeDependentBlend(AFWGChunk* pOnlyForThisChunk) 943 | { 944 | float fSteepSlopeMinHeightDiff = GenerationMaxZFromActorZ * MinSlopeHeightMultiplier; 945 | 946 | std::vector vChunksToProcess; 947 | 948 | if (pOnlyForThisChunk) 949 | { 950 | vChunksToProcess.push_back(pOnlyForThisChunk); 951 | } 952 | else 953 | { 954 | for (size_t iChunk = 0; iChunk < pChunkMap->vChunks.size(); iChunk++) 955 | { 956 | vChunksToProcess.push_back(pChunkMap->vChunks[iChunk]); 957 | } 958 | } 959 | 960 | for (size_t iChunk = 0; iChunk < vChunksToProcess.size(); iChunk++) 961 | { 962 | std::vector vProcessedVertices(vChunksToProcess[iChunk]->vVertices.Num()); 963 | vProcessedVertices[0] = true; 964 | 965 | size_t iVertexIndex = 0; 966 | 967 | for (size_t iRow = 0; iRow < ChunkPieceRowCount + 1; iRow++) 968 | { 969 | for (size_t iColumn = 0; iColumn < ChunkPieceColumnCount + 1; iColumn++) 970 | { 971 | if (((iRow < 2) || (iRow > ChunkPieceRowCount - 3)) 972 | || ((iColumn < 2) || (iColumn > ChunkPieceColumnCount - 3))) 973 | { 974 | iVertexIndex++; 975 | continue; 976 | } 977 | 978 | bool bHasLeftPoints = true; 979 | bool bHasTopPoints = true; 980 | bool bHasRightPoints = true; 981 | bool bHasDownPoints = true; 982 | 983 | if (iRow == 0) 984 | { 985 | bHasTopPoints = false; 986 | } 987 | 988 | if (iRow == ChunkPieceRowCount) 989 | { 990 | bHasDownPoints = false; 991 | } 992 | 993 | if (iColumn == 0) 994 | { 995 | bHasLeftPoints = false; 996 | } 997 | 998 | if (iColumn == ChunkPieceColumnCount) 999 | { 1000 | bHasRightPoints = false; 1001 | } 1002 | 1003 | float fCurrentVertexZ = vChunksToProcess[iChunk]->vVertices[iVertexIndex].Z; 1004 | 1005 | // Process the left points: 1006 | if (bHasLeftPoints) 1007 | { 1008 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex - 1, fSteepSlopeMinHeightDiff); 1009 | 1010 | if (bHasTopPoints) 1011 | { 1012 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex - 1 - (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1013 | } 1014 | 1015 | if (bHasDownPoints) 1016 | { 1017 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex - 1 + (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1018 | } 1019 | } 1020 | 1021 | // Process the right points: 1022 | if (bHasRightPoints) 1023 | { 1024 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex + 1, fSteepSlopeMinHeightDiff); 1025 | 1026 | if (bHasTopPoints) 1027 | { 1028 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex + 1 - (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1029 | } 1030 | 1031 | if (bHasDownPoints) 1032 | { 1033 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex + 1 + (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1034 | } 1035 | } 1036 | 1037 | // Top point: 1038 | if (bHasTopPoints) 1039 | { 1040 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex - (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1041 | } 1042 | 1043 | // Down point: 1044 | if (bHasDownPoints) 1045 | { 1046 | compareHeightDifference(vChunksToProcess[iChunk], vProcessedVertices, fCurrentVertexZ, iVertexIndex + (ChunkPieceColumnCount + 1), fSteepSlopeMinHeightDiff); 1047 | } 1048 | 1049 | iVertexIndex++; 1050 | } 1051 | } 1052 | } 1053 | } 1054 | 1055 | void AFWGen::spawnObjects(AFWGChunk* pOnlyForThisChunk) 1056 | { 1057 | if (vObjectsToSpawn.size() == 0) 1058 | { 1059 | return; 1060 | } 1061 | 1062 | std::mt19937_64 gen(std::random_device{}()); 1063 | 1064 | std::uniform_real_distribution urd(0.0f, 1.0f); 1065 | std::uniform_real_distribution urd_rotation(-MaxRotation, MaxRotation); 1066 | 1067 | std::sort(vObjectsToSpawn.begin(), vObjectsToSpawn.end(), 1068 | [](const FWGCallback& a, const FWGCallback& b) -> bool 1069 | { 1070 | return a.fProbabilityToSpawn < b.fProbabilityToSpawn; 1071 | }); 1072 | 1073 | std::vector vChunksToProcess; 1074 | 1075 | if (pOnlyForThisChunk) 1076 | { 1077 | vChunksToProcess.push_back(pOnlyForThisChunk); 1078 | } 1079 | else 1080 | { 1081 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 1082 | { 1083 | vChunksToProcess.push_back(pChunkMap->vChunks[i]); 1084 | } 1085 | } 1086 | 1087 | for (size_t i = 0; i < vChunksToProcess.size(); i++) 1088 | { 1089 | float fChunkX = GetActorLocation().X; 1090 | float fChunkY = GetActorLocation().Y; 1091 | 1092 | if (vChunksToProcess[i]->iX != 0) 1093 | { 1094 | fChunkX += (vChunksToProcess[i]->iX * ChunkPieceColumnCount * ChunkPieceSizeX); 1095 | } 1096 | 1097 | if (vChunksToProcess[i]->iY != 0) 1098 | { 1099 | fChunkY += (vChunksToProcess[i]->iY * ChunkPieceRowCount * ChunkPieceSizeY); 1100 | } 1101 | 1102 | float fStartX = fChunkX - (ChunkPieceColumnCount * ChunkPieceSizeX) / 2; 1103 | float fStartY = fChunkY - (ChunkPieceRowCount * ChunkPieceSizeY) / 2; 1104 | 1105 | float fXCellSize = ChunkPieceColumnCount * ChunkPieceSizeX / DivideChunkXCount; 1106 | float fYCellSize = ChunkPieceRowCount * ChunkPieceSizeY / DivideChunkYCount; 1107 | 1108 | 1109 | std::uniform_real_distribution offsetByX(-fXCellSize * MaxOffsetByX, fXCellSize * MaxOffsetByX); 1110 | std::uniform_real_distribution offsetByY(-fYCellSize * MaxOffsetByY, fYCellSize * MaxOffsetByY); 1111 | 1112 | 1113 | // Divide all object into 4 layers. 1114 | 1115 | std::vector vWaterLayer; 1116 | std::vector vFirstLayer; 1117 | std::vector vSecondLayer; 1118 | std::vector vThirdLayer; 1119 | 1120 | for (size_t k = 0; k < vObjectsToSpawn.size(); k++) 1121 | { 1122 | if (areEqual(vObjectsToSpawn[k].fLayer, -0.5f, 0.1f)) 1123 | { 1124 | vWaterLayer.push_back(vObjectsToSpawn[k]); 1125 | } 1126 | else if (areEqual(vObjectsToSpawn[k].fLayer, 0.0f, 0.1f)) 1127 | { 1128 | vFirstLayer.push_back(vObjectsToSpawn[k]); 1129 | } 1130 | else if (areEqual(vObjectsToSpawn[k].fLayer, 0.5f, 0.1f)) 1131 | { 1132 | vSecondLayer.push_back(vObjectsToSpawn[k]); 1133 | } 1134 | else 1135 | { 1136 | vThirdLayer.push_back(vObjectsToSpawn[k]); 1137 | } 1138 | } 1139 | 1140 | 1141 | for (size_t y = 0; y < vChunksToProcess[i]->vChunkCells.size(); y++) 1142 | { 1143 | for (size_t x = 0; x < vChunksToProcess[i]->vChunkCells[y].size(); x++) 1144 | { 1145 | FVector location; 1146 | location.X = fStartX + x * fXCellSize + fXCellSize / 2; 1147 | location.Y = fStartY + y * fYCellSize + fYCellSize / 2; 1148 | location.Z = GetActorLocation().Z; 1149 | 1150 | 1151 | location.X += offsetByX(gen); 1152 | location.Y += offsetByY(gen); 1153 | 1154 | 1155 | FHitResult OutHit; 1156 | FVector TraceStart(location.X, location.Y, GetActorLocation().Z + GenerationMaxZFromActorZ + 5.0f); 1157 | FVector TraceEnd(location.X, location.Y, GetActorLocation().Z - 5.0f); 1158 | FCollisionQueryParams CollisionParams; 1159 | 1160 | // Get Z. 1161 | if (GetWorld()->LineTraceSingleByChannel(OutHit, TraceStart, TraceEnd, ECC_Visibility, CollisionParams)) 1162 | { 1163 | if (OutHit.bBlockingHit) 1164 | { 1165 | location.Z = OutHit.ImpactPoint.Z; 1166 | } 1167 | } 1168 | 1169 | 1170 | 1171 | // Check if this cell is on the steep slope. 1172 | 1173 | bool bSteepSlope = false; 1174 | 1175 | std::vector vXOffset; 1176 | vXOffset.push_back(TraceStart.X + fXCellSize / 2); 1177 | vXOffset.push_back(TraceStart.X - fXCellSize / 2); 1178 | 1179 | 1180 | for (size_t k = 0; k < vXOffset.size(); k++) 1181 | { 1182 | TraceStart.X = vXOffset[k]; 1183 | TraceEnd.X = vXOffset[k]; 1184 | 1185 | if (GetWorld()->LineTraceSingleByChannel(OutHit, TraceStart, TraceEnd, ECC_Visibility, CollisionParams)) 1186 | { 1187 | if (OutHit.bBlockingHit) 1188 | { 1189 | if (fabs(OutHit.ImpactPoint.Z - location.Z) > MaxZDiffInCell) 1190 | { 1191 | bSteepSlope = true; 1192 | break; 1193 | } 1194 | } 1195 | } 1196 | } 1197 | 1198 | if (bSteepSlope == false) 1199 | { 1200 | std::vector vYOffset; 1201 | vYOffset.push_back(TraceStart.Y + fYCellSize / 2); 1202 | vYOffset.push_back(TraceStart.Y - fYCellSize / 2); 1203 | 1204 | for (size_t k = 0; k < vYOffset.size(); k++) 1205 | { 1206 | TraceStart.Y = vYOffset[k]; 1207 | TraceEnd.Y = vYOffset[k]; 1208 | 1209 | if (GetWorld()->LineTraceSingleByChannel(OutHit, TraceStart, TraceEnd, ECC_Visibility, CollisionParams)) 1210 | { 1211 | if (OutHit.bBlockingHit) 1212 | { 1213 | if (fabs(OutHit.ImpactPoint.Z - location.Z) > MaxZDiffInCell) 1214 | { 1215 | bSteepSlope = true; 1216 | break; 1217 | } 1218 | } 1219 | } 1220 | } 1221 | } 1222 | 1223 | if (bSteepSlope) 1224 | { 1225 | vChunksToProcess[i]->vChunkCells[y][x] = true; 1226 | 1227 | continue; 1228 | } 1229 | 1230 | 1231 | std::vector* pCurrentLayer = nullptr; 1232 | 1233 | 1234 | if ( CreateWater && (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * ZWaterLevelInWorld 1235 | + GenerationMaxZFromActorZ * 0.01f)) // error 1236 | { 1237 | // Water layer. 1238 | 1239 | pCurrentLayer = &vWaterLayer; 1240 | } 1241 | else if (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * FirstMaterialMaxRelativeHeight) 1242 | { 1243 | // First layer. 1244 | 1245 | pCurrentLayer = &vFirstLayer; 1246 | } 1247 | else if (location.Z <= GetActorLocation().Z + GenerationMaxZFromActorZ * SecondMaterialMaxRelativeHeight) 1248 | { 1249 | // Second layer. 1250 | 1251 | pCurrentLayer = &vSecondLayer; 1252 | } 1253 | else 1254 | { 1255 | // Third layer. 1256 | 1257 | pCurrentLayer = &vThirdLayer; 1258 | } 1259 | 1260 | 1261 | float fGeneratedProbForThisCell = urd(gen); 1262 | float fFullProb = 0.0f; 1263 | 1264 | for (size_t k = 0; k < pCurrentLayer->size(); k++) 1265 | { 1266 | float fNextValue = 1.0f - fFullProb; 1267 | if (k != pCurrentLayer->size() - 1) 1268 | { 1269 | fNextValue = pCurrentLayer->operator[](k).fProbabilityToSpawn; 1270 | } 1271 | 1272 | if ((fGeneratedProbForThisCell > fFullProb) && (fGeneratedProbForThisCell <= fFullProb + fNextValue) ) 1273 | { 1274 | if (pCurrentLayer->operator[](k).bIsBlocking) 1275 | { 1276 | vChunksToProcess[i]->vChunkCells[y][x] = true; 1277 | } 1278 | 1279 | 1280 | FTransform transform = FTransform(FRotator(0, urd_rotation(gen), 0), location, FVector(1, 1, 1)); 1281 | 1282 | struct params 1283 | { 1284 | FTransform transform; 1285 | int64 x; 1286 | int64 y; 1287 | }; 1288 | 1289 | params p; 1290 | p.transform = transform; 1291 | p.x = vChunksToProcess[i]->iX; 1292 | p.y = vChunksToProcess[i]->iY; 1293 | 1294 | pCurrentLayer->operator[](k).pOwner->ProcessEvent( pCurrentLayer->operator[](k).pFunction, &p); 1295 | 1296 | fFullProb += pCurrentLayer->operator[](k).fProbabilityToSpawn; 1297 | 1298 | break; 1299 | } 1300 | 1301 | fFullProb += pCurrentLayer->operator[](k).fProbabilityToSpawn; 1302 | } 1303 | } 1304 | } 1305 | } 1306 | } 1307 | 1308 | void AFWGen::createTriggerBoxForChunk(AFWGChunk* pChunk) 1309 | { 1310 | float fTriggerX = GetActorLocation().X; 1311 | float fTriggerY = GetActorLocation().Y; 1312 | float fTriggerZ = GetActorLocation().Z + LoadUnloadChunkMaxZ / 2; 1313 | 1314 | if (pChunk->iX != 0) 1315 | { 1316 | // Left or right chunk 1317 | 1318 | fTriggerX += (pChunk->iX * ChunkPieceColumnCount * ChunkPieceSizeX); 1319 | } 1320 | 1321 | if (pChunk->iY != 0) 1322 | { 1323 | // Top or bottom chunk 1324 | 1325 | fTriggerY += (pChunk->iY * ChunkPieceRowCount * ChunkPieceSizeY); 1326 | } 1327 | 1328 | pChunk->SetActorLocation(FVector(fTriggerX, fTriggerY, fTriggerZ)); 1329 | pChunk->pTriggerBox->SetBoxExtent(FVector(ChunkPieceColumnCount * ChunkPieceSizeX / 2, 1330 | ChunkPieceRowCount * ChunkPieceSizeY / 2, 1331 | LoadUnloadChunkMaxZ / 2)); 1332 | 1333 | pChunk->pTriggerBox->SetGenerateOverlapEvents(true); 1334 | pChunk->setOverlapToActors(vOverlapToClasses); 1335 | } 1336 | 1337 | bool AFWGen::SetMaxRotation(float fMaxRotation) 1338 | { 1339 | if (fMaxRotation >= 0.0f) 1340 | { 1341 | MaxRotation = fMaxRotation; 1342 | 1343 | return false; 1344 | } 1345 | else 1346 | { 1347 | return true; 1348 | } 1349 | } 1350 | 1351 | void AFWGen::SetMaxZDiffInCell(float fNewMaxZDiffInCell) 1352 | { 1353 | MaxZDiffInCell = fNewMaxZDiffInCell; 1354 | } 1355 | 1356 | #if !UE_BUILD_SHIPPING 1357 | 1358 | void AFWGen::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 1359 | { 1360 | Super::PostEditChangeProperty(PropertyChangedEvent); 1361 | 1362 | FName MemberPropertyChanged = (PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None); 1363 | 1364 | 1365 | if ( 1366 | MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ChunkPieceRowCount) 1367 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ChunkPieceColumnCount) 1368 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ChunkPieceSizeX) 1369 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ChunkPieceSizeY) 1370 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, GenerationFrequency) 1371 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, GenerationOctaves) 1372 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, GenerationSeed) 1373 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, GenerationMaxZFromActorZ) 1374 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ComplexPreview) 1375 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, InvertWorld) 1376 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, FirstMaterialOnSecondProbability) 1377 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, FirstMaterialOnThirdProbability) 1378 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, SecondMaterialOnFirstProbability) 1379 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, SecondMaterialOnThirdProbability) 1380 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ThirdMaterialOnFirstProbability) 1381 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ThirdMaterialOnSecondProbability) 1382 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, TerrainCutHeightFromActorZ) 1383 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, DrawChunkBounds) 1384 | ) 1385 | { 1386 | if (DrawChunkBounds == false) 1387 | { 1388 | FlushPersistentDebugLines(GetWorld()); 1389 | } 1390 | 1391 | if ((TerrainCutHeightFromActorZ > 1.0f) || (TerrainCutHeightFromActorZ < 0.0f)) 1392 | { 1393 | TerrainCutHeightFromActorZ = 1.0f; 1394 | } 1395 | 1396 | if ((FirstMaterialOnSecondProbability < 0.0f) || (FirstMaterialOnSecondProbability > 1.0f)) 1397 | { 1398 | FirstMaterialOnSecondProbability = 0.01f; 1399 | } 1400 | 1401 | if ((FirstMaterialOnThirdProbability < 0.0f) || (FirstMaterialOnThirdProbability > 1.0f)) 1402 | { 1403 | FirstMaterialOnThirdProbability = 0.01f; 1404 | } 1405 | 1406 | if ((SecondMaterialOnFirstProbability < 0.0f) || (SecondMaterialOnFirstProbability > 1.0f)) 1407 | { 1408 | SecondMaterialOnFirstProbability = 0.01f; 1409 | } 1410 | 1411 | if ((SecondMaterialOnThirdProbability < 0.0f) || (SecondMaterialOnThirdProbability > 1.0f)) 1412 | { 1413 | SecondMaterialOnThirdProbability = 0.01f; 1414 | } 1415 | 1416 | if ((ThirdMaterialOnFirstProbability < 0.0f) || (ThirdMaterialOnFirstProbability > 1.0f)) 1417 | { 1418 | ThirdMaterialOnFirstProbability = 0.01f; 1419 | } 1420 | 1421 | if ((ThirdMaterialOnSecondProbability < 0.0f) || (ThirdMaterialOnSecondProbability > 1.0f)) 1422 | { 1423 | ThirdMaterialOnSecondProbability = 0.01f; 1424 | } 1425 | 1426 | if ((IncreasedMaterialBlendProbability < 0.0f) || (IncreasedMaterialBlendProbability > 1.0f)) 1427 | { 1428 | IncreasedMaterialBlendProbability = 0.0f; 1429 | } 1430 | 1431 | 1432 | 1433 | if (ChunkPieceRowCount < 1) 1434 | { 1435 | ChunkPieceRowCount = 1; 1436 | } 1437 | 1438 | if (ChunkPieceColumnCount < 1) 1439 | { 1440 | ChunkPieceColumnCount = 1; 1441 | } 1442 | 1443 | if (GenerationMaxZFromActorZ < 0.0f) 1444 | { 1445 | GenerationMaxZFromActorZ = 0.0f; 1446 | } 1447 | 1448 | if (GenerationSeed < 0) 1449 | { 1450 | GenerationSeed = 0; 1451 | } 1452 | 1453 | if (GenerationFrequency > 64.0f) 1454 | { 1455 | GenerationFrequency = 64.0f; 1456 | } 1457 | else if (GenerationFrequency < 0.1f) 1458 | { 1459 | GenerationFrequency = 0.1f; 1460 | } 1461 | 1462 | if (GenerationOctaves > 16) 1463 | { 1464 | GenerationOctaves = 16; 1465 | } 1466 | else if (GenerationOctaves < 1) 1467 | { 1468 | GenerationOctaves = 1; 1469 | } 1470 | 1471 | refreshPreview(); 1472 | 1473 | if (ComplexPreview) 1474 | { 1475 | GenerateWorld(); 1476 | } 1477 | else 1478 | { 1479 | pChunkMap->clearWorld(pProcMeshComponent); 1480 | } 1481 | } 1482 | else if ( MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, CreateWater) 1483 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ZWaterLevelInWorld) 1484 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, WaterSize) 1485 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, WaterMaterial) 1486 | || MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, GroundMaterial)) 1487 | { 1488 | if (ZWaterLevelInWorld < 0.0f) 1489 | { 1490 | ZWaterLevelInWorld = 0.0f; 1491 | } 1492 | else if (ZWaterLevelInWorld > 1.0f) 1493 | { 1494 | ZWaterLevelInWorld = 1.0f; 1495 | } 1496 | 1497 | if (GroundMaterial) 1498 | { 1499 | pProcMeshComponent->SetMaterial(0, GroundMaterial); 1500 | } 1501 | 1502 | if (CreateWater) 1503 | { 1504 | if (WaterMaterial) 1505 | { 1506 | WaterPlane->SetMaterial(0, WaterMaterial); 1507 | WaterPlane->SetVisibility(true); 1508 | } 1509 | 1510 | WaterPlane->SetWorldLocation(FVector( 1511 | GetActorLocation().X, 1512 | GetActorLocation().Y, 1513 | GetActorLocation().Z + ((GenerationMaxZFromActorZ - GetActorLocation().Z) * ZWaterLevelInWorld) 1514 | )); 1515 | WaterPlane->SetWorldScale3D(FVector( 1516 | ((ChunkPieceColumnCount) * ChunkPieceSizeX) * (WaterSize / 100.0f), 1517 | ((ChunkPieceRowCount) * ChunkPieceSizeY) * (WaterSize / 100.0f), 1518 | 0.1f 1519 | )); 1520 | } 1521 | else 1522 | { 1523 | WaterPlane->SetVisibility(false); 1524 | } 1525 | } 1526 | else if ((MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ViewDistance)) 1527 | || 1528 | (MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, WorldSize))) 1529 | { 1530 | if (WorldSize < -1) 1531 | { 1532 | WorldSize = -1; 1533 | } 1534 | 1535 | if (ViewDistance < 1) 1536 | { 1537 | ViewDistance = 1; 1538 | } 1539 | 1540 | refreshPreview(); 1541 | 1542 | if (ComplexPreview) GenerateWorld(); 1543 | } 1544 | else if (MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, ReadParamsFromFileRightNow)) 1545 | { 1546 | if (ReadParamsFromFileRightNow) 1547 | { 1548 | LoadWorldParamsFromFile(PathToSaveFile); 1549 | } 1550 | } 1551 | else if (MemberPropertyChanged == GET_MEMBER_NAME_CHECKED(AFWGen, SaveParamsToFileRightNow)) 1552 | { 1553 | if (SaveParamsToFileRightNow) 1554 | { 1555 | SaveWorldParamsToFile(PathToSaveFile); 1556 | } 1557 | } 1558 | else 1559 | { 1560 | refreshPreview(); 1561 | 1562 | if (ComplexPreview) GenerateWorld(); 1563 | } 1564 | } 1565 | #endif // WITH_EDITOR 1566 | 1567 | bool AFWGen::SetChunkPieceRowCount(int32 NewChunkPieceRowCount) 1568 | { 1569 | if (NewChunkPieceRowCount < 1) 1570 | { 1571 | return true; 1572 | } 1573 | else 1574 | { 1575 | ChunkPieceRowCount = NewChunkPieceRowCount; 1576 | 1577 | return false; 1578 | } 1579 | } 1580 | 1581 | bool AFWGen::SetChunkPieceColumnCount(int32 NewChunkPieceColumnCount) 1582 | { 1583 | if (NewChunkPieceColumnCount < 1) 1584 | { 1585 | return true; 1586 | } 1587 | else 1588 | { 1589 | ChunkPieceColumnCount = NewChunkPieceColumnCount; 1590 | 1591 | return false; 1592 | } 1593 | } 1594 | 1595 | bool AFWGen::SetChunkPieceSizeX(float NewChunkPieceSizeX) 1596 | { 1597 | if (NewChunkPieceSizeX < 0.5f) 1598 | { 1599 | return true; 1600 | } 1601 | else 1602 | { 1603 | ChunkPieceSizeX = NewChunkPieceSizeX; 1604 | 1605 | return false; 1606 | } 1607 | } 1608 | 1609 | bool AFWGen::SetChunkPieceSizeY(float NewChunkPieceSizeY) 1610 | { 1611 | if (NewChunkPieceSizeY < 0.5f) 1612 | { 1613 | return true; 1614 | } 1615 | else 1616 | { 1617 | ChunkPieceSizeY = NewChunkPieceSizeY; 1618 | 1619 | return false; 1620 | } 1621 | } 1622 | 1623 | bool AFWGen::SetViewDistance(int32 NewViewDistance) 1624 | { 1625 | if (NewViewDistance < 1) 1626 | { 1627 | return true; 1628 | } 1629 | else 1630 | { 1631 | ViewDistance = NewViewDistance; 1632 | 1633 | return false; 1634 | } 1635 | } 1636 | 1637 | void AFWGen::SetLoadUnloadChunkMaxZ(float NewLoadUnloadChunkMaxZ) 1638 | { 1639 | LoadUnloadChunkMaxZ = NewLoadUnloadChunkMaxZ; 1640 | } 1641 | 1642 | bool AFWGen::SetGenerationFrequency(float NewGenerationFrequency) 1643 | { 1644 | if ((NewGenerationFrequency > 64.0f) || (NewGenerationFrequency < 0.1f)) 1645 | { 1646 | return true; 1647 | } 1648 | else 1649 | { 1650 | GenerationFrequency = NewGenerationFrequency; 1651 | 1652 | return false; 1653 | } 1654 | } 1655 | 1656 | bool AFWGen::SetGenerationOctaves(int32 NewGenerationOctaves) 1657 | { 1658 | if ((NewGenerationOctaves > 16) || (NewGenerationOctaves < 1)) 1659 | { 1660 | return true; 1661 | } 1662 | else 1663 | { 1664 | GenerationOctaves = NewGenerationOctaves; 1665 | 1666 | return false; 1667 | } 1668 | } 1669 | 1670 | bool AFWGen::SetGenerationSeed(int32 NewGenerationSeed) 1671 | { 1672 | if (NewGenerationSeed < 0) 1673 | { 1674 | return true; 1675 | } 1676 | else 1677 | { 1678 | GenerationSeed = NewGenerationSeed; 1679 | 1680 | return false; 1681 | } 1682 | } 1683 | 1684 | bool AFWGen::SetGenerationMaxZFromActorZ(float NewGenerationMaxZFromActorZ) 1685 | { 1686 | if (NewGenerationMaxZFromActorZ < 0.0f) 1687 | { 1688 | return true; 1689 | } 1690 | else 1691 | { 1692 | GenerationMaxZFromActorZ = NewGenerationMaxZFromActorZ; 1693 | 1694 | return false; 1695 | } 1696 | } 1697 | 1698 | void AFWGen::SetInvertWorld(bool Invertworld) 1699 | { 1700 | this->InvertWorld = Invertworld; 1701 | } 1702 | 1703 | bool AFWGen::SetWorldSize(int32 NewWorldSize) 1704 | { 1705 | if (NewWorldSize < -1) 1706 | { 1707 | return true; 1708 | } 1709 | else 1710 | { 1711 | WorldSize = NewWorldSize; 1712 | 1713 | return false; 1714 | } 1715 | } 1716 | 1717 | void AFWGen::SetGroundMaterial(UMaterialInterface* NewGroundMaterial) 1718 | { 1719 | GroundMaterial = NewGroundMaterial; 1720 | } 1721 | 1722 | bool AFWGen::SetFirstMaterialMaxRelativeHeight(float NewFirstMaterialMaxRelativeHeight) 1723 | { 1724 | if ((NewFirstMaterialMaxRelativeHeight < 0.0f) || (NewFirstMaterialMaxRelativeHeight > 1.0f) || (NewFirstMaterialMaxRelativeHeight > SecondMaterialMaxRelativeHeight)) 1725 | { 1726 | return true; 1727 | } 1728 | else 1729 | { 1730 | FirstMaterialMaxRelativeHeight = NewFirstMaterialMaxRelativeHeight; 1731 | 1732 | return false; 1733 | } 1734 | } 1735 | 1736 | bool AFWGen::SetSecondMaterialMaxRelativeHeight(float NewSecondMaterialMaxRelativeHeight) 1737 | { 1738 | if ((NewSecondMaterialMaxRelativeHeight < 0.0f) || (NewSecondMaterialMaxRelativeHeight > 1.0f) || (NewSecondMaterialMaxRelativeHeight < FirstMaterialMaxRelativeHeight)) 1739 | { 1740 | return true; 1741 | } 1742 | else 1743 | { 1744 | SecondMaterialMaxRelativeHeight = NewSecondMaterialMaxRelativeHeight; 1745 | 1746 | return false; 1747 | } 1748 | } 1749 | 1750 | bool AFWGen::SetMaterialHeightMaxDeviation(float NewMaterialHeightMaxDeviation) 1751 | { 1752 | if ((NewMaterialHeightMaxDeviation > 1.0f) || (NewMaterialHeightMaxDeviation < 0.0f)) 1753 | { 1754 | return true; 1755 | } 1756 | else 1757 | { 1758 | MaterialHeightMaxDeviation = NewMaterialHeightMaxDeviation; 1759 | 1760 | return false; 1761 | } 1762 | } 1763 | 1764 | bool AFWGen::SetTerrainCutHeightFromActorZ(float NewTerrainCutHeightFromActorZ) 1765 | { 1766 | if ((NewTerrainCutHeightFromActorZ > 1.0f) || (NewTerrainCutHeightFromActorZ < 0.0f)) 1767 | { 1768 | return true; 1769 | } 1770 | else 1771 | { 1772 | TerrainCutHeightFromActorZ = NewTerrainCutHeightFromActorZ; 1773 | return false; 1774 | } 1775 | } 1776 | 1777 | bool AFWGen::SetFirstMaterialOnOtherProbability(float FirstOnSecond, float FirstOnThird) 1778 | { 1779 | if ( ((FirstOnSecond > 1.0f) || (FirstOnSecond < 0.0f)) || ((FirstOnThird > 1.0f) || (FirstOnThird < 0.0f))) 1780 | { 1781 | return true; 1782 | } 1783 | else 1784 | { 1785 | FirstMaterialOnSecondProbability = FirstOnSecond; 1786 | FirstMaterialOnThirdProbability = FirstOnThird; 1787 | return false; 1788 | } 1789 | } 1790 | 1791 | bool AFWGen::SetSecondMaterialOnOtherProbability(float SecondOnFirst, float SecondOnThird) 1792 | { 1793 | if ( ((SecondOnFirst > 1.0f) || (SecondOnFirst < 0.0f)) || ((SecondOnThird > 1.0f) || (SecondOnThird < 0.0f))) 1794 | { 1795 | return true; 1796 | } 1797 | else 1798 | { 1799 | SecondMaterialOnFirstProbability = SecondOnFirst; 1800 | SecondMaterialOnThirdProbability = SecondOnThird; 1801 | return false; 1802 | } 1803 | } 1804 | 1805 | bool AFWGen::SetThirdMaterialOnOtherProbability(float ThirdOnFirst, float ThirdOnSecond) 1806 | { 1807 | if ( ((ThirdOnFirst > 1.0f) || (ThirdOnFirst < 0.0f)) || ((ThirdOnSecond > 1.0f) || (ThirdOnSecond < 0.0f))) 1808 | { 1809 | return true; 1810 | } 1811 | else 1812 | { 1813 | ThirdMaterialOnFirstProbability = ThirdOnFirst; 1814 | ThirdMaterialOnSecondProbability = ThirdOnSecond; 1815 | return false; 1816 | } 1817 | } 1818 | 1819 | void AFWGen::SetCreateWater(bool Createwater) 1820 | { 1821 | this->CreateWater = Createwater; 1822 | } 1823 | 1824 | bool AFWGen::SetZWaterLevelInWorld(float NewZWaterLevelInWorld) 1825 | { 1826 | if ((NewZWaterLevelInWorld < 0.0f) || (NewZWaterLevelInWorld > 1.0f)) 1827 | { 1828 | return true; 1829 | } 1830 | else 1831 | { 1832 | ZWaterLevelInWorld = NewZWaterLevelInWorld; 1833 | 1834 | return false; 1835 | } 1836 | } 1837 | 1838 | bool AFWGen::SetWaterSize(int32 NewWaterSize) 1839 | { 1840 | if (NewWaterSize <= 0) 1841 | { 1842 | return true; 1843 | } 1844 | else 1845 | { 1846 | WaterSize = NewWaterSize; 1847 | 1848 | return false; 1849 | } 1850 | } 1851 | 1852 | void AFWGen::SetWaterMaterial(UMaterialInterface* NewWaterMaterial) 1853 | { 1854 | WaterMaterial = NewWaterMaterial; 1855 | } 1856 | 1857 | #if !UE_BUILD_SHIPPING 1858 | void AFWGen::PostEditMove(bool bFinished) 1859 | { 1860 | Super::PostEditMove(bFinished); 1861 | 1862 | refreshPreview(); 1863 | 1864 | if (ComplexPreview) GenerateWorld(); 1865 | } 1866 | #endif // WITH_EDITOR 1867 | 1868 | void AFWGen::BeginPlay() 1869 | { 1870 | Super::BeginPlay(); 1871 | } 1872 | 1873 | void AFWGen::generateSeed() 1874 | { 1875 | uint32_t seed = 0; 1876 | 1877 | if (GenerationSeed == 0) 1878 | { 1879 | // Generate seed 1880 | std::mt19937_64 gen(std::random_device{}()); 1881 | std::uniform_int_distribution uid(0, UINT_MAX); 1882 | 1883 | seed = uid(gen); 1884 | } 1885 | else 1886 | { 1887 | seed = GenerationSeed; 1888 | } 1889 | 1890 | iGeneratedSeed = seed; 1891 | } 1892 | 1893 | float AFWGen::pickVertexMaterial(double height, std::uniform_real_distribution* pUrd, std::mt19937_64* pRnd, float* pfLayerTypeWithoutRnd) 1894 | { 1895 | std::uniform_real_distribution urd_mat(0.005f, 1.0f); 1896 | std::uniform_int_distribution urd_bool(0, 1); 1897 | 1898 | 1899 | float fDiviation = (*pUrd)(*pRnd); 1900 | 1901 | float fVertexColor = 0.0f; // apply first material to the vertex 1902 | 1903 | if ( (height >= (FirstMaterialMaxRelativeHeight + fDiviation)) 1904 | && (height <= (SecondMaterialMaxRelativeHeight + fDiviation)) ) 1905 | { 1906 | fVertexColor = 0.5f; // apply second material to the vertex 1907 | 1908 | if (pfLayerTypeWithoutRnd) 1909 | { 1910 | *pfLayerTypeWithoutRnd = fVertexColor; 1911 | } 1912 | 1913 | float fFirstProb = urd_mat(*pRnd); 1914 | float fThirdProb = urd_mat(*pRnd); 1915 | 1916 | bool bPickFirst = false, bPickThird = false; 1917 | 1918 | if (fFirstProb <= FirstMaterialOnSecondProbability) 1919 | { 1920 | bPickFirst = true; 1921 | } 1922 | 1923 | if (fThirdProb <= ThirdMaterialOnSecondProbability) 1924 | { 1925 | bPickThird = true; 1926 | } 1927 | 1928 | if (bPickFirst && bPickThird) 1929 | { 1930 | if (urd_bool(*pRnd)) 1931 | { 1932 | // Pick Third 1933 | fVertexColor = 1.0f; // apply third material to the vertex 1934 | } 1935 | else 1936 | { 1937 | // Pick First 1938 | fVertexColor = 0.0f; // apply first material to the vertex 1939 | } 1940 | } 1941 | else 1942 | { 1943 | if (bPickFirst) 1944 | { 1945 | fVertexColor = 0.0f; // apply first material to the vertex 1946 | } 1947 | 1948 | if (bPickThird) 1949 | { 1950 | fVertexColor = 1.0f; // apply third material to the vertex 1951 | } 1952 | } 1953 | } 1954 | else if (height >= (SecondMaterialMaxRelativeHeight + fDiviation)) 1955 | { 1956 | fVertexColor = 1.0f; // apply third material to the vertex 1957 | 1958 | if (pfLayerTypeWithoutRnd) 1959 | { 1960 | *pfLayerTypeWithoutRnd = fVertexColor; 1961 | } 1962 | 1963 | float fFirstProb = urd_mat(*pRnd); 1964 | float fSecondProb = urd_mat(*pRnd); 1965 | 1966 | bool bPickFirst = false, bPickSecond = false; 1967 | 1968 | if (fFirstProb <= FirstMaterialOnThirdProbability) 1969 | { 1970 | bPickFirst = true; 1971 | } 1972 | 1973 | if (fSecondProb <= SecondMaterialOnThirdProbability) 1974 | { 1975 | bPickSecond = true; 1976 | } 1977 | 1978 | if (bPickFirst && bPickSecond) 1979 | { 1980 | if (urd_bool(*pRnd)) 1981 | { 1982 | // Pick Third 1983 | fVertexColor = 0.5f; // apply second material to the vertex 1984 | } 1985 | else 1986 | { 1987 | // Pick First 1988 | fVertexColor = 0.0f; // apply first material to the vertex 1989 | } 1990 | } 1991 | else 1992 | { 1993 | if (bPickFirst) 1994 | { 1995 | fVertexColor = 0.0f; // apply first material to the vertex 1996 | } 1997 | 1998 | if (bPickSecond) 1999 | { 2000 | fVertexColor = 0.5f; // apply second material to the vertex 2001 | } 2002 | } 2003 | } 2004 | else 2005 | { 2006 | // first material 2007 | 2008 | fVertexColor = 0.0f; 2009 | 2010 | if (pfLayerTypeWithoutRnd) 2011 | { 2012 | *pfLayerTypeWithoutRnd = fVertexColor; 2013 | } 2014 | 2015 | float fSecondProb = urd_mat(*pRnd); 2016 | float fThirdProb = urd_mat(*pRnd); 2017 | 2018 | bool bPickSecond = false, bPickThird = false; 2019 | 2020 | if (fSecondProb <= SecondMaterialOnFirstProbability) 2021 | { 2022 | bPickSecond = true; 2023 | } 2024 | 2025 | if (fThirdProb <= ThirdMaterialOnFirstProbability) 2026 | { 2027 | bPickThird = true; 2028 | } 2029 | 2030 | if (bPickSecond && bPickThird) 2031 | { 2032 | if (urd_bool(*pRnd)) 2033 | { 2034 | // Pick Third 2035 | fVertexColor = 1.0f; // apply third material to the vertex 2036 | } 2037 | else 2038 | { 2039 | // Pick Second 2040 | fVertexColor = 0.5f; // apply second material to the vertex 2041 | } 2042 | } 2043 | else 2044 | { 2045 | if (bPickSecond) 2046 | { 2047 | fVertexColor = 0.5f; // apply second material to the vertex 2048 | } 2049 | 2050 | if (bPickThird) 2051 | { 2052 | fVertexColor = 1.0f; // apply third material to the vertex 2053 | } 2054 | } 2055 | } 2056 | 2057 | return fVertexColor; 2058 | } 2059 | 2060 | AFWGChunk* AFWGen::generateChunk(long long iX, long long iY, int32 iSectionIndex, bool bAroundCenter, 2061 | long long iUnloadX, long long iUnloadY, bool bUnload) 2062 | { 2063 | // Generation setup 2064 | 2065 | uint32_t seed = iGeneratedSeed; 2066 | 2067 | 2068 | 2069 | // We ++ here because we start to make polygons from 2nd row 2070 | int32 iCorrectedRowCount = ChunkPieceRowCount + 1; 2071 | int32 iCorrectedColumnCount = ChunkPieceColumnCount + 1; 2072 | 2073 | 2074 | 2075 | 2076 | // Perlin Noise setup 2077 | 2078 | const siv::PerlinNoise perlinNoise(seed); 2079 | const double fx = ((iCorrectedColumnCount - 1) * ChunkPieceSizeX) / GenerationFrequency; 2080 | const double fy = ((iCorrectedRowCount - 1) * ChunkPieceSizeY) / GenerationFrequency; 2081 | 2082 | 2083 | 2084 | 2085 | // Prepare chunk coordinates 2086 | 2087 | float fChunkX = GetActorLocation().X; 2088 | float fChunkY = GetActorLocation().Y; 2089 | 2090 | if (iX != 0) 2091 | { 2092 | // Left or right chunk 2093 | 2094 | fChunkX += (iX * ChunkPieceColumnCount * ChunkPieceSizeX); 2095 | } 2096 | 2097 | if (iY != 0) 2098 | { 2099 | // Top or bottom chunk 2100 | 2101 | fChunkY += (iY * ChunkPieceRowCount * ChunkPieceSizeY); 2102 | } 2103 | 2104 | 2105 | 2106 | 2107 | // Generation params 2108 | 2109 | float fStartX = fChunkX - ((iCorrectedColumnCount - 1) * ChunkPieceSizeX) / 2; 2110 | float fStartY = fChunkY - ((iCorrectedRowCount - 1) * ChunkPieceSizeY) / 2; 2111 | 2112 | FVector vPrevLocation(fStartX, fStartY, GetActorLocation().Z); 2113 | 2114 | 2115 | 2116 | 2117 | // Create chunk. 2118 | 2119 | AFWGChunk* pNewChunk = nullptr; 2120 | 2121 | if (bUnload == false) 2122 | { 2123 | // You don't want to use NewObject for Actors (only UObjects). 2124 | //AFWGChunk* pNewChunk = NewObject(GetTransientPackage(), MakeUniqueObjectName(this, AFWGChunk::StaticClass(), "Chunk_")); 2125 | FActorSpawnParameters params; 2126 | params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; 2127 | 2128 | pNewChunk = GetWorld()->SpawnActor(AFWGChunk::StaticClass(), FTransform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(1, 1, 1)), params); 2129 | pNewChunk->setInit(iX, iY, iSectionIndex, bAroundCenter); 2130 | pNewChunk->setChunkSize(DivideChunkXCount, DivideChunkYCount); 2131 | pNewChunk->setChunkMap(pChunkMap); 2132 | } 2133 | else 2134 | { 2135 | for (size_t i = 0; i < pChunkMap->vChunks.size(); i++) 2136 | { 2137 | if (pChunkMap->vChunks[i]->iX == iUnloadX 2138 | && pChunkMap->vChunks[i]->iY == iUnloadY) 2139 | { 2140 | pNewChunk = pChunkMap->vChunks[i]; 2141 | 2142 | pNewChunk->setUpdate(iX, iY, bAroundCenter); 2143 | pNewChunk->setChunkSize(DivideChunkXCount, DivideChunkYCount); 2144 | 2145 | iSectionIndex = pNewChunk->iSectionIndex; 2146 | 2147 | break; 2148 | } 2149 | } 2150 | } 2151 | 2152 | 2153 | 2154 | // Prepare random for vectex color 2155 | 2156 | std::mt19937_64 rnd(iGeneratedSeed); 2157 | std::uniform_real_distribution urd(-MaterialHeightMaxDeviation, MaterialHeightMaxDeviation); 2158 | 2159 | 2160 | // Generation 2161 | 2162 | float fMaxGeneratedZ = GetActorLocation().Z; 2163 | size_t iMaxGeneratedZIndex = 0; 2164 | 2165 | for (int32 i = 0; i < iCorrectedRowCount; i++) 2166 | { 2167 | for (int32 j = 0; j < iCorrectedColumnCount; j++) 2168 | { 2169 | // Generate vertex 2170 | double generatedValue = perlinNoise.octaveNoise0_1(vPrevLocation.X / fx, vPrevLocation.Y / fy, GenerationOctaves); 2171 | 2172 | if (generatedValue > TerrainCutHeightFromActorZ) 2173 | { 2174 | generatedValue = TerrainCutHeightFromActorZ; 2175 | } 2176 | 2177 | if (InvertWorld) 2178 | { 2179 | generatedValue = 1.0 - generatedValue; 2180 | } 2181 | 2182 | 2183 | pNewChunk->vNormals .Add(FVector(0, 0, 1.0f)); 2184 | pNewChunk->vUV0 .Add(FVector2D(i, j)); 2185 | pNewChunk->vTangents .Add(FProcMeshTangent(0.0f, 1.0f, 0.0f)); 2186 | 2187 | 2188 | 2189 | // Set vertex Z 2190 | 2191 | // Here return value from perlinNoise.octaveNoise0_1 can be 2192 | // 0 , but should be GetActorLocation().Z 2193 | // ... , but should be value from interval [GetActorLocation().Z; GenerationMaxZFromActorZ] 2194 | // 1 , but should be GenerationMaxZFromActorZ 2195 | 2196 | vPrevLocation.Z = GetActorLocation().Z + (GenerationMaxZFromActorZ * generatedValue); 2197 | 2198 | if (vPrevLocation.Z > fMaxGeneratedZ) 2199 | { 2200 | fMaxGeneratedZ = vPrevLocation.Z; 2201 | iMaxGeneratedZIndex = pNewChunk->vVertices.Num(); 2202 | } 2203 | 2204 | pNewChunk->vVertices .Add (vPrevLocation); 2205 | 2206 | 2207 | 2208 | // Set "material" to vertex 2209 | 2210 | float fAlphaColor = 0.0f; 2211 | 2212 | float fAlphaColorWithoutRnd = 0.0f; 2213 | 2214 | if (SecondMaterialUnderWater && (generatedValue < (ZWaterLevelInWorld + 0.005f))) 2215 | { 2216 | fAlphaColor = 0.5f; 2217 | fAlphaColorWithoutRnd = 0.5f; 2218 | } 2219 | else 2220 | { 2221 | if ((i == 0) || (i == iCorrectedRowCount - 1) || (j == 0) || (j == iCorrectedColumnCount - 1)) 2222 | { 2223 | pickVertexMaterial(generatedValue, &urd, &rnd, &fAlphaColorWithoutRnd); 2224 | fAlphaColor = fAlphaColorWithoutRnd; 2225 | } 2226 | else 2227 | { 2228 | fAlphaColor = pickVertexMaterial(generatedValue, &urd, &rnd, &fAlphaColorWithoutRnd); 2229 | } 2230 | } 2231 | 2232 | pNewChunk->vVertexColors .Add(FLinearColor(0.0f, 0.0f, 0.0f, fAlphaColor)); 2233 | 2234 | if (areEqual(fAlphaColorWithoutRnd, 0.0f, 0.1f)) 2235 | { 2236 | // First Layer 2237 | 2238 | pNewChunk->vLayerIndex.push_back(1); 2239 | } 2240 | else if (areEqual(fAlphaColorWithoutRnd, 0.5f, 0.1f)) 2241 | { 2242 | // Second Layer 2243 | pNewChunk->vLayerIndex.push_back(2); 2244 | } 2245 | else 2246 | { 2247 | // Third Layer 2248 | pNewChunk->vLayerIndex.push_back(3); 2249 | } 2250 | 2251 | 2252 | 2253 | vPrevLocation.X += ChunkPieceSizeX; 2254 | 2255 | if (i != 0) 2256 | { 2257 | // j = 0, 1, 2, 3 ... 2258 | // i = 0: +----+----+----+- ... 2259 | // | /| /| /| 2260 | // | / | / | / | 2261 | // | / | / | / | 2262 | // i = 1: +----+----+----+- ... 2263 | 2264 | int32 iFirstIndexInRow = (i - 1) * iCorrectedColumnCount; 2265 | 2266 | if (j == 0) 2267 | { 2268 | // Add triangle #1 2269 | pNewChunk->vTriangles.Add(iFirstIndexInRow + j); 2270 | pNewChunk->vTriangles.Add(i * iCorrectedColumnCount + j); 2271 | pNewChunk->vTriangles.Add(iFirstIndexInRow + j + 1); 2272 | } 2273 | else 2274 | { 2275 | // Add triangle #2 2276 | pNewChunk->vTriangles.Add(iFirstIndexInRow + j); 2277 | pNewChunk->vTriangles.Add(i * iCorrectedColumnCount + j - 1); 2278 | pNewChunk->vTriangles.Add(i * iCorrectedColumnCount + j); 2279 | 2280 | if (j < (iCorrectedColumnCount - 1)) 2281 | { 2282 | // Add triangle #1 2283 | pNewChunk->vTriangles.Add(iFirstIndexInRow + j); 2284 | pNewChunk->vTriangles.Add(i * iCorrectedColumnCount + j); 2285 | pNewChunk->vTriangles.Add(iFirstIndexInRow + j + 1); 2286 | } 2287 | } 2288 | } 2289 | } 2290 | 2291 | vPrevLocation.Set(fStartX, vPrevLocation.Y + ChunkPieceSizeY, GetActorLocation().Z); 2292 | } 2293 | 2294 | pNewChunk->iMaxZVertexIndex = iMaxGeneratedZIndex; 2295 | 2296 | if (bUnload == false) 2297 | { 2298 | pProcMeshComponent->CreateMeshSection_LinearColor(iSectionIndex, pNewChunk->vVertices, pNewChunk->vTriangles, pNewChunk->vNormals, 2299 | pNewChunk->vUV0, pNewChunk->vVertexColors, pNewChunk->vTangents, true); 2300 | 2301 | // Set material 2302 | if (GroundMaterial) 2303 | { 2304 | pProcMeshComponent->SetMaterial(iSectionIndex, GroundMaterial); 2305 | } 2306 | 2307 | pNewChunk->setMeshSection(pProcMeshComponent->GetProcMeshSection(iSectionIndex)); 2308 | } 2309 | else 2310 | { 2311 | pProcMeshComponent->UpdateMeshSection_LinearColor(iSectionIndex, pNewChunk->vVertices, pNewChunk->vNormals, 2312 | pNewChunk->vUV0, pNewChunk->vVertexColors, pNewChunk->vTangents); 2313 | } 2314 | 2315 | return pNewChunk; 2316 | } 2317 | 2318 | #if !UE_BUILD_SHIPPING 2319 | void AFWGen::refreshPreview() 2320 | { 2321 | if (WorldSize == -1) 2322 | { 2323 | PreviewPlane ->SetBoxExtent ( FVector ( 2324 | (ChunkPieceColumnCount * ChunkPieceSizeX / 2), 2325 | (ChunkPieceRowCount * ChunkPieceSizeY / 2), 2326 | GenerationMaxZFromActorZ / 2 2327 | ) 2328 | ); 2329 | } 2330 | else if (WorldSize == 0) 2331 | { 2332 | PreviewPlane ->SetBoxExtent ( FVector ( 2333 | (ViewDistance * 2 + 1) * (ChunkPieceColumnCount * ChunkPieceSizeX / 2), 2334 | (ViewDistance * 2 + 1) * (ChunkPieceRowCount * ChunkPieceSizeY / 2), 2335 | GenerationMaxZFromActorZ / 2 2336 | ) 2337 | ); 2338 | } 2339 | else 2340 | { 2341 | PreviewPlane ->SetBoxExtent ( FVector ( 2342 | ((WorldSize * ViewDistance) * 2 + 1) * (ChunkPieceColumnCount * ChunkPieceSizeX / 2), 2343 | ((WorldSize * ViewDistance) * 2 + 1) * (ChunkPieceRowCount * ChunkPieceSizeY / 2), 2344 | GenerationMaxZFromActorZ / 2 2345 | ) 2346 | ); 2347 | } 2348 | 2349 | PreviewPlane ->SetWorldLocation ( FVector ( 2350 | GetActorLocation () .X, 2351 | GetActorLocation () .Y, 2352 | GetActorLocation () .Z + (GenerationMaxZFromActorZ) / 2 2353 | ) 2354 | ); 2355 | } 2356 | #endif // WITH_EDITOR 2357 | 2358 | 2359 | 2360 | bool AFWGen::areEqual(float a, float b, float eps) 2361 | { 2362 | return fabs(a - b) < eps; 2363 | } 2364 | 2365 | void AFWGen::compareHeightDifference(AFWGChunk* pChunk, std::vector& vProcessedVertices, float& fCurrentZ, size_t iCompareToIndex, float& fSteepSlopeMinHeightDiff) 2366 | { 2367 | if (vProcessedVertices[iCompareToIndex] == false) 2368 | { 2369 | if (fabs(pChunk->vVertices[iCompareToIndex].Z - fCurrentZ) > fSteepSlopeMinHeightDiff) 2370 | { 2371 | pChunk->vVertexColors[iCompareToIndex] = FLinearColor(0.0f, 0.0f, 0.0f, 0.5f); 2372 | } 2373 | 2374 | vProcessedVertices[iCompareToIndex] = true; 2375 | } 2376 | } 2377 | 2378 | void AFWGen::SetSecondMaterialUnderWater(bool NewSecondMaterialUnderWater) 2379 | { 2380 | SecondMaterialUnderWater = NewSecondMaterialUnderWater; 2381 | } 2382 | 2383 | bool AFWGen::SetIncreasedMaterialBlendProbability(float NewIncreasedMaterialBlendProbability) 2384 | { 2385 | if (NewIncreasedMaterialBlendProbability < 0.0f || NewIncreasedMaterialBlendProbability > 1.0f) 2386 | { 2387 | return true; 2388 | } 2389 | else 2390 | { 2391 | IncreasedMaterialBlendProbability = NewIncreasedMaterialBlendProbability; 2392 | 2393 | return false; 2394 | } 2395 | } 2396 | 2397 | void AFWGen::SetApplyGroundMaterialBlend(bool bApply) 2398 | { 2399 | ApplyGroundMaterialBlend = bApply; 2400 | } 2401 | 2402 | void AFWGen::SetApplySlopeDependentBlend(bool bApply) 2403 | { 2404 | ApplySlopeDependentBlend = bApply; 2405 | } 2406 | 2407 | bool AFWGen::SetMinSlopeHeightMultiplier(float NewMinSlopeHeightMultiplier) 2408 | { 2409 | if (NewMinSlopeHeightMultiplier < 0.0f || NewMinSlopeHeightMultiplier > 1.0f) 2410 | { 2411 | return true; 2412 | } 2413 | else 2414 | { 2415 | MinSlopeHeightMultiplier = NewMinSlopeHeightMultiplier; 2416 | 2417 | return false; 2418 | } 2419 | } 2420 | 2421 | bool AFWGen::SetDivideChunkXCount(int32 DivideChunkXcount) 2422 | { 2423 | if (DivideChunkXcount < 1) 2424 | { 2425 | return true; 2426 | } 2427 | else 2428 | { 2429 | this->DivideChunkXCount = DivideChunkXcount; 2430 | 2431 | return false; 2432 | } 2433 | } 2434 | 2435 | bool AFWGen::SetDivideChunkYCount(int32 DivideChunkYcount) 2436 | { 2437 | if (DivideChunkYcount < 1) 2438 | { 2439 | return true; 2440 | } 2441 | else 2442 | { 2443 | this->DivideChunkYCount = DivideChunkYcount; 2444 | 2445 | return false; 2446 | } 2447 | } 2448 | 2449 | bool AFWGen::SetMaxOffsetByX(float fMaxOffsetByX) 2450 | { 2451 | if (fMaxOffsetByX >= 0.0f && fMaxOffsetByX <= 1.0f) 2452 | { 2453 | MaxOffsetByX = fMaxOffsetByX; 2454 | 2455 | return false; 2456 | } 2457 | else 2458 | { 2459 | return true; 2460 | } 2461 | } 2462 | 2463 | bool AFWGen::SetMaxOffsetByY(float fMaxOffsetByY) 2464 | { 2465 | if (fMaxOffsetByY >= 0.0f && fMaxOffsetByY <= 1.0f) 2466 | { 2467 | MaxOffsetByY = fMaxOffsetByY; 2468 | 2469 | return false; 2470 | } 2471 | else 2472 | { 2473 | return true; 2474 | } 2475 | } 2476 | 2477 | FWGenChunkMap::FWGenChunkMap(AFWGen* pGen) 2478 | { 2479 | pCurrentChunk = nullptr; 2480 | this->pGen = pGen; 2481 | } 2482 | 2483 | void FWGenChunkMap::loadNewChunk(long long iLoadX, long long iLoadY, long long iUnloadX, long long iUnloadY) 2484 | { 2485 | if (pGen->WorldSize != -1) 2486 | { 2487 | bool bAroundCenter = false; 2488 | 2489 | if ((iLoadX <= 1) && (iLoadX >= -1) && (iLoadY <= 1) && (iLoadY >= -1)) 2490 | { 2491 | bAroundCenter = true; 2492 | } 2493 | 2494 | 2495 | // Unload old actors. 2496 | if (pGen->pCallbackToDespawn) 2497 | { 2498 | struct params 2499 | { 2500 | int64 x; 2501 | int64 y; 2502 | }; 2503 | 2504 | params p; 2505 | p.x = iUnloadX; 2506 | p.y = iUnloadY; 2507 | 2508 | pGen->pCallbackToDespawn->pOwner->ProcessEvent( pGen->pCallbackToDespawn->pFunction, &p); 2509 | } 2510 | 2511 | 2512 | AFWGChunk* pNewChunk = pGen->generateChunk(iLoadX, iLoadY, pGen->iCurrentSectionIndex, bAroundCenter, iUnloadX, iUnloadY, true); 2513 | 2514 | if (pGen->ApplyGroundMaterialBlend) 2515 | { 2516 | pGen->blendWorldMaterialsMore(pNewChunk); 2517 | } 2518 | 2519 | if (pGen->ApplySlopeDependentBlend) 2520 | { 2521 | pGen->applySlopeDependentBlend(pNewChunk); 2522 | } 2523 | 2524 | // Update mesh. 2525 | pGen->pProcMeshComponent->UpdateMeshSection_LinearColor(pNewChunk->iSectionIndex, pNewChunk->vVertices, pNewChunk->vNormals, 2526 | pNewChunk->vUV0, pNewChunk->vVertexColors, pNewChunk->vTangents); 2527 | 2528 | 2529 | 2530 | // Move Water Plane. 2531 | 2532 | float fX = pGen->GetActorLocation().X; 2533 | float fY = pGen->GetActorLocation().Y; 2534 | 2535 | if (iLoadX != 0) 2536 | { 2537 | fX += (iLoadX * pGen->ChunkPieceColumnCount * pGen->ChunkPieceSizeX); 2538 | } 2539 | 2540 | if (iLoadY != 0) 2541 | { 2542 | fY += (iLoadY * pGen->ChunkPieceRowCount * pGen->ChunkPieceSizeY); 2543 | } 2544 | 2545 | 2546 | pGen->WaterPlane->SetWorldLocation(FVector( 2547 | fX, 2548 | fY, 2549 | pGen->GetActorLocation().Z + (pGen->GenerationMaxZFromActorZ * pGen->ZWaterLevelInWorld) 2550 | )); 2551 | 2552 | 2553 | // Do last steps. 2554 | 2555 | pGen->spawnObjects(pNewChunk); 2556 | 2557 | pGen->createTriggerBoxForChunk(pNewChunk); 2558 | } 2559 | } 2560 | 2561 | void FWGenChunkMap::addChunk(AFWGChunk* pChunk) 2562 | { 2563 | vChunks.push_back(pChunk); 2564 | } 2565 | 2566 | void FWGenChunkMap::clearWorld(UProceduralMeshComponent* pProcMeshComponent) 2567 | { 2568 | for (size_t i = 0; i < vChunks.size(); i++) 2569 | { 2570 | if (!vChunks[i]->IsValidLowLevel()) 2571 | { 2572 | continue;; 2573 | } 2574 | 2575 | if (vChunks[i]->IsPendingKill()) 2576 | { 2577 | continue; 2578 | } 2579 | 2580 | vChunks[i]->Destroy(); 2581 | vChunks[i] = nullptr; 2582 | } 2583 | 2584 | vChunks.clear(); 2585 | 2586 | pProcMeshComponent->ClearAllMeshSections(); 2587 | } 2588 | 2589 | void FWGenChunkMap::setCurrentChunk(AFWGChunk* pChunk) 2590 | { 2591 | if (pCurrentChunk) 2592 | { 2593 | if (pGen->WorldSize > 0) 2594 | { 2595 | long long iMaxCoord = pGen->WorldSize; 2596 | 2597 | if (abs(pChunk->iX) == pGen->WorldSize || abs(pChunk->iY) == pGen->WorldSize) 2598 | { 2599 | return; 2600 | } 2601 | } 2602 | 2603 | long long offsetX = pChunk->iX - pCurrentChunk->iX; 2604 | long long offsetY = pChunk->iY - pCurrentChunk->iY; 2605 | 2606 | if (offsetX != 0 && offsetY != 0) 2607 | { 2608 | return; 2609 | } 2610 | 2611 | float fChunkXSize = pGen->ChunkPieceColumnCount * pGen->ChunkPieceSizeX; 2612 | float fChunkYSize = pGen->ChunkPieceRowCount * pGen->ChunkPieceSizeY; 2613 | 2614 | mtxLoadChunks.lock(); 2615 | 2616 | 2617 | 2618 | // Move blocking volumes. 2619 | 2620 | FVector volumeLocation = pGen->pBlockingVolumeX1->GetComponentLocation(); 2621 | volumeLocation.X += offsetX * fChunkXSize; 2622 | volumeLocation.Y += offsetY * fChunkYSize; 2623 | pGen->pBlockingVolumeX1->SetWorldLocation(volumeLocation); 2624 | 2625 | volumeLocation = pGen->pBlockingVolumeX2->GetComponentLocation(); 2626 | volumeLocation.X += offsetX * fChunkXSize; 2627 | volumeLocation.Y += offsetY * fChunkYSize; 2628 | pGen->pBlockingVolumeX2->SetWorldLocation(volumeLocation); 2629 | 2630 | volumeLocation = pGen->pBlockingVolumeY1->GetComponentLocation(); 2631 | volumeLocation.X += offsetX * fChunkXSize; 2632 | volumeLocation.Y += offsetY * fChunkYSize; 2633 | pGen->pBlockingVolumeY1->SetWorldLocation(volumeLocation); 2634 | 2635 | volumeLocation = pGen->pBlockingVolumeY2->GetComponentLocation(); 2636 | volumeLocation.X += offsetX * fChunkXSize; 2637 | volumeLocation.Y += offsetY * fChunkYSize; 2638 | pGen->pBlockingVolumeY2->SetWorldLocation(volumeLocation); 2639 | 2640 | 2641 | 2642 | long long iX = pChunk->iX + (offsetX * pGen->ViewDistance); 2643 | long long iY = pChunk->iY + (offsetY * pGen->ViewDistance); 2644 | 2645 | long long iUnloadX = pCurrentChunk->iX + (-offsetX * pGen->ViewDistance); 2646 | long long iUnloadY = pCurrentChunk->iY + (-offsetY * pGen->ViewDistance); 2647 | 2648 | pCurrentChunk = pChunk; 2649 | 2650 | loadNewChunk(iX, iY, iUnloadX, iUnloadY); 2651 | 2652 | 2653 | // Generate other chunks. 2654 | 2655 | for (int32 i = 1; i <= pGen->ViewDistance; i++) 2656 | { 2657 | if (offsetX == 0) 2658 | { 2659 | loadNewChunk(iX - i, iY, iUnloadX + i, iUnloadY); 2660 | loadNewChunk(iX + i, iY, iUnloadX - i, iUnloadY); 2661 | } 2662 | else if (offsetY == 0) 2663 | { 2664 | loadNewChunk(iX, iY - i, iUnloadX, iUnloadY + i); 2665 | loadNewChunk(iX, iY + i, iUnloadX, iUnloadY - i); 2666 | } 2667 | } 2668 | 2669 | mtxLoadChunks.unlock(); 2670 | } 2671 | else 2672 | { 2673 | pCurrentChunk = pChunk; 2674 | } 2675 | } 2676 | 2677 | long long FWGenChunkMap::getCentralChunkX() 2678 | { 2679 | if (pCurrentChunk) 2680 | { 2681 | return pCurrentChunk->iX; 2682 | } 2683 | else 2684 | { 2685 | return 0; 2686 | } 2687 | } 2688 | 2689 | long long FWGenChunkMap::getCentralChunkY() 2690 | { 2691 | if (pCurrentChunk) 2692 | { 2693 | return pCurrentChunk->iY; 2694 | } 2695 | else 2696 | { 2697 | return 0; 2698 | } 2699 | } 2700 | 2701 | FWGenChunkMap::~FWGenChunkMap() 2702 | { 2703 | for (size_t i = 0; i < vChunks.size(); i++) 2704 | { 2705 | if (!vChunks[i]->IsValidLowLevel()) 2706 | { 2707 | continue;; 2708 | } 2709 | 2710 | if (vChunks[i]->IsPendingKill()) 2711 | { 2712 | continue; 2713 | } 2714 | 2715 | vChunks[i]->Destroy(); 2716 | vChunks[i] = nullptr; 2717 | } 2718 | } -------------------------------------------------------------------------------- /Source/FWorldGenerator/Private/PerlinNoise.hpp: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------- 2 | // 3 | // siv::PerlinNoise 4 | // Perlin noise library for modern C++ 5 | // 6 | // Copyright (C) 2013-2018 Ryo Suzuki 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files(the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions : 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | //---------------------------------------------------------------------------------------- 27 | 28 | # pragma once 29 | # include 30 | # include 31 | # include 32 | # include 33 | 34 | namespace siv 35 | { 36 | class PerlinNoise 37 | { 38 | private: 39 | 40 | std::uint8_t p[512]; 41 | 42 | static double Fade(double t) noexcept 43 | { 44 | return t * t * t * (t * (t * 6 - 15) + 10); 45 | } 46 | 47 | static double Lerp(double t, double a, double b) noexcept 48 | { 49 | return a + t * (b - a); 50 | } 51 | 52 | static double Grad(std::uint8_t hash, double x, double y, double z) noexcept 53 | { 54 | const std::uint8_t h = hash & 15; 55 | const double u = h < 8 ? x : y; 56 | const double v = h < 4 ? y : h == 12 || h == 14 ? x : z; 57 | return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); 58 | } 59 | 60 | public: 61 | 62 | explicit PerlinNoise(std::uint32_t seed = std::default_random_engine::default_seed) 63 | { 64 | reseed(seed); 65 | } 66 | 67 | template 68 | explicit PerlinNoise(URNG& urng) 69 | { 70 | reseed(urng); 71 | } 72 | 73 | void reseed(std::uint32_t seed) 74 | { 75 | for (size_t i = 0; i < 256; ++i) 76 | { 77 | p[i] = static_cast(i); 78 | } 79 | 80 | std::shuffle(std::begin(p), std::begin(p) + 256, std::default_random_engine(seed)); 81 | 82 | for (size_t i = 0; i < 256; ++i) 83 | { 84 | p[256 + i] = p[i]; 85 | } 86 | } 87 | 88 | template 89 | void reseed(URNG& urng) 90 | { 91 | for (size_t i = 0; i < 256; ++i) 92 | { 93 | p[i] = static_cast(i); 94 | } 95 | 96 | std::shuffle(std::begin(p), std::begin(p) + 256, urng); 97 | 98 | for (size_t i = 0; i < 256; ++i) 99 | { 100 | p[256 + i] = p[i]; 101 | } 102 | } 103 | 104 | double noise(double x) const 105 | { 106 | return noise(x, 0.0, 0.0); 107 | } 108 | 109 | double noise(double x, double y) const 110 | { 111 | return noise(x, y, 0.0); 112 | } 113 | 114 | double noise(double x, double y, double z) const 115 | { 116 | const std::int32_t X = static_cast(std::floor(x)) & 255; 117 | const std::int32_t Y = static_cast(std::floor(y)) & 255; 118 | const std::int32_t Z = static_cast(std::floor(z)) & 255; 119 | 120 | x -= std::floor(x); 121 | y -= std::floor(y); 122 | z -= std::floor(z); 123 | 124 | const double u = Fade(x); 125 | const double v = Fade(y); 126 | const double w = Fade(z); 127 | 128 | const std::int32_t A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z; 129 | const std::int32_t B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; 130 | 131 | return Lerp(w, Lerp(v, Lerp(u, Grad(p[AA], x, y, z), 132 | Grad(p[BA], x - 1, y, z)), 133 | Lerp(u, Grad(p[AB], x, y - 1, z), 134 | Grad(p[BB], x - 1, y - 1, z))), 135 | Lerp(v, Lerp(u, Grad(p[AA + 1], x, y, z - 1), 136 | Grad(p[BA + 1], x - 1, y, z - 1)), 137 | Lerp(u, Grad(p[AB + 1], x, y - 1, z - 1), 138 | Grad(p[BB + 1], x - 1, y - 1, z - 1)))); 139 | } 140 | 141 | double octaveNoise(double x, std::int32_t octaves) const 142 | { 143 | double result = 0.0; 144 | double amp = 1.0; 145 | 146 | for (std::int32_t i = 0; i < octaves; ++i) 147 | { 148 | result += noise(x) * amp; 149 | x *= 2.0; 150 | amp *= 0.5; 151 | } 152 | 153 | return result; 154 | } 155 | 156 | double octaveNoise(double x, double y, std::int32_t octaves) const 157 | { 158 | double result = 0.0; 159 | double amp = 1.0; 160 | 161 | for (std::int32_t i = 0; i < octaves; ++i) 162 | { 163 | result += noise(x, y) * amp; 164 | x *= 2.0; 165 | y *= 2.0; 166 | amp *= 0.5; 167 | } 168 | 169 | return result; 170 | } 171 | 172 | double octaveNoise(double x, double y, double z, std::int32_t octaves) const 173 | { 174 | double result = 0.0; 175 | double amp = 1.0; 176 | 177 | for (std::int32_t i = 0; i < octaves; ++i) 178 | { 179 | result += noise(x, y, z) * amp; 180 | x *= 2.0; 181 | y *= 2.0; 182 | z *= 2.0; 183 | amp *= 0.5; 184 | } 185 | 186 | return result; 187 | } 188 | 189 | double noise0_1(double x) const 190 | { 191 | return noise(x) * 0.5 + 0.5; 192 | } 193 | 194 | double noise0_1(double x, double y) const 195 | { 196 | return noise(x, y) * 0.5 + 0.5; 197 | } 198 | 199 | double noise0_1(double x, double y, double z) const 200 | { 201 | return noise(x, y, z) * 0.5 + 0.5; 202 | } 203 | 204 | double octaveNoise0_1(double x, std::int32_t octaves) const 205 | { 206 | return octaveNoise(x, octaves) * 0.5 + 0.5; 207 | } 208 | 209 | double octaveNoise0_1(double x, double y, std::int32_t octaves) const 210 | { 211 | return octaveNoise(x, y, octaves) * 0.5 + 0.5; 212 | } 213 | 214 | double octaveNoise0_1(double x, double y, double z, std::int32_t octaves) const 215 | { 216 | return octaveNoise(x, y, z, octaves) * 0.5 + 0.5; 217 | } 218 | }; 219 | } 220 | -------------------------------------------------------------------------------- /Source/FWorldGenerator/Public/FWGen.h: -------------------------------------------------------------------------------- 1 | // This file is part of the FWorldGenenerator. 2 | // Copyright Aleksandr "Flone" Tretyakov (github.com/Flone-dnb). 3 | // Licensed under the ZLib license. 4 | // Refer to the LICENSE file included. 5 | 6 | #pragma once 7 | 8 | // UE 9 | #include "CoreMinimal.h" 10 | #include "GameFramework/Actor.h" 11 | #include "ProceduralMeshComponent.h" 12 | #include "Components/BoxComponent.h" 13 | 14 | // STL 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "FWGen.generated.h" 21 | 22 | 23 | class UStaticMeshComponent; 24 | 25 | 26 | #define VERSION_SIZE 20 27 | // Max size: 20 chars. 28 | #define FWGEN_VERSION "FWG 1.0.2" 29 | 30 | // -------------------------------------------------------------------------------------------------------- 31 | // -------------------------------------------------------------------------------------------------------- 32 | // -------------------------------------------------------------------------------------------------------- 33 | 34 | class FWGenChunkMap; 35 | class AFWGChunk; 36 | class FWGCallback; 37 | 38 | UCLASS() 39 | class FWORLDGENERATOR_API AFWGen : public AActor 40 | { 41 | GENERATED_BODY() 42 | 43 | public: 44 | AFWGen(); 45 | 46 | ~AFWGen(); 47 | 48 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 49 | bool BindFunctionToSpawn(UObject* FunctionOwner, FString FunctionName, float Layer, 50 | float ProbabilityToSpawn, bool IsBlocking); 51 | 52 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 53 | bool BindFunctionToDespawnActors(UObject* FunctionOwner, FString FunctionName); 54 | 55 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 56 | void UnBindFunctionToSpawn(FString FunctionName); 57 | 58 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 59 | void AddOverlapToActorClass(UClass* OverlapToClass); 60 | 61 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 62 | void RemoveOverlapToActorClass(UClass* OverlapToClass); 63 | 64 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 65 | FVector GetFreeCellLocation(float Layer, bool SetBlocking = true); 66 | 67 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 68 | void SaveWorldParamsToFile(FString PathToFile); 69 | 70 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 71 | void LoadWorldParamsFromFile(FString PathToFile); 72 | 73 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 74 | void GenerateWorld(int64 iCentralChunkX = 0, int64 iCentralChunkY = 0, AActor* Character = nullptr); 75 | 76 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 77 | int64 GetCentralChunkX(); 78 | 79 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 80 | int64 GetCentralChunkY(); 81 | 82 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 83 | int64 GetChunkXByLocation(FVector Location); 84 | 85 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator") 86 | int64 GetChunkYByLocation(FVector Location); 87 | 88 | 89 | // "Set" functions 90 | 91 | // Chunks 92 | 93 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 94 | bool SetChunkPieceRowCount(int32 NewChunkPieceRowCount); 95 | 96 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 97 | bool SetChunkPieceColumnCount(int32 NewChunkPieceColumnCount); 98 | 99 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 100 | bool SetChunkPieceSizeX(float NewChunkPieceSizeX); 101 | 102 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 103 | bool SetChunkPieceSizeY(float NewChunkPieceSizeY); 104 | 105 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 106 | bool SetViewDistance(int32 NewViewDistance); 107 | 108 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Chunks") 109 | void SetLoadUnloadChunkMaxZ(float NewLoadUnloadChunkMaxZ); 110 | 111 | 112 | // Generation 113 | 114 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Generation") 115 | bool SetGenerationFrequency(float NewGenerationFrequency); 116 | 117 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Generation") 118 | bool SetGenerationOctaves(int32 NewGenerationOctaves); 119 | 120 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Generation") 121 | bool SetGenerationSeed(int32 NewGenerationSeed); 122 | 123 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Generation") 124 | bool SetGenerationMaxZFromActorZ(float NewGenerationMaxZFromActorZ); 125 | 126 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Generation") 127 | void SetInvertWorld(bool InvertWorld); 128 | 129 | 130 | // World 131 | 132 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | World") 133 | bool SetWorldSize(int32 NewWorldSize); 134 | 135 | 136 | // Ground 137 | 138 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground") 139 | void SetGroundMaterial(UMaterialInterface* NewGroundMaterial); 140 | 141 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground") 142 | bool SetFirstMaterialMaxRelativeHeight(float NewFirstMaterialMaxRelativeHeight); 143 | 144 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground") 145 | bool SetSecondMaterialMaxRelativeHeight(float NewSecondMaterialMaxRelativeHeight); 146 | 147 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground") 148 | bool SetMaterialHeightMaxDeviation(float NewMaterialHeightMaxDeviation); 149 | 150 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground") 151 | bool SetTerrainCutHeightFromActorZ(float NewTerrainCutHeightFromActorZ); 152 | 153 | 154 | 155 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground Material Blend") 156 | bool SetFirstMaterialOnOtherProbability(float FirstOnSecond, float FirstOnThird); 157 | 158 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground Material Blend") 159 | bool SetSecondMaterialOnOtherProbability(float SecondOnFirst, float SecondOnThird); 160 | 161 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground Material Blend") 162 | bool SetThirdMaterialOnOtherProbability(float ThirdOnFirst, float ThirdOnSecond); 163 | 164 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Ground Material Blend") 165 | bool SetIncreasedMaterialBlendProbability(float NewIncreasedMaterialBlendProbability); 166 | 167 | 168 | // Water 169 | 170 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Water") 171 | void SetCreateWater(bool CreateWater); 172 | 173 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Water") 174 | void SetSecondMaterialUnderWater(bool NewSecondMaterialUnderWater); 175 | 176 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Water") 177 | bool SetZWaterLevelInWorld(float NewZWaterLevelInWorld); 178 | 179 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Water") 180 | bool SetWaterSize(int32 NewWaterSize); 181 | 182 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Water") 183 | void SetWaterMaterial(UMaterialInterface* NewWaterMaterial); 184 | 185 | 186 | 187 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Additional Steps") 188 | void SetApplyGroundMaterialBlend(bool bApply); 189 | 190 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Additional Steps") 191 | void SetApplySlopeDependentBlend(bool bApply); 192 | 193 | 194 | 195 | UFUNCTION(BlueprintCallable, Category = "FWorldGenerator | Slope Dependent Blend") 196 | bool SetMinSlopeHeightMultiplier(float NewMinSlopeHeightMultiplier); 197 | 198 | 199 | 200 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 201 | bool SetDivideChunkXCount(int32 DivideChunkXCount); 202 | 203 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 204 | bool SetDivideChunkYCount(int32 DivideChunkYCount); 205 | 206 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 207 | bool SetMaxOffsetByX(float fMaxOffsetByX); 208 | 209 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 210 | bool SetMaxOffsetByY(float fMaxOffsetByY); 211 | 212 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 213 | bool SetMaxRotation(float fMaxRotation); 214 | 215 | UFUNCTION(BlueprintCallable, Category = "Spawning Objects") 216 | void SetMaxZDiffInCell(float fNewMaxZDiffInCell); 217 | 218 | 219 | #if !UE_BUILD_SHIPPING 220 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 221 | virtual void PostEditMove (bool bFinished); 222 | #endif // WITH_EDITOR 223 | 224 | 225 | //#if !UE_BUILD_SHIPPING 226 | #if WITH_EDITORONLY_DATA 227 | 228 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Preview") 229 | bool ComplexPreview = false; 230 | 231 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Preview") 232 | bool DrawChunkBounds = false; 233 | 234 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Save / Load") 235 | FString FWGenVersion = FWGEN_VERSION; 236 | 237 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Save / Load") 238 | bool ReadParamsFromFileRightNow = false; 239 | 240 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Save / Load") 241 | bool SaveParamsToFileRightNow = false; 242 | 243 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Save / Load") 244 | FString PathToSaveFile = L""; 245 | 246 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Save / Load") 247 | bool LastSaveLoadOperationStatus = false; 248 | #endif // WITH_EDITOR 249 | 250 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 251 | int32 ChunkPieceRowCount = 200; 252 | 253 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 254 | int32 ChunkPieceColumnCount = 200; 255 | 256 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 257 | float ChunkPieceSizeX = 300.0f; 258 | 259 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 260 | float ChunkPieceSizeY = 300.0f; 261 | 262 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 263 | int32 ViewDistance = 1; 264 | 265 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chunks") 266 | float LoadUnloadChunkMaxZ = 200000.0f; 267 | 268 | 269 | 270 | 271 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation") 272 | float GenerationFrequency = 0.55f; 273 | 274 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation") 275 | int32 GenerationOctaves = 7; 276 | 277 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation") 278 | int32 GenerationSeed = 0; 279 | 280 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation") 281 | float GenerationMaxZFromActorZ = 45000.0f; 282 | 283 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generation") 284 | bool InvertWorld = false; 285 | 286 | 287 | 288 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "World") 289 | int32 WorldSize = 0; 290 | 291 | 292 | 293 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground") 294 | UMaterialInterface* GroundMaterial; 295 | 296 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground") 297 | float FirstMaterialMaxRelativeHeight = 0.5f; 298 | 299 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground") 300 | float SecondMaterialMaxRelativeHeight = 0.7f; 301 | 302 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground") 303 | float MaterialHeightMaxDeviation = 0.08f; 304 | 305 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground") 306 | float TerrainCutHeightFromActorZ = 1.0f; 307 | 308 | 309 | 310 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Water") 311 | bool CreateWater = false; 312 | 313 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Water") 314 | bool SecondMaterialUnderWater = true; 315 | 316 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Water") 317 | float ZWaterLevelInWorld = 0.25f; 318 | 319 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Water") 320 | int32 WaterSize = 10; 321 | 322 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Water") 323 | UMaterialInterface* WaterMaterial; 324 | 325 | 326 | 327 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Additional Steps") 328 | bool ApplyGroundMaterialBlend = true; 329 | 330 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Additional Steps") 331 | bool ApplySlopeDependentBlend = true; 332 | 333 | 334 | 335 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 336 | float FirstMaterialOnSecondProbability = 0.02f; 337 | 338 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 339 | float FirstMaterialOnThirdProbability = 0.002f; 340 | 341 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 342 | float SecondMaterialOnFirstProbability = 0.008f; 343 | 344 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 345 | float SecondMaterialOnThirdProbability = 0.02f; 346 | 347 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 348 | float ThirdMaterialOnFirstProbability = 0.0f; 349 | 350 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 351 | float ThirdMaterialOnSecondProbability = 0.004f; 352 | 353 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground Material Blend") 354 | float IncreasedMaterialBlendProbability = 0.16f; 355 | 356 | 357 | 358 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Slope Dependent Blend") 359 | float MinSlopeHeightMultiplier = 0.006f; 360 | 361 | 362 | 363 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 364 | int32 DivideChunkXCount = 700; 365 | 366 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 367 | int32 DivideChunkYCount = 700; 368 | 369 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 370 | float MaxOffsetByX = 1.0f; 371 | 372 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 373 | float MaxOffsetByY = 1.0f; 374 | 375 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 376 | float MaxRotation = 360.0f; 377 | 378 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning Objects") 379 | float MaxZDiffInCell = 45.0f; 380 | 381 | protected: 382 | 383 | virtual void BeginPlay() override; 384 | 385 | 386 | 387 | // -------------------------------------------------------------- 388 | 389 | 390 | 391 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Default) 392 | USceneComponent* pRootNode; 393 | 394 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Default) 395 | UStaticMeshComponent* WaterPlane; 396 | 397 | #if WITH_EDITORONLY_DATA 398 | //#if !UE_BUILD_SHIPPING 399 | UPROPERTY() 400 | UBoxComponent* PreviewPlane; 401 | #endif // WITH_EDITOR 402 | 403 | 404 | private: 405 | 406 | AFWGChunk* generateChunk (long long iX, long long iY, int32 iSectionIndex, bool bAroundCenter, 407 | long long iUnloadX = 0, long long iUnloadY = 0, bool bUnload = false); 408 | void generateSeed (); 409 | float pickVertexMaterial (double height, std::uniform_real_distribution* pUrd, std::mt19937_64* pRnd, float* pfLayerTypeWithoutRnd = nullptr); 410 | void blendWorldMaterialsMore (AFWGChunk* pOnlyForThisChunk = nullptr); 411 | void applySlopeDependentBlend (AFWGChunk* pOnlyForThisChunk = nullptr); 412 | void spawnObjects (AFWGChunk* pOnlyForThisChunk = nullptr); 413 | void createTriggerBoxForChunk (AFWGChunk* pChunk); 414 | 415 | bool areEqual (float a, float b, float eps); 416 | void compareHeightDifference (AFWGChunk* pChunk, std::vector& vProcessedVertices, float& fCurrentZ, size_t iCompareToIndex, float& fSteepSlopeMinHeightDiff); 417 | 418 | #if WITH_EDITORONLY_DATA 419 | //#if !UE_BUILD_SHIPPING 420 | void refreshPreview(); 421 | #endif // WITH_EDITOR 422 | 423 | std::vector vObjectsToSpawn; 424 | std::vector vOverlapToClasses; 425 | 426 | 427 | UPROPERTY() 428 | UProceduralMeshComponent* pProcMeshComponent; 429 | 430 | 431 | UPROPERTY() 432 | UBoxComponent* pBlockingVolumeX1; 433 | UPROPERTY() 434 | UBoxComponent* pBlockingVolumeX2; 435 | UPROPERTY() 436 | UBoxComponent* pBlockingVolumeY1; 437 | UPROPERTY() 438 | UBoxComponent* pBlockingVolumeY2; 439 | 440 | 441 | int32 iCurrentSectionIndex; 442 | 443 | 444 | FWGenChunkMap* pChunkMap; 445 | FWGCallback* pCallbackToDespawn; 446 | 447 | 448 | int32 iGeneratedSeed; 449 | 450 | 451 | bool bWorldCreated; 452 | 453 | 454 | friend class FWGenChunkMap; 455 | }; 456 | 457 | // -------------------------------------------------------------------------------------------------------- 458 | // -------------------------------------------------------------------------------------------------------- 459 | // -------------------------------------------------------------------------------------------------------- 460 | 461 | class FWGCallback 462 | { 463 | public: 464 | UObject* pOwner; 465 | UFunction* pFunction; 466 | 467 | float fLayer; 468 | float fProbabilityToSpawn; 469 | FString sFunctionName; 470 | 471 | bool bIsBlocking; 472 | }; 473 | 474 | // -------------------------------------------------------------------------------------------------------- 475 | // -------------------------------------------------------------------------------------------------------- 476 | // -------------------------------------------------------------------------------------------------------- 477 | 478 | 479 | class FWGenChunkMap 480 | { 481 | public: 482 | 483 | FWGenChunkMap(AFWGen* pGen); 484 | 485 | void addChunk(AFWGChunk* pChunk); 486 | 487 | void clearWorld(UProceduralMeshComponent* pProcMeshComponent); 488 | 489 | void setCurrentChunk(AFWGChunk* pChunk); 490 | 491 | long long getCentralChunkX(); 492 | long long getCentralChunkY(); 493 | 494 | ~FWGenChunkMap(); 495 | 496 | std::vector vChunks; 497 | 498 | private: 499 | 500 | void loadNewChunk(long long iLoadX, long long iLoadY, long long iUnloadX, long long iUnloadY); 501 | 502 | AFWGChunk* pCurrentChunk; 503 | AFWGen* pGen; 504 | 505 | std::mutex mtxLoadChunks; 506 | }; 507 | -------------------------------------------------------------------------------- /pics/Example BindFunctionToSpawn 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flone-dnb/FWorldGenerator/77c712d1001ec178c9b336767bd740c42b76a003/pics/Example BindFunctionToSpawn 1.png -------------------------------------------------------------------------------- /pics/Example BindFunctionToSpawn 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flone-dnb/FWorldGenerator/77c712d1001ec178c9b336767bd740c42b76a003/pics/Example BindFunctionToSpawn 2.jpg -------------------------------------------------------------------------------- /pics/Example Ground Material.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flone-dnb/FWorldGenerator/77c712d1001ec178c9b336767bd740c42b76a003/pics/Example Ground Material.jpg -------------------------------------------------------------------------------- /pics/FWorldGenerator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flone-dnb/FWorldGenerator/77c712d1001ec178c9b336767bd740c42b76a003/pics/FWorldGenerator.jpg --------------------------------------------------------------------------------