可用
This commit is contained in:
		
							
								
								
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					Binaries
 | 
				
			||||||
 | 
					.vs
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					.history
 | 
				
			||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					Intermediate
 | 
				
			||||||
 | 
					DerivedDataCache
 | 
				
			||||||
 | 
					Build
 | 
				
			||||||
 | 
					Saved
 | 
				
			||||||
 | 
					/Content/JavaScript
 | 
				
			||||||
 | 
					/Content/Typing
 | 
				
			||||||
 | 
					/Content/TypeScript
 | 
				
			||||||
 | 
					/Content/Developers
 | 
				
			||||||
 | 
					/Content/Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/Content/Data
 | 
				
			||||||
 | 
					/Content/project_ok
 | 
				
			||||||
 | 
					/Content/Blueprints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ArtistRenderer.sln
 | 
				
			||||||
 | 
					*.pyc
 | 
				
			||||||
 | 
					package-lock.json
 | 
				
			||||||
 | 
					/dpaks
 | 
				
			||||||
							
								
								
									
										11
									
								
								B.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								B.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					@echo off
 | 
				
			||||||
 | 
					rem 编译Shipping版本,打包pak
 | 
				
			||||||
 | 
					set BuildConfig="Shipping"
 | 
				
			||||||
 | 
					set UprojectPath="%~dp0%wjj%.uproject"
 | 
				
			||||||
 | 
					set DebugPath="%~dp0Build/%BuildConfig%"
 | 
				
			||||||
 | 
					D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
 | 
				
			||||||
 | 
					 -nocompileeditor^
 | 
				
			||||||
 | 
					 -noP4 -platform=Win64^
 | 
				
			||||||
 | 
					 -clientconfig=%BuildConfig%^
 | 
				
			||||||
 | 
					 -cook -allmaps -build -stage^
 | 
				
			||||||
 | 
					 -pak -stage -archive -archivedirectory="%~dp0/Build/%BuildConfig%"
 | 
				
			||||||
							
								
								
									
										1
									
								
								Config/DefaultEditor.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Config/DefaultEditor.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								Config/DefaultEngine.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Config/DefaultEngine.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/EngineSettings.GameMapsSettings]
 | 
				
			||||||
 | 
					GameDefaultMap=/Game/NewMap.NewMap
 | 
				
			||||||
 | 
					EditorStartupMap=/Game/NewMap.NewMap
 | 
				
			||||||
 | 
					GameInstanceClass=/Script/SMC_Build.SMCGameInstance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/HardwareTargeting.HardwareTargetingSettings]
 | 
				
			||||||
 | 
					TargetedHardwareClass=Desktop
 | 
				
			||||||
 | 
					AppliedTargetedHardwareClass=Desktop
 | 
				
			||||||
 | 
					DefaultGraphicsPerformance=Maximum
 | 
				
			||||||
 | 
					AppliedDefaultGraphicsPerformance=Maximum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/Engine.RendererSettings]
 | 
				
			||||||
 | 
					r.GenerateMeshDistanceFields=True
 | 
				
			||||||
 | 
					r.DynamicGlobalIlluminationMethod=1
 | 
				
			||||||
 | 
					r.ReflectionMethod=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					r.SkinCache.CompileShaders=True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					r.RayTracing=True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					r.Shadow.Virtual.Enable=1
 | 
				
			||||||
 | 
					r.Lumen.HardwareRayTracing=True
 | 
				
			||||||
 | 
					r.MinScreenRadiusForLights=500.000000
 | 
				
			||||||
 | 
					r.RayTracing.Shadows=True
 | 
				
			||||||
 | 
					r.AllowStaticLighting=False
 | 
				
			||||||
 | 
					r.DefaultFeature.LightUnits=2
 | 
				
			||||||
 | 
					r.DefaultFeature.AutoExposure=False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/WindowsTargetPlatform.WindowsTargetSettings]
 | 
				
			||||||
 | 
					DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
 | 
				
			||||||
 | 
					bEnableWorldPartition=False
 | 
				
			||||||
 | 
					bEnableConversionPrompt=True
 | 
				
			||||||
 | 
					CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/Engine.Engine]
 | 
				
			||||||
 | 
					+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/SMC_Build")
 | 
				
			||||||
 | 
					+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/SMC_Build")
 | 
				
			||||||
 | 
					+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="SMC_BuildGameModeBase")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								Config/DefaultGame.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Config/DefaultGame.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/EngineSettings.GeneralProjectSettings]
 | 
				
			||||||
 | 
					ProjectID=DFD3A2844A4CA1EAF134B5968BDC572F
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[/Script/UnrealEd.ProjectPackagingSettings]
 | 
				
			||||||
 | 
					IncludeDebugFiles=True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								Content/NewMap.umap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/NewMap.umap
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/NewMap1.umap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/NewMap1.umap
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/NewMap_BuiltData.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/NewMap_BuiltData.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/UV_Grid_Sm.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/UV_Grid_Sm.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/UV_Grid_Sm_Mat.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/UV_Grid_Sm_Mat.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/aaa.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/aaa.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/baise.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/baise.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/file__1_.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/file__1_.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/webcad.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/webcad.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										17
									
								
								D.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								D.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					@echo off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set "lj=%~p0"
 | 
				
			||||||
 | 
					set "lj=%lj:\= %"
 | 
				
			||||||
 | 
					for %%a in (%lj%) do set wjj=%%a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rem 编译DebugGame的版本,跳过烘焙,打包pak
 | 
				
			||||||
 | 
					set BuildConfig=DebugGame
 | 
				
			||||||
 | 
					set UprojectPath="%~dp0%wjj%.uproject"
 | 
				
			||||||
 | 
					set DebugPath="%~dp0Build\%BuildConfig%"
 | 
				
			||||||
 | 
					D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
 | 
				
			||||||
 | 
					 -nocompileeditor^
 | 
				
			||||||
 | 
					 -noP4 -platform=Win64^
 | 
				
			||||||
 | 
					 -clientconfig=%BuildConfig%^
 | 
				
			||||||
 | 
					 -build -utf8output -compile -ForceDebugInfo^
 | 
				
			||||||
 | 
					 -SkipCook^
 | 
				
			||||||
 | 
					 -pak -stage -archive -archivedirectory="%DebugPath%"
 | 
				
			||||||
							
								
								
									
										16
									
								
								DC.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								DC.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					@echo off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set "lj=%~p0"
 | 
				
			||||||
 | 
					set "lj=%lj:\= %"
 | 
				
			||||||
 | 
					for %%a in (%lj%) do set wjj=%%a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rem 编译DebugGame的版本,跳过烘焙,打包pak
 | 
				
			||||||
 | 
					set BuildConfig=DebugGame
 | 
				
			||||||
 | 
					set UprojectPath="%~dp0%wjj%.uproject"
 | 
				
			||||||
 | 
					set DebugPath="%~dp0Build\%BuildConfig%"
 | 
				
			||||||
 | 
					D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
 | 
				
			||||||
 | 
					 -nocompileeditor^
 | 
				
			||||||
 | 
					 -noP4 -platform=Win64^
 | 
				
			||||||
 | 
					 -clientconfig=%BuildConfig%^
 | 
				
			||||||
 | 
					 -cook -allmaps -build -stage^
 | 
				
			||||||
 | 
					 -pak -stage -archive -archivedirectory=%DebugPath%
 | 
				
			||||||
							
								
								
									
										24
									
								
								Plugins/MeshUtilities2/MeshUtilities2.uplugin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Plugins/MeshUtilities2/MeshUtilities2.uplugin
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"FileVersion": 3,
 | 
				
			||||||
 | 
						"Version": 1,
 | 
				
			||||||
 | 
						"VersionName": "1.0",
 | 
				
			||||||
 | 
						"FriendlyName": "MeshUtilities2",
 | 
				
			||||||
 | 
						"Description": "",
 | 
				
			||||||
 | 
						"Category": "Other",
 | 
				
			||||||
 | 
						"CreatedBy": "ChenX",
 | 
				
			||||||
 | 
						"CreatedByURL": "",
 | 
				
			||||||
 | 
						"DocsURL": "",
 | 
				
			||||||
 | 
						"MarketplaceURL": "",
 | 
				
			||||||
 | 
						"SupportURL": "",
 | 
				
			||||||
 | 
						"CanContainContent": true,
 | 
				
			||||||
 | 
						"IsBetaVersion": false,
 | 
				
			||||||
 | 
						"IsExperimentalVersion": false,
 | 
				
			||||||
 | 
						"Installed": false,
 | 
				
			||||||
 | 
						"Modules": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "MeshUtilities2",
 | 
				
			||||||
 | 
								"Type": "Runtime",
 | 
				
			||||||
 | 
								"LoadingPhase": "Default"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								Plugins/MeshUtilities2/Resources/Icon128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Plugins/MeshUtilities2/Resources/Icon128.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 12 KiB  | 
