├── Config ├── DefaultEditor.ini ├── DefaultGame.ini └── DefaultEngine.ini ├── Content ├── L_Globe.umap ├── NS_FoliageAnalysis.uasset ├── BP_FoliageCaptureActor.uasset ├── RenderTargets │ ├── RT_Depth.uasset │ ├── RT_Imagery.uasset │ ├── RT_Normals.uasset │ └── RT_Classifications.uasset ├── NF_DetermineFoliageType.uasset └── BP_ProceduralFoliageEllipsoid.uasset ├── Source ├── aiden_geo_tutorial │ ├── Private │ │ ├── FoliageHISM.cpp │ │ ├── ProceduralFoliageEllipsoid.cpp │ │ └── FoliageCaptureActor.cpp │ ├── aiden_geo_tutorial.h │ ├── aiden_geo_tutorial.cpp │ ├── Public │ │ ├── FoliageHISM.h │ │ ├── ProceduralFoliageEllipsoid.h │ │ └── FoliageCaptureActor.h │ └── aiden_geo_tutorial.Build.cs ├── aiden_geo_tutorial.Target.cs └── aiden_geo_tutorialEditor.Target.cs ├── aiden_geo_tutorial.uproject ├── .gitignore ├── README.md └── LICENSE /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Content/L_Globe.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/L_Globe.umap -------------------------------------------------------------------------------- /Content/NS_FoliageAnalysis.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/NS_FoliageAnalysis.uasset -------------------------------------------------------------------------------- /Content/BP_FoliageCaptureActor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/BP_FoliageCaptureActor.uasset -------------------------------------------------------------------------------- /Content/RenderTargets/RT_Depth.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/RenderTargets/RT_Depth.uasset -------------------------------------------------------------------------------- /Content/NF_DetermineFoliageType.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/NF_DetermineFoliageType.uasset -------------------------------------------------------------------------------- /Content/RenderTargets/RT_Imagery.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/RenderTargets/RT_Imagery.uasset -------------------------------------------------------------------------------- /Content/RenderTargets/RT_Normals.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/RenderTargets/RT_Normals.uasset -------------------------------------------------------------------------------- /Content/BP_ProceduralFoliageEllipsoid.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/BP_ProceduralFoliageEllipsoid.uasset -------------------------------------------------------------------------------- /Content/RenderTargets/RT_Classifications.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SquarerFive/cesium-procedural-foliage/HEAD/Content/RenderTargets/RT_Classifications.uasset -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Private/FoliageHISM.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "FoliageHISM.h" -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/aiden_geo_tutorial.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/EngineSettings.GeneralProjectSettings] 4 | ProjectID=41C65E404A04BFB704F3E3B974F8B99A 5 | 6 | [StartupActions] 7 | bAddPacks=True 8 | InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent") 9 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/aiden_geo_tutorial.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "aiden_geo_tutorial.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, aiden_geo_tutorial, "aiden_geo_tutorial" ); 7 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial.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 aiden_geo_tutorialTarget : TargetRules 7 | { 8 | public aiden_geo_tutorialTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Game; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | 13 | ExtraModuleNames.AddRange( new string[] { "aiden_geo_tutorial" } ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorialEditor.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 aiden_geo_tutorialEditorTarget : TargetRules 7 | { 8 | public aiden_geo_tutorialEditorTarget(TargetInfo Target) : base(Target) 9 | { 10 | Type = TargetType.Editor; 11 | DefaultBuildSettings = BuildSettingsVersion.V2; 12 | 13 | ExtraModuleNames.AddRange( new string[] { "aiden_geo_tutorial" } ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Public/FoliageHISM.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/HierarchicalInstancedStaticMeshComponent.h" 7 | #include "FoliageHISM.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class AIDEN_GEO_TUTORIAL_API UFoliageHISM : public UHierarchicalInstancedStaticMeshComponent 14 | { 15 | friend class UInstancedStaticMeshComponent; 16 | GENERATED_BODY() 17 | public: 18 | UPROPERTY() 19 | TArray Transforms; 20 | 21 | UPROPERTY() 22 | bool bMarkedForAdd = false; 23 | 24 | UPROPERTY() 25 | bool bMarkedForClear = false; 26 | 27 | UPROPERTY() 28 | bool bCleared = false; 29 | }; 30 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Public/ProceduralFoliageEllipsoid.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Cesium3DTileset.h" 7 | #include "FoliageCaptureActor.h" 8 | 9 | #include "ProceduralFoliageEllipsoid.generated.h" 10 | 11 | /** 12 | * 13 | */ 14 | UCLASS() 15 | class AIDEN_GEO_TUTORIAL_API AProceduralFoliageEllipsoid : public ACesium3DTileset 16 | { 17 | GENERATED_BODY() 18 | 19 | public: 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage") 21 | AFoliageCaptureActor* FoliageCaptureActor; 22 | 23 | virtual void Tick(float DeltaSeconds) override; 24 | 25 | protected: 26 | // Initial spawn 27 | bool bHasFoliageSpawned = false; 28 | }; 29 | -------------------------------------------------------------------------------- /aiden_geo_tutorial.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "5.0", 4 | "Category": "", 5 | "Description": "", 6 | "Modules": [ 7 | { 8 | "Name": "aiden_geo_tutorial", 9 | "Type": "Runtime", 10 | "LoadingPhase": "Default", 11 | "AdditionalDependencies": [ 12 | "Engine", 13 | "CesiumRuntime" 14 | ] 15 | } 16 | ], 17 | "Plugins": [ 18 | { 19 | "Name": "CesiumForUnreal", 20 | "Enabled": true, 21 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/87b0d05800a545d49bf858ef3458c4f7", 22 | "SupportedTargetPlatforms": [ 23 | "Win64", 24 | "Mac", 25 | "Linux", 26 | "Android", 27 | "IOS" 28 | ] 29 | }, 30 | { 31 | "Name": "Bridge", 32 | "Enabled": true, 33 | "SupportedTargetPlatforms": [ 34 | "Win64", 35 | "Mac", 36 | "Linux" 37 | ] 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/aiden_geo_tutorial.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class aiden_geo_tutorial : ModuleRules 6 | { 7 | public aiden_geo_tutorial(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Foliage", "CesiumRuntime", "RHI", "RenderCore", "HTTP" }); 12 | 13 | PrivateDependencyModuleNames.AddRange(new string[] { }); 14 | 15 | CppStandard = CppStandardVersion.Cpp17; 16 | // Uncomment if you are using Slate UI 17 | // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); 18 | 19 | // Uncomment if you are using online features 20 | // PrivateDependencyModuleNames.Add("OnlineSubsystem"); 21 | 22 | // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | Config/DefaultEngine.ini 76 | *.xml 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cesium-procedural-foliage 2 | 3 | ![Banner](https://raw.githubusercontent.com/SquarerFive/SquarerFive/refs/heads/master/rect9550.png) 4 | 5 | This is an example project based on the tutorial: https://cesium.com/learn/unreal/unreal-procedural-foliage/ . 6 | 7 | ## Requirements: 8 | - Unreal Engine 4.27 9 | - The Cesium for Unreal plugin. 10 | - DX12-capable graphics card and OS. 11 | - Microsoft Visual Studio 2019 12 | 13 | ## Setting it up 14 | 1. Right click on `aiden_geo_tutorial.uproject` and click 'Generate Visual Studio Project Files'. 15 | 2. Open aiden_geo_tutorial.sln and click on `Local Windows Debugger`. This will build the solution and start the Unreal Editor. 16 | 3. Set your Cesium ION token in the Cesium 3D tileset actor. 17 | 4. After clicking Play, foliage instances should now be spawning in. 18 | 19 | ## Troubleshooting and optimisations 20 | ### Please see https://cesium.com/learn/unreal/unreal-procedural-foliage/#optimizations--troubleshooting 21 | 22 | ## Changes in this repo (DD/MM/YYYY) 23 | 24 | ### 19/12/2021 25 | ``` 26 | 1. Fixed #3 bug, system should balance instances across ISMs. 27 | 2. Improve world origin rebasing support 28 | ``` 29 | 30 | ### 10/12/2021 31 | ``` 32 | 1. Upgraded project to support cesium-unreal 1.7.0 to 1.8.1 33 | ``` 34 | 35 | ### 20/10/2021 36 | ``` 37 | 1. Set DX12 as the default RHI 38 | 2. Fix unusual startup crash by disabling auto-activate on the Niagara component. 39 | ``` 40 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Private/ProceduralFoliageEllipsoid.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProceduralFoliageEllipsoid.h" 5 | 6 | #include "Kismet/GameplayStatics.h" 7 | 8 | void AProceduralFoliageEllipsoid::Tick(float DeltaSeconds) 9 | { 10 | Super::Tick(DeltaSeconds); 11 | ACesiumGeoreference* Geo = this->ResolveGeoreference(); 12 | if (IsValid(Geo) && IsValid(FoliageCaptureActor)) 13 | { 14 | // Don't start another build while the previous one is still running 15 | if (!FoliageCaptureActor->IsBuilding()) 16 | { 17 | APlayerCameraManager* CameraManager = UGameplayStatics::GetPlayerCameraManager(this, 0); 18 | if (IsValid(CameraManager)) 19 | { 20 | // Project the camera coordinates to geographic coordinates. 21 | const FVector CameraLocation = CameraManager->GetCameraLocation(); 22 | glm::dvec3 GeographicCameraLocation = Geo->TransformUnrealToLongitudeLatitudeHeight( 23 | glm::dvec3(CameraLocation.X, CameraLocation.Y, CameraLocation.Z)); 24 | 25 | // Keep original camera elevation as a variable, and swap z with the capture elevation (this will be the new capture location). 26 | const double CurrentCameraElevation = GeographicCameraLocation.z; 27 | GeographicCameraLocation.z = FoliageCaptureActor->CaptureElevation; 28 | 29 | // Ensure CesiumGeoreference is valid 30 | if (!IsValid(FoliageCaptureActor->Georeference)) 31 | { 32 | FoliageCaptureActor->Georeference = Geo; 33 | } 34 | 35 | // Current geographic location of the foliage capture actor (used to measure the distance). 36 | const glm::dvec3 CurrentFoliageCaptureGeographicLocation = Geo->TransformUnrealToLongitudeLatitudeHeight(glm::dvec3( 37 | FoliageCaptureActor->GetActorLocation().X, FoliageCaptureActor->GetActorLocation().Y, FoliageCaptureActor->GetActorLocation().Z 38 | )); 39 | 40 | const double Distance = glm::distance(GeographicCameraLocation, CurrentFoliageCaptureGeographicLocation); 41 | const double Speed = CameraManager->GetVelocity().Size(); 42 | 43 | // New capture position 44 | const glm::dvec3 NewFoliageCaptureUELocation = Geo->TransformLongitudeLatitudeHeightToUnreal(GeographicCameraLocation); 45 | 46 | FoliageCaptureActor->PlayerSpeed = Speed; 47 | 48 | // Only update the foliage capture actor if the player is outside of the capture grid, within elevation and a speed less than 5000. 49 | if ((Distance > FoliageCaptureActor->CaptureWidthInDegrees/2 && CurrentCameraElevation <= FoliageCaptureActor->CaptureElevation && Speed < FoliageCaptureActor->PlayerSpeedUpdateThreshold && !FoliageCaptureActor->IsWaiting()) || !bHasFoliageSpawned) 50 | { 51 | FoliageCaptureActor->OnUpdate(FVector(NewFoliageCaptureUELocation.x, NewFoliageCaptureUELocation.y, NewFoliageCaptureUELocation.z)); 52 | bHasFoliageSpawned = true; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Public/FoliageCaptureActor.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/Actor.h" 7 | 8 | #include "Engine/TextureRenderTarget2D.h" 9 | #include "FoliageType_InstancedStaticMesh.h" 10 | #include "CesiumGeoreference.h" 11 | #include "FoliageHISM.h" 12 | 13 | #include "FoliageCaptureActor.generated.h" 14 | 15 | // EXPERIMENTAL 16 | #define FOLIAGE_REDUCE_FLICKER_APPROACH_ENABLED 1 17 | 18 | /** 19 | * @brief Used to store the reprojected points gathered from the RT. 20 | */ 21 | USTRUCT(BlueprintType) 22 | struct FFoliageTransforms 23 | { 24 | GENERATED_BODY() 25 | 26 | TMap> HISMTransformMap; 27 | }; 28 | 29 | /** 30 | * @brief Array of foliage type transforms gathered from the RT extraction task. 31 | */ 32 | USTRUCT() 33 | struct FFoliageTransformsTypeMap 34 | { 35 | GENERATED_BODY() 36 | TArray FoliageTypes; 37 | }; 38 | 39 | /** 40 | * @brief Foliage geometry container 41 | */ 42 | USTRUCT(BlueprintType) 43 | struct FFoliageGeometryType 44 | { 45 | GENERATED_BODY() 46 | 47 | /* Placement */ 48 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Placement") 49 | float Density = 0.5f; 50 | 51 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Placement") 52 | bool bRandomYaw = false; 53 | 54 | UPROPERTY(EditAnywhere, Category = "Placement") 55 | FFloatInterval ZOffset = FFloatInterval(0.0, 0.0); 56 | 57 | UPROPERTY(EditAnywhere, Category = "Placement") 58 | FFloatInterval Scale = FFloatInterval(1.0, 1.0); 59 | 60 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Placement") 61 | bool bAlignToNormal = false; 62 | 63 | /* Mesh Settings */ 64 | 65 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 66 | UStaticMesh* Mesh; 67 | 68 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 69 | bool bCollidesWithWorld = true; 70 | 71 | UPROPERTY(EditAnywhere, Category = "Placement") 72 | FFloatInterval CullingDistances = FFloatInterval(4096, 32768); 73 | 74 | /* Expensive */ 75 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mesh") 76 | bool bAffectsDistanceFieldLighting = false; 77 | 78 | friend uint32 GetTypeHash(const FFoliageGeometryType& A) 79 | { 80 | return GetTypeHash(A.Density) + GetTypeHash(A.bRandomYaw) + GetTypeHash(A.ZOffset) + GetTypeHash(A.Scale) + 81 | GetTypeHash(A.Mesh) + GetTypeHash(A.bCollidesWithWorld) + 82 | GetTypeHash(A.bAffectsDistanceFieldLighting); 83 | } 84 | 85 | friend bool operator==(const FFoliageGeometryType& A, const FFoliageGeometryType& B) 86 | { 87 | return A.Density == B.Density && A.Mesh == B.Mesh && A.bCollidesWithWorld == B.bCollidesWithWorld && A. 88 | bAffectsDistanceFieldLighting == B.bAffectsDistanceFieldLighting 89 | && A.Scale.Max == B.Scale.Max && A.Scale.Min == B.Scale.Min && A.bRandomYaw == B.bRandomYaw && 90 | A.ZOffset.Min == B.ZOffset.Min && A.ZOffset.Max == B.ZOffset.Max; 91 | } 92 | }; 93 | 94 | /** 95 | * @brief Container for a foliage type 96 | */ 97 | USTRUCT(BlueprintType) 98 | struct FFoliageClassificationType 99 | { 100 | GENERATED_BODY() 101 | 102 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 103 | FString Type; 104 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 105 | FLinearColor ColourClassification; 106 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 107 | TArray FoliageTypes; 108 | 109 | /** 110 | * @brief If enabled, a line trace will be casted downwards from each point to determine surface normals. 111 | */ 112 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 113 | bool bAlignToSurfaceWithRaycast = false; 114 | 115 | UPROPERTY(BlueprintReadWrite, EditAnywhere) 116 | int32 PooledHISMsToCreatePerFoliageType = 4; 117 | }; 118 | 119 | // Called after points have been gathered and reprojected from the classification RT. 120 | DECLARE_DELEGATE_OneParam(FOnFoliageTransformsGenerated, FFoliageTransformsTypeMap); 121 | 122 | // This is called after pixels have been extracted from input RTs 123 | DECLARE_DELEGATE_OneParam(FOnRenderTargetRead, bool); 124 | 125 | UCLASS() 126 | class AIDEN_GEO_TUTORIAL_API AFoliageCaptureActor : public AActor 127 | { 128 | GENERATED_BODY() 129 | 130 | public: 131 | // Sets default values for this actor's properties 132 | AFoliageCaptureActor(); 133 | 134 | protected: 135 | // Called when the game starts or when spawned 136 | virtual void BeginPlay() override; 137 | 138 | public: 139 | // Called every frame 140 | virtual void Tick(float DeltaTime) override; 141 | 142 | public: 143 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 144 | TArray FoliageTypes; 145 | 146 | UPROPERTY() 147 | ACesiumGeoreference* Georeference; 148 | 149 | /** 150 | * @brief Elevation (in metres) of the scene capture component that's placed above the player. 151 | */ 152 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 153 | float CaptureElevation = 1024.f; 154 | 155 | /** 156 | * @brief Orthographic width of our scene capture components 157 | */ 158 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 159 | float CaptureWidth = 131072.f; 160 | 161 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 162 | int32 UpdateFoliageAfterNumFrames = 2; 163 | 164 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 165 | int32 MaxComponentsToUpdatePerFrame = 1; 166 | 167 | /** 168 | * @brief Coverage grid. 169 | */ 170 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 171 | FIntVector GridSize = FIntVector(0, 0, 0); 172 | 173 | /** 174 | * @brief Set to true if origin rebasing is enabled within the project. 175 | * Set to disabled if not needed to save performance. 176 | */ 177 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Foliage Spawner") 178 | bool bIsRebasing = true; 179 | 180 | /** 181 | * @brief Average geographic width in degrees. 182 | */ 183 | double CaptureWidthInDegrees = 0.01; 184 | 185 | /** 186 | * @brief Camera speed 187 | */ 188 | double PlayerSpeed = 0.0; 189 | /** 190 | * @brief Camera speed threshold, we should only update if the speed is below this threshold 191 | */ 192 | double PlayerSpeedUpdateThreshold = 5000; 193 | 194 | public: 195 | /** 196 | * @brief Build foliage transforms according to classification types. 197 | * @param FoliageDistributionMap Render Target containing classifications. 198 | * @param NormalAndDepthMap Render Target with normals in the RGB channel and *normalized* depth in Alpha. 199 | * @param RTWorldBounds UE world extents of the render targets. 200 | */ 201 | UFUNCTION(BlueprintCallable, Category = "Foliage Spawner") 202 | void BuildFoliageTransforms(UTextureRenderTarget2D* FoliageDistributionMap, 203 | UTextureRenderTarget2D* NormalAndDepthMap, FBox RTWorldBounds); 204 | 205 | UFUNCTION(BlueprintCallable, Category = "Foliage Spawner") 206 | void ClearFoliageInstances(); 207 | 208 | /** 209 | * @brief Create required HISM components, removing if outdated 210 | */ 211 | void ResetAndCreateHISMComponents(); 212 | 213 | /** 214 | * @brief Implementable BP event, called when the player moves outside of the capture boundaries. 215 | */ 216 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Foliage Spawner") 217 | void OnUpdate(const FVector& NewLocation); 218 | 219 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Foliage Spawner") 220 | void OnInstancesCleared(); 221 | 222 | /** 223 | * @brief Is the foliage currently building? 224 | */ 225 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Foliage Spawner") 226 | bool IsBuilding() const; 227 | 228 | /* 229 | * @brief Are we waiting to be built? 230 | */ 231 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Foliage Spawner") 232 | bool IsWaiting() const; 233 | 234 | protected: 235 | /** 236 | * @brief Attempt to correct normals and elevation by raycasting 237 | */ 238 | void CorrectFoliageTransform(const FVector& InEngineCoordinates, const FMatrix& InEastNorthUp, 239 | FVector& OutCorrectedPosition, FVector& OutSurfaceNormals, bool& bSuccess) const; 240 | 241 | /** 242 | * @brief For each static mesh, we also want to have multiple HISM components to reduce 243 | * hitches when updating instances. 244 | */ 245 | TMap> HISMFoliageMap; 246 | 247 | /** 248 | * @brief The scene depth value is multiplied by a small value so it remains within the range of 0.0 to 1.0. 249 | * In this function it is projected back to it's (approximated) original value and then inverted. 250 | * @param Value Depth value 251 | * @return Height in metres. 252 | */ 253 | double GetHeightFromDepth(const double& Value) const; 254 | 255 | 256 | /** 257 | * @brief Converts pixel coordinates back to geographic coordinates. 258 | */ 259 | FVector PixelToGeographicLocation(const double& X, const double& Y, const double& Altitude, 260 | UTextureRenderTarget2D* RT, const glm::dvec4& GeographicExtents) const; 261 | /** 262 | * @brief Converts geographic coordinates to pixel coordinates. 263 | */ 264 | FIntPoint GeographicToPixelLocation(const double& Longitude, const double& Latitude, UTextureRenderTarget2D* RT, 265 | const glm::dvec4& GeographicExtents) const; 266 | 267 | /** 268 | * @brief Modified version of ReadRenderColorPixels 269 | */ 270 | void ReadLinearColorPixelsAsync( 271 | FOnRenderTargetRead OnRenderTargetRead, 272 | TArray RTs, 273 | TArray*> OutImageData, 274 | FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags( 275 | RCM_MinMax, 276 | CubeFace_MAX), 277 | FIntRect InRect = FIntRect(0, 0, 0, 0), 278 | ENamedThreads::Type ExitThread = ENamedThreads::AnyBackgroundThreadNormalTask); 279 | 280 | /** 281 | * @brief Don't run tick update if true. 282 | */ 283 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Foliage Spawner") 284 | bool bIsBuilding = false; 285 | 286 | /** 287 | * @brief If we are waiting to build foliage 288 | */ 289 | UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Foliage Spawner") 290 | bool bIsWaiting = false; 291 | 292 | /** 293 | * @brief Number of frames that have passed after updating foliage. 294 | */ 295 | int32 Ticks = 0; 296 | 297 | static glm::dvec3 VectorToDVector(const FVector& InVector); 298 | 299 | /** 300 | * @brief Gets the total instance count 301 | */ 302 | int32 GetInstanceCount(); 303 | 304 | /** 305 | * @brief Are all ISMs marked as cleared 306 | */ 307 | bool AllISMsMarkedAsCleared(); 308 | 309 | /** 310 | * @brief Offset all instances 311 | */ 312 | void OffsetAllInstances(const FVector& InOffset); 313 | 314 | TOptional NewActorLocation; 315 | 316 | bool bInstancesClearedCalled = false; 317 | 318 | /** 319 | * @brief Offset between the current world origin and the last world origin. Fixed to 0 if rebasing isn't enabled. 320 | */ 321 | FVector WorldOffset = FVector(0.f); 322 | /** 323 | * @brief Offset between last actor position and current actor position. 324 | */ 325 | FVector ActorOffset = FVector(0.f); 326 | }; 327 | 328 | inline int32 AFoliageCaptureActor::GetInstanceCount() 329 | { 330 | int32 Count = 0; 331 | for (TPair>& FoliageHISMPair : HISMFoliageMap) 332 | { 333 | for (UFoliageHISM* FoliageHISM : FoliageHISMPair.Value) { 334 | Count += FoliageHISM->GetInstanceCount(); 335 | } 336 | } 337 | return Count; 338 | } 339 | 340 | inline bool AFoliageCaptureActor::AllISMsMarkedAsCleared() 341 | { 342 | bool bIsCleared = true; 343 | for (TPair>& FoliageHISMPair : HISMFoliageMap) 344 | { 345 | for (UFoliageHISM* FoliageHISM : FoliageHISMPair.Value) { 346 | if (!FoliageHISM->bCleared) { 347 | bIsCleared = false; 348 | break; 349 | } 350 | } 351 | if (!bIsCleared) { 352 | break; 353 | } 354 | } 355 | return bIsCleared; 356 | } 357 | 358 | inline void AFoliageCaptureActor::OffsetAllInstances(const FVector& InOffset) 359 | { 360 | for (TPair>& FoliageHISMPair : HISMFoliageMap) 361 | { 362 | for (UFoliageHISM* FoliageHISM : FoliageHISMPair.Value) { 363 | TArray WorldTransforms; 364 | WorldTransforms.SetNum(FoliageHISM->GetInstanceCount()); 365 | ParallelFor(WorldTransforms.Num(), [&](int32 Index) { 366 | FoliageHISM->GetInstanceTransform(Index, WorldTransforms[Index], true); 367 | WorldTransforms[Index].SetLocation( 368 | WorldTransforms[Index].GetLocation() + InOffset 369 | ); 370 | }); 371 | FoliageHISM->BatchUpdateInstancesTransforms(0, WorldTransforms, true, true, true); 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/EngineSettings.GameMapsSettings] 4 | GameDefaultMap=/Game/L_Globe.L_Globe 5 | 6 | 7 | EditorStartupMap=/Game/L_Globe.L_Globe 8 | 9 | [/Script/HardwareTargeting.HardwareTargetingSettings] 10 | TargetedHardwareClass=Desktop 11 | AppliedTargetedHardwareClass=Desktop 12 | DefaultGraphicsPerformance=Maximum 13 | AppliedDefaultGraphicsPerformance=Maximum 14 | 15 | [/Script/Engine.Engine] 16 | +ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/aiden_geo_tutorial") 17 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/aiden_geo_tutorial") 18 | 19 | [/Script/Engine.CollisionProfile] 20 | -Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) 21 | -Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 22 | -Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 23 | -Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 24 | -Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 25 | -Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) 26 | -Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) 27 | -Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) 28 | -Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) 29 | -Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) 30 | -Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) 31 | -Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) 32 | -Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) 33 | -Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) 34 | -Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) 35 | -Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) 36 | -Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) 37 | -Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) 38 | +Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision") 39 | +Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ") 40 | +Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ") 41 | +Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ") 42 | +Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ") 43 | +Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.") 44 | +Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ") 45 | +Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ") 46 | +Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.") 47 | +Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.") 48 | +Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors") 49 | +Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors") 50 | +Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.") 51 | +Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.") 52 | +Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.") 53 | +Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.") 54 | +Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.") 55 | +Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ") 56 | +Profiles=(Name="WaterBodyCollision",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="",CustomResponses=((Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="Default Water Collision Profile (Created by Water Plugin)") 57 | -ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") 58 | -ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") 59 | -ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") 60 | -ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") 61 | -ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") 62 | +ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") 63 | +ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") 64 | +ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") 65 | +ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") 66 | +ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") 67 | -CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") 68 | -CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") 69 | -CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") 70 | -CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") 71 | +CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") 72 | +CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") 73 | +CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") 74 | +CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") 75 | 76 | 77 | [CoreRedirects] 78 | +PropertyRedirects=(OldName="/Script/aiden_geo_tutorial.FoliageClassificationType.bLinetrace",NewName="/Script/aiden_geo_tutorial.FoliageClassificationType.bAlignToSurfaceWithRaycast") 79 | 80 | [/Script/WindowsTargetPlatform.WindowsTargetSettings] 81 | DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 82 | 83 | -------------------------------------------------------------------------------- /Source/aiden_geo_tutorial/Private/FoliageCaptureActor.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "FoliageCaptureActor.h" 5 | 6 | #include "Kismet/KismetMathLibrary.h" 7 | 8 | // Sets default values 9 | AFoliageCaptureActor::AFoliageCaptureActor() 10 | { 11 | // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. 12 | PrimaryActorTick.bCanEverTick = true; 13 | } 14 | 15 | // Called when the game starts or when spawned 16 | void AFoliageCaptureActor::BeginPlay() 17 | { 18 | Super::BeginPlay(); 19 | ResetAndCreateHISMComponents(); 20 | 21 | FCoreDelegates::PreWorldOriginOffset.AddLambda([&](UWorld* World, FIntVector CurrentOrigin, FIntVector NewOrigin) { 22 | bIsRebasing = true; 23 | 24 | WorldOffset = FVector(NewOrigin - CurrentOrigin); 25 | }); 26 | 27 | FCoreDelegates::PostWorldOriginOffset.AddLambda([&](UWorld* World, FIntVector CurrentOrigin, FIntVector NewOrigin) { 28 | bIsRebasing = false; 29 | WorldOffset = FVector(0.); 30 | }); 31 | } 32 | 33 | // Called every frame 34 | void AFoliageCaptureActor::Tick(float DeltaTime) 35 | { 36 | Super::Tick(DeltaTime); 37 | 38 | if (Ticks > UpdateFoliageAfterNumFrames && !bIsBuilding) 39 | { 40 | Ticks = 0; 41 | int32 ComponentsUpdated = 0; 42 | 43 | for (TPair>& FoliageHISMPair : HISMFoliageMap) 44 | { 45 | for (UFoliageHISM* FoliageHISM : FoliageHISMPair.Value) 46 | { 47 | // if (!IsValid(FoliageHISM)) { continue; } 48 | if (FoliageHISM->bMarkedForClear) 49 | { 50 | #if !FOLIAGE_REDUCE_FLICKER_APPROACH_ENABLED 51 | FoliageHISM->ClearInstances(); 52 | FoliageHISM->bCleared = true; 53 | FoliageHISM->bMarkedForClear = false; 54 | ComponentsUpdated++; 55 | #endif 56 | } 57 | else if (FoliageHISM->bMarkedForAdd) 58 | { 59 | #if FOLIAGE_REDUCE_FLICKER_APPROACH_ENABLED 60 | FoliageHISM->ClearInstances(); 61 | #endif 62 | FoliageHISM->PreAllocateInstancesMemory(FoliageHISM->Transforms.Num()); 63 | FoliageHISM->AddInstances(FoliageHISM->Transforms, false); 64 | FoliageHISM->bMarkedForAdd = false; 65 | FoliageHISM->Transforms.Empty(); 66 | FoliageHISM->bCleared = false; 67 | ComponentsUpdated++; 68 | } 69 | if (ComponentsUpdated > MaxComponentsToUpdatePerFrame) 70 | { 71 | break; 72 | } 73 | } 74 | if (ComponentsUpdated > MaxComponentsToUpdatePerFrame) 75 | { 76 | break; 77 | } 78 | } 79 | 80 | if (IsValid(Georeference)) { 81 | if (AllISMsMarkedAsCleared() && !bInstancesClearedCalled && IsWaiting()) { 82 | OnInstancesCleared(); 83 | } 84 | } 85 | } 86 | 87 | Ticks++; 88 | } 89 | 90 | void AFoliageCaptureActor::BuildFoliageTransforms(UTextureRenderTarget2D* FoliageDistributionMap, 91 | UTextureRenderTarget2D* NormalAndDepthMap, FBox RTWorldBounds) 92 | { 93 | // Need to check whether the CesiumGeoreference actor and input RTs are valid. 94 | if (!IsValid(Georeference)) 95 | { 96 | UE_LOG(LogTemp, Warning, TEXT("Georeference is invalid! Not spawning in foliage")); 97 | return; 98 | } 99 | if (!IsValid(NormalAndDepthMap) || !IsValid(FoliageDistributionMap)) 100 | { 101 | UE_LOG(LogTemp, Warning, TEXT("Invaid inputs for FoliageCaptureActor!")); 102 | return; 103 | } 104 | if (FoliageTypes.Num() == 0) 105 | { 106 | UE_LOG(LogTemp, Warning, TEXT("No foliage types added!")); 107 | return; 108 | } 109 | 110 | bIsBuilding = true; 111 | 112 | // Find the geographic bounds of the RT 113 | const glm::dvec3 MinGeographic = Georeference->TransformUnrealToLongitudeLatitudeHeight( 114 | glm::dvec3( 115 | RTWorldBounds.Min.X, 116 | RTWorldBounds.Min.Y, 117 | RTWorldBounds.Min.Z 118 | )); 119 | 120 | const glm::dvec3 MaxGeographic = Georeference->TransformUnrealToLongitudeLatitudeHeight( 121 | glm::dvec3( 122 | RTWorldBounds.Max.X, 123 | RTWorldBounds.Max.Y, 124 | RTWorldBounds.Max.Z 125 | ) 126 | ); 127 | const glm::dvec4 GeographicExtents2D = glm::dvec4( 128 | MinGeographic.x, MinGeographic.y, 129 | MaxGeographic.x, MaxGeographic.y 130 | ); 131 | 132 | // Setup pixel extraction 133 | const int32 TotalPixels = FoliageDistributionMap->SizeX * FoliageDistributionMap->SizeY; 134 | 135 | TArray* ClassificationPixels = new TArray(); 136 | TArray* NormalPixels = new TArray(); 137 | 138 | FOnRenderTargetRead OnRenderTargetRead; 139 | 140 | OnRenderTargetRead.BindLambda( 141 | [this, FoliageDistributionMap, ClassificationPixels, NormalPixels, GeographicExtents2D, TotalPixels]( 142 | bool bSuccess) mutable 143 | { 144 | if (!bSuccess) 145 | { 146 | bIsBuilding = false; 147 | return; 148 | } 149 | 150 | const int32 Width = FoliageDistributionMap->SizeX; 151 | 152 | TMap NewInstanceCountMap; 153 | FFoliageTransforms FoliageTransforms; 154 | 155 | for (int Index = 0; Index < TotalPixels; ++Index) 156 | { 157 | // Get the 2D pixel coordinates. 158 | const double X = (Index % Width); 159 | const double Y = (Index / Width); 160 | 161 | // Extract classification, normals and depth from the pixel arrays. 162 | const FLinearColor Classification = (*ClassificationPixels)[Index]; 163 | const FLinearColor NormalDepth = (*NormalPixels)[Index]; 164 | // Convert the RGB channel in the NormalDepth array to a FVector 165 | FVector Normal = FVector(NormalDepth.R, NormalDepth.G, NormalDepth.B); 166 | // Project the Alpha channel in NormalDepth to elevation (in metres) 167 | const double Elevation = GetHeightFromDepth(NormalDepth.A); 168 | 169 | // Project pixel coords to geographic. 170 | const FVector GeographicCoords = PixelToGeographicLocation(X, Y, Elevation, FoliageDistributionMap, 171 | GeographicExtents2D); 172 | // Then project to UE world coordinates 173 | FVector Location = Georeference->TransformLongitudeLatitudeHeightToUnreal(GeographicCoords); 174 | 175 | // Compute east north up 176 | const FMatrix EastNorthUpEngine = Georeference->ComputeEastNorthUpToUnreal(Location); 177 | 178 | for (FFoliageClassificationType& FoliageType : FoliageTypes) 179 | { 180 | // If classification pixel colour matches the classification of FoliageType 181 | if (Classification == FoliageType.ColourClassification) 182 | { 183 | bool bHasDoneRaycast = false; 184 | 185 | if (FoliageType.bAlignToSurfaceWithRaycast) 186 | { 187 | CorrectFoliageTransform(Location, EastNorthUpEngine, Location, Normal, bHasDoneRaycast); 188 | } 189 | // Iterate through the mesh types inside FoliageType 190 | for (FFoliageGeometryType& FoliageGeometryType : FoliageType.FoliageTypes) 191 | { 192 | if (FMath::FRand() >= FoliageGeometryType.Density) 193 | { 194 | continue; 195 | } 196 | 197 | // Find rotation and scale 198 | const float Scale = FoliageGeometryType.Scale.Interpolate(FMath::FRand()); 199 | FRotator Rotation; 200 | 201 | if (FoliageGeometryType.bAlignToNormal) 202 | { 203 | Rotation = UKismetMathLibrary::MakeRotFromZ(Normal); 204 | } 205 | else 206 | { 207 | Rotation = EastNorthUpEngine.Rotator(); 208 | } 209 | 210 | // Apply a random angle to the rotation yaw if RandomYaw is true. 211 | if (FoliageGeometryType.bRandomYaw) 212 | { 213 | Rotation = UKismetMathLibrary::RotatorFromAxisAndAngle( 214 | Rotation.Quaternion().GetUpVector(), FMath::FRandRange( 215 | 0.0, 360.0 216 | )); 217 | } 218 | 219 | // Find HISM with minimum amount of transforms. 220 | 221 | UFoliageHISM* MinimumHISM = HISMFoliageMap[FoliageGeometryType][0]; 222 | for (UFoliageHISM* HISM : HISMFoliageMap[FoliageGeometryType]) 223 | { 224 | if (FoliageTransforms.HISMTransformMap.Contains(HISM) && FoliageTransforms.HISMTransformMap.Contains(MinimumHISM)) 225 | { 226 | if (FoliageTransforms.HISMTransformMap[HISM].Num() < FoliageTransforms.HISMTransformMap[MinimumHISM].Num()) 227 | { 228 | MinimumHISM = HISM; 229 | } 230 | } 231 | } 232 | if (!IsValid(MinimumHISM)) 233 | { 234 | UE_LOG(LogTemp, Error, TEXT("MinimumHISM is invalid!")); 235 | } 236 | 237 | Location += WorldOffset; 238 | 239 | // Add our transform, and make it relative to the actor. 240 | FTransform NewTransform = FTransform( 241 | Rotation, 242 | Location + (Rotation.Quaternion(). 243 | GetUpVector() * FoliageGeometryType.ZOffset. 244 | Interpolate(FMath::FRand())), FVector(Scale) 245 | ).GetRelativeTransform(GetTransform()); 246 | 247 | if (NewTransform.IsRotationNormalized()) 248 | { 249 | if (!FoliageTransforms.HISMTransformMap.Contains(MinimumHISM)) 250 | { 251 | FoliageTransforms.HISMTransformMap.Add(MinimumHISM, TArray{ NewTransform }); 252 | } 253 | else 254 | { 255 | FoliageTransforms.HISMTransformMap[MinimumHISM].Add(NewTransform); 256 | } 257 | } 258 | } 259 | } 260 | } 261 | } 262 | delete ClassificationPixels; 263 | delete NormalPixels; 264 | ClassificationPixels = nullptr; 265 | NormalPixels = nullptr; 266 | 267 | AsyncTask(ENamedThreads::GameThread, [FoliageTransforms, this]() 268 | { 269 | // Marked for add 270 | for (const TPair>& Pair : FoliageTransforms.HISMTransformMap) 271 | { 272 | Pair.Key->Transforms.Append(Pair.Value); 273 | Pair.Key->bMarkedForAdd = true; 274 | } 275 | bIsBuilding = false; 276 | }); 277 | }); 278 | // Extract the pixels from the render targets, calling OnRenderTargetRead when complete. 279 | ReadLinearColorPixelsAsync(OnRenderTargetRead, TArray{ 280 | FoliageDistributionMap->GameThread_GetRenderTargetResource(), 281 | NormalAndDepthMap->GameThread_GetRenderTargetResource() 282 | }, TArray*>{ 283 | ClassificationPixels, NormalPixels 284 | }); 285 | } 286 | 287 | void AFoliageCaptureActor::ClearFoliageInstances() 288 | { 289 | // Ensure the transforms array on the HISMs are cleared before building. 290 | for (TPair>& FoliageHISMPair : HISMFoliageMap) 291 | { 292 | for (UFoliageHISM* FoliageHISM : FoliageHISMPair.Value) 293 | { 294 | if (IsValid(FoliageHISM)) 295 | { 296 | FoliageHISM->Transforms.Empty(); 297 | FoliageHISM->bMarkedForClear = true; 298 | } 299 | } 300 | } 301 | } 302 | 303 | void AFoliageCaptureActor::ResetAndCreateHISMComponents() 304 | { 305 | for (FFoliageClassificationType& FoliageType : FoliageTypes) 306 | { 307 | for (FFoliageGeometryType& FoliageGeometryType : FoliageType.FoliageTypes) 308 | { 309 | if (FoliageGeometryType.Mesh == nullptr) { continue; } 310 | if (HISMFoliageMap.Contains(FoliageGeometryType)) 311 | { 312 | for (UFoliageHISM* HISM : HISMFoliageMap[FoliageGeometryType]) 313 | { 314 | if (IsValid(HISM)) 315 | { 316 | HISM->DestroyComponent(); 317 | } 318 | } 319 | HISMFoliageMap[FoliageGeometryType].Empty(); 320 | } 321 | HISMFoliageMap.Remove(FoliageGeometryType); 322 | 323 | for (int32 i = 0; i < FoliageType.PooledHISMsToCreatePerFoliageType; ++i) 324 | { 325 | UFoliageHISM* HISM = NewObject(this); 326 | HISM->SetupAttachment(GetRootComponent()); 327 | HISM->RegisterComponent(); 328 | 329 | HISM->SetStaticMesh(FoliageGeometryType.Mesh); 330 | HISM->SetCollisionEnabled(FoliageGeometryType.bCollidesWithWorld 331 | ? ECollisionEnabled::QueryAndPhysics 332 | : ECollisionEnabled::NoCollision); 333 | HISM->SetCullDistances(FoliageGeometryType.CullingDistances.Min, FoliageGeometryType.CullingDistances.Max); 334 | 335 | // This may cause a slight hitch when enabled. 336 | HISM->bAffectDistanceFieldLighting = FoliageGeometryType.bAffectsDistanceFieldLighting; 337 | if (!HISMFoliageMap.Contains(FoliageGeometryType)) 338 | { 339 | HISMFoliageMap.Add(FoliageGeometryType, TArray{HISM}); 340 | } 341 | else 342 | { 343 | HISMFoliageMap[FoliageGeometryType].Add(HISM); 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | void AFoliageCaptureActor::OnUpdate_Implementation(const FVector& NewLocation) 351 | { 352 | // Align the actor to face the planet surface. 353 | // SetActorLocation(NewLocation); 354 | NewActorLocation = NewLocation; 355 | bInstancesClearedCalled = false; 356 | 357 | 358 | const FRotator PlanetAlignedRotation = Georeference->ComputeEastNorthUpToUnreal(NewLocation).Rotator(); 359 | 360 | SetActorRotation( 361 | PlanetAlignedRotation 362 | ); 363 | 364 | // Get grid min and max coords. 365 | const int32 Size = GridSize.X > 0 ? GridSize.X : 1; 366 | const FVector Start = GetActorTransform().TransformPosition(FVector(-(CaptureWidth * Size) / 2, 0, 0)); 367 | const FVector End = GetActorTransform().TransformPosition(FVector((CaptureWidth * Size) / 2, 0, 0)); 368 | 369 | // Find the distance (in degrees) between the grid min and max. 370 | glm::dvec3 GeoStart = Georeference->TransformUnrealToLongitudeLatitudeHeight(VectorToDVector(Start)); 371 | glm::dvec3 GeoEnd = Georeference->TransformUnrealToLongitudeLatitudeHeight(VectorToDVector(End)); 372 | 373 | GeoStart.z = CaptureElevation; 374 | GeoEnd.z = CaptureElevation; 375 | 376 | CaptureWidthInDegrees = glm::distance(GeoStart, GeoEnd) / 2; 377 | 378 | bIsWaiting = true; 379 | 380 | #if FOLIAGE_REDUCE_FLICKER_APPROACH_ENABLED 381 | OnInstancesCleared(); 382 | #else 383 | ClearFoliageInstances(); 384 | #endif 385 | 386 | } 387 | 388 | void AFoliageCaptureActor::OnInstancesCleared_Implementation() 389 | { 390 | if (NewActorLocation.IsSet()) { 391 | FVector GeoPosition = Georeference->TransformUnrealToLongitudeLatitudeHeight( 392 | *NewActorLocation 393 | ); 394 | GeoPosition.Z = CaptureElevation; 395 | FVector EnginePosition = Georeference->TransformLongitudeLatitudeHeightToUnreal(GeoPosition); 396 | ActorOffset = EnginePosition - GetActorLocation(); 397 | 398 | SetActorLocation( 399 | EnginePosition 400 | ); 401 | 402 | #if FOLIAGE_REDUCE_FLICKER_APPROACH_ENABLED 403 | OffsetAllInstances(ActorOffset); 404 | #endif 405 | 406 | NewActorLocation.Reset(); 407 | bIsWaiting = false; 408 | } 409 | } 410 | 411 | bool AFoliageCaptureActor::IsBuilding() const 412 | { 413 | return bIsBuilding; 414 | } 415 | 416 | bool AFoliageCaptureActor::IsWaiting() const 417 | { 418 | return bIsWaiting; 419 | } 420 | 421 | void AFoliageCaptureActor::CorrectFoliageTransform(const FVector& InEngineCoordinates, const FMatrix& InEastNorthUp, 422 | FVector& OutCorrectedPosition, FVector& OutSurfaceNormals, bool& bSuccess) const 423 | { 424 | UWorld* World = GetWorld(); 425 | 426 | if (IsValid(World)) 427 | { 428 | const FVector Up = InEastNorthUp.ToQuat().GetUpVector(); 429 | FHitResult HitResult; 430 | 431 | World->LineTraceSingleByChannel(HitResult, InEngineCoordinates + (Up * 6000), 432 | InEngineCoordinates - (Up * 6000), ECollisionChannel::ECC_Visibility); 433 | 434 | if (HitResult.bBlockingHit) 435 | { 436 | bSuccess = true; 437 | OutCorrectedPosition = HitResult.ImpactPoint; 438 | OutSurfaceNormals = HitResult.ImpactNormal; 439 | } 440 | } 441 | } 442 | 443 | double AFoliageCaptureActor::GetHeightFromDepth(const double& Value) const 444 | { 445 | return CaptureElevation - (1 - Value) / 0.00001 / 100; 446 | 447 | } 448 | 449 | FVector AFoliageCaptureActor::PixelToGeographicLocation(const double& X, const double& Y, const double& Altitude, 450 | UTextureRenderTarget2D* RT, 451 | const glm::dvec4& GeographicExtents) const 452 | { 453 | // Normalize the ranges of the coords 454 | const double AX = X / static_cast(RT->SizeX); 455 | const double AY = Y / static_cast(RT->SizeY); 456 | 457 | const double Long = FMath::Lerp( 458 | GeographicExtents.x, 459 | GeographicExtents.z, 460 | 1 - AY); 461 | const double Lat = FMath::Lerp( 462 | GeographicExtents.y, 463 | GeographicExtents.w, 464 | AX); 465 | 466 | return FVector(Long, Lat, Altitude); 467 | } 468 | 469 | FIntPoint AFoliageCaptureActor::GeographicToPixelLocation(const double& Longitude, const double& Latitude, 470 | UTextureRenderTarget2D* RT, 471 | const glm::dvec4& GeographicExtents) const 472 | { 473 | // Normalize long and lat 474 | const double LongitudeRange = GeographicExtents.z - GeographicExtents.x; 475 | const double LatitudeRange = GeographicExtents.w - GeographicExtents.y; 476 | const double ALongitude = (Longitude - GeographicExtents.x) / LongitudeRange; 477 | const double ALatitude = (Latitude - GeographicExtents.y) / LatitudeRange; 478 | 479 | const double X = FMath::Lerp(0, RT->SizeX, ALatitude); 480 | const double Y = FMath::Lerp(RT->SizeY, 0, ALongitude); 481 | 482 | return FIntPoint(X, Y); 483 | } 484 | 485 | void AFoliageCaptureActor::ReadLinearColorPixelsAsync( 486 | FOnRenderTargetRead OnRenderTargetRead, 487 | TArray RTs, 488 | TArray*> OutImageData, 489 | FReadSurfaceDataFlags InFlags, 490 | FIntRect InRect, 491 | ENamedThreads::Type ExitThread) 492 | { 493 | if (InRect == FIntRect(0, 0, 0, 0)) 494 | { 495 | InRect = FIntRect(0, 0, RTs[0]->GetSizeXY().X, RTs[0]->GetSizeXY().Y); 496 | } 497 | 498 | 499 | struct FReadSurfaceContext 500 | { 501 | TArray SrcRenderTargets; 502 | TArray*> OutData; 503 | FIntRect Rect; 504 | FReadSurfaceDataFlags Flags; 505 | }; 506 | 507 | for (auto DT : OutImageData) { DT->Reset(); } 508 | FReadSurfaceContext Context = 509 | { 510 | RTs, 511 | OutImageData, 512 | InRect, 513 | InFlags 514 | }; 515 | 516 | if (!Context.OutData[0]) 517 | { 518 | UE_LOG(LogTemp, Error, TEXT("Buffer invalid!")); 519 | return; 520 | } 521 | 522 | FRenderCommandFence Fence; 523 | 524 | ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)( 525 | [Context, OnRenderTargetRead, ExitThread](FRHICommandListImmediate& RHICmdList) 526 | { 527 | const FIntRect Rect = Context.Rect; 528 | const FReadSurfaceDataFlags Flags = Context.Flags; 529 | int i = 0; 530 | for (FRenderTarget* RT : Context.SrcRenderTargets) 531 | { 532 | 533 | const FTexture2DRHIRef& RefRenderTarget = RT-> 534 | GetRenderTargetTexture(); 535 | 536 | TArray* Buffer = Context.OutData[i]; 537 | RHICmdList.ReadSurfaceData( 538 | RefRenderTarget, 539 | Rect, 540 | *Buffer, 541 | Flags 542 | ); 543 | i++; 544 | } 545 | // instead of blocking the game thread, execute the delegate when finished. 546 | AsyncTask( 547 | ExitThread, 548 | [OnRenderTargetRead, Context]() 549 | { 550 | OnRenderTargetRead.Execute((*Context.OutData[0]).Num() > 0); 551 | }); 552 | }); 553 | 554 | Fence.BeginFence(true); 555 | 556 | } 557 | 558 | glm::dvec3 AFoliageCaptureActor::VectorToDVector(const FVector& InVector) 559 | { 560 | return glm::dvec3(InVector.X, InVector.Y, InVector.Z); 561 | } 562 | --------------------------------------------------------------------------------