├── Source ├── Landscape_1 │ ├── Private │ │ ├── FoliageTileBlockingVolume.cpp │ │ ├── TileUtils.cpp │ │ ├── SimplexNoise.h │ │ ├── TileActor.cpp │ │ ├── SimplexNoise.cpp │ │ ├── FCalculateTileTask.cpp │ │ ├── FoliageTileActor.cpp │ │ ├── FoliageGroupTileActor.cpp │ │ └── ForestTileActor.cpp │ ├── Landscape_1.h │ ├── Landscape_1.cpp │ ├── Public │ │ ├── TileUtils.h │ │ ├── FoliageTileBlockingVolume.h │ │ ├── FCalculateTileTask.h │ │ ├── FoliageGroupTileActor.h │ │ ├── TileActor.h │ │ ├── FoliageTileActor.h │ │ ├── FoliageGroup.h │ │ └── ForestTileActor.h │ └── Landscape_1.Build.cs ├── Landscape_1.Target.cs └── Landscape_1Editor.Target.cs ├── .gitignore ├── Landscape_1.uproject ├── README.md ├── LICENSE └── Landscape_1.sln /Source/Landscape_1/Private/FoliageTileBlockingVolume.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "FoliageTileBlockingVolume.h" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | Intermediate/ 3 | Content/ 4 | Saved/ 5 | Binaries/ 6 | Config/ 7 | DerivedDataCache/ 8 | *.VC.db 9 | *.VC.opendb 10 | -------------------------------------------------------------------------------- /Source/Landscape_1/Landscape_1.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | 7 | -------------------------------------------------------------------------------- /Source/Landscape_1/Landscape_1.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "Landscape_1.h" 4 | 5 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, Landscape_1, "Landscape_1" ); 6 | -------------------------------------------------------------------------------- /Landscape_1.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "4.15", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "Landscape_1", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "Engine" 13 | ] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Public/TileUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const FVector emptyFVector = FVector(0.0f, 0.0f, -100000.0f); 4 | 5 | class LANDSCAPE_1_API TileUtils 6 | { 7 | public: 8 | static uint32 Hash(uint32 a); 9 | static FVector GetCurrentCameraLocation(UWorld* world); 10 | static bool IsEmptyFVector(FVector location); 11 | }; 12 | -------------------------------------------------------------------------------- /Source/Landscape_1/Public/FoliageTileBlockingVolume.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GameFramework/Volume.h" 4 | #include "FoliageTileBlockingVolume.generated.h" 5 | 6 | UCLASS() 7 | class LANDSCAPE_1_API AFoliageTileBlockingVolume : public AVolume 8 | { 9 | GENERATED_BODY() 10 | 11 | public: 12 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 13 | TArray FoliageLayers; 14 | }; 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Actor for dynamicly spawning objects around the player in Unreal Engine. 2 | Personal project to learn a bit more C++ and play around with the engine. 3 | 4 | * **TileActor** is the base class that warps the tiles around the viewport. 5 | 6 | * **FoliageTileActor** draws procedurally placed meshes. 7 | 8 | ![](http://imgur.com/4MdYnwy.png) 9 | 10 | Forum thread: https://forums.unrealengine.com/showthread.php?140814-Procedurally-placed-objects 11 | -------------------------------------------------------------------------------- /Source/Landscape_1.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class Landscape_1Target : TargetRules 7 | { 8 | public Landscape_1Target(TargetInfo Target) 9 | { 10 | Type = TargetType.Game; 11 | } 12 | 13 | // 14 | // TargetRules interface. 15 | // 16 | 17 | public override void SetupBinaries( 18 | TargetInfo Target, 19 | ref List OutBuildBinaryConfigurations, 20 | ref List OutExtraModuleNames 21 | ) 22 | { 23 | OutExtraModuleNames.AddRange( new string[] { "Landscape_1" } ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/Landscape_1Editor.Target.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | public class Landscape_1EditorTarget : TargetRules 7 | { 8 | public Landscape_1EditorTarget(TargetInfo Target) 9 | { 10 | Type = TargetType.Editor; 11 | } 12 | 13 | // 14 | // TargetRules interface. 15 | // 16 | 17 | public override void SetupBinaries( 18 | TargetInfo Target, 19 | ref List OutBuildBinaryConfigurations, 20 | ref List OutExtraModuleNames 21 | ) 22 | { 23 | OutExtraModuleNames.AddRange( new string[] { "Landscape_1" } ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/Landscape_1/Private/TileUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "TileUtils.h" 3 | 4 | uint32 TileUtils::Hash(uint32 a) 5 | { 6 | a = (a ^ 61) ^ (a >> 16); 7 | a = a + (a << 3); 8 | a = a ^ (a >> 4); 9 | a = a * 0x27d4eb2d; 10 | a = a ^ (a >> 15); 11 | return a; 12 | } 13 | 14 | FVector TileUtils::GetCurrentCameraLocation(UWorld* world) 15 | { 16 | if (world == nullptr) 17 | return emptyFVector; 18 | 19 | auto player = world->GetFirstLocalPlayerFromController(); 20 | 21 | if (player != NULL) 22 | return player->LastViewLocation; 23 | 24 | if (world->ViewLocationsRenderedLastFrame.Num() > 0) 25 | return world->ViewLocationsRenderedLastFrame[0]; 26 | 27 | return emptyFVector; 28 | } 29 | 30 | bool TileUtils::IsEmptyFVector(FVector location) 31 | { 32 | return location.X == emptyFVector.X && 33 | location.Y == emptyFVector.Y && 34 | location.Z == emptyFVector.Z; 35 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Landscape_1.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class Landscape_1 : ModuleRules 6 | { 7 | public Landscape_1(TargetInfo Target) 8 | { 9 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Landscape" }); 10 | 11 | PrivateDependencyModuleNames.AddRange(new string[] { }); 12 | 13 | // Uncomment if you are using Slate UI 14 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 15 | 16 | // Uncomment if you are using online features 17 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 18 | 19 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 20 | 21 | MinFilesUsingPrecompiledHeaderOverride = 1; 22 | bFasterWithoutUnity = true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Source/Landscape_1/Private/SimplexNoise.h: -------------------------------------------------------------------------------- 1 | /* 2 | SimplexNoise 1.0.0 3 | ----- 4 | DevDad - Afan Olovcic @ www.art-and-code.com - 08/12/2015 5 | This algorithm was originally designed by Ken Perlin, but my code has been 6 | adapted and extended from the implementation written by Stefan Gustavson (stegu@itn.liu.se) 7 | and modified to fit to Unreal Engine 4 8 | * This is a clean, fast, modern and free Perlin Simplex noise function. 9 | * If we change float to double it could be even faster but there is no double type in Blueprint 10 | * All Public Functions are BlueprintCallable so they can be used in every blueprint 11 | From DevDad and Dedicated to you and Unreal Community 12 | Use it free for what ever you want 13 | I only request that you mention me in the credits for your game in the way that feels most appropriate to you. 14 | */ 15 | 16 | #pragma once 17 | 18 | class LANDSCAPE_1_API USimplexNoise 19 | { 20 | public: 21 | static void setNoiseSeed(const int32& newSeed); 22 | static float SimplexNoise2D(float x, float y); 23 | private: 24 | static unsigned char perm[]; 25 | static float grad(int hash, float x, float y); 26 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tore Lervik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/Landscape_1/Public/FCalculateTileTask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FoliageGroup.h" 4 | 5 | DECLARE_DELEGATE_OneParam(FTileTaskResultDelegate, FTileTaskResult); 6 | 7 | class LANDSCAPE_1_API FCalculateTileTask : public FNonAbandonableTask 8 | { 9 | friend class FAutoDeleteAsyncTask; 10 | 11 | public: 12 | FCalculateTileTask(FVector location, int tileIndex, TArray groups, float tileSize, uint32 seed, FTileTaskResultDelegate taskResultDelegate) 13 | { 14 | Location = location; 15 | TileIndex = tileIndex; 16 | Groups = groups; 17 | TileSize = tileSize; 18 | Seed = seed; 19 | TaskResultDelegate = taskResultDelegate; 20 | } 21 | 22 | protected: 23 | FVector Location; 24 | int TileIndex; 25 | TArray Groups; 26 | float TileSize; 27 | uint32 Seed; 28 | FTileTaskResultDelegate TaskResultDelegate; 29 | 30 | void DoWork(); 31 | 32 | FORCEINLINE TStatId GetStatId() const 33 | { 34 | RETURN_QUICK_DECLARE_CYCLE_STAT(FCalculateTileTask, STATGROUP_ThreadPoolAsyncTasks); 35 | } 36 | 37 | private: 38 | float GetFallOffSpawnChance(FFoliageGroupSpawnNoise& noise, float noiseValue); 39 | FItemSpawnSpace CalculateSpawnSpace(GridArrayType& collisionGrid, int size, int x, int y, int spacing); 40 | void Spawn(GridArrayType& collisionGrid, int size, int x, int y, int width); 41 | }; 42 | -------------------------------------------------------------------------------- /Source/Landscape_1/Public/FoliageGroupTileActor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TileActor.h" 4 | #include "FoliageGroup.h" 5 | #include "FoliageTileBlockingVolume.h" 6 | #include "Components/HierarchicalInstancedStaticMeshComponent.h" 7 | #include "FoliageGroupTileActor.generated.h" 8 | 9 | 10 | UCLASS() 11 | class UFoliageGroupTileItem : public UObject 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | UPROPERTY() 17 | TArray MeshComponents; 18 | }; 19 | 20 | UCLASS() 21 | class UFoliageGroupTileGroup : public UObject 22 | { 23 | GENERATED_BODY() 24 | 25 | public: 26 | UPROPERTY() 27 | TArray Items; 28 | }; 29 | 30 | UCLASS() 31 | class UFoliageGroupTile : public UObject 32 | { 33 | GENERATED_BODY() 34 | 35 | public: 36 | UPROPERTY() 37 | TArray Groups; 38 | }; 39 | 40 | UCLASS() 41 | class LANDSCAPE_1_API AFoliageGroupTileActor : public ATileActor 42 | { 43 | GENERATED_BODY() 44 | 45 | public: 46 | AFoliageGroupTileActor(); 47 | 48 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 49 | TArray Groups; 50 | 51 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override; 52 | virtual void Tick(float DeltaSeconds) override; 53 | 54 | protected: 55 | virtual void UpdateTile(int32 x, int32 y, FVector location) override; 56 | virtual void Load() override; 57 | virtual void Unload() override; 58 | 59 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Advanced") 60 | int32 Seed; 61 | 62 | private: 63 | void TaskResultCompleted(FTileTaskResult tileTaskResult); 64 | void ClearInstances(UFoliageGroupTile* tile); 65 | FTransform GetTransform(FVector location, uint32 seed, TArray actorsToIgnore); 66 | int GetClosestTileToRender(FVector currentLocation); 67 | 68 | UPROPERTY(transient) 69 | TArray FoliageGroupTiles; 70 | 71 | UPROPERTY(transient) 72 | TArray TileTaskResults; 73 | }; 74 | -------------------------------------------------------------------------------- /Source/Landscape_1/Public/TileActor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GameFramework/Actor.h" 4 | #include "TileActor.generated.h" 5 | 6 | USTRUCT() 7 | struct FTile 8 | { 9 | GENERATED_USTRUCT_BODY() 10 | 11 | public: 12 | UPROPERTY() 13 | FVector Location; 14 | 15 | UPROPERTY() 16 | FVector NewLocation; 17 | 18 | UPROPERTY() 19 | bool ShouldUpdate; 20 | 21 | FTile() 22 | { 23 | } 24 | }; 25 | 26 | USTRUCT() 27 | struct FTileInfo 28 | { 29 | GENERATED_USTRUCT_BODY() 30 | 31 | public: 32 | UPROPERTY() 33 | int32 X; 34 | 35 | UPROPERTY() 36 | int32 Y; 37 | 38 | FTileInfo() 39 | { 40 | X = 0; 41 | Y = 0; 42 | } 43 | 44 | FTileInfo(int32 x, int32 y) 45 | { 46 | X = x; 47 | Y = y; 48 | } 49 | }; 50 | 51 | UCLASS() 52 | class LANDSCAPE_1_API ATileActor : public AActor 53 | { 54 | GENERATED_BODY() 55 | 56 | public: 57 | ATileActor(); 58 | virtual void BeginPlay() override; 59 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 60 | virtual void Tick(float DeltaSeconds) override; 61 | virtual bool ShouldTickIfViewportsOnly() const override; 62 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override; 63 | 64 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Advanced") 65 | bool RenderInEditor; 66 | 67 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Spawning") 68 | float Radius; 69 | 70 | protected: 71 | UPROPERTY(transient) 72 | int32 Size; 73 | 74 | UPROPERTY(transient) 75 | bool IsLoaded; 76 | 77 | UPROPERTY(transient) 78 | bool IsLoadedFromBeginPlay; 79 | 80 | int32 GetIndex(int32 x, int32 y); 81 | virtual void UpdateTile(int32 x, int32 y, FVector location); 82 | virtual void PostUpdateTiles(); 83 | virtual void Load(); 84 | virtual void Reload(); 85 | virtual void Unload(); 86 | 87 | private: 88 | UPROPERTY(transient) 89 | int32 CurrentTileX; 90 | 91 | UPROPERTY(transient) 92 | int32 CurrentTileY; 93 | 94 | UPROPERTY(transient) 95 | FVector CurrentTileLocation; 96 | 97 | UPROPERTY(transient) 98 | TArray Tiles; 99 | 100 | UPROPERTY(transient) 101 | int32 CurrentUpdateIndex; 102 | 103 | FVector GetTileLocation(int32 x, int32 y); 104 | int32 ConvertTileIndex(int32 index); 105 | FTileInfo GetClosestTileToUpdate(FVector location); 106 | }; 107 | -------------------------------------------------------------------------------- /Source/Landscape_1/Public/FoliageTileActor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TileActor.h" 4 | #include "FoliageTileBlockingVolume.h" 5 | #include "Components/HierarchicalInstancedStaticMeshComponent.h" 6 | #include "FoliageTileActor.generated.h" 7 | 8 | USTRUCT() 9 | struct FFoliageSpawnNoise 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | 13 | public: 14 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "1", UIMin = "1")) 15 | int32 NoiseSize; 16 | 17 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 18 | float Min; 19 | 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 21 | float Max; 22 | 23 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 24 | int32 Seed; 25 | 26 | FFoliageSpawnNoise() 27 | { 28 | NoiseSize = 1000; 29 | Min = 0.0f; 30 | Max = 1.0f; 31 | Seed = 0; 32 | } 33 | }; 34 | 35 | USTRUCT() 36 | struct FFoliageScaleNoise 37 | { 38 | GENERATED_USTRUCT_BODY() 39 | 40 | public: 41 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "1", UIMin = "1")) 42 | int32 NoiseSize; 43 | 44 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 45 | float Min; 46 | 47 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 48 | float Max; 49 | 50 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 51 | int32 Seed; 52 | 53 | FFoliageScaleNoise() 54 | { 55 | NoiseSize = 1000; 56 | Min = 1.0f; 57 | Max = 1.0f; 58 | Seed = 0; 59 | } 60 | }; 61 | 62 | UCLASS() 63 | class UFoliageTile : public UObject 64 | { 65 | GENERATED_BODY() 66 | 67 | public: 68 | UPROPERTY(transient) 69 | TArray MeshComponents; 70 | }; 71 | 72 | UCLASS() 73 | class LANDSCAPE_1_API AFoliageTileActor : public ATileActor 74 | { 75 | GENERATED_BODY() 76 | 77 | public: 78 | AFoliageTileActor(); 79 | 80 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 81 | UStaticMesh* Mesh; 82 | 83 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0"), Category = "Mesh") 84 | FFoliageScaleNoise Scale; 85 | 86 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 87 | bool AlignWithSlope; 88 | 89 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 90 | bool Collision; 91 | 92 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 93 | uint32 CastShadow : 1; 94 | 95 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 96 | bool AffectDistanceFieldLighting; 97 | 98 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0"), Category = "Spawning") 99 | float DistanceBetween; 100 | 101 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0"), Category = "Spawning") 102 | float OffsetFactor; 103 | 104 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0"), Category = "Spawning") 105 | float SpawnChance; 106 | 107 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Spawning") 108 | FName Layer; 109 | 110 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Spawning") 111 | TArray SpawnNoise; 112 | 113 | /** A value of 0.0 causes this to be calculated to 50 % of the radius. */ 114 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0"), Category = "Spawning") 115 | float MinCullDistance; 116 | 117 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Advanced") 118 | int32 Seed; 119 | 120 | protected: 121 | virtual void UpdateTile(int32 x, int32 y, FVector location) override; 122 | virtual void Load() override; 123 | virtual void Unload() override; 124 | virtual bool ShouldExport() override; 125 | 126 | private: 127 | UPROPERTY(transient) 128 | TArray FoliageTiles; 129 | 130 | UPROPERTY(transient) 131 | TArray BlockingVolumes; 132 | 133 | uint32 Hash(uint32 a); 134 | FTransform GetTransform(FVector location, uint32 seed, TArray actorsToIgnore); 135 | }; 136 | -------------------------------------------------------------------------------- /Landscape_1.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{72C848A1-2377-485D-91D0-8609027D56F3}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Games", "Games", "{DD9016F4-0760-4974-9C27-3CED69A52059}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE4", "Intermediate\ProjectFiles\UE4.vcxproj", "{F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Landscape_1", "Intermediate\ProjectFiles\Landscape_1.vcxproj", "{46A5F767-62BA-4A3B-9EF2-D86114467883}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | DebugGame Editor|Win32 = DebugGame Editor|Win32 17 | DebugGame Editor|Win64 = DebugGame Editor|Win64 18 | DebugGame|Win32 = DebugGame|Win32 19 | DebugGame|Win64 = DebugGame|Win64 20 | Development Editor|Win32 = Development Editor|Win32 21 | Development Editor|Win64 = Development Editor|Win64 22 | Development|Win32 = Development|Win32 23 | Development|Win64 = Development|Win64 24 | Shipping|Win32 = Shipping|Win32 25 | Shipping|Win64 = Shipping|Win64 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32 29 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame Editor|Win32.Build.0 = BuiltWithUnrealBuildTool|Win32 30 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32 31 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame Editor|Win64.Build.0 = BuiltWithUnrealBuildTool|Win32 32 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32 33 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame|Win32.Build.0 = BuiltWithUnrealBuildTool|Win32 34 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32 35 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.DebugGame|Win64.Build.0 = BuiltWithUnrealBuildTool|Win32 36 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32 37 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development Editor|Win32.Build.0 = BuiltWithUnrealBuildTool|Win32 38 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32 39 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development Editor|Win64.Build.0 = BuiltWithUnrealBuildTool|Win32 40 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32 41 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development|Win32.Build.0 = BuiltWithUnrealBuildTool|Win32 42 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32 43 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Development|Win64.Build.0 = BuiltWithUnrealBuildTool|Win32 44 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Shipping|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32 45 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Shipping|Win32.Build.0 = BuiltWithUnrealBuildTool|Win32 46 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32 47 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4}.Shipping|Win64.Build.0 = BuiltWithUnrealBuildTool|Win32 48 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame Editor|Win32.ActiveCfg = DebugGame_Editor|Win32 49 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64 50 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64 51 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame|Win32.ActiveCfg = DebugGame_Game|Win32 52 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame|Win32.Build.0 = DebugGame_Game|Win32 53 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame|Win64.ActiveCfg = DebugGame_Game|x64 54 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.DebugGame|Win64.Build.0 = DebugGame_Game|x64 55 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development Editor|Win32.ActiveCfg = Development_Editor|Win32 56 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development Editor|Win64.ActiveCfg = Development_Editor|x64 57 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development Editor|Win64.Build.0 = Development_Editor|x64 58 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development|Win32.ActiveCfg = Development_Game|Win32 59 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development|Win32.Build.0 = Development_Game|Win32 60 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development|Win64.ActiveCfg = Development_Game|x64 61 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Development|Win64.Build.0 = Development_Game|x64 62 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Shipping|Win32.ActiveCfg = Shipping_Game|Win32 63 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Shipping|Win32.Build.0 = Shipping_Game|Win32 64 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Shipping|Win64.ActiveCfg = Shipping_Game|x64 65 | {46A5F767-62BA-4A3B-9EF2-D86114467883}.Shipping|Win64.Build.0 = Shipping_Game|x64 66 | EndGlobalSection 67 | GlobalSection(SolutionProperties) = preSolution 68 | HideSolutionNode = FALSE 69 | EndGlobalSection 70 | GlobalSection(NestedProjects) = preSolution 71 | {F61C2E11-C4AE-4C0C-8D9A-6816D9236AD4} = {72C848A1-2377-485D-91D0-8609027D56F3} 72 | {46A5F767-62BA-4A3B-9EF2-D86114467883} = {DD9016F4-0760-4974-9C27-3CED69A52059} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /Source/Landscape_1/Private/TileActor.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "TileUtils.h" 3 | #include "TileActor.h" 4 | 5 | ATileActor::ATileActor() 6 | { 7 | RenderInEditor = false; 8 | Size = 11; 9 | PrimaryActorTick.bCanEverTick = true; 10 | Radius = 10000.0f; 11 | 12 | CurrentTileX = 1; 13 | CurrentTileY = 1; 14 | CurrentTileLocation = FVector(-1000000.0f, -1000000.0f, -1000000.0f); 15 | IsLoaded = false; 16 | } 17 | 18 | void ATileActor::BeginPlay() 19 | { 20 | Super::BeginPlay(); 21 | IsLoadedFromBeginPlay = true; 22 | 23 | if (!IsLoaded) 24 | Reload(); 25 | } 26 | 27 | void ATileActor::EndPlay(const EEndPlayReason::Type EndPlayReason) 28 | { 29 | Super::EndPlay(EndPlayReason); 30 | IsLoadedFromBeginPlay = false; 31 | 32 | if (!RenderInEditor) 33 | Unload(); 34 | } 35 | 36 | void ATileActor::Load() 37 | { 38 | Tiles.AddUninitialized(Size * Size); 39 | 40 | for (int32 i = 0; i < Size * Size; i++) 41 | { 42 | Tiles[i] = FTile(); 43 | Tiles[i].ShouldUpdate = true; 44 | } 45 | 46 | IsLoaded = true; 47 | } 48 | 49 | void ATileActor::Reload() 50 | { 51 | Unload(); 52 | Load(); 53 | } 54 | 55 | void ATileActor::Unload() 56 | { 57 | IsLoaded = false; 58 | Tiles.Empty(); 59 | CurrentTileX = 1; 60 | CurrentTileY = 1; 61 | CurrentTileLocation = FVector(-1000000.0f, -1000000.0f, -1000000.0f); 62 | } 63 | 64 | void ATileActor::Tick(float DeltaTime) 65 | { 66 | Super::Tick(DeltaTime); 67 | 68 | if (!RenderInEditor && !IsLoadedFromBeginPlay) 69 | return; 70 | 71 | if (IsLoaded == false) 72 | { 73 | this->Load(); 74 | return; 75 | } 76 | 77 | auto world = GetWorld(); 78 | auto currentCameraLocation = TileUtils::GetCurrentCameraLocation(world); 79 | 80 | if (TileUtils::IsEmptyFVector(currentCameraLocation)) 81 | return; 82 | 83 | if (Tiles.Num() == Size * Size) { 84 | FTileInfo tileInfo = GetClosestTileToUpdate(currentCameraLocation); 85 | 86 | if (tileInfo.X >= 0) 87 | { 88 | FTile & tile = Tiles[GetIndex(tileInfo.X, tileInfo.Y)]; 89 | tile.ShouldUpdate = false; 90 | UpdateTile(tileInfo.X, tileInfo.Y, tile.Location); 91 | } 92 | } 93 | 94 | float tileSize = Radius * 2 / Size; 95 | float disX = currentCameraLocation.X - CurrentTileLocation.X; 96 | float disY = currentCameraLocation.Y - CurrentTileLocation.Y; 97 | bool updated = false; 98 | 99 | if (disX > tileSize || disX * -1 > tileSize / 2) { 100 | int32 currentX = (int32)floor(currentCameraLocation.X / tileSize); 101 | CurrentTileLocation.X = currentX * (int32)tileSize; 102 | CurrentTileX = currentX % Size; 103 | updated = true; 104 | 105 | if (CurrentTileX < 0) 106 | CurrentTileX += Size; 107 | } 108 | 109 | if (disY > tileSize || disY * -1 > tileSize / 2) { 110 | int32 currentY = (int32)floor(currentCameraLocation.Y / tileSize); 111 | CurrentTileLocation.Y = currentY * (int32)tileSize; 112 | CurrentTileY = currentY % Size; 113 | updated = true; 114 | 115 | if (CurrentTileY < 0) 116 | CurrentTileY += Size; 117 | } 118 | 119 | if (updated == false || Tiles.Num() != Size * Size) 120 | return; 121 | 122 | for (int32 x = 0; x < Size; x++) 123 | { 124 | for (int32 y = 0; y < Size; y++) 125 | { 126 | FTile & tile = Tiles[GetIndex(x, y)]; 127 | tile.NewLocation = GetTileLocation(x, y); 128 | 129 | if (FVector::Dist(tile.Location, tile.NewLocation) > tileSize / 3) { 130 | tile.Location = tile.NewLocation; 131 | tile.ShouldUpdate = true; 132 | } 133 | } 134 | } 135 | } 136 | 137 | FTileInfo ATileActor::GetClosestTileToUpdate(FVector location) 138 | { 139 | int32 closestX = -1; 140 | int32 closestY = -1; 141 | float distance = MAX_flt; 142 | 143 | for (int32 x = 0; x < Size; x++) 144 | { 145 | for (int32 y = 0; y < Size; y++) 146 | { 147 | FTile & tile = Tiles[GetIndex(x, y)]; 148 | if (tile.ShouldUpdate) { 149 | float tmpDistance = FVector::DistSquaredXY(location, tile.Location); 150 | 151 | if (tmpDistance > distance) 152 | continue; 153 | 154 | distance = tmpDistance; 155 | closestX = x; 156 | closestY = y; 157 | } 158 | } 159 | } 160 | 161 | return FTileInfo(closestX, closestY); 162 | } 163 | 164 | int32 ATileActor::ConvertTileIndex(int32 index) 165 | { 166 | int32 tmpIndex = index; 167 | if (index < 0) 168 | tmpIndex *= -1; 169 | 170 | if (index < 0 && tmpIndex > (float) Size / 2) 171 | return (Size * -1 + tmpIndex) * -1; 172 | else if (index > 0 && tmpIndex > (float)Size / 2) 173 | return (Size * -1 + tmpIndex); 174 | 175 | return index; 176 | } 177 | 178 | FVector ATileActor::GetTileLocation(int32 x, int32 y) { 179 | int32 tileSize = Radius * 2 / Size; 180 | int32 tmpX = ConvertTileIndex(x - CurrentTileX); 181 | int32 tmpY = ConvertTileIndex(y - CurrentTileY); 182 | float xOffset = tmpX * tileSize; 183 | float yOffset = tmpY * tileSize; 184 | FVector offset = FVector(xOffset, yOffset, 0.0f); 185 | return CurrentTileLocation + offset; 186 | } 187 | 188 | int32 ATileActor::GetIndex(int32 x, int32 y) { 189 | int32 result = 0; 190 | 191 | for (int32 x2 = 0; x2 < Size; x2++) 192 | { 193 | for (int32 y2 = 0; y2 < Size; y2++) 194 | { 195 | if (x2 == x && y2 == y) 196 | return result; 197 | 198 | result++; 199 | 200 | if (x2 == x && y2 == y) 201 | return result; 202 | } 203 | } 204 | 205 | return result; 206 | } 207 | 208 | void ATileActor::UpdateTile(int32 x, int32 y, FVector location) {} 209 | void ATileActor::PostUpdateTiles() {} 210 | bool ATileActor::ShouldTickIfViewportsOnly() const 211 | { 212 | return true; 213 | } 214 | 215 | void ATileActor::PostEditChangeProperty(struct FPropertyChangedEvent& e) 216 | { 217 | Super::PostEditChangeProperty(e); 218 | 219 | if (RenderInEditor) 220 | Reload(); 221 | else 222 | Unload(); 223 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Public/FoliageGroup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FoliageGroup.generated.h" 4 | 5 | const int collisionGridSize = 400; 6 | typedef TArray GridArrayType; 7 | 8 | USTRUCT() 9 | struct FFoliageGroupSpawnNoise 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | 13 | public: 14 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "1", UIMin = "1")) 15 | int32 NoiseSize; 16 | 17 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 18 | float Min; 19 | 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 21 | float Max; 22 | 23 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 24 | float FallOff; 25 | 26 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 27 | int32 Seed; 28 | 29 | FFoliageGroupSpawnNoise() 30 | { 31 | NoiseSize = 1000; 32 | Min = 0.0f; 33 | Max = 1.0f; 34 | FallOff = 0.0f; 35 | Seed = 0; 36 | } 37 | }; 38 | 39 | USTRUCT() 40 | struct FFoliageGroupScaleNoise 41 | { 42 | GENERATED_USTRUCT_BODY() 43 | 44 | public: 45 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "1", UIMin = "1")) 46 | int32 NoiseSize; 47 | 48 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 49 | float Min; 50 | 51 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 52 | float Max; 53 | 54 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 55 | int32 Seed; 56 | 57 | FFoliageGroupScaleNoise() 58 | { 59 | NoiseSize = 1000; 60 | Min = 1.0f; 61 | Max = 1.0f; 62 | Seed = 0; 63 | } 64 | }; 65 | 66 | USTRUCT() 67 | struct FFoliageGroupItem 68 | { 69 | GENERATED_USTRUCT_BODY() 70 | 71 | public: 72 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 73 | TArray Meshes; 74 | 75 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 76 | FFoliageGroupScaleNoise Scale; 77 | 78 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "1", UIMin = "1")) 79 | int Width; 80 | 81 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere) 82 | float CalculatedWidth; 83 | 84 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0", UIMin = "0")) 85 | int Spacing; 86 | 87 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 88 | float OffsetFactor; 89 | 90 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) 91 | float SpawnChance; 92 | 93 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 94 | TArray Noise; 95 | 96 | UPROPERTY(BlueprintReadOnly, EditAnywhere) 97 | uint32 CastShadow : 1; 98 | 99 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 100 | bool AffectDistanceFieldLighting; 101 | 102 | /** A value of 0.0 causes this to be calculated automatically. */ 103 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0")) 104 | float CullDistance; 105 | 106 | FFoliageGroupItem() 107 | { 108 | Scale = FFoliageGroupScaleNoise(); 109 | Width = 10; 110 | Spacing = 2; 111 | OffsetFactor = 0.3f; 112 | SpawnChance = 1.0f; 113 | } 114 | }; 115 | 116 | USTRUCT() 117 | struct FFoliageGroup 118 | { 119 | GENERATED_USTRUCT_BODY() 120 | 121 | public: 122 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 123 | TArray Items; 124 | 125 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 126 | TArray Noise; 127 | 128 | FFoliageGroup() 129 | { 130 | } 131 | }; 132 | 133 | USTRUCT() 134 | struct FSortItem 135 | { 136 | GENERATED_USTRUCT_BODY() 137 | 138 | public: 139 | UPROPERTY() 140 | int Index; 141 | 142 | UPROPERTY() 143 | int Width; 144 | 145 | FSortItem() 146 | { 147 | } 148 | 149 | FSortItem(int index, int width) 150 | { 151 | Index = index; 152 | Width = width; 153 | } 154 | }; 155 | 156 | USTRUCT() 157 | struct FItemSpawnSpace 158 | { 159 | GENERATED_USTRUCT_BODY() 160 | 161 | public: 162 | UPROPERTY() 163 | bool IsSpace; 164 | 165 | UPROPERTY() 166 | int FailedDistanceY; 167 | 168 | FItemSpawnSpace() 169 | { 170 | } 171 | 172 | FItemSpawnSpace(bool isSpace, int failedDistanceY) 173 | { 174 | IsSpace = isSpace; 175 | FailedDistanceY = failedDistanceY; 176 | } 177 | }; 178 | 179 | USTRUCT() 180 | struct FTileTaskResultItem 181 | { 182 | GENERATED_USTRUCT_BODY() 183 | 184 | public: 185 | UPROPERTY() 186 | FVector Location; 187 | 188 | UPROPERTY() 189 | int GroupIndex; 190 | 191 | UPROPERTY() 192 | int ItemIndex; 193 | 194 | UPROPERTY() 195 | float Scale; 196 | 197 | UPROPERTY() 198 | uint32 Seed; 199 | 200 | FTileTaskResultItem() 201 | { 202 | } 203 | 204 | FTileTaskResultItem(FVector location, int groupIndex, int itemIndex, float scale, uint32 seed) 205 | { 206 | Location = location; 207 | GroupIndex = groupIndex; 208 | ItemIndex = itemIndex; 209 | Scale = scale; 210 | Seed = seed; 211 | } 212 | }; 213 | 214 | 215 | 216 | USTRUCT() 217 | struct FTileTaskResult 218 | { 219 | GENERATED_USTRUCT_BODY() 220 | 221 | public: 222 | UPROPERTY() 223 | int TileIndex; 224 | 225 | UPROPERTY() 226 | bool ShouldRender; 227 | 228 | UPROPERTY() 229 | FVector Location; 230 | 231 | UPROPERTY() 232 | TArray Items; 233 | 234 | FTileTaskResult() 235 | { 236 | } 237 | 238 | FTileTaskResult(int tileIndex, bool shouldRender, FVector location) 239 | { 240 | TileIndex = tileIndex; 241 | ShouldRender = shouldRender; 242 | Location = location; 243 | } 244 | }; -------------------------------------------------------------------------------- /Source/Landscape_1/Public/ForestTileActor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TileActor.h" 4 | #include "Components/HierarchicalInstancedStaticMeshComponent.h" 5 | #include "ForestTileActor.generated.h" 6 | 7 | USTRUCT() 8 | struct FForestMesh 9 | { 10 | GENERATED_USTRUCT_BODY() 11 | 12 | public: 13 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 14 | class UStaticMesh* Mesh; 15 | 16 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 17 | float ScaleMin; 18 | 19 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 20 | float ScaleMax; 21 | 22 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 23 | float Bounds; 24 | 25 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 26 | float ZOffset; 27 | 28 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 29 | float HeightMin; 30 | 31 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 32 | float HeightMax; 33 | 34 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 35 | float MaxSlope; 36 | 37 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 38 | bool AlignWithSlope; 39 | 40 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 41 | bool Collision; 42 | 43 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 44 | float SpawnChanceVsOtherTrees; 45 | 46 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 47 | int32 NumberOfSteps; 48 | 49 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 50 | int32 ChildrenPerTree; 51 | 52 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 53 | float DeadTreePercentage; 54 | 55 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 56 | int32 GroupId; 57 | 58 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 59 | TArray PhysicalMaterials; 60 | 61 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 62 | int32 InitialSeed; 63 | 64 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere) 65 | int32 TotalMeshes; 66 | 67 | FForestMesh() 68 | { 69 | ScaleMin = 1.0f; 70 | ScaleMax = 1.0f; 71 | Bounds = 300.0f; 72 | HeightMin = INT32_MIN; 73 | HeightMax = INT32_MAX; 74 | MaxSlope = 30.0f; 75 | GroupId = 0; 76 | SpawnChanceVsOtherTrees = 1.0f; 77 | NumberOfSteps = 0; 78 | ChildrenPerTree = 0; 79 | DeadTreePercentage = 0.0f; 80 | AlignWithSlope = false; 81 | Collision = false; 82 | } 83 | }; 84 | 85 | USTRUCT() 86 | struct FForestFoliageMesh 87 | { 88 | GENERATED_USTRUCT_BODY() 89 | 90 | public: 91 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 92 | class UStaticMesh* Mesh; 93 | 94 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 95 | int32 MaxPerTree; 96 | 97 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 98 | float ScaleMin; 99 | 100 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 101 | float ScaleMax; 102 | 103 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 104 | float MinBounds; 105 | 106 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 107 | float MaxBounds; 108 | 109 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 110 | float ZOffset; 111 | 112 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 113 | float HeightMin; 114 | 115 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 116 | float HeightMax; 117 | 118 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 119 | float MaxSlope; 120 | 121 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 122 | bool AlignWithSlope; 123 | 124 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 125 | bool Collision; 126 | 127 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 128 | TArray PhysicalMaterials; 129 | 130 | FForestFoliageMesh() 131 | { 132 | MaxPerTree = 1; 133 | ScaleMin = 1.0f; 134 | ScaleMax = 1.0f; 135 | MinBounds = 100.0f; 136 | MaxBounds = 300.0f; 137 | HeightMin = INT32_MIN; 138 | HeightMax = INT32_MAX; 139 | MaxSlope = 30.0f; 140 | AlignWithSlope = true; 141 | Collision = false; 142 | } 143 | }; 144 | 145 | USTRUCT() 146 | struct FTreeInstance 147 | { 148 | GENERATED_USTRUCT_BODY() 149 | 150 | public: 151 | UPROPERTY() 152 | int32 MeshIndex; 153 | 154 | UPROPERTY() 155 | int32 InstanceId; 156 | 157 | UPROPERTY() 158 | FVector Location; 159 | 160 | UPROPERTY() 161 | float Bounds; 162 | 163 | UPROPERTY() 164 | float Scale; 165 | 166 | UPROPERTY() 167 | bool IsFoliage; 168 | 169 | FTreeInstance() 170 | { 171 | IsFoliage = false; 172 | } 173 | }; 174 | 175 | //USTRUCT() 176 | //struct FTile 177 | //{ 178 | // GENERATED_USTRUCT_BODY() 179 | // 180 | //public: 181 | // 182 | // UPROPERTY() 183 | // FVector Location; 184 | // 185 | // UPROPERTY() 186 | // TArray Instances; 187 | // 188 | // UPROPERTY(transient) 189 | // TArray MeshComponents; 190 | // 191 | // FTile() 192 | // { 193 | // } 194 | //}; 195 | 196 | UCLASS() 197 | class UForestTile : public UObject 198 | { 199 | GENERATED_BODY() 200 | 201 | public: 202 | UPROPERTY() 203 | TArray MeshComponents; 204 | 205 | UPROPERTY() 206 | TArray Instances; 207 | }; 208 | 209 | UCLASS() 210 | class LANDSCAPE_1_API AForestTileActor : public ATileActor 211 | { 212 | GENERATED_BODY() 213 | 214 | public: 215 | AForestTileActor(); 216 | ~AForestTileActor(); 217 | virtual void BeginPlay() override; 218 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& e) override; 219 | 220 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 221 | TArray Trees; 222 | 223 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 224 | TArray FoliageAroundTrees; 225 | 226 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 227 | float SpawnChance; 228 | 229 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 230 | float FoliageSpawnChance; 231 | 232 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 233 | int32 TreesPerTile; 234 | 235 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 236 | int32 FoliageStartCullDistance; 237 | 238 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 239 | int32 FoliageEndCullDistance; 240 | 241 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 242 | int32 InitialSeed; 243 | 244 | protected: 245 | virtual void UpdateTile(int32 x, int32 y, FVector location) override; 246 | 247 | private: 248 | UPROPERTY(transient, duplicatetransient) 249 | TArray ForestTiles; 250 | 251 | uint32 Hash(uint32 a); 252 | FTransform GetTransform(FVector location, uint32 seed, bool AlignWithSlope, float HeightMin, float HeightMax, float MaxSlope, TArray PhysicalMaterials); 253 | void Generate(); 254 | void AddChildren(FTreeInstance parent, int32 step, TArray & instances, uint32 seed); 255 | bool FindLocation(uint32 seed, FVector parentLocation, float bounds, TArray & instances, FVector & childLocation); 256 | bool TestLocation(FVector location, float bounds, TArray & instances); 257 | float GetAngle(FVector start, FHitResult HitData); 258 | int32 GetIndex(int32 x, int32 y); 259 | }; 260 | -------------------------------------------------------------------------------- /Source/Landscape_1/Private/SimplexNoise.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SimplexNoise 1.0.0 3 | ----- 4 | DevDad - Afan Olovcic @ www.art-and-code.com - 08/12/2015 5 | This algorithm was originally designed by Ken Perlin, but my code has been 6 | adapted and extended from the implementation written by Stefan Gustavson (stegu@itn.liu.se) 7 | and modified to fit to Unreal Engine 4 8 | * This is a clean, fast, modern and free Perlin Simplex noise function. 9 | * If we change float to double it could be even faster but there is no double type in Blueprint 10 | * All Public Functions are BlueprintCallable so they can be used in every blueprint 11 | From DevDad and Dedicated to you and Unreal Community 12 | Use it free for what ever you want 13 | I only request that you mention me in the credits for your game in the way that feels most appropriate to you. 14 | */ 15 | 16 | 17 | #include "Landscape_1.h" 18 | #include "SimplexNoise.h" 19 | 20 | // USimplexNoise 21 | #define FASTFLOOR(x) ( ((x)>0) ? ((int)x) : (((int)x)-1) ) 22 | 23 | unsigned char USimplexNoise::perm[512] = { 151,160,137,91,90,15, 24 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 25 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 26 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 27 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 28 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 29 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 30 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 31 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 32 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 33 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 34 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 35 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, 36 | 151,160,137,91,90,15, 37 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 38 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 39 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 40 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 41 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 42 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 43 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 44 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 45 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 46 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 47 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 48 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 49 | }; 50 | 51 | void USimplexNoise::setNoiseSeed(const int32& newSeed) 52 | { 53 | TArray availableSeeds; 54 | availableSeeds.Init(true, 256); 55 | FMath::RandInit(newSeed); 56 | for (uint16 it = 0; it < 256;++it) 57 | { 58 | uint8 nextNum; 59 | do 60 | { 61 | nextNum = FMath::RandRange(0, 255); 62 | } while (!availableSeeds[nextNum]); 63 | perm[it] = (unsigned char)nextNum; 64 | perm[it + 256] = (unsigned char)nextNum; 65 | } 66 | } 67 | 68 | static unsigned char simplex[64][4] = { 69 | { 0,1,2,3 },{ 0,1,3,2 },{ 0,0,0,0 },{ 0,2,3,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 1,2,3,0 }, 70 | { 0,2,1,3 },{ 0,0,0,0 },{ 0,3,1,2 },{ 0,3,2,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 1,3,2,0 }, 71 | { 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 }, 72 | { 1,2,0,3 },{ 0,0,0,0 },{ 1,3,0,2 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 2,3,0,1 },{ 2,3,1,0 }, 73 | { 1,0,2,3 },{ 1,0,3,2 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 2,0,3,1 },{ 0,0,0,0 },{ 2,1,3,0 }, 74 | { 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 }, 75 | { 2,0,1,3 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 3,0,1,2 },{ 3,0,2,1 },{ 0,0,0,0 },{ 3,1,2,0 }, 76 | { 2,1,0,3 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 },{ 3,1,0,2 },{ 0,0,0,0 },{ 3,2,0,1 },{ 3,2,1,0 } }; 77 | 78 | 79 | float USimplexNoise::grad(int hash, float x, float y) 80 | { 81 | int h = hash & 7; // Convert low 3 bits of hash code 82 | float u = h < 4 ? x : y; // into 8 simple gradient directions, 83 | float v = h < 4 ? y : x; // and compute the dot product with (x,y). 84 | return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f*v : 2.0f*v); 85 | } 86 | 87 | float USimplexNoise::SimplexNoise2D(float x, float y) 88 | { 89 | 90 | #define F2 0.366025403f // F2 = 0.5*(sqrt(3.0)-1.0) 91 | #define G2 0.211324865f // G2 = (3.0-Math.sqrt(3.0))/6.0 92 | 93 | float n0, n1, n2; // Noise contributions from the three corners 94 | 95 | // Skew the input space to determine which simplex cell we're in 96 | float s = (x + y)*F2; // Hairy factor for 2D 97 | float xs = x + s; 98 | float ys = y + s; 99 | int i = FASTFLOOR(xs); 100 | int j = FASTFLOOR(ys); 101 | 102 | float t = (float)(i + j)*G2; 103 | float X0 = i - t; // Unskew the cell origin back to (x,y) space 104 | float Y0 = j - t; 105 | float x0 = x - X0; // The x,y distances from the cell origin 106 | float y0 = y - Y0; 107 | 108 | // For the 2D case, the simplex shape is an equilateral triangle. 109 | // Determine which simplex we are in. 110 | int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords 111 | if (x0 > y0) { i1 = 1; j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1) 112 | else { i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) 113 | 114 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 115 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 116 | // c = (3-sqrt(3))/6 117 | 118 | float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords 119 | float y1 = y0 - j1 + G2; 120 | float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords 121 | float y2 = y0 - 1.0f + 2.0f * G2; 122 | 123 | // Wrap the integer indices at 256, to avoid indexing perm[] out of bounds 124 | int ii = i & 0xff; 125 | int jj = j & 0xff; 126 | 127 | // Calculate the contribution from the three corners 128 | float t0 = 0.5f - x0*x0 - y0*y0; 129 | if (t0 < 0.0f) n0 = 0.0f; 130 | else { 131 | t0 *= t0; 132 | n0 = t0 * t0 * grad(perm[ii + perm[jj]], x0, y0); 133 | } 134 | 135 | float t1 = 0.5f - x1*x1 - y1*y1; 136 | if (t1 < 0.0f) n1 = 0.0f; 137 | else { 138 | t1 *= t1; 139 | n1 = t1 * t1 * grad(perm[ii + i1 + perm[jj + j1]], x1, y1); 140 | } 141 | 142 | float t2 = 0.5f - x2*x2 - y2*y2; 143 | if (t2 < 0.0f) n2 = 0.0f; 144 | else { 145 | t2 *= t2; 146 | n2 = t2 * t2 * grad(perm[ii + 1 + perm[jj + 1]], x2, y2); 147 | } 148 | 149 | // Add contributions from each corner to get the final noise value. 150 | // The result is scaled to return values in the interval [-1,1]. 151 | return 40.0f * (n0 + n1 + n2); // TODO: The scale factor is preliminary! 152 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Private/FCalculateTileTask.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "FoliageGroup.h" 3 | #include "SimplexNoise.h" 4 | #include "TileUtils.h" 5 | #include "FCalculateTileTask.h" 6 | 7 | void FCalculateTileTask::DoWork() 8 | { 9 | GridArrayType collisionGrid; 10 | collisionGrid.AddDefaulted(collisionGridSize * collisionGridSize); 11 | FTileTaskResult result = FTileTaskResult(TileIndex, true, Location); 12 | 13 | for (int groupIndex = 0; groupIndex < Groups.Num(); groupIndex++) 14 | { 15 | FFoliageGroup group = Groups[groupIndex]; 16 | TArray itemsSorted; 17 | 18 | for (int itemIndex = 0; itemIndex < group.Items.Num(); itemIndex++) 19 | { 20 | itemsSorted.Add(FSortItem(itemIndex, group.Items[itemIndex].Width)); 21 | } 22 | 23 | itemsSorted.Sort([](const FSortItem& a, const FSortItem& b) { 24 | return a.Width > b.Width; 25 | }); 26 | 27 | for (int tmpIndex = 0; tmpIndex < group.Items.Num(); tmpIndex++) 28 | { 29 | int itemIndex = itemsSorted[tmpIndex].Index; 30 | FFoliageGroupItem& item = group.Items[itemIndex]; 31 | int itemRadius = FMath::Max(1, item.Width / 2); 32 | float itemWorldWidth = TileSize / collisionGridSize * item.Width; 33 | GridArrayType itemCollisionGrid; 34 | itemCollisionGrid.AddDefaulted(collisionGridSize * collisionGridSize); 35 | 36 | for (int tileX = 0; tileX < collisionGridSize; tileX++) 37 | { 38 | for (int tileY = 0; tileY < collisionGridSize; tileY++) 39 | { 40 | Seed = TileUtils::Hash(Seed); 41 | 42 | if (collisionGrid[(tileX * collisionGridSize) + tileY] || 43 | tileX < itemRadius || 44 | tileY < itemRadius || 45 | collisionGridSize - tileX < itemRadius || 46 | collisionGridSize - tileY < itemRadius) 47 | continue; 48 | 49 | auto tileSeed = Seed; 50 | bool spawn = true; 51 | float fallOffSpawnChance = 0.0f; 52 | float extraWidth = 0.0f; 53 | tileSeed = TileUtils::Hash(tileSeed); 54 | float r1 = ((double)tileSeed / UINT32_MAX * 2) - 1.0f; 55 | tileSeed = TileUtils::Hash(tileSeed); 56 | float r2 = ((double)tileSeed / UINT32_MAX * 2) - 1.0f; 57 | 58 | FVector tileLocation = FVector( 59 | Location.X + (TileSize / collisionGridSize * tileX) + (itemWorldWidth * r1 * item.OffsetFactor), 60 | Location.Y + (TileSize / collisionGridSize * tileY) + (itemWorldWidth * r2 * item.OffsetFactor), 61 | -1000000.0f); 62 | 63 | float scaleNoise = FMath::Abs(USimplexNoise::SimplexNoise2D(tileLocation.X / item.Scale.NoiseSize, tileLocation.Y / item.Scale.NoiseSize)); 64 | float scale = item.Scale.Min + ((item.Scale.Max - item.Scale.Min) * scaleNoise); 65 | 66 | // Parent Noise check 67 | for (int noiseIndex = 0; noiseIndex < group.Noise.Num(); noiseIndex++) 68 | { 69 | auto& noise = group.Noise[noiseIndex]; 70 | float noiseSeed = 100000.0f * ((float)TileUtils::Hash(noise.Seed) / UINT32_MAX); 71 | float noiseValue = FMath::Abs(USimplexNoise::SimplexNoise2D((tileLocation.X + noiseSeed) / noise.NoiseSize, (tileLocation.Y + noiseSeed) / noise.NoiseSize)); 72 | 73 | if (noiseValue < noise.Min || noiseValue > noise.Max) 74 | spawn = false; 75 | 76 | fallOffSpawnChance = GetFallOffSpawnChance(noise, noiseValue); 77 | tileSeed = TileUtils::Hash(tileSeed); 78 | if (fallOffSpawnChance < (float)tileSeed / UINT32_MAX) 79 | { 80 | extraWidth = (item.Spacing * 2) * (1.0f - fallOffSpawnChance); 81 | } 82 | } 83 | 84 | if (!spawn) 85 | continue; 86 | 87 | // Noise check 88 | for (int noiseIndex = 0; noiseIndex < item.Noise.Num(); noiseIndex++) 89 | { 90 | auto& noise = item.Noise[noiseIndex]; 91 | float noiseSeed = 100000.0f * ((float)TileUtils::Hash(noise.Seed) / UINT32_MAX); 92 | float noiseValue = FMath::Abs(USimplexNoise::SimplexNoise2D((tileLocation.X + noiseSeed) / noise.NoiseSize, (tileLocation.Y + noiseSeed) / noise.NoiseSize)); 93 | 94 | if (noiseValue < noise.Min || noiseValue > noise.Max) 95 | spawn = false; 96 | 97 | fallOffSpawnChance = GetFallOffSpawnChance(noise, noiseValue); 98 | tileSeed = TileUtils::Hash(tileSeed); 99 | if (fallOffSpawnChance < (float)tileSeed / UINT32_MAX) 100 | { 101 | extraWidth = (item.Spacing * 2) * (1.0f - fallOffSpawnChance); 102 | } 103 | } 104 | 105 | if (!spawn) 106 | continue; 107 | 108 | auto spawnSpace = CalculateSpawnSpace(collisionGrid, collisionGridSize, tileX, tileY, item.Width / 2 + item.Spacing); 109 | 110 | if (spawnSpace.IsSpace) 111 | spawnSpace = CalculateSpawnSpace(itemCollisionGrid, collisionGridSize, tileX, tileY, item.Width / 2 + item.Spacing); 112 | 113 | if (!spawnSpace.IsSpace) 114 | { 115 | tileY += FMath::Max(0, (item.Width / 2 + item.Spacing) - spawnSpace.FailedDistanceY); 116 | continue; 117 | } 118 | 119 | tileSeed = TileUtils::Hash(tileSeed); 120 | if (extraWidth > 0 && (fallOffSpawnChance + 0.5f) / 1.5f < (float)tileSeed / UINT32_MAX) 121 | { 122 | Spawn(itemCollisionGrid, collisionGridSize, tileX, tileY, extraWidth); 123 | continue; 124 | } 125 | 126 | Spawn(itemCollisionGrid, collisionGridSize, tileX, tileY, item.Width); 127 | 128 | // Spawn chance check 129 | tileSeed = TileUtils::Hash(tileSeed); 130 | if (item.SpawnChance < (float)tileSeed / UINT32_MAX) 131 | continue; 132 | 133 | Spawn(collisionGrid, collisionGridSize, tileX, tileY, item.Width); 134 | result.Items.Add(FTileTaskResultItem(tileLocation, groupIndex, itemIndex, scale, tileSeed)); 135 | } 136 | } 137 | 138 | itemCollisionGrid.Empty(); 139 | } 140 | } 141 | 142 | collisionGrid.Empty(); 143 | TaskResultDelegate.ExecuteIfBound(result); 144 | } 145 | 146 | float FCalculateTileTask::GetFallOffSpawnChance(FFoliageGroupSpawnNoise& noise, float noiseValue) 147 | { 148 | if (noise.FallOff == 0.0f) 149 | return 1.0f; 150 | 151 | float minFallOff = 1.0f / noise.FallOff * (noiseValue - noise.Min); 152 | float maxFallOff = 1.0f / noise.FallOff * (noise.Max - noiseValue); 153 | 154 | if (noise.Min > 0.0f && (noise.Max == 1.0f || minFallOff < maxFallOff)) 155 | return minFallOff; 156 | 157 | if (noise.Max < 1.0f && (noise.Min == 0.0f || minFallOff > maxFallOff)) 158 | return maxFallOff; 159 | 160 | return 1.0f; 161 | } 162 | 163 | FItemSpawnSpace FCalculateTileTask::CalculateSpawnSpace(GridArrayType& collisionGrid, int size, int x, int y, int spacing) { 164 | for (int tmpX = 0; tmpX <= spacing; tmpX++) 165 | { 166 | for (int tmpY = 0; tmpY <= spacing; tmpY++) 167 | { 168 | int gridPlusX = x + tmpX; 169 | int gridPlusY = y + tmpY; 170 | int gridMinusX = x - tmpX; 171 | int gridMinusY = y - tmpY; 172 | 173 | if ((gridMinusX >= 0 && gridMinusY >= 0 && collisionGrid[(gridMinusX * size) + gridMinusY]) || 174 | (gridPlusX < size && gridMinusY >= 0 && collisionGrid[(gridPlusX * size) + gridMinusY]) || 175 | (gridPlusX < size && gridPlusY < size && collisionGrid[(gridPlusX * size) + gridPlusY]) || 176 | (gridMinusX >= 0 && gridPlusY < size && collisionGrid[(gridMinusX * size) + gridPlusY])) 177 | return FItemSpawnSpace(false, tmpY); 178 | } 179 | } 180 | 181 | return FItemSpawnSpace(true, 0); 182 | } 183 | 184 | void FCalculateTileTask::Spawn(GridArrayType& collisionGrid, int size, int x, int y, int width) { 185 | if (width == 1) 186 | { 187 | collisionGrid[(x * size) + y] = true; 188 | return; 189 | } 190 | 191 | int radius = width / 2; 192 | int startX = FMath::Max(0, x - radius); 193 | int startY = FMath::Max(0, y - radius); 194 | int endX = FMath::Min(size, x + radius); 195 | int endY = FMath::Min(size, y + radius); 196 | 197 | for (int tmpX = startX; tmpX < endX; tmpX++) 198 | { 199 | for (int tmpY = startY; tmpY < endY; tmpY++) 200 | { 201 | collisionGrid[(tmpX * size) + tmpY] = true; 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Private/FoliageTileActor.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "FoliageTileActor.h" 3 | #include "SimplexNoise.h" 4 | #include "Runtime/Landscape/Classes/Landscape.h" 5 | #include "Kismet/KismetSystemLibrary.h" 6 | 7 | AFoliageTileActor::AFoliageTileActor() 8 | { 9 | DistanceBetween = 200.0f; 10 | OffsetFactor = 0.3f; 11 | Scale = FFoliageScaleNoise(); 12 | MinCullDistance = 0.0f; 13 | SpawnChance = 1.0f; 14 | AlignWithSlope = true; 15 | Collision = false; 16 | AffectDistanceFieldLighting = false; 17 | CastShadow = 0; 18 | } 19 | 20 | void AFoliageTileActor::Load() 21 | { 22 | uint32 seed = Hash(Seed); 23 | 24 | if (Mesh == NULL) 25 | return; 26 | 27 | FoliageTiles.AddUninitialized(Size * Size); 28 | UGameplayStatics::GetAllActorsOfClass(GetWorld(), AFoliageTileBlockingVolume::StaticClass(), BlockingVolumes); 29 | 30 | int32 componentSize = 2; 31 | float FoliageTilesize = Radius * 2 / Size; 32 | float endCullDistance = Radius - (FoliageTilesize * 1.1f); 33 | float minCullDistance = MinCullDistance > 0 ? MinCullDistance : endCullDistance / 2; 34 | USceneComponent* rootComponent = GetRootComponent(); 35 | 36 | for (int32 y = 0; y < FoliageTiles.Num(); y++) 37 | { 38 | seed = Hash(seed); 39 | UFoliageTile* tile = NewObject(this, NAME_None); 40 | tile->MeshComponents.AddUninitialized(componentSize); 41 | FoliageTiles[y] = tile; 42 | 43 | for (int32 componentIndex = 0; componentIndex < componentSize; componentIndex++) 44 | { 45 | UHierarchicalInstancedStaticMeshComponent* component = NewObject(this); 46 | component->SetStaticMesh(Mesh); 47 | component->bSelectable = false; 48 | component->bHasPerInstanceHitProxies = true; 49 | component->InstancingRandomSeed = seed % INT32_MAX; 50 | component->bAffectDistanceFieldLighting = AffectDistanceFieldLighting; 51 | component->CastShadow = CastShadow; 52 | component->SetCollisionEnabled(Collision ? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision); 53 | 54 | float cullDistance = (endCullDistance / componentSize * (componentIndex + 1)) * FMath::Max(0.0f, (1.0f - (1.0f / endCullDistance * minCullDistance))); 55 | component->InstanceEndCullDistance = endCullDistance - cullDistance; 56 | component->InstanceStartCullDistance = endCullDistance - cullDistance - (endCullDistance / componentSize * (componentIndex + 1)); 57 | 58 | component->AttachToComponent(rootComponent, FAttachmentTransformRules::KeepRelativeTransform); 59 | component->RegisterComponent(); 60 | tile->MeshComponents[componentIndex] = component; 61 | } 62 | } 63 | 64 | Super::Load(); 65 | } 66 | 67 | void AFoliageTileActor::Unload() { 68 | FoliageTiles.Empty(); 69 | TArray components = GetComponentsByClass(UHierarchicalInstancedStaticMeshComponent::StaticClass()); 70 | 71 | for (int32 i = 0; i < components.Num(); i++) 72 | { 73 | components[i]->DestroyComponent(); 74 | } 75 | 76 | Super::Unload(); 77 | } 78 | 79 | bool AFoliageTileActor::ShouldExport() { 80 | if (RenderInEditor) { 81 | FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("You cannot copy this actor while Render In Editor is enabled.")); 82 | return false; 83 | } 84 | 85 | return true; 86 | } 87 | 88 | void AFoliageTileActor::UpdateTile(int32 x, int32 y, FVector location) { 89 | if (Mesh == NULL || DistanceBetween <= 0.0) 90 | return; 91 | 92 | UFoliageTile* tile = FoliageTiles[GetIndex(x, y)]; 93 | uint32 seed = Hash(Seed + (x * Size) + y); 94 | 95 | for (int32 componentIndex = 0; componentIndex < tile->MeshComponents.Num(); componentIndex++) 96 | { 97 | tile->MeshComponents[componentIndex]->ClearInstances(); 98 | } 99 | 100 | float tileSize = Radius * 2 / Size; 101 | int32 arraySize = (int32)(tileSize / DistanceBetween); 102 | float split = tileSize / arraySize; 103 | float ditterOffsetSize = split / 4; 104 | 105 | for (int32 tileX = 0; tileX < arraySize; tileX++) 106 | { 107 | for (int32 tileY = 0; tileY < arraySize; tileY++) 108 | { 109 | int32 compIndex = FMath::RandRange(0, tile->MeshComponents.Num() - 1); 110 | seed = Hash(seed); 111 | 112 | if (SpawnChance > (double)seed / UINT32_MAX) { 113 | uint32 tempSeed = Hash(seed); 114 | float r1 = ((double)tempSeed / UINT32_MAX * 2) - 1.0f; 115 | tempSeed = Hash(tempSeed); 116 | float r2 = ((double)tempSeed / UINT32_MAX * 2) - 1.0f; 117 | bool spawn = true; 118 | float ditterOffset = tileY % 2 > 0 ? ditterOffsetSize * -1 : ditterOffsetSize; 119 | FVector instanceLocation = FVector(location.X + split * tileX + (split * r1 * OffsetFactor) + ditterOffset, location.Y + split * tileY + (split * r2 * OffsetFactor), -1000000.0f); 120 | 121 | for (int32 spawnNoiseIndex = 0; spawnNoiseIndex < SpawnNoise.Num(); spawnNoiseIndex++) 122 | { 123 | float noiseSeed = 100000.0f * ((float)Hash(SpawnNoise[spawnNoiseIndex].Seed) / UINT32_MAX); 124 | float noise = FMath::Abs(USimplexNoise::SimplexNoise2D((instanceLocation.X + noiseSeed) / SpawnNoise[spawnNoiseIndex].NoiseSize, (instanceLocation.Y + noiseSeed) / SpawnNoise[spawnNoiseIndex].NoiseSize)); 125 | 126 | if (noise < SpawnNoise[spawnNoiseIndex].Min || noise > SpawnNoise[spawnNoiseIndex].Max) 127 | { 128 | spawn = false; 129 | break; 130 | } 131 | } 132 | 133 | if (!spawn) 134 | continue; 135 | 136 | tempSeed = Hash(tempSeed); 137 | float noise = FMath::Abs(USimplexNoise::SimplexNoise2D(instanceLocation.X / Scale.NoiseSize, instanceLocation.Y / Scale.NoiseSize)); 138 | FTransform transform = GetTransform(instanceLocation, tempSeed, BlockingVolumes); 139 | float scale = Scale.Min + ((Scale.Max - Scale.Min) * noise); 140 | transform.SetScale3D(FVector(scale, scale, scale)); 141 | 142 | for (int32 blockingVolumeIndex = 0; blockingVolumeIndex < BlockingVolumes.Num(); blockingVolumeIndex++) 143 | { 144 | auto volume = (AFoliageTileBlockingVolume*)BlockingVolumes[blockingVolumeIndex]; 145 | if (volume->GetBrushComponent()->OverlapComponent(transform.GetLocation(), transform.GetRotation(), FCollisionShape())) 146 | { 147 | if ((Layer == NAME_None && volume->FoliageLayers.Num() == 0) || 148 | (Layer != NAME_None && volume->FoliageLayers.Contains(Layer))) 149 | { 150 | spawn = false; 151 | break; 152 | } 153 | } 154 | } 155 | 156 | if (!spawn) 157 | continue; 158 | 159 | tile->MeshComponents[compIndex]->AddInstance(transform); 160 | } 161 | } 162 | } 163 | } 164 | 165 | FTransform AFoliageTileActor::GetTransform(FVector location, uint32 seed, TArray actorsToIgnore) { 166 | const FVector Start = FVector(location.X, location.Y, 100000.0f); 167 | const FVector End = FVector(location.X, location.Y, -100000.0f); 168 | float r = (double)seed / UINT32_MAX; 169 | 170 | FTransform result = FTransform(); 171 | FQuat rotator = FQuat(FRotator(0.0f, 360.0f * r, 0.0f)); 172 | result.SetTranslation(FVector(-1.0f, -1.0f, -100000.0f)); 173 | result.SetRotation(rotator); 174 | FHitResult HitData(ForceInit); 175 | TArray > Objects; 176 | Objects.Add(EObjectTypeQuery::ObjectTypeQuery1); 177 | 178 | // Todo: Find a way to trace only landscapes for better performance 179 | if (UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), Start, End, Objects, false, actorsToIgnore, EDrawDebugTrace::None, HitData, true)) 180 | { 181 | if (HitData.GetActor() && HitData.Actor->IsA(ALandscape::StaticClass())) 182 | { 183 | result.SetTranslation(FVector(location.X, location.Y, HitData.ImpactPoint.Z)); 184 | 185 | if (AlignWithSlope == true) 186 | { 187 | FRotator rotation = FRotationMatrix::MakeFromZ(HitData.Normal).Rotator(); 188 | result.SetRotation(FQuat(rotation) * rotator); 189 | } 190 | } 191 | } 192 | 193 | return result; 194 | } 195 | 196 | uint32 AFoliageTileActor::Hash(uint32 a) 197 | { 198 | a = (a ^ 61) ^ (a >> 16); 199 | a = a + (a << 3); 200 | a = a ^ (a >> 4); 201 | a = a * 0x27d4eb2d; 202 | a = a ^ (a >> 15); 203 | return a; 204 | } -------------------------------------------------------------------------------- /Source/Landscape_1/Private/FoliageGroupTileActor.cpp: -------------------------------------------------------------------------------- 1 | #include "Landscape_1.h" 2 | #include "FoliageGroupTileActor.h" 3 | #include "FCalculateTileTask.h" 4 | #include "FoliageGroup.h" 5 | #include "SimplexNoise.h" 6 | #include "TileUtils.h" 7 | #include "Runtime/Landscape/Classes/Landscape.h" 8 | #include "Kismet/KismetSystemLibrary.h" 9 | 10 | AFoliageGroupTileActor::AFoliageGroupTileActor() 11 | { 12 | } 13 | 14 | void AFoliageGroupTileActor::Load() 15 | { 16 | uint32 seed = TileUtils::Hash(Seed); 17 | float FoliageTilesize = Radius * 2 / Size; 18 | float endCullDistance = Radius - (FoliageTilesize * 1.1f); 19 | USceneComponent* rootComponent = GetRootComponent(); 20 | FoliageGroupTiles.Reserve(Size * Size); 21 | TileTaskResults.AddDefaulted(Size * Size); 22 | 23 | for (int groupTileIndex = 0; groupTileIndex < Size * Size; groupTileIndex++) 24 | { 25 | UFoliageGroupTile* tile = NewObject(this, NAME_None); 26 | FoliageGroupTiles.Add(tile); 27 | tile->Groups.Reserve(Groups.Num()); 28 | 29 | for (int groupIndex = 0; groupIndex < Groups.Num(); groupIndex++) 30 | { 31 | FFoliageGroup group = Groups[groupIndex]; 32 | UFoliageGroupTileGroup* tileGroup = NewObject(this, NAME_None); 33 | tile->Groups.Add(tileGroup); 34 | tileGroup->Items.Reserve(group.Items.Num()); 35 | 36 | for (int itemIndex = 0; itemIndex < group.Items.Num(); itemIndex++) 37 | { 38 | FFoliageGroupItem item = group.Items[itemIndex]; 39 | UFoliageGroupTileItem* tileItem = NewObject(this, NAME_None); 40 | tileGroup->Items.Add(tileItem); 41 | tileItem->MeshComponents.Reserve(item.Meshes.Num()); 42 | 43 | for (int meshIndex = 0; meshIndex < item.Meshes.Num(); meshIndex++) 44 | { 45 | seed = TileUtils::Hash(seed); 46 | UHierarchicalInstancedStaticMeshComponent* component = NewObject(this); 47 | component->SetStaticMesh(item.Meshes[meshIndex]); 48 | component->bSelectable = false; 49 | component->bHasPerInstanceHitProxies = true; 50 | component->InstancingRandomSeed = seed % UINT32_MAX; 51 | component->bAffectDistanceFieldLighting = item.AffectDistanceFieldLighting; 52 | component->CastShadow = item.CastShadow; 53 | component->SetCollisionEnabled(ECollisionEnabled::NoCollision); 54 | component->InstanceEndCullDistance = item.CullDistance > 0.0f ? item.CullDistance : endCullDistance; 55 | component->AttachToComponent(rootComponent, FAttachmentTransformRules::KeepRelativeTransform); 56 | component->RegisterComponent(); 57 | tileItem->MeshComponents.Add(component); 58 | } 59 | } 60 | } 61 | } 62 | 63 | Super::Load(); 64 | } 65 | 66 | void AFoliageGroupTileActor::Unload() 67 | { 68 | Super::Unload(); 69 | FoliageGroupTiles.Empty(); 70 | TileTaskResults.Empty(); 71 | TArray components = GetComponentsByClass(UHierarchicalInstancedStaticMeshComponent::StaticClass()); 72 | 73 | for (int32 i = 0; i < components.Num(); i++) 74 | { 75 | components[i]->DestroyComponent(); 76 | } 77 | } 78 | 79 | void AFoliageGroupTileActor::TaskResultCompleted(FTileTaskResult tileTaskResult) 80 | { 81 | if (TileTaskResults.Num() > tileTaskResult.TileIndex) 82 | TileTaskResults[tileTaskResult.TileIndex] = tileTaskResult; 83 | } 84 | 85 | int AFoliageGroupTileActor::GetClosestTileToRender(FVector currentLocation) 86 | { 87 | int32 index = -1; 88 | float distance = MAX_flt; 89 | 90 | for (int tileIndex = 0; tileIndex < TileTaskResults.Num(); tileIndex++) 91 | { 92 | if (TileTaskResults[tileIndex].ShouldRender) 93 | { 94 | float tmpDistance = FVector::DistSquaredXY(currentLocation, TileTaskResults[tileIndex].Location); 95 | 96 | if (tmpDistance > distance) 97 | continue; 98 | 99 | distance = tmpDistance; 100 | index = tileIndex; 101 | } 102 | } 103 | 104 | return index; 105 | } 106 | 107 | void AFoliageGroupTileActor::Tick(float DeltaTime) 108 | { 109 | Super::Tick(DeltaTime); 110 | 111 | if (!IsLoaded) 112 | return; 113 | 114 | auto world = GetWorld(); 115 | FVector currentCameraLocation = TileUtils::GetCurrentCameraLocation(world); 116 | 117 | if (TileUtils::IsEmptyFVector(currentCameraLocation)) 118 | return; 119 | 120 | int tileIndex = GetClosestTileToRender(currentCameraLocation); 121 | 122 | if (tileIndex == -1) 123 | return; 124 | 125 | auto& result = TileTaskResults[tileIndex]; 126 | 127 | if (result.ShouldRender && FoliageGroupTiles.Num() > tileIndex) 128 | { 129 | result.ShouldRender = false; 130 | UFoliageGroupTile* tile = FoliageGroupTiles[tileIndex]; 131 | float tileSize = Radius * 2 / Size; 132 | ClearInstances(tile); 133 | 134 | for (int i = 0; i < result.Items.Num(); i++) 135 | { 136 | auto& item = result.Items[i]; 137 | auto meshComponents = tile->Groups[item.GroupIndex]->Items[item.ItemIndex]->MeshComponents; 138 | auto tileSeed = TileUtils::Hash(item.Seed); 139 | 140 | if (meshComponents.Num() == 0) 141 | continue; 142 | 143 | int meshIndex = tileSeed % meshComponents.Num(); 144 | TArray BlockingVolumes; 145 | tileSeed = TileUtils::Hash(tileSeed); 146 | FTransform transform = GetTransform(item.Location, tileSeed, BlockingVolumes); 147 | transform.SetScale3D(FVector(item.Scale, item.Scale, item.Scale)); 148 | 149 | if (transform.GetLocation().Z == -100000.0f) 150 | continue; 151 | 152 | meshComponents[meshIndex]->AddInstance(transform); 153 | } 154 | 155 | result.Items.Empty(); 156 | } 157 | } 158 | 159 | void AFoliageGroupTileActor::UpdateTile(int32 x, int32 y, FVector location) 160 | { 161 | int tileIndex = GetIndex(x, y); 162 | uint32 seed = TileUtils::Hash(Seed + (x * Size) + y); 163 | UFoliageGroupTile* tile = FoliageGroupTiles[tileIndex]; 164 | float tileSize = Radius * 2 / Size; 165 | FTileTaskResultDelegate taskResultDelegate; 166 | taskResultDelegate.BindUObject(this, &AFoliageGroupTileActor::TaskResultCompleted); 167 | (new FAutoDeleteAsyncTask(location, tileIndex, Groups, tileSize, seed, taskResultDelegate))->StartBackgroundTask(); 168 | } 169 | 170 | void AFoliageGroupTileActor::PostEditChangeProperty(struct FPropertyChangedEvent& e) 171 | { 172 | Super::PostEditChangeProperty(e); 173 | float tileSize = Radius * 2 / Size; 174 | 175 | for (int groupIndex = 0; groupIndex < Groups.Num(); groupIndex++) 176 | { 177 | FFoliageGroup& group = Groups[groupIndex]; 178 | for (int itemIndex = 0; itemIndex < group.Items.Num(); itemIndex++) 179 | { 180 | FFoliageGroupItem& item = group.Items[itemIndex]; 181 | item.CalculatedWidth = tileSize / collisionGridSize * item.Width; 182 | } 183 | } 184 | } 185 | 186 | FTransform AFoliageGroupTileActor::GetTransform(FVector location, uint32 seed, TArray actorsToIgnore) 187 | { 188 | const FVector Start = FVector(location.X, location.Y, 100000.0f); 189 | const FVector End = FVector(location.X, location.Y, -100000.0f); 190 | float r = (double)seed / UINT32_MAX; 191 | 192 | FTransform result = FTransform(); 193 | FQuat rotator = FQuat(FRotator(0.0f, 360.0f * r, 0.0f)); 194 | result.SetTranslation(FVector(-1.0f, -1.0f, -100000.0f)); 195 | result.SetRotation(rotator); 196 | FHitResult HitData(ForceInit); 197 | TArray > Objects; 198 | Objects.Add(EObjectTypeQuery::ObjectTypeQuery1); 199 | 200 | // Todo: Find a way to trace only landscapes for better performance 201 | if (UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), Start, End, Objects, false, actorsToIgnore, EDrawDebugTrace::None, HitData, true) && 202 | HitData.GetActor() && 203 | HitData.Actor->IsA(ALandscape::StaticClass())) 204 | { 205 | result.SetTranslation(FVector(location.X, location.Y, HitData.ImpactPoint.Z)); 206 | } 207 | 208 | return result; 209 | } 210 | 211 | void AFoliageGroupTileActor::ClearInstances(UFoliageGroupTile* tile) 212 | { 213 | for (int groupIndex = 0; groupIndex < tile->Groups.Num(); groupIndex++) 214 | { 215 | auto group = tile->Groups[groupIndex]; 216 | 217 | for (int itemIndex = 0; itemIndex < group->Items.Num(); itemIndex++) 218 | { 219 | auto item = group->Items[itemIndex]; 220 | 221 | for (int meshIndex = 0; meshIndex < item->MeshComponents.Num(); meshIndex++) 222 | { 223 | item->MeshComponents[meshIndex]->ClearInstances(); 224 | } 225 | } 226 | } 227 | } 228 | 229 | -------------------------------------------------------------------------------- /Source/Landscape_1/Private/ForestTileActor.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "Landscape_1.h" 4 | #include "ForestTileActor.h" 5 | #include "Runtime/Landscape/Classes/Landscape.h" 6 | #include "Kismet/KismetSystemLibrary.h" 7 | #include "math.h" 8 | 9 | AForestTileActor::AForestTileActor() { 10 | SpawnChance = 1; 11 | FoliageSpawnChance = 1; 12 | TreesPerTile = 1; 13 | FoliageStartCullDistance = 30000; 14 | FoliageEndCullDistance = 40000; 15 | } 16 | 17 | AForestTileActor::~AForestTileActor() { 18 | ForestTiles.Empty(); 19 | } 20 | 21 | void AForestTileActor::BeginPlay() 22 | { 23 | Super::BeginPlay(); 24 | 25 | ForestTiles.AddUninitialized(Super::Size * Super::Size); 26 | uint32 seed = Hash(InitialSeed); 27 | 28 | float tilesize = Radius * 2 / Super::Size; 29 | float startCullDistance = Radius - (tilesize * 2.5f); 30 | float endCullDistance = Radius - (tilesize * 1.5f); 31 | 32 | for (int32 y = 0; y < Super::Size * Super::Size; y++) 33 | { 34 | UForestTile* tile = NewObject(this, NAME_None); 35 | TArray components; 36 | 37 | for (int32 i = 0; i < Trees.Num(); i++) 38 | { 39 | seed = Hash(seed); 40 | UHierarchicalInstancedStaticMeshComponent* component = NewObject(this); 41 | component->StaticMesh = Trees[i].Mesh; 42 | component->bSelectable = false; 43 | component->bHasPerInstanceHitProxies = true; 44 | component->InstancingRandomSeed = seed % INT32_MAX; 45 | 46 | //@todo - take the settings from a UFoliageType object. For now, disable distance field lighting on grass so we don't hitch. 47 | //component->bAffectDistanceFieldLighting = false; 48 | 49 | component->InstanceStartCullDistance = startCullDistance; 50 | component->InstanceEndCullDistance = endCullDistance; 51 | 52 | if (Trees[i].Collision == false) 53 | component->SetCollisionEnabled(ECollisionEnabled::NoCollision); 54 | else 55 | component->SetCollisionEnabled(ECollisionEnabled::QueryOnly); 56 | 57 | component->AttachTo(GetRootComponent()); 58 | component->RegisterComponent(); 59 | components.Add(component); 60 | } 61 | 62 | for (int32 i = 0; i < FoliageAroundTrees.Num(); i++) 63 | { 64 | seed = Hash(seed); 65 | UHierarchicalInstancedStaticMeshComponent* component = NewObject(this); 66 | component->StaticMesh = FoliageAroundTrees[i].Mesh; 67 | component->bSelectable = false; 68 | component->bHasPerInstanceHitProxies = true; 69 | component->InstancingRandomSeed = seed % INT32_MAX; 70 | 71 | //@todo - take the settings from a UFoliageType object. For now, disable distance field lighting on grass so we don't hitch. 72 | //component->bAffectDistanceFieldLighting = false; 73 | 74 | component->InstanceStartCullDistance = FoliageStartCullDistance; 75 | component->InstanceEndCullDistance = FoliageEndCullDistance; 76 | 77 | if (FoliageAroundTrees[i].Collision == false) 78 | component->SetCollisionEnabled(ECollisionEnabled::NoCollision); 79 | else 80 | component->SetCollisionEnabled(ECollisionEnabled::QueryOnly); 81 | 82 | component->AttachTo(GetRootComponent()); 83 | component->RegisterComponent(); 84 | components.Add(component); 85 | } 86 | 87 | tile->MeshComponents = components; 88 | ForestTiles[y] = tile; 89 | } 90 | 91 | Generate(); 92 | } 93 | 94 | void AForestTileActor::Generate() { 95 | if (Trees.Num() == 0) 96 | return; 97 | 98 | double StartTime = FPlatformTime::Seconds(); 99 | uint32 seed = Hash(InitialSeed); 100 | TArray instances; 101 | 102 | float initialForestTilesize = Radius * 2 / Super::Size; 103 | 104 | for (int32 i = 0; i < Super::Size; i++) 105 | { 106 | for (int32 y = 0; y < Super::Size; y++) 107 | { 108 | for (int32 prTile = 0; prTile < TreesPerTile; prTile++) 109 | { 110 | seed = Hash(seed); 111 | 112 | if ((double)seed / UINT32_MAX > SpawnChance) 113 | continue; 114 | 115 | seed = Hash(seed); 116 | float r1 = (double)seed / UINT32_MAX; 117 | seed = Hash(seed); 118 | float r2 = (double)seed / UINT32_MAX; 119 | 120 | FTreeInstance treeInstance = FTreeInstance(); 121 | treeInstance.Location = FVector(initialForestTilesize * i + initialForestTilesize * r1, initialForestTilesize * y + initialForestTilesize * r2, 0.0f); 122 | 123 | treeInstance.MeshIndex = seed % Trees.Num(); 124 | treeInstance.Bounds = Trees[treeInstance.MeshIndex].Bounds; 125 | treeInstance.Scale = Trees[treeInstance.MeshIndex].ScaleMax; 126 | 127 | if (TestLocation(treeInstance.Location, treeInstance.Bounds, instances)) 128 | instances.Add(treeInstance); 129 | } 130 | } 131 | } 132 | 133 | int32 mainInstancesCount = instances.Num(); 134 | 135 | for (int32 i = 0; i < mainInstancesCount; i++) 136 | { 137 | seed = Hash(seed); 138 | AddChildren(instances[i], 0, instances, seed); 139 | } 140 | 141 | float ForestTilesize = Radius * 2 / Super::Size; 142 | 143 | for (int32 i = 0; i < instances.Num(); i++) 144 | { 145 | FTreeInstance & instance = instances[i]; 146 | int32 x = (int32)floor(instance.Location.X / ForestTilesize); 147 | int32 y = (int32)floor(instance.Location.Y / ForestTilesize); 148 | 149 | if (x > Super::Size - 1) 150 | x = Super::Size - 1; 151 | 152 | if (y > Super::Size - 1) 153 | y = Super::Size - 1; 154 | 155 | int32 index = GetIndex(x, y); 156 | instance.Location -= FVector(x * ForestTilesize, y * ForestTilesize, 0.0f); // Make location relative to tile cell 157 | UForestTile* tile = ForestTiles[index]; 158 | 159 | if (tile) 160 | { 161 | instance.InstanceId = tile->MeshComponents[instance.MeshIndex]->AddInstance(FTransform(FVector(0.0f, 0.0f, -1000000.0f))); 162 | tile->Instances.Add(instance); 163 | 164 | for (int32 y = 0; y < FoliageAroundTrees.Num(); y++) 165 | { 166 | FForestFoliageMesh & mesh = FoliageAroundTrees[y]; 167 | 168 | for (int32 x = 0; x < mesh.MaxPerTree; x++) 169 | { 170 | seed = Hash(seed); 171 | 172 | if ((double)seed / UINT32_MAX > FoliageSpawnChance) 173 | continue; 174 | 175 | FTreeInstance foliageInstance = FTreeInstance(); 176 | foliageInstance.MeshIndex = y; 177 | foliageInstance.IsFoliage = true; 178 | 179 | seed = Hash(seed); 180 | float r1 = ((double)seed / UINT32_MAX) - 0.5f; 181 | 182 | seed = Hash(seed); 183 | float r2 = ((double)seed / UINT32_MAX); 184 | 185 | seed = Hash(seed); 186 | float rad = seed % 57; 187 | float bounds = mesh.MinBounds + (mesh.MaxBounds - mesh.MinBounds) * r2; 188 | float locationX = bounds * cos(rad); 189 | float locationY = bounds * sin(rad); 190 | 191 | foliageInstance.Scale = mesh.ScaleMin + (mesh.ScaleMax - mesh.ScaleMin) * r1; 192 | foliageInstance.Location = instance.Location + FVector(locationX, locationY, 0.0f); 193 | 194 | foliageInstance.InstanceId = tile->MeshComponents[Trees.Num() + foliageInstance.MeshIndex]->AddInstance(FTransform(FVector(0.0f, 0.0f, -1000000.0f))); 195 | tile->Instances.Add(foliageInstance); 196 | } 197 | 198 | } 199 | } 200 | } 201 | 202 | instances.Empty(); 203 | 204 | UE_LOG(LogStaticMesh, Display, TEXT("Generated %d trees in %.3fs."), instances.Num(), float(FPlatformTime::Seconds() - StartTime)); 205 | } 206 | 207 | void AForestTileActor::AddChildren(FTreeInstance parent, int32 step, TArray & instances, uint32 seed) { 208 | FForestMesh forestMesh = Trees[parent.MeshIndex]; 209 | 210 | step = step++; 211 | 212 | if (step > forestMesh.NumberOfSteps) 213 | return; 214 | 215 | for (int i = 0; i < forestMesh.ChildrenPerTree; i++) 216 | { 217 | seed = Hash(seed); 218 | FVector childLocation = FVector(); 219 | int32 childMeshIndex = seed % Trees.Num(); 220 | FForestMesh childMesh = Trees[childMeshIndex]; 221 | 222 | float r = ((double)seed / UINT32_MAX) * 0.2f; 223 | float r2 = 1.0f; 224 | 225 | if (forestMesh.NumberOfSteps > 0) // Divide by zero check 226 | r2 = 1.0f - ((double)step / forestMesh.NumberOfSteps); 227 | 228 | float childScale = childMesh.ScaleMin + ((childMesh.ScaleMax - childMesh.ScaleMin) * r2) + ((childMesh.ScaleMax - childMesh.ScaleMin) * r); 229 | float childBounds = childMesh.Bounds * childScale; 230 | float r3 = 1.0f + (0.5f * ((double)seed / UINT32_MAX)); 231 | float bounds = ((childBounds + parent.Bounds) / 2) * r3; 232 | 233 | if (FindLocation(seed, parent.Location, bounds, instances, childLocation)) { 234 | FTreeInstance treeInstance = FTreeInstance(); 235 | treeInstance.MeshIndex = childMeshIndex; 236 | treeInstance.Location = childLocation; 237 | treeInstance.Bounds = childBounds; 238 | treeInstance.Scale = childScale; 239 | instances.Add(treeInstance); 240 | 241 | AddChildren(treeInstance, step, instances, seed); 242 | //UE_LOG(LogTemp, Display, TEXT("Child: %f, %f"), childLocation.X, childLocation.Y); 243 | } 244 | } 245 | } 246 | 247 | bool AForestTileActor::FindLocation(uint32 seed, FVector parentLocation, float bounds, TArray & instances, FVector & childLocation) { 248 | 249 | seed = Hash(seed); 250 | float scaler = seed / UINT32_MAX; 251 | seed = Hash(seed); 252 | float r1 = ((double)seed / UINT32_MAX) + 1.0f; 253 | seed = Hash(seed); 254 | float r2 = ((double)seed / UINT32_MAX) + 1.0f; 255 | seed = Hash(seed); 256 | float rad = seed % 57; 257 | float locationX = bounds * cos(rad) * r1; 258 | float locationY = bounds * sin(rad) * r2; 259 | 260 | childLocation = parentLocation + FVector(locationX, locationY, 0.0f); 261 | 262 | if (childLocation.X < 1.0f) 263 | childLocation.X = Radius * 2 + childLocation.X; 264 | 265 | if (childLocation.X > Radius * 2 - 1) 266 | childLocation.X = 0.0f + childLocation.X; 267 | 268 | if (childLocation.Y < 1.0f) 269 | childLocation.Y = Radius * 2 + childLocation.Y; 270 | 271 | if (childLocation.Y > Radius * 2 - 1) 272 | childLocation.Y = 0.0f + childLocation.Y; 273 | 274 | return TestLocation(childLocation, bounds, instances); 275 | } 276 | 277 | bool AForestTileActor::TestLocation(FVector location, float bounds, TArray & instances) { 278 | for (int32 i = 0; i < instances.Num(); i++) 279 | { 280 | float x = location.X - instances[i].Location.X; 281 | 282 | if (x < 0.0f) 283 | x *= -1; 284 | 285 | if (x > bounds * 2) 286 | continue; 287 | 288 | float y = location.Y - instances[i].Location.Y; 289 | 290 | if (y < 0.0f) 291 | y *= -1; 292 | 293 | if (y > bounds * 2) 294 | continue; 295 | 296 | if (FVector::Dist(location, instances[i].Location) < bounds) 297 | return false; 298 | } 299 | 300 | return true; 301 | } 302 | 303 | void AForestTileActor::UpdateTile(int32 x, int32 y, FVector location) { 304 | if (Trees.Num() == 0) 305 | return; 306 | 307 | UForestTile* tile = ForestTiles[GetIndex(x, y)]; 308 | uint32 seed = Hash(InitialSeed + (x * Super::Size) + y); 309 | 310 | for (int32 i = 0; i < tile->Instances.Num(); i++) 311 | { 312 | seed = Hash(seed); 313 | FTreeInstance instance = tile->Instances[i]; 314 | 315 | if (instance.IsFoliage == false) { 316 | FForestMesh mesh = Trees[instance.MeshIndex]; 317 | FVector newLocation = location + instance.Location; 318 | FTransform transform = GetTransform(newLocation, seed, mesh.AlignWithSlope, mesh.HeightMin, mesh.HeightMax, mesh.MaxSlope, mesh.PhysicalMaterials); 319 | transform.SetScale3D(FVector(instance.Scale, instance.Scale, instance.Scale)); 320 | tile->MeshComponents[instance.MeshIndex]->UpdateInstanceTransform(instance.InstanceId, transform, true); 321 | } 322 | else { 323 | FForestFoliageMesh mesh = FoliageAroundTrees[instance.MeshIndex]; 324 | FVector newLocation = location + instance.Location; 325 | FTransform transform = GetTransform(newLocation, seed, mesh.AlignWithSlope, mesh.HeightMin, mesh.HeightMax, mesh.MaxSlope, mesh.PhysicalMaterials); 326 | transform.SetScale3D(FVector(instance.Scale, instance.Scale, instance.Scale)); 327 | tile->MeshComponents[Trees.Num() + instance.MeshIndex]->UpdateInstanceTransform(instance.InstanceId, transform, true); 328 | } 329 | } 330 | } 331 | 332 | FTransform AForestTileActor::GetTransform(FVector location, uint32 seed, bool AlignWithSlope, float HeightMin, float HeightMax, float MaxSlope, TArray PhysicalMaterials) { 333 | const FVector Start = FVector(location.X, location.Y, 100000.0f); 334 | const FVector End = FVector(location.X, location.Y, -100000.0f); 335 | float r = (double)seed / UINT32_MAX; 336 | FTransform result = FTransform(); 337 | FQuat rotator = FQuat(FRotator(0.0f, 360.0f * r, 0.0f)); 338 | result.SetTranslation(FVector(-1.0f, -1.0f, -100000.0f)); 339 | result.SetRotation(rotator); 340 | 341 | FHitResult HitData(ForceInit); 342 | 343 | TArray ActorsToIgnore; 344 | ActorsToIgnore.Add(NULL); 345 | 346 | TArray > Objects; 347 | Objects.Add(EObjectTypeQuery::ObjectTypeQuery1); 348 | 349 | if (UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), Start, End, Objects, false, ActorsToIgnore, EDrawDebugTrace::None, HitData, true)) 350 | { 351 | if (HitData.GetActor() && HitData.Actor->IsA(ALandscape::StaticClass())) 352 | { 353 | float angle = GetAngle(Start, HitData); 354 | 355 | if (HitData.ImpactPoint.Z < HeightMin || HitData.ImpactPoint.Z > HeightMax || angle > MaxSlope) 356 | return result; 357 | 358 | if (PhysicalMaterials.Num() > 0) { 359 | if (HitData.PhysMaterial == NULL) { 360 | return result; 361 | } 362 | 363 | auto name = HitData.PhysMaterial->GetName(); 364 | bool found = false; 365 | 366 | for (int i = 0; i < PhysicalMaterials.Num(); i++) 367 | { 368 | if (PhysicalMaterials[i]->GetName() == name) 369 | found = true; 370 | } 371 | 372 | if (found == false) 373 | return result; 374 | } 375 | 376 | result.SetTranslation(FVector(location.X, location.Y, HitData.ImpactPoint.Z)); 377 | 378 | if (AlignWithSlope == true) 379 | { 380 | FRotator rotation = FRotationMatrix::MakeFromZ(HitData.Normal).Rotator(); 381 | result.SetRotation(FQuat(rotation) * rotator); 382 | } 383 | } 384 | } 385 | 386 | return result; 387 | } 388 | 389 | void AForestTileActor::PostEditChangeProperty(struct FPropertyChangedEvent& e) 390 | { 391 | Super::PostEditChangeProperty(e); 392 | } 393 | 394 | float AForestTileActor::GetAngle(FVector start, FHitResult HitData) { 395 | float slope = HitData.ImpactPoint.Size() / HitData.ImpactNormal.Size(); 396 | FVector impact = start - HitData.ImpactPoint; 397 | impact.Normalize(); 398 | float DotProduct = FVector::DotProduct(impact, HitData.ImpactNormal); 399 | float radians = acosf(DotProduct); 400 | return FMath::RadiansToDegrees(radians); 401 | } 402 | 403 | int32 AForestTileActor::GetIndex(int32 x, int32 y) { 404 | int32 result = 0; 405 | 406 | for (int32 x2 = 0; x2 < Super::Size; x2++) 407 | { 408 | for (int32 y2 = 0; y2 < Super::Size; y2++) 409 | { 410 | if (x2 == x && y2 == y) 411 | return result; 412 | 413 | result++; 414 | 415 | if (x2 == x && y2 == y) 416 | return result; 417 | } 418 | } 419 | 420 | return result; 421 | } 422 | 423 | uint32 AForestTileActor::Hash(uint32 a) 424 | { 425 | a = (a ^ 61) ^ (a >> 16); 426 | a = a + (a << 3); 427 | a = a ^ (a >> 4); 428 | a = a * 0x27d4eb2d; 429 | a = a ^ (a >> 15); 430 | return a; 431 | } 432 | 433 | 434 | --------------------------------------------------------------------------------