@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using UnrealBuildTool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class MeshUtilities2 : ModuleRules
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public MeshUtilities2(ReadOnlyTargetRules Target) : base(Target)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							PublicDependencyModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// "MaterialUtilities",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrivateDependencyModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"Core",
 | 
				
			||||||
 | 
									"CoreUObject",
 | 
				
			||||||
 | 
									"Engine",
 | 
				
			||||||
 | 
									"RawMesh",
 | 
				
			||||||
 | 
									"RenderCore", // For FPackedNormal
 | 
				
			||||||
 | 
									"SlateCore",
 | 
				
			||||||
 | 
									"Slate",
 | 
				
			||||||
 | 
									// "MaterialUtilities",
 | 
				
			||||||
 | 
									// "MeshBoneReduction",
 | 
				
			||||||
 | 
									// "EditorFramework",
 | 
				
			||||||
 | 
									// "UnrealEd",
 | 
				
			||||||
 | 
									"RHI",
 | 
				
			||||||
 | 
									//"HierarchicalLODUtilities",
 | 
				
			||||||
 | 
									"Landscape",
 | 
				
			||||||
 | 
									// "LevelEditor",
 | 
				
			||||||
 | 
									// "PropertyEditor",
 | 
				
			||||||
 | 
									// "EditorStyle",
 | 
				
			||||||
 | 
									// "GraphColor",
 | 
				
			||||||
 | 
									// "MeshBuilderCommon",
 | 
				
			||||||
 | 
									"MeshUtilitiesCommon",
 | 
				
			||||||
 | 
									"MeshDescription",
 | 
				
			||||||
 | 
									"StaticMeshDescription",
 | 
				
			||||||
 | 
									// "ToolMenus",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PublicIncludePathModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"MeshMergeUtilities"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrivateIncludePathModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// "AnimationBlueprintEditor",
 | 
				
			||||||
 | 
									// "AnimationEditor",
 | 
				
			||||||
 | 
									// "MeshMergeUtilities",
 | 
				
			||||||
 | 
									// "MaterialBaking",
 | 
				
			||||||
 | 
									// "Persona",
 | 
				
			||||||
 | 
									// "SkeletalMeshEditor",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DynamicallyLoadedModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// "AnimationBlueprintEditor",
 | 
				
			||||||
 | 
									// "AnimationEditor",
 | 
				
			||||||
 | 
									// "MeshMergeUtilities",
 | 
				
			||||||
 | 
									// "MaterialBaking",
 | 
				
			||||||
 | 
									// "SkeletalMeshEditor",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTriStrip");
 | 
				
			||||||
 | 
							AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer");
 | 
				
			||||||
 | 
							// AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction");
 | 
				
			||||||
 | 
							AddEngineThirdPartyPrivateStaticDependencies(Target, "MikkTSpace");
 | 
				
			||||||
 | 
							AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (Target.Platform == UnrealTargetPlatform.Win64)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								AddEngineThirdPartyPrivateStaticDependencies(Target, "DX9");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							AddEngineThirdPartyPrivateStaticDependencies(Target, "Embree3");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,598 @@
 | 
				
			|||||||
 | 
					#include "DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					// #include "DistanceFieldAtlas.h"
 | 
				
			||||||
 | 
					// #include "HAL/RunnableThread.h"
 | 
				
			||||||
 | 
					// #include "HAL/Runnable.h"
 | 
				
			||||||
 | 
					// #include "Misc/App.h"
 | 
				
			||||||
 | 
					// #include "Serialization/MemoryReader.h"
 | 
				
			||||||
 | 
					// #include "Serialization/MemoryWriter.h"
 | 
				
			||||||
 | 
					#include "Modules/ModuleManager.h"
 | 
				
			||||||
 | 
					#include "StaticMeshResources.h"
 | 
				
			||||||
 | 
					#include "ProfilingDebugging/CookStats.h"
 | 
				
			||||||
 | 
					#include "Templates/UniquePtr.h"
 | 
				
			||||||
 | 
					#include "Engine/StaticMesh.h"
 | 
				
			||||||
 | 
					#include "Misc/AutomationTest.h"
 | 
				
			||||||
 | 
					// #include "Async/ParallelFor.h"
 | 
				
			||||||
 | 
					// #include "DistanceFieldDownsampling.h"
 | 
				
			||||||
 | 
					// #include "GlobalShader.h"
 | 
				
			||||||
 | 
					#include "RenderGraph.h"
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation.h"
 | 
				
			||||||
 | 
					#include "Misc/QueuedThreadPoolWrapper.h"
 | 
				
			||||||
 | 
					// #include "Async/Async.h"
 | 
				
			||||||
 | 
					#include "ObjectCacheContext.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #if WITH_EDITOR
 | 
				
			||||||
 | 
					// #include "DerivedDataCacheInterface.h"
 | 
				
			||||||
 | 
					#include "Engine/Public/DistanceFieldAtlas.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #include "AssetCompilingManager.h"
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation2.h"
 | 
				
			||||||
 | 
					#include "StaticMeshCompiler.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					// #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CSV_DEFINE_CATEGORY(DistanceField2, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if ENABLE_COOK_STATS
 | 
				
			||||||
 | 
					namespace DistanceFieldCookStats
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FCookStats::FDDCResourceUsageStats UsageStats;
 | 
				
			||||||
 | 
						static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UsageStats.LogStats(AddStat, TEXT("DistanceField2.Usage"), TEXT(""));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FDistanceFieldAsyncQueue2* GDistanceFieldAsyncQueue2 = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32 GUseAsyncDistanceFieldBuildQueue2 = 1;
 | 
				
			||||||
 | 
					static FAutoConsoleVariableRef CVarAOAsyncBuildQueue(
 | 
				
			||||||
 | 
						TEXT("r.AOAsyncBuildQueue2"),
 | 
				
			||||||
 | 
						GUseAsyncDistanceFieldBuildQueue2,
 | 
				
			||||||
 | 
						TEXT("是否从网格异步构建距离场体数据。"),
 | 
				
			||||||
 | 
						ECVF_Default | ECVF_ReadOnly
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//构建距离场
 | 
				
			||||||
 | 
					void BuildMeshDistanceField(UStaticMesh* StaticMesh)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						//ref:StaticMesh.cpp 2782
 | 
				
			||||||
 | 
						auto RenderData = StaticMesh->GetRenderData();
 | 
				
			||||||
 | 
						if (RenderData->LODResources.IsValidIndex(0))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto& LODResource = RenderData->LODResources[0];
 | 
				
			||||||
 | 
							if (!LODResource.DistanceFieldData)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								LODResource.DistanceFieldData = new FDistanceFieldVolumeData();
 | 
				
			||||||
 | 
								LODResource.DistanceFieldData->AssetName = StaticMesh->GetFName();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We don't actually build the resource until later, so only track the cycles used here.
 | 
				
			||||||
 | 
							// COOK_STAT(Timer.TrackCyclesOnly());
 | 
				
			||||||
 | 
							FAsyncDistanceFieldTask2* NewTask = new FAsyncDistanceFieldTask2;
 | 
				
			||||||
 | 
							NewTask->DDCKey = "";
 | 
				
			||||||
 | 
							// check(Mesh && GenerateSource);
 | 
				
			||||||
 | 
							// NewTask->TargetPlatform = RunningPlatform;
 | 
				
			||||||
 | 
							NewTask->StaticMesh = StaticMesh;
 | 
				
			||||||
 | 
							NewTask->GenerateSource = StaticMesh; //GenerateSource;
 | 
				
			||||||
 | 
							NewTask->DistanceFieldResolutionScale = 2; //DistanceFieldResolutionScale;
 | 
				
			||||||
 | 
							NewTask->bGenerateDistanceFieldAsIfTwoSided = false; // bGenerateDistanceFieldAsIfTwoSided;
 | 
				
			||||||
 | 
							NewTask->GeneratedVolumeData = new FDistanceFieldVolumeData(); //;给一个新的用于存放
 | 
				
			||||||
 | 
							NewTask->GeneratedVolumeData->AssetName = StaticMesh->GetFName();
 | 
				
			||||||
 | 
							NewTask->GeneratedVolumeData->bAsyncBuilding = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); MaterialIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FSignedDistanceFieldBuildMaterialData2 MaterialData;
 | 
				
			||||||
 | 
								// Default material blend mode
 | 
				
			||||||
 | 
								MaterialData.BlendMode = BLEND_Opaque;
 | 
				
			||||||
 | 
								MaterialData.bTwoSided = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									MaterialData.BlendMode = StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface->GetBlendMode();
 | 
				
			||||||
 | 
									MaterialData.bTwoSided = StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface->IsTwoSided();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								NewTask->MaterialBlendModes.Add(MaterialData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// // Nanite overrides source static mesh with a coarse representation. Need to load original data before we build the mesh SDF.
 | 
				
			||||||
 | 
							// if (StaticMesh->NaniteSettings.bEnabled)
 | 
				
			||||||
 | 
							// {
 | 
				
			||||||
 | 
							// 	IMeshBuilderModule& MeshBuilderModule = IMeshBuilderModule::GetForPlatform(TargetPlatform);
 | 
				
			||||||
 | 
							// 	if (!MeshBuilderModule.BuildMeshVertexPositions(Mesh, NewTask->SourceMeshData.TriangleIndices, NewTask->SourceMeshData.VertexPositions))
 | 
				
			||||||
 | 
							// 	{
 | 
				
			||||||
 | 
							// 		UE_LOG(LogStaticMesh, Error, TEXT("Failed to build static mesh. See previous line(s) for details."));
 | 
				
			||||||
 | 
							// 	}
 | 
				
			||||||
 | 
							// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							GDistanceFieldAsyncQueue2->AddTask(NewTask);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BuildMeshCardRepresentation(UStaticMesh* StaticMeshAsset, FStaticMeshRenderData& RenderData, FSourceMeshDataForDerivedDataTask* OptionalSourceMeshData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (RenderData.LODResources.IsValidIndex(0))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (!RenderData.LODResources[0].CardRepresentationData)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								RenderData.LODResources[0].CardRepresentationData = new FCardRepresentationData();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// const FMeshBuildSettings& BuildSettings = StaticMeshAsset->GetSourceModel(0).BuildSettings;
 | 
				
			||||||
 | 
							// UStaticMesh* MeshToGenerateFrom = StaticMeshAsset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We don't actually build the resource until later, so only track the cycles used here.
 | 
				
			||||||
 | 
							// COOK_STAT(Timer.TrackCyclesOnly());
 | 
				
			||||||
 | 
							FAsyncCardRepresentationTask2* NewTask = new FAsyncCardRepresentationTask2;
 | 
				
			||||||
 | 
							// NewTask->DDCKey = InDDCKey;
 | 
				
			||||||
 | 
							check(StaticMeshAsset);
 | 
				
			||||||
 | 
							NewTask->StaticMesh = StaticMeshAsset;
 | 
				
			||||||
 | 
							NewTask->GenerateSource = StaticMeshAsset;
 | 
				
			||||||
 | 
							NewTask->GeneratedCardRepresentation = new FCardRepresentationData();
 | 
				
			||||||
 | 
							NewTask->bGenerateDistanceFieldAsIfTwoSided = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (int32 MaterialIndex = 0; MaterialIndex < StaticMeshAsset->GetStaticMaterials().Num(); MaterialIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FSignedDistanceFieldBuildMaterialData2 MaterialData;
 | 
				
			||||||
 | 
								// Default material blend mode
 | 
				
			||||||
 | 
								MaterialData.BlendMode = BLEND_Opaque;
 | 
				
			||||||
 | 
								MaterialData.bTwoSided = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									MaterialData.BlendMode = StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface->GetBlendMode();
 | 
				
			||||||
 | 
									MaterialData.bTwoSided = StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface->IsTwoSided();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								NewTask->MaterialBlendModes.Add(MaterialData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Nanite overrides source static mesh with a coarse representation. Need to load original data before we build the mesh SDF.
 | 
				
			||||||
 | 
							if (OptionalSourceMeshData)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								NewTask->SourceMeshData = *OptionalSourceMeshData;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// else if (StaticMeshAsset->NaniteSettings.bEnabled)
 | 
				
			||||||
 | 
							// {
 | 
				
			||||||
 | 
							// 	IMeshBuilderModule& MeshBuilderModule = IMeshBuilderModule::GetForPlatform(TargetPlatform);
 | 
				
			||||||
 | 
							// 	if (!MeshBuilderModule.BuildMeshVertexPositions(Mesh, NewTask->SourceMeshData.TriangleIndices, NewTask->SourceMeshData.VertexPositions))
 | 
				
			||||||
 | 
							// 	{
 | 
				
			||||||
 | 
							// 		UE_LOG(LogStaticMesh, Error, TEXT("Failed to build static mesh. See previous line(s) for details."));
 | 
				
			||||||
 | 
							// 	}
 | 
				
			||||||
 | 
							// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							GCardRepresentationAsyncQueue2->AddTask(NewTask);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FAsyncDistanceFieldTask2::FAsyncDistanceFieldTask2()
 | 
				
			||||||
 | 
						: StaticMesh(nullptr)
 | 
				
			||||||
 | 
						  , GenerateSource(nullptr)
 | 
				
			||||||
 | 
						  , DistanceFieldResolutionScale(0.0f)
 | 
				
			||||||
 | 
						  , bGenerateDistanceFieldAsIfTwoSided(false)
 | 
				
			||||||
 | 
						  , GeneratedVolumeData(nullptr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FDistanceFieldAsyncQueue2::FDistanceFieldAsyncQueue2()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshUtilities = NULL;
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						// 	const int32 MaxConcurrency = -1;
 | 
				
			||||||
 | 
						// 	// In Editor, we allow faster compilation by letting the asset compiler's scheduler organize work.
 | 
				
			||||||
 | 
						// 	ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(FAssetCompilingManager::Get().GetThreadPool(), MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
 | 
				
			||||||
 | 
						// #else
 | 
				
			||||||
 | 
						const int32 MaxConcurrency = -1;
 | 
				
			||||||
 | 
						ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(GThreadPool, MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FDistanceFieldAsyncQueue2::~FDistanceFieldAsyncQueue2()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FAsyncDistanceFieldTaskWorker2::DoWork()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Put on background thread to avoid interfering with game-thread bound tasks
 | 
				
			||||||
 | 
						FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
 | 
				
			||||||
 | 
						GDistanceFieldAsyncQueue2->Build(&Task, TaskGraphWrapper);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::CancelBackgroundTask(TArray<FAsyncDistanceFieldTask2*> Tasks)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Do all the cancellation first to make sure none of these tasks
 | 
				
			||||||
 | 
						// get scheduled as we're waiting for completion.
 | 
				
			||||||
 | 
						for (FAsyncDistanceFieldTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->Cancel();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (FAsyncDistanceFieldTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->EnsureCompletion();
 | 
				
			||||||
 | 
								Task->AsyncTask.Reset();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::StartBackgroundTask(FAsyncDistanceFieldTask2* Task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						check(Task->AsyncTask == nullptr);
 | 
				
			||||||
 | 
						Task->AsyncTask = MakeUnique<FAsyncTask<FAsyncDistanceFieldTaskWorker2>>(*Task);
 | 
				
			||||||
 | 
						Task->AsyncTask->StartBackgroundTask(ThreadPool.Get(), EQueuedWorkPriority::Lowest);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::ProcessPendingTasks()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
						TArray<FAsyncDistanceFieldTask2*> Tasks = MoveTemp(PendingTasks);
 | 
				
			||||||
 | 
						for (FAsyncDistanceFieldTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->GenerateSource && Task->GenerateSource->IsCompiling())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								PendingTasks.Add(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								StartBackgroundTask(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::AddTask(FAsyncDistanceFieldTask2* Task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						if (!MeshUtilities)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshUtilities = &FModuleManager::Get().LoadModuleChecked<IMeshUtilities2>(TEXT("MeshUtilities2"));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Array protection when called from multiple threads
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							ReferencedTasks.Add(Task);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The Source Mesh's RenderData is not yet ready, postpone the build
 | 
				
			||||||
 | 
						if (Task->GenerateSource->IsCompiling())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Array protection when called from multiple threads
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							PendingTasks.Add(Task);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// If we're already in worker threads, there is no need to launch an async task.
 | 
				
			||||||
 | 
							if (GUseAsyncDistanceFieldBuildQueue2 || !IsInGameThread())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								StartBackgroundTask(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// To avoid deadlocks, we must queue the inner build tasks on another thread pool, so use the task graph.
 | 
				
			||||||
 | 
								// Put on background thread to avoid interfering with game-thread bound tasks
 | 
				
			||||||
 | 
								FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
 | 
				
			||||||
 | 
								Build(Task, TaskGraphWrapper);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// #else
 | 
				
			||||||
 | 
						// 	UE_LOG(LogStaticMesh,Fatal,TEXT("Tried to build a distance field without editor support (this should have been done during cooking)"));
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::CancelBuild(UStaticMesh* StaticMesh)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::CancelBuild)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FAsyncDistanceFieldTask2*> TasksToCancel;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							TArray<FAsyncDistanceFieldTask2*> Tasks = MoveTemp(PendingTasks);
 | 
				
			||||||
 | 
							PendingTasks.Reserve(Tasks.Num());
 | 
				
			||||||
 | 
							for (FAsyncDistanceFieldTask2* Task : Tasks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									PendingTasks.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Tasks = MoveTemp(ReferencedTasks);
 | 
				
			||||||
 | 
							ReferencedTasks.Reserve(Tasks.Num());
 | 
				
			||||||
 | 
							for (FAsyncDistanceFieldTask2* Task : Tasks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									ReferencedTasks.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									TasksToCancel.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CancelBackgroundTask(TasksToCancel);
 | 
				
			||||||
 | 
						for (FAsyncDistanceFieldTask2* Task : TasksToCancel)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->GeneratedVolumeData != nullptr)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
 | 
				
			||||||
 | 
								BeginCleanup(Task->GeneratedVolumeData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::CancelAllOutstandingBuilds()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::CancelAllOutstandingBuilds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FAsyncDistanceFieldTask2*> OutstandingTasks;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							PendingTasks.Empty();
 | 
				
			||||||
 | 
							OutstandingTasks = MoveTemp(ReferencedTasks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CancelBackgroundTask(OutstandingTasks);
 | 
				
			||||||
 | 
						for (FAsyncDistanceFieldTask2* Task : OutstandingTasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::RescheduleBackgroundTask(FAsyncDistanceFieldTask2* InTask, EQueuedWorkPriority InPriority)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (InTask->AsyncTask)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (InTask->AsyncTask->GetPriority() != InPriority)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								InTask->AsyncTask->Reschedule(GThreadPool, InPriority);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::BlockUntilBuildComplete)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We will track the wait time here, but only the cycles used.
 | 
				
			||||||
 | 
						// This function is called whether or not an async task is pending,
 | 
				
			||||||
 | 
						// so we have to look elsewhere to properly count how many resources have actually finished building.
 | 
				
			||||||
 | 
						COOK_STAT(auto Timer = DistanceFieldCookStats::UsageStats.TimeAsyncWait());
 | 
				
			||||||
 | 
						COOK_STAT(Timer.TrackCyclesOnly());
 | 
				
			||||||
 | 
						bool bReferenced = false;
 | 
				
			||||||
 | 
						bool bHadToBlock = false;
 | 
				
			||||||
 | 
						double StartTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TSet<UStaticMesh*> RequiredFinishCompilation;
 | 
				
			||||||
 | 
						do
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ProcessAsyncTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bReferenced = false;
 | 
				
			||||||
 | 
							RequiredFinishCompilation.Reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Reschedule the tasks we're waiting on as highest prio
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (ReferencedTasks[TaskIndex]->StaticMesh == StaticMesh ||
 | 
				
			||||||
 | 
										ReferencedTasks[TaskIndex]->GenerateSource == StaticMesh)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										bReferenced = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// If the task we are waiting on depends on other static meshes
 | 
				
			||||||
 | 
										// we need to force finish them too.
 | 
				
			||||||
 | 
										// #if WITH_EDITOR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (ReferencedTasks[TaskIndex]->GenerateSource != nullptr &&
 | 
				
			||||||
 | 
											ReferencedTasks[TaskIndex]->GenerateSource->IsCompiling())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											RequiredFinishCompilation.Add(ReferencedTasks[TaskIndex]->GenerateSource);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (ReferencedTasks[TaskIndex]->StaticMesh != nullptr &&
 | 
				
			||||||
 | 
											ReferencedTasks[TaskIndex]->StaticMesh->IsCompiling())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											RequiredFinishCompilation.Add(ReferencedTasks[TaskIndex]->StaticMesh);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
							// Call the finish compilation outside of the critical section since those compilations
 | 
				
			||||||
 | 
							// might need to register new distance field tasks which also uses the critical section.
 | 
				
			||||||
 | 
							if (RequiredFinishCompilation.Num())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FStaticMeshCompilingManager::Get().FinishCompilation(RequiredFinishCompilation.Array());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bReferenced)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (!bHadToBlock)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bHadToBlock = true;
 | 
				
			||||||
 | 
								FPlatformProcess::Sleep(.01f);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while (bReferenced);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bHadToBlock &&
 | 
				
			||||||
 | 
							bWarnIfBlocked
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
							&& !FAutomationTestFramework::Get().GetCurrentTest() // HACK - Don't output this warning during automation test
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogStaticMesh, Display, TEXT("Main thread blocked for %.3fs for async distance field build of %s to complete!  This can happen if the mesh is rebuilt excessively."),
 | 
				
			||||||
 | 
							       (float)(FPlatformTime::Seconds() - StartTime),
 | 
				
			||||||
 | 
							       *StaticMesh->GetName());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::BlockUntilAllBuildsComplete()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::BlockUntilAllBuildsComplete)
 | 
				
			||||||
 | 
						do
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
							FStaticMeshCompilingManager::Get().FinishAllCompilation();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
							// Reschedule as highest prio since we're explicitly waiting on them
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ProcessAsyncTasks();
 | 
				
			||||||
 | 
							FPlatformProcess::Sleep(.01f);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while (GetNumOutstandingTasks() > 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::Build(FAsyncDistanceFieldTask2* Task, FQueuedThreadPool& BuildThreadPool)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						//#if WITH_EDITOR
 | 
				
			||||||
 | 
						// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
 | 
				
			||||||
 | 
						if (Task->StaticMesh && Task->GenerateSource)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::Build);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
 | 
				
			||||||
 | 
							MeshUtilities->GenerateSignedDistanceFieldVolumeData(
 | 
				
			||||||
 | 
								Task->StaticMesh->GetName(),
 | 
				
			||||||
 | 
								Task->SourceMeshData,
 | 
				
			||||||
 | 
								LODModel,
 | 
				
			||||||
 | 
								BuildThreadPool,
 | 
				
			||||||
 | 
								Task->MaterialBlendModes,
 | 
				
			||||||
 | 
								Task->GenerateSource->GetRenderData()->Bounds,
 | 
				
			||||||
 | 
								Task->DistanceFieldResolutionScale,
 | 
				
			||||||
 | 
								Task->bGenerateDistanceFieldAsIfTwoSided,
 | 
				
			||||||
 | 
								*Task->GeneratedVolumeData);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CompletedTasks.Push(Task);
 | 
				
			||||||
 | 
						//#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::AddReferencedObjects(FReferenceCollector& Collector)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
						for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Make sure none of the UObjects referenced by the async tasks are GC'ed during the task
 | 
				
			||||||
 | 
							Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->StaticMesh);
 | 
				
			||||||
 | 
							Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->GenerateSource);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FString FDistanceFieldAsyncQueue2::GetReferencerName() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return TEXT("FDistanceFieldAsyncQueue2");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::ProcessAsyncTasks(bool bLimitExecutionTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::ProcessAsyncTasks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ProcessPendingTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FObjectCacheContextScope ObjectCacheScope;
 | 
				
			||||||
 | 
						const double MaxProcessingTime = 0.016f;
 | 
				
			||||||
 | 
						double StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
						while (!bLimitExecutionTime || (FPlatformTime::Seconds() - StartTime) < MaxProcessingTime)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FAsyncDistanceFieldTask2* Task = CompletedTasks.Pop();
 | 
				
			||||||
 | 
							if (Task == nullptr)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We want to count each resource built from a DDC miss, so count each iteration of the loop separately.
 | 
				
			||||||
 | 
							COOK_STAT(auto Timer = DistanceFieldCookStats::UsageStats.TimeSyncWork());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bWasCancelled = false;
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								bWasCancelled = ReferencedTasks.Remove(Task) == 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bWasCancelled)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->EnsureCompletion();
 | 
				
			||||||
 | 
								Task->AsyncTask.Reset();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
 | 
				
			||||||
 | 
							if (Task->StaticMesh)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->GeneratedVolumeData->bAsyncBuilding = false;
 | 
				
			||||||
 | 
								FDistanceFieldVolumeData* OldVolumeData = Task->StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Assign the new volume data, this is safe because the render thread makes a copy of the pointer at scene proxy creation time.
 | 
				
			||||||
 | 
								Task->StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData = Task->GeneratedVolumeData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Renderstates are not initialized between UStaticMesh::PreEditChange() and UStaticMesh::PostEditChange()
 | 
				
			||||||
 | 
								if (Task->StaticMesh->GetRenderData()->IsInitialized())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(Task->StaticMesh))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										if (Component->IsRegistered() && Component->IsRenderStateCreated())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Component->MarkRenderStateDirty();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (OldVolumeData)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
 | 
				
			||||||
 | 
									BeginCleanup(OldVolumeData);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								//第二阶段
 | 
				
			||||||
 | 
								BuildMeshCardRepresentation(Task->StaticMesh, *Task->StaticMesh->GetRenderData(), &Task->SourceMeshData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FDistanceFieldAsyncQueue2::Shutdown()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						CancelAllOutstandingBuilds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UE_LOG(LogStaticMesh, Log, TEXT("Abandoning remaining async distance field tasks for shutdown"));
 | 
				
			||||||
 | 
						ThreadPool->Destroy();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,456 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*=============================================================================
 | 
				
			||||||
 | 
						MeshCardRepresentation.cpp
 | 
				
			||||||
 | 
					=============================================================================*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2.h"
 | 
				
			||||||
 | 
					#include "Modules/ModuleManager.h"
 | 
				
			||||||
 | 
					#include "StaticMeshResources.h"
 | 
				
			||||||
 | 
					#include "ProfilingDebugging/CookStats.h"
 | 
				
			||||||
 | 
					#include "Templates/UniquePtr.h"
 | 
				
			||||||
 | 
					#include "Engine/StaticMesh.h"
 | 
				
			||||||
 | 
					#include "Misc/AutomationTest.h"
 | 
				
			||||||
 | 
					#include "Misc/QueuedThreadPoolWrapper.h"
 | 
				
			||||||
 | 
					#include "ObjectCacheContext.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
					#include "AssetCompilingManager.h"
 | 
				
			||||||
 | 
					// #include "DerivedDataCacheInterface.h"
 | 
				
			||||||
 | 
					// #include "MeshUtilities.h"
 | 
				
			||||||
 | 
					// #include "StaticMeshCompiler.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if WITH_EDITORONLY_DATA
 | 
				
			||||||
 | 
					#include "IMeshBuilderModule.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if ENABLE_COOK_STATS
 | 
				
			||||||
 | 
					namespace CardRepresentationCookStats
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FCookStats::FDDCResourceUsageStats UsageStats;
 | 
				
			||||||
 | 
						static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UsageStats.LogStats(AddStat, TEXT("CardRepresentation.Usage"), TEXT(""));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// static TAutoConsoleVariable<int32> CVarCardRepresentation(
 | 
				
			||||||
 | 
					// 	TEXT("r.MeshCardRepresentation"),
 | 
				
			||||||
 | 
					// 	1,
 | 
				
			||||||
 | 
					// 	TEXT(""),
 | 
				
			||||||
 | 
					// 	ECVF_ReadOnly);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// static TAutoConsoleVariable<float> CVarCardRepresentationMinSurface(
 | 
				
			||||||
 | 
					// 	TEXT("r.MeshCardRepresentation.MinSurface"),
 | 
				
			||||||
 | 
					// 	0.2f,
 | 
				
			||||||
 | 
					// 	TEXT("Min percentage of surface treshold to spawn a new card, [0;1] range."),
 | 
				
			||||||
 | 
					// 	ECVF_ReadOnly);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FCardRepresentationAsyncQueue2* GCardRepresentationAsyncQueue2 = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32 GUseAsyncCardRepresentationBuildQueue2 = 1;
 | 
				
			||||||
 | 
					static FAutoConsoleVariableRef CVarCardRepresentationAsyncBuildQueue2(
 | 
				
			||||||
 | 
						TEXT("r.MeshCardRepresentation.Async"),
 | 
				
			||||||
 | 
						GUseAsyncCardRepresentationBuildQueue2,
 | 
				
			||||||
 | 
						TEXT("."),
 | 
				
			||||||
 | 
						ECVF_Default | ECVF_ReadOnly
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FCardRepresentationAsyncQueue2::FCardRepresentationAsyncQueue2()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshUtilities = NULL;
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
						const int32 MaxConcurrency = -1;
 | 
				
			||||||
 | 
						// In Editor, we allow faster compilation by letting the asset compiler's scheduler organize work.
 | 
				
			||||||
 | 
						ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(FAssetCompilingManager::Get().GetThreadPool(), MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						const int32 MaxConcurrency = -1;
 | 
				
			||||||
 | 
						ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(GThreadPool, MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FCardRepresentationAsyncQueue2::~FCardRepresentationAsyncQueue2()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FAsyncCardRepresentationTaskWorker2::DoWork()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Put on background thread to avoid interfering with game-thread bound tasks
 | 
				
			||||||
 | 
						FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
 | 
				
			||||||
 | 
						GCardRepresentationAsyncQueue2->Build(&Task, TaskGraphWrapper);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::CancelBackgroundTask(TArray<FAsyncCardRepresentationTask2*> Tasks)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Do all the cancellation first to make sure none of these tasks
 | 
				
			||||||
 | 
						// get scheduled as we're waiting for completion.
 | 
				
			||||||
 | 
						for (FAsyncCardRepresentationTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->Cancel();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (FAsyncCardRepresentationTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->EnsureCompletion();
 | 
				
			||||||
 | 
								Task->AsyncTask.Reset();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::StartBackgroundTask(FAsyncCardRepresentationTask2* Task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						check(Task->AsyncTask == nullptr);
 | 
				
			||||||
 | 
						Task->AsyncTask = MakeUnique<FAsyncTask<FAsyncCardRepresentationTaskWorker2>>(*Task);
 | 
				
			||||||
 | 
						Task->AsyncTask->StartBackgroundTask(ThreadPool.Get(), EQueuedWorkPriority::Lowest);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::ProcessPendingTasks()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
						TArray<FAsyncCardRepresentationTask2*> Tasks = MoveTemp(PendingTasks);
 | 
				
			||||||
 | 
						for (FAsyncCardRepresentationTask2* Task : Tasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->GenerateSource && Task->GenerateSource->IsCompiling())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								PendingTasks.Add(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								StartBackgroundTask(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::AddTask(FAsyncCardRepresentationTask2* Task)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						if (!MeshUtilities)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshUtilities = &FModuleManager::Get().LoadModuleChecked<IMeshUtilities2>(TEXT("MeshUtilities2"));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Array protection when called from multiple threads
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							ReferencedTasks.Add(Task);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The Source Mesh's RenderData is not yet ready, postpone the build
 | 
				
			||||||
 | 
						if (Task->GenerateSource->IsCompiling())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Array protection when called from multiple threads
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							PendingTasks.Add(Task);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// If we're already in worker threads there is no need to launch an async task.
 | 
				
			||||||
 | 
							if (GUseAsyncCardRepresentationBuildQueue2 || !IsInGameThread())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								StartBackgroundTask(Task);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// To avoid deadlocks, we must queue the inner build tasks on another thread pool, so use the task graph.
 | 
				
			||||||
 | 
								// Put on background thread to avoid interfering with game-thread bound tasks
 | 
				
			||||||
 | 
								FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
 | 
				
			||||||
 | 
								Build(Task, TaskGraphWrapper);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// #else
 | 
				
			||||||
 | 
						// 	UE_LOG(LogStaticMesh,Fatal,TEXT("Tried to build a card representation without editor support (this should have been done during cooking)"));
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::CancelBuild(UStaticMesh* StaticMesh)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::CancelBuild);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FAsyncCardRepresentationTask2*> TasksToCancel;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							TArray<FAsyncCardRepresentationTask2*> Tasks = MoveTemp(PendingTasks);
 | 
				
			||||||
 | 
							PendingTasks.Reserve(Tasks.Num());
 | 
				
			||||||
 | 
							for (FAsyncCardRepresentationTask2* Task : Tasks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									PendingTasks.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Tasks = MoveTemp(ReferencedTasks);
 | 
				
			||||||
 | 
							ReferencedTasks.Reserve(Tasks.Num());
 | 
				
			||||||
 | 
							for (FAsyncCardRepresentationTask2* Task : Tasks)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									ReferencedTasks.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									TasksToCancel.Add(Task);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CancelBackgroundTask(TasksToCancel);
 | 
				
			||||||
 | 
						for (FAsyncCardRepresentationTask2* Task : TasksToCancel)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Task->GeneratedCardRepresentation != nullptr)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
 | 
				
			||||||
 | 
								BeginCleanup(Task->GeneratedCardRepresentation);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::CancelAllOutstandingBuilds()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::CancelAllOutstandingBuilds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FAsyncCardRepresentationTask2*> OutstandingTasks;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							PendingTasks.Empty();
 | 
				
			||||||
 | 
							OutstandingTasks = MoveTemp(ReferencedTasks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CancelBackgroundTask(OutstandingTasks);
 | 
				
			||||||
 | 
						for (FAsyncCardRepresentationTask2* Task : OutstandingTasks)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::RescheduleBackgroundTask(FAsyncCardRepresentationTask2* InTask, EQueuedWorkPriority InPriority)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (InTask->AsyncTask)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (InTask->AsyncTask->GetPriority() != InPriority)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								InTask->AsyncTask->Reschedule(GThreadPool, InPriority);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// We will track the wait time here, but only the cycles used.
 | 
				
			||||||
 | 
						// This function is called whether or not an async task is pending,
 | 
				
			||||||
 | 
						// so we have to look elsewhere to properly count how many resources have actually finished building.
 | 
				
			||||||
 | 
						COOK_STAT(auto Timer = CardRepresentationCookStats::UsageStats.TimeAsyncWait());
 | 
				
			||||||
 | 
						COOK_STAT(Timer.TrackCyclesOnly());
 | 
				
			||||||
 | 
						bool bReferenced = false;
 | 
				
			||||||
 | 
						bool bHadToBlock = false;
 | 
				
			||||||
 | 
						double StartTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						// 	FStaticMeshCompilingManager::Get().FinishCompilation({ StaticMesh });
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ProcessAsyncTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bReferenced = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (ReferencedTasks[TaskIndex]->StaticMesh == StaticMesh ||
 | 
				
			||||||
 | 
										ReferencedTasks[TaskIndex]->GenerateSource == StaticMesh)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										bReferenced = true;
 | 
				
			||||||
 | 
										RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bReferenced)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (!bHadToBlock)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bHadToBlock = true;
 | 
				
			||||||
 | 
								FPlatformProcess::Sleep(.01f);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while (bReferenced);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bHadToBlock &&
 | 
				
			||||||
 | 
							bWarnIfBlocked
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
							&& !FAutomationTestFramework::Get().GetCurrentTest() // HACK - Don't output this warning during automation test
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogStaticMesh, Display, TEXT("Main thread blocked for %.3fs for async card representation build of %s to complete!  This can happen if the mesh is rebuilt excessively."),
 | 
				
			||||||
 | 
							       (float)(FPlatformTime::Seconds() - StartTime),
 | 
				
			||||||
 | 
							       *StaticMesh->GetName());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::BlockUntilAllBuildsComplete()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::BlockUntilAllBuildsComplete)
 | 
				
			||||||
 | 
						do
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// #if WITH_EDITOR
 | 
				
			||||||
 | 
							// 		FStaticMeshCompilingManager::Get().FinishAllCompilation();
 | 
				
			||||||
 | 
							// #endif
 | 
				
			||||||
 | 
							// Reschedule as highest prio since we're explicitly waiting on them
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ProcessAsyncTasks();
 | 
				
			||||||
 | 
							FPlatformProcess::Sleep(.01f);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while (GetNumOutstandingTasks() > 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::Build(FAsyncCardRepresentationTask2* Task, FQueuedThreadPool& BuildThreadPool)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg UProperty or serialized)
 | 
				
			||||||
 | 
						if (Task->StaticMesh && Task->GenerateSource)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::Build);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Task->bSuccess = MeshUtilities->GenerateCardRepresentationData(
 | 
				
			||||||
 | 
								Task->StaticMesh->GetName(),
 | 
				
			||||||
 | 
								Task->SourceMeshData,
 | 
				
			||||||
 | 
								LODModel,
 | 
				
			||||||
 | 
								BuildThreadPool,
 | 
				
			||||||
 | 
								Task->MaterialBlendModes,
 | 
				
			||||||
 | 
								Task->GenerateSource->GetRenderData()->Bounds,
 | 
				
			||||||
 | 
								Task->GenerateSource->GetRenderData()->LODResources[0].DistanceFieldData,
 | 
				
			||||||
 | 
								Task->bGenerateDistanceFieldAsIfTwoSided,
 | 
				
			||||||
 | 
								*Task->GeneratedCardRepresentation);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CompletedTasks.Push(Task);
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::AddReferencedObjects(FReferenceCollector& Collector)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
						for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Make sure none of the UObjects referenced by the async tasks are GC'ed during the task
 | 
				
			||||||
 | 
							Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->StaticMesh);
 | 
				
			||||||
 | 
							Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->GenerateSource);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FString FCardRepresentationAsyncQueue2::GetReferencerName() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return TEXT("FCardRepresentationAsyncQueue2");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::ProcessAsyncTasks(bool bLimitExecutionTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::ProcessAsyncTasks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ProcessPendingTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FObjectCacheContextScope ObjectCacheScope;
 | 
				
			||||||
 | 
						const double MaxProcessingTime = 0.016f;
 | 
				
			||||||
 | 
						double StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
						while (!bLimitExecutionTime || (FPlatformTime::Seconds() - StartTime) < MaxProcessingTime)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FAsyncCardRepresentationTask2* Task = CompletedTasks.Pop();
 | 
				
			||||||
 | 
							if (Task == nullptr)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We want to count each resource built from a DDC miss, so count each iteration of the loop separately.
 | 
				
			||||||
 | 
							COOK_STAT(auto Timer = CardRepresentationCookStats::UsageStats.TimeSyncWork());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bWasCancelled = false;
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
								bWasCancelled = ReferencedTasks.Remove(Task) == 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bWasCancelled)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (Task->AsyncTask)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Task->AsyncTask->EnsureCompletion();
 | 
				
			||||||
 | 
								Task->AsyncTask.Reset();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg UProperty or serialized)
 | 
				
			||||||
 | 
							if (Task->StaticMesh && Task->bSuccess)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FCardRepresentationData* OldCardData = Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Assign the new data, this is safe because the render threads makes a copy of the pointer at scene proxy creation time.
 | 
				
			||||||
 | 
								Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData = Task->GeneratedCardRepresentation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Any already created render state needs to be dirtied
 | 
				
			||||||
 | 
								if (Task->StaticMesh->GetRenderData()->IsInitialized())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(Task->StaticMesh))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										if (Component->IsRegistered() && Component->IsRenderStateCreated())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											Component->MarkRenderStateDirty();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
 | 
				
			||||||
 | 
								BeginCleanup(OldCardData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// {
 | 
				
			||||||
 | 
								// 	TArray<uint8> DerivedData;
 | 
				
			||||||
 | 
								// 	// Save built data to DDC
 | 
				
			||||||
 | 
								// 	FMemoryWriter Ar(DerivedData, /*bIsPersistent=*/ true);
 | 
				
			||||||
 | 
								// 	Ar << *(Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData);
 | 
				
			||||||
 | 
								// 	GetDerivedDataCacheRef().Put(*Task->DDCKey, DerivedData, Task->StaticMesh->GetPathName());
 | 
				
			||||||
 | 
								// 	COOK_STAT(Timer.AddMiss(DerivedData.Num()));
 | 
				
			||||||
 | 
								// }
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delete Task;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FCardRepresentationAsyncQueue2::Shutdown()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						CancelAllOutstandingBuilds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UE_LOG(LogStaticMesh, Log, TEXT("Abandoning remaining async card representation tasks for shutdown"));
 | 
				
			||||||
 | 
						ThreadPool->Destroy();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,510 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					#include "MeshUtilitiesPrivate.h"
 | 
				
			||||||
 | 
					#include "StaticMeshResources.h"
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation.h"
 | 
				
			||||||
 | 
					#include "DistanceFieldAtlas.h"
 | 
				
			||||||
 | 
					#include "MeshRepresentationCommon.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FGenerateCardMeshContext
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						const FString& MeshName;
 | 
				
			||||||
 | 
						RTCScene FullMeshEmbreeScene;
 | 
				
			||||||
 | 
						RTCDevice EmbreeDevice;
 | 
				
			||||||
 | 
						FCardRepresentationData& OutData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FGenerateCardMeshContext(const FString& InMeshName, RTCScene InEmbreeScene, RTCDevice InEmbreeDevice, FCardRepresentationData& InOutData) :
 | 
				
			||||||
 | 
							MeshName(InMeshName),
 | 
				
			||||||
 | 
							FullMeshEmbreeScene(InEmbreeScene),
 | 
				
			||||||
 | 
							EmbreeDevice(InEmbreeDevice),
 | 
				
			||||||
 | 
							OutData(InOutData)
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FPlacedCard
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						int32 SliceMin;
 | 
				
			||||||
 | 
						int32 SliceMax;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						float NearPlane;
 | 
				
			||||||
 | 
						float FarPlane;
 | 
				
			||||||
 | 
						FBox Bounds;
 | 
				
			||||||
 | 
						int32 NumHits;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool IsSurfacePointInsideMesh(const RTCScene& FullMeshEmbreeScene, FVector SurfacePoint, FVector SurfaceNormal, const TArray<FVector4>& RayDirectionsOverHemisphere)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						uint32 NumHits = 0;
 | 
				
			||||||
 | 
						uint32 NumBackFaceHits = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FMatrix SurfaceBasis = MeshRepresentation::GetTangentBasisFrisvad(SurfaceNormal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int32 SampleIndex = 0; SampleIndex < RayDirectionsOverHemisphere.Num(); ++SampleIndex)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FVector RayDirection = SurfaceBasis.TransformVector(RayDirectionsOverHemisphere[SampleIndex]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FEmbreeRay EmbreeRay;
 | 
				
			||||||
 | 
							EmbreeRay.ray.org_x = SurfacePoint.X;
 | 
				
			||||||
 | 
							EmbreeRay.ray.org_y = SurfacePoint.Y;
 | 
				
			||||||
 | 
							EmbreeRay.ray.org_z = SurfacePoint.Z;
 | 
				
			||||||
 | 
							EmbreeRay.ray.dir_x = RayDirection.X;
 | 
				
			||||||
 | 
							EmbreeRay.ray.dir_y = RayDirection.Y;
 | 
				
			||||||
 | 
							EmbreeRay.ray.dir_z = RayDirection.Z;
 | 
				
			||||||
 | 
							EmbreeRay.ray.tnear = 0.1f;
 | 
				
			||||||
 | 
							EmbreeRay.ray.tfar = FLT_MAX;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FEmbreeIntersectionContext EmbreeContext;
 | 
				
			||||||
 | 
							rtcInitIntersectContext(&EmbreeContext);
 | 
				
			||||||
 | 
							rtcIntersect1(FullMeshEmbreeScene, &EmbreeContext, &EmbreeRay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								++NumHits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (FVector::DotProduct(RayDirection, EmbreeRay.GetHitNormal()) > 0.0f && !EmbreeContext.IsHitTwoSided())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									++NumBackFaceHits;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (NumHits > 0 && NumBackFaceHits > RayDirectionsOverHemisphere.Num() * 0.4f)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FSurfacePoint
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						float MinT;
 | 
				
			||||||
 | 
						float HitT;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32 UpdatePlacedCards(TArray<FPlacedCard, TInlineAllocator<16>>& PlacedCards,
 | 
				
			||||||
 | 
						FVector RayOriginFrame,
 | 
				
			||||||
 | 
						FVector RayDirection,
 | 
				
			||||||
 | 
						FVector HeighfieldStepX,
 | 
				
			||||||
 | 
						FVector HeighfieldStepY,
 | 
				
			||||||
 | 
						FIntPoint HeighfieldSize,
 | 
				
			||||||
 | 
						int32 MeshSliceNum,
 | 
				
			||||||
 | 
						float MaxRayT,
 | 
				
			||||||
 | 
						int32 MinCardHits,
 | 
				
			||||||
 | 
						FVector VoxelExtent,
 | 
				
			||||||
 | 
						const TArray<TArray<FSurfacePoint, TInlineAllocator<16>>>& HeightfieldLayers)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
 | 
				
			||||||
 | 
							PlacedCard.NearPlane = PlacedCard.SliceMin / float(MeshSliceNum) * MaxRayT;
 | 
				
			||||||
 | 
							PlacedCard.FarPlane = (PlacedCard.SliceMax / float(MeshSliceNum)) * MaxRayT;
 | 
				
			||||||
 | 
							PlacedCard.Bounds.Init();
 | 
				
			||||||
 | 
							PlacedCard.NumHits = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const int32 HeightfieldLinearIndex = HeighfieldX + HeighfieldY * HeighfieldSize.X;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								FVector RayOrigin = RayOriginFrame;
 | 
				
			||||||
 | 
								RayOrigin += (HeighfieldX + 0.5f) * HeighfieldStepX;
 | 
				
			||||||
 | 
								RayOrigin += (HeighfieldY + 0.5f) * HeighfieldStepY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								int32 LayerIndex = 0;
 | 
				
			||||||
 | 
								int32 PlacedCardIndex = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								while (LayerIndex < HeightfieldLayers[HeightfieldLinearIndex].Num() && PlacedCardIndex < PlacedCards.Num())
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									const FSurfacePoint& SurfacePoint = HeightfieldLayers[HeightfieldLinearIndex][LayerIndex];
 | 
				
			||||||
 | 
									FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (SurfacePoint.HitT >= PlacedCard.NearPlane && SurfacePoint.HitT <= PlacedCard.FarPlane
 | 
				
			||||||
 | 
										&& SurfacePoint.MinT <= PlacedCard.NearPlane)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										PlacedCard.NumHits += 1;
 | 
				
			||||||
 | 
										PlacedCard.Bounds += RayOrigin + SurfacePoint.HitT * RayDirection - VoxelExtent;
 | 
				
			||||||
 | 
										PlacedCard.Bounds += RayOrigin + SurfacePoint.HitT * RayDirection + VoxelExtent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										++PlacedCardIndex;
 | 
				
			||||||
 | 
										++LayerIndex;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										if (SurfacePoint.HitT >= PlacedCard.FarPlane)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											++PlacedCardIndex;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											++LayerIndex;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32 NumMeshHits = 0;
 | 
				
			||||||
 | 
						for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
 | 
				
			||||||
 | 
							if (PlacedCard.NumHits >= MinCardHits)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								NumMeshHits += PlacedCard.NumHits;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NumMeshHits;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SerializePlacedCards(TArray<FPlacedCard, TInlineAllocator<16>>& PlacedCards,
 | 
				
			||||||
 | 
						int32 LODLevel,
 | 
				
			||||||
 | 
						int32 Orientation,
 | 
				
			||||||
 | 
						int32 MinCardHits,
 | 
				
			||||||
 | 
						const FBox& MeshCardsBounds,
 | 
				
			||||||
 | 
						FCardRepresentationData& OutData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
 | 
				
			||||||
 | 
							if (PlacedCard.NumHits >= MinCardHits)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FBox ClampedBox = PlacedCard.Bounds.Overlap(MeshCardsBounds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								FLumenCardBuildData CardBuildData;
 | 
				
			||||||
 | 
								CardBuildData.Center = ClampedBox.GetCenter();
 | 
				
			||||||
 | 
								CardBuildData.Extent = ClampedBox.GetExtent();
 | 
				
			||||||
 | 
								CardBuildData.Extent = FLumenCardBuildData::TransformFaceExtent(CardBuildData.Extent, Orientation);
 | 
				
			||||||
 | 
								CardBuildData.Orientation = Orientation;
 | 
				
			||||||
 | 
								CardBuildData.LODLevel = LODLevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								OutData.MeshCardsBuildData.CardBuildData.Add(CardBuildData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BuildMeshCards(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, FCardRepresentationData& OutData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						static const auto CVarMeshCardRepresentationMinSurface = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.MeshCardRepresentation.MinSurface"));
 | 
				
			||||||
 | 
						const float MinSurfaceThreshold = CVarMeshCardRepresentationMinSurface->GetValueOnAnyThread();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes.
 | 
				
			||||||
 | 
						const FVector MeshCardsBoundsCenter = MeshBounds.GetCenter();
 | 
				
			||||||
 | 
						const FVector MeshCardsBoundsExtent = FVector::Max(MeshBounds.GetExtent() + 1.0f, FVector(5.0f));
 | 
				
			||||||
 | 
						const FBox MeshCardsBounds(MeshCardsBoundsCenter - MeshCardsBoundsExtent, MeshCardsBoundsCenter + MeshCardsBoundsExtent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OutData.MeshCardsBuildData.Bounds = MeshCardsBounds;
 | 
				
			||||||
 | 
						OutData.MeshCardsBuildData.MaxLODLevel = 1;
 | 
				
			||||||
 | 
						OutData.MeshCardsBuildData.CardBuildData.Reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const float SamplesPerWorldUnit = 1.0f / 10.0f;
 | 
				
			||||||
 | 
						const int32 MinSamplesPerAxis = 4;
 | 
				
			||||||
 | 
						const int32 MaxSamplesPerAxis = 64;
 | 
				
			||||||
 | 
						FIntVector VolumeSizeInVoxels;
 | 
				
			||||||
 | 
						VolumeSizeInVoxels.X = FMath::Clamp<int32>(MeshCardsBounds.GetSize().X * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
 | 
				
			||||||
 | 
						VolumeSizeInVoxels.Y = FMath::Clamp<int32>(MeshCardsBounds.GetSize().Y * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
 | 
				
			||||||
 | 
						VolumeSizeInVoxels.Z = FMath::Clamp<int32>(MeshCardsBounds.GetSize().Z * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FVector VoxelExtent = MeshCardsBounds.GetSize() / FVector(VolumeSizeInVoxels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate random ray directions over a hemisphere
 | 
				
			||||||
 | 
						TArray<FVector4> RayDirectionsOverHemisphere;
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FRandomStream RandomStream(0);
 | 
				
			||||||
 | 
							MeshUtilities::GenerateStratifiedUniformHemisphereSamples(64, RandomStream, RayDirectionsOverHemisphere);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int32 Orientation = 0; Orientation < 6; ++Orientation)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FIntPoint HeighfieldSize(0, 0);
 | 
				
			||||||
 | 
							FVector RayDirection(0.0f, 0.0f, 0.0f);
 | 
				
			||||||
 | 
							FVector RayOriginFrame = MeshCardsBounds.Min;
 | 
				
			||||||
 | 
							FVector HeighfieldStepX(0.0f, 0.0f, 0.0f);
 | 
				
			||||||
 | 
							FVector HeighfieldStepY(0.0f, 0.0f, 0.0f);
 | 
				
			||||||
 | 
							float MaxRayT = 0.0f;
 | 
				
			||||||
 | 
							int32 MeshSliceNum = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch (Orientation / 2)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									MaxRayT = MeshCardsBounds.GetSize().X + 0.1f;
 | 
				
			||||||
 | 
									MeshSliceNum = VolumeSizeInVoxels.X;
 | 
				
			||||||
 | 
									HeighfieldSize.X = VolumeSizeInVoxels.Y;
 | 
				
			||||||
 | 
									HeighfieldSize.Y = VolumeSizeInVoxels.Z;
 | 
				
			||||||
 | 
									HeighfieldStepX = FVector(0.0f, MeshCardsBounds.GetSize().Y / HeighfieldSize.X, 0.0f);
 | 
				
			||||||
 | 
									HeighfieldStepY = FVector(0.0f, 0.0f, MeshCardsBounds.GetSize().Z / HeighfieldSize.Y);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									MaxRayT = MeshCardsBounds.GetSize().Y + 0.1f;
 | 
				
			||||||
 | 
									MeshSliceNum = VolumeSizeInVoxels.Y;
 | 
				
			||||||
 | 
									HeighfieldSize.X = VolumeSizeInVoxels.X;
 | 
				
			||||||
 | 
									HeighfieldSize.Y = VolumeSizeInVoxels.Z;
 | 
				
			||||||
 | 
									HeighfieldStepX = FVector(MeshCardsBounds.GetSize().X / HeighfieldSize.X, 0.0f, 0.0f);
 | 
				
			||||||
 | 
									HeighfieldStepY = FVector(0.0f, 0.0f, MeshCardsBounds.GetSize().Z / HeighfieldSize.Y);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									MaxRayT = MeshCardsBounds.GetSize().Z + 0.1f;
 | 
				
			||||||
 | 
									MeshSliceNum = VolumeSizeInVoxels.Z;
 | 
				
			||||||
 | 
									HeighfieldSize.X = VolumeSizeInVoxels.X;
 | 
				
			||||||
 | 
									HeighfieldSize.Y = VolumeSizeInVoxels.Y;
 | 
				
			||||||
 | 
									HeighfieldStepX = FVector(MeshCardsBounds.GetSize().X / HeighfieldSize.X, 0.0f, 0.0f);
 | 
				
			||||||
 | 
									HeighfieldStepY = FVector(0.0f, MeshCardsBounds.GetSize().Y / HeighfieldSize.Y, 0.0f);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch (Orientation)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								case 0:
 | 
				
			||||||
 | 
									RayDirection.X = +1.0f;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 1:
 | 
				
			||||||
 | 
									RayDirection.X = -1.0f;
 | 
				
			||||||
 | 
									RayOriginFrame.X = MeshCardsBounds.Max.X;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 2:
 | 
				
			||||||
 | 
									RayDirection.Y = +1.0f;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 3:
 | 
				
			||||||
 | 
									RayDirection.Y = -1.0f;
 | 
				
			||||||
 | 
									RayOriginFrame.Y = MeshCardsBounds.Max.Y;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 4:
 | 
				
			||||||
 | 
									RayDirection.Z = +1.0f;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case 5:
 | 
				
			||||||
 | 
									RayDirection.Z = -1.0f;
 | 
				
			||||||
 | 
									RayOriginFrame.Z = MeshCardsBounds.Max.Z;
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									check(false);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TArray<TArray<FSurfacePoint, TInlineAllocator<16>>> HeightfieldLayers;
 | 
				
			||||||
 | 
							HeightfieldLayers.SetNum(HeighfieldSize.X * HeighfieldSize.Y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fill surface points
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								TRACE_CPUPROFILER_EVENT_SCOPE(FillSurfacePoints);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TArray<float> Heightfield;
 | 
				
			||||||
 | 
								Heightfield.SetNum(HeighfieldSize.X * HeighfieldSize.Y);
 | 
				
			||||||
 | 
								for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Heightfield[HeighfieldX + HeighfieldY * HeighfieldSize.X] = -1.0f;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										FVector RayOrigin = RayOriginFrame;
 | 
				
			||||||
 | 
										RayOrigin += (HeighfieldX + 0.5f) * HeighfieldStepX;
 | 
				
			||||||
 | 
										RayOrigin += (HeighfieldY + 0.5f) * HeighfieldStepY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										float StepTMin = 0.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										for (int32 StepIndex = 0; StepIndex < 64; ++StepIndex)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											FEmbreeRay EmbreeRay;
 | 
				
			||||||
 | 
											EmbreeRay.ray.org_x = RayOrigin.X;
 | 
				
			||||||
 | 
											EmbreeRay.ray.org_y = RayOrigin.Y;
 | 
				
			||||||
 | 
											EmbreeRay.ray.org_z = RayOrigin.Z;
 | 
				
			||||||
 | 
											EmbreeRay.ray.dir_x = RayDirection.X;
 | 
				
			||||||
 | 
											EmbreeRay.ray.dir_y = RayDirection.Y;
 | 
				
			||||||
 | 
											EmbreeRay.ray.dir_z = RayDirection.Z;
 | 
				
			||||||
 | 
											EmbreeRay.ray.tnear = StepTMin;
 | 
				
			||||||
 | 
											EmbreeRay.ray.tfar = FLT_MAX;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											FEmbreeIntersectionContext EmbreeContext;
 | 
				
			||||||
 | 
											rtcInitIntersectContext(&EmbreeContext);
 | 
				
			||||||
 | 
											rtcIntersect1(Context.FullMeshEmbreeScene, &EmbreeContext, &EmbreeRay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												const FVector SurfacePoint = RayOrigin + RayDirection * EmbreeRay.ray.tfar;
 | 
				
			||||||
 | 
												const FVector SurfaceNormal = EmbreeRay.GetHitNormal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const float NdotD = FVector::DotProduct(RayDirection, SurfaceNormal);
 | 
				
			||||||
 | 
												const bool bPassCullTest = EmbreeContext.IsHitTwoSided() || NdotD <= 0.0f;
 | 
				
			||||||
 | 
												const bool bPassProjectionAngleTest = FMath::Abs(NdotD) >= FMath::Cos(75.0f * (PI / 180.0f));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const float MinDistanceBetweenPoints = (MaxRayT / MeshSliceNum);
 | 
				
			||||||
 | 
												const bool bPassDistanceToAnotherSurfaceTest = EmbreeRay.ray.tnear <= 0.0f || (EmbreeRay.ray.tfar - EmbreeRay.ray.tnear > MinDistanceBetweenPoints);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												if (bPassCullTest && bPassProjectionAngleTest && bPassDistanceToAnotherSurfaceTest)
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													const bool bIsInsideMesh = IsSurfacePointInsideMesh(Context.FullMeshEmbreeScene, SurfacePoint, SurfaceNormal, RayDirectionsOverHemisphere);
 | 
				
			||||||
 | 
													if (!bIsInsideMesh)
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														HeightfieldLayers[HeighfieldX + HeighfieldY * HeighfieldSize.X].Add(
 | 
				
			||||||
 | 
															{ EmbreeRay.ray.tnear, EmbreeRay.ray.tfar }
 | 
				
			||||||
 | 
														);
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												StepTMin = EmbreeRay.ray.tfar + 0.01f;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											else
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int32 MinCardHits = FMath::Floor(HeighfieldSize.X * HeighfieldSize.Y * MinSurfaceThreshold);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TArray<FPlacedCard, TInlineAllocator<16>> PlacedCards;
 | 
				
			||||||
 | 
							int32 PlacedCardsHits = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Place a default card
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FPlacedCard PlacedCard;
 | 
				
			||||||
 | 
								PlacedCard.SliceMin = 0;
 | 
				
			||||||
 | 
								PlacedCard.SliceMax = MeshSliceNum;
 | 
				
			||||||
 | 
								PlacedCards.Add(PlacedCard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								PlacedCardsHits = UpdatePlacedCards(PlacedCards,
 | 
				
			||||||
 | 
									RayOriginFrame,
 | 
				
			||||||
 | 
									RayDirection,
 | 
				
			||||||
 | 
									HeighfieldStepX,
 | 
				
			||||||
 | 
									HeighfieldStepY,
 | 
				
			||||||
 | 
									HeighfieldSize,
 | 
				
			||||||
 | 
									MeshSliceNum,
 | 
				
			||||||
 | 
									MaxRayT,
 | 
				
			||||||
 | 
									MinCardHits,
 | 
				
			||||||
 | 
									VoxelExtent,
 | 
				
			||||||
 | 
									HeightfieldLayers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (PlacedCardsHits < MinCardHits)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									PlacedCards.Reset();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SerializePlacedCards(PlacedCards, /*LOD level*/ 0, Orientation, MinCardHits, MeshCardsBounds, OutData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Try to place more cards by splitting existing ones
 | 
				
			||||||
 | 
							for (uint32 CardPlacementIteration = 0; CardPlacementIteration < 4; ++CardPlacementIteration)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								TArray<FPlacedCard, TInlineAllocator<16>> BestPlacedCards;
 | 
				
			||||||
 | 
								int32 BestPlacedCardHits = PlacedCardsHits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
 | 
				
			||||||
 | 
									for (int32 SliceIndex = PlacedCard.SliceMin + 2; SliceIndex < PlacedCard.SliceMax; ++SliceIndex)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										TArray<FPlacedCard, TInlineAllocator<16>> TempPlacedCards(PlacedCards);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										FPlacedCard NewPlacedCard;
 | 
				
			||||||
 | 
										NewPlacedCard.SliceMin = SliceIndex;
 | 
				
			||||||
 | 
										NewPlacedCard.SliceMax = PlacedCard.SliceMax;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										TempPlacedCards[PlacedCardIndex].SliceMax = SliceIndex - 1;
 | 
				
			||||||
 | 
										TempPlacedCards.Insert(NewPlacedCard, PlacedCardIndex + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const int32 NumHits = UpdatePlacedCards(
 | 
				
			||||||
 | 
											TempPlacedCards,
 | 
				
			||||||
 | 
											RayOriginFrame,
 | 
				
			||||||
 | 
											RayDirection,
 | 
				
			||||||
 | 
											HeighfieldStepX,
 | 
				
			||||||
 | 
											HeighfieldStepY,
 | 
				
			||||||
 | 
											HeighfieldSize,
 | 
				
			||||||
 | 
											MeshSliceNum,
 | 
				
			||||||
 | 
											MaxRayT,
 | 
				
			||||||
 | 
											MinCardHits,
 | 
				
			||||||
 | 
											VoxelExtent,
 | 
				
			||||||
 | 
											HeightfieldLayers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (NumHits > BestPlacedCardHits)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											BestPlacedCards = TempPlacedCards;
 | 
				
			||||||
 | 
											BestPlacedCardHits = NumHits;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (BestPlacedCardHits >= PlacedCardsHits + MinCardHits)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									PlacedCards = BestPlacedCards;
 | 
				
			||||||
 | 
									PlacedCardsHits = BestPlacedCardHits;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							SerializePlacedCards(PlacedCards, /*LOD level*/ 1, Orientation, MinCardHits, MeshCardsBounds, OutData);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // #if USE_EMBREE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool FMeshUtilities2::GenerateCardRepresentationData(
 | 
				
			||||||
 | 
						FString MeshName,
 | 
				
			||||||
 | 
						const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
						const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
						class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
						const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
						const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
						const FDistanceFieldVolumeData* DistanceFieldVolumeData,
 | 
				
			||||||
 | 
						bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
						FCardRepresentationData& OutData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities2::GenerateCardRepresentationData);
 | 
				
			||||||
 | 
						const double StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FEmbreeScene EmbreeScene;
 | 
				
			||||||
 | 
						MeshRepresentation::SetupEmbreeScene(MeshName,
 | 
				
			||||||
 | 
							SourceMeshData,
 | 
				
			||||||
 | 
							LODModel,
 | 
				
			||||||
 | 
							MaterialBlendModes,
 | 
				
			||||||
 | 
							bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!EmbreeScene.EmbreeScene)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FGenerateCardMeshContext Context(MeshName, EmbreeScene.EmbreeScene, EmbreeScene.EmbreeDevice, OutData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Note: must operate on the SDF bounds because SDF generation can expand the mesh's bounds
 | 
				
			||||||
 | 
						BuildMeshCards(DistanceFieldVolumeData ? DistanceFieldVolumeData->LocalSpaceMeshBounds : Bounds.GetBox(), Context, OutData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const float TimeElapsed = (float)(FPlatformTime::Seconds() - StartTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (TimeElapsed > 1.0f)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogMeshUtilities, Log, TEXT("Finished mesh card build in %.1fs %s"),
 | 
				
			||||||
 | 
								TimeElapsed,
 | 
				
			||||||
 | 
								*MeshName);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed."));
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,554 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					#include "MeshUtilitiesPrivate.h"
 | 
				
			||||||
 | 
					#include "RawMesh.h"
 | 
				
			||||||
 | 
					#include "StaticMeshResources.h"
 | 
				
			||||||
 | 
					#include "DistanceFieldAtlas.h"
 | 
				
			||||||
 | 
					#include "MeshRepresentationCommon.h"
 | 
				
			||||||
 | 
					#include "Async/ParallelFor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FEmbreePointQueryContext : public RTCPointQueryContext
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						RTCGeometry MeshGeometry;
 | 
				
			||||||
 | 
						int32 NumTriangles;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						check(args->userPtr);
 | 
				
			||||||
 | 
						float& ClosestDistanceSq = *(float*)(args->userPtr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const int32 TriangleIndex = args->primID;
 | 
				
			||||||
 | 
						check(TriangleIndex < Context->NumTriangles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FVector* VertexBuffer = (const FVector*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
 | 
				
			||||||
 | 
						const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
 | 
				
			||||||
 | 
						const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
 | 
				
			||||||
 | 
						const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FVector V0 = VertexBuffer[I0];
 | 
				
			||||||
 | 
						const FVector V1 = VertexBuffer[I1];
 | 
				
			||||||
 | 
						const FVector V2 = VertexBuffer[I2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FVector QueryPosition(args->query->x, args->query->y, args->query->z);
 | 
				
			||||||
 | 
						const FVector ClosestPoint = FMath::ClosestPointOnTriangleToPoint(QueryPosition, V0, V1, V2);
 | 
				
			||||||
 | 
						const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (QueryDistanceSq < ClosestDistanceSq)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ClosestDistanceSq = QueryDistanceSq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bShrinkQuery = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bShrinkQuery)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								args->query->radius = FMath::Sqrt(ClosestDistanceSq);
 | 
				
			||||||
 | 
								// Return true to indicate that the query radius has shrunk
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return false to indicate that the query radius hasn't changed
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FSparseMeshDistanceFieldAsyncTask
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FSparseMeshDistanceFieldAsyncTask(
 | 
				
			||||||
 | 
							const FEmbreeScene& InEmbreeScene,
 | 
				
			||||||
 | 
							const TArray<FVector4>* InSampleDirections,
 | 
				
			||||||
 | 
							float InLocalSpaceTraceDistance,
 | 
				
			||||||
 | 
							FBox InVolumeBounds,
 | 
				
			||||||
 | 
							float InLocalToVolumeScale,
 | 
				
			||||||
 | 
							FVector2D InDistanceFieldToVolumeScaleBias,
 | 
				
			||||||
 | 
							FIntVector InBrickCoordinate,
 | 
				
			||||||
 | 
							FIntVector InIndirectionSize,
 | 
				
			||||||
 | 
							bool bInUsePointQuery)
 | 
				
			||||||
 | 
							:
 | 
				
			||||||
 | 
							EmbreeScene(InEmbreeScene),
 | 
				
			||||||
 | 
							SampleDirections(InSampleDirections),
 | 
				
			||||||
 | 
							LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
 | 
				
			||||||
 | 
							VolumeBounds(InVolumeBounds),
 | 
				
			||||||
 | 
							LocalToVolumeScale(InLocalToVolumeScale),
 | 
				
			||||||
 | 
							DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
 | 
				
			||||||
 | 
							BrickCoordinate(InBrickCoordinate),
 | 
				
			||||||
 | 
							IndirectionSize(InIndirectionSize),
 | 
				
			||||||
 | 
							bUsePointQuery(bInUsePointQuery),
 | 
				
			||||||
 | 
							BrickMaxDistance(MIN_uint8),
 | 
				
			||||||
 | 
							BrickMinDistance(MAX_uint8)
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void DoWork();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Readonly inputs
 | 
				
			||||||
 | 
						const FEmbreeScene& EmbreeScene;
 | 
				
			||||||
 | 
						const TArray<FVector4>* SampleDirections;
 | 
				
			||||||
 | 
						float LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
						FBox VolumeBounds;
 | 
				
			||||||
 | 
						float LocalToVolumeScale;
 | 
				
			||||||
 | 
						FVector2D DistanceFieldToVolumeScaleBias;
 | 
				
			||||||
 | 
						FIntVector BrickCoordinate;
 | 
				
			||||||
 | 
						FIntVector IndirectionSize;
 | 
				
			||||||
 | 
						bool bUsePointQuery;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Output
 | 
				
			||||||
 | 
						uint8 BrickMaxDistance;
 | 
				
			||||||
 | 
						uint8 BrickMinDistance;
 | 
				
			||||||
 | 
						TArray<uint8> DistanceFieldVolume;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32 DebugX = 0;
 | 
				
			||||||
 | 
					int32 DebugY = 0;
 | 
				
			||||||
 | 
					int32 DebugZ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FSparseMeshDistanceFieldAsyncTask::DoWork()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
 | 
				
			||||||
 | 
						const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
 | 
				
			||||||
 | 
						const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
 | 
				
			||||||
 | 
						DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										int32 DebugBreak = 0;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
 | 
				
			||||||
 | 
									const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									float MinLocalSpaceDistance = LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									bool bTraceRays = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (bUsePointQuery)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										RTCPointQuery PointQuery;
 | 
				
			||||||
 | 
										PointQuery.x = VoxelPosition.X;
 | 
				
			||||||
 | 
										PointQuery.y = VoxelPosition.Y;
 | 
				
			||||||
 | 
										PointQuery.z = VoxelPosition.Z;
 | 
				
			||||||
 | 
										PointQuery.time = 0;
 | 
				
			||||||
 | 
										PointQuery.radius = LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										FEmbreePointQueryContext QueryContext;
 | 
				
			||||||
 | 
										rtcInitPointQueryContext(&QueryContext);
 | 
				
			||||||
 | 
										QueryContext.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
 | 
				
			||||||
 | 
										QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
 | 
				
			||||||
 | 
										float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
 | 
				
			||||||
 | 
										rtcPointQuery(EmbreeScene.EmbreeScene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
 | 
				
			||||||
 | 
										bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
										MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (bTraceRays)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										int32 Hit = 0;
 | 
				
			||||||
 | 
										int32 HitBack = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											const FVector UnitRayDirection = (*SampleDirections)[SampleIndex];
 | 
				
			||||||
 | 
											const float PullbackEpsilon = 1.e-4f;
 | 
				
			||||||
 | 
											// Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.
 | 
				
			||||||
 | 
											// This happens a lot with boxes, since we trace from voxel corners.
 | 
				
			||||||
 | 
											const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
 | 
				
			||||||
 | 
											const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												FEmbreeRay EmbreeRay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												FVector RayDirection = EndPosition - VoxelPosition;
 | 
				
			||||||
 | 
												EmbreeRay.ray.org_x = StartPosition.X;
 | 
				
			||||||
 | 
												EmbreeRay.ray.org_y = StartPosition.Y;
 | 
				
			||||||
 | 
												EmbreeRay.ray.org_z = StartPosition.Z;
 | 
				
			||||||
 | 
												EmbreeRay.ray.dir_x = RayDirection.X;
 | 
				
			||||||
 | 
												EmbreeRay.ray.dir_y = RayDirection.Y;
 | 
				
			||||||
 | 
												EmbreeRay.ray.dir_z = RayDirection.Z;
 | 
				
			||||||
 | 
												EmbreeRay.ray.tnear = 0;
 | 
				
			||||||
 | 
												EmbreeRay.ray.tfar = 1.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												FEmbreeIntersectionContext EmbreeContext;
 | 
				
			||||||
 | 
												rtcInitIntersectContext(&EmbreeContext);
 | 
				
			||||||
 | 
												rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
 | 
				
			||||||
 | 
												{
 | 
				
			||||||
 | 
													check(EmbreeContext.ElementIndex != -1);
 | 
				
			||||||
 | 
													Hit++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													const FVector HitNormal = EmbreeRay.GetHitNormal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														HitBack++;
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													if (!bUsePointQuery)
 | 
				
			||||||
 | 
													{
 | 
				
			||||||
 | 
														const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														if (CurrentDistance < MinLocalSpaceDistance)
 | 
				
			||||||
 | 
														{
 | 
				
			||||||
 | 
															MinLocalSpaceDistance = CurrentDistance;
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Consider this voxel 'inside' an object if we hit a significant number of backfaces
 | 
				
			||||||
 | 
										if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MinLocalSpaceDistance *= -1;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Transform to the tracing shader's Volume space
 | 
				
			||||||
 | 
									const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
 | 
				
			||||||
 | 
									// Transform to the Distance Field texture's space
 | 
				
			||||||
 | 
									const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
 | 
				
			||||||
 | 
									check(DistanceField::DistanceFieldFormat == PF_G8);
 | 
				
			||||||
 | 
									const uint8 QuantizedDistance = FMath::Clamp<int32>(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
 | 
				
			||||||
 | 
									DistanceFieldVolume[Index] = QuantizedDistance;
 | 
				
			||||||
 | 
									BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
 | 
				
			||||||
 | 
									BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
 | 
				
			||||||
 | 
						FString MeshName,
 | 
				
			||||||
 | 
						const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
						const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
						class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
						const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
						const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
						float DistanceFieldResolutionScale,
 | 
				
			||||||
 | 
						bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
						FDistanceFieldVolumeData& OutData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (DistanceFieldResolutionScale > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							const double StartTime = FPlatformTime::Seconds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FEmbreeScene EmbreeScene;
 | 
				
			||||||
 | 
							MeshRepresentation::SetupEmbreeScene(MeshName,
 | 
				
			||||||
 | 
								SourceMeshData,
 | 
				
			||||||
 | 
								LODModel,
 | 
				
			||||||
 | 
								MaterialBlendModes,
 | 
				
			||||||
 | 
								bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
								EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							check(EmbreeScene.bUseEmbree);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bMostlyTwoSided;
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								uint32 NumTrianglesTotal = 0;
 | 
				
			||||||
 | 
								uint32 NumTwoSidedTriangles = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										NumTrianglesTotal += Section.NumTriangles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											NumTwoSidedTriangles += Section.NumTriangles;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Whether to use an Embree Point Query to compute the closest unsigned distance.  Rays will only be traced to determine backfaces visible for sign.
 | 
				
			||||||
 | 
							const bool bUsePointQuery = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TArray<FVector4> SampleDirections;
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const int32 NumVoxelDistanceSamples = bUsePointQuery ? 120 : 1200;
 | 
				
			||||||
 | 
								FRandomStream RandomStream(0);
 | 
				
			||||||
 | 
								MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
 | 
				
			||||||
 | 
								TArray<FVector4> OtherHemisphereSamples;
 | 
				
			||||||
 | 
								MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FVector4 Sample = OtherHemisphereSamples[i];
 | 
				
			||||||
 | 
									Sample.Z *= -1;
 | 
				
			||||||
 | 
									SampleDirections.Add(Sample);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
 | 
				
			||||||
 | 
							const int32 PerMeshMax = CVar->GetValueOnAnyThread();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Meshes with explicit artist-specified scale can go higher
 | 
				
			||||||
 | 
							const int32 MaxNumBlocksOneDim = FMath::Min<int32>(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
 | 
				
			||||||
 | 
							const float VoxelDensity = CVarDensity->GetValueOnAnyThread();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
 | 
				
			||||||
 | 
							FBox LocalSpaceMeshBounds(Bounds.GetBox());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make sure the mesh bounding box has positive extents to handle planes
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
 | 
				
			||||||
 | 
								FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
 | 
				
			||||||
 | 
								LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
 | 
				
			||||||
 | 
								LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We sample on voxel corners and use central differencing for gradients, so a box mesh using two-sided materials whose vertices lie on LocalSpaceMeshBounds produces a zero gradient on intersection
 | 
				
			||||||
 | 
							// Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
 | 
				
			||||||
 | 
							// Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
 | 
				
			||||||
 | 
							if (bMostlyTwoSided)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
 | 
				
			||||||
 | 
								const FIntVector Mip0IndirectionDimensions = FIntVector(
 | 
				
			||||||
 | 
									FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
 | 
				
			||||||
 | 
									FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
 | 
				
			||||||
 | 
									FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const float CentralDifferencingExpandInVoxels = .25f;
 | 
				
			||||||
 | 
								const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * CentralDifferencingExpandInVoxels));
 | 
				
			||||||
 | 
								LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
 | 
				
			||||||
 | 
							const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
 | 
				
			||||||
 | 
							const FIntVector Mip0IndirectionDimensions = FIntVector(
 | 
				
			||||||
 | 
								FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
 | 
				
			||||||
 | 
								FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
 | 
				
			||||||
 | 
								FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TArray<uint8> StreamableMipData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FIntVector IndirectionDimensions = FIntVector(
 | 
				
			||||||
 | 
									FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
 | 
				
			||||||
 | 
									FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
 | 
				
			||||||
 | 
									FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
 | 
				
			||||||
 | 
								const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
 | 
				
			||||||
 | 
								const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
 | 
				
			||||||
 | 
								const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
 | 
				
			||||||
 | 
								const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
 | 
				
			||||||
 | 
								const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
 | 
				
			||||||
 | 
								const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks;
 | 
				
			||||||
 | 
								AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											AsyncTasks.Emplace(
 | 
				
			||||||
 | 
												EmbreeScene,
 | 
				
			||||||
 | 
												&SampleDirections,
 | 
				
			||||||
 | 
												LocalSpaceTraceDistance,
 | 
				
			||||||
 | 
												DistanceFieldVolumeBounds,
 | 
				
			||||||
 | 
												LocalToVolumeScale,
 | 
				
			||||||
 | 
												DistanceFieldToVolumeScaleBias,
 | 
				
			||||||
 | 
												FIntVector(XIndex, YIndex, ZIndex),
 | 
				
			||||||
 | 
												IndirectionDimensions,
 | 
				
			||||||
 | 
												bUsePointQuery);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								static bool bMultiThreaded = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (bMultiThreaded)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									ParallelForTemplate(AsyncTasks.Num(), [&AsyncTasks](int32 TaskIndex)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										AsyncTasks[TaskIndex].DoWork();
 | 
				
			||||||
 | 
									}, Flags);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										AsyncTask.DoWork();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
 | 
				
			||||||
 | 
								TArray<uint32> IndirectionTable;
 | 
				
			||||||
 | 
								IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
 | 
				
			||||||
 | 
								IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 i = 0; i < IndirectionTable.Num(); i++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									IndirectionTable[i] = DistanceField::InvalidBrickIndex;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TArray<FSparseMeshDistanceFieldAsyncTask*> ValidBricks;
 | 
				
			||||||
 | 
								ValidBricks.Empty(AsyncTasks.Num());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ValidBricks.Add(&AsyncTasks[TaskIndex]);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const uint32 NumBricks = ValidBricks.Num();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TArray<uint8> DistanceFieldBrickData;
 | 
				
			||||||
 | 
								DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
 | 
				
			||||||
 | 
								DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
 | 
				
			||||||
 | 
									const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
 | 
				
			||||||
 | 
									IndirectionTable[IndirectionIndex] = BrickIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
 | 
				
			||||||
 | 
									FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
 | 
				
			||||||
 | 
								const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (MipIndex == DistanceField::NumMips - 1)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									OutData.AlwaysLoadedMip.Empty(MipDataBytes);
 | 
				
			||||||
 | 
									OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (DistanceFieldBrickData.Num() > 0)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									OutMip.BulkOffset = StreamableMipData.Num();
 | 
				
			||||||
 | 
									StreamableMipData.AddUninitialized(MipDataBytes);
 | 
				
			||||||
 | 
									OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
 | 
				
			||||||
 | 
									checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (DistanceFieldBrickData.Num() > 0)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								OutMip.IndirectionDimensions = IndirectionDimensions;
 | 
				
			||||||
 | 
								OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
 | 
				
			||||||
 | 
								OutMip.NumDistanceFieldBricks = NumBricks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Account for the border voxels we added
 | 
				
			||||||
 | 
								const FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
 | 
				
			||||||
 | 
								const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
 | 
				
			||||||
 | 
								OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
 | 
				
			||||||
 | 
								OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OutData.bMostlyTwoSided = bMostlyTwoSided;
 | 
				
			||||||
 | 
							OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							OutData.StreamableMips.Lock(LOCK_READ_WRITE);
 | 
				
			||||||
 | 
							uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
 | 
				
			||||||
 | 
							FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
 | 
				
			||||||
 | 
							OutData.StreamableMips.Unlock();
 | 
				
			||||||
 | 
							OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (BuildTime > 1.0f)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UE_LOG(LogMeshUtilities, Log, TEXT("完成:距离场构建 %.1fs - %ux%ux%u 稀疏距离场, %.1fMb total, %.1fMb 总是加载, %u%% occupied, %u 三角形, %s"),
 | 
				
			||||||
 | 
									BuildTime,
 | 
				
			||||||
 | 
									Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
 | 
				
			||||||
 | 
									Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
 | 
				
			||||||
 | 
									Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
 | 
				
			||||||
 | 
									(OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
 | 
				
			||||||
 | 
									(OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
 | 
				
			||||||
 | 
									FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
 | 
				
			||||||
 | 
									EmbreeScene.NumIndices / 3,
 | 
				
			||||||
 | 
									*MeshName);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
 | 
				
			||||||
 | 
						FString MeshName,
 | 
				
			||||||
 | 
						const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
						const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
						class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
						const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
						const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
						float DistanceFieldResolutionScale,
 | 
				
			||||||
 | 
						bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
						FDistanceFieldVolumeData& OutData)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (DistanceFieldResolutionScale > 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogMeshUtilities, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // PLATFORM_ENABLE_VECTORINTRINSICS
 | 
				
			||||||
@@ -0,0 +1,318 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshRepresentationCommon.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					#include "MeshUtilitiesPrivate.h"
 | 
				
			||||||
 | 
					#include "DerivedMeshDataTaskUtils.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MeshUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const int32 NumThetaSteps = FMath::TruncToInt(FMath::Sqrt(NumSamples / (2.0f * (float)PI)));
 | 
				
			||||||
 | 
						const int32 NumPhiSteps = FMath::TruncToInt(NumThetaSteps * (float)PI);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Samples.Empty(NumThetaSteps * NumPhiSteps);
 | 
				
			||||||
 | 
						for (int32 ThetaIndex = 0; ThetaIndex < NumThetaSteps; ThetaIndex++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (int32 PhiIndex = 0; PhiIndex < NumPhiSteps; PhiIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const float U1 = RandomStream.GetFraction();
 | 
				
			||||||
 | 
								const float U2 = RandomStream.GetFraction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const float Fraction1 = (ThetaIndex + U1) / (float)NumThetaSteps;
 | 
				
			||||||
 | 
								const float Fraction2 = (PhiIndex + U2) / (float)NumPhiSteps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const float R = FMath::Sqrt(1.0f - Fraction1 * Fraction1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const float Phi = 2.0f * (float)PI * Fraction2;
 | 
				
			||||||
 | 
								// Convert to Cartesian
 | 
				
			||||||
 | 
								Samples.Add(FVector4(FMath::Cos(Phi) * R, FMath::Sin(Phi) * R, Fraction1));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
 | 
				
			||||||
 | 
					FMatrix MeshRepresentation::GetTangentBasisFrisvad(FVector TangentZ)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FVector TangentX;
 | 
				
			||||||
 | 
						FVector TangentY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (TangentZ.Z < -0.9999999f)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							TangentX = FVector(0, -1, 0);
 | 
				
			||||||
 | 
							TangentY = FVector(-1, 0, 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							float A = 1.0f / (1.0f + TangentZ.Z);
 | 
				
			||||||
 | 
							float B = -TangentZ.X * TangentZ.Y * A;
 | 
				
			||||||
 | 
							TangentX = FVector(1.0f - TangentZ.X * TangentZ.X * A, B, -TangentZ.X);
 | 
				
			||||||
 | 
							TangentY = FVector(B, 1.0f - TangentZ.Y * TangentZ.Y * A, -TangentZ.Y);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FMatrix LocalBasis;
 | 
				
			||||||
 | 
						LocalBasis.SetIdentity();
 | 
				
			||||||
 | 
						LocalBasis.SetAxis(0, TangentX);
 | 
				
			||||||
 | 
						LocalBasis.SetAxis(1, TangentY);
 | 
				
			||||||
 | 
						LocalBasis.SetAxis(2, TangentZ);
 | 
				
			||||||
 | 
						return LocalBasis;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
					void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FEmbreeGeometry* EmbreeGeometry = (FEmbreeGeometry*)args->geometryUserPtr;
 | 
				
			||||||
 | 
						FEmbreeTriangleDesc Desc = EmbreeGeometry->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FEmbreeIntersectionContext& IntersectionContext = *static_cast<FEmbreeIntersectionContext*>(args->context);
 | 
				
			||||||
 | 
						IntersectionContext.ElementIndex = Desc.ElementIndex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FString ErrorString;
 | 
				
			||||||
 | 
						TArray<TCHAR>& ErrorStringArray = ErrorString.GetCharArray();
 | 
				
			||||||
 | 
						ErrorStringArray.Empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32 StrLen = FCStringAnsi::Strlen(str);
 | 
				
			||||||
 | 
						int32 Length = FUTF8ToTCHAR_Convert::ConvertedLength(str, StrLen);
 | 
				
			||||||
 | 
						ErrorStringArray.AddUninitialized(Length + 1); // +1 for the null terminator
 | 
				
			||||||
 | 
						FUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast<const ANSICHAR*>(str), StrLen);
 | 
				
			||||||
 | 
						ErrorStringArray[Length] = TEXT('\0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UE_LOG(LogMeshUtilities, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MeshRepresentation::SetupEmbreeScene(
 | 
				
			||||||
 | 
						FString MeshName,
 | 
				
			||||||
 | 
						const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
						const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
						const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
						bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
						FEmbreeScene& EmbreeScene)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const uint32 NumIndices = SourceMeshData.IsValid() ? SourceMeshData.GetNumIndices() : LODModel.IndexBuffer.GetNumIndices();
 | 
				
			||||||
 | 
						const int32 NumTriangles = NumIndices / 3;
 | 
				
			||||||
 | 
						const uint32 NumVertices = SourceMeshData.IsValid() ? SourceMeshData.GetNumVertices() : LODModel.VertexBuffers.PositionVertexBuffer.GetNumVertices();
 | 
				
			||||||
 | 
						EmbreeScene.NumIndices = NumTriangles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FkDOPBuildCollisionTriangle<uint32> > BuildTriangles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
						EmbreeScene.bUseEmbree = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (EmbreeScene.bUseEmbree)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							EmbreeScene.EmbreeDevice = rtcNewDevice(nullptr);
 | 
				
			||||||
 | 
							rtcSetDeviceErrorFunction(EmbreeScene.EmbreeDevice, EmbreeErrorFunc, nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RTCError ReturnErrorNewDevice = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
							if (ReturnErrorNewDevice != RTC_ERROR_NONE)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewDevice failed. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							EmbreeScene.EmbreeScene = rtcNewScene(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
							rtcSetSceneFlags(EmbreeScene.EmbreeScene, RTC_SCENE_FLAG_NONE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RTCError ReturnErrorNewScene = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
							if (ReturnErrorNewScene != RTC_ERROR_NONE)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewScene failed. Code: %d"), *MeshName, (int32)ReturnErrorNewScene);
 | 
				
			||||||
 | 
								rtcReleaseDevice(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<int32> FilteredTriangles;
 | 
				
			||||||
 | 
						FilteredTriangles.Empty(NumTriangles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (SourceMeshData.IsValid())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const uint32 I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
 | 
				
			||||||
 | 
								const uint32 I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
 | 
				
			||||||
 | 
								const uint32 I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector V0 = SourceMeshData.VertexPositions[I0];
 | 
				
			||||||
 | 
								const FVector V1 = SourceMeshData.VertexPositions[I1];
 | 
				
			||||||
 | 
								const FVector V2 = SourceMeshData.VertexPositions[I2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector TriangleNormal = ((V1 - V2) ^ (V0 - V2));
 | 
				
			||||||
 | 
								const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
 | 
				
			||||||
 | 
								if (!bDegenerateTriangle)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FilteredTriangles.Add(TriangleIndex);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
 | 
				
			||||||
 | 
								const uint32 I0 = Indices[TriangleIndex * 3 + 0];
 | 
				
			||||||
 | 
								const uint32 I1 = Indices[TriangleIndex * 3 + 1];
 | 
				
			||||||
 | 
								const uint32 I2 = Indices[TriangleIndex * 3 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
 | 
				
			||||||
 | 
								const FVector V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
 | 
				
			||||||
 | 
								const FVector V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const FVector TriangleNormal = ((V1 - V2) ^ (V0 - V2));
 | 
				
			||||||
 | 
								const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
 | 
				
			||||||
 | 
								if (!bDegenerateTriangle)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									bool bTriangleIsOpaqueOrMasked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												bTriangleIsOpaqueOrMasked = !IsTranslucentBlendMode(MaterialBlendModes[Section.MaterialIndex].BlendMode);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (bTriangleIsOpaqueOrMasked)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										FilteredTriangles.Add(TriangleIndex);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EmbreeScene.Geometry.VertexArray.Empty(NumVertices);
 | 
				
			||||||
 | 
						EmbreeScene.Geometry.VertexArray.AddUninitialized(NumVertices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const int32 NumFilteredIndices = FilteredTriangles.Num() * 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EmbreeScene.Geometry.IndexArray.Empty(NumFilteredIndices);
 | 
				
			||||||
 | 
						EmbreeScene.Geometry.IndexArray.AddUninitialized(NumFilteredIndices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FVector* EmbreeVertices = EmbreeScene.Geometry.VertexArray.GetData();
 | 
				
			||||||
 | 
						uint32* EmbreeIndices = EmbreeScene.Geometry.IndexArray.GetData();
 | 
				
			||||||
 | 
						EmbreeScene.Geometry.TriangleDescs.Empty(FilteredTriangles.Num());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							uint32 I0, I1, I2;
 | 
				
			||||||
 | 
							FVector V0, V1, V2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex];
 | 
				
			||||||
 | 
							if (SourceMeshData.IsValid())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
 | 
				
			||||||
 | 
								I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
 | 
				
			||||||
 | 
								I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								V0 = SourceMeshData.VertexPositions[I0];
 | 
				
			||||||
 | 
								V1 = SourceMeshData.VertexPositions[I1];
 | 
				
			||||||
 | 
								V2 = SourceMeshData.VertexPositions[I2];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
 | 
				
			||||||
 | 
								I0 = Indices[TriangleIndex * 3 + 0];
 | 
				
			||||||
 | 
								I1 = Indices[TriangleIndex * 3 + 1];
 | 
				
			||||||
 | 
								I2 = Indices[TriangleIndex * 3 + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
 | 
				
			||||||
 | 
								V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
 | 
				
			||||||
 | 
								V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bTriangleIsTwoSided = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										bTriangleIsTwoSided = MaterialBlendModes[Section.MaterialIndex].bTwoSided;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (EmbreeScene.bUseEmbree)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								EmbreeIndices[FilteredTriangleIndex * 3 + 0] = I0;
 | 
				
			||||||
 | 
								EmbreeIndices[FilteredTriangleIndex * 3 + 1] = I1;
 | 
				
			||||||
 | 
								EmbreeIndices[FilteredTriangleIndex * 3 + 2] = I2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								EmbreeVertices[I0] = V0;
 | 
				
			||||||
 | 
								EmbreeVertices[I1] = V1;
 | 
				
			||||||
 | 
								EmbreeVertices[I2] = V2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								FEmbreeTriangleDesc Desc;
 | 
				
			||||||
 | 
								// Store bGenerateAsIfTwoSided in material index
 | 
				
			||||||
 | 
								Desc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;
 | 
				
			||||||
 | 
								EmbreeScene.Geometry.TriangleDescs.Add(Desc);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								BuildTriangles.Add(FkDOPBuildCollisionTriangle<uint32>(
 | 
				
			||||||
 | 
									// Store bGenerateAsIfTwoSided in material index
 | 
				
			||||||
 | 
									bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,
 | 
				
			||||||
 | 
									V0,
 | 
				
			||||||
 | 
									V1,
 | 
				
			||||||
 | 
									V2));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
						if (EmbreeScene.bUseEmbree)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RTCGeometry Geometry = rtcNewGeometry(EmbreeScene.EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
 | 
				
			||||||
 | 
							EmbreeScene.Geometry.InternalGeometry = Geometry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector), NumVertices);
 | 
				
			||||||
 | 
							rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, FilteredTriangles.Num());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rtcSetGeometryUserData(Geometry, &EmbreeScene.Geometry);
 | 
				
			||||||
 | 
							rtcSetGeometryIntersectFilterFunction(Geometry, EmbreeFilterFunc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rtcCommitGeometry(Geometry);
 | 
				
			||||||
 | 
							rtcAttachGeometry(EmbreeScene.EmbreeScene, Geometry);
 | 
				
			||||||
 | 
							rtcReleaseGeometry(Geometry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rtcCommitScene(EmbreeScene.EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RTCError ReturnError = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
							if (ReturnError != RTC_ERROR_NONE)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcCommitScene failed. Code: %d"), *MeshName, (int32)ReturnError);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							EmbreeScene.kDopTree.Build(BuildTriangles);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MeshRepresentation::DeleteEmbreeScene(FEmbreeScene& EmbreeScene)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
						if (EmbreeScene.bUseEmbree)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							rtcReleaseScene(EmbreeScene.EmbreeScene);
 | 
				
			||||||
 | 
							rtcReleaseDevice(EmbreeScene.EmbreeDevice);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					#include "kDOP.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
						#include <embree3/rtcore.h>
 | 
				
			||||||
 | 
						#include <embree3/rtcore_ray.h>
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
						typedef void* RTCDevice;
 | 
				
			||||||
 | 
						typedef void* RTCScene;
 | 
				
			||||||
 | 
						typedef void* RTCGeometry;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FSourceMeshDataForDerivedDataTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FMeshBuildDataProvider
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Initialization constructor. */
 | 
				
			||||||
 | 
						FMeshBuildDataProvider(
 | 
				
			||||||
 | 
							const TkDOPTree<const FMeshBuildDataProvider, uint32>& InkDopTree) :
 | 
				
			||||||
 | 
							kDopTree(InkDopTree)
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// kDOP data provider interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE const TkDOPTree<const FMeshBuildDataProvider, uint32>& GetkDOPTree(void) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return kDopTree;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE const FMatrix& GetLocalToWorld(void) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return FMatrix::Identity;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE const FMatrix& GetWorldToLocal(void) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return FMatrix::Identity;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return FMatrix::Identity;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE float GetDeterminant(void) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return 1.0f;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const TkDOPTree<const FMeshBuildDataProvider, uint32>& kDopTree;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FEmbreeTriangleDesc
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int16 ElementIndex;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Mapping between Embree Geometry Id and engine Mesh/LOD Id
 | 
				
			||||||
 | 
					struct FEmbreeGeometry
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TArray<uint32> IndexArray;
 | 
				
			||||||
 | 
						TArray<FVector> VertexArray;
 | 
				
			||||||
 | 
						TArray<FEmbreeTriangleDesc> TriangleDescs; // The material ID of each triangle.
 | 
				
			||||||
 | 
						RTCGeometry InternalGeometry;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FEmbreeScene
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						bool bUseEmbree = false;
 | 
				
			||||||
 | 
						int32 NumIndices = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Embree
 | 
				
			||||||
 | 
						RTCDevice EmbreeDevice = nullptr;
 | 
				
			||||||
 | 
						RTCScene EmbreeScene = nullptr;
 | 
				
			||||||
 | 
						FEmbreeGeometry Geometry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// DOP tree fallback
 | 
				
			||||||
 | 
						TkDOPTree<const FMeshBuildDataProvider, uint32> kDopTree;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if USE_EMBREE
 | 
				
			||||||
 | 
					struct FEmbreeRay : public RTCRayHit
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FEmbreeRay() :
 | 
				
			||||||
 | 
							ElementIndex(-1)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							hit.u = hit.v = 0;
 | 
				
			||||||
 | 
							ray.time = 0;
 | 
				
			||||||
 | 
							ray.mask = 0xFFFFFFFF;
 | 
				
			||||||
 | 
							hit.geomID = RTC_INVALID_GEOMETRY_ID;
 | 
				
			||||||
 | 
							hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
 | 
				
			||||||
 | 
							hit.primID = RTC_INVALID_GEOMETRY_ID;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FVector GetHitNormal() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return FVector(-hit.Ng_x, -hit.Ng_y, -hit.Ng_z).GetSafeNormal();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool IsHitTwoSided() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
 | 
				
			||||||
 | 
							return ElementIndex == 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Additional Outputs.
 | 
				
			||||||
 | 
						int32 ElementIndex; // Material Index
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FEmbreeIntersectionContext : public RTCIntersectContext
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FEmbreeIntersectionContext() :
 | 
				
			||||||
 | 
							ElementIndex(-1)
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool IsHitTwoSided() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
 | 
				
			||||||
 | 
							return ElementIndex == 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Additional Outputs.
 | 
				
			||||||
 | 
						int32 ElementIndex; // Material Index
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace MeshRepresentation
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *	Generates unit length, stratified and uniformly distributed direction samples in a hemisphere.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *	[Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						FMatrix GetTangentBasisFrisvad(FVector TangentZ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void SetupEmbreeScene(FString MeshName,
 | 
				
			||||||
 | 
							const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
							const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
							bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							FEmbreeScene& EmbreeScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,259 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#include "MeshUtilities.h"
 | 
				
			||||||
 | 
					// #include "IAnimationBlueprintEditor.h"
 | 
				
			||||||
 | 
					// #include "IAnimationBlueprintEditorModule.h"
 | 
				
			||||||
 | 
					// #include "IAnimationEditor.h"
 | 
				
			||||||
 | 
					// #include "IAnimationEditorModule.h"
 | 
				
			||||||
 | 
					// #include "ISkeletalMeshEditor.h"
 | 
				
			||||||
 | 
					// #include "ISkeletalMeshEditorModule.h"
 | 
				
			||||||
 | 
					// #include "ISkeletonEditor.h"
 | 
				
			||||||
 | 
					// #include "ISkeletonEditorModule.h"
 | 
				
			||||||
 | 
					#include "Engine/StaticMesh.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/MeshUtilities2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FProcessAsyncTasksTickObject : FTickableGameObject
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						virtual bool IsTickableInEditor() const override { return true; }
 | 
				
			||||||
 | 
						virtual void Tick(float DeltaTime) override;
 | 
				
			||||||
 | 
						virtual TStatId GetStatId() const { return TStatId(); }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FMeshUtilities2 : public IMeshUtilities2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
 | 
				
			||||||
 | 
						// virtual IMeshReduction* GetStaticMeshReductionInterface() override;
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
 | 
				
			||||||
 | 
						// virtual IMeshReduction* GetSkeletalMeshReductionInterface() override;
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
 | 
				
			||||||
 | 
						// virtual IMeshMerging* GetMeshMergingInterface() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
 | 
				
			||||||
 | 
						//virtual void MergeActors(
 | 
				
			||||||
 | 
						//	const TArray<AActor*>& SourceActors,
 | 
				
			||||||
 | 
						//	const FMeshMergingSettings& InSettings,
 | 
				
			||||||
 | 
						//	UPackage* InOuter,
 | 
				
			||||||
 | 
						//	const FString& InBasePackageName,
 | 
				
			||||||
 | 
						//	TArray<UObject*>& OutAssetsToSync,
 | 
				
			||||||
 | 
						//	FVector& OutMergedActorLocation,
 | 
				
			||||||
 | 
						//	bool bSilent = false) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
 | 
				
			||||||
 | 
						//virtual void MergeStaticMeshComponents(
 | 
				
			||||||
 | 
						//	const TArray<UStaticMeshComponent*>& ComponentsToMerge,
 | 
				
			||||||
 | 
						//	UWorld* World,
 | 
				
			||||||
 | 
						//	const FMeshMergingSettings& InSettings,
 | 
				
			||||||
 | 
						//	UPackage* InOuter,
 | 
				
			||||||
 | 
						//	const FString& InBasePackageName,
 | 
				
			||||||
 | 
						//	TArray<UObject*>& OutAssetsToSync,
 | 
				
			||||||
 | 
						//	FVector& OutMergedActorLocation,
 | 
				
			||||||
 | 
						//	const float ScreenSize,
 | 
				
			||||||
 | 
						//	bool bSilent = false) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
 | 
				
			||||||
 | 
						//virtual void CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync,
 | 
				
			||||||
 | 
						//const float ScreenAreaSize = 1.0f) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UE_DEPRECATED(4.17, "Function is removed, use functionality in new MeshMergeUtilities Module")
 | 
				
			||||||
 | 
						//virtual void FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						FProcessAsyncTasksTickObject* TickObject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cached version string. */
 | 
				
			||||||
 | 
						FString VersionString;
 | 
				
			||||||
 | 
						/** True if NvTriStrip is being used for tri order optimization. */
 | 
				
			||||||
 | 
						bool bUsingNvTriStrip;
 | 
				
			||||||
 | 
						/** True if we disable triangle order optimization.  For debugging purposes only */
 | 
				
			||||||
 | 
						bool bDisableTriangleOrderOptimization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// IMeshUtilities interface.
 | 
				
			||||||
 | 
						virtual const FString& GetVersionString() const override
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return VersionString;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void FixupMaterialSlotNames(UStaticMesh* StaticMesh) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void FixupMaterialSlotNames(USkeletalMesh* SkeletalMesh) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual bool BuildStaticMesh(
 | 
				
			||||||
 | 
						// FStaticMeshRenderData& OutRenderData,
 | 
				
			||||||
 | 
						// UStaticMesh* StaticMesh,
 | 
				
			||||||
 | 
						// const FStaticMeshLODGroup& LODGroup
 | 
				
			||||||
 | 
						// ) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void BuildStaticMeshVertexAndIndexBuffers(
 | 
				
			||||||
 | 
							TArray<FStaticMeshBuildVertex>& OutVertices,
 | 
				
			||||||
 | 
							TArray<TArray<uint32>>& OutPerSectionIndices,
 | 
				
			||||||
 | 
							TArray<int32>& OutWedgeMap,
 | 
				
			||||||
 | 
							const FRawMesh& RawMesh,
 | 
				
			||||||
 | 
							const FOverlappingCorners& OverlappingCorners,
 | 
				
			||||||
 | 
							const TMap<uint32, uint32>& MaterialToSectionMapping,
 | 
				
			||||||
 | 
							float ComparisonThreshold,
 | 
				
			||||||
 | 
							FVector BuildScale,
 | 
				
			||||||
 | 
							int32 ImportVersion
 | 
				
			||||||
 | 
						) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual bool GenerateStaticMeshLODs(UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void GenerateSignedDistanceFieldVolumeData(
 | 
				
			||||||
 | 
							FString MeshName,
 | 
				
			||||||
 | 
							const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
							class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
							const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
							const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
							float DistanceFieldResolutionScale,
 | 
				
			||||||
 | 
							bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							FDistanceFieldVolumeData& OutData) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool GenerateCardRepresentationData(
 | 
				
			||||||
 | 
							FString MeshName,
 | 
				
			||||||
 | 
							const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
							class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
							const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
							const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
							const FDistanceFieldVolumeData* DistanceFieldVolumeData,
 | 
				
			||||||
 | 
							bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							class FCardRepresentationData& OutData) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh& OutRawMesh) const override;
 | 
				
			||||||
 | 
						virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, const FOverlappingCorners& InOverlappingCorners, FRawMesh& OutRawMesh) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const override;
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual bool BuildSkeletalMesh(FSkeletalMeshLODModel& LODModel,	const FString& SkeletalMeshName, const FReferenceSkeleton& RefSkeleton, const TArray<SkeletalMeshImportData::FVertInfluence>& Influences, const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges, const TArray<SkeletalMeshImportData::FMeshFace>& Faces, const TArray<FVector>& Points, const TArray<int32>& PointToOriginalMap, const MeshBuildOptions& BuildOptions = MeshBuildOptions(), TArray<FText> * OutWarningMessages = NULL, TArray<FName> * OutWarningNames = NULL) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UE_DEPRECATED(4.24, "Use functionality in FSkeletalMeshUtilityBuilder instead.")
 | 
				
			||||||
 | 
						//bool BuildSkeletalMesh_Legacy(FSkeletalMeshLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, const TArray<SkeletalMeshImportData::FVertInfluence>& Influences, const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges, const TArray<SkeletalMeshImportData::FMeshFace>& Faces, const TArray<FVector>& Points, const TArray<int32>& PointToOriginalMap, const FOverlappingThresholds& OverlappingThresholds, bool bComputeNormals = true, bool bComputeTangents = true, bool bComputeWeightedNormals = true, TArray<FText> * OutWarningMessages = NULL, TArray<FName> * OutWarningNames = NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void CacheOptimizeIndexBuffer(TArray<uint16>& Indices) override;
 | 
				
			||||||
 | 
						virtual void CacheOptimizeIndexBuffer(TArray<uint32>& Indices) override;
 | 
				
			||||||
 | 
						void CacheOptimizeVertexAndIndexBuffer(TArray<FStaticMeshBuildVertex>& Vertices, TArray<TArray<uint32>>& PerSectionIndices, TArray<int32>& WedgeMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void BuildSkeletalAdjacencyIndexBuffer(
 | 
				
			||||||
 | 
							const TArray<FSoftSkinVertex>& VertexBuffer,
 | 
				
			||||||
 | 
							const uint32 TexCoordCount,
 | 
				
			||||||
 | 
							const TArray<uint32>& Indices,
 | 
				
			||||||
 | 
							TArray<uint32>& OutPnAenIndices
 | 
				
			||||||
 | 
						) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *  Calculate The tangent bi normal and normal for the triangle define by the tree SoftSkinVertex.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 *  @note The function will always fill properly the OutTangents array with 3 FVector. If the triangle is degenerated the OutTangent will contain zeroed vectors.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 *  @param VertexA - First triangle vertex.
 | 
				
			||||||
 | 
						 *  @param VertexB - Second triangle vertex.
 | 
				
			||||||
 | 
						 *  @param VertexC - Third triangle vertex.
 | 
				
			||||||
 | 
						 *  @param OutTangents - The function allocate the TArray with 3 FVector, to represent the triangle tangent, bi normal and normal.
 | 
				
			||||||
 | 
						 *  @param CompareThreshold - The threshold use to compare a tangent vector with zero.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalculateTriangleTangent(const FSoftSkinVertex& VertexA, const FSoftSkinVertex& VertexB, const FSoftSkinVertex& VertexC, TArray<FVector>& OutTangents, float CompareThreshold) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void CalcBoneVertInfos(USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo2>& Infos, bool bOnlyDominant) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Convert a set of mesh components in their current pose to a static mesh.
 | 
				
			||||||
 | 
						 * @param	InMeshComponents		The mesh components we want to convert
 | 
				
			||||||
 | 
						 * @param	InRootTransform			The transform of the root of the mesh we want to output
 | 
				
			||||||
 | 
						 * @param	InPackageName			The package name to create the static mesh in. If this is empty then a dialog will be displayed to pick the mesh.
 | 
				
			||||||
 | 
						 * @return a new static mesh (specified by the user)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual UStaticMesh* ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform = FTransform::Identity, const FString& InPackageName = FString()) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* Builds a renderable skeletal mesh LOD model. Note that the array of chunks
 | 
				
			||||||
 | 
						* will be destroyed during this process!
 | 
				
			||||||
 | 
						* @param LODModel				Upon return contains a renderable skeletal mesh LOD model.
 | 
				
			||||||
 | 
						* @param RefSkeleton			The reference skeleton associated with the model.
 | 
				
			||||||
 | 
						* @param Chunks				Skinned mesh chunks from which to build the renderable model.
 | 
				
			||||||
 | 
						* @param PointToOriginalMap	Maps a vertex's RawPointIdx to its index at import time.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						//void BuildSkeletalModelFromChunks(FSkeletalMeshLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, TArray<FSkinnedMeshChunk*>& Chunks, const TArray<int32>& PointToOriginalMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, float ComparisonThreshold) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, FRawMesh const& RawMesh, float ComparisonThreshold) const;
 | 
				
			||||||
 | 
						// IModuleInterface interface.
 | 
				
			||||||
 | 
						virtual void StartupModule() override;
 | 
				
			||||||
 | 
						virtual void ShutdownModule() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void ExtractMeshDataForGeometryCache(FRawMesh& RawMesh, const FMeshBuildSettings& BuildSettings, TArray<FStaticMeshBuildVertex>& OutVertices, TArray<TArray<uint32>>& OutPerSectionIndices, int32 ImportVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual void CalculateTextureCoordinateBoundsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, TArray<FBox2D>& OutBounds) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void CalculateTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutTangentX,
 | 
				
			||||||
 | 
						                               TArray<FVector>& OutTangentY, TArray<FVector>& OutNormals) const override;
 | 
				
			||||||
 | 
						virtual void CalculateMikkTSpaceTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<FVector>& InNormals, bool bIgnoreDegenerateTriangles, TArray<FVector>& OutTangentX,
 | 
				
			||||||
 | 
						                                         TArray<FVector>& OutTangentY) const override;
 | 
				
			||||||
 | 
						//virtual void CalculateNormals(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutNormals) const override;
 | 
				
			||||||
 | 
						virtual void CalculateOverlappingCorners(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, bool bIgnoreDegenerateTriangles, FOverlappingCorners& OutOverlappingCorners) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//virtual void GenerateRuntimeSkinWeightData(const FSkeletalMeshLODModel* ImportedModel, const TArray<FRawSkinWeight>& InRawSkinWeights, FRuntimeSkinWeightProfileData& InOutSkinWeightOverrideData) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void RegisterMenus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Need to call some members from this class, (which is internal to this module)
 | 
				
			||||||
 | 
						friend class FStaticMeshUtilityBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						void AddAnimationBlueprintEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void RemoveAnimationBlueprintEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//TSharedRef<FExtender> GetAnimationBlueprintEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationBlueprintEditor> InAnimationBlueprintEditor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void AddAnimationEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void RemoveAnimationEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//TSharedRef<FExtender> GetAnimationEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationEditor> InAnimationEditor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//TSharedRef<FExtender> GetSkeletalMeshEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletalMeshEditor> InSkeletalMeshEditor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void AddSkeletonEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void RemoveSkeletonEditorToolbarExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//TSharedRef<FExtender> GetSkeletonEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletonEditor> InSkeletonEditor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void HandleAddSkeletalMeshActionExtenderToToolbar(FToolBarBuilder& ParentToolbarBuilder, UMeshComponent* MeshComponent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void AddLevelViewportMenuExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void RemoveLevelViewportMenuExtender();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TSharedRef<FExtender> GetLevelViewportContextMenuExtender(const TSharedRef<FUICommandList> CommandList, const TArray<AActor*> InActors);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void ConvertActorMeshesToStaticMeshUIAction(const TArray<AActor*> InActors);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDelegateHandle ModuleLoadedDelegateHandle;
 | 
				
			||||||
 | 
						FDelegateHandle LevelViewportExtenderHandle;
 | 
				
			||||||
 | 
						FDelegateHandle AnimationBlueprintEditorExtenderHandle;
 | 
				
			||||||
 | 
						FDelegateHandle AnimationEditorExtenderHandle;
 | 
				
			||||||
 | 
						FDelegateHandle SkeletonEditorExtenderHandle;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DECLARE_LOG_CATEGORY_EXTERN(LogMeshUtilities, Verbose, All);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace MeshUtilities
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/** Generates unit length, stratified and uniformly distributed direction samples in a hemisphere. */
 | 
				
			||||||
 | 
						void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*=============================================================================
 | 
				
			||||||
 | 
						DistanceFieldAtlas.h
 | 
				
			||||||
 | 
					=============================================================================*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Containers/LockFreeList.h"
 | 
				
			||||||
 | 
					#include "ProfilingDebugging/ResourceSize.h"
 | 
				
			||||||
 | 
					#include "Engine/EngineTypes.h"
 | 
				
			||||||
 | 
					#include "UObject/GCObject.h"
 | 
				
			||||||
 | 
					#include "Templates/UniquePtr.h"
 | 
				
			||||||
 | 
					#include "DerivedMeshDataTaskUtils.h"
 | 
				
			||||||
 | 
					#include "Async/AsyncWork.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MESHUTILITIES2_API FSignedDistanceFieldBuildMaterialData2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						EBlendMode BlendMode;
 | 
				
			||||||
 | 
						bool bTwoSided;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UStaticMesh;
 | 
				
			||||||
 | 
					class UTexture2D;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <class T>
 | 
				
			||||||
 | 
					class TLockFreePointerListLIFO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Change DDC key when modifying these (or any DF encoding logic)
 | 
				
			||||||
 | 
					namespace DistanceField2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// One voxel border around object for handling gradient
 | 
				
			||||||
 | 
						constexpr int32 MeshDistanceFieldObjectBorder = 1;
 | 
				
			||||||
 | 
						constexpr int32 UniqueDataBrickSize = 7;
 | 
				
			||||||
 | 
						// Half voxel border around brick for trilinear filtering
 | 
				
			||||||
 | 
						constexpr int32 BrickSize = 8;
 | 
				
			||||||
 | 
						// Trade off between SDF memory and number of steps required to find intersection
 | 
				
			||||||
 | 
						constexpr int32 BandSizeInVoxels = 4;
 | 
				
			||||||
 | 
						constexpr int32 NumMips = 3;
 | 
				
			||||||
 | 
						constexpr uint32 InvalidBrickIndex = 0xFFFFFFFF;
 | 
				
			||||||
 | 
						constexpr EPixelFormat DistanceFieldFormat = PF_G8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Must match LoadDFAssetData
 | 
				
			||||||
 | 
						constexpr uint32 MaxIndirectionDimension = 1024;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FSparseDistanceFieldMip2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FSparseDistanceFieldMip2() :
 | 
				
			||||||
 | 
							IndirectionDimensions(FIntVector::ZeroValue),
 | 
				
			||||||
 | 
							NumDistanceFieldBricks(0),
 | 
				
			||||||
 | 
							VolumeToVirtualUVScale(FVector::ZeroVector),
 | 
				
			||||||
 | 
							VolumeToVirtualUVAdd(FVector::ZeroVector),
 | 
				
			||||||
 | 
							DistanceFieldToVolumeScaleBias(FVector2D::ZeroVector),
 | 
				
			||||||
 | 
							BulkOffset(0),
 | 
				
			||||||
 | 
							BulkSize(0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FIntVector IndirectionDimensions;
 | 
				
			||||||
 | 
						int32 NumDistanceFieldBricks;
 | 
				
			||||||
 | 
						FVector VolumeToVirtualUVScale;
 | 
				
			||||||
 | 
						FVector VolumeToVirtualUVAdd;
 | 
				
			||||||
 | 
						FVector2D DistanceFieldToVolumeScaleBias;
 | 
				
			||||||
 | 
						uint32 BulkOffset;
 | 
				
			||||||
 | 
						uint32 BulkSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						friend FArchive& operator<<(FArchive& Ar, FSparseDistanceFieldMip2& Mip)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Ar << Mip.IndirectionDimensions << Mip.NumDistanceFieldBricks << Mip.VolumeToVirtualUVScale << Mip.VolumeToVirtualUVAdd << Mip.DistanceFieldToVolumeScaleBias << Mip.BulkOffset << Mip.BulkSize;
 | 
				
			||||||
 | 
							return Ar;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SIZE_T GetResourceSizeBytes() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FResourceSizeEx ResSize;
 | 
				
			||||||
 | 
							GetResourceSizeEx(ResSize);
 | 
				
			||||||
 | 
							return ResSize.GetTotalMemoryBytes();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							CumulativeResourceSize.AddDedicatedSystemMemoryBytes(sizeof(*this));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FAsyncDistanceFieldTask2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FAsyncDistanceFieldTaskWorker2 : public FNonAbandonableTask
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FAsyncDistanceFieldTaskWorker2(FAsyncDistanceFieldTask2& InTask)
 | 
				
			||||||
 | 
							: Task(InTask)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE TStatId GetStatId() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncDistanceFieldTaskWorker2, STATGROUP_ThreadPoolAsyncTasks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void DoWork();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						FAsyncDistanceFieldTask2& Task;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** A task to build a distance field for a single mesh */
 | 
				
			||||||
 | 
					class MESHUTILITIES2_API FAsyncDistanceFieldTask2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FAsyncDistanceFieldTask2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						TArray<FSignedDistanceFieldBuildMaterialData2> MaterialBlendModes;
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
						FSourceMeshDataForDerivedDataTask SourceMeshData;
 | 
				
			||||||
 | 
						UStaticMesh* StaticMesh;
 | 
				
			||||||
 | 
						UStaticMesh* GenerateSource;
 | 
				
			||||||
 | 
						float DistanceFieldResolutionScale;
 | 
				
			||||||
 | 
						bool bGenerateDistanceFieldAsIfTwoSided;
 | 
				
			||||||
 | 
						const ITargetPlatform* TargetPlatform;
 | 
				
			||||||
 | 
						FString DDCKey;
 | 
				
			||||||
 | 
						FDistanceFieldVolumeData* GeneratedVolumeData;
 | 
				
			||||||
 | 
						TUniquePtr<FAsyncTask<FAsyncDistanceFieldTaskWorker2>> AsyncTask = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Class that manages asynchronous building of mesh distance fields. */
 | 
				
			||||||
 | 
					class MESHUTILITIES2_API FDistanceFieldAsyncQueue2 : public FGCObject
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FDistanceFieldAsyncQueue2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual ~FDistanceFieldAsyncQueue2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Adds a new build task. (Thread-Safe) */
 | 
				
			||||||
 | 
						void AddTask(FAsyncDistanceFieldTask2* Task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cancel the build on this specific static mesh or block until it is completed if already started. */
 | 
				
			||||||
 | 
						void CancelBuild(UStaticMesh* StaticMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until the async build are either cancelled or completed. */
 | 
				
			||||||
 | 
						void CancelAllOutstandingBuilds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until the async build of the specified mesh is complete. */
 | 
				
			||||||
 | 
						void BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until all async builds complete. */
 | 
				
			||||||
 | 
						void BlockUntilAllBuildsComplete();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Called once per frame, fetches completed tasks and applies them to the scene. */
 | 
				
			||||||
 | 
						void ProcessAsyncTasks(bool bLimitExecutionTime = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Exposes UObject references used by the async build. */
 | 
				
			||||||
 | 
						virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Returns name of class for reference tracking */
 | 
				
			||||||
 | 
						virtual FString GetReferencerName() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks until it is safe to shut down (worker threads are idle). */
 | 
				
			||||||
 | 
						void Shutdown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32 GetNumOutstandingTasks() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							return ReferencedTasks.Num();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						friend FAsyncDistanceFieldTaskWorker2;
 | 
				
			||||||
 | 
						void ProcessPendingTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TUniquePtr<FQueuedThreadPool> ThreadPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Builds a single task with the given threadpool.  Called from the worker thread. */
 | 
				
			||||||
 | 
						void Build(FAsyncDistanceFieldTask2* Task, class FQueuedThreadPool& ThreadPool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Change the priority of the background task. */
 | 
				
			||||||
 | 
						void RescheduleBackgroundTask(FAsyncDistanceFieldTask2* InTask, EQueuedWorkPriority InPriority);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Task will be sent to a background worker. */
 | 
				
			||||||
 | 
						void StartBackgroundTask(FAsyncDistanceFieldTask2* Task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cancel or finish any background work for the given task. */
 | 
				
			||||||
 | 
						void CancelBackgroundTask(TArray<FAsyncDistanceFieldTask2*> Tasks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Game-thread managed list of tasks in the async system. */
 | 
				
			||||||
 | 
						TArray<FAsyncDistanceFieldTask2*> ReferencedTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Tasks that are waiting on static mesh compilation to proceed */
 | 
				
			||||||
 | 
						TArray<FAsyncDistanceFieldTask2*> PendingTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Tasks that have completed processing. */
 | 
				
			||||||
 | 
						// consider changing this from FIFO to Unordered, which may be faster
 | 
				
			||||||
 | 
						TLockFreePointerListLIFO<FAsyncDistanceFieldTask2> CompletedTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						class IMeshUtilities2* MeshUtilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutable FCriticalSection CriticalSection;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Global build queue. */
 | 
				
			||||||
 | 
					extern MESHUTILITIES2_API FDistanceFieldAsyncQueue2* GDistanceFieldAsyncQueue2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern MESHUTILITIES2_API FString BuildDistanceFieldDerivedDataKey2(const FString& InMeshKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern MESHUTILITIES2_API void BuildMeshDistanceField(UStaticMesh* StaticMesh);
 | 
				
			||||||
 | 
					extern MESHUTILITIES2_API void BuildMeshCardRepresentation(UStaticMesh* StaticMeshAsset, class FStaticMeshRenderData& RenderData, FSourceMeshDataForDerivedDataTask* OptionalSourceMeshData);
 | 
				
			||||||
@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*=============================================================================
 | 
				
			||||||
 | 
						MeshCardRepresentation.h
 | 
				
			||||||
 | 
					=============================================================================*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Containers/LockFreeList.h"
 | 
				
			||||||
 | 
					#include "UObject/GCObject.h"
 | 
				
			||||||
 | 
					#include "Templates/UniquePtr.h"
 | 
				
			||||||
 | 
					#include "DerivedMeshDataTaskUtils.h"
 | 
				
			||||||
 | 
					#include "DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					#include "Async/AsyncWork.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <class T>
 | 
				
			||||||
 | 
					class TLockFreePointerListLIFO;
 | 
				
			||||||
 | 
					class FSignedDistanceFieldBuildMaterialData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FAsyncCardRepresentationTask2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FAsyncCardRepresentationTaskWorker2 : public FNonAbandonableTask
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FAsyncCardRepresentationTaskWorker2(FAsyncCardRepresentationTask2& InTask)
 | 
				
			||||||
 | 
							: Task(InTask)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FORCEINLINE TStatId GetStatId() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncCardRepresentationTaskWorker2, STATGROUP_ThreadPoolAsyncTasks);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void DoWork();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						FAsyncCardRepresentationTask2& Task;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FAsyncCardRepresentationTask2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						bool bSuccess = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// #if WITH_EDITOR
 | 
				
			||||||
 | 
						TArray<FSignedDistanceFieldBuildMaterialData2> MaterialBlendModes;
 | 
				
			||||||
 | 
						// #endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FSourceMeshDataForDerivedDataTask SourceMeshData;
 | 
				
			||||||
 | 
						bool bGenerateDistanceFieldAsIfTwoSided = false;
 | 
				
			||||||
 | 
						UStaticMesh* StaticMesh = nullptr;
 | 
				
			||||||
 | 
						UStaticMesh* GenerateSource = nullptr;
 | 
				
			||||||
 | 
						FString DDCKey;
 | 
				
			||||||
 | 
						FCardRepresentationData* GeneratedCardRepresentation;
 | 
				
			||||||
 | 
						TUniquePtr<FAsyncTask<FAsyncCardRepresentationTaskWorker2>> AsyncTask = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Class that manages asynchronous building of mesh distance fields. */
 | 
				
			||||||
 | 
					class MESHUTILITIES2_API FCardRepresentationAsyncQueue2 : public FGCObject
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						FCardRepresentationAsyncQueue2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual ~FCardRepresentationAsyncQueue2() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Adds a new build task. */
 | 
				
			||||||
 | 
						void AddTask(FAsyncCardRepresentationTask2* Task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cancel the build on this specific static mesh or block until it is completed if already started. */
 | 
				
			||||||
 | 
						void CancelBuild(UStaticMesh* StaticMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until the async build are either cancelled or completed. */
 | 
				
			||||||
 | 
						void CancelAllOutstandingBuilds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until the async build of the specified mesh is complete. */
 | 
				
			||||||
 | 
						void BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks the main thread until all async builds complete. */
 | 
				
			||||||
 | 
						void BlockUntilAllBuildsComplete();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Called once per frame, fetches completed tasks and applies them to the scene. */
 | 
				
			||||||
 | 
						void ProcessAsyncTasks(bool bLimitExecutionTime = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Exposes UObject references used by the async build. */
 | 
				
			||||||
 | 
						void AddReferencedObjects(FReferenceCollector& Collector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual FString GetReferencerName() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Blocks until it is safe to shut down (worker threads are idle). */
 | 
				
			||||||
 | 
						void Shutdown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32 GetNumOutstandingTasks() const
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FScopeLock Lock(&CriticalSection);
 | 
				
			||||||
 | 
							return ReferencedTasks.Num();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						friend FAsyncCardRepresentationTaskWorker2;
 | 
				
			||||||
 | 
						void ProcessPendingTasks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TUniquePtr<FQueuedThreadPool> ThreadPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Builds a single task with the given threadpool.  Called from the worker thread. */
 | 
				
			||||||
 | 
						void Build(FAsyncCardRepresentationTask2* Task, class FQueuedThreadPool& ThreadPool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Change the priority of the background task. */
 | 
				
			||||||
 | 
						void RescheduleBackgroundTask(FAsyncCardRepresentationTask2* InTask, EQueuedWorkPriority InPriority);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Task will be sent to a background worker. */
 | 
				
			||||||
 | 
						void StartBackgroundTask(FAsyncCardRepresentationTask2* Task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cancel or finish any background work for the given task. */
 | 
				
			||||||
 | 
						void CancelBackgroundTask(TArray<FAsyncCardRepresentationTask2*> Tasks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Game-thread managed list of tasks in the async system. */
 | 
				
			||||||
 | 
						TArray<FAsyncCardRepresentationTask2*> ReferencedTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Tasks that are waiting on static mesh compilation to proceed */
 | 
				
			||||||
 | 
						TArray<FAsyncCardRepresentationTask2*> PendingTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Tasks that have completed processing. */
 | 
				
			||||||
 | 
						TLockFreePointerListLIFO<FAsyncCardRepresentationTask2> CompletedTasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						class IMeshUtilities2* MeshUtilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutable FCriticalSection CriticalSection;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Global build queue. */
 | 
				
			||||||
 | 
					extern MESHUTILITIES2_API FCardRepresentationAsyncQueue2* GCardRepresentationAsyncQueue2;
 | 
				
			||||||
@@ -0,0 +1,410 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Modules/ModuleInterface.h"
 | 
				
			||||||
 | 
					#include "Components.h"
 | 
				
			||||||
 | 
					#include "Engine/MeshMerging.h"
 | 
				
			||||||
 | 
					// #include "SkelImport.h"
 | 
				
			||||||
 | 
					#include "DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					#include "MeshBuild.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IMeshMergeUtilities.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UMeshComponent;
 | 
				
			||||||
 | 
					class USkeletalMesh;
 | 
				
			||||||
 | 
					class UStaticMesh;
 | 
				
			||||||
 | 
					class UStaticMeshComponent;
 | 
				
			||||||
 | 
					struct FFlattenMaterial;
 | 
				
			||||||
 | 
					struct FRawMesh;
 | 
				
			||||||
 | 
					struct FStaticMeshLODResources;
 | 
				
			||||||
 | 
					class FSourceMeshDataForDerivedDataTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef FIntPoint FMeshIdAndLOD;
 | 
				
			||||||
 | 
					struct FFlattenMaterial;
 | 
				
			||||||
 | 
					struct FReferenceSkeleton;
 | 
				
			||||||
 | 
					struct FStaticMeshLODResources;
 | 
				
			||||||
 | 
					class UMeshComponent;
 | 
				
			||||||
 | 
					class UStaticMesh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ETangentOptions2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						enum Type
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							None = 0,
 | 
				
			||||||
 | 
							BlendOverlappingNormals = 0x1,
 | 
				
			||||||
 | 
							IgnoreDegenerateTriangles = 0x2,
 | 
				
			||||||
 | 
							UseMikkTSpace = 0x4,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					*	Contains the vertices that are most dominated by that bone. Vertices are in Bone space.
 | 
				
			||||||
 | 
					*	Not used at runtime, but useful for fitting physics assets etc.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					struct FBoneVertInfo2
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Invariant: Arrays should be same length!
 | 
				
			||||||
 | 
						TArray<FVector>	Positions;
 | 
				
			||||||
 | 
						TArray<FVector>	Normals;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct FOverlappingCorners;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IMeshUtilities2 : public IModuleInterface
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/************************************************************************/
 | 
				
			||||||
 | 
						/*  DEPRECATED FUNCTIONALITY                                            */
 | 
				
			||||||
 | 
						/************************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* Harvest static mesh components from input actors
 | 
				
			||||||
 | 
						* and merge into signle mesh grouping them by unique materials
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param SourceActors				List of actors to merge
 | 
				
			||||||
 | 
						* @param InSettings				Settings to use
 | 
				
			||||||
 | 
						* @param InOuter					Outer if required
 | 
				
			||||||
 | 
						* @param InBasePackageName			Destination package name for a generated assets. Used if Outer is null.
 | 
				
			||||||
 | 
						* @param UseLOD					-1 if you'd like to build for all LODs. If you specify, that LOD mesh for source meshes will be used to merge the mesh
 | 
				
			||||||
 | 
						*									This is used by hierarchical building LODs
 | 
				
			||||||
 | 
						* @param OutAssetsToSync			Merged mesh assets
 | 
				
			||||||
 | 
						* @param OutMergedActorLocation	World position of merged mesh
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// virtual void MergeActors(
 | 
				
			||||||
 | 
						// 	const TArray<AActor*>& SourceActors,
 | 
				
			||||||
 | 
						// 	const FMeshMergingSettings& InSettings,
 | 
				
			||||||
 | 
						// 	UPackage* InOuter,
 | 
				
			||||||
 | 
						// 	const FString& InBasePackageName,
 | 
				
			||||||
 | 
						// 	TArray<UObject*>& OutAssetsToSync,
 | 
				
			||||||
 | 
						// 	FVector& OutMergedActorLocation,
 | 
				
			||||||
 | 
						// 	bool bSilent=false) const = 0;
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* MergeStaticMeshComponents
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param ComponentsToMerge - Components to merge
 | 
				
			||||||
 | 
						* @param World - World in which the component reside
 | 
				
			||||||
 | 
						* @param InSettings	- Settings to use
 | 
				
			||||||
 | 
						* @param InOuter - Outer if required
 | 
				
			||||||
 | 
						* @param InBasePackageName - Destination package name for a generated assets. Used if Outer is null.
 | 
				
			||||||
 | 
						* @param UseLOD	-1 if you'd like to build for all LODs. If you specify, that LOD mesh for source meshes will be used to merge the mesh
 | 
				
			||||||
 | 
						*									This is used by hierarchical building LODs
 | 
				
			||||||
 | 
						* @param OutAssetsToSync Merged mesh assets
 | 
				
			||||||
 | 
						* @param OutMergedActorLocation	World position of merged mesh
 | 
				
			||||||
 | 
						* @param ViewDistance Distance for LOD determination
 | 
				
			||||||
 | 
						* @param bSilent Non-verbose flag
 | 
				
			||||||
 | 
						* @return void
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						// virtual void MergeStaticMeshComponents(
 | 
				
			||||||
 | 
						// 	const TArray<UStaticMeshComponent*>& ComponentsToMerge,
 | 
				
			||||||
 | 
						// 	UWorld* World,
 | 
				
			||||||
 | 
						// 	const FMeshMergingSettings& InSettings,
 | 
				
			||||||
 | 
						// 	UPackage* InOuter,
 | 
				
			||||||
 | 
						// 	const FString& InBasePackageName,
 | 
				
			||||||
 | 
						// 	TArray<UObject*>& OutAssetsToSync,
 | 
				
			||||||
 | 
						// 	FVector& OutMergedActorLocation,
 | 
				
			||||||
 | 
						// 	const float ScreenAreaSize,
 | 
				
			||||||
 | 
						// 	bool bSilent /*= false*/) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* Creates a (proxy)-mesh combining the static mesh components from the given list of actors (at the moment this requires having Simplygon)
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param InActors - List of Actors to merge
 | 
				
			||||||
 | 
						* @param InMeshProxySettings - Merge settings
 | 
				
			||||||
 | 
						* @param InOuter - Package for a generated assets, if NULL new packages will be created for each asset
 | 
				
			||||||
 | 
						* @param InProxyBasePackageName - Will be used for naming generated assets, in case InOuter is not specified ProxyBasePackageName will be used as long package name for creating new packages
 | 
				
			||||||
 | 
						* @param InGuid - Guid identifying the data used for this proxy job
 | 
				
			||||||
 | 
						* @param InProxyCreatedDelegate - Delegate callback for when the proxy is finished
 | 
				
			||||||
 | 
						* @param bAllowAsync - Flag whether or not this call could be run async (SimplygonSwarm)
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						// virtual void CreateProxyMesh(const TArray<class AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync = false, const float ScreenAreaSize = 1.0f) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* FlattenMaterialsWithMeshData
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param InMaterials - List of unique materials used by InSourceMeshes
 | 
				
			||||||
 | 
						* @param InSourceMeshes - List of raw meshes used to flatten the materials with (vertex data)
 | 
				
			||||||
 | 
						* @param InMaterialIndexMap - Map used for mapping the raw meshes to the correct materials
 | 
				
			||||||
 | 
						* @param InMeshShouldBakeVertexData - Array of flags to determine whether or not a mesh requires to have its vertex data baked down
 | 
				
			||||||
 | 
						* @param InMaterialProxySettings - Settings for creating the flattened material
 | 
				
			||||||
 | 
						* @param OutFlattenedMaterials - List of flattened materials (one for each mesh)
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						//virtual	void FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<struct FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						* Calculates (new) non-overlapping UV coordinates for the given Raw Mesh
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param RawMesh - Raw Mesh to generate UV coordinates for
 | 
				
			||||||
 | 
						* @param TextureResolution - Texture resolution to take into account while generating the UVs
 | 
				
			||||||
 | 
						* @param bMergeIdenticalMaterials - Whether faces with identical materials can be treated as one in the resulting set of unique UVs
 | 
				
			||||||
 | 
						* @param OutTexCoords - New set of UV coordinates
 | 
				
			||||||
 | 
						* @return bool - whether or not generating the UVs succeeded
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const = 0;
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// /** Returns the mesh reduction plugin if available. */
 | 
				
			||||||
 | 
						// virtual IMeshReduction* GetStaticMeshReductionInterface() = 0;
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// /** Returns the mesh reduction plugin if available. */
 | 
				
			||||||
 | 
						// virtual IMeshReduction* GetSkeletalMeshReductionInterface() = 0;
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// /** Returns the mesh merging plugin if available. */
 | 
				
			||||||
 | 
						// virtual IMeshMerging* GetMeshMergingInterface() = 0;
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/** Returns a string uniquely identifying this version of mesh utilities. */
 | 
				
			||||||
 | 
						virtual const FString& GetVersionString() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Used to make sure all imported material slot name are unique and non empty.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param StaticMesh
 | 
				
			||||||
 | 
						 * @param bForceUniqueSlotName	If true, make sure all slot names are unique as well.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void FixupMaterialSlotNames(UStaticMesh* StaticMesh) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Used to make sure all imported material slot name are unique and non empty.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param SkeletalMesh
 | 
				
			||||||
 | 
						 * @param bForceUniqueSlotName	If true, make sure all slot names are unique as well.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void FixupMaterialSlotNames(USkeletalMesh* SkeletalMesh) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a renderable static mesh using the provided source models and the LOD groups settings.
 | 
				
			||||||
 | 
						 * @returns true if the renderable mesh was built successfully.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						//virtual bool BuildStaticMesh(
 | 
				
			||||||
 | 
							//class FStaticMeshRenderData& OutRenderData,
 | 
				
			||||||
 | 
							//UStaticMesh* StaticMesh,
 | 
				
			||||||
 | 
							//const class FStaticMeshLODGroup& LODGroup
 | 
				
			||||||
 | 
							//) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void BuildStaticMeshVertexAndIndexBuffers(
 | 
				
			||||||
 | 
							TArray<FStaticMeshBuildVertex>& OutVertices,
 | 
				
			||||||
 | 
							TArray<TArray<uint32> >& OutPerSectionIndices,
 | 
				
			||||||
 | 
							TArray<int32>& OutWedgeMap,
 | 
				
			||||||
 | 
							const FRawMesh& RawMesh,
 | 
				
			||||||
 | 
							const FOverlappingCorners& OverlappingCorners,
 | 
				
			||||||
 | 
							const TMap<uint32, uint32>& MaterialToSectionMapping,
 | 
				
			||||||
 | 
							float ComparisonThreshold,
 | 
				
			||||||
 | 
							FVector BuildScale,
 | 
				
			||||||
 | 
							int32 ImportVersion
 | 
				
			||||||
 | 
							) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a static mesh using the provided source models and the LOD groups settings, and replaces
 | 
				
			||||||
 | 
						 * the RawMeshes with the reduced meshes. Does not modify renderable data.
 | 
				
			||||||
 | 
						 * @returns true if the meshes were built successfully.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						// virtual bool GenerateStaticMeshLODs(
 | 
				
			||||||
 | 
						// 	UStaticMesh* StaticMesh,
 | 
				
			||||||
 | 
						// 	const class FStaticMeshLODGroup& LODGroup
 | 
				
			||||||
 | 
						// 	) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Builds a signed distance field volume for the given LODModel. */
 | 
				
			||||||
 | 
						virtual void GenerateSignedDistanceFieldVolumeData(
 | 
				
			||||||
 | 
							FString MeshName,
 | 
				
			||||||
 | 
							const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
							class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
							const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
							const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
							float DistanceFieldResolutionScale,
 | 
				
			||||||
 | 
							bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							class FDistanceFieldVolumeData& OutData) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual bool GenerateCardRepresentationData(
 | 
				
			||||||
 | 
							FString MeshName,
 | 
				
			||||||
 | 
							const FSourceMeshDataForDerivedDataTask& SourceMeshData,
 | 
				
			||||||
 | 
							const FStaticMeshLODResources& LODModel,
 | 
				
			||||||
 | 
							class FQueuedThreadPool& ThreadPool,
 | 
				
			||||||
 | 
							const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
 | 
				
			||||||
 | 
							const FBoxSphereBounds& Bounds,
 | 
				
			||||||
 | 
							const class FDistanceFieldVolumeData* DistanceFieldVolumeData,
 | 
				
			||||||
 | 
							bool bGenerateAsIfTwoSided,
 | 
				
			||||||
 | 
							class FCardRepresentationData& OutData) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Helper structure for skeletal mesh import options */
 | 
				
			||||||
 | 
						struct MeshBuildOptions
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshBuildOptions()
 | 
				
			||||||
 | 
							: bRemoveDegenerateTriangles(false)
 | 
				
			||||||
 | 
							, bComputeNormals(true)
 | 
				
			||||||
 | 
							, bComputeTangents(true)
 | 
				
			||||||
 | 
							, bUseMikkTSpace(false)
 | 
				
			||||||
 | 
							, bComputeWeightedNormals(false)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bRemoveDegenerateTriangles;
 | 
				
			||||||
 | 
							bool bComputeNormals;
 | 
				
			||||||
 | 
							bool bComputeTangents;
 | 
				
			||||||
 | 
							bool bUseMikkTSpace;
 | 
				
			||||||
 | 
							bool bComputeWeightedNormals;
 | 
				
			||||||
 | 
							FOverlappingThresholds OverlappingThresholds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void FillOptions(const FSkeletalMeshBuildSettings& SkeletalMeshBuildSettings)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								OverlappingThresholds.ThresholdPosition = SkeletalMeshBuildSettings.ThresholdPosition;
 | 
				
			||||||
 | 
								OverlappingThresholds.ThresholdTangentNormal = SkeletalMeshBuildSettings.ThresholdTangentNormal;
 | 
				
			||||||
 | 
								OverlappingThresholds.ThresholdUV = SkeletalMeshBuildSettings.ThresholdUV;
 | 
				
			||||||
 | 
								OverlappingThresholds.MorphThresholdPosition = SkeletalMeshBuildSettings.MorphThresholdPosition;
 | 
				
			||||||
 | 
								bComputeNormals = SkeletalMeshBuildSettings.bRecomputeNormals;
 | 
				
			||||||
 | 
								bComputeTangents = SkeletalMeshBuildSettings.bRecomputeTangents;
 | 
				
			||||||
 | 
								bUseMikkTSpace = SkeletalMeshBuildSettings.bUseMikkTSpace;
 | 
				
			||||||
 | 
								bComputeWeightedNormals = SkeletalMeshBuildSettings.bComputeWeightedNormals;
 | 
				
			||||||
 | 
								bRemoveDegenerateTriangles = SkeletalMeshBuildSettings.bRemoveDegenerates;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// /**
 | 
				
			||||||
 | 
						//  * Create all render specific data for a skeletal mesh LOD model
 | 
				
			||||||
 | 
						//  * @returns true if the mesh was built successfully.
 | 
				
			||||||
 | 
						//  */
 | 
				
			||||||
 | 
						// virtual bool BuildSkeletalMesh(
 | 
				
			||||||
 | 
						// 	FSkeletalMeshLODModel& LODModel,
 | 
				
			||||||
 | 
						// 	const FString& SkeletalMeshName,
 | 
				
			||||||
 | 
						// 	const FReferenceSkeleton& RefSkeleton,
 | 
				
			||||||
 | 
						// 	const TArray<SkeletalMeshImportData::FVertInfluence>& Influences,
 | 
				
			||||||
 | 
						// 	const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges,
 | 
				
			||||||
 | 
						// 	const TArray<SkeletalMeshImportData::FMeshFace>& Faces,
 | 
				
			||||||
 | 
						// 	const TArray<FVector>& Points,
 | 
				
			||||||
 | 
						// 	const TArray<int32>& PointToOriginalMap,
 | 
				
			||||||
 | 
						// 	const MeshBuildOptions& BuildOptions = MeshBuildOptions(),
 | 
				
			||||||
 | 
						// 	TArray<FText> * OutWarningMessages = NULL,
 | 
				
			||||||
 | 
						// 	TArray<FName> * OutWarningNames = NULL
 | 
				
			||||||
 | 
						// 	) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cache optimize the index buffer. */
 | 
				
			||||||
 | 
						virtual void CacheOptimizeIndexBuffer(TArray<uint16>& Indices) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Cache optimize the index buffer. */
 | 
				
			||||||
 | 
						virtual void CacheOptimizeIndexBuffer(TArray<uint32>& Indices) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Build adjacency information for the skeletal mesh used for tessellation. */
 | 
				
			||||||
 | 
						virtual void BuildSkeletalAdjacencyIndexBuffer(
 | 
				
			||||||
 | 
							const TArray<struct FSoftSkinVertex>& VertexBuffer,
 | 
				
			||||||
 | 
							const uint32 TexCoordCount,
 | 
				
			||||||
 | 
							const TArray<uint32>& Indices,
 | 
				
			||||||
 | 
							TArray<uint32>& OutPnAenIndices
 | 
				
			||||||
 | 
							) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *  Calculate The tangent, bi normal and normal for the triangle define by the tree SoftSkinVertex.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 *  @note The function will always fill properly the OutTangents array with 3 FVector. If the triangle is degenerated the OutTangent will contain zeroed vectors.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 *  @param VertexA - First triangle vertex.
 | 
				
			||||||
 | 
						 *  @param VertexB - Second triangle vertex.
 | 
				
			||||||
 | 
						 *  @param VertexC - Third triangle vertex.
 | 
				
			||||||
 | 
						 *  @param OutTangents - The function allocate the TArray with 3 FVector, to represent the triangle tangent, bi normal and normal.
 | 
				
			||||||
 | 
						 *  @param CompareThreshold - The threshold use to compare a tangent vector with zero.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalculateTriangleTangent(const FSoftSkinVertex& VertexA, const FSoftSkinVertex& VertexB, const FSoftSkinVertex& VertexC, TArray<FVector>& OutTangents, float CompareThreshold) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *	Calculate the verts associated weighted to each bone of the skeleton.
 | 
				
			||||||
 | 
						 *	The vertices returned are in the local space of the bone.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 *	@param	SkeletalMesh	The target skeletal mesh.
 | 
				
			||||||
 | 
						 *	@param	Infos			The output array of vertices associated with each bone.
 | 
				
			||||||
 | 
						 *	@param	bOnlyDominant	Controls whether a vertex is added to the info for a bone if it is most controlled by that bone, or if that bone has ANY influence on that vert.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalcBoneVertInfos( USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo2>& Infos, bool bOnlyDominant) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Convert a set of mesh components in their current pose to a static mesh.
 | 
				
			||||||
 | 
						 * @param	InMeshComponents		The mesh components we want to convert
 | 
				
			||||||
 | 
						 * @param	InRootTransform			The transform of the root of the mesh we want to output
 | 
				
			||||||
 | 
						 * @param	InPackageName			The package name to create the static mesh in. If this is empty then a dialog will be displayed to pick the mesh.
 | 
				
			||||||
 | 
						 * @return a new static mesh (specified by the user)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual UStaticMesh* ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform = FTransform::Identity, const FString& InPackageName = FString()) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						*	Calculates UV coordinates bounds for the given Skeletal Mesh
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param InRawMesh - Skeletal Mesh to calculate the bounds for
 | 
				
			||||||
 | 
						* @param OutBounds - Out texture bounds (min-max)
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						//virtual void CalculateTextureCoordinateBoundsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, TArray<FBox2D>& OutBounds) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Calculates (new) non-overlapping UV coordinates for the given Skeletal Mesh
 | 
				
			||||||
 | 
						*
 | 
				
			||||||
 | 
						* @param LODModel - Skeletal Mesh to generate UV coordinates for
 | 
				
			||||||
 | 
						* @param TextureResolution - Texture resolution to take into account while generating the UVs
 | 
				
			||||||
 | 
						* @param OutTexCoords - New set of UV coordinates
 | 
				
			||||||
 | 
						* @return bool - whether or not generating the UVs succeeded
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						//virtual bool GenerateUniqueUVsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Remove Bones based on LODInfo setting
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param SkeletalMesh	Mesh that needs bones to be removed
 | 
				
			||||||
 | 
						 * @param LODIndex		Desired LOD to remove bones [ 0 based ]
 | 
				
			||||||
 | 
						 * @param BoneNamesToRemove	List of bone names to remove
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return true if success
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual bool RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Calculates Tangents and Normals for a given set of vertex data
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param InVertices Vertices that make up the mesh
 | 
				
			||||||
 | 
						 * @param InIndices Indices for the Vertex array
 | 
				
			||||||
 | 
						 * @param InUVs Texture coordinates (per-index based)
 | 
				
			||||||
 | 
						 * @param InSmoothingGroupIndices Smoothing group index (per-face based)
 | 
				
			||||||
 | 
						 * @param InTangentOptions Flags for Tangent calculation
 | 
				
			||||||
 | 
						 * @param OutTangentX Array to hold calculated Tangents
 | 
				
			||||||
 | 
						 * @param OutTangentY Array to hold calculated Bitangents
 | 
				
			||||||
 | 
						 * @param OutNormals Array to hold calculated normals (if already contains normals will use those instead for the tangent calculation)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalculateTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutTangentX, TArray<FVector>& OutTangentY, TArray<FVector>& OutNormals) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Calculates MikkTSpace Tangents for a given set of vertex data with normals provided
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param InVertices Vertices that make up the mesh
 | 
				
			||||||
 | 
						 * @param InIndices Indices for the Vertex array
 | 
				
			||||||
 | 
						 * @param InUVs Texture coordinates (per-index based)
 | 
				
			||||||
 | 
						 * @param InNormals Normals used for the tangent calculation (must be normalized)
 | 
				
			||||||
 | 
						 * @param bIgnoreDegenerateTriangles Flag for MikkTSpace to skip degenerate triangles fix-up path
 | 
				
			||||||
 | 
						 * @param OutTangentX Array to hold calculated Tangents
 | 
				
			||||||
 | 
						 * @param OutTangentY Array to hold calculated Bitangents
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalculateMikkTSpaceTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<FVector>& InNormals, bool bIgnoreDegenerateTriangles, TArray<FVector>& OutTangentX, TArray<FVector>& OutTangentY) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Calculates Normals for a given set of vertex data
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param InVertices Vertices that make up the mesh
 | 
				
			||||||
 | 
						 * @param InIndices Indices for the Vertex array
 | 
				
			||||||
 | 
						 * @param InUVs Texture coordinates (per-index based)
 | 
				
			||||||
 | 
						 * @param InSmoothingGroupIndices Smoothing group index (per-face based)
 | 
				
			||||||
 | 
						 * @param InTangentOptions Flags for Tangent calculation
 | 
				
			||||||
 | 
						 * @param OutNormals Array to hold calculated normals
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						//virtual void CalculateNormals(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutNormals) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Calculates the overlapping corners for a given set of vertex data
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param InVertices Vertices that make up the mesh
 | 
				
			||||||
 | 
						 * @param InIndices Indices for the Vertex array
 | 
				
			||||||
 | 
						 * @param bIgnoreDegenerateTriangles Indicates if we should skip degenerate triangles
 | 
				
			||||||
 | 
						 * @param OutOverlappingCorners Container to hold the overlapping corners. For a vertex, lists all the overlapping vertices.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void CalculateOverlappingCorners(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, bool bIgnoreDegenerateTriangles, FOverlappingCorners& OutOverlappingCorners) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh &OutRawMesh) const = 0;
 | 
				
			||||||
 | 
						virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, const FOverlappingCorners& InOverlappingCorners, FRawMesh &OutRawMesh) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, float ComparisonThreshold) const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Used to generate runtime skin weight data from Editor-only data */
 | 
				
			||||||
 | 
						// virtual void GenerateRuntimeSkinWeightData(const FSkeletalMeshLODModel* ImportedModel, const TArray<FRawSkinWeight>& InRawSkinWeights, struct FRuntimeSkinWeightProfileData& InOutSkinWeightOverrideData) const = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Content/BlueMaterial.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Content/BlueMaterial.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Content/RedMaterial.uasset
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Content/RedMaterial.uasset
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Resources/Icon128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Plugins/RuntimeGeometryUtils/Resources/Icon128.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 12 KiB  | 
							
								
								
									
										42
									
								
								Plugins/RuntimeGeometryUtils/RuntimeGeometryUtils.uplugin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Plugins/RuntimeGeometryUtils/RuntimeGeometryUtils.uplugin
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"FileVersion": 3,
 | 
				
			||||||
 | 
						"Version": 1,
 | 
				
			||||||
 | 
						"VersionName": "1.0",
 | 
				
			||||||
 | 
						"FriendlyName": "RuntimeGeometryUtils",
 | 
				
			||||||
 | 
						"Description": "",
 | 
				
			||||||
 | 
						"Category": "Other",
 | 
				
			||||||
 | 
						"CreatedBy": "Ryan Schmidt",
 | 
				
			||||||
 | 
						"CreatedByURL": "",
 | 
				
			||||||
 | 
						"DocsURL": "",
 | 
				
			||||||
 | 
						"MarketplaceURL": "",
 | 
				
			||||||
 | 
						"SupportURL": "",
 | 
				
			||||||
 | 
						"CanContainContent": true,
 | 
				
			||||||
 | 
						"IsBetaVersion": false,
 | 
				
			||||||
 | 
						"IsExperimentalVersion": false,
 | 
				
			||||||
 | 
						"Installed": false,
 | 
				
			||||||
 | 
						"Modules": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "RuntimeGeometryUtils",
 | 
				
			||||||
 | 
								"Type": "Runtime",
 | 
				
			||||||
 | 
								"LoadingPhase": "Default"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						"Plugins": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "GeometryProcessing",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "MeshModelingToolset",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "ProceduralMeshComponent",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "MeshUtilities2",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,441 @@
 | 
				
			|||||||
 | 
					#include "DynamicMeshBaseActor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Generators/SphereGenerator.h"
 | 
				
			||||||
 | 
					#include "Generators/GridBoxMeshGenerator.h"
 | 
				
			||||||
 | 
					#include "MeshQueries.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					#include "MeshNormals.h"
 | 
				
			||||||
 | 
					#include "MeshTransforms.h"
 | 
				
			||||||
 | 
					#include "MeshSimplification.h"
 | 
				
			||||||
 | 
					#include "Operations/MeshBoolean.h"
 | 
				
			||||||
 | 
					#include "Implicit/Solidify.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DynamicMeshOBJReader.h"
 | 
				
			||||||
 | 
					#include "MeshCardRepresentation2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets default values
 | 
				
			||||||
 | 
					ADynamicMeshBaseActor::ADynamicMeshBaseActor()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
 | 
				
			||||||
 | 
						PrimaryActorTick.bCanEverTick = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AccumulatedTime = 0;
 | 
				
			||||||
 | 
						MeshAABBTree.SetMesh(&SourceMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FastWinding = MakeUnique<TFastWindingTree<FDynamicMesh3>>(&MeshAABBTree, false);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::PostLoad()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::PostLoad();
 | 
				
			||||||
 | 
						OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::PostActorCreated()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::PostActorCreated();
 | 
				
			||||||
 | 
						OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called when the game starts or when spawned
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::BeginPlay()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::BeginPlay();
 | 
				
			||||||
 | 
						AccumulatedTime = 0;
 | 
				
			||||||
 | 
						OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called every frame
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::Tick(float DeltaTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::Tick(DeltaTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AccumulatedTime += DeltaTime;
 | 
				
			||||||
 | 
						if (bRegenerateOnTick && SourceType == EDynamicMeshActorSourceType::Primitive)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::PostEditChangeProperty(PropertyChangedEvent);
 | 
				
			||||||
 | 
						OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						EditFunc(SourceMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// update spatial data structures
 | 
				
			||||||
 | 
						if (bEnableSpatialQueries || bEnableInsideQueries)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshAABBTree.Build();
 | 
				
			||||||
 | 
							if (bEnableInsideQueries)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FastWinding->Build();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						OnMeshEditedInternal();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::GetMeshCopy(FDynamicMesh3& MeshOut)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshOut = SourceMesh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FDynamicMesh3& ADynamicMeshBaseActor::GetMeshRef() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return SourceMesh;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::OnMeshEditedInternal()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						OnMeshModified.Broadcast(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::OnMeshGenerationSettingsModified()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						EditMesh([this](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RegenerateSourceMesh(MeshToUpdate);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::RegenerateSourceMesh(FDynamicMesh3& MeshOut)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (SourceType == EDynamicMeshActorSourceType::Primitive)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							double UseRadius = (this->MinimumRadius + this->VariableRadius)
 | 
				
			||||||
 | 
								+ (this->VariableRadius) * FMathd::Sin(PulseSpeed * AccumulatedTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// generate new mesh
 | 
				
			||||||
 | 
							if (this->PrimitiveType == EDynamicMeshActorPrimitiveType::Sphere)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FSphereGenerator SphereGen;
 | 
				
			||||||
 | 
								SphereGen.NumPhi = SphereGen.NumTheta = FMath::Clamp(this->TessellationLevel, 3, 50);
 | 
				
			||||||
 | 
								SphereGen.Radius = UseRadius;
 | 
				
			||||||
 | 
								MeshOut.Copy(&SphereGen.Generate());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FGridBoxMeshGenerator BoxGen;
 | 
				
			||||||
 | 
								int TessLevel = FMath::Clamp(this->TessellationLevel, 2, 50);
 | 
				
			||||||
 | 
								BoxGen.EdgeVertices = FIndex3i(TessLevel, TessLevel, TessLevel);
 | 
				
			||||||
 | 
								FVector3d BoxExtents = UseRadius * FVector3d::One();
 | 
				
			||||||
 | 
								BoxExtents.Z *= BoxDepthRatio;
 | 
				
			||||||
 | 
								BoxGen.Box = FOrientedBox3d(FVector3d::Zero(), BoxExtents);
 | 
				
			||||||
 | 
								MeshOut.Copy(&BoxGen.Generate());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (SourceType == EDynamicMeshActorSourceType::ImportedMesh)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FString UsePath = ImportPath;
 | 
				
			||||||
 | 
							if (FPaths::FileExists(UsePath) == false && FPaths::IsRelative(UsePath))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UsePath = FPaths::ProjectContentDir() + ImportPath;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MeshOut = FDynamicMesh3();
 | 
				
			||||||
 | 
							if (! RTGUtils::ReadOBJMesh(UsePath, MeshOut, true, true, true, bReverseOrientation))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *UsePath);
 | 
				
			||||||
 | 
								FSphereGenerator SphereGen;
 | 
				
			||||||
 | 
								SphereGen.NumPhi = SphereGen.NumTheta = 8;
 | 
				
			||||||
 | 
								SphereGen.Radius = this->MinimumRadius;
 | 
				
			||||||
 | 
								MeshOut.Copy(&SphereGen.Generate());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bCenterPivot)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								MeshTransforms::Translate(MeshOut, -MeshOut.GetBounds().Center());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ImportScale != 1.0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								MeshTransforms::Scale(MeshOut, ImportScale * FVector3d::One(), FVector3d::Zero());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RecomputeNormals(MeshOut);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::RecomputeNormals(FDynamicMesh3& MeshOut)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (this->NormalsMode == EDynamicMeshActorNormalsMode::PerVertexNormals)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshOut.EnableAttributes();
 | 
				
			||||||
 | 
							FMeshNormals::InitializeOverlayToPerVertexNormals(MeshOut.Attributes()->PrimaryNormals(), false);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshOut.EnableAttributes();
 | 
				
			||||||
 | 
							FMeshNormals::InitializeOverlayToPerTriangleNormals(MeshOut.Attributes()->PrimaryNormals());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ADynamicMeshBaseActor::GetTriangleCount()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return SourceMesh.TriangleCount();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float ADynamicMeshBaseActor::DistanceToPoint(FVector WorldPoint, FVector& NearestWorldPoint, int& NearestTriangle, FVector& TriBaryCoords)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						NearestWorldPoint = WorldPoint;
 | 
				
			||||||
 | 
						NearestTriangle = -1;
 | 
				
			||||||
 | 
						if (bEnableSpatialQueries == false)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return TNumericLimits<float>::Max();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FTransform3d ActorToWorld(GetActorTransform());
 | 
				
			||||||
 | 
						FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						double NearDistSqr;
 | 
				
			||||||
 | 
						NearestTriangle = MeshAABBTree.FindNearestTriangle(LocalPoint, NearDistSqr);
 | 
				
			||||||
 | 
						if (NearestTriangle < 0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return TNumericLimits<float>::Max();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDistPoint3Triangle3d DistQuery = TMeshQueries<FDynamicMesh3>::TriangleDistance(SourceMesh, NearestTriangle, LocalPoint);
 | 
				
			||||||
 | 
						NearestWorldPoint = (FVector)ActorToWorld.TransformPosition(DistQuery.ClosestTrianglePoint);
 | 
				
			||||||
 | 
						TriBaryCoords = (FVector)DistQuery.TriangleBaryCoords;
 | 
				
			||||||
 | 
						return (float)FMathd::Sqrt(NearDistSqr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FVector ADynamicMeshBaseActor::NearestPoint(FVector WorldPoint)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (bEnableSpatialQueries)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FTransform3d ActorToWorld(GetActorTransform());
 | 
				
			||||||
 | 
							FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
 | 
				
			||||||
 | 
							return (FVector)ActorToWorld.TransformPosition(MeshAABBTree.FindNearestPoint(LocalPoint));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return WorldPoint;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ADynamicMeshBaseActor::ContainsPoint(FVector WorldPoint, float WindingThreshold)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (bEnableInsideQueries)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FTransform3d ActorToWorld(GetActorTransform());
 | 
				
			||||||
 | 
							FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
 | 
				
			||||||
 | 
							return FastWinding->IsInside(LocalPoint, WindingThreshold);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ADynamicMeshBaseActor::IntersectRay(FVector RayOrigin, FVector RayDirection,
 | 
				
			||||||
 | 
					                                         FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords,
 | 
				
			||||||
 | 
					                                         float MaxDistance)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (bEnableSpatialQueries)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FTransform3d ActorToWorld(GetActorTransform());
 | 
				
			||||||
 | 
							FVector3d WorldDirection(RayDirection);
 | 
				
			||||||
 | 
							WorldDirection.Normalize();
 | 
				
			||||||
 | 
							FRay3d LocalRay(ActorToWorld.InverseTransformPosition((FVector3d)RayOrigin),
 | 
				
			||||||
 | 
							                ActorToWorld.InverseTransformNormal(WorldDirection));
 | 
				
			||||||
 | 
							IMeshSpatial::FQueryOptions QueryOptions;
 | 
				
			||||||
 | 
							if (MaxDistance > 0)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								QueryOptions.MaxDistance = MaxDistance;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							NearestTriangle = MeshAABBTree.FindNearestHitTriangle(LocalRay, QueryOptions);
 | 
				
			||||||
 | 
							if (SourceMesh.IsTriangle(NearestTriangle))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FIntrRay3Triangle3d IntrQuery = TMeshQueries<FDynamicMesh3>::TriangleIntersection(SourceMesh, NearestTriangle, LocalRay);
 | 
				
			||||||
 | 
								if (IntrQuery.IntersectionType == EIntersectionType::Point)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									HitDistance = IntrQuery.RayParameter;
 | 
				
			||||||
 | 
									WorldHitPoint = (FVector)ActorToWorld.TransformPosition(LocalRay.PointAt(IntrQuery.RayParameter));
 | 
				
			||||||
 | 
									TriBaryCoords = (FVector)IntrQuery.TriangleBaryCoords;
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::SubtractMesh(ADynamicMeshBaseActor* OtherMeshActor)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Subtraction);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::UnionWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Union);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::IntersectWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Intersection);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::BooleanWithMesh(ADynamicMeshBaseActor* OtherMeshActor, EDynamicMeshActorBooleanOperation Operation)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (ensure(OtherMeshActor) == false) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FTransform3d ActorToWorld(GetActorTransform());
 | 
				
			||||||
 | 
						FTransform3d OtherToWorld(OtherMeshActor->GetActorTransform());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDynamicMesh3 OtherMesh;
 | 
				
			||||||
 | 
						OtherMeshActor->GetMeshCopy(OtherMesh);
 | 
				
			||||||
 | 
						MeshTransforms::ApplyTransform(OtherMesh, OtherToWorld);
 | 
				
			||||||
 | 
						MeshTransforms::ApplyTransformInverse(OtherMesh, ActorToWorld);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditMesh([&](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FDynamicMesh3 ResultMesh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FMeshBoolean::EBooleanOp ApplyOp = FMeshBoolean::EBooleanOp::Union;
 | 
				
			||||||
 | 
							switch (Operation)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case EDynamicMeshActorBooleanOperation::Subtraction:
 | 
				
			||||||
 | 
								ApplyOp = FMeshBoolean::EBooleanOp::Difference;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case EDynamicMeshActorBooleanOperation::Intersection:
 | 
				
			||||||
 | 
								ApplyOp = FMeshBoolean::EBooleanOp::Intersect;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FMeshBoolean Boolean(
 | 
				
			||||||
 | 
								&MeshToUpdate, FTransform3d::Identity(),
 | 
				
			||||||
 | 
								&OtherMesh, FTransform3d::Identity(),
 | 
				
			||||||
 | 
								&ResultMesh,
 | 
				
			||||||
 | 
								ApplyOp);
 | 
				
			||||||
 | 
							Boolean.bPutResultInInputSpace = true;
 | 
				
			||||||
 | 
							bool bOK = Boolean.Compute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!bOK)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// fill holes
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RecomputeNormals(ResultMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MeshToUpdate = MoveTemp(ResultMesh);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ADynamicMeshBaseActor::ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FDynamicMesh3 ImportedMesh;
 | 
				
			||||||
 | 
						if (!RTGUtils::ReadOBJMesh(Path, ImportedMesh, true, true, true, bFlipOrientation))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *Path);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bRecomputeNormals)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RecomputeNormals(ImportedMesh);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditMesh([&](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshToUpdate = MoveTemp(ImportedMesh);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (! ensure(OtherMesh)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the part where we generate a new mesh
 | 
				
			||||||
 | 
						FDynamicMesh3 TmpMesh;
 | 
				
			||||||
 | 
						OtherMesh->GetMeshCopy(TmpMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// apply our normals setting
 | 
				
			||||||
 | 
						if (bRecomputeNormals)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							RecomputeNormals(TmpMesh);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// update the mesh
 | 
				
			||||||
 | 
						EditMesh([&](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshToUpdate = MoveTemp(TmpMesh);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::SolidifyMesh(int VoxelResolution, float WindingThreshold)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (MeshAABBTree.IsValid() == false)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshAABBTree.Build();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (FastWinding->IsBuilt() == false)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							FastWinding->Build();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ugh workaround for bug
 | 
				
			||||||
 | 
						FDynamicMesh3 CompactMesh;
 | 
				
			||||||
 | 
						CompactMesh.CompactCopy(SourceMesh, false, false, false, false);
 | 
				
			||||||
 | 
						FDynamicMeshAABBTree3 AABBTree(&CompactMesh, true);
 | 
				
			||||||
 | 
						TFastWindingTree<FDynamicMesh3> Winding(&AABBTree, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						double ExtendBounds = 2.0;
 | 
				
			||||||
 | 
						//TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&SourceMesh, &MeshAABBTree, FastWinding.Get());
 | 
				
			||||||
 | 
						//SolidifyCalc.SetCellSizeAndExtendBounds(MeshAABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
 | 
				
			||||||
 | 
						TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&CompactMesh, &AABBTree, &Winding);
 | 
				
			||||||
 | 
						SolidifyCalc.SetCellSizeAndExtendBounds(AABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
 | 
				
			||||||
 | 
						SolidifyCalc.WindingThreshold = WindingThreshold;
 | 
				
			||||||
 | 
						SolidifyCalc.SurfaceSearchSteps = 5;
 | 
				
			||||||
 | 
						SolidifyCalc.bSolidAtBoundaries = true;
 | 
				
			||||||
 | 
						SolidifyCalc.ExtendBounds = ExtendBounds;
 | 
				
			||||||
 | 
						FDynamicMesh3 SolidMesh(&SolidifyCalc.Generate());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SolidMesh.EnableAttributes();
 | 
				
			||||||
 | 
						RecomputeNormals(SolidMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditMesh([&](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshToUpdate = MoveTemp(SolidMesh);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicMeshBaseActor::SimplifyMeshToTriCount(int32 TargetTriangleCount)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						TargetTriangleCount = FMath::Max(1, TargetTriangleCount);
 | 
				
			||||||
 | 
						if (TargetTriangleCount >= SourceMesh.TriangleCount()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make compacted copy because it seems to change the results?
 | 
				
			||||||
 | 
						FDynamicMesh3 SimplifyMesh;
 | 
				
			||||||
 | 
						SimplifyMesh.CompactCopy(SourceMesh, false, false, false, false);
 | 
				
			||||||
 | 
						SimplifyMesh.EnableTriangleGroups(); // workaround for failing check()
 | 
				
			||||||
 | 
						FQEMSimplification Simplifier(&SimplifyMesh);
 | 
				
			||||||
 | 
						Simplifier.SimplifyToTriangleCount(TargetTriangleCount);
 | 
				
			||||||
 | 
						SimplifyMesh.EnableAttributes();
 | 
				
			||||||
 | 
						RecomputeNormals(SimplifyMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditMesh([&](FDynamicMesh3& MeshToUpdate)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshToUpdate.CompactCopy(SimplifyMesh);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					#include "DynamicMeshOBJReader.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshAttributeSet.h"
 | 
				
			||||||
 | 
					#include "tinyobj/tiny_obj_loader.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool RTGUtils::ReadOBJMesh(
 | 
				
			||||||
 | 
						const FString& Path,
 | 
				
			||||||
 | 
						FDynamicMesh3& MeshOut,
 | 
				
			||||||
 | 
						bool bNormals,
 | 
				
			||||||
 | 
						bool bTexCoords,
 | 
				
			||||||
 | 
						bool bVertexColors,
 | 
				
			||||||
 | 
						bool bReverseOrientation)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						std::string inputfile(TCHAR_TO_UTF8(*Path));
 | 
				
			||||||
 | 
						tinyobj::attrib_t attrib;
 | 
				
			||||||
 | 
						std::vector<tinyobj::shape_t> shapes;
 | 
				
			||||||
 | 
						std::vector<tinyobj::material_t> materials;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::string warn;
 | 
				
			||||||
 | 
						std::string err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!warn.empty())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogTemp, Display, TEXT("%s"), warn.c_str());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!err.empty())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UE_LOG(LogTemp, Display, TEXT("%s"), err.c_str());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!ret)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// append vertices
 | 
				
			||||||
 | 
						for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							tinyobj::real_t vx = attrib.vertices[3 * vi + 0];
 | 
				
			||||||
 | 
							tinyobj::real_t vy = attrib.vertices[3 * vi + 1];
 | 
				
			||||||
 | 
							tinyobj::real_t vz = attrib.vertices[3 * vi + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MeshOut.AppendVertex(FVector3d(vx, vy, vz));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bVertexColors)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshOut.EnableVertexColors(FVector3f::Zero());
 | 
				
			||||||
 | 
							for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								tinyobj::real_t r = attrib.colors[3 * vi + 0];
 | 
				
			||||||
 | 
								tinyobj::real_t g = attrib.colors[3 * vi + 1];
 | 
				
			||||||
 | 
								tinyobj::real_t b = attrib.colors[3 * vi + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								MeshOut.SetVertexColor(vi, FVector3f((float)r, (float)g, (float)b));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bNormals || bTexCoords)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshOut.EnableAttributes();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						FDynamicMeshNormalOverlay* Normals = (bNormals) ? MeshOut.Attributes()->PrimaryNormals() : nullptr;
 | 
				
			||||||
 | 
						FDynamicMeshUVOverlay* UVs = (bTexCoords) ? MeshOut.Attributes()->PrimaryUV() : nullptr;
 | 
				
			||||||
 | 
						if (Normals)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (size_t ni = 0; ni < attrib.normals.size() / 3; ++ni)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								tinyobj::real_t nx = attrib.normals[3 * ni + 0];
 | 
				
			||||||
 | 
								tinyobj::real_t ny = attrib.normals[3 * ni + 1];
 | 
				
			||||||
 | 
								tinyobj::real_t nz = attrib.normals[3 * ni + 2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Normals->AppendElement(FVector3f((float)nx, (float)ny, (float)nz));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (UVs)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							for (size_t ti = 0; ti < attrib.texcoords.size() / 2; ++ti)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								tinyobj::real_t tx = attrib.texcoords[2 * ti + 0];
 | 
				
			||||||
 | 
								tinyobj::real_t ty = attrib.texcoords[2 * ti + 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								UVs->AppendElement(FVector2f((float)tx, (float)ty));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// append faces as triangles
 | 
				
			||||||
 | 
						for (size_t s = 0; s < shapes.size(); s++)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// Loop over shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							size_t index_offset = 0;
 | 
				
			||||||
 | 
							for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								// Loop over faces(polygon)
 | 
				
			||||||
 | 
								int fv = shapes[s].mesh.num_face_vertices[f];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								TArray<FIndex3i> Triangles;
 | 
				
			||||||
 | 
								for (size_t v = 1; v < fv - 1; v++)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Triangles.Add(FIndex3i(0, v, v + 1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								int32 NumTris = Triangles.Num();
 | 
				
			||||||
 | 
								for (int32 ti = 0; ti < NumTris; ++ti)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FIndex3i TriVerts = Triangles[ti];
 | 
				
			||||||
 | 
									tinyobj::index_t idx0 = shapes[s].mesh.indices[index_offset + TriVerts.A];
 | 
				
			||||||
 | 
									tinyobj::index_t idx1 = shapes[s].mesh.indices[index_offset + TriVerts.B];
 | 
				
			||||||
 | 
									tinyobj::index_t idx2 = shapes[s].mesh.indices[index_offset + TriVerts.C];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									int32 tid = MeshOut.AppendTriangle(idx0.vertex_index, idx1.vertex_index, idx2.vertex_index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (Normals && Normals->IsElement(idx0.normal_index) && Normals->IsElement(idx1.normal_index) && Normals->IsElement(idx2.normal_index))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Normals->SetTriangle(tid, FIndex3i(idx0.normal_index, idx1.normal_index, idx2.normal_index));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (UVs && UVs->IsElement(idx0.texcoord_index) && UVs->IsElement(idx1.texcoord_index) && UVs->IsElement(idx2.texcoord_index))
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										UVs->SetTriangle(tid, FIndex3i(idx0.texcoord_index, idx1.texcoord_index, idx2.texcoord_index));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								index_offset += fv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// per-face material
 | 
				
			||||||
 | 
								//shapes[s].mesh.material_ids[f];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bReverseOrientation)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							MeshOut.ReverseOrientation();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,191 @@
 | 
				
			|||||||
 | 
					#include "DynamicMeshOBJWriter.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshAttributeSet.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshEditor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <fstream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FDynamicMeshOBJWriter
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::ofstream FileOut;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TFunction<bool(const FString&)> OpenFile = [this](const FString& Path) { FileOut.open(*Path, std::ofstream::out | std::ofstream::trunc); return !!FileOut; };
 | 
				
			||||||
 | 
						TFunction<void()> CloseFile = [this]() { FileOut.close(); };
 | 
				
			||||||
 | 
						TFunction<void(const TCHAR*)> WriteLine = [this](const TCHAR* Line) { FileOut << TCHAR_TO_ANSI(Line) << std::endl; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool Write(const char* OutputPath, const FDynamicMesh3& Mesh)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (!OpenFile(OutputPath))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int32 NumVertices = Mesh.VertexCount();
 | 
				
			||||||
 | 
							for (int32 vi = 0; vi < NumVertices; ++vi)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FVector3d Pos = Mesh.GetVertex(vi);
 | 
				
			||||||
 | 
								WriteLine(*FString::Printf(TEXT("v %f %f %f"), Pos.X, Pos.Y, Pos.Z));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int32 NumUVs = 0;
 | 
				
			||||||
 | 
							const FDynamicMeshUVOverlay* UVs = nullptr;
 | 
				
			||||||
 | 
							if (Mesh.Attributes() && Mesh.Attributes()->PrimaryUV())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UVs = Mesh.Attributes()->PrimaryUV();
 | 
				
			||||||
 | 
								NumUVs = UVs->ElementCount();
 | 
				
			||||||
 | 
								for (int32 ui = 0; ui < NumUVs; ++ui)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FVector2f UV = UVs->GetElement(ui);
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("vt %f %f"), UV.X, UV.Y));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int32 NumNormals = 0;
 | 
				
			||||||
 | 
							const FDynamicMeshNormalOverlay* Normals = nullptr;
 | 
				
			||||||
 | 
							if (Mesh.Attributes() && Mesh.Attributes()->PrimaryNormals())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Normals = Mesh.Attributes()->PrimaryNormals();
 | 
				
			||||||
 | 
								NumNormals = Normals->ElementCount();
 | 
				
			||||||
 | 
								for (int32 ni = 0; ni < NumNormals; ++ni)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									FVector3f Normal = Normals->GetElement(ni);
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("vn %f %f %f"), Normal.X, Normal.Y, Normal.Z));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							struct FMeshTri
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								int32 Index;
 | 
				
			||||||
 | 
								int32 Group;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							TSet<int32> AllGroupIDs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TArray<FMeshTri> Triangles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int32 NumTriangles = Mesh.TriangleCount();
 | 
				
			||||||
 | 
							for (int32 ti = 0; ti < NumTriangles; ++ti)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (Mesh.IsTriangle(ti))
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									int32 GroupID = Mesh.GetTriangleGroup(ti);
 | 
				
			||||||
 | 
									AllGroupIDs.Add(GroupID);
 | 
				
			||||||
 | 
									Triangles.Add({ ti, GroupID });
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bool bHaveGroups = AllGroupIDs.Num() > 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Triangles.StableSort([](const FMeshTri& Tri0, const FMeshTri& Tri1)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								return Tri0.Group < Tri1.Group;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int32 CurGroupID = -99999;
 | 
				
			||||||
 | 
							for (FMeshTri MeshTri : Triangles)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								if (bHaveGroups && MeshTri.Group != CurGroupID)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("g %d"), MeshTri.Group));
 | 
				
			||||||
 | 
									CurGroupID = MeshTri.Group;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								int32 ti = MeshTri.Index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								FIndex3i TriVertices = Mesh.GetTriangle(ti);
 | 
				
			||||||
 | 
								FIndex3i TriUVs = (NumUVs > 0) ? UVs->GetTriangle(ti) : FIndex3i::Invalid();
 | 
				
			||||||
 | 
								FIndex3i TriNormals = (NumNormals > 0) ? Normals->GetTriangle(ti) : FIndex3i::Invalid();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								bool bHaveUV = (NumUVs != 0) && UVs->IsSetTriangle(ti);
 | 
				
			||||||
 | 
								bool bHaveNormal = (NumNormals != 0) && Normals->IsSetTriangle(ti);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (bHaveUV && bHaveNormal)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("f %d/%d/%d %d/%d/%d %d/%d/%d"),
 | 
				
			||||||
 | 
										TriVertices.A + 1, TriUVs.A + 1, TriNormals.A + 1,
 | 
				
			||||||
 | 
										TriVertices.B + 1, TriUVs.B + 1, TriNormals.B + 1,
 | 
				
			||||||
 | 
										TriVertices.C + 1, TriUVs.C + 1, TriNormals.C + 1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else if (bHaveUV)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("f %d/%d %d/%d %d/%d"),
 | 
				
			||||||
 | 
										TriVertices.A + 1, TriUVs.A + 1, TriVertices.B + 1, TriUVs.B + 1, TriVertices.C + 1, TriUVs.C + 1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else if (bHaveNormal)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("f %d//%d %d//%d %d//%d"),
 | 
				
			||||||
 | 
										TriVertices.A + 1, TriNormals.A + 1, TriVertices.B + 1, TriNormals.B + 1, TriVertices.C + 1, TriNormals.C + 1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									WriteLine(*FString::Printf(TEXT("f %d %d %d"), TriVertices.A + 1, TriVertices.B + 1, TriVertices.C + 1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CloseFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool RTGUtils::WriteOBJMesh(
 | 
				
			||||||
 | 
						const FString& OutputPath,
 | 
				
			||||||
 | 
						const FDynamicMesh3& Mesh,
 | 
				
			||||||
 | 
						bool bReverseOrientation)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const FDynamicMesh3* WriteMesh = &Mesh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDynamicMesh3 CopyMesh;
 | 
				
			||||||
 | 
						if (bReverseOrientation)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							CopyMesh = Mesh;
 | 
				
			||||||
 | 
							CopyMesh.ReverseOrientation();
 | 
				
			||||||
 | 
							WriteMesh = &CopyMesh;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDynamicMeshOBJWriter Writer;
 | 
				
			||||||
 | 
						std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
 | 
				
			||||||
 | 
						return Writer.Write(OutputFilePath.c_str(), *WriteMesh);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool RTGUtils::WriteOBJMeshes(
 | 
				
			||||||
 | 
						const FString& OutputPath,
 | 
				
			||||||
 | 
						const TArray<FDynamicMesh3>& Meshes,
 | 
				
			||||||
 | 
						bool bReverseOrientation)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FDynamicMesh3 CombinedMesh;
 | 
				
			||||||
 | 
						FDynamicMeshEditor Editor(&CombinedMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const FDynamicMesh3& Mesh : Meshes)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (Mesh.HasTriangleGroups())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CombinedMesh.EnableTriangleGroups();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (Mesh.HasAttributes())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								CombinedMesh.EnableAttributes();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FMeshIndexMappings Mappings;
 | 
				
			||||||
 | 
							Editor.AppendMesh(&Mesh, Mappings);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (bReverseOrientation)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							CombinedMesh.ReverseOrientation();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDynamicMeshOBJWriter Writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
 | 
				
			||||||
 | 
						return Writer.Write(OutputFilePath.c_str(), CombinedMesh);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					#include "DynamicPMCActor.h"
 | 
				
			||||||
 | 
					#include "MeshComponentRuntimeUtils.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets default values
 | 
				
			||||||
 | 
					ADynamicPMCActor::ADynamicPMCActor()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("Mesh"), false);
 | 
				
			||||||
 | 
						SetRootComponent(MeshComponent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called when the game starts or when spawned
 | 
				
			||||||
 | 
					void ADynamicPMCActor::BeginPlay()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::BeginPlay();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called every frame
 | 
				
			||||||
 | 
					void ADynamicPMCActor::Tick(float DeltaTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::Tick(DeltaTime);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicPMCActor::OnMeshEditedInternal()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						UpdatePMCMesh();
 | 
				
			||||||
 | 
						Super::OnMeshEditedInternal();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicPMCActor::UpdatePMCMesh()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (MeshComponent)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							bool bUseFaceNormals = (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals);
 | 
				
			||||||
 | 
							bool bUseUV0 = true;
 | 
				
			||||||
 | 
							bool bUseVertexColors = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							bool bGenerateSectionCollision = false;
 | 
				
			||||||
 | 
							if (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimple
 | 
				
			||||||
 | 
								|| this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								bGenerateSectionCollision = true;
 | 
				
			||||||
 | 
								MeshComponent->bUseAsyncCooking = (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync);
 | 
				
			||||||
 | 
								MeshComponent->bUseComplexAsSimpleCollision = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(MeshComponent, &SourceMesh, bUseFaceNormals, bUseUV0, bUseVertexColors, bGenerateSectionCollision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update material on new section
 | 
				
			||||||
 | 
							UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
 | 
				
			||||||
 | 
							MeshComponent->SetMaterial(0, UseMaterial);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#include "DynamicSDMCActor.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets default values
 | 
				
			||||||
 | 
					ADynamicSDMCActor::ADynamicSDMCActor()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshComponent = CreateDefaultSubobject<USimpleDynamicMeshComponent>(TEXT("MeshComponent"), false);
 | 
				
			||||||
 | 
						SetRootComponent(MeshComponent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called when the game starts or when spawned
 | 
				
			||||||
 | 
					void ADynamicSDMCActor::BeginPlay()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::BeginPlay();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called every frame
 | 
				
			||||||
 | 
					void ADynamicSDMCActor::Tick(float DeltaTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::Tick(DeltaTime);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSDMCActor::OnMeshEditedInternal()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						UpdateSDMCMesh();
 | 
				
			||||||
 | 
						Super::OnMeshEditedInternal();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSDMCActor::UpdateSDMCMesh()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (MeshComponent)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							*(MeshComponent->GetMesh()) = SourceMesh;
 | 
				
			||||||
 | 
							MeshComponent->NotifyMeshUpdated();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update material on new section
 | 
				
			||||||
 | 
							UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
 | 
				
			||||||
 | 
							MeshComponent->SetMaterial(0, UseMaterial);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					#include "DynamicSMCActor.h"
 | 
				
			||||||
 | 
					#include "MeshComponentRuntimeUtils.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets default values
 | 
				
			||||||
 | 
					ADynamicSMCActor::ADynamicSMCActor()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"), false);
 | 
				
			||||||
 | 
						SetRootComponent(MeshComponent);
 | 
				
			||||||
 | 
						StaticMesh = nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called when the game starts or when spawned
 | 
				
			||||||
 | 
					void ADynamicSMCActor::BeginPlay()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						StaticMesh = nullptr;
 | 
				
			||||||
 | 
						Super::BeginPlay();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSMCActor::PostLoad()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						StaticMesh = nullptr;
 | 
				
			||||||
 | 
						Super::PostLoad();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSMCActor::PostActorCreated()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						StaticMesh = nullptr;
 | 
				
			||||||
 | 
						Super::PostActorCreated();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called every frame
 | 
				
			||||||
 | 
					void ADynamicSMCActor::Tick(float DeltaTime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Super::Tick(DeltaTime);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSMCActor::OnMeshEditedInternal()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						UpdateSMCMesh();
 | 
				
			||||||
 | 
						Super::OnMeshEditedInternal();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ADynamicSMCActor::UpdateSMCMesh()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (StaticMesh == nullptr)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							StaticMesh = NewObject<UStaticMesh>();
 | 
				
			||||||
 | 
							MeshComponent->SetStaticMesh(StaticMesh);
 | 
				
			||||||
 | 
							// add one material slot
 | 
				
			||||||
 | 
							StaticMesh->GetStaticMaterials().Add(FStaticMaterial());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (MeshComponent)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto MTL = LoadObject<UMaterialInterface>(nullptr,TEXT("Material'/Game/baise.baise'"));
 | 
				
			||||||
 | 
							// update material on new section
 | 
				
			||||||
 | 
							UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : MTL;
 | 
				
			||||||
 | 
							MeshComponent->SetMaterial(0, UseMaterial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							RTGUtils::UpdateStaticMeshFromDynamicMesh(StaticMesh, &SourceMesh);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					#include "MeshComponentRuntimeUtils.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DynamicMeshAttributeSet.h"
 | 
				
			||||||
 | 
					#include "MeshNormals.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DynamicMeshToMeshDescription.h"
 | 
				
			||||||
 | 
					#include "StaticMeshAttributes.h"
 | 
				
			||||||
 | 
					#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void RTGUtils::UpdateStaticMeshFromDynamicMesh(
 | 
				
			||||||
 | 
						UStaticMesh* StaticMesh,
 | 
				
			||||||
 | 
						const FDynamicMesh3* Mesh)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FMeshDescription MeshDescription;
 | 
				
			||||||
 | 
						FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
 | 
				
			||||||
 | 
						StaticMeshAttributes.Register();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FDynamicMeshToMeshDescription Converter;
 | 
				
			||||||
 | 
						Converter.Convert(Mesh, MeshDescription);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// todo: vertex color support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Component);
 | 
				
			||||||
 | 
						//FName MaterialSlotName = StaticMesh->AddMaterial(MyMaterial);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Build the static mesh render data, one FMeshDescription* per LOD.
 | 
				
			||||||
 | 
						TArray<const FMeshDescription*> MeshDescriptionPtrs;
 | 
				
			||||||
 | 
						MeshDescriptionPtrs.Emplace(&MeshDescription);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UStaticMesh::FBuildMeshDescriptionsParams Params;
 | 
				
			||||||
 | 
						Params.bFastBuild = true;
 | 
				
			||||||
 | 
						StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs,Params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//在编辑器模式下,也使用自定义的距离场构建和MeshCard构建
 | 
				
			||||||
 | 
						BuildMeshDistanceField(StaticMesh);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(
 | 
				
			||||||
 | 
						UProceduralMeshComponent* Component,
 | 
				
			||||||
 | 
						const FDynamicMesh3* Mesh,
 | 
				
			||||||
 | 
						bool bUseFaceNormals,
 | 
				
			||||||
 | 
						bool bInitializeUV0,
 | 
				
			||||||
 | 
						bool bInitializePerVertexColors,
 | 
				
			||||||
 | 
						bool bCreateCollision)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Component->ClearAllMeshSections();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int32 NumTriangles = Mesh->TriangleCount();
 | 
				
			||||||
 | 
						int32 NumVertices = NumTriangles * 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FVector> Vertices, Normals;
 | 
				
			||||||
 | 
						Vertices.SetNumUninitialized(NumVertices);
 | 
				
			||||||
 | 
						Normals.SetNumUninitialized(NumVertices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FMeshNormals PerVertexNormals(Mesh);
 | 
				
			||||||
 | 
						bool bUsePerVertexNormals = false;
 | 
				
			||||||
 | 
						const FDynamicMeshNormalOverlay* NormalOverlay = nullptr;
 | 
				
			||||||
 | 
						if (Mesh->HasAttributes() == false && bUseFaceNormals == false)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							PerVertexNormals.ComputeVertexNormals();
 | 
				
			||||||
 | 
							bUsePerVertexNormals = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else if (Mesh->HasAttributes())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							NormalOverlay = Mesh->Attributes()->PrimaryNormals();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const FDynamicMeshUVOverlay* UVOverlay = (Mesh->HasAttributes()) ? Mesh->Attributes()->PrimaryUV() : nullptr;
 | 
				
			||||||
 | 
						TArray<FVector2D> UV0;
 | 
				
			||||||
 | 
						if (UVOverlay && bInitializeUV0)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							UV0.SetNum(NumVertices);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FLinearColor> VtxColors;
 | 
				
			||||||
 | 
						bool bUsePerVertexColors = false;
 | 
				
			||||||
 | 
						if (bInitializePerVertexColors && Mesh->HasVertexColors())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							VtxColors.SetNum(NumVertices);
 | 
				
			||||||
 | 
							bUsePerVertexColors = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<FProcMeshTangent> Tangents; // not supporting this for now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TArray<int32> Triangles;
 | 
				
			||||||
 | 
						Triangles.SetNumUninitialized(NumTriangles * 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FVector3d Position[3];
 | 
				
			||||||
 | 
						FVector3f Normal[3];
 | 
				
			||||||
 | 
						FVector2f UV[3];
 | 
				
			||||||
 | 
						int32 BufferIndex = 0;
 | 
				
			||||||
 | 
						for (int32 tid : Mesh->TriangleIndicesItr())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							int32 k = 3 * (BufferIndex++);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							FIndex3i TriVerts = Mesh->GetTriangle(tid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Mesh->GetTriVertices(tid, Position[0], Position[1], Position[2]);
 | 
				
			||||||
 | 
							Vertices[k] = (FVector)Position[0];
 | 
				
			||||||
 | 
							Vertices[k + 1] = (FVector)Position[1];
 | 
				
			||||||
 | 
							Vertices[k + 2] = (FVector)Position[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bUsePerVertexNormals)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Normals[k] = (FVector)PerVertexNormals[TriVerts.A];
 | 
				
			||||||
 | 
								Normals[k + 1] = (FVector)PerVertexNormals[TriVerts.B];
 | 
				
			||||||
 | 
								Normals[k + 2] = (FVector)PerVertexNormals[TriVerts.C];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else if (NormalOverlay != nullptr && bUseFaceNormals == false)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								NormalOverlay->GetTriElements(tid, Normal[0], Normal[1], Normal[2]);
 | 
				
			||||||
 | 
								Normals[k] = (FVector)Normal[0];
 | 
				
			||||||
 | 
								Normals[k + 1] = (FVector)Normal[1];
 | 
				
			||||||
 | 
								Normals[k + 2] = (FVector)Normal[2];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								FVector3d TriNormal = Mesh->GetTriNormal(tid);
 | 
				
			||||||
 | 
								Normals[k] = (FVector)TriNormal;
 | 
				
			||||||
 | 
								Normals[k + 1] = (FVector)TriNormal;
 | 
				
			||||||
 | 
								Normals[k + 2] = (FVector)TriNormal;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (UVOverlay != nullptr && UVOverlay->IsSetTriangle(tid))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								UVOverlay->GetTriElements(tid, UV[0], UV[1], UV[2]);
 | 
				
			||||||
 | 
								UV0[k] = (FVector2D)UV[0];
 | 
				
			||||||
 | 
								UV0[k + 1] = (FVector2D)UV[1];
 | 
				
			||||||
 | 
								UV0[k + 2] = (FVector2D)UV[2];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bUsePerVertexColors)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								VtxColors[k] = (FLinearColor)Mesh->GetVertexColor(TriVerts.A);
 | 
				
			||||||
 | 
								VtxColors[k + 1] = (FLinearColor)Mesh->GetVertexColor(TriVerts.B);
 | 
				
			||||||
 | 
								VtxColors[k + 2] = (FLinearColor)Mesh->GetVertexColor(TriVerts.C);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Triangles[k] = k;
 | 
				
			||||||
 | 
							Triangles[k + 1] = k + 1;
 | 
				
			||||||
 | 
							Triangles[k + 2] = k + 2;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Component->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VtxColors, Tangents, bCreateCollision);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "RuntimeGeometryUtilsModule.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define LOCTEXT_NAMESPACE "FRuntimeGeometryUtilsModule"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FRuntimeGeometryUtilsModule::StartupModule()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FRuntimeGeometryUtilsModule::ShutdownModule()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
 | 
				
			||||||
 | 
						// we call this function before unloading the module.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#undef LOCTEXT_NAMESPACE
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					IMPLEMENT_MODULE(FRuntimeGeometryUtilsModule, RuntimeGeometryUtils)
 | 
				
			||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2012-2019 Syoyo Fujita and many contributors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
 | 
					THE SOFTWARE.
 | 
				
			||||||
@@ -0,0 +1,334 @@
 | 
				
			|||||||
 | 
					# tinyobjloader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://travis-ci.org/tinyobjloader/tinyobjloader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://coveralls.io/github/syoyo/tinyobjloader?branch=master)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://aur.archlinux.org/packages/tinyobjloader)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion) (not recommended)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tiny but powerful single file wavefront obj loader written in C++03. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Notice!
 | 
				
			||||||
 | 
					-------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We have released new version v1.0.0 on 20 Aug, 2016.
 | 
				
			||||||
 | 
					Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What's new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* 19 Feb, 2020 : The repository has been moved to https://github.com/tinyobjloader/tinyobjloader !
 | 
				
			||||||
 | 
					* 18 May, 2019 : Python binding!(See `python` folder. Also see https://pypi.org/project/tinyobjloader/)
 | 
				
			||||||
 | 
					* 14 Apr, 2019 : Bump version v2.0.0 rc0. New C++ API and python bindings!(1.x API still exists for backward compatibility)
 | 
				
			||||||
 | 
					* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* C++03 compiler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Old version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Previous old version is available in `v0.9.x` branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tinyobjloader can successfully load 6M triangles Rungholt scene.
 | 
				
			||||||
 | 
					http://casual-effects.com/data/index.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* [examples/viewer/](examples/viewer) OpenGL .obj viewer
 | 
				
			||||||
 | 
					* [examples/callback_api/](examples/callback_api/) Callback API example
 | 
				
			||||||
 | 
					* [examples/voxelize/](examples/voxelize/) Voxelizer example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Use case
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TinyObjLoader is successfully used in ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### New version(v1.0.x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma
 | 
				
			||||||
 | 
					* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models
 | 
				
			||||||
 | 
					* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master
 | 
				
			||||||
 | 
					* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook
 | 
				
			||||||
 | 
					* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox
 | 
				
			||||||
 | 
					* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake
 | 
				
			||||||
 | 
					* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer
 | 
				
			||||||
 | 
					* glslViewer: https://github.com/patriciogonzalezvivo/glslViewer
 | 
				
			||||||
 | 
					* Lighthouse2: https://github.com/jbikker/lighthouse2
 | 
				
			||||||
 | 
					* rayrender(an open source R package for raytracing scenes in created in R): https://github.com/tylermorganwall/rayrender
 | 
				
			||||||
 | 
					* liblava - A modern C++ and easy-to-use framework for the Vulkan API. [MIT]: https://github.com/liblava/liblava
 | 
				
			||||||
 | 
					* rtxON - Simple Vulkan raytracing tutorials  https://github.com/iOrange/rtxON
 | 
				
			||||||
 | 
					* metal-ray-tracer - Writing ray-tracer using Metal Performance Shaders https://github.com/sergeyreznik/metal-ray-tracer https://sergeyreznik.github.io/metal-ray-tracer/index.html
 | 
				
			||||||
 | 
					* Your project here! (Letting us know via github issue is welcome!)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Old version(v0.9.x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* bullet3 https://github.com/erwincoumans/bullet3
 | 
				
			||||||
 | 
					* pbrt-v2 https://github.com/mmp/pbrt-v2
 | 
				
			||||||
 | 
					* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01
 | 
				
			||||||
 | 
					* mallie https://lighttransport.github.io/mallie
 | 
				
			||||||
 | 
					* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/
 | 
				
			||||||
 | 
					* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf
 | 
				
			||||||
 | 
					* Awesome Bump http://awesomebump.besaba.com/about/
 | 
				
			||||||
 | 
					* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront
 | 
				
			||||||
 | 
					* pbrt-v3 https://github.com/mmp/pbrt-v3
 | 
				
			||||||
 | 
					* cocos2d-x https://github.com/cocos2d/cocos2d-x/
 | 
				
			||||||
 | 
					* Android Vulkan demo https://github.com/SaschaWillems/Vulkan
 | 
				
			||||||
 | 
					* voxelizer https://github.com/karimnaaji/voxelizer
 | 
				
			||||||
 | 
					* Probulator https://github.com/kayru/Probulator
 | 
				
			||||||
 | 
					* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking
 | 
				
			||||||
 | 
					* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK
 | 
				
			||||||
 | 
					* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg
 | 
				
			||||||
 | 
					* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl
 | 
				
			||||||
 | 
					* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/
 | 
				
			||||||
 | 
					* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf
 | 
				
			||||||
 | 
					* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf
 | 
				
			||||||
 | 
					* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Group(parse multiple group name)
 | 
				
			||||||
 | 
					* Vertex
 | 
				
			||||||
 | 
					  * Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender)
 | 
				
			||||||
 | 
					* Texcoord
 | 
				
			||||||
 | 
					* Normal
 | 
				
			||||||
 | 
					* Material
 | 
				
			||||||
 | 
					  * Unknown material attributes are returned as key-value(value is string) map.
 | 
				
			||||||
 | 
					* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification)
 | 
				
			||||||
 | 
					* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
 | 
				
			||||||
 | 
					* Callback API for custom loading.
 | 
				
			||||||
 | 
					* Double precision support(for HPC application).
 | 
				
			||||||
 | 
					* Smoothing group
 | 
				
			||||||
 | 
					* Python binding : See `python` folder.
 | 
				
			||||||
 | 
					  * Precompiled binary(manylinux1-x86_64 only) is hosted at pypi https://pypi.org/project/tinyobjloader/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Primitives
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* [x] face(`f`)
 | 
				
			||||||
 | 
					* [x] lines(`l`)
 | 
				
			||||||
 | 
					* [ ] points(`p`)
 | 
				
			||||||
 | 
					* [ ] curve
 | 
				
			||||||
 | 
					* [ ] 2D curve
 | 
				
			||||||
 | 
					* [ ] surface.
 | 
				
			||||||
 | 
					* [ ] Free form curve/surfaces
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* [ ] Fix obj_sticker example.
 | 
				
			||||||
 | 
					* [ ] More unit test codes.
 | 
				
			||||||
 | 
					* [x] Texture options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TinyObjLoader is licensed under MIT license.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Third party licenses.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* pybind11 : BSD-style license.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					One option is to simply copy the header file into your project and to make sure that `TINYOBJLOADER_IMPLEMENTATION` is defined exactly once.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tinyobjlaoder is also available as a [conan package](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion). Conan integrates with many build systems and lets you avoid manual dependency installation. Their [documentation](https://docs.conan.io/en/latest/getting_started.html) is a great starting point.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Building tinyobjloader - Using vcpkg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can download and install tinyobjloader using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    git clone https://github.com/Microsoft/vcpkg.git
 | 
				
			||||||
 | 
					    cd vcpkg
 | 
				
			||||||
 | 
					    ./bootstrap-vcpkg.sh
 | 
				
			||||||
 | 
					    ./vcpkg integrate install
 | 
				
			||||||
 | 
					    ./vcpkg install tinyobjloader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The tinyobjloader port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Data format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`attrib_t` contains single and linear array of vertex data(position, normal and texcoord).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					attrib_t::vertices => 3 floats per vertex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       v[0]        v[1]        v[2]        v[3]               v[n-1]
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					  | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					attrib_t::normals => 3 floats per vertex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       n[0]        n[1]        n[2]        n[3]               n[n-1]
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					  | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					attrib_t::texcoords => 2 floats per vertex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       t[0]        t[1]        t[2]        t[3]               t[n-1]
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					  |  u  |  v  |  u  |  v  |  u  |  v  |  u  |  v  | .... |  u  |  v  |
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					attrib_t::colors => 3 floats per vertex(vertex color. optional)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       c[0]        c[1]        c[2]        c[3]               c[n-1]
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					  | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
 | 
				
			||||||
 | 
					  +-----------+-----------+-----------+-----------+      +-----------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`.
 | 
				
			||||||
 | 
					See `loader_example.cc` for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mesh_t::indices => array of vertex indices.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  +----+----+----+----+----+----+----+----+----+----+     +--------+
 | 
				
			||||||
 | 
					  | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) |
 | 
				
			||||||
 | 
					  +----+----+----+----+----+----+----+----+----+----+     +--------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  +---+---+---+        +---+
 | 
				
			||||||
 | 
					  | 3 | 4 | 3 | ...... | 3 |
 | 
				
			||||||
 | 
					  +---+---+---+        +---+
 | 
				
			||||||
 | 
					    |   |   |            |
 | 
				
			||||||
 | 
					    |   |   |            +-----------------------------------------+
 | 
				
			||||||
 | 
					    |   |   |                                                      |
 | 
				
			||||||
 | 
					    |   |   +------------------------------+                       |
 | 
				
			||||||
 | 
					    |   |                                  |                       |
 | 
				
			||||||
 | 
					    |   +------------------+               |                       |
 | 
				
			||||||
 | 
					    |                      |               |                       |
 | 
				
			||||||
 | 
					    |/                     |/              |/                      |/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 mesh_t::indices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  |    face[0]   |       face[1]     |    face[2]   |     |      face[n-1]           |
 | 
				
			||||||
 | 
					  +----+----+----+----+----+----+----+----+----+----+     +--------+--------+--------+
 | 
				
			||||||
 | 
					  | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) |
 | 
				
			||||||
 | 
					  +----+----+----+----+----+----+----+----+----+----+     +--------+--------+--------+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that when `triangulate` flag is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### float data type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TinyObjLoader now use `real_t` for floating point data type.
 | 
				
			||||||
 | 
					Default is `float(32bit)`.
 | 
				
			||||||
 | 
					You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Example code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc
 | 
				
			||||||
 | 
					#include "tiny_obj_loader.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string inputfile = "cornell_box.obj";
 | 
				
			||||||
 | 
					tinyobj::attrib_t attrib;
 | 
				
			||||||
 | 
					std::vector<tinyobj::shape_t> shapes;
 | 
				
			||||||
 | 
					std::vector<tinyobj::material_t> materials;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string warn;
 | 
				
			||||||
 | 
					std::string err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!warn.empty()) {
 | 
				
			||||||
 | 
					  std::cout << warn << std::endl;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!err.empty()) {
 | 
				
			||||||
 | 
					  std::cerr << err << std::endl;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!ret) {
 | 
				
			||||||
 | 
					  exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Loop over shapes
 | 
				
			||||||
 | 
					for (size_t s = 0; s < shapes.size(); s++) {
 | 
				
			||||||
 | 
					  // Loop over faces(polygon)
 | 
				
			||||||
 | 
					  size_t index_offset = 0;
 | 
				
			||||||
 | 
					  for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
 | 
				
			||||||
 | 
					    int fv = shapes[s].mesh.num_face_vertices[f];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Loop over vertices in the face.
 | 
				
			||||||
 | 
					    for (size_t v = 0; v < fv; v++) {
 | 
				
			||||||
 | 
					      // access to vertex
 | 
				
			||||||
 | 
					      tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
 | 
				
			||||||
 | 
					      tinyobj::real_t vx = attrib.vertices[3*idx.vertex_index+0];
 | 
				
			||||||
 | 
					      tinyobj::real_t vy = attrib.vertices[3*idx.vertex_index+1];
 | 
				
			||||||
 | 
					      tinyobj::real_t vz = attrib.vertices[3*idx.vertex_index+2];
 | 
				
			||||||
 | 
					      tinyobj::real_t nx = attrib.normals[3*idx.normal_index+0];
 | 
				
			||||||
 | 
					      tinyobj::real_t ny = attrib.normals[3*idx.normal_index+1];
 | 
				
			||||||
 | 
					      tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2];
 | 
				
			||||||
 | 
					      tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0];
 | 
				
			||||||
 | 
					      tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1];
 | 
				
			||||||
 | 
					      // Optional: vertex colors
 | 
				
			||||||
 | 
					      // tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
 | 
				
			||||||
 | 
					      // tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
 | 
				
			||||||
 | 
					      // tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    index_offset += fv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // per-face material
 | 
				
			||||||
 | 
					    shapes[s].mesh.material_ids[f];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Optimized loader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Optimized multi-threaded .obj loader is available at `experimental/` directory.
 | 
				
			||||||
 | 
					If you want absolute performance to load .obj data, this optimized loader will fit your purpose.
 | 
				
			||||||
 | 
					Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Rungholt scene(6M triangles)
 | 
				
			||||||
 | 
					  * old version(v0.9.x): 15500 msecs.
 | 
				
			||||||
 | 
					  * baseline(v1.0.x): 6800 msecs(2.3x faster than old version)
 | 
				
			||||||
 | 
					  * optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Python binding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### CI + PyPI upload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cibuildwheels + twine upload for each git tagging event is handled in Azure Pipeline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### How to bump version(For developer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Bump version in CMakeLists.txt
 | 
				
			||||||
 | 
					* Update version in `python/setup.py`
 | 
				
			||||||
 | 
					* Commit with tag name starging with `v`(e.g. `v2.1.0`)
 | 
				
			||||||
 | 
					* `git push --tags`
 | 
				
			||||||
 | 
					  * cibuildwheels + pypi upload(through twine) will be automatically triggered in Azure Pipeline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unit tests are provided in `tests` directory. See `tests/README.md` for details.
 | 
				
			||||||
@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#define TINYOBJLOADER_IMPLEMENTATION
 | 
				
			||||||
 | 
					#include "tiny_obj_loader.h"
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,373 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "GameFramework/Actor.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshAABBTree3.h"
 | 
				
			||||||
 | 
					#include "Spatial/FastWinding.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshBaseActor.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Type of Normals computation used by ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UENUM()
 | 
				
			||||||
 | 
					enum class EDynamicMeshActorNormalsMode : uint8
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						SplitNormals = 0,
 | 
				
			||||||
 | 
						PerVertexNormals = 1,
 | 
				
			||||||
 | 
						FaceNormals = 2
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Source of mesh used to initialize ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UENUM()
 | 
				
			||||||
 | 
					enum class EDynamicMeshActorSourceType : uint8
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Primitive,
 | 
				
			||||||
 | 
						ImportedMesh
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Geometric primitive types supported of by ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UENUM()
 | 
				
			||||||
 | 
					enum class EDynamicMeshActorPrimitiveType : uint8
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Sphere,
 | 
				
			||||||
 | 
						Box
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Boolean operation types supported of by ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UENUM(BlueprintType)
 | 
				
			||||||
 | 
					enum class EDynamicMeshActorBooleanOperation : uint8
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Union,
 | 
				
			||||||
 | 
						Subtraction,
 | 
				
			||||||
 | 
						Intersection
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UENUM(BlueprintType)
 | 
				
			||||||
 | 
					enum class EDynamicMeshActorCollisionMode : uint8
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						NoCollision,
 | 
				
			||||||
 | 
						ComplexAsSimple,
 | 
				
			||||||
 | 
						ComplexAsSimpleAsync
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * ADynamicMeshBaseActor is a base class for Actors that support being
 | 
				
			||||||
 | 
					 * rebuilt in-game after mesh editing operations. The base Actor itself
 | 
				
			||||||
 | 
					 * does not have any Components, it should be used via one of the 
 | 
				
			||||||
 | 
					 * DynamicPMCActor, DynamicSMCActor, or DynamicSDMCActor subclasses.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ADynamicMeshBaseActor provides a FDynamicMesh3 "Source Mesh", which can
 | 
				
			||||||
 | 
					 * be modified via lambdas passed to the EditMesh() function, which will
 | 
				
			||||||
 | 
					 * then cause necessary updates to happen to the implementing Components.
 | 
				
			||||||
 | 
					 * An AABBTree and FastWindingTree can optionally be enabled with the
 | 
				
			||||||
 | 
					 * bEnableSpatialQueries and bEnableInsideQueries flags.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * When Spatial queries are enabled, a set of UFunctions DistanceToPoint(), 
 | 
				
			||||||
 | 
					 * NearestPoint(), ContainsPoint(), and IntersectRay() are available via Blueprints
 | 
				
			||||||
 | 
					 * on the relevant Actor. These functions *do not* depend on the UE4 Physics
 | 
				
			||||||
 | 
					 * system to work.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * A small set of mesh modification UFunctions are also available via Blueprints,
 | 
				
			||||||
 | 
					 * including BooleanWithMesh(), SolidifyMesh(), SimplifyMeshToTriCount(), and 
 | 
				
			||||||
 | 
					 * CopyFromMesh().
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Meshes can be read from OBJ files either using the ImportedMesh type for
 | 
				
			||||||
 | 
					 * the SourceType property, or by calling the ImportMesh() UFunction from a Blueprint.
 | 
				
			||||||
 | 
					 * Note that calling this in a Construction Script will be problematic in the Editor
 | 
				
			||||||
 | 
					 * as the OBJ will be re-read any time the Actor is modified (including translated/rotated).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Any Material set on the subclass Components will be overriden by the Material property.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UCLASS(Abstract)
 | 
				
			||||||
 | 
					class RUNTIMEGEOMETRYUTILS_API ADynamicMeshBaseActor : public AActor
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					public:	
 | 
				
			||||||
 | 
						// Sets default values for this actor's properties
 | 
				
			||||||
 | 
						ADynamicMeshBaseActor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/** Type of mesh used to initialize this Actor - either a generated mesh Primitive or an Imported OBJ file */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = MeshOptions)
 | 
				
			||||||
 | 
						EDynamicMeshActorSourceType SourceType = EDynamicMeshActorSourceType::Primitive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Type of normals computed for the Mesh */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = MeshOptions)
 | 
				
			||||||
 | 
						EDynamicMeshActorNormalsMode NormalsMode = EDynamicMeshActorNormalsMode::SplitNormals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Material assigned to child Components created by subclasses */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = MaterialOptions)
 | 
				
			||||||
 | 
						UMaterialInterface* Material;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** If true, mesh will be regenerated or re-imported on tick. Can be useful for prototyping procedural animation, but not the most efficient way to do it */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						bool bRegenerateOnTick = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Parameters for SourceType = Imported
 | 
				
			||||||
 | 
						// 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Path to OBJ file read to initialize mesh in SourceType=Imported mode */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
 | 
				
			||||||
 | 
						FString ImportPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Whether the imported mesh should have it's triangles reversed (commonly required for meshes authored in DCC tools) */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
 | 
				
			||||||
 | 
						bool bReverseOrientation = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** If true the imported mesh will be recentered around the origin */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
 | 
				
			||||||
 | 
						bool bCenterPivot = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Uniform scaling applied to the imported mesh (baked into the mesh vertices, not the actor Transform) */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
 | 
				
			||||||
 | 
						float ImportScale = 1.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Parameters for SourceType = Primitive
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Type of generated mesh primitive */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						EDynamicMeshActorPrimitiveType PrimitiveType = EDynamicMeshActorPrimitiveType::Box;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Triangle density of generated primitive */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, UIMax = 50, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						int TessellationLevel = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Radius of generated sphere / Width of generated box */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						float MinimumRadius = 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Multiplier on MinimumRadius used to define box depth (ie dimension on Z axis) */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0.01, UIMax = 1.0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive && PrimitiveType == EDynamicMeshActorPrimitiveType::Box", EditConditionHides))
 | 
				
			||||||
 | 
						float BoxDepthRatio = 1.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** A random value in range [-VariableRadius, VariableRadius] is added to MinimumRadius */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						float VariableRadius = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Speed of variation of VariableRadius, only has an effect when bRegenerateOnTick is true */
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
 | 
				
			||||||
 | 
						float PulseSpeed = 3.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// ADynamicMeshBaseActor API
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Call EditMesh() to safely modify the SourceMesh owned by this Actor.
 | 
				
			||||||
 | 
						 * Your EditFunc will be called with the Current SourceMesh as argument,
 | 
				
			||||||
 | 
						 * and you are expected to pass back the new/modified version.
 | 
				
			||||||
 | 
						 * (If you are generating an entirely new mesh, MoveTemp can be used to do this without a copy)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Get a copy of the current SourceMesh stored in MeshOut
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void GetMeshCopy(FDynamicMesh3& MeshOut);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Get a reference to the current SourceMesh
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual const FDynamicMesh3& GetMeshRef() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This delegate is broadcast whenever the internal SourceMesh is updated
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						DECLARE_MULTICAST_DELEGATE_OneParam(FOnMeshModified, ADynamicMeshBaseActor*);
 | 
				
			||||||
 | 
						FOnMeshModified OnMeshModified;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** The SourceMesh used to initialize the mesh Components in the various subclasses */
 | 
				
			||||||
 | 
						FDynamicMesh3 SourceMesh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Accumulated time since Actor was created, this is used for the animated primitives when bRegenerateOnTick = true*/
 | 
				
			||||||
 | 
						double AccumulatedTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Called whenever the initial Source mesh needs to be regenerated / re-imported. Calls EditMesh() to do so. */
 | 
				
			||||||
 | 
						virtual void OnMeshGenerationSettingsModified();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Called to generate or import a new source mesh. Override this to provide your own generated mesh. */
 | 
				
			||||||
 | 
						virtual void RegenerateSourceMesh(FDynamicMesh3& MeshOut);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Call this on a Mesh to compute normals according to the NormalsMode setting */
 | 
				
			||||||
 | 
						virtual void RecomputeNormals(FDynamicMesh3& MeshOut);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Support for AABBTree / Spatial Queries
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
 | 
				
			||||||
 | 
						bool bEnableSpatialQueries = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
 | 
				
			||||||
 | 
						bool bEnableInsideQueries = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// This AABBTree is updated each time SourceMesh is modified if bEnableSpatialQueries=true or bEnableInsideQueries=true
 | 
				
			||||||
 | 
						FDynamicMeshAABBTree3 MeshAABBTree;
 | 
				
			||||||
 | 
						// This FastWindingTree is updated each time SourceMesh is modified if bEnableInsideQueries=true
 | 
				
			||||||
 | 
						TUniquePtr<TFastWindingTree<FDynamicMesh3>> FastWinding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Support for Runtime-Generated Collision
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UPROPERTY(EditAnywhere, Category = RuntimeCollisionOptions)
 | 
				
			||||||
 | 
						EDynamicMeshActorCollisionMode CollisionMode = EDynamicMeshActorCollisionMode::NoCollision;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// ADynamicMeshBaseActor API that subclasses must implement.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Called when the SourceMesh has been modified. Subclasses override this function to
 | 
				
			||||||
 | 
						 * update their respective Component with the new SourceMesh.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void OnMeshEditedInternal();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Standard UE4 Actor Callbacks. If you need to override these functions,
 | 
				
			||||||
 | 
						// make sure to call (eg) Super::Tick() or you will break the mesh updating functionality.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// Called when the game starts or when spawned
 | 
				
			||||||
 | 
						virtual void BeginPlay() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void PostLoad() override;
 | 
				
			||||||
 | 
						virtual void PostActorCreated() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if WITH_EDITOR
 | 
				
			||||||
 | 
						// called when property is modified. This will call OnMeshGenerationSettingsModified() to update the mesh
 | 
				
			||||||
 | 
						virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:	
 | 
				
			||||||
 | 
						// Called every frame
 | 
				
			||||||
 | 
						virtual void Tick(float DeltaTime) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Blueprint API
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/** 
 | 
				
			||||||
 | 
						 * Update SourceMesh by reading external mesh file at Path. Optionally flip orientation and recompute normals. 
 | 
				
			||||||
 | 
						 * Note: Path may be relative to Content folder, otherwise it must be an absolute path.
 | 
				
			||||||
 | 
						 * @return false if mesh read failed
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						bool ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Copy the SourceMesh of OtherMesh into our SourceMesh, and optionally recompute normals */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Mesh Spatial Queries API
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Find NearestMeshWorldPoint on SourceMesh to WorldPoint, as well as NearestTriangle ID and barycentric coordinates of NearestMeshWorldPoint in triangle
 | 
				
			||||||
 | 
						 * @return distance to point
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						float DistanceToPoint(FVector WorldPoint, FVector& NearestMeshWorldPoint, int& NearestTriangle, FVector& TriBaryCoords);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return nearest world-space point on SourceMesh to WorldPoint
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						FVector NearestPoint(FVector WorldPoint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return true if mesh contains WorldPoint, which is defined as the mesh winding number being >= WindingThreshold
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						bool ContainsPoint(FVector WorldPoint, float WindingThreshold = 0.5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Calculate intersection of given 3D World-Space ray defined by (RayOrigin,RayDirection) with the SourceMesh.
 | 
				
			||||||
 | 
						 * If hit, returns WorldHitPoint position, distance along ray in HitDistance, NearestTriangle ID, and barycentric coordinates of hit point in triangle
 | 
				
			||||||
 | 
						 * Pass MaxDistance > 0 to limit the allowable ray-hit distance
 | 
				
			||||||
 | 
						 * @return true if hit is found
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						bool IntersectRay(FVector RayOrigin, FVector RayDirection, FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords, float MaxDistance = 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Mesh Modification API
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Compute the specified a Boolean operation with OtherMesh (transformed to world space) and store in our SourceMesh */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void BooleanWithMesh(ADynamicMeshBaseActor* OtherMesh, EDynamicMeshActorBooleanOperation Operation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Subtract OtherMesh from our SourceMesh */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void SubtractMesh(ADynamicMeshBaseActor* OtherMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Union OtherMesh with our SourceMesh */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void UnionWithMesh(ADynamicMeshBaseActor* OtherMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Intersect OtherMesh with our SourceMesh */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void IntersectWithMesh(ADynamicMeshBaseActor* OtherMesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Create a "solid" verison of SourceMesh by voxelizing with the fast winding number at the given grid resolution */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void SolidifyMesh(int VoxelResolution = 64, float WindingThreshold = 0.5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** Simplify current SourceMesh to the target triangle count */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						void SimplifyMeshToTriCount(int32 TargetTriangleCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						/** @return number of triangles in current SourceMesh */
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						int GetTriangleCount();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace RTGUtils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Read mesh in OBJ format from the given path into a FDynamicMesh3.
 | 
				
			||||||
 | 
						 * @param bNormals should normals be imported into primary normal attribute overlay
 | 
				
			||||||
 | 
						 * @param bTexCoords should texture coordinates be imported into primary UV attribute overlay
 | 
				
			||||||
 | 
						 * @param bVertexColors should normals be imported into per-vertex colors
 | 
				
			||||||
 | 
						 * @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for importing to UE4 from other apps.
 | 
				
			||||||
 | 
						 * @param return false if read failed
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						RUNTIMEGEOMETRYUTILS_API bool ReadOBJMesh(
 | 
				
			||||||
 | 
							const FString& Path,
 | 
				
			||||||
 | 
							FDynamicMesh3& MeshOut,
 | 
				
			||||||
 | 
							bool bNormals,
 | 
				
			||||||
 | 
							bool bTexCoords,
 | 
				
			||||||
 | 
							bool bVertexColors,
 | 
				
			||||||
 | 
							bool bReverseOrientation);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace RTGUtils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Write mesh to the given output path in OBJ format. 
 | 
				
			||||||
 | 
						 * @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
 | 
				
			||||||
 | 
						 * @param return false if write failed
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						RUNTIMEGEOMETRYUTILS_API bool WriteOBJMesh(
 | 
				
			||||||
 | 
							const FString& OutputPath,
 | 
				
			||||||
 | 
							const FDynamicMesh3& Mesh,
 | 
				
			||||||
 | 
							bool bReverseOrientation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Write set of meshes to the given output path in OBJ format.
 | 
				
			||||||
 | 
						 * @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
 | 
				
			||||||
 | 
						 * @param return false if write failed
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						RUNTIMEGEOMETRYUTILS_API bool WriteOBJMeshes(
 | 
				
			||||||
 | 
							const FString& OutputPath,
 | 
				
			||||||
 | 
							const TArray<FDynamicMesh3>& Meshes,
 | 
				
			||||||
 | 
							bool bReverseOrientation);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "GameFramework/Actor.h"
 | 
				
			||||||
 | 
					#include "ProceduralMeshComponent.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshBaseActor.h"
 | 
				
			||||||
 | 
					#include "DynamicPMCActor.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class RUNTIMEGEOMETRYUTILS_API ADynamicPMCActor : public ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					public:	
 | 
				
			||||||
 | 
						// Sets default values for this actor's properties
 | 
				
			||||||
 | 
						ADynamicPMCActor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UPROPERTY(VisibleAnywhere)
 | 
				
			||||||
 | 
						UProceduralMeshComponent* MeshComponent = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// Called when the game starts or when spawned
 | 
				
			||||||
 | 
						virtual void BeginPlay() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:	
 | 
				
			||||||
 | 
						// Called every frame
 | 
				
			||||||
 | 
						virtual void Tick(float DeltaTime) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * ADynamicBaseActor API
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void OnMeshEditedInternal() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						virtual void UpdatePMCMesh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "GameFramework/Actor.h"
 | 
				
			||||||
 | 
					#include "SimpleDynamicMeshComponent.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshBaseActor.h"
 | 
				
			||||||
 | 
					#include "DynamicSDMCActor.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FDynamicMesh3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class RUNTIMEGEOMETRYUTILS_API ADynamicSDMCActor : public ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// Sets default values for this actor's properties
 | 
				
			||||||
 | 
						ADynamicSDMCActor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UPROPERTY(VisibleAnywhere)
 | 
				
			||||||
 | 
						USimpleDynamicMeshComponent* MeshComponent = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// Called when the game starts or when spawned
 | 
				
			||||||
 | 
						virtual void BeginPlay() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// Called every frame
 | 
				
			||||||
 | 
						virtual void Tick(float DeltaTime) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * ADynamicBaseActor API
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void OnMeshEditedInternal() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						virtual void UpdateSDMCMesh();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "GameFramework/Actor.h"
 | 
				
			||||||
 | 
					#include "Engine/StaticMesh.h"
 | 
				
			||||||
 | 
					#include "Components/StaticMeshComponent.h"
 | 
				
			||||||
 | 
					#include "DynamicMeshBaseActor.h"
 | 
				
			||||||
 | 
					#include "DynamicSMCActor.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class RUNTIMEGEOMETRYUTILS_API ADynamicSMCActor : public ADynamicMeshBaseActor
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// Sets default values for this actor's properties
 | 
				
			||||||
 | 
						ADynamicSMCActor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UPROPERTY(VisibleAnywhere)
 | 
				
			||||||
 | 
						UStaticMeshComponent* MeshComponent = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UPROPERTY(Transient)
 | 
				
			||||||
 | 
						UStaticMesh* StaticMesh = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						// Called when the game starts or when spawned
 | 
				
			||||||
 | 
						virtual void BeginPlay() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						virtual void PostLoad() override;
 | 
				
			||||||
 | 
						virtual void PostActorCreated() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						// Called every frame
 | 
				
			||||||
 | 
						virtual void Tick(float DeltaTime) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * ADynamicBaseActor API
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						virtual void OnMeshEditedInternal() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						virtual void UpdateSMCMesh();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Engine/StaticMesh.h"
 | 
				
			||||||
 | 
					#include "ProceduralMeshComponent.h"
 | 
				
			||||||
 | 
					#include "DynamicMesh3.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace RTGUtils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Reinitialize the given StaticMesh with the input FDynamicMesh3.
 | 
				
			||||||
 | 
						 * This calls StaticMesh->BuildFromMeshDescriptions(), which can be used at Runtime (vs StaticMesh->Build() which cannot)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						RUNTIMEGEOMETRYUTILS_API void UpdateStaticMeshFromDynamicMesh(
 | 
				
			||||||
 | 
							UStaticMesh* StaticMesh,
 | 
				
			||||||
 | 
							const FDynamicMesh3* Mesh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initialize a ProceduralMeshComponent with a single section defined by the given FDynamicMesh3.
 | 
				
			||||||
 | 
						 * @param bUseFaceNormals if true, each triangle is shaded with per-triangle normal instead of split-vertex normals from FDynamicMesh3 overlay
 | 
				
			||||||
 | 
						 * @param bInitializeUV0 if true, UV0 is initialized, otherwise it is not (set to 0)
 | 
				
			||||||
 | 
						 * @param bInitializePerVertexColors if true, per-vertex colors on the FDynamicMesh3 are used to initialize vertex colors of the PMC
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						RUNTIMEGEOMETRYUTILS_API void UpdatePMCFromDynamicMesh_SplitTriangles(
 | 
				
			||||||
 | 
							UProceduralMeshComponent* Component, 
 | 
				
			||||||
 | 
							const FDynamicMesh3* Mesh,
 | 
				
			||||||
 | 
							bool bUseFaceNormals,
 | 
				
			||||||
 | 
							bool bInitializeUV0,
 | 
				
			||||||
 | 
							bool bInitializePerVertexColors,
 | 
				
			||||||
 | 
							bool bCreateCollision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Modules/ModuleManager.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FRuntimeGeometryUtilsModule : public IModuleInterface
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** IModuleInterface implementation */
 | 
				
			||||||
 | 
						virtual void StartupModule() override;
 | 
				
			||||||
 | 
						virtual void ShutdownModule() override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using UnrealBuildTool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class RuntimeGeometryUtils : ModuleRules
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public RuntimeGeometryUtils(ReadOnlyTargetRules Target) : base(Target)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PublicIncludePaths.AddRange(
 | 
				
			||||||
 | 
								new string[] {
 | 
				
			||||||
 | 
									// ... add public include paths required here ...
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrivateIncludePaths.AddRange(
 | 
				
			||||||
 | 
								new string[] {
 | 
				
			||||||
 | 
									// ... add other private include paths required here ...
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PublicDependencyModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"Core",
 | 
				
			||||||
 | 
									"GeometricObjects",
 | 
				
			||||||
 | 
									"DynamicMesh",
 | 
				
			||||||
 | 
									"ProceduralMeshComponent",
 | 
				
			||||||
 | 
									"ModelingComponents",
 | 
				
			||||||
 | 
									"RenderCore"
 | 
				
			||||||
 | 
									// ... add other public dependencies that you statically link with here ...
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrivateDependencyModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"CoreUObject",
 | 
				
			||||||
 | 
									"Engine",
 | 
				
			||||||
 | 
									"MeshDescription",
 | 
				
			||||||
 | 
									"StaticMeshDescription",
 | 
				
			||||||
 | 
									"GeometryAlgorithms",
 | 
				
			||||||
 | 
									"MeshConversion",
 | 
				
			||||||
 | 
									"MeshUtilities2"
 | 
				
			||||||
 | 
									// ... add private dependencies that you statically link with here ...
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							DynamicallyLoadedModuleNames.AddRange(
 | 
				
			||||||
 | 
								new string[]
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									// ... add any modules that your module loads dynamically here ...
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4594
									
								
								SMC_Build.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4594
									
								
								SMC_Build.sln
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										41
									
								
								SMC_Build.uproject
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								SMC_Build.uproject
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"FileVersion": 3,
 | 
				
			||||||
 | 
						"EngineAssociation": "{24A6842D-4991-98A9-1A25-0DBE60CE4641}",
 | 
				
			||||||
 | 
						"Category": "",
 | 
				
			||||||
 | 
						"Description": "",
 | 
				
			||||||
 | 
						"Modules": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "SMC_Build",
 | 
				
			||||||
 | 
								"Type": "Runtime",
 | 
				
			||||||
 | 
								"LoadingPhase": "Default",
 | 
				
			||||||
 | 
								"AdditionalDependencies": [
 | 
				
			||||||
 | 
									"Engine"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						"Plugins": [
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "MeshUtilities2",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "ModelingToolsEditorMode",
 | 
				
			||||||
 | 
								"Enabled": false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "MeshModelingToolset",
 | 
				
			||||||
 | 
								"Enabled": false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "RuntimeGeometryUtils",
 | 
				
			||||||
 | 
								"Enabled": true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Name": "Bridge",
 | 
				
			||||||
 | 
								"Enabled": false,
 | 
				
			||||||
 | 
								"SupportedTargetPlatforms": [
 | 
				
			||||||
 | 
									"Win64"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								Source/SMC_Build.Target.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Source/SMC_Build.Target.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using UnrealBuildTool;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SMC_BuildTarget : TargetRules
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public SMC_BuildTarget( TargetInfo Target) : base(Target)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Type = TargetType.Game;
 | 
				
			||||||
 | 
							DefaultBuildSettings = BuildSettingsVersion.Latest;
 | 
				
			||||||
 | 
							ExtraModuleNames.AddRange( new string[] { "SMC_Build" } );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								Source/SMC_Build/MyBlueprintFunctionLibrary.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Source/SMC_Build/MyBlueprintFunctionLibrary.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MyBlueprintFunctionLibrary.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void UMyBlueprintFunctionLibrary::MarkRenderStateDirty(UStaticMeshComponent* Comp)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Comp->MarkRenderStateDirty();
 | 
				
			||||||
 | 
						Comp->GetScene()->UpdatePrimitiveDistanceFieldSceneData_GameThread(Comp);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void UMyBlueprintFunctionLibrary::MarkRenderDynamicDataDirty(UStaticMeshComponent* Comp)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Comp->MarkRenderDynamicDataDirty();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								Source/SMC_Build/MyBlueprintFunctionLibrary.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Source/SMC_Build/MyBlueprintFunctionLibrary.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					#include "Kismet/BlueprintFunctionLibrary.h"
 | 
				
			||||||
 | 
					#include "MyBlueprintFunctionLibrary.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class SMC_BUILD_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						static void MarkRenderStateDirty(UStaticMeshComponent* Comp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UFUNCTION(BlueprintCallable)
 | 
				
			||||||
 | 
						static void MarkRenderDynamicDataDirty(UStaticMeshComponent* Comp);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										15
									
								
								Source/SMC_Build/SMCGameInstance.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Source/SMC_Build/SMCGameInstance.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "SMCGameInstance.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #include "IMeshReductionManagerModule.h"
 | 
				
			||||||
 | 
					// #include "MeshUtilities.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void USMCGameInstance::Shutdown()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void USMCGameInstance::OnStart()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								Source/SMC_Build/SMCGameInstance.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Source/SMC_Build/SMCGameInstance.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					// Fill out your copyright notice in the Description page of Project Settings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "SMCGameInstance.generated.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					UCLASS()
 | 
				
			||||||
 | 
					class SMC_BUILD_API USMCGameInstance : public UGameInstance
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						GENERATED_BODY()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						virtual void Shutdown() override;
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						virtual void OnStart() override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										27
									
								
								Source/SMC_Build/SMC_Build.Build.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Source/SMC_Build/SMC_Build.Build.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using UnrealBuildTool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SMC_Build : ModuleRules
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public SMC_Build(ReadOnlyTargetRules Target) : base(Target)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PublicDependencyModuleNames.AddRange(new string[]
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"Core", "CoreUObject", "Engine", "InputCore",
 | 
				
			||||||
 | 
								"DynamicMesh",
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							PrivateDependencyModuleNames.AddRange(new string[] { });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Uncomment if you are using Slate UI
 | 
				
			||||||
 | 
							// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Uncomment if you are using online features
 | 
				
			||||||
 | 
							// PrivateDependencyModuleNames.Add("OnlineSubsystem");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								Source/SMC_Build/SMC_Build.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Source/SMC_Build/SMC_Build.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "SMC_Build.h"
 | 
				
			||||||
 | 
					#include "Modules/ModuleManager.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, SMC_Build, "SMC_Build" );
 | 
				
			||||||
							
								
								
									
										6
									
								
								Source/SMC_Build/SMC_Build.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Source/SMC_Build/SMC_Build.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "CoreMinimal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								Source/SMC_BuildEditor.Target.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Source/SMC_BuildEditor.Target.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					// Copyright Epic Games, Inc. All Rights Reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using UnrealBuildTool;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SMC_BuildEditorTarget : TargetRules
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public SMC_BuildEditorTarget( TargetInfo Target) : base(Target)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Type = TargetType.Editor;
 | 
				
			||||||
 | 
							DefaultBuildSettings = BuildSettingsVersion.Latest;
 | 
				
			||||||
 | 
							ExtraModuleNames.AddRange( new string[] { "SMC_Build" } );
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